Bug 1001090 - Part 1: Implement let temporal dead zone in the frontend and interpreter. (r=Waldo)
authorShu-yu Guo <shu@rfrn.org>
Mon, 15 Sep 2014 16:30:45 -0700
changeset 205365 7027efe7fae3
parent 205364 170ce237e4a0
child 205366 03242a11d044
push id49165
push usershu@rfrn.org
push date2014-09-15 23:31 +0000
treeherdermozilla-inbound@fada58fb0996 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1001090
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1001090 - Part 1: Implement let temporal dead zone in the frontend and interpreter. (r=Waldo)
js/public/Value.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/frontend/SyntaxParseHandler.h
js/src/jit-test/tests/arguments/alias-function-closed.js
js/src/jit-test/tests/arguments/alias-function-not-closed.js
js/src/jit-test/tests/basic/bug1001090-1.js
js/src/jit-test/tests/basic/bug1001090-2.js
js/src/jit-test/tests/basic/bug1001090-3.js
js/src/jit-test/tests/basic/bug1001090-4.js
js/src/jit-test/tests/basic/bug1001090-5.js
js/src/jit-test/tests/basic/bug1001090-6.js
js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
js/src/jit-test/tests/basic/letTDZAfterInitializer.js
js/src/jit-test/tests/basic/testFunctionStatementAliasLocals.js
js/src/jit-test/tests/basic/testLet.js
js/src/jit-test/tests/debug/Environment-variables.js
js/src/jit-test/tests/debug/Frame-eval-21.js
js/src/jit-test/tests/jaeger/bug583672.js
js/src/jit-test/tests/parallel/write-array.js
js/src/jit-test/tests/parallel/write-obj.js
js/src/jit/BaselineFrame.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/CompileInfo.h
js/src/jit/IonCaches.cpp
js/src/js.msg
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/tests/js1_7/block/order-of-operation.js
js/src/tests/js1_7/block/regress-411279.js
js/src/tests/js1_8_1/extensions/regress-353214-02.js
js/src/tests/js1_8_1/regress/regress-452498-103.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/Opcodes.h
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -237,16 +237,18 @@ typedef enum JSWhyMagic
     JS_OPTIMIZED_ARGUMENTS,      /* optimized-away 'arguments' value */
     JS_IS_CONSTRUCTING,          /* magic value passed to natives to indicate construction */
     JS_OVERWRITTEN_CALLEE,       /* arguments.callee has been overwritten */
     JS_BLOCK_NEEDS_CLONE,        /* value of static block object slot */
     JS_HASH_KEY_EMPTY,           /* see class js::HashableValue */
     JS_ION_ERROR,                /* error while running Ion code */
     JS_ION_BAILOUT,              /* missing recover instruction result */
     JS_OPTIMIZED_OUT,            /* optimized out slot */
+    JS_UNINITIALIZED_LEXICAL,    /* uninitialized lexical bindings that produce ReferenceError
+                                  * on touch. */
     JS_GENERIC_MAGIC             /* for local use */
 } JSWhyMagic;
 
 #if defined(IS_LITTLE_ENDIAN)
 # if defined(JS_NUNBOX32)
 typedef union jsval_layout
 {
     uint64_t asBits;
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -286,17 +286,18 @@ frontend::CompileScript(ExclusiveContext
     // We can specialize a bit for the given scope chain if that scope chain is the global object.
     JSObject *globalScope =
         scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : nullptr;
     JS_ASSERT_IF(globalScope, globalScope->isNative());
     JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass()));
 
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
-    BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, options.forEval,
+    BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
+                        /* lazyScript = */ js::NullPtr(), options.forEval,
                         evalCaller, !!globalScope, options.lineno, emitterMode);
     if (!bce.init())
         return nullptr;
 
     // Syntax parsing may cause us to restart processing of top level
     // statements in the script. Use Maybe<> so that the parse context can be
     // reset when this occurs.
     Maybe<ParseContext<FullParseHandler> > pc;
@@ -431,19 +432,21 @@ frontend::CompileScript(ExclusiveContext
     if (Emit1(cx, &bce, JSOP_RETRVAL) < 0)
         return nullptr;
 
     // Global/eval script bindings are always empty (all names are added to the
     // scope dynamically via JSOP_DEFFUN/VAR).  They may have block-scoped
     // locals, however, which are allocated to the fixed part of the stack
     // frame.
     InternalHandle<Bindings*> bindings(script, &script->bindings);
-    if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr,
-                                            pc->blockScopeDepth))
+    if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, 0,
+                                            pc->blockScopeDepth, nullptr))
+    {
         return nullptr;
+    }
 
     if (!JSScript::fullyInitFromEmitter(cx, script, &bce))
         return nullptr;
 
     // Note that this marking must happen before we tell Debugger
     // about the new script, in case Debugger delazifies the script's
     // inner functions.
     if (options.forEval)
@@ -504,25 +507,23 @@ frontend::CompileLazyFunction(JSContext 
 
     if (lazy->directlyInsideEval())
         script->setDirectlyInsideEval();
     if (lazy->usesArgumentsAndApply())
         script->setUsesArgumentsAndApply();
     if (lazy->hasBeenCloned())
         script->setHasBeenCloned();
 
-    BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, options.forEval,
-                        /* evalCaller = */ NullPtr(), /* hasGlobalScope = */ true,
-                        options.lineno, BytecodeEmitter::LazyFunction);
+    BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
+                        options.forEval, /* evalCaller = */ js::NullPtr(),
+                        /* hasGlobalScope = */ true, options.lineno,
+                        BytecodeEmitter::LazyFunction);
     if (!bce.init())
         return false;
 
-    if (lazy->treatAsRunOnce())
-        bce.lazyRunOnceLambda = true;
-
     return EmitFunctionScript(cx, &bce, pn->pn_body);
 }
 
 // Compile a JS function body, which might appear as the value of an event
 // handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
 static bool
 CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyCompileOptions &options,
                     const AutoNameVector &formals, SourceBufferHolder &srcBuf,
@@ -627,17 +628,18 @@ CompileFunctionBody(JSContext *cx, Mutab
         /*
          * The reason for checking fun->environment() below is that certain
          * consumers of JS::CompileFunction, namely
          * EventListenerManager::CompileEventHandlerInternal, passes in a
          * nullptr environment. This compiled function is never used, but
          * instead is cloned immediately onto the right scope chain.
          */
         BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script,
-                               /* insideEval = */ false, /* evalCaller = */ js::NullPtr(),
+                               /* lazyScript = */ js::NullPtr(), /* insideEval = */ false,
+                               /* evalCaller = */ js::NullPtr(),
                                fun->environment() && fun->environment()->is<GlobalObject>(),
                                options.lineno);
         if (!funbce.init())
             return false;
 
         if (!EmitFunctionScript(cx, &funbce, fn->pn_body))
             return false;
     } else {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -26,16 +26,17 @@
 #include "jsscript.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "asmjs/AsmJSLink.h"
 #include "frontend/Parser.h"
 #include "frontend/TokenStream.h"
 #include "vm/Debugger.h"
+#include "vm/Stack.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "frontend/ParseMaps-inl.h"
 #include "frontend/ParseNode-inl.h"
 #include "vm/ScopeObject-inl.h"
@@ -105,21 +106,23 @@ struct LoopStmtInfo : public StmtInfoBCE
         return static_cast<LoopStmtInfo*>(stmt);
     }
 };
 
 } // anonymous namespace
 
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent,
                                  Parser<FullParseHandler> *parser, SharedContext *sc,
-                                 HandleScript script, bool insideEval, HandleScript evalCaller,
+                                 HandleScript script, Handle<LazyScript *> lazyScript,
+                                 bool insideEval, HandleScript evalCaller,
                                  bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode)
   : sc(sc),
     parent(parent),
     script(sc->context, script),
+    lazyScript(sc->context, lazyScript),
     prolog(sc->context, lineNum),
     main(sc->context, lineNum),
     current(&main),
     parser(parser),
     evalCaller(evalCaller),
     topStmt(nullptr),
     topScopeStmt(nullptr),
     staticScope(sc->context),
@@ -130,22 +133,22 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
     emitLevel(0),
     constList(sc->context),
     tryNoteList(sc->context),
     blockScopeList(sc->context),
     typesetCount(0),
     hasSingletons(false),
     emittingForInit(false),
     emittingRunOnceLambda(false),
-    lazyRunOnceLambda(false),
     insideEval(insideEval),
     hasGlobalScope(hasGlobalScope),
     emitterMode(emitterMode)
 {
     JS_ASSERT_IF(evalCaller, insideEval);
+    JS_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
 }
 
 bool
 BytecodeEmitter::init()
 {
     return atomIndices.ensureMap(sc->context);
 }
 
@@ -218,17 +221,17 @@ frontend::Emit2(ExclusiveContext *cx, By
     code[0] = jsbytecode(op);
     code[1] = op1;
     UpdateDepth(cx, bce, offset);
     return offset;
 }
 
 ptrdiff_t
 frontend::Emit3(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, jsbytecode op1,
-                    jsbytecode op2)
+                jsbytecode op2)
 {
     /* These should filter through EmitVarOp. */
     JS_ASSERT(!IsArgOp(op));
     JS_ASSERT(!IsLocalOp(op));
 
     ptrdiff_t offset = EmitCheck(cx, bce, 3);
     if (offset < 0)
         return -1;
@@ -794,32 +797,32 @@ EmitInternedObjectOp(ExclusiveContext *c
 
 // In a function, block-scoped locals go after the vars, and form part of the
 // fixed part of a stack frame.  Outside a function, there are no fixed vars,
 // but block-scoped locals still form part of the fixed part of a stack frame
 // and are thus addressable via GETLOCAL and friends.
 static void
 ComputeLocalOffset(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBlockObject *> blockObj)
 {
-    unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0;
-    unsigned localOffset = nfixedvars;
+    unsigned nbodyfixed = bce->sc->isFunctionBox() ? bce->script->bindings.numBodyLevelLocals() : 0;
+    unsigned localOffset = nbodyfixed;
 
     if (bce->staticScope) {
         Rooted<NestedScopeObject *> outer(cx, bce->staticScope);
         for (; outer; outer = outer->enclosingNestedScope()) {
             if (outer->is<StaticBlockObject>()) {
                 StaticBlockObject &outerBlock = outer->as<StaticBlockObject>();
                 localOffset = outerBlock.localOffset() + outerBlock.numVariables();
                 break;
             }
         }
     }
 
     JS_ASSERT(localOffset + blockObj->numVariables()
-              <= nfixedvars + bce->script->bindings.numBlockScoped());
+              <= nbodyfixed + bce->script->bindings.numBlockScoped());
 
     blockObj->setLocalOffset(localOffset);
 }
 
 // ~ Nested Scopes ~
 //
 // A nested scope is a region of a compilation unit (function, script, or eval
 // code) with an additional node on the scope chain.  This node may either be a
@@ -1056,40 +1059,56 @@ EmitRegExp(ExclusiveContext *cx, uint32_
 /*
  * To catch accidental misuse, EMIT_UINT16_IMM_OP/Emit3 assert that they are
  * not used to unconditionally emit JSOP_GETLOCAL. Variable access should
  * instead be emitted using EmitVarOp. In special cases, when the caller
  * definitely knows that a given local slot is unaliased, this function may be
  * used as a non-asserting version of EMIT_UINT16_IMM_OP.
  */
 static bool
-EmitUnaliasedVarOp(ExclusiveContext *cx, JSOp op, uint32_t slot, BytecodeEmitter *bce)
+EmitLocalOp(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint32_t slot)
+{
+    JS_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD);
+    JS_ASSERT(IsLocalOp(op));
+
+    ptrdiff_t off = EmitN(cx, bce, op, LOCALNO_LEN);
+    if (off < 0)
+        return false;
+
+    SET_LOCALNO(bce->code(off), slot);
+    return true;
+}
+
+static bool
+EmitUnaliasedVarOp(ExclusiveContext *cx, JSOp op, uint32_t slot, MaybeCheckLexical checkLexical,
+                   BytecodeEmitter *bce)
 {
     JS_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD);
 
     if (IsLocalOp(op)) {
-        ptrdiff_t off = EmitN(cx, bce, op, LOCALNO_LEN);
-        if (off < 0)
-            return false;
-
-        SET_LOCALNO(bce->code(off), slot);
-        return true;
+        if (checkLexical) {
+            MOZ_ASSERT(op != JSOP_INITLEXICAL);
+            if (!EmitLocalOp(cx, bce, JSOP_CHECKLEXICAL, slot))
+                return false;
+        }
+
+        return EmitLocalOp(cx, bce, op, slot);
     }
 
     JS_ASSERT(IsArgOp(op));
     ptrdiff_t off = EmitN(cx, bce, op, ARGNO_LEN);
     if (off < 0)
         return false;
 
     SET_ARGNO(bce->code(off), slot);
     return true;
 }
 
 static bool
-EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ScopeCoordinate sc, BytecodeEmitter *bce)
+EmitScopeCoordOp(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, ScopeCoordinate sc)
 {
     JS_ASSERT(JOF_OPTYPE(op) == JOF_SCOPECOORD);
 
     unsigned n = SCOPECOORD_HOPS_LEN + SCOPECOORD_SLOT_LEN;
     JS_ASSERT(int(n) + 1 /* op */ == js_CodeSpec[op].length);
 
     ptrdiff_t off = EmitN(cx, bce, op, n);
     if (off < 0)
@@ -1099,55 +1118,98 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
     SET_SCOPECOORD_HOPS(pc, sc.hops());
     pc += SCOPECOORD_HOPS_LEN;
     SET_SCOPECOORD_SLOT(pc, sc.slot());
     pc += SCOPECOORD_SLOT_LEN;
     CheckTypeSet(cx, bce, op);
     return true;
 }
 
+static bool
+EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical,
+                 BytecodeEmitter *bce)
+{
+    if (checkLexical) {
+        MOZ_ASSERT(op != JSOP_INITALIASEDLEXICAL);
+        if (!EmitScopeCoordOp(cx, bce, JSOP_CHECKALIASEDLEXICAL, sc))
+            return false;
+    }
+
+    return EmitScopeCoordOp(cx, bce, op, sc);
+}
+
 // Compute the number of nested scope objects that will actually be on the scope
 // chain at runtime, given the BCE's current staticScope.
 static unsigned
 DynamicNestedScopeDepth(BytecodeEmitter *bce)
 {
     unsigned depth = 0;
     for (NestedScopeObject *b = bce->staticScope; b; b = b->enclosingNestedScope()) {
         if (!b->is<StaticBlockObject>() || b->as<StaticBlockObject>().needsClone())
             ++depth;
     }
 
     return depth;
 }
 
 static bool
-LookupAliasedName(HandleScript script, PropertyName *name, uint32_t *pslot)
-{
+LookupAliasedName(BytecodeEmitter *bce, HandleScript script, PropertyName *name, uint32_t *pslot,
+                  ParseNode *pn = nullptr)
+{
+    LazyScript::FreeVariable *freeVariables = nullptr;
+    uint32_t lexicalBegin = 0;
+    uint32_t numFreeVariables = 0;
+    if (bce->emitterMode == BytecodeEmitter::LazyFunction) {
+        freeVariables = bce->lazyScript->freeVariables();
+        lexicalBegin = script->bindings.lexicalBegin();
+        numFreeVariables = bce->lazyScript->numFreeVariables();
+    }
+
     /*
      * Beware: BindingIter may contain more than one Binding for a given name
      * (in the case of |function f(x,x) {}|) but only one will be aliased.
      */
+    uint32_t bindingIndex = 0;
     uint32_t slot = CallObject::RESERVED_SLOTS;
     for (BindingIter bi(script); !bi.done(); bi++) {
         if (bi->aliased()) {
             if (bi->name() == name) {
+                // Check if the free variable from a lazy script was marked as
+                // a possible hoisted use and is a lexical binding. If so,
+                // mark it as such so we emit a dead zone check.
+                if (freeVariables) {
+                    for (uint32_t i = 0; i < numFreeVariables; i++) {
+                        if (freeVariables[i].atom() == name) {
+                            if (freeVariables[i].isHoistedUse() && bindingIndex >= lexicalBegin) {
+                                MOZ_ASSERT(pn);
+                                MOZ_ASSERT(pn->isUsed());
+                                pn->pn_dflags |= PND_LET;
+                            }
+
+                            break;
+                        }
+                    }
+                }
+
                 *pslot = slot;
                 return true;
             }
             slot++;
         }
+        bindingIndex++;
     }
     return false;
 }
 
 static bool
-LookupAliasedNameSlot(HandleScript script, PropertyName *name, ScopeCoordinate *sc)
+LookupAliasedNameSlot(BytecodeEmitter *bce, HandleScript script, PropertyName *name,
+                      ScopeCoordinate *sc)
 {
     uint32_t slot;
-    if (!LookupAliasedName(script, name, &slot))
+    if (!LookupAliasedName(bce, script, name, &slot))
         return false;
 
     sc->setSlot(slot);
     return true;
 }
 
 /*
  * Use this function instead of assigning directly to 'hops' to guard for
@@ -1160,16 +1222,22 @@ AssignHops(BytecodeEmitter *bce, ParseNo
         bce->reportError(pn, JSMSG_TOO_DEEP, js_function_str);
         return false;
     }
 
     dst->setHops(src);
     return true;
 }
 
+static inline MaybeCheckLexical
+NodeNeedsCheckLexical(ParseNode *pn)
+{
+    return pn->isHoistedLetUse() ? CheckLexical : DontCheckLexical;
+}
+
 static bool
 EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *bce)
 {
     /*
      * While pn->pn_cookie tells us how many function scopes are between the use and the def this
      * is not the same as how many hops up the dynamic scope chain are needed. In particular:
      *  - a lexical function scope only contributes a hop if it is "heavyweight" (has a dynamic
      *    scope object).
@@ -1206,67 +1274,68 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
      * and so includes the full DynamicNestedScopeDepth. A let/catch-binding
      * requires a search of the block chain to see how many (dynamic) block
      * objects to skip.
      */
     ScopeCoordinate sc;
     if (IsArgOp(pn->getOp())) {
         if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
             return false;
-        JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
+        JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef, bceOfDef->script, pn->name(), &sc));
     } else {
         JS_ASSERT(IsLocalOp(pn->getOp()) || pn->isKind(PNK_FUNCTION));
         uint32_t local = pn->pn_cookie.slot();
-        if (local < bceOfDef->script->bindings.numVars()) {
+        if (local < bceOfDef->script->bindings.numBodyLevelLocals()) {
             if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
                 return false;
-            JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
+            JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef, bceOfDef->script, pn->name(), &sc));
         } else {
             JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals());
             JS_ASSERT(bceOfDef->staticScope->is<StaticBlockObject>());
             Rooted<StaticBlockObject*> b(cx, &bceOfDef->staticScope->as<StaticBlockObject>());
             while (local < b->localOffset()) {
                 if (b->needsClone())
                     skippedScopes++;
                 b = &b->enclosingNestedScope()->as<StaticBlockObject>();
             }
             if (!AssignHops(bce, pn, skippedScopes, &sc))
                 return false;
             sc.setSlot(b->localIndexToSlot(local));
         }
     }
 
-    return EmitAliasedVarOp(cx, op, sc, bce);
+    return EmitAliasedVarOp(cx, op, sc, NodeNeedsCheckLexical(pn), bce);
 }
 
 static bool
 EmitVarOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
 {
     JS_ASSERT(pn->isKind(PNK_FUNCTION) || pn->isKind(PNK_NAME));
     JS_ASSERT(!pn->pn_cookie.isFree());
 
     if (IsAliasedVarOp(op)) {
         ScopeCoordinate sc;
         sc.setHops(pn->pn_cookie.level());
         sc.setSlot(pn->pn_cookie.slot());
-        return EmitAliasedVarOp(cx, op, sc, bce);
+        return EmitAliasedVarOp(cx, op, sc, NodeNeedsCheckLexical(pn), bce);
     }
 
     JS_ASSERT_IF(pn->isKind(PNK_NAME), IsArgOp(op) || IsLocalOp(op));
 
     if (!bce->isAliasedName(pn)) {
         JS_ASSERT(pn->isUsed() || pn->isDefn());
         JS_ASSERT_IF(pn->isUsed(), pn->pn_cookie.level() == 0);
         JS_ASSERT_IF(pn->isDefn(), pn->pn_cookie.level() == bce->script->staticLevel());
-        return EmitUnaliasedVarOp(cx, op, pn->pn_cookie.slot(), bce);
+        return EmitUnaliasedVarOp(cx, op, pn->pn_cookie.slot(), NodeNeedsCheckLexical(pn), bce);
     }
 
     switch (op) {
       case JSOP_GETARG: case JSOP_GETLOCAL: op = JSOP_GETALIASEDVAR; break;
       case JSOP_SETARG: case JSOP_SETLOCAL: op = JSOP_SETALIASEDVAR; break;
+      case JSOP_INITLEXICAL: op = JSOP_INITALIASEDLEXICAL; break;
       default: MOZ_CRASH("unexpected var op");
     }
 
     return EmitAliasedVarOp(cx, op, pn, bce);
 }
 
 static JSOp
 GetIncDecInfo(ParseNodeKind kind, bool *post)
@@ -1430,23 +1499,24 @@ TryConvertFreeName(BytecodeEmitter *bce,
                     hops++;
                 continue;
             }
             RootedScript script(bce->sc->context, ssi.funScript());
             if (script->functionNonDelazifying()->atom() == pn->pn_atom)
                 return false;
             if (ssi.hasDynamicScopeObject()) {
                 uint32_t slot;
-                if (LookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot)) {
+                if (LookupAliasedName(bce, script, pn->pn_atom->asPropertyName(), &slot, pn)) {
                     JSOp op;
                     switch (pn->getOp()) {
-                      case JSOP_NAME:     op = JSOP_GETALIASEDVAR; break;
-                      case JSOP_SETNAME:  op = JSOP_SETALIASEDVAR; break;
+                      case JSOP_NAME:    op = JSOP_GETALIASEDVAR; break;
+                      case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
                       default: return false;
                     }
+
                     pn->setOp(op);
                     JS_ALWAYS_TRUE(pn->pn_cookie.set(bce->parser->tokenStream, hops, slot));
                     return true;
                 }
                 hops++;
             }
 
             if (script->funHasExtensibleScope() || script->directlyInsideEval())
@@ -2459,54 +2529,58 @@ EmitNumberOp(ExclusiveContext *cx, doubl
 
 static inline void
 SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off)
 {
     SET_JUMP_OFFSET(bce->code(off), bce->offset() - off);
 }
 
 static bool
-PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n)
-{
+PushInitialConstants(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, unsigned n)
+{
+    MOZ_ASSERT(op == JSOP_UNDEFINED || op == JSOP_UNINITIALIZED);
     for (unsigned i = 0; i < n; ++i) {
-        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+        if (Emit1(cx, bce, op) < 0)
             return false;
     }
     return true;
 }
 
 static bool
 InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce,
                                      Handle<StaticBlockObject *> blockObj)
 {
     for (unsigned i = blockObj->numVariables(); i > 0; --i) {
         if (blockObj->isAliased(i - 1)) {
             ScopeCoordinate sc;
             sc.setHops(0);
             sc.setSlot(BlockObject::RESERVED_SLOTS + i - 1);
-            if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce))
+            if (!EmitAliasedVarOp(cx, JSOP_INITALIASEDLEXICAL, sc, DontCheckLexical, bce))
                 return false;
         } else {
             unsigned local = blockObj->blockIndexToLocalIndex(i - 1);
-            if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, local, bce))
+            if (!EmitUnaliasedVarOp(cx, JSOP_INITLEXICAL, local, DontCheckLexical, bce))
                 return false;
         }
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
     }
     return true;
 }
 
 static bool
 EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmtInfo,
-                ObjectBox *objbox, unsigned alreadyPushed = 0)
-{
-    // Initial values for block-scoped locals.
+                ObjectBox *objbox, JSOp initialValueOp, unsigned alreadyPushed = 0)
+{
+    // Initial values for block-scoped locals. Whether it is undefined or the
+    // JS_UNINITIALIZED_LEXICAL magic value depends on the context. The
+    // current way we emit for-in and for-of heads means its let bindings will
+    // always be initialized, so we can initialize them to undefined.
     Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
-    if (!PushUndefinedValues(cx, bce, blockObj->numVariables() - alreadyPushed))
+    if (!PushInitialConstants(cx, bce, initialValueOp, blockObj->numVariables() - alreadyPushed))
         return false;
 
     if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK))
         return false;
 
     if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj))
         return false;
 
@@ -2539,17 +2613,17 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
     JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST));
 
     /* Push the discriminant. */
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
 
     StmtInfoBCE stmtInfo(cx);
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
-        if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 0))
+        if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, JSOP_UNINITIALIZED, 0))
             return false;
 
         stmtInfo.type = STMT_SWITCH;
         stmtInfo.update = top = bce->offset();
         /* Advance pn2 to refer to the switch case list. */
         pn2 = pn2->expr();
     } else {
         JS_ASSERT(pn2->isKind(PNK_STATEMENTLIST));
@@ -2842,18 +2916,21 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
 
 bool
 BytecodeEmitter::isRunOnceLambda()
 {
     // The run once lambda flags set by the parser are approximate, and we look
     // at properties of the function itself before deciding to emit a function
     // as a run once lambda.
 
-    if (!(parent && parent->emittingRunOnceLambda) && !lazyRunOnceLambda)
-        return false;
+    if (!(parent && parent->emittingRunOnceLambda) &&
+        (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce()))
+    {
+        return false;
+    }
 
     FunctionBox *funbox = sc->asFunctionBox();
     return !funbox->argumentsHasLocalBinding() &&
            !funbox->isGenerator() &&
            !funbox->function()->name();
 }
 
 bool
@@ -2872,21 +2949,21 @@ frontend::EmitFunctionScript(ExclusiveCo
         bce->switchToProlog();
         if (Emit1(cx, bce, JSOP_ARGUMENTS) < 0)
             return false;
         InternalBindingsHandle bindings(bce->script, &bce->script->bindings);
         uint32_t varIndex = Bindings::argumentsVarIndex(cx, bindings);
         if (bce->script->varIsAliased(varIndex)) {
             ScopeCoordinate sc;
             sc.setHops(0);
-            JS_ALWAYS_TRUE(LookupAliasedNameSlot(bce->script, cx->names().arguments, &sc));
-            if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce))
+            JS_ALWAYS_TRUE(LookupAliasedNameSlot(bce, bce->script, cx->names().arguments, &sc));
+            if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, DontCheckLexical, bce))
                 return false;
         } else {
-            if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, varIndex, bce))
+            if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, varIndex, DontCheckLexical, bce))
                 return false;
         }
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         bce->switchToMain();
     }
 
     if (funbox->isGenerator()) {
@@ -3002,63 +3079,97 @@ enum VarEmitOption
     DefineVars        = 0,
     PushInitialValues = 1,
     InitializeVars    = 2
 };
 
 typedef bool
 (*DestructuringDeclEmitter)(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn);
 
+template <DestructuringDeclEmitter EmitName>
 static bool
-EmitDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn)
-{
-    JS_ASSERT(pn->isKind(PNK_NAME));
-    if (!BindNameToSlot(cx, bce, pn))
-        return false;
-
-    JS_ASSERT(!pn->isOp(JSOP_CALLEE));
-    return MaybeEmitVarDecl(cx, bce, prologOp, pn, nullptr);
-}
-
-static bool
-EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
-                       ParseNode *pattern)
+EmitDestructuringDeclsWithEmitter(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
+                                  ParseNode *pattern)
 {
     if (pattern->isKind(PNK_ARRAY)) {
         for (ParseNode *element = pattern->pn_head; element; element = element->pn_next) {
             if (element->isKind(PNK_ELISION))
                 continue;
             ParseNode *target = element;
             if (element->isKind(PNK_SPREAD)) {
                 JS_ASSERT(element->pn_kid->isKind(PNK_NAME));
                 target = element->pn_kid;
             }
-            DestructuringDeclEmitter emitter =
-                target->isKind(PNK_NAME) ? EmitDestructuringDecl : EmitDestructuringDecls;
-            if (!emitter(cx, bce, prologOp, target))
-                return false;
+            if (target->isKind(PNK_NAME)) {
+                if (!EmitName(cx, bce, prologOp, target))
+                    return false;
+            } else {
+                if (!EmitDestructuringDeclsWithEmitter<EmitName>(cx, bce, prologOp, target))
+                    return false;
+            }
         }
         return true;
     }
 
     MOZ_ASSERT(pattern->isKind(PNK_OBJECT));
     for (ParseNode *member = pattern->pn_head; member; member = member->pn_next) {
         MOZ_ASSERT(member->isKind(PNK_MUTATEPROTO) ||
                    member->isKind(PNK_COLON) ||
                    member->isKind(PNK_SHORTHAND));
 
         ParseNode *target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right;
-        DestructuringDeclEmitter emitter =
-            target->isKind(PNK_NAME) ? EmitDestructuringDecl : EmitDestructuringDecls;
-        if (!emitter(cx, bce, prologOp, target))
-            return false;
+
+        if (target->isKind(PNK_NAME)) {
+            if (!EmitName(cx, bce, prologOp, target))
+                return false;
+        } else {
+            if (!EmitDestructuringDeclsWithEmitter<EmitName>(cx, bce, prologOp, target))
+                return false;
+        }
     }
     return true;
 }
 
+bool
+EmitDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn)
+{
+    JS_ASSERT(pn->isKind(PNK_NAME));
+    if (!BindNameToSlot(cx, bce, pn))
+        return false;
+
+    JS_ASSERT(!pn->isOp(JSOP_CALLEE));
+    return MaybeEmitVarDecl(cx, bce, prologOp, pn, nullptr);
+}
+
+static inline bool
+EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
+                       ParseNode *pattern)
+{
+    return EmitDestructuringDeclsWithEmitter<EmitDestructuringDecl>(cx, bce, prologOp, pattern);
+}
+
+bool
+EmitInitializeDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
+                                ParseNode *pn)
+{
+    MOZ_ASSERT(pn->isKind(PNK_NAME));
+    MOZ_ASSERT(pn->isBound());
+    return EmitVarOp(cx, pn, pn->getOp(), bce);
+}
+
+// Emit code to initialize all destructured names to the value on the top of
+// the stack.
+static inline bool
+EmitInitializeDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
+                                 ParseNode *pattern)
+{
+    return EmitDestructuringDeclsWithEmitter<EmitInitializeDestructuringDecl>(cx, bce,
+                                                                              prologOp, pattern);
+}
+
 static bool
 EmitDestructuringOpsHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pattern,
                            VarEmitOption emitOption);
 
 /*
  * EmitDestructuringLHS assumes the to-be-destructured value has been pushed on
  * the stack and emits code to destructure a single lhs expression (either a
  * name or a compound []/{} expression).
@@ -3088,17 +3199,17 @@ EmitDestructuringLHS(ExclusiveContext *c
             // Per its post-condition, EmitDestructuringOpsHelper has left the
             // to-be-destructured value on top of the stack.
             if (Emit1(cx, bce, JSOP_POP) < 0)
                 return false;
         }
     } else if (emitOption == PushInitialValues) {
         // The lhs is a simple name so the to-be-destructured value is
         // its initial value and there is nothing to do.
-        JS_ASSERT(pn->getOp() == JSOP_SETLOCAL);
+        JS_ASSERT(pn->getOp() == JSOP_SETLOCAL || pn->getOp() == JSOP_INITLEXICAL);
         JS_ASSERT(pn->pn_dflags & PND_BOUND);
     } else {
         switch (pn->getKind()) {
           case PNK_NAME:
             if (!BindNameToSlot(cx, bce, pn))
                 return false;
 
             // Allow 'const [x,y] = o', make 'const x,y; [x,y] = o' a nop.
@@ -3133,16 +3244,17 @@ EmitDestructuringLHS(ExclusiveContext *c
 
                 if (!EmitIndexOp(cx, pn->getOp(), atomIndex, bce))
                     return false;
                 break;
               }
 
               case JSOP_SETLOCAL:
               case JSOP_SETARG:
+              case JSOP_INITLEXICAL:
                 if (!EmitVarOp(cx, pn, pn->getOp(), bce))
                     return false;
                 break;
 
               default:
                 MOZ_CRASH("EmitDestructuringLHS: bad name op");
             }
             break;
@@ -3529,42 +3641,59 @@ EmitTemplateString(ExclusiveContext *cx,
         }
 
     }
     return true;
 }
 
 static bool
 EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption,
-              bool isLet = false)
+              bool isLetExpr = false)
 {
     JS_ASSERT(pn->isArity(PN_LIST));
-    JS_ASSERT(isLet == (emitOption == PushInitialValues));
+    JS_ASSERT(isLetExpr == (emitOption == PushInitialValues));
 
     ParseNode *next;
     for (ParseNode *pn2 = pn->pn_head; ; pn2 = next) {
         if (!UpdateSourceCoordNotes(cx, bce, pn2->pn_pos.begin))
             return false;
         next = pn2->pn_next;
 
         ParseNode *pn3;
         if (!pn2->isKind(PNK_NAME)) {
             if (pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT)) {
-                /*
-                 * Emit variable binding ops, but not destructuring ops.  The
-                 * parser (see Parser::variables) has ensured that our caller
-                 * will be the PNK_FOR/PNK_FORIN/PNK_FOROF case in EmitTree, and
-                 * that case will emit the destructuring code only after
-                 * emitting an enumerating opcode and a branch that tests
-                 * whether the enumeration ended.
-                 */
-                JS_ASSERT(emitOption == DefineVars);
+                // If the emit option is DefineVars, emit variable binding
+                // ops, but not destructuring ops.  The parser (see
+                // Parser::variables) has ensured that our caller will be the
+                // PNK_FOR/PNK_FORIN/PNK_FOROF case in EmitTree (we don't have
+                // to worry about this being a variable declaration, as
+                // destructuring declarations without initializers, e.g., |var
+                // [x]|, are not legal syntax), and that case will emit the
+                // destructuring code only after emitting an enumerating
+                // opcode and a branch that tests whether the enumeration
+                // ended. Thus, each iteration's assignment is responsible for
+                // initializing, and nothing needs to be done here.
+                //
+                // Otherwise this is emitting destructuring let binding
+                // initialization for a legacy comprehension expression. See
+                // EmitForInOrOfVariables.
                 JS_ASSERT(pn->pn_count == 1);
-                if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2))
-                    return false;
+                if (emitOption == DefineVars) {
+                    if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2))
+                        return false;
+                } else {
+                    // Lexical bindings cannot be used before they are
+                    // initialized. Similar to the JSOP_INITLEXICAL case below.
+                    MOZ_ASSERT(emitOption != DefineVars);
+                    MOZ_ASSERT_IF(emitOption == InitializeVars, pn->pn_xflags & PNX_POPVAR);
+                    if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+                        return false;
+                    if (!EmitInitializeDestructuringDecls(cx, bce, pn->getOp(), pn2))
+                        return false;
+                }
                 break;
             }
 
             /*
              * A destructuring initialiser assignment preceded by var will
              * never occur to the left of 'in' in a for-in loop.  As with 'for
              * (var x = i in o)...', this will cause the entire 'var [a, b] =
              * i' to be hoisted out of the loop.
@@ -3586,17 +3715,17 @@ EmitVariables(ExclusiveContext *cx, Byte
 
             pn3 = pn2->pn_left;
             if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn3))
                 return false;
 
             if (!EmitTree(cx, bce, pn2->pn_right))
                 return false;
 
-            if (!EmitDestructuringOps(cx, bce, pn3, isLet))
+            if (!EmitDestructuringOps(cx, bce, pn3, isLetExpr))
                 return false;
 
             /* If we are not initializing, nothing to pop. */
             if (emitOption != InitializeVars) {
                 if (next)
                     continue;
                 break;
             }
@@ -3640,23 +3769,27 @@ EmitVariables(ExclusiveContext *cx, Byte
                     return false;
             }
 
             bool oldEmittingForInit = bce->emittingForInit;
             bce->emittingForInit = false;
             if (!EmitTree(cx, bce, pn3))
                 return false;
             bce->emittingForInit = oldEmittingForInit;
-        } else if (isLet) {
-            /* JSOP_ENTERLETx expects at least 1 slot to have been pushed. */
+        } else if (op == JSOP_INITLEXICAL || isLetExpr) {
+            // 'let' bindings cannot be used before they are
+            // initialized. JSOP_INITLEXICAL distinguishes the binding site.
+            MOZ_ASSERT(emitOption != DefineVars);
+            MOZ_ASSERT_IF(emitOption == InitializeVars, pn->pn_xflags & PNX_POPVAR);
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return false;
         }
 
-        /* If we are not initializing, nothing to pop. */
+        // If we are not initializing, nothing to pop. If we are initializing
+        // lets, we must emit the pops.
         if (emitOption != InitializeVars) {
             if (next)
                 continue;
             break;
         }
 
         JS_ASSERT_IF(pn2->isDefn(), pn3 == pn2->pn_expr);
         if (!pn2->pn_cookie.isFree()) {
@@ -4104,17 +4237,17 @@ EmitCatch(ExclusiveContext *cx, Bytecode
             return false;
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         break;
 
       case PNK_NAME:
         /* Inline and specialize BindNameToSlot for pn2. */
         JS_ASSERT(!pn2->pn_cookie.isFree());
-        if (!EmitVarOp(cx, pn2, JSOP_SETLOCAL, bce))
+        if (!EmitVarOp(cx, pn2, JSOP_INITLEXICAL, bce))
             return false;
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         break;
 
       default:
         JS_ASSERT(0);
     }
@@ -4458,19 +4591,19 @@ EmitLet(ExclusiveContext *cx, BytecodeEm
     JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE));
 
     int letHeadDepth = bce->stackDepth;
 
     if (!EmitVariables(cx, bce, varList, PushInitialValues, true))
         return false;
 
     /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */
-    uint32_t alreadyPushed = bce->stackDepth - letHeadDepth;
+    uint32_t valuesPushed = bce->stackDepth - letHeadDepth;
     StmtInfoBCE stmtInfo(cx);
-    if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed))
+    if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, JSOP_UNINITIALIZED, valuesPushed))
         return false;
 
     if (!EmitTree(cx, bce, letBody->pn_expr))
         return false;
 
     if (!LeaveNestedScope(cx, bce, &stmtInfo))
         return false;
 
@@ -4482,17 +4615,17 @@ EmitLet(ExclusiveContext *cx, BytecodeEm
  * the comment on EmitSwitch.
  */
 MOZ_NEVER_INLINE static bool
 EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
 
     StmtInfoBCE stmtInfo(cx);
-    if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0))
+    if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, JSOP_UNINITIALIZED, 0))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_expr))
         return false;
 
     if (!LeaveNestedScope(cx, bce, &stmtInfo))
         return false;
 
@@ -4529,16 +4662,49 @@ EmitIterator(ExclusiveContext *cx, Bytec
     if (Emit1(cx, bce, JSOP_SWAP) < 0)                         // @@ITERATOR OBJ
         return false;
     if (EmitCall(cx, bce, JSOP_CALL, 0) < 0)                   // ITER
         return false;
     CheckTypeSet(cx, bce, JSOP_CALL);
     return true;
 }
 
+static bool
+EmitForInOrOfVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool *letDecl)
+{
+    *letDecl = pn->isKind(PNK_LEXICALSCOPE);
+    MOZ_ASSERT_IF(*letDecl, pn->isLet());
+
+    // If the left part is 'var x', emit code to define x if necessary using a
+    // prolog opcode, but do not emit a pop. If it is 'let x', EnterBlockScope
+    // will initialize let bindings in EmitForOf and EmitForIn with
+    // undefineds.
+    //
+    // Due to the horror of legacy comprehensions, there is a third case where
+    // we have PNK_LET without a lexical scope, because those expressions are
+    // parsed with single lexical scope for the entire comprehension. In this
+    // case we must initialize the lets to not trigger dead zone checks via
+    // InitializeVars.
+    if (!*letDecl) {
+        bce->emittingForInit = true;
+        if (pn->isKind(PNK_VAR)) {
+            if (!EmitVariables(cx, bce, pn, DefineVars))
+                return false;
+        } else {
+            MOZ_ASSERT(pn->isKind(PNK_LET));
+            if (!EmitVariables(cx, bce, pn, InitializeVars))
+                return false;
+        }
+        bce->emittingForInit = false;
+    }
+
+    return true;
+}
+
+
 /**
  * If type is STMT_FOR_OF_LOOP, it emits bytecode for for-of loop.
  * pn should be PNK_FOR, and pn->pn_left should be PNK_FOROF.
  *
  * If type is STMT_SPREAD, it emits bytecode for spread operator.
  * pn should be nullptr.
  * Please refer the comment above EmitSpread for additional information about
  * stack convention.
@@ -4549,29 +4715,19 @@ EmitForOf(ExclusiveContext *cx, Bytecode
     JS_ASSERT(type == STMT_FOR_OF_LOOP || type == STMT_SPREAD);
     JS_ASSERT_IF(type == STMT_FOR_OF_LOOP, pn && pn->pn_left->isKind(PNK_FOROF));
     JS_ASSERT_IF(type == STMT_SPREAD, !pn);
 
     ParseNode *forHead = pn ? pn->pn_left : nullptr;
     ParseNode *forBody = pn ? pn->pn_right : nullptr;
 
     ParseNode *pn1 = forHead ? forHead->pn_kid1 : nullptr;
-    bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
-    JS_ASSERT_IF(letDecl, pn1->isLet());
-
-    // If the left part is 'var x', emit code to define x if necessary using a
-    // prolog opcode, but do not emit a pop.
-    if (pn1) {
-        ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
-        JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
-        bce->emittingForInit = true;
-        if (!EmitVariables(cx, bce, decl, DefineVars))
-            return false;
-        bce->emittingForInit = false;
-    }
+    bool letDecl = false;
+    if (pn1 && !EmitForInOrOfVariables(cx, bce, pn1, &letDecl))
+        return false;
 
     if (type == STMT_FOR_OF_LOOP) {
         // For-of loops run with two values on the stack: the iterator and the
         // current result object.
 
         // Compile the object expression to the right of 'of'.
         if (!EmitTree(cx, bce, forHead->pn_kid3))
             return false;
@@ -4579,19 +4735,21 @@ EmitForOf(ExclusiveContext *cx, Bytecode
             return false;
 
         // Push a dummy result so that we properly enter iteration midstream.
         if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)                // ITER RESULT
             return false;
     }
 
     // Enter the block before the loop body, after evaluating the obj.
+    // Initialize let bindings with undefined when entering, as the name
+    // assigned to is a plain assignment.
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, JSOP_UNDEFINED, 0))
             return false;
     }
 
     LoopStmtInfo stmtInfo(cx);
     PushLoopStatement(bce, &stmtInfo, type, top);
 
     // Jump down to the loop condition to minimize overhead assuming at least
     // one iteration, as the other loop forms do.  Annotate so IonMonkey can
@@ -4703,51 +4861,39 @@ EmitForOf(ExclusiveContext *cx, Bytecode
 
 static bool
 EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
     ParseNode *forHead = pn->pn_left;
     ParseNode *forBody = pn->pn_right;
 
     ParseNode *pn1 = forHead->pn_kid1;
-    bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
-    JS_ASSERT_IF(letDecl, pn1->isLet());
-
-    /*
-     * If the left part is 'var x', emit code to define x if necessary
-     * using a prolog opcode, but do not emit a pop. If the left part was
-     * originally 'var x = i', the parser will have rewritten it; see
-     * Parser::forStatement. 'for (let x = i in o)' is mercifully banned.
-     */
-    if (pn1) {
-        ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
-        JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
-        bce->emittingForInit = true;
-        if (!EmitVariables(cx, bce, decl, DefineVars))
-            return false;
-        bce->emittingForInit = false;
-    }
+    bool letDecl = false;
+    if (pn1 && !EmitForInOrOfVariables(cx, bce, pn1, &letDecl))
+        return false;
 
     /* Compile the object expression to the right of 'in'. */
     if (!EmitTree(cx, bce, forHead->pn_kid3))
         return false;
 
     /*
      * Emit a bytecode to convert top of stack value to the iterator
      * object depending on the loop variant (for-in, for-each-in, or
      * destructuring for-in).
      */
     JS_ASSERT(pn->isOp(JSOP_ITER));
     if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0)
         return false;
 
-    /* Enter the block before the loop body, after evaluating the obj. */
+    // Enter the block before the loop body, after evaluating the obj.
+    // Initialize let bindings with undefined when entering, as the name
+    // assigned to is a plain assignment.
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, JSOP_UNDEFINED, 0))
             return false;
     }
 
     LoopStmtInfo stmtInfo(cx);
     PushLoopStatement(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
 
     /* Annotate so IonMonkey can find the loop-closing jump. */
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN);
@@ -5037,18 +5183,18 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
                                                           sourceObject,
                                                           funbox->bufStart, funbox->bufEnd));
             if (!script)
                 return false;
 
             script->bindings = funbox->bindings;
 
             uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
-            BytecodeEmitter bce2(bce, bce->parser, funbox, script, bce->insideEval,
-                                 bce->evalCaller, bce->hasGlobalScope, lineNum,
+            BytecodeEmitter bce2(bce, bce->parser, funbox, script, /* lazyScript = */ js::NullPtr(),
+                                 bce->insideEval, bce->evalCaller, bce->hasGlobalScope, lineNum,
                                  bce->emitterMode);
             if (!bce2.init())
                 return false;
 
             /* We measured the max scope depth when we parsed the function. */
             if (!EmitFunctionScript(cx, &bce2, pn->pn_body))
                 return false;
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -81,16 +81,19 @@ struct BytecodeEmitter
     typedef StmtInfoBCE StmtInfo;
 
     SharedContext   *const sc;      /* context shared between parsing and bytecode generation */
 
     BytecodeEmitter *const parent;  /* enclosing function or global context */
 
     Rooted<JSScript*> script;       /* the JSScript we're ultimately producing */
 
+    Rooted<LazyScript *> lazyScript; /* the lazy script if mode is LazyFunction,
+                                        nullptr otherwise. */
+
     struct EmitSection {
         BytecodeVector code;        /* bytecode */
         SrcNotesVector notes;       /* source notes, see below */
         ptrdiff_t   lastNoteOffset; /* code offset for last source note */
         uint32_t    currentLine;    /* line number for tree-based srcnote gen */
         uint32_t    lastColumn;     /* zero-based column index on currentLine of
                                        last SRC_COLSPAN-annotated opcode */
 
@@ -131,18 +134,16 @@ struct BytecodeEmitter
     uint16_t        typesetCount;   /* Number of JOF_TYPESET opcodes generated */
 
     bool            hasSingletons:1;    /* script contains singleton initializer JSOP_OBJECT */
 
     bool            emittingForInit:1;  /* true while emitting init expr of for; exclude 'in' */
 
     bool            emittingRunOnceLambda:1; /* true while emitting a lambda which is only
                                                 expected to run once. */
-    bool            lazyRunOnceLambda:1; /* true while lazily emitting a script for
-                                          * a lambda which is only expected to run once. */
 
     bool isRunOnceLambda();
 
     bool            insideEval:1;       /* True if compiling an eval-expression or a function
                                            nested inside an eval. */
 
     const bool      hasGlobalScope:1;   /* frontend::CompileScript's scope chain is the
                                            global object */
@@ -168,18 +169,19 @@ struct BytecodeEmitter
 
     /*
      * Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
      * space above their tempMark points. This means that you cannot alloc from
      * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter
      * destruction.
      */
     BytecodeEmitter(BytecodeEmitter *parent, Parser<FullParseHandler> *parser, SharedContext *sc,
-                    HandleScript script, bool insideEval, HandleScript evalCaller,
-                    bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode = Normal);
+                    HandleScript script, Handle<LazyScript *> lazyScript,
+                    bool insideEval, HandleScript evalCaller, bool hasGlobalScope,
+                    uint32_t lineNum, EmitterMode emitterMode = Normal);
     bool init();
 
     bool isAliasedName(ParseNode *pn);
 
     MOZ_ALWAYS_INLINE
     bool makeAtomIndex(JSAtom *atom, jsatomid *indexp) {
         AtomIndexAddPtr p = atomIndices->lookupForAdd(atom);
         if (p) {
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -592,16 +592,19 @@ class FullParseHandler
     inline ParseNode *makeAssignment(ParseNode *pn, ParseNode *rhs);
 
     static Definition *getDefinitionNode(Definition *dn) {
         return dn;
     }
     static Definition::Kind getDefinitionKind(Definition *dn) {
         return dn->kind();
     }
+    static bool isPlaceholderDefinition(Definition *dn) {
+        return dn->isPlaceholder();
+    }
     void linkUseToDef(ParseNode *pn, Definition *dn)
     {
         JS_ASSERT(!pn->isUsed());
         JS_ASSERT(!pn->isDefn());
         JS_ASSERT(pn != dn->dn_uses);
         JS_ASSERT(dn->isDefn());
         pn->pn_link = dn->dn_uses;
         dn->dn_uses = pn;
@@ -619,16 +622,23 @@ class FullParseHandler
             JS_ASSERT(!pnu->isDefn());
             if (pnu->pn_pos.begin >= pos.begin && pnu->pn_pos.end <= pos.end)
                 pnu->pn_dflags |= PND_DEOPTIMIZED;
         }
     }
     bool dependencyCovered(ParseNode *pn, unsigned blockid, bool functionScope) {
         return pn->pn_blockid >= blockid;
     }
+    void markMaybeUninitializedLexicalUseInSwitch(ParseNode *pn, Definition *dn,
+                                                  uint16_t firstDominatingLexicalSlot)
+    {
+        MOZ_ASSERT(pn->isUsed());
+        if (dn->isLet() && dn->pn_cookie.slot() < firstDominatingLexicalSlot)
+            pn->pn_dflags |= PND_LET;
+    }
 
     static uintptr_t definitionToBits(Definition *dn) {
         return uintptr_t(dn);
     }
     static Definition *definitionFromBits(uintptr_t bits) {
         return (Definition *) bits;
     }
     static Definition *nullDefinition() {
@@ -706,21 +716,24 @@ FullParseHandler::finishInitializerAssig
     if (pn->isUsed()) {
         pn = makeAssignment(pn, init);
         if (!pn)
             return false;
     } else {
         pn->pn_expr = init;
     }
 
-    pn->setOp((pn->pn_dflags & PND_BOUND)
-              ? JSOP_SETLOCAL
-              : (op == JSOP_DEFCONST)
-              ? JSOP_SETCONST
-              : JSOP_SETNAME);
+    if (op == JSOP_INITLEXICAL)
+        pn->setOp(op);
+    else if (pn->pn_dflags & PND_BOUND)
+        pn->setOp(JSOP_SETLOCAL);
+    else if (op == JSOP_DEFCONST)
+        pn->setOp(JSOP_SETCONST);
+    else
+        pn->setOp(JSOP_SETNAME);
 
     pn->markAsAssigned();
 
     /* The declarator's position must include the initializer. */
     pn->pn_pos.end = init->pn_pos.end;
     return true;
 }
 
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -402,16 +402,17 @@ Parser<FullParseHandler>::cloneParseTree
         if (opn->isUsed()) {
             /*
              * The old name is a use of its pn_lexdef. Make the clone also be a
              * use of that definition.
              */
             Definition *dn = pn->pn_lexdef;
 
             pn->pn_link = dn->dn_uses;
+            pn->pn_dflags = opn->pn_dflags;
             dn->dn_uses = pn;
         } else if (opn->pn_expr) {
             NULLCHECK(pn->pn_expr = cloneParseTree(opn->pn_expr));
 
             /*
              * If the old name is a definition, the new one has pn_defn set.
              * Make the old name a use of the new node.
              */
@@ -508,17 +509,17 @@ Parser<FullParseHandler>::cloneLeftHandS
 
         pn->pn_link = dn->dn_uses;
         dn->dn_uses = pn;
     } else {
         pn->pn_expr = nullptr;
         if (opn->isDefn()) {
             /* We copied some definition-specific state into pn. Clear it out. */
             pn->pn_cookie.makeFree();
-            pn->pn_dflags &= ~PND_BOUND;
+            pn->pn_dflags &= ~(PND_LET | PND_BOUND);
             pn->setDefn(false);
 
             handler.linkUseToDef(pn, (Definition *) opn);
         }
     }
     return pn;
 }
 
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -659,17 +659,17 @@ class ParseNode
     }
 
     ParseNode  *maybeExpr()   { return pn_used ? nullptr : expr(); }
     Definition *maybeLexDef() { return pn_used ? lexdef() : nullptr; }
 
     Definition *resolve();
 
 /* PN_CODE and PN_NAME pn_dflags bits. */
-#define PND_LET                 0x01    /* let (block-scoped) binding */
+#define PND_LET                 0x01    /* let (block-scoped) binding or use of a hoisted let */
 #define PND_CONST               0x02    /* const binding (orthogonal to let) */
 #define PND_ASSIGNED            0x04    /* set if ever LHS of assignment */
 #define PND_PLACEHOLDER         0x08    /* placeholder definition for lexdep */
 #define PND_BOUND               0x10    /* bound to a stack or global slot */
 #define PND_DEOPTIMIZED         0x20    /* former pn_used name node, pn_lexdef
                                            still valid, but this use no longer
                                            optimizable via an upvar opcode */
 #define PND_CLOSED              0x40    /* variable is closed over */
@@ -741,24 +741,25 @@ class ParseNode
             if (kid && kid->getKind() == PNK_STRING && !kid->pn_parens)
                 return kid->pn_atom;
         }
         return nullptr;
     }
 
     inline bool test(unsigned flag) const;
 
-    bool isLet() const          { return test(PND_LET); }
+    bool isLet() const          { return test(PND_LET) && !isUsed(); }
     bool isConst() const        { return test(PND_CONST); }
     bool isPlaceholder() const  { return test(PND_PLACEHOLDER); }
     bool isDeoptimized() const  { return test(PND_DEOPTIMIZED); }
     bool isAssigned() const     { return test(PND_ASSIGNED); }
     bool isClosed() const       { return test(PND_CLOSED); }
     bool isBound() const        { return test(PND_BOUND); }
     bool isImplicitArguments() const { return test(PND_IMPLICITARGUMENTS); }
+    bool isHoistedLetUse() const { return test(PND_LET) && isUsed(); }
 
     /* True if pn is a parsenode representing a literal constant. */
     bool isLiteral() const {
         return isKind(PNK_NUMBER) ||
                isKind(PNK_STRING) ||
                isKind(PNK_TRUE) ||
                isKind(PNK_FALSE) ||
                isKind(PNK_NULL);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -91,16 +91,75 @@ GenerateBlockId(TokenStream &ts, ParseCo
 template <typename ParseHandler>
 static void
 PushStatementPC(ParseContext<ParseHandler> *pc, StmtInfoPC *stmt, StmtType type)
 {
     stmt->blockid = pc->blockid();
     PushStatement(pc, stmt, type);
 }
 
+template <>
+bool
+ParseContext<FullParseHandler>::checkLocalsOverflow(TokenStream &ts)
+{
+    if (vars_.length() + bodyLevelLexicals_.length() >= LOCALNO_LIMIT) {
+        ts.reportError(JSMSG_TOO_MANY_LOCALS);
+        return false;
+    }
+    return true;
+}
+
+static void
+MarkUsesAsHoistedLexical(ParseNode *pn)
+{
+    MOZ_ASSERT(pn->isDefn());
+
+    Definition *dn = (Definition *)pn;
+    ParseNode **pnup = &dn->dn_uses;
+    ParseNode *pnu;
+    unsigned start = pn->pn_blockid;
+
+    // In ES6, lexical bindings cannot be accessed until initialized.
+    // Distinguish hoisted uses as a different JSOp for easier compilation.
+    while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) {
+        MOZ_ASSERT(pnu->isUsed());
+
+        // JavaScript is parsed in dominator order. This condition says to
+        // mark uses which either:
+        //
+        // 1) Is at the same dominator level.
+        //
+        //    This covers the case where the right hand side of declarations
+        //    cannot refer to the binding itself. e.g, |let x = x| is a
+        //    ReferenceError. Note that the use of 'x' follows the definition
+        //    node 'let x' in the program text.
+        //
+        // 2) Precedes the definition in the program text.
+        //
+        //    This covers all hoisted uses.
+        //
+        // The uses that are not covered by these two conditions are uses of
+        // the binding as free variables in function definitions on the right
+        // hand side of the binding node. e.g.,
+        //
+        //     let x = function () { x(); }
+        //
+        // does not mark the upvar use of 'x' inside the lambda as needing a
+        // TDZ check.
+        //
+        // N.B. This function expects to be called at the point of defining
+        // the lexical binding, and should not be called afterwards, as it
+        // would erroneously unmark hoisted function definitions and needing
+        // TDZ checks, which is currently handled in leaveFunction.
+        if (pnu->pn_blockid == start || pnu->pn_pos < pn->pn_pos)
+            pnu->pn_dflags |= PND_LET;
+        pnup = &pnu->pn_link;
+    }
+}
+
 // See comment on member function declaration.
 template <>
 bool
 ParseContext<FullParseHandler>::define(TokenStream &ts,
                                        HandlePropertyName name, ParseNode *pn, Definition::Kind kind)
 {
     JS_ASSERT(!pn->isUsed());
     JS_ASSERT_IF(pn->isDefn(), pn->isPlaceholder());
@@ -145,16 +204,17 @@ ParseContext<FullParseHandler>::define(T
     if (kind == Definition::CONST)
         pn->pn_dflags |= PND_CONST;
 
     Definition *dn = (Definition *)pn;
     switch (kind) {
       case Definition::ARG:
         JS_ASSERT(sc->isFunctionBox());
         dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETARG : JSOP_GETARG);
+        dn->pn_blockid = bodyid;
         dn->pn_dflags |= PND_BOUND;
         if (!dn->pn_cookie.set(ts, staticLevel, args_.length()))
             return false;
         if (!args_.append(dn))
             return false;
         if (args_.length() >= ARGNO_LIMIT) {
             ts.reportError(JSMSG_TOO_MANY_FUN_ARGS);
             return false;
@@ -164,47 +224,65 @@ ParseContext<FullParseHandler>::define(T
         if (!decls_.addUnique(name, dn))
             return false;
         break;
 
       case Definition::CONST:
       case Definition::VAR:
         if (sc->isFunctionBox()) {
             dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
+            dn->pn_blockid = bodyid;
             dn->pn_dflags |= PND_BOUND;
             if (!dn->pn_cookie.set(ts, staticLevel, vars_.length()))
                 return false;
             if (!vars_.append(dn))
                 return false;
-            if (vars_.length() >= LOCALNO_LIMIT) {
-                ts.reportError(JSMSG_TOO_MANY_LOCALS);
+            if (!checkLocalsOverflow(ts))
                 return false;
-            }
         }
         if (!decls_.addUnique(name, dn))
             return false;
         break;
 
       case Definition::LET:
-        dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
+        dn->setOp(JSOP_INITLEXICAL);
         dn->pn_dflags |= (PND_LET | PND_BOUND);
         JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */
+        if (atBodyLevel()) {
+            if (!bodyLevelLexicals_.append(dn))
+                return false;
+            if (!checkLocalsOverflow(ts))
+                return false;
+        }
+
+        // In ES6, lexical bindings cannot be accessed until initialized. If
+        // the definition has existing uses, they need to be marked so that we
+        // emit dead zone checks.
+        MarkUsesAsHoistedLexical(pn);
+
         if (!decls_.addShadow(name, dn))
             return false;
         break;
 
       default:
         MOZ_CRASH("unexpected kind");
     }
 
     return true;
 }
 
 template <>
 bool
+ParseContext<SyntaxParseHandler>::checkLocalsOverflow(TokenStream &ts)
+{
+    return true;
+}
+
+template <>
+bool
 ParseContext<SyntaxParseHandler>::define(TokenStream &ts, HandlePropertyName name, Node pn,
                                          Definition::Kind kind)
 {
     JS_ASSERT(!decls_.lookupFirst(name));
 
     if (lexdeps.lookupDefn<SyntaxParseHandler>(name))
         lexdeps->remove(name);
 
@@ -273,16 +351,21 @@ static void
 AppendPackedBindings(const ParseContext<ParseHandler> *pc, const DeclVector &vec, Binding *dst)
 {
     for (size_t i = 0; i < vec.length(); ++i, ++dst) {
         Definition *dn = vec[i];
         PropertyName *name = dn->name();
 
         Binding::Kind kind;
         switch (dn->kind()) {
+          case Definition::LET:
+            // Treat body-level let declarations as var bindings by falling
+            // through. The fact that the binding is in fact a let declaration
+            // is reflected in the slot. All body-level lets go after the
+            // vars.
           case Definition::VAR:
             kind = Binding::VARIABLE;
             break;
           case Definition::CONST:
             kind = Binding::CONSTANT;
             break;
           case Definition::ARG:
             kind = Binding::ARGUMENT;
@@ -308,37 +391,48 @@ AppendPackedBindings(const ParseContext<
 template <typename ParseHandler>
 bool
 ParseContext<ParseHandler>::generateFunctionBindings(ExclusiveContext *cx, TokenStream &ts,
                                                      LifoAlloc &alloc,
                                                      InternalHandle<Bindings*> bindings) const
 {
     JS_ASSERT(sc->isFunctionBox());
     JS_ASSERT(args_.length() < ARGNO_LIMIT);
-    JS_ASSERT(vars_.length() < LOCALNO_LIMIT);
+    JS_ASSERT(vars_.length() + bodyLevelLexicals_.length() < LOCALNO_LIMIT);
 
     /*
      * Avoid pathological edge cases by explicitly limiting the total number of
      * bindings to what will fit in a uint32_t.
      */
-    if (UINT32_MAX - args_.length() <= vars_.length())
+    if (UINT32_MAX - args_.length() <= vars_.length() + bodyLevelLexicals_.length())
         return ts.reportError(JSMSG_TOO_MANY_LOCALS);
 
-    uint32_t count = args_.length() + vars_.length();
+    // Fix up the slots of body-level lets to come after the vars now that we
+    // know how many vars there are.
+    for (size_t i = 0; i < bodyLevelLexicals_.length(); i++) {
+        Definition *dn = bodyLevelLexicals_[i];
+        if (!dn->pn_cookie.set(ts, dn->pn_cookie.level(), vars_.length() + i))
+            return false;
+    }
+
+    uint32_t count = args_.length() + vars_.length() + bodyLevelLexicals_.length();
     Binding *packedBindings = alloc.newArrayUninitialized<Binding>(count);
     if (!packedBindings) {
         js_ReportOutOfMemory(cx);
         return false;
     }
 
     AppendPackedBindings(this, args_, packedBindings);
     AppendPackedBindings(this, vars_, packedBindings + args_.length());
+    AppendPackedBindings(this, bodyLevelLexicals_,
+                         packedBindings + args_.length() + vars_.length());
 
     return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(),
-                                              packedBindings, blockScopeDepth);
+                                              bodyLevelLexicals_.length(), blockScopeDepth,
+                                              packedBindings);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
                                    unsigned errorNumber, va_list args)
 {
     bool result = false;
@@ -808,17 +902,20 @@ Parser<FullParseHandler>::checkFunctionA
     }
 
     /*
      * Report error if both rest parameters and 'arguments' are used. Do this
      * check before adding artificial 'arguments' below.
      */
     Definition *maybeArgDef = pc->decls().lookupFirst(arguments);
     bool argumentsHasBinding = !!maybeArgDef;
-    bool argumentsHasLocalBinding = maybeArgDef && maybeArgDef->kind() != Definition::ARG;
+    // ES6 9.2.13.17 says that a lexical binding of 'arguments' shadows the
+    // arguments object.
+    bool argumentsHasLocalBinding = maybeArgDef && (maybeArgDef->kind() != Definition::ARG &&
+                                                    maybeArgDef->kind() != Definition::LET);
     bool hasRest = pc->sc->asFunctionBox()->function()->hasRest();
     if (hasRest && argumentsHasLocalBinding) {
         report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST);
         return false;
     }
 
     /*
      * Even if 'arguments' isn't explicitly mentioned, dynamic name lookup
@@ -972,18 +1069,22 @@ Parser<ParseHandler>::functionBody(Funct
         return null();
 
     return pn;
 }
 
 /* See comment for use in Parser::functionDef. */
 template <>
 bool
-Parser<FullParseHandler>::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom)
-{
+Parser<FullParseHandler>::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom,
+                                         bool *pbodyLevelHoistedUse)
+{
+    // See comment in addFreeVariablesFromLazyFunction.
+    *pbodyLevelHoistedUse = !!dn->dn_uses;
+
     /* Turn pn into a definition. */
     pc->updateDecl(atom, pn);
 
     /* Change all uses of dn to be uses of pn. */
     for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
         JS_ASSERT(pnu->isUsed());
         JS_ASSERT(!pnu->isDefn());
         pnu->pn_lexdef = (Definition *) pn;
@@ -1069,22 +1170,22 @@ struct BindData
 
     struct LetData {
         explicit LetData(ExclusiveContext *cx) : blockObj(cx) {}
         VarContext varContext;
         RootedStaticBlockObject blockObj;
         unsigned   overflow;
     } let;
 
-    void initLet(VarContext varContext, StaticBlockObject &blockObj, unsigned overflow) {
+    void initLet(VarContext varContext, StaticBlockObject *blockObj, unsigned overflow) {
         this->pn = ParseHandler::null();
-        this->op = JSOP_NOP;
+        this->op = JSOP_INITLEXICAL;
         this->binder = Parser<ParseHandler>::bindLet;
         this->let.varContext = varContext;
-        this->let.blockObj = &blockObj;
+        this->let.blockObj = blockObj;
         this->let.overflow = overflow;
     }
 
     void initVarOrConst(JSOp op) {
         this->op = op;
         this->binder = Parser<ParseHandler>::bindVarOrConst;
     }
 };
@@ -1186,17 +1287,17 @@ ConvertDefinitionToNamedLambdaUse(TokenS
  * Beware: this function is called for functions nested in other functions or
  * global scripts but not for functions compiled through the Function
  * constructor or JSAPI. To always execute code when a function has finished
  * parsing, use Parser::functionBody.
  */
 template <>
 bool
 Parser<FullParseHandler>::leaveFunction(ParseNode *fn, ParseContext<FullParseHandler> *outerpc,
-                                        FunctionSyntaxKind kind)
+                                        bool bodyLevelHoistedUse, FunctionSyntaxKind kind)
 {
     outerpc->blockidGen = pc->blockidGen;
 
     FunctionBox *funbox = fn->pn_funbox;
     JS_ASSERT(funbox == pc->sc->asFunctionBox());
 
     /* Propagate unresolved lexical names up to outerpc->lexdeps. */
     if (pc->lexdeps->count()) {
@@ -1258,16 +1359,39 @@ Parser<FullParseHandler>::leaveFunction(
              * when leaving the inner function.
              *
              * The dn == outer_dn case arises with generator expressions
              * (see LegacyCompExprTransplanter::transplant, the PN_CODE/PN_NAME
              * case), and nowhere else, currently.
              */
             if (dn != outer_dn) {
                 if (ParseNode *pnu = dn->dn_uses) {
+                    // In ES6, lexical bindings cannot be accessed until
+                    // initialized. If we are parsing a function with a
+                    // hoisted body-level use, all free variables that get
+                    // linked to an outer 'let' binding need to be marked as
+                    // needing dead zone checks. e.g.,
+                    //
+                    // function outer() {
+                    //   inner();
+                    //   function inner() { use(x); }
+                    //   let x;
+                    // }
+                    //
+                    // The use of 'x' inside 'inner' needs to be marked.
+                    if (bodyLevelHoistedUse && outer_dn->isLet()) {
+                        while (true) {
+                            pnu->pn_dflags |= PND_LET;
+                            if (!pnu->pn_link)
+                                break;
+                            pnu = pnu->pn_link;
+                        }
+                        pnu = dn->dn_uses;
+                    }
+
                     while (true) {
                         pnu->pn_lexdef = outer_dn;
                         if (!pnu->pn_link)
                             break;
                         pnu = pnu->pn_link;
                     }
                     pnu->pn_link = outer_dn->dn_uses;
                     outer_dn->dn_uses = dn->dn_uses;
@@ -1285,22 +1409,22 @@ Parser<FullParseHandler>::leaveFunction(
     InternalHandle<Bindings*> bindings =
         InternalHandle<Bindings*>::fromMarkedLocation(&funbox->bindings);
     return pc->generateFunctionBindings(context, tokenStream, alloc, bindings);
 }
 
 template <>
 bool
 Parser<SyntaxParseHandler>::leaveFunction(Node fn, ParseContext<SyntaxParseHandler> *outerpc,
-                                          FunctionSyntaxKind kind)
+                                          bool bodyLevelHoistedUse, FunctionSyntaxKind kind)
 {
     outerpc->blockidGen = pc->blockidGen;
 
     FunctionBox *funbox = pc->sc->asFunctionBox();
-    return addFreeVariablesFromLazyFunction(funbox->function(), outerpc);
+    return addFreeVariablesFromLazyFunction(funbox->function(), outerpc, bodyLevelHoistedUse);
 }
 
 /*
  * defineArg is called for both the arguments of a regular function definition
  * and the arguments specified by the Function constructor.
  *
  * The 'disallowDuplicateArgs' bool indicates whether the use of another
  * feature (destructuring or default arguments) disables duplicate arguments.
@@ -1561,38 +1685,42 @@ Parser<ParseHandler>::functionArguments(
 
     return true;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
                                                   ParseNode **pn_, FunctionSyntaxKind kind,
-                                                  bool *pbodyProcessed)
+                                                  bool *pbodyProcessed,
+                                                  bool *pbodyLevelHoistedUse)
 {
     ParseNode *&pn = *pn_;
     *pbodyProcessed = false;
+    *pbodyLevelHoistedUse = false;
 
     /* Function statements add a binding to the enclosing scope. */
     bool bodyLevel = pc->atBodyLevel();
 
     if (kind == Statement) {
         /*
          * Handle redeclaration and optimize cases where we can statically bind the
          * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
          */
         if (Definition *dn = pc->decls().lookupFirst(funName)) {
             JS_ASSERT(!dn->isUsed());
             JS_ASSERT(dn->isDefn());
 
-            if (options().extraWarningsOption || dn->kind() == Definition::CONST) {
+            bool throwRedeclarationError = dn->kind() == Definition::CONST ||
+                                           dn->kind() == Definition::LET;
+            if (options().extraWarningsOption || throwRedeclarationError) {
                 JSAutoByteString name;
-                ParseReportKind reporter = (dn->kind() != Definition::CONST)
-                                           ? ParseExtraWarning
-                                           : ParseError;
+                ParseReportKind reporter = throwRedeclarationError
+                                           ? ParseError
+                                           : ParseExtraWarning;
                 if (!AtomToPrintableString(context, funName, &name) ||
                     !report(reporter, false, nullptr, JSMSG_REDECLARED_VAR,
                             Definition::kindString(dn->kind()), name.ptr()))
                 {
                     return false;
                 }
             }
 
@@ -1611,17 +1739,17 @@ Parser<FullParseHandler>::checkFunctionD
                     // remains a definition. But change the function node pn so
                     // that it knows where the argument is located.
                     pn->setOp(JSOP_GETARG);
                     pn->setDefn(true);
                     pn->pn_cookie = dn->pn_cookie;
                     pn->pn_dflags |= PND_BOUND;
                     dn->markAsAssigned();
                 } else {
-                    if (!makeDefIntoUse(dn, pn, funName))
+                    if (!makeDefIntoUse(dn, pn, funName, pbodyLevelHoistedUse))
                         return false;
                 }
             }
         } else if (bodyLevel) {
             /*
              * If this function was used before it was defined, claim the
              * pre-created definition node for this function that primaryExpr
              * put in pc->lexdeps on first forward reference, and recycle pn.
@@ -1634,16 +1762,18 @@ Parser<FullParseHandler>::checkFunctionD
                 fn->pn_pos.end = pn->pn_pos.end;
 
                 fn->pn_body = nullptr;
                 fn->pn_cookie.makeFree();
 
                 pc->lexdeps->remove(funName);
                 handler.freeTree(pn);
                 pn = fn;
+
+                *pbodyLevelHoistedUse = true;
             }
 
             if (!pc->define(tokenStream, funName, pn, Definition::VAR))
                 return false;
         }
 
         if (bodyLevel) {
             JS_ASSERT(pn->functionIsHoisted());
@@ -1702,17 +1832,17 @@ Parser<FullParseHandler>::checkFunctionD
     if (LazyScript *lazyOuter = handler.lazyOuterFunction()) {
         JSFunction *fun = handler.nextLazyInnerFunction();
         JS_ASSERT(!fun->isLegacyGenerator());
         FunctionBox *funbox = newFunctionBox(pn, fun, pc, Directives(/* strict = */ false),
                                              fun->generatorKind());
         if (!funbox)
             return false;
 
-        if (!addFreeVariablesFromLazyFunction(fun, pc))
+        if (!addFreeVariablesFromLazyFunction(fun, pc, *pbodyLevelHoistedUse))
             return false;
 
         // The position passed to tokenStream.advance() is relative to
         // userbuf.base() while LazyScript::{begin,end} offsets are relative to
         // the outermost script source. N.B: userbuf.base() is initialized
         // (in TokenStream()) to begin() - column() so that column numbers in
         // the lazily parsed script are correct.
         uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column();
@@ -1733,53 +1863,69 @@ PropagateTransitiveParseFlags(const T *i
      outer->setBindingsAccessedDynamically();
    if (inner->hasDebuggerStatement())
      outer->setHasDebuggerStatement();
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::addFreeVariablesFromLazyFunction(JSFunction *fun,
-                                                       ParseContext<ParseHandler> *pc)
-{
+                                                       ParseContext<ParseHandler> *pc,
+                                                       bool bodyLevelHoistedUse)
+{
+    MOZ_ASSERT_IF(bodyLevelHoistedUse, fun->displayAtom() && pc->atBodyLevel());
+
     // Update any definition nodes in this context according to free variables
     // in a lazily parsed inner function.
 
     LazyScript *lazy = fun->lazyScript();
-    HeapPtrAtom *freeVariables = lazy->freeVariables();
+    LazyScript::FreeVariable *freeVariables = lazy->freeVariables();
     for (size_t i = 0; i < lazy->numFreeVariables(); i++) {
-        JSAtom *atom = freeVariables[i];
+        JSAtom *atom = freeVariables[i].atom();
 
         // 'arguments' will be implicitly bound within the inner function.
         if (atom == context->names().arguments)
             continue;
 
         DefinitionNode dn = pc->decls().lookupFirst(atom);
 
         if (!dn) {
             dn = getOrCreateLexicalDependency(pc, atom);
             if (!dn)
                 return false;
         }
 
+        // In ES6, lexical bindings are unaccessible before initialization. If
+        // the inner function closes over a placeholder definition, we need to
+        // mark the variable as maybe needing a dead zone check when we emit
+        // bytecode.
+        //
+        // Note that body-level function declaration statements are always
+        // hoisted to the top, so all accesses to free let variables need the
+        // dead zone check.
+        if (handler.isPlaceholderDefinition(dn) || bodyLevelHoistedUse)
+            freeVariables[i].setIsHoistedUse();
+
         /* Mark the outer dn as escaping. */
         handler.setFlag(handler.getDefinitionNode(dn), PND_CLOSED);
     }
 
     PropagateTransitiveParseFlags(lazy, pc->sc);
     return true;
 }
 
 template <>
 bool
 Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
                                                     Node *pn, FunctionSyntaxKind kind,
-                                                    bool *pbodyProcessed)
+                                                    bool *pbodyProcessed,
+                                                    bool *pbodyLevelHoistedUse)
 {
     *pbodyProcessed = false;
+    *pbodyLevelHoistedUse = false;
 
     /* Function statements add a binding to the enclosing scope. */
     bool bodyLevel = pc->atBodyLevel();
 
     if (kind == Statement) {
         /*
          * Handle redeclaration and optimize cases where we can statically bind the
          * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
@@ -1790,18 +1936,20 @@ Parser<SyntaxParseHandler>::checkFunctio
                 if (!AtomToPrintableString(context, funName, &name) ||
                     !report(ParseError, false, null(), JSMSG_REDECLARED_VAR,
                             Definition::kindString(dn), name.ptr()))
                 {
                     return false;
                 }
             }
         } else if (bodyLevel) {
-            if (pc->lexdeps.lookupDefn<SyntaxParseHandler>(funName))
+            if (pc->lexdeps.lookupDefn<SyntaxParseHandler>(funName)) {
+                *pbodyLevelHoistedUse = true;
                 pc->lexdeps->remove(funName);
+            }
 
             if (!pc->define(tokenStream, funName, *pn, Definition::VAR))
                 return false;
         }
 
         if (!bodyLevel && funName == context->names().arguments)
             pc->sc->setBindingsAccessedDynamically();
     }
@@ -1890,17 +2038,18 @@ Parser<ParseHandler>::functionDef(Handle
     JS_ASSERT_IF(kind == Statement, funName);
 
     /* Make a TOK_FUNCTION node. */
     Node pn = handler.newFunctionDefinition();
     if (!pn)
         return null();
 
     bool bodyProcessed;
-    if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed))
+    bool bodyLevelHoistedUse;
+    if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed, &bodyLevelHoistedUse))
         return null();
 
     if (bodyProcessed)
         return pn;
 
     RootedObject proto(context);
     if (generatorKind == StarGenerator) {
         // If we are off the main thread, the generator meta-objects have
@@ -1918,17 +2067,18 @@ Parser<ParseHandler>::functionDef(Handle
     // Speculatively parse using the directives of the parent parsing context.
     // If a directive is encountered (e.g., "use strict") that changes how the
     // function should have been parsed, we backup and reparse with the new set
     // of directives.
     Directives directives(pc);
     Directives newDirectives = directives;
 
     while (true) {
-        if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives))
+        if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives,
+                                bodyLevelHoistedUse))
             break;
         if (tokenStream.hadError() || directives == newDirectives)
             return null();
 
         // Assignment must be monotonic to prevent reparsing iloops
         JS_ASSERT_IF(directives.strict(), newDirectives.strict());
         JS_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
         directives = newDirectives;
@@ -2009,20 +2159,20 @@ Parser<SyntaxParseHandler>::finishFuncti
 
     RootedFunction fun(context, funbox->function());
     LazyScript *lazy = LazyScript::CreateRaw(context, fun, numFreeVariables, numInnerFunctions,
                                              versionNumber(), funbox->bufStart, funbox->bufEnd,
                                              funbox->startLine, funbox->startColumn);
     if (!lazy)
         return false;
 
-    HeapPtrAtom *freeVariables = lazy->freeVariables();
+    LazyScript::FreeVariable *freeVariables = lazy->freeVariables();
     size_t i = 0;
     for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront())
-        freeVariables[i++].init(r.front().key());
+        freeVariables[i++] = LazyScript::FreeVariable(r.front().key());
     JS_ASSERT(i == numFreeVariables);
 
     HeapPtrFunction *innerFunctions = lazy->innerFunctions();
     for (size_t i = 0; i < numInnerFunctions; i++)
         innerFunctions[i].init(pc->innerFunctions[i]);
 
     if (pc->sc->strict)
         lazy->setStrict();
@@ -2036,17 +2186,18 @@ Parser<SyntaxParseHandler>::finishFuncti
 }
 
 template <>
 bool
 Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
                                               FunctionType type, FunctionSyntaxKind kind,
                                               GeneratorKind generatorKind,
                                               Directives inheritedDirectives,
-                                              Directives *newDirectives)
+                                              Directives *newDirectives,
+                                              bool bodyLevelHoistedUse)
 {
     ParseContext<FullParseHandler> *outerpc = pc;
 
     // Create box for fun->object early to protect against last-ditch GC.
     FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind);
     if (!funbox)
         return false;
 
@@ -2060,18 +2211,17 @@ Parser<FullParseHandler>::functionArgsAn
             // Move the syntax parser to the current position in the stream.
             TokenStream::Position position(keepAtoms);
             tokenStream.tell(&position);
             if (!parser->tokenStream.seek(position, tokenStream))
                 return false;
 
             ParseContext<SyntaxParseHandler> funpc(parser, outerpc, SyntaxParseHandler::null(), funbox,
                                                    newDirectives, outerpc->staticLevel + 1,
-                                                   outerpc->blockidGen,
-                                                   /* blockScopeDepth = */ 0);
+                                                   outerpc->blockidGen, /* blockScopeDepth = */ 0);
             if (!funpc.init(tokenStream))
                 return false;
 
             if (!parser->functionArgsAndBodyGeneric(SyntaxParseHandler::NodeGeneric,
                                                     fun, type, kind))
             {
                 if (parser->hadAbortedSyntaxParse()) {
                     // Try again with a full parse.
@@ -2087,17 +2237,17 @@ Parser<FullParseHandler>::functionArgsAn
             parser->tokenStream.tell(&position);
             if (!tokenStream.seek(position, parser->tokenStream))
                 return false;
 
             // Update the end position of the parse node.
             pn->pn_pos.end = tokenStream.currentToken().pos.end;
         }
 
-        if (!addFreeVariablesFromLazyFunction(fun, pc))
+        if (!addFreeVariablesFromLazyFunction(fun, pc, bodyLevelHoistedUse))
             return false;
 
         pn->pn_blockid = outerpc->blockid();
         PropagateTransitiveParseFlags(funbox, outerpc->sc);
         return true;
     } while (false);
 
     // Continue doing a full parse for this inner function.
@@ -2105,17 +2255,17 @@ Parser<FullParseHandler>::functionArgsAn
                                          outerpc->staticLevel + 1, outerpc->blockidGen,
                                          /* blockScopeDepth = */ 0);
     if (!funpc.init(tokenStream))
         return false;
 
     if (!functionArgsAndBodyGeneric(pn, fun, type, kind))
         return false;
 
-    if (!leaveFunction(pn, outerpc, kind))
+    if (!leaveFunction(pn, outerpc, bodyLevelHoistedUse, kind))
         return false;
 
     pn->pn_blockid = outerpc->blockid();
 
     /*
      * Fruit of the poisonous tree: if a closure contains a dynamic name access
      * (eval, with, etc), we consider the parent to do the same. The reason is
      * that the deoptimizing effects of dynamic name access apply equally to
@@ -2126,17 +2276,18 @@ Parser<FullParseHandler>::functionArgsAn
 }
 
 template <>
 bool
 Parser<SyntaxParseHandler>::functionArgsAndBody(Node pn, HandleFunction fun,
                                                 FunctionType type, FunctionSyntaxKind kind,
                                                 GeneratorKind generatorKind,
                                                 Directives inheritedDirectives,
-                                                Directives *newDirectives)
+                                                Directives *newDirectives,
+                                                bool bodyLevelHoistedUse)
 {
     ParseContext<SyntaxParseHandler> *outerpc = pc;
 
     // Create box for fun->object early to protect against last-ditch GC.
     FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind);
     if (!funbox)
         return false;
 
@@ -2145,17 +2296,17 @@ Parser<SyntaxParseHandler>::functionArgs
                                            outerpc->staticLevel + 1, outerpc->blockidGen,
                                            /* blockScopeDepth = */ 0);
     if (!funpc.init(tokenStream))
         return false;
 
     if (!functionArgsAndBodyGeneric(pn, fun, type, kind))
         return false;
 
-    if (!leaveFunction(pn, outerpc, kind))
+    if (!leaveFunction(pn, outerpc, bodyLevelHoistedUse, kind))
         return false;
 
     // This is a lazy function inner to another lazy function. Remember the
     // inner function so that if the outer function is eventually parsed we do
     // not need any further parsing or processing of the inner function.
     JS_ASSERT(fun->lazyScript());
     return outerpc->innerFunctions.append(fun);
 }
@@ -2656,58 +2807,76 @@ Parser<FullParseHandler>::bindLet(BindDa
                                   HandlePropertyName name, Parser<FullParseHandler> *parser)
 {
     ParseContext<FullParseHandler> *pc = parser->pc;
     ParseNode *pn = data->pn;
     if (!parser->checkStrictBinding(name, pn))
         return false;
 
     ExclusiveContext *cx = parser->context;
-
     Rooted<StaticBlockObject *> blockObj(cx, data->let.blockObj);
-    unsigned index = blockObj->numVariables();
-    if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) {
-        parser->report(ParseError, false, pn, data->let.overflow);
-        return false;
-    }
-
-    /*
-     * Assign block-local index to pn->pn_cookie right away, encoding it as an
-     * upvar cookie whose skip tells the current static level. The emitter will
-     * adjust the node's slot based on its stack depth model -- and, for global
-     * and eval code, js::frontend::CompileScript will adjust the slot
-     * again to include script->nfixed.
-     */
+
+    unsigned index;
+    if (blockObj) {
+        index = blockObj->numVariables();
+        if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) {
+            parser->report(ParseError, false, pn, data->let.overflow);
+            return false;
+        }
+    } else {
+        // If we don't have a block object, we are parsing a body-level let,
+        // in which case we use a bogus index. See comment block below in
+        // setting the pn_cookie for explanation on how it gets adjusted.
+        index = 0;
+    }
+
+    // For block-level lets, assign block-local index to pn->pn_cookie right
+    // away, encoding it as an upvar cookie whose skip tells the current
+    // static level. The emitter will adjust the node's slot based on its
+    // stack depth model -- and, for global and eval code,
+    // js::frontend::CompileScript will adjust the slot again to include
+    // script->nfixed and body-level lets.
+    //
+    // For body-level lets, the index is bogus at this point and is adjusted
+    // when creating Bindings. See ParseContext::generateFunctionBindings and
+    // AppendPackedBindings.
     if (!pn->pn_cookie.set(parser->tokenStream, pc->staticLevel, index))
         return false;
 
     /*
      * For bindings that are hoisted to the beginning of the block/function,
      * define() right now. Otherwise, delay define until PushLetScope.
      */
     if (data->let.varContext == HoistVars) {
-        JS_ASSERT(!pc->atBodyLevel());
         Definition *dn = pc->decls().lookupFirst(name);
         if (dn && dn->pn_blockid == pc->blockid())
             return parser->reportRedeclaration(pn, dn->isConst(), name);
         if (!pc->define(parser->tokenStream, name, pn, Definition::LET))
             return false;
     }
 
-    bool redeclared;
-    RootedId id(cx, NameToId(name));
-    RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared));
-    if (!shape) {
-        if (redeclared)
-            parser->reportRedeclaration(pn, false, name);
-        return false;
-    }
-
-    /* Store pn in the static block object. */
-    blockObj->setDefinitionParseNode(index, reinterpret_cast<Definition *>(pn));
+    if (blockObj) {
+        bool redeclared;
+        RootedId id(cx, NameToId(name));
+        RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared));
+        if (!shape) {
+            if (redeclared)
+                parser->reportRedeclaration(pn, false, name);
+            return false;
+        }
+
+        /* Store pn in the static block object. */
+        blockObj->setDefinitionParseNode(index, reinterpret_cast<Definition *>(pn));
+    } else {
+        // Body-level lets are hoisted and need to have been defined via
+        // pc->define above.
+        MOZ_ASSERT(data->let.varContext == HoistVars);
+        MOZ_ASSERT(pc->decls().lookupFirst(name));
+    }
+
     return true;
 }
 
 template <>
 /* static */ bool
 Parser<SyntaxParseHandler>::bindLet(BindData<SyntaxParseHandler> *data,
                                     HandlePropertyName name, Parser<SyntaxParseHandler> *parser)
 {
@@ -2993,18 +3162,27 @@ Parser<ParseHandler>::noteNameUse(Handle
          */
         dn = getOrCreateLexicalDependency(pc, name);
         if (!dn)
             return false;
     }
 
     handler.linkUseToDef(pn, dn);
 
-    if (stmt && stmt->type == STMT_WITH)
-        handler.setFlag(pn, PND_DEOPTIMIZED);
+    if (stmt) {
+        if (stmt->type == STMT_WITH) {
+            handler.setFlag(pn, PND_DEOPTIMIZED);
+        } else if (stmt->type == STMT_SWITCH && stmt->isBlockScope) {
+            // See comments above StmtInfoPC and switchStatement for how
+            // firstDominatingLetInCase is computed.
+            MOZ_ASSERT(stmt->firstDominatingLexicalInCase <= stmt->staticBlock().numVariables());
+            handler.markMaybeUninitializedLexicalUseInSwitch(pn, dn,
+                                                             stmt->firstDominatingLexicalInCase);
+        }
+    }
 
     return true;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::bindDestructuringVar(BindData<FullParseHandler> *data, ParseNode *pn)
 {
@@ -3015,17 +3193,19 @@ Parser<FullParseHandler>::bindDestructur
     data->pn = pn;
     if (!data->binder(data, name, this))
         return false;
 
     /*
      * Select the appropriate name-setting opcode, respecting eager selection
      * done by the data->binder function.
      */
-    if (pn->pn_dflags & PND_BOUND)
+    if (data->op == JSOP_INITLEXICAL)
+        pn->setOp(JSOP_INITLEXICAL);
+    else if (pn->pn_dflags & PND_BOUND)
         pn->setOp(JSOP_SETLOCAL);
     else if (data->op == JSOP_DEFCONST)
         pn->setOp(JSOP_SETCONST);
     else
         pn->setOp(JSOP_SETNAME);
 
     if (data->op == JSOP_DEFCONST)
         pn->pn_dflags |= PND_CONST;
@@ -3434,30 +3614,30 @@ Parser<ParseHandler>::variables(ParseNod
     JS_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_CALL);
 
     /*
      * The simple flag is set if the declaration has the form 'var x', with
      * only one variable declared and no initializer expression.
      */
     JS_ASSERT_IF(psimple, *psimple);
 
-    JSOp op = blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST;
+    JSOp op = kind == PNK_LET ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST;
 
     Node pn = handler.newList(kind, null(), op);
     if (!pn)
         return null();
 
     /*
      * SpiderMonkey const is really "write once per initialization evaluation"
      * var, whereas let is block scoped. ES-Harmony wants block-scoped const so
      * this code will change soon.
      */
     BindData<ParseHandler> data(context);
-    if (blockObj)
-        data.initLet(varContext, *blockObj, JSMSG_TOO_MANY_LOCALS);
+    if (kind == PNK_LET)
+        data.initLet(varContext, blockObj, JSMSG_TOO_MANY_LOCALS);
     else
         data.initVarOrConst(op);
 
     bool first = true;
     Node pn2;
     do {
         if (psimple && !first)
             *psimple = false;
@@ -3469,31 +3649,40 @@ Parser<ParseHandler>::variables(ParseNod
                 *psimple = false;
 
             pc->inDeclDestructuring = true;
             pn2 = primaryExpr(tt);
             pc->inDeclDestructuring = false;
             if (!pn2)
                 return null();
 
-            if (!checkDestructuring(&data, pn2))
+            bool ignored;
+            bool parsingForInOrOfInit = pc->parsingForInit && matchInOrOf(&ignored);
+
+            // See comment below for bindBeforeInitializer in the code that
+            // handles the non-destructuring case.
+            bool bindBeforeInitializer = kind != PNK_LET || parsingForInOrOfInit;
+            if (bindBeforeInitializer && !checkDestructuring(&data, pn2))
                 return null();
-            bool ignored;
-            if (pc->parsingForInit && matchInOrOf(&ignored)) {
+
+            if (parsingForInOrOfInit) {
                 tokenStream.ungetToken();
                 handler.addList(pn, pn2);
                 continue;
             }
 
             MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL);
 
             Node init = assignExpr();
             if (!init)
                 return null();
 
+            if (!bindBeforeInitializer && !checkDestructuring(&data, pn2))
+                return null();
+
             pn2 = handler.newBinaryOrAppend(PNK_ASSIGN, pn2, init, pc);
             if (!pn2)
                 return null();
             handler.addList(pn, pn2);
             continue;
         }
 
         if (tt != TOK_NAME) {
@@ -3509,31 +3698,50 @@ Parser<ParseHandler>::variables(ParseNod
 
         RootedPropertyName name(context, tokenStream.currentName());
         pn2 = newBindingNode(name, kind == PNK_VAR || kind == PNK_CONST, varContext);
         if (!pn2)
             return null();
         if (data.op == JSOP_DEFCONST)
             handler.setFlag(pn2, PND_CONST);
         data.pn = pn2;
-        if (!data.binder(&data, name, this))
-            return null();
+
         handler.addList(pn, pn2);
 
         if (tokenStream.matchToken(TOK_ASSIGN)) {
             if (psimple)
                 *psimple = false;
 
+            // In ES6, lexical bindings may not be accessed until
+            // initialized. So a declaration of the form |let x = x| results
+            // in a ReferenceError, as the 'x' on the RHS is accessing the let
+            // binding before it is initialized.
+            //
+            // If we are not parsing a let declaration, bind the name
+            // now. Otherwise we must wait until after parsing the initializing
+            // assignment.
+            bool bindBeforeInitializer = kind != PNK_LET;
+            if (bindBeforeInitializer && !data.binder(&data, name, this))
+                return null();
+
             Node init = assignExpr();
             if (!init)
                 return null();
 
+            if (!bindBeforeInitializer && !data.binder(&data, name, this))
+                return null();
+
             if (!handler.finishInitializerAssignment(pn2, init, data.op))
                 return null();
-        }
+        } else {
+            if (!data.binder(&data, name, this))
+                return null();
+        }
+
+
     } while (tokenStream.matchToken(TOK_COMMA));
 
     return pn;
 }
 
 template <>
 ParseNode *
 Parser<FullParseHandler>::letDeclaration()
@@ -3570,29 +3778,37 @@ Parser<FullParseHandler>::letDeclaration
                  * before the global object in the overall chain.  This extra
                  * object is present in the scope chain for all code in that
                  * global, including self-hosted code.  But self-hosted code
                  * must be usable against *any* global object, including ones
                  * with other let variables -- variables possibly placed in
                  * conflicting slots.  Forbid top-level let declarations to
                  * prevent such conflicts from ever occurring.
                  */
-                if (options().selfHostingMode &&
-                    !pc->sc->isFunctionBox() &&
-                    stmt == pc->topScopeStmt)
-                {
+                bool globalLet = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt;
+                if (options().selfHostingMode && globalLet) {
                     report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LET);
                     return null();
                 }
 
                 /*
-                 * ES4 specifies that let at top level and at body-block scope
-                 * does not shadow var, so convert back to var.
+                 * Parse body-level lets without a new block object. ES6 specs
+                 * that an execution environment's initial lexical environment
+                 * is the VariableEnvironment, i.e., body-level lets are in
+                 * the same environment record as vars.
+                 *
+                 * However, they cannot be parsed exactly as vars, as ES6
+                 * requires that uninitialized lets throw ReferenceError on use.
+                 *
+                 * See 8.1.1.1.6 and the note in 13.2.1.
+                 *
+                 * FIXME global-level lets are still considered vars until
+                 * other bugs are fixed.
                  */
-                pn = variables(PNK_VAR);
+                pn = variables(globalLet ? PNK_VAR : PNK_LET);
                 if (!pn)
                     return null();
                 pn->pn_xflags |= PNX_POPVAR;
                 break;
             }
 
             /*
              * Some obvious assertions here, but they may help clarify the
@@ -4620,16 +4836,29 @@ Parser<ParseHandler>::switchStatement()
             if (tt == TOK_ERROR)
                 return null();
             Node stmt = statement();
             if (!stmt)
                 return null();
             handler.addList(body, stmt);
         }
 
+        // In ES6, lexical bindings canot be accessed until initialized. If
+        // there was a 'let' declaration in the case we just parsed, remember
+        // the slot starting at which new lexical bindings will be
+        // assigned. Since lexical bindings from previous cases will not
+        // dominate uses in the current case, any such uses will require a
+        // dead zone check.
+        //
+        // Currently this is overly conservative; we could do better, but
+        // declaring lexical bindings within switch cases without introducing
+        // a new block is poor form and should be avoided.
+        if (stmtInfo.isBlockScope)
+            stmtInfo.firstDominatingLexicalInCase = stmtInfo.staticBlock().numVariables();
+
         Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body);
         if (!casepn)
             return null();
         handler.addList(caseList, casepn);
     }
 
     /*
      * Handle the case where there was a let declaration in any case in
@@ -5097,17 +5326,17 @@ Parser<ParseHandler>::tryStatement()
              */
             MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
 
             /*
              * Contrary to ECMA Ed. 3, the catch variable is lexically
              * scoped, not a property of a new Object instance.  This is
              * an intentional change that anticipates ECMA Ed. 4.
              */
-            data.initLet(HoistVars, pc->staticScope->template as<StaticBlockObject>(),
+            data.initLet(HoistVars, &pc->staticScope->template as<StaticBlockObject>(),
                          JSMSG_TOO_MANY_CATCH_VARS);
             JS_ASSERT(data.let.blockObj);
 
             tt = tokenStream.getToken();
             Node catchName;
             switch (tt) {
               case TOK_LB:
               case TOK_LC:
@@ -6151,17 +6380,17 @@ Parser<FullParseHandler>::legacyComprehe
     LegacyCompExprTransplanter transplanter(bodyStmt, this, outerpc, comprehensionKind, adjust);
     if (!transplanter.init())
         return null();
 
     if (!transplanter.transplant(bodyStmt))
         return null();
 
     JS_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object);
-    data.initLet(HoistVars, pc->staticScope->as<StaticBlockObject>(), JSMSG_ARRAY_INIT_TOO_BIG);
+    data.initLet(HoistVars, &pc->staticScope->as<StaticBlockObject>(), JSMSG_ARRAY_INIT_TOO_BIG);
 
     do {
         /*
          * FOR node is binary, left is loop control and right is body.  Use
          * index to count each block-local let-variable on the left-hand side
          * of the in/of.
          */
         pn2 = BinaryNode::create(PNK_FOR, &handler);
@@ -6267,31 +6496,35 @@ Parser<FullParseHandler>::legacyComprehe
             break;
 
           default:;
         }
 
         /*
          * Synthesize a declaration. Every definition must appear in the parse
          * tree in order for ComprehensionTranslator to work.
+         *
+         * These are lets to tell the bytecode emitter to emit initialization
+         * code for the temporal dead zone.
          */
-        ParseNode *vars = ListNode::create(PNK_VAR, &handler);
-        if (!vars)
+        ParseNode *lets = ListNode::create(PNK_LET, &handler);
+        if (!lets)
             return null();
-        vars->setOp(JSOP_NOP);
-        vars->pn_pos = pn3->pn_pos;
-        vars->makeEmpty();
-        vars->append(pn3);
+        lets->setOp(JSOP_NOP);
+        lets->pn_pos = pn3->pn_pos;
+        lets->makeEmpty();
+        lets->append(pn3);
+        lets->pn_xflags |= PNX_POPVAR;
 
         /* Definitions can't be passed directly to EmitAssignment as lhs. */
         pn3 = cloneLeftHandSide(pn3);
         if (!pn3)
             return null();
 
-        pn2->pn_left = handler.newTernary(headKind, vars, pn3, pn4);
+        pn2->pn_left = handler.newTernary(headKind, lets, pn3, pn4);
         if (!pn2->pn_left)
             return null();
         *pnp = pn2;
         pnp = &pn2->pn_right;
     } while (tokenStream.matchToken(TOK_FOR));
 
     if (tokenStream.matchToken(TOK_IF)) {
         pn2 = TernaryNode::create(PNK_IF, &handler);
@@ -6447,17 +6680,17 @@ Parser<ParseHandler>::generatorComprehen
 
     // Note that if we ever start syntax-parsing generators, we will also
     // need to propagate the closed-over variable set to the inner
     // lazyscript, as in finishFunctionDefinition.
     handler.setFunctionBody(genfn, body);
 
     PropagateTransitiveParseFlags(genFunbox, outerpc->sc);
 
-    if (!leaveFunction(genfn, outerpc))
+    if (!leaveFunction(genfn, outerpc, /* bodyLevelHoistedUse = */ false))
         return null();
 
     return genfn;
 }
 
 #if JS_HAS_GENERATOR_EXPRS
 
 /*
@@ -6553,17 +6786,17 @@ Parser<ParseHandler>::comprehensionFor(G
 
     TokenPos headPos(begin, pos().end);
 
     StmtInfoPC stmtInfo(context);
     BindData<ParseHandler> data(context);
     RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context));
     if (!blockObj)
         return null();
-    data.initLet(DontHoistVars, *blockObj, JSMSG_TOO_MANY_LOCALS);
+    data.initLet(DontHoistVars, blockObj, JSMSG_TOO_MANY_LOCALS);
     Node lhs = newName(name);
     if (!lhs)
         return null();
     Node decls = handler.newList(PNK_LET, lhs, JSOP_NOP);
     if (!decls)
         return null();
     data.pn = lhs;
     if (!data.binder(&data, name, this))
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -25,17 +25,29 @@ namespace frontend {
 
 struct StmtInfoPC : public StmtInfoBase {
     StmtInfoPC      *down;          /* info for enclosing statement */
     StmtInfoPC      *downScope;     /* next enclosing lexical scope */
 
     uint32_t        blockid;        /* for simplified dominance computation */
     uint32_t        innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */
 
-    explicit StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {}
+    // Lexical declarations inside switches are tricky because the block id
+    // doesn't convey dominance information. Record what index the current
+    // case's lexical declarations start at so we may generate dead zone
+    // checks for other cases' declarations.
+    //
+    // Only valid if type is STMT_SWITCH.
+    uint16_t        firstDominatingLexicalInCase;
+
+    explicit StmtInfoPC(ExclusiveContext *cx)
+      : StmtInfoBase(cx),
+        innerBlockScopeDepth(0),
+        firstDominatingLexicalInCase(0)
+    {}
 };
 
 typedef HashSet<JSAtom *, DefaultHasher<JSAtom *>, LifoAllocPolicy<Fallible>> FuncStmtSet;
 class SharedContext;
 
 typedef Vector<Definition *, 16> DeclVector;
 
 struct GenericParseContext
@@ -121,20 +133,24 @@ struct ParseContext : public GenericPars
 
     bool isArrowFunction() const {
         return sc->isFunctionBox() && sc->asFunctionBox()->function()->isArrow();
     }
 
     uint32_t        blockScopeDepth; /* maximum depth of nested block scopes, in slots */
     Node            blockNode;      /* parse node for a block with let declarations
                                        (block with its own lexical scope)  */
+
   private:
-    AtomDecls<ParseHandler> decls_; /* function, const, and var declarations */
-    DeclVector      args_;          /* argument definitions */
-    DeclVector      vars_;          /* var/const definitions */
+    AtomDecls<ParseHandler> decls_;     /* function, const, and var declarations */
+    DeclVector      args_;              /* argument definitions */
+    DeclVector      vars_;              /* var/const definitions */
+    DeclVector      bodyLevelLexicals_; /* lexical definitions at body-level */
+
+    bool checkLocalsOverflow(TokenStream &ts);
 
   public:
     const AtomDecls<ParseHandler> &decls() const {
         return decls_;
     }
 
     uint32_t numArgs() const {
         JS_ASSERT(sc->isFunctionBox());
@@ -254,16 +270,17 @@ struct ParseContext : public GenericPars
         maybeFunction(maybeFunction),
         staticLevel(staticLevel),
         lastYieldOffset(NoYieldOffset),
         blockScopeDepth(blockScopeDepth),
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
         vars_(prs->context),
+        bodyLevelLexicals_(prs->context),
         parserPC(&prs->pc),
         oldpc(prs->pc),
         lexdeps(prs->context),
         funcStmts(nullptr),
         innerFunctions(prs->context),
         newDirectives(newDirectives),
         inDeclDestructuring(false)
     {
@@ -552,17 +569,18 @@ class Parser : private JS::AutoGCRooter,
      */
     bool functionArguments(FunctionSyntaxKind kind, Node *list, Node funcpn, bool *hasRest);
 
     Node functionDef(HandlePropertyName name, const TokenStream::Position &start,
                      FunctionType type, FunctionSyntaxKind kind, GeneratorKind generatorKind);
     bool functionArgsAndBody(Node pn, HandleFunction fun,
                              FunctionType type, FunctionSyntaxKind kind,
                              GeneratorKind generatorKind,
-                             Directives inheritedDirectives, Directives *newDirectives);
+                             Directives inheritedDirectives, Directives *newDirectives,
+                             bool bodyLevelHoistedUse);
 
     Node unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin);
 
     Node condition();
 
     Node generatorComprehensionLambda(GeneratorKind comprehensionKind, unsigned begin,
                                       Node innerStmt);
 
@@ -601,21 +619,22 @@ class Parser : private JS::AutoGCRooter,
         KeyedDestructuringAssignment,
         IncDecAssignment
     };
 
     bool checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor);
     bool matchInOrOf(bool *isForOfp);
 
     bool checkFunctionArguments();
-    bool makeDefIntoUse(Definition *dn, Node pn, JSAtom *atom);
+    bool makeDefIntoUse(Definition *dn, Node pn, JSAtom *atom, bool *pbodyLevelHoistedUse);
     bool checkFunctionDefinition(HandlePropertyName funName, Node *pn, FunctionSyntaxKind kind,
-                                 bool *pbodyProcessed);
+                                 bool *pbodyProcessed, bool *pbodyLevelHoistedUse);
     bool finishFunctionDefinition(Node pn, FunctionBox *funbox, Node prelude, Node body);
-    bool addFreeVariablesFromLazyFunction(JSFunction *fun, ParseContext<ParseHandler> *pc);
+    bool addFreeVariablesFromLazyFunction(JSFunction *fun, ParseContext<ParseHandler> *pc,
+                                          bool bodyLevelHoistedUse);
 
     bool isValidForStatementLHS(Node pn1, JSVersion version, bool forDecl, bool forEach,
                                 ParseNodeKind headKind);
     bool checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder);
     bool checkStrictAssignment(Node lhs);
     bool checkStrictBinding(PropertyName *name, Node pn);
     bool defineArg(Node funcpn, HandlePropertyName name,
                    bool disallowDuplicateArgs = false, Node *duplicatedArg = nullptr);
@@ -656,17 +675,17 @@ class Parser : private JS::AutoGCRooter,
 
     static Node null() { return ParseHandler::null(); }
 
     bool reportRedeclaration(Node pn, bool isConst, HandlePropertyName name);
     bool reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum);
     DefinitionNode getOrCreateLexicalDependency(ParseContext<ParseHandler> *pc, JSAtom *atom);
 
     bool leaveFunction(Node fn, ParseContext<ParseHandler> *outerpc,
-                       FunctionSyntaxKind kind = Expression);
+                       bool bodyLevelHoistedUse, FunctionSyntaxKind kind = Expression);
 
     TokenPos pos() const { return tokenStream.currentToken().pos; }
 
     bool asmJS(Node list);
 
     void accumulateTelemetry();
 
     friend class LegacyCompExprTransplanter;
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -422,16 +422,19 @@ struct StmtInfoBase {
     bool maybeScope() const {
         return STMT_BLOCK <= type && type <= STMT_SUBROUTINE && type != STMT_WITH;
     }
 
     bool linksScope() const {
         return isNestedScope;
     }
 
+    void setStaticScope() {
+    }
+
     StaticBlockObject& staticBlock() const {
         JS_ASSERT(isNestedScope);
         JS_ASSERT(isBlockScope);
         return staticScope->as<StaticBlockObject>();
     }
 
     bool isLoop() const {
         return type >= STMT_DO_LOOP;
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -244,25 +244,28 @@ class SyntaxParseHandler
         }
         return nullptr;
     }
 
     Node makeAssignment(Node pn, Node rhs) { return NodeGeneric; }
 
     static Node getDefinitionNode(DefinitionNode dn) { return NodeGeneric; }
     static Definition::Kind getDefinitionKind(DefinitionNode dn) { return dn; }
+    static bool isPlaceholderDefinition(DefinitionNode dn) { return dn == Definition::PLACEHOLDER; }
     void linkUseToDef(Node pn, DefinitionNode dn) {}
     DefinitionNode resolve(DefinitionNode dn) { return dn; }
     void deoptimizeUsesWithin(DefinitionNode dn, const TokenPos &pos) {}
     bool dependencyCovered(Node pn, unsigned blockid, bool functionScope) {
         // Only resolve lexical dependencies in cases where a definition covers
         // the entire function. Not enough information is kept to compare the
         // dependency location with blockid.
         return functionScope;
     }
+    void markMaybeUninitializedLexicalUseInSwitch(Node pn, DefinitionNode dn,
+                                                  uint16_t firstDominatingLexicalSlot) {}
 
     static uintptr_t definitionToBits(DefinitionNode dn) {
         // Use a shift, as DefinitionList tags the lower bit of its associated union.
         return uintptr_t(dn << 1);
     }
     static DefinitionNode definitionFromBits(uintptr_t bits) {
         return (DefinitionNode) (bits >> 1);
     }
--- a/js/src/jit-test/tests/arguments/alias-function-closed.js
+++ b/js/src/jit-test/tests/arguments/alias-function-closed.js
@@ -117,17 +117,17 @@ function f5(a, b, c, d) {
     // close here
     assertEq(a(), 52);
     assertEq(b(), 63);
     assertEq(c(), 54);
     assertEq(d(), 65);
   }
 
   var a, b = ()=>63;
-  let c, d = ()=>65;
+  var c, d = ()=>65;
 
   // after var declarations, before function declarations
   assertEq(a(), 52);
   assertEq(b(), 63);
   assertEq(c(), 54);
   assertEq(d(), 65);
 
   function h() {
--- a/js/src/jit-test/tests/arguments/alias-function-not-closed.js
+++ b/js/src/jit-test/tests/arguments/alias-function-not-closed.js
@@ -54,17 +54,17 @@ f2(()=>42);
 function f3(a, b, c, d) {
   // before var/function declarations
   assertEq(a(), 52);
   assertEq(b(), 53);
   assertEq(c(), 54);
   assertEq(d(), 55);
 
   var a, b = ()=>63;
-  let c, d = ()=>65;
+  var c, d = ()=>65;
 
   // after var declarations, before function declarations
   assertEq(a(), 52);
   assertEq(b(), 63);
   assertEq(c(), 54);
   assertEq(d(), 65);
 
   function a() {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1001090-1.js
@@ -0,0 +1,2 @@
+(function() { let arguments })();
+(() => { let arguments; })()
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1001090-2.js
@@ -0,0 +1,5 @@
+// |jit-test| error: ReferenceError
+(function() {
+    with(x);
+    let x
+})()
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1001090-3.js
@@ -0,0 +1,27 @@
+var output = [];
+function g(s) {
+    L = s.length;
+    for (var i = 0; i < L; i++) {
+        a = s.charAt()
+    }
+}
+function h(f, inputs) {
+    results = [];
+    for (var j = 0; j < 99; ++j) {
+        for (var k = 0; k < 99; ++k) {
+            try {
+                results.push(f())
+            } catch (e) {}
+        }
+    }
+    output.push(g(uneval(results)))
+}
+m = (function(x, y) {});
+h(m, [])
+try {
+    output.push(x);
+    let x = s()
+} catch (e) {}
+
+assertEq(output.length, 1);
+assertEq(output[0], undefined);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1001090-4.js
@@ -0,0 +1,30 @@
+var output = [];
+function g(s) {
+    for (var i = 0; i < s.length; i++) {
+        s.charAt()
+    }
+}
+function h(f, inputs) {
+    results = []
+    for (var j = 0; j < 99; ++j) {
+        for (var k = 0; k < 99; ++k) {
+            try {
+                results.push(f())
+            } catch (e) {}
+        }
+    }
+    g(uneval(results))
+}
+try {
+    x()
+} catch (e) {}
+m = function(y) {
+    return y;
+};
+h(m, []);
+try {
+    output.push(b);
+    let b = "";
+} catch (e) {}
+
+assertEq(output.length, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1001090-5.js
@@ -0,0 +1,6 @@
+// |jit-test| error: ReferenceError
+evalcx("\
+    for(x = 0; x < 9; x++) {\
+        let y = y.s()\
+    }\
+", newGlobal())
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1001090-6.js
@@ -0,0 +1,7 @@
+// |jit-test| error: ReferenceError
+(function() {
+    ((function() {
+        p(y)
+    })());
+    let y
+})()
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
@@ -0,0 +1,5 @@
+var x = "foobar";
+{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); }
+
+var x = "foobar";
+{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/letTDZAfterInitializer.js
@@ -0,0 +1,19 @@
+function throwSomething() {
+  throw "something";
+}
+
+try {
+  // Use eval to force BINDNAME. Should throw "something" instead of the TDZ
+  // ReferenceError.
+  eval("x = throwSomething()");
+  let x;
+} catch (e) {
+  assertEq(e, "something");
+}
+
+try {
+  eval("x = 42");
+  let x;
+} catch (e) {
+  assertEq(e instanceof ReferenceError, true);
+}
--- a/js/src/jit-test/tests/basic/testFunctionStatementAliasLocals.js
+++ b/js/src/jit-test/tests/basic/testFunctionStatementAliasLocals.js
@@ -9,18 +9,8 @@ assertEq(f1(false), 3);
 
 function f2(b, w) {
     if (b)
         function w() {}
     return w;
 }
 assertEq(typeof f2(true, 3), "function");
 assertEq(f2(false, 3), 3);
-
-function f3(b) {
-    let (w = 3) {
-        if (b)
-            function w() {}
-        return w;
-    }
-}
-assertEq(f3(true, 3), 3);
-assertEq(f3(false), 3);
--- a/js/src/jit-test/tests/basic/testLet.js
+++ b/js/src/jit-test/tests/basic/testLet.js
@@ -27,23 +27,35 @@ function test(str, arg, result)
     var expect = result;
     if (got !== expect) {
         print("GOT:" + got);
         print("EXPECT: " + expect);
         assertEq(got, expect);
     }
 }
 
-function isError(str)
+function isParseError(str)
 {
     var caught = false;
     try {
         new Function(str);
     } catch(e) {
-        assertEq(String(e).indexOf('TypeError') == 0 || String(e).indexOf('SyntaxError') == 0, true);
+        assertEq(e instanceof TypeError || e instanceof SyntaxError, true);
+        caught = true;
+    }
+    assertEq(caught, true);
+}
+
+function isReferenceError(str)
+{
+    var caught = false;
+    try {
+        (new Function(str))();
+    } catch(e) {
+        assertEq(e instanceof ReferenceError, true);
         caught = true;
     }
     assertEq(caught, true);
 }
 
 // let expr
 test('return let (y) x;');
 test('return let (x) "" + x;', 'unicorns', 'undefined');
@@ -81,22 +93,22 @@ test('return let (x = x + "s") x;', 'pon
 test('return let ([x] = (3, [x])) x;');
 test('return let (y = x) function () {return eval("y");}();');
 test('return eval("let (y = x) y");');
 test('return let (y = x) (eval("var y = 2"), y);', 'ponies', 2);
 test('"use strict";return let (y = x) (eval("var y = 2"), y);');
 test('this.y = x;return let (y = 1) this.eval("y");');
 test('try {let (x = x) eval("throw x");} catch (e) {return e;}');
 test('try {return let (x = eval("throw x")) x;} catch (e) {return e;}');
-isError('let (x = 1, x = 2) x');
-isError('let ([x, y] = a, {a:x} = b) x');
-isError('let ([x, y, x] = a) x');
-isError('let ([x, [y, [x]]] = a) x');
-isError('let (x = function() { return x}) x()return x;');
-isError('(let (x = function() { return x}) x())return x;');
+isParseError('let (x = 1, x = 2) x');
+isParseError('let ([x, y] = a, {a:x} = b) x');
+isParseError('let ([x, y, x] = a) x');
+isParseError('let ([x, [y, [x]]] = a) x');
+isParseError('let (x = function() { return x}) x()return x;');
+isParseError('(let (x = function() { return x}) x())return x;');
 
 // let block
 test('let (y) {return x;}');
 test('let (y = x) {y++;return "" + y;}', 'unicorns', 'NaN');
 test('let (y = 1) {y = x;return y;}');
 test('let (x) {return "" + x;}', 'unicorns', 'undefined');
 test('let ([] = x) {return x;}');
 test('let (x) {}return x;');
@@ -128,20 +140,20 @@ test('let ([x, [y, z]] = [x, x + 1]) {re
 test('let ([{x: [x]}, {y1: y, z1: z}] = [x, x + 1]) {return x;}',{x:['ponies']});
 test('let (y = x[1]) {let (x = x[0]) {try {let (y = "unicorns") {throw y;}} catch (e) {return x + y;}}}', ['pon','ies']);
 test('let (x = x) {try {let (x = "unicorns") eval("throw x");} catch (e) {return x;}}');
 test('let (y = x) {return function () {return eval("y");}();}');
 test('return eval("let (y = x) {y;}");');
 test('let (y = x) {eval("var y = 2");return y;}', 'ponies', 2);
 test('"use strict";let (y = x) {eval("var y = 2");return y;}');
 test('this.y = x;let (y = 1) {return this.eval("y");}');
-isError('let (x = 1, x = 2) {x}');
-isError('let ([x, y] = a, {a:x} = b) {x}');
-isError('let ([x, y, x] = a) {x}');
-isError('let ([x, [y, [x]]] = a) {x}');
+isParseError('let (x = 1, x = 2) {x}');
+isParseError('let ([x, y] = a, {a:x} = b) {x}');
+isParseError('let ([x, y, x] = a) {x}');
+isParseError('let ([x, [y, [x]]] = a) {x}');
 
 // var declarations
 test('var y;return x;');
 test('var y = x;return x;');
 test('var [] = x;return x;');
 test('var [, ] = x;return x;');
 test('var [, , , , ] = x;return x;');
 test('var x = x;return x;');
@@ -166,25 +178,20 @@ test('var [{x: [x]}, {y1: y, z1: z}] = [
 test('if (x) {var y = x;return x;}');
 test('if (x) {y = x;var y = y;return y;}');
 test('if (x) {var z = y;var [y] = x;z += y;}return z;', ['-'], 'undefined-');
 
 // let declaration in context
 test('if (x) {let y;return x;}');
 test('if (x) {let x;return "" + x;}', 'unicorns', 'undefined');
 test('if (x) {let y = x;return x;}');
-test('if (x) {y = x;let y = y;return y;}');
-test('if (x) {var z = y;let [y] = x;z += y;}return z;', ['-'], 'undefined-');
 test('if (x) {let y = x;return x;}');
 test('if (x) {let [] = x;return x;}');
 test('if (x) {let [, ] = x;return x;}');
 test('if (x) {let [, , , , ] = x;return x;}');
-test('if (x) {let x = x;return "" + x;}', 'unicorns', 'undefined');
-test('if (x) {let y = y;return "" + y;}', 'unicorns', 'undefined');
-test('if (x) {let x = eval("x");return "" + x;}', 'unicorns', 'undefined');
 test('if (x) {let y = (let (x = x + 1) x) + 1;return y;}', 1, 3);
 test('if (x) {let y = (let (x = eval("x") + 1) eval("x")) + 1;return eval("y");}', 1, 3);
 test('if (x) {let X = x + 1, y = x;return y;}');
 test('if (x) {let X = x + 1, [] = X, [[, , ]] = X, y = x;return y;}');
 test('if (x) {let [{a: X}] = x, [, {b: Y}] = x;var XX = X + 1, YY = Y + 2;return XX + YY;}', [{a:"p"},{b:"p"}], "p1p2");
 test('if (x) {let [[a, [b, c]]] = [[x, []]];return a;}');
 test('if (x) {let [X] = [x];return X;}');
 test('if (x) {let [y] = [x];return y;}');
@@ -209,21 +216,21 @@ test('x.foo;{let y = x;return y;}');
 test('x.foo;if (x) {x.bar;let y = x;return y;}');
 test('if (x) {let y = x;return function () {return eval("y");}();}');
 test('return eval("let y = x; y");');
 test('if (x) {let y = x;eval("var y = 2");return y;}', 'ponies', 2);
 test('"use strict";if (x) {let y = x;eval("var y = 2");return y;}');
 test('"use strict";if (x) {let y = x;eval("let y = 2");return y;}');
 test('"use strict";if (x) {let y = 1;return eval("let y = x;y;");}');
 test('this.y = x;if (x) {let y = 1;return this.eval("y");}');
-isError('if (x) {let (x = 1, x = 2) {x}}');
-isError('if (x) {let ([x, y] = a, {a:x} = b) {x}}');
-isError('if (x) {let ([x, y, x] = a) {x}}');
-isError('if (x) {let ([x, [y, [x]]] = a) {x}}');
-isError('let ([x, y] = x) {let x;}');
+isParseError('if (x) {let (x = 1, x = 2) {x}}');
+isParseError('if (x) {let ([x, y] = a, {a:x} = b) {x}}');
+isParseError('if (x) {let ([x, y, x] = a) {x}}');
+isParseError('if (x) {let ([x, [y, [x]]] = a) {x}}');
+isParseError('let ([x, y] = x) {let x;}');
 
 // for(;;)
 test('for (;;) {return x;}');
 test('for (let y = 1;;) {return x;}');
 test('for (let y = 1;; ++y) {return x;}');
 test('for (let y = 1; ++y;) {return x;}');
 test('for (let (x = 1) x; x != 1; ++x) {return x;}');
 test('for (let (x = 1, [{a: b, c: d}] = [{a: 1, c: 2}]) x; x != 1; ++x) {return x;}');
@@ -248,20 +255,20 @@ test('try {for (let x = eval("throw x");
 test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie');
 test('for (let y = x;;) {let x;return y;}');
 test('for (let y = x;;) {let y;return x;}');
 test('for (let y;;) {let y;return x;}');
 test('for (let a = x;;) {let c = x, d = x;return c;}');
 test('for (let [a, b] = x;;) {let c = x, d = x;return c;}');
 test('for (let [a] = (1, [x]);;) {return a;}');
 test('for (let [a] = (1, x, 1, x);;) {return a;}', ['ponies']);
-isError('for (let x = 1, x = 2;;) {}');
-isError('for (let [x, y] = a, {a:x} = b;;) {}');
-isError('for (let [x, y, x] = a;;) {}');
-isError('for (let [x, [y, [x]]] = a;;) {}');
+isParseError('for (let x = 1, x = 2;;) {}');
+isParseError('for (let [x, y] = a, {a:x} = b;;) {}');
+isParseError('for (let [x, y, x] = a;;) {}');
+isParseError('for (let [x, [y, [x]]] = a;;) {}');
 
 // for(in)
 test('for (let i in x) {return x;}');
 test('for (let i in x) {let y;return x;}');
 test('for each (let [a, b] in x) {let y;return x;}');
 test('for (let i in x) {let (i = x) {return i;}}');
 test('for (let i in x) {let i = x;return i;}');
 test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]);
@@ -278,30 +285,45 @@ test('for (let i in x) {break;}return x;
 test('for (let i in x) {break;}return eval("x");');
 test('for (let x in x) {break;}return x;');
 test('for (let x in x) {break;}return eval("x");');
 test('a:for (let i in x) {for (let j in x) {break a;}}return x;');
 test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");');
 test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true});
 test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}');
 test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']);
-isError('for (let [x, x] in o) {}');
-isError('for (let [x, y, x] in o) {}');
-isError('for (let [x, [y, [x]]] in o) {}');
+isParseError('for (let [x, x] in o) {}');
+isParseError('for (let [x, y, x] in o) {}');
+isParseError('for (let [x, [y, [x]]] in o) {}');
 
 // genexps
 test('return (i for (i in x)).next();', {ponies:true});
 test('return (eval("i") for (i in x)).next();', {ponies:true});
 test('return (eval("i") for (i in eval("x"))).next();', {ponies:true});
 test('try {return (eval("throw i") for (i in x)).next();} catch (e) {return e;}', {ponies:true});
 
 // array comprehension
 test('return [i for (i in x)][0];', {ponies:true});
 test('return [eval("i") for (i in x)][0];', {ponies:true});
 test('return [eval("i") for (i in eval("x"))][0];', {ponies:true});
 test('try {return [eval("throw i") for (i in x)][0];} catch (e) {return e;}', {ponies:true});
 
 // don't forget about switch craziness
 test('var y = 3;switch (function () {return eval("y");}()) {case 3:let y;return x;default:;}');
 test('switch (x) {case 3:let y;return 3;case 4:let z;return 4;default:return x;}');
-test('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
 test('switch (x) {case 3:default:let y;let (y = x) {return y;}}');
-isError('switch (x) {case 3:let y;return 3;case 4:let y;return 4;default:;}');
+isParseError('switch (x) {case 3:let y;return 3;case 4:let y;return 4;default:;}');
+
+// TDZ checks
+isReferenceError('x + 1; let x = 42;');
+isReferenceError('x = 42; let x;');
+isReferenceError('inner(); function inner() { x++; } let x;');
+isReferenceError('inner(); let x; function inner() { x++; }');
+isReferenceError('inner(); let x; function inner() { function innerer() { x++; } innerer(); }');
+isReferenceError('let x; var inner = function () { y++; }; inner(); let y;');
+isReferenceError('let x = x;');
+isReferenceError('let [x] = [x];');
+isReferenceError('let {x} = {x:x};');
+isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
+isReferenceError('let x = function() {} ? x() : function() {}');
+
+// redecl with function statements
+isParseError('let a; function a() {}');
--- a/js/src/jit-test/tests/debug/Environment-variables.js
+++ b/js/src/jit-test/tests/debug/Environment-variables.js
@@ -25,18 +25,16 @@ var cases = [
     "function f(x) { @@ } f(VAL);",
     "function f([w, x]) { @@ } f([0, VAL]);",
     "function f({v: x}) { @@ } f({v: VAL});",
     "function f([w, {v: x}]) { @@ } f([0, {v: VAL}]);",
 
     // bindings in functions
     "function f() { var x = VAL; @@ } f();",
     "function f() { let x = VAL; @@ } f();",
-    "function f([x]) { let x = VAL; @@ } f(['fail']);",
-    "function f(x) { { let x = VAL; @@ } } f('fail');",
     "function f() { function x() {} x = VAL; @@ } f();",
 
     // dynamic bindings
     "function f(s) { eval(s); @@ } f('var x = VAL');",
     "function f(s) { let (x = 'fail') { eval(s); } x = VAL; @@ } f('var x;');",
     "var x = VAL; function f(s) { eval('var x = 0;'); eval(s); @@ } f('delete x;');",
     "function f(obj) { with (obj) { @@ } } f({x: VAL});",
     "function f(obj) { with (obj) { @@ } } f(Object.create({x: VAL}));",
--- a/js/src/jit-test/tests/debug/Frame-eval-21.js
+++ b/js/src/jit-test/tests/debug/Frame-eval-21.js
@@ -1,10 +1,13 @@
 // Eval-in-frame with different type on baseline frame with let-scoping
 
+// FIXMEshu disabled until TDZ checks are implemented in the JITs.
+quit(0);
+
 load(libdir + "jitopts.js");
 
 if (!jitTogglesMatch(Opts_BaselineEager))
   quit(0);
 
 withJitOptions(Opts_BaselineEager, function () {
   var g = newGlobal();
   var dbg = new Debugger;
--- a/js/src/jit-test/tests/jaeger/bug583672.js
+++ b/js/src/jit-test/tests/jaeger/bug583672.js
@@ -4,27 +4,27 @@
         this.y = w
         this.y = (void 0)
         Object
     }
     for (a in [0, 0, 0, 0])
     {
         new f
     }
-    let w = {}
+    var w = {}
 })()
 
 /* Make sure that MICs don't have the same bug. */
 x = Object();
 (function () {
     function f()
     {
         x = w
         x = (void 0)
         Object
     }
     for (a in [0, 0, 0, 0])
     {
         new f
     }
-    let w = {}
+    var w = {}
 })()
 /* Don't assert. */
--- a/js/src/jit-test/tests/parallel/write-array.js
+++ b/js/src/jit-test/tests/parallel/write-array.js
@@ -1,14 +1,14 @@
 load(libdir + "parallelarray-helpers.js");
 
 function buildSimple() {
   assertParallelModesCommute(["seq", "par"], function(m) {
     return Array.buildPar(256, function(i) {
-      let obj = [i, 1, 2];
+      var obj = [i, 1, 2];
       obj[0] += 1;
       obj[1] += 1;
       obj[2] += 1;
       return obj;
     }, m);
   });
 }
 
--- a/js/src/jit-test/tests/parallel/write-obj.js
+++ b/js/src/jit-test/tests/parallel/write-obj.js
@@ -1,14 +1,14 @@
 load(libdir + "parallelarray-helpers.js");
 
 function buildSimple() {
   assertParallelModesCommute(["seq", "par"], function(m) {
     Array.buildPar(256, function(i) {
-      let obj = { x: i, y: i + 1, z: i + 2 };
+      var obj = { x: i, y: i + 1, z: i + 2 };
       obj.x += 1;
       obj.y += 1;
       obj.z += 1;
       return obj;
     }, m);
   });
 }
 
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -52,17 +52,17 @@ BaselineFrame::trace(JSTracer *trc, JitF
         gc::MarkScriptRoot(trc, &evalScript_, "baseline-evalscript");
 
     if (hasArgsObj())
         gc::MarkObjectRoot(trc, &argsObj_, "baseline-args-obj");
 
     // Mark locals and stack values.
     JSScript *script = this->script();
     size_t nfixed = script->nfixed();
-    size_t nlivefixed = script->nfixedvars();
+    size_t nlivefixed = script->nbodyfixed();
 
     if (nfixed != nlivefixed) {
         jsbytecode *pc;
         NestedScopeObject *staticScope;
 
         frameIterator.baselineScriptAndPc(nullptr, &pc);
         staticScope = script->getStaticScope(pc);
         while (staticScope && !staticScope->is<StaticBlockObject>())
@@ -70,35 +70,39 @@ BaselineFrame::trace(JSTracer *trc, JitF
 
         if (staticScope) {
             StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
             nlivefixed = blockObj.localOffset() + blockObj.numVariables();
         }
     }
 
     JS_ASSERT(nlivefixed <= nfixed);
-    JS_ASSERT(nlivefixed >= script->nfixedvars());
+    JS_ASSERT(nlivefixed >= script->nbodyfixed());
 
     // NB: It is possible that numValueSlots() could be zero, even if nfixed is
     // nonzero.  This is the case if the function has an early stack check.
     if (numValueSlots() == 0)
         return;
 
     JS_ASSERT(nfixed <= numValueSlots());
 
     if (nfixed == nlivefixed) {
         // All locals are live.
         MarkLocals(this, trc, 0, numValueSlots());
     } else {
         // Mark operand stack.
         MarkLocals(this, trc, nfixed, numValueSlots());
 
-        // Clear dead locals.
-        while (nfixed > nlivefixed)
-            unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setUndefined();
+        // Clear non-magic dead locals. Magic values such as
+        // JS_UNINITIALIZED_LET need to be left as is for correctness.
+        while (nfixed > nlivefixed) {
+            --nfixed;
+            if (!unaliasedLocal(nfixed, DONT_CHECK_ALIASING).isMagic())
+                unaliasedLocal(nfixed, DONT_CHECK_ALIASING).setUndefined();
+        }
 
         // Mark live locals.
         MarkLocals(this, trc, 0, nlivefixed);
     }
 }
 
 bool
 BaselineFrame::copyRawFrameSlots(AutoValueVector *vec) const
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -5957,17 +5957,18 @@ DoBindNameFallback(JSContext *cx, Baseli
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "BindName(%s)", js_CodeName[JSOp(*pc)]);
 
     JS_ASSERT(op == JSOP_BINDNAME);
 
     RootedPropertyName name(cx, frame->script()->getName(pc));
 
     RootedObject scope(cx);
-    if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
+    RootedShape shape(cx);
+    if (!LookupNameUnqualified(cx, name, scopeChain, &scope, &shape))
         return false;
 
     res.setObject(*scope);
     return true;
 }
 
 typedef bool (*DoBindNameFallbackFn)(JSContext *, BaselineFrame *, ICBindName_Fallback *,
                                      HandleObject, MutableHandleValue);
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -182,30 +182,30 @@ class CompileInfo
             JS_ASSERT(fun_->isTenured());
         }
 
         osrStaticScope_ = osrPc ? script->getStaticScope(osrPc) : nullptr;
 
         nimplicit_ = StartArgSlot(script)                   /* scope chain and argument obj */
                    + (fun ? 1 : 0);                         /* this */
         nargs_ = fun ? fun->nargs() : 0;
-        nfixedvars_ = script->nfixedvars();
+        nbodyfixed_ = script->nbodyfixed();
         nlocals_ = script->nfixed();
         nstack_ = script->nslots() - script->nfixed();
         nslots_ = nimplicit_ + nargs_ + nlocals_ + nstack_;
     }
 
     CompileInfo(unsigned nlocals, ExecutionMode executionMode)
       : script_(nullptr), fun_(nullptr), osrPc_(nullptr), osrStaticScope_(nullptr),
         constructing_(false), executionMode_(executionMode), scriptNeedsArgsObj_(false),
         inlineScriptTree_(nullptr)
     {
         nimplicit_ = 0;
         nargs_ = 0;
-        nfixedvars_ = 0;
+        nbodyfixed_ = 0;
         nlocals_ = nlocals;
         nstack_ = 1;  /* For FunctionCompiler::pushPhiInput/popPhiOutput */
         nslots_ = nlocals_ + nstack_;
     }
 
     JSScript *script() const {
         return script_;
     }
@@ -286,20 +286,20 @@ class CompileInfo
     // maybe argumentsobject and this value.
     unsigned nimplicit() const {
         return nimplicit_;
     }
     // Number of arguments (without counting this value).
     unsigned nargs() const {
         return nargs_;
     }
-    // Number of slots needed for "fixed vars".  Note that this is only non-zero
-    // for function code.
-    unsigned nfixedvars() const {
-        return nfixedvars_;
+    // Number of slots needed for fixed body-level bindings.  Note that this
+    // is only non-zero for function code.
+    unsigned nbodyfixed() const {
+        return nbodyfixed_;
     }
     // Number of slots needed for all local variables.  This includes "fixed
     // vars" (see above) and also block-scoped locals.
     unsigned nlocals() const {
         return nlocals_;
     }
     unsigned ninvoke() const {
         return nslots_ - nstack_;
@@ -372,19 +372,19 @@ class CompileInfo
             return false;
 
         uint32_t arg = index - firstArgSlot();
         if (arg < nargs())
             return script()->formalIsAliased(arg);
 
         uint32_t local = index - firstLocalSlot();
         if (local < nlocals()) {
-            // First, check if this local is a var.
-            if (local < nfixedvars())
-                return script()->varIsAliased(local);
+            // First, check if this local is body-level.
+            if (local < nbodyfixed())
+                return script()->bodyLevelLocalIsAliased(local);
 
             // Otherwise, it might be part of a block scope.
             for (; staticScope; staticScope = staticScope->enclosingNestedScope()) {
                 if (!staticScope->is<StaticBlockObject>())
                     continue;
                 StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
                 if (blockObj.localOffset() < local) {
                     if (local - blockObj.localOffset() < blockObj.numVariables())
@@ -461,17 +461,17 @@ class CompileInfo
         }
 
         return false;
     }
 
   private:
     unsigned nimplicit_;
     unsigned nargs_;
-    unsigned nfixedvars_;
+    unsigned nbodyfixed_;
     unsigned nlocals_;
     unsigned nstack_;
     unsigned nslots_;
     JSScript *script_;
     JSFunction *fun_;
     jsbytecode *osrPc_;
     NestedScopeObject *osrStaticScope_;
     bool constructing_;
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -4202,17 +4202,18 @@ BindNameIC::update(JSContext *cx, size_t
     IonScript *ion = outerScript->ionScript();
     BindNameIC &cache = ion->getCache(cacheIndex).toBindName();
     HandlePropertyName name = cache.name();
 
     RootedObject holder(cx);
     if (scopeChain->is<GlobalObject>()) {
         holder = scopeChain;
     } else {
-        if (!LookupNameUnqualified(cx, name, scopeChain, &holder))
+        RootedShape shape(cx);
+        if (!LookupNameUnqualified(cx, name, scopeChain, &holder, &shape))
             return nullptr;
     }
 
     // Stop generating new stubs once we hit the stub count limit, see
     // GetPropertyCache.
     if (cache.canAttachStub()) {
         if (scopeChain->is<GlobalObject>()) {
             if (!cache.attachGlobal(cx, outerScript, ion, scopeChain))
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -104,16 +104,17 @@ MSG_DEF(JSMSG_JSON_CYCLIC_VALUE,       1
 
 // Runtime errors
 MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS,      1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}")
 MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS,     0, JSEXN_REFERENCEERR, "invalid assignment left-hand side")
 MSG_DEF(JSMSG_BAD_PROTOTYPE,           1, JSEXN_TYPEERR, "'prototype' property of {0} is not an object")
 MSG_DEF(JSMSG_IN_NOT_OBJECT,           1, JSEXN_TYPEERR, "invalid 'in' operand {0}")
 MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 0, JSEXN_RANGEERR, "too many constructor arguments")
 MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 0, JSEXN_RANGEERR, "too many function arguments")
+MSG_DEF(JSMSG_UNINITIALIZED_LEXICAL,   1, JSEXN_REFERENCEERR, "can't access let declaration `{0}' before initialization")
 
 // Date
 MSG_DEF(JSMSG_INVALID_DATE,            0, JSEXN_RANGEERR, "invalid date")
 MSG_DEF(JSMSG_BAD_TOISOSTRING_PROP,    0, JSEXN_TYPEERR, "toISOString property is not callable")
 
 // String
 MSG_DEF(JSMSG_BAD_URI,                 0, JSEXN_URIERR, "malformed URI sequence")
 MSG_DEF(JSMSG_INVALID_NORMALIZE_FORM,  0, JSEXN_RANGEERR, "form must be one of 'NFC', 'NFD', 'NFKC', or 'NFKD'")
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -134,18 +134,21 @@ GetValueType(const Value &val)
     if (val.isObject())
         return Type::ObjectType(&val.toObject());
     return Type::PrimitiveType(val.extractNonDoubleType());
 }
 
 inline Type
 GetMaybeOptimizedOutValueType(const Value &val)
 {
-    if (val.isMagic() && val.whyMagic() == JS_OPTIMIZED_OUT)
+    if (val.isMagic() && (val.whyMagic() == JS_OPTIMIZED_OUT ||
+                          val.whyMagic() == JS_UNINITIALIZED_LEXICAL))
+    {
         return Type::UnknownType();
+    }
     return GetValueType(val);
 }
 
 inline TypeFlags
 PrimitiveTypeFlag(JSValueType type)
 {
     switch (type) {
       case JSVAL_TYPE_UNDEFINED:
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -4760,52 +4760,59 @@ js::LookupNameNoGC(JSContext *cx, Proper
         }
     }
 
     return true;
 }
 
 bool
 js::LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
-                                MutableHandleObject objp)
+                                MutableHandleObject objp, MutableHandleShape propp)
 {
     RootedId id(cx, NameToId(name));
 
     RootedObject pobj(cx);
-    RootedShape prop(cx);
 
     RootedObject scope(cx, scopeChain);
     for (; !scope->is<GlobalObject>(); scope = scope->enclosingScope()) {
-        if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop))
+        if (!JSObject::lookupGeneric(cx, scope, id, &pobj, propp))
             return false;
-        if (prop)
+        if (propp)
             break;
     }
 
     objp.set(scope);
     return true;
 }
 
 bool
 js::LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
-                          MutableHandleObject objp)
+                          MutableHandleObject objp, MutableHandleShape propp)
 {
     RootedId id(cx, NameToId(name));
 
     RootedObject pobj(cx);
-    RootedShape prop(cx);
 
     RootedObject scope(cx, scopeChain);
     for (; !scope->isUnqualifiedVarObj(); scope = scope->enclosingScope()) {
-        if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop))
+        if (!JSObject::lookupGeneric(cx, scope, id, &pobj, propp))
             return false;
-        if (prop)
+        if (propp)
             break;
     }
 
+    // If the name was found not on the scope object itself, null out the
+    // shape, which is passed as an out pointer to determine uninitialized
+    // lexical slots. In the case when the name is not found on the scope
+    // object itself, it cannot be an uninitialized lexical slot.
+    //
+    // See the JSOP_BINDNAME case in the Interpreter.
+    if (pobj != scope)
+        propp.set(nullptr);
+
     objp.set(scope);
     return true;
 }
 
 template <AllowGC allowGC>
 bool
 js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup,
                    typename MaybeRooted<JSObject*, allowGC>::HandleType obj,
@@ -4885,18 +4892,18 @@ NativeGetInline(JSContext *cx,
                 typename MaybeRooted<JSObject*, allowGC>::HandleType pobj,
                 typename MaybeRooted<Shape*, allowGC>::HandleType shape,
                 typename MaybeRooted<Value, allowGC>::MutableHandleType vp)
 {
     JS_ASSERT(pobj->isNative());
 
     if (shape->hasSlot()) {
         vp.set(pobj->nativeGetSlot(shape->slot()));
-        JS_ASSERT(!vp.isMagic());
-        JS_ASSERT_IF(!pobj->hasSingletonType() &&
+        JS_ASSERT_IF(!vp.isMagic(JS_UNINITIALIZED_LEXICAL) &&
+                     !pobj->hasSingletonType() &&
                      !pobj->template is<ScopeObject>() &&
                      shape->hasDefaultGetter(),
                      js::types::TypeHasProperty(cx, pobj->type(), shape->propid(), vp));
     } else {
         vp.setUndefined();
     }
     if (shape->hasDefaultGetter())
         return true;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1463,28 +1463,27 @@ LookupNameNoGC(JSContext *cx, PropertyNa
  * Like LookupName except returns the global object if 'name' is not found in
  * any preceding scope.
  *
  * Additionally, pobjp and propp are not needed by callers so they are not
  * returned.
  */
 extern bool
 LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
-                            MutableHandleObject objp);
+                            MutableHandleObject objp, MutableHandleShape propp);
 
 /*
  * Like LookupName except returns the unqualified var object if 'name' is not found in
  * any preceding scope. Normally the unqualified var object is the global.
  *
- * Additionally, pobjp and propp are not needed by callers so they are not
- * returned.
+ * Additionally, pobjp is not needed by callers so it is not returned.
  */
 extern bool
 LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
-                      MutableHandleObject objp);
+                      MutableHandleObject objp, MutableHandleShape propp);
 
 }
 
 extern JSObject *
 js_FindVariableScope(JSContext *cx, JSFunction **funp);
 
 
 namespace js {
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1630,17 +1630,17 @@ ExpressionDecompiler::getArg(unsigned sl
     JS_ASSERT(slot < script->bindings.count());
     return (*localNames)[slot].name();
 }
 
 JSAtom *
 ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc)
 {
     JS_ASSERT(local < script->nfixed());
-    if (local < script->nfixedvars()) {
+    if (local < script->nbodyfixed()) {
         JS_ASSERT(fun);
         uint32_t slot = local + fun->nargs();
         JS_ASSERT(slot < script->bindings.count());
         return (*localNames)[slot].name();
     }
     for (NestedScopeObject *chain = script->getStaticScope(pc);
          chain;
          chain = chain->enclosingNestedScope()) {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -71,31 +71,37 @@ Bindings::argumentsVarIndex(ExclusiveCon
     BindingIter bi(bindings);
     while (bi->name() != arguments)
         bi++;
     return bi.frameIndex();
 }
 
 bool
 Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
-                                   unsigned numArgs, uint32_t numVars,
-                                   Binding *bindingArray, uint32_t numBlockScoped)
+                                   uint32_t numArgs, uint32_t numVars,
+                                   uint32_t numBodyLevelLexicals, uint32_t numBlockScoped,
+                                   Binding *bindingArray)
 {
     JS_ASSERT(!self->callObjShape_);
     JS_ASSERT(self->bindingArrayAndFlag_ == TEMPORARY_STORAGE_BIT);
     JS_ASSERT(!(uintptr_t(bindingArray) & TEMPORARY_STORAGE_BIT));
     JS_ASSERT(numArgs <= ARGC_LIMIT);
     JS_ASSERT(numVars <= LOCALNO_LIMIT);
     JS_ASSERT(numBlockScoped <= LOCALNO_LIMIT);
-    JS_ASSERT(numVars <= LOCALNO_LIMIT - numBlockScoped);
-    JS_ASSERT(UINT32_MAX - numArgs >= numVars + numBlockScoped);
+    JS_ASSERT(numBodyLevelLexicals <= LOCALNO_LIMIT);
+    uint64_t totalSlots = uint64_t(numVars) +
+                          uint64_t(numBodyLevelLexicals) +
+                          uint64_t(numBlockScoped);
+    JS_ASSERT(totalSlots <= LOCALNO_LIMIT);
+    JS_ASSERT(UINT32_MAX - numArgs >= totalSlots);
 
     self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT;
     self->numArgs_ = numArgs;
     self->numVars_ = numVars;
+    self->numBodyLevelLexicals_ = numBodyLevelLexicals;
     self->numBlockScoped_ = numBlockScoped;
 
     // Get the initial shape to use when creating CallObjects for this script.
     // After creation, a CallObject's shape may change completely (via direct eval() or
     // other operations that mutate the lexical scope). However, since the
     // lexical bindings added to the initial shape are permanent and the
     // allocKind/nfixed of a CallObject cannot change, one may assume that the
     // slot location (whether in the fixed or dynamic slots) of a variable is
@@ -103,20 +109,35 @@ Bindings::initWithTemporaryStorage(Exclu
     // JITs when interpreting/compiling aliasedvar ops.)
 
     // Since unaliased variables are, by definition, only accessed by local
     // operations and never through the scope chain, only give shapes to
     // aliased variables. While the debugger may observe any scope object at
     // any time, such accesses are mediated by DebugScopeProxy (see
     // DebugScopeProxy::handleUnaliasedAccess).
     uint32_t nslots = CallObject::RESERVED_SLOTS;
+    uint32_t aliasedBodyLevelLexicalBegin = UINT16_MAX;
     for (BindingIter bi(self); bi; bi++) {
-        if (bi->aliased())
+        if (bi->aliased()) {
+            // Per ES6, lexical bindings cannot be accessed until
+            // initialized. Remember the first aliased slot that is a
+            // body-level let, so that they may be initialized to sentinel
+            // magic values.
+            if (numBodyLevelLexicals > 0 &&
+                nslots < aliasedBodyLevelLexicalBegin &&
+                bi->kind() == Binding::VARIABLE &&
+                bi.frameIndex() >= numVars)
+            {
+                aliasedBodyLevelLexicalBegin = nslots;
+            }
+
             nslots++;
+        }
     }
+    self->aliasedBodyLevelLexicalBegin_ = aliasedBodyLevelLexicalBegin;
 
     // Put as many of nslots inline into the object header as possible.
     uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots));
 
     // Start with the empty shape and then append one shape per aliased binding.
     RootedShape shape(cx,
         EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr), nullptr, nullptr,
                                     nfixed, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE));
@@ -189,50 +210,54 @@ Bindings::clone(JSContext *cx, InternalB
     JS_ASSERT(off >= 0);
     JS_ASSERT(size_t(off) <= srcScript->dataSize());
     Binding *dstPackedBindings = (Binding *)(dstScriptData + off);
 
     /*
      * Since atoms are shareable throughout the runtime, we can simply copy
      * the source's bindingArray directly.
      */
-    if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray(),
-                                  src.numBlockScoped()))
+    if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(),
+                                  src.numBodyLevelLexicals(), src.numBlockScoped(),
+                                  src.bindingArray()))
+    {
         return false;
+    }
+
     self->switchToScriptStorage(dstPackedBindings);
     return true;
 }
 
 /* static */ Bindings
 GCMethods<Bindings>::initial()
 {
     return Bindings();
 }
 
 template<XDRMode mode>
 static bool
-XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars,
-                  HandleScript script, unsigned numBlockScoped)
+XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, uint16_t numArgs, uint32_t numVars,
+                  uint16_t numBodyLevelLexicals, uint16_t numBlockScoped, HandleScript script)
 {
     JSContext *cx = xdr->cx();
 
     if (mode == XDR_ENCODE) {
         for (BindingIter bi(script); bi; bi++) {
             RootedAtom atom(cx, bi->name());
             if (!XDRAtom(xdr, &atom))
                 return false;
         }
 
         for (BindingIter bi(script); bi; bi++) {
             uint8_t u8 = (uint8_t(bi->kind()) << 1) | uint8_t(bi->aliased());
             if (!xdr->codeUint8(&u8))
                 return false;
         }
     } else {
-        uint32_t nameCount = numArgs + numVars;
+        uint32_t nameCount = numArgs + numVars + numBodyLevelLexicals;
 
         AutoValueVector atoms(cx);
         if (!atoms.resize(nameCount))
             return false;
         for (uint32_t i = 0; i < nameCount; i++) {
             RootedAtom atom(cx);
             if (!XDRAtom(xdr, &atom))
                 return false;
@@ -250,19 +275,22 @@ XDRScriptBindings(XDRState<mode> *xdr, L
             PropertyName *name = atoms[i].toString()->asAtom().asPropertyName();
             Binding::Kind kind = Binding::Kind(u8 >> 1);
             bool aliased = bool(u8 & 1);
 
             bindingArray[i] = Binding(name, kind, aliased);
         }
 
         InternalBindingsHandle bindings(script, &script->bindings);
-        if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray,
-                                                numBlockScoped))
+        if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars,
+                                                numBodyLevelLexicals, numBlockScoped,
+                                                bindingArray))
+        {
             return false;
+        }
     }
 
     return true;
 }
 
 bool
 Bindings::bindingIsAliased(uint32_t bindingIndex)
 {
@@ -426,27 +454,35 @@ js::XDRScriptConst(XDRState<XDR_DECODE> 
 
 // Code LazyScript's free variables.
 template<XDRMode mode>
 static bool
 XDRLazyFreeVariables(XDRState<mode> *xdr, MutableHandle<LazyScript *> lazy)
 {
     JSContext *cx = xdr->cx();
     RootedAtom atom(cx);
-    HeapPtrAtom *freeVariables = lazy->freeVariables();
+    uint8_t isHoistedUse;
+    LazyScript::FreeVariable *freeVariables = lazy->freeVariables();
     size_t numFreeVariables = lazy->numFreeVariables();
     for (size_t i = 0; i < numFreeVariables; i++) {
-        if (mode == XDR_ENCODE)
-            atom = freeVariables[i];
+        if (mode == XDR_ENCODE) {
+            atom = freeVariables[i].atom();
+            isHoistedUse = freeVariables[i].isHoistedUse();
+        }
 
         if (!XDRAtom(xdr, &atom))
             return false;
-
-        if (mode == XDR_DECODE)
-            freeVariables[i] = atom;
+        if (!xdr->codeUint8(&isHoistedUse))
+            return false;
+
+        if (mode == XDR_DECODE) {
+            freeVariables[i] = LazyScript::FreeVariable(atom);
+            if (isHoistedUse)
+                freeVariables[i].setIsHoistedUse();
+        }
     }
 
     return true;
 }
 
 // Code the missing part needed to re-create a LazyScript from a JSScript.
 template<XDRMode mode>
 static bool
@@ -557,29 +593,33 @@ js::XDRScript(XDRState<mode> *xdr, Handl
     JSContext *cx = xdr->cx();
     RootedScript script(cx);
     natoms = nsrcnotes = 0;
     nconsts = nobjects = nregexps = ntrynotes = nblockscopes = 0;
 
     /* XDR arguments and vars. */
     uint16_t nargs = 0;
     uint16_t nblocklocals = 0;
+    uint16_t nbodylevellexicals = 0;
     uint32_t nvars = 0;
     if (mode == XDR_ENCODE) {
         script = scriptp.get();
         JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
 
         nargs = script->bindings.numArgs();
         nblocklocals = script->bindings.numBlockScoped();
+        nbodylevellexicals = script->bindings.numBodyLevelLexicals();
         nvars = script->bindings.numVars();
     }
     if (!xdr->codeUint16(&nargs))
         return false;
     if (!xdr->codeUint16(&nblocklocals))
         return false;
+    if (!xdr->codeUint16(&nbodylevellexicals))
+        return false;
     if (!xdr->codeUint32(&nvars))
         return false;
 
     if (mode == XDR_ENCODE)
         length = script->length();
     if (!xdr->codeUint32(&length))
         return false;
 
@@ -716,17 +756,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
                                   options, /* staticLevel = */ 0, sourceObject, 0, 0);
         if (!script)
             return false;
     }
 
     /* JSScript::partiallyInit assumes script->bindings is fully initialized. */
     LifoAllocScope las(&cx->tempLifoAlloc());
-    if (!XDRScriptBindings(xdr, las, nargs, nvars, script, nblocklocals))
+    if (!XDRScriptBindings(xdr, las, nargs, nvars, nbodylevellexicals, nblocklocals, script))
         return false;
 
     if (mode == XDR_DECODE) {
         if (!JSScript::partiallyInit(cx, script, nconsts, nobjects, nregexps, ntrynotes,
                                      nblockscopes, nTypeSets))
         {
             return false;
         }
@@ -3364,19 +3404,22 @@ LazyScript::markChildren(JSTracer *trc)
         MarkObject(trc, &sourceObject_, "sourceObject");
 
     if (enclosingScope_)
         MarkObject(trc, &enclosingScope_, "enclosingScope");
 
     if (script_)
         MarkScript(trc, &script_, "realScript");
 
-    HeapPtrAtom *freeVariables = this->freeVariables();
-    for (size_t i = 0; i < numFreeVariables(); i++)
-        MarkString(trc, &freeVariables[i], "lazyScriptFreeVariable");
+    // We rely on the fact that atoms are always tenured.
+    FreeVariable *freeVariables = this->freeVariables();
+    for (size_t i = 0; i < numFreeVariables(); i++) {
+        JSAtom *atom = freeVariables[i].atom();
+        MarkStringUnbarriered(trc, &atom, "lazyScriptFreeVariable");
+    }
 
     HeapPtrFunction *innerFunctions = this->innerFunctions();
     for (size_t i = 0; i < numInnerFunctions(); i++)
         MarkObject(trc, &innerFunctions[i], "lazyScriptInnerFunction");
 }
 
 void
 LazyScript::finalize(FreeOp *fop)
@@ -3559,17 +3602,23 @@ JSScript::argumentsOptimizationFailed(JS
     }
 
     return true;
 }
 
 bool
 JSScript::varIsAliased(uint32_t varSlot)
 {
-    return bindings.bindingIsAliased(bindings.numArgs() + varSlot);
+    return bodyLevelLocalIsAliased(varSlot);
+}
+
+bool
+JSScript::bodyLevelLocalIsAliased(uint32_t localSlot)
+{
+    return bindings.bindingIsAliased(bindings.numArgs() + localSlot);
 }
 
 bool
 JSScript::formalIsAliased(unsigned argSlot)
 {
     return bindings.bindingIsAliased(argSlot);
 }
 
@@ -3636,17 +3685,17 @@ LazyScript::CreateRaw(ExclusiveContext *
     };
 
     packed = packedFields;
 
     // Reset runtime flags to obtain a fresh LazyScript.
     p.hasBeenCloned = false;
     p.treatAsRunOnce = false;
 
-    size_t bytes = (p.numFreeVariables * sizeof(HeapPtrAtom))
+    size_t bytes = (p.numFreeVariables * sizeof(FreeVariable))
                  + (p.numInnerFunctions * sizeof(HeapPtrFunction));
 
     ScopedJSFreePtr<uint8_t> table(bytes ? fun->pod_malloc<uint8_t>(bytes) : nullptr);
     if (bytes && !table)
         return nullptr;
 
     LazyScript *res = js_NewGCLazyScript(cx);
     if (!res)
@@ -3696,19 +3745,19 @@ LazyScript::Create(ExclusiveContext *cx,
 
     LazyScript *res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column);
     if (!res)
         return nullptr;
 
     // Fill with dummies, to be GC-safe after the initialization of the free
     // variables and inner functions.
     size_t i, num;
-    HeapPtrAtom *variables = res->freeVariables();
+    FreeVariable *variables = res->freeVariables();
     for (i = 0, num = res->numFreeVariables(); i < num; i++)
-        variables[i].init(dummyAtom);
+        variables[i] = FreeVariable(dummyAtom);
 
     HeapPtrFunction *functions = res->innerFunctions();
     for (i = 0, num = res->numInnerFunctions(); i < num; i++)
         functions[i].init(dummyFun);
 
     return res;
 }
 
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -132,17 +132,18 @@ class Binding
     // packed-word representation.
     uintptr_t bits_;
 
     static const uintptr_t KIND_MASK = 0x3;
     static const uintptr_t ALIASED_BIT = 0x4;
     static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT);
 
   public:
-    // A "binding" is a formal, 'var', or 'const' declaration. A function's
+    // A "binding" is a formal parameter, 'var' (also a stand in for
+    // body-level 'let' declarations), or 'const' declaration. A function's
     // lexical scope is composed of these three kinds of bindings.
     enum Kind { ARGUMENT, VARIABLE, CONSTANT };
 
     explicit Binding() : bits_(0) {}
 
     Binding(PropertyName *name, Kind kind, bool aliased) {
         JS_STATIC_ASSERT(CONSTANT <= KIND_MASK);
         JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0);
@@ -178,18 +179,26 @@ class Bindings
 {
     friend class BindingIter;
     friend class AliasedFormalIter;
 
     HeapPtrShape callObjShape_;
     uintptr_t bindingArrayAndFlag_;
     uint16_t numArgs_;
     uint16_t numBlockScoped_;
+    uint16_t numBodyLevelLexicals_;
+    uint16_t aliasedBodyLevelLexicalBegin_;
     uint32_t numVars_;
 
+#if JS_BITS_PER_WORD == 32
+    // Bindings is allocated inline inside JSScript, which needs to be
+    // gc::Cell aligned.
+    uint32_t padding_;
+#endif
+
     /*
      * During parsing, bindings are allocated out of a temporary LifoAlloc.
      * After parsing, a JSScript object is created and the bindings are
      * permanently transferred to it. On error paths, the JSScript object may
      * end up with bindings that still point to the (new released) LifoAlloc
      * memory. To avoid tracing these bindings during GC, we keep track of
      * whether the bindings are temporary or permanent in the low bit of
      * bindingArrayAndFlag_.
@@ -204,23 +213,25 @@ class Bindings
     Binding *bindingArray() const {
         return reinterpret_cast<Binding *>(bindingArrayAndFlag_ & ~TEMPORARY_STORAGE_BIT);
     }
 
     inline Bindings();
 
     /*
      * Initialize a Bindings with a pointer into temporary storage.
-     * bindingArray must have length numArgs+numVars. Before the temporary
-     * storage is release, switchToScriptStorage must be called, providing a
-     * pointer into the Binding array stored in script->data.
+     * bindingArray must have length numArgs + numVars +
+     * numBodyLevelLexicals. Before the temporary storage is release,
+     * switchToScriptStorage must be called, providing a pointer into the
+     * Binding array stored in script->data.
      */
     static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
-                                         unsigned numArgs, uint32_t numVars,
-                                         Binding *bindingArray, unsigned numBlockScoped);
+                                         uint32_t numArgs, uint32_t numVars,
+                                         uint32_t numBodyLevelLexicals, uint32_t numBlockScoped,
+                                         Binding *bindingArray);
 
     // CompileScript parses and compiles one statement at a time, but the result
     // is one Script object.  There will be no vars or bindings, because those
     // go on the global, but there may be block-scoped locals, and the number of
     // block-scoped locals may increase as we parse more expressions.  This
     // helper updates the number of block scoped variables in a script as it is
     // being parsed.
     void updateNumBlockScoped(unsigned numBlockScoped) {
@@ -235,23 +246,27 @@ class Bindings
 
     /*
      * Clone srcScript's bindings (as part of js::CloneScript). dstScriptData
      * is the pointer to what will eventually be dstScript->data.
      */
     static bool clone(JSContext *cx, InternalBindingsHandle self, uint8_t *dstScriptData,
                       HandleScript srcScript);
 
-    unsigned numArgs() const { return numArgs_; }
+    uint32_t numArgs() const { return numArgs_; }
     uint32_t numVars() const { return numVars_; }
-    unsigned numBlockScoped() const { return numBlockScoped_; }
-    uint32_t numLocals() const { return numVars() + numBlockScoped(); }
+    uint32_t numBodyLevelLexicals() const { return numBodyLevelLexicals_; }
+    uint32_t numBlockScoped() const { return numBlockScoped_; }
+    uint32_t numBodyLevelLocals() const { return numVars_ + numBodyLevelLexicals_; }
+    uint32_t numLocals() const { return numVars() + numBodyLevelLexicals() + numBlockScoped(); }
+    uint32_t lexicalBegin() const { return numArgs() + numVars(); }
+    uint32_t aliasedBodyLevelLexicalBegin() const { return aliasedBodyLevelLexicalBegin_; }
 
     // Return the size of the bindingArray.
-    uint32_t count() const { return numArgs() + numVars(); }
+    uint32_t count() const { return numArgs() + numVars() + numBodyLevelLexicals(); }
 
     /* Return the initial shape of call objects created for this scope. */
     Shape *callObjShape() const { return callObjShape_; }
 
     /* Convenience method to get the var index of 'arguments'. */
     static uint32_t argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle);
 
     /* Return whether the binding at bindingIndex is aliased. */
@@ -1028,21 +1043,38 @@ class JSScript : public js::gc::Barriere
     void setColumn(size_t column) { column_ = column; }
 
     // The fixed part of a stack frame is comprised of vars (in function code)
     // and block-scoped locals (in all kinds of code).
     size_t nfixed() const {
         return function_ ? bindings.numLocals() : bindings.numBlockScoped();
     }
 
-    // Number of fixed slots reserved for vars.  Only nonzero for function code.
+    // Number of fixed slots reserved for vars.  Only nonzero for function
+    // code.
     size_t nfixedvars() const {
         return function_ ? bindings.numVars() : 0;
     }
 
+    // Number of fixed slots reserved for body-level lexicals and vars. This
+    // value minus nfixedvars() is the number of body-level lexicals. Only
+    // nonzero for function code.
+    size_t nbodyfixed() const {
+        return function_ ? bindings.numBodyLevelLocals() : 0;
+    }
+
+    // Aliases for clarity when dealing with lexical slots.
+    size_t fixedLexicalBegin() const {
+        return nfixedvars();
+    }
+
+    size_t fixedLexicalEnd() const {
+        return nfixed();
+    }
+
     size_t nslots() const {
         return nslots_;
     }
 
     size_t staticLevel() const {
         return staticLevel_;
     }
 
@@ -1558,16 +1590,17 @@ class JSScript : public js::gc::Barriere
 
         jsbytecode *pc = code();
         if (noScriptRval() && JSOp(*pc) == JSOP_FALSE)
             ++pc;
         return JSOp(*pc) == JSOP_RETRVAL;
     }
 
     bool varIsAliased(uint32_t varSlot);
+    bool bodyLevelLocalIsAliased(uint32_t localSlot);
     bool formalIsAliased(unsigned argSlot);
     bool formalLivesInArgumentsObject(unsigned argSlot);
 
   private:
     /* Change this->stepMode to |newValue|. */
     void setNewStepMode(js::FreeOp *fop, uint32_t newValue);
 
     bool ensureHasDebugScript(JSContext *cx);
@@ -1686,16 +1719,45 @@ class AliasedFormalIter
     unsigned frameIndex() const { JS_ASSERT(!done()); return p_ - begin_; }
     unsigned scopeSlot() const { JS_ASSERT(!done()); return slot_; }
 };
 
 // Information about a script which may be (or has been) lazily compiled to
 // bytecode from its source.
 class LazyScript : public gc::BarrieredCell<LazyScript>
 {
+  public:
+    class FreeVariable
+    {
+        // Free variable names are possible tagged JSAtom *s.
+        uintptr_t bits_;
+
+        static const uintptr_t HOISTED_USE_BIT = 0x1;
+        static const uintptr_t MASK = ~HOISTED_USE_BIT;
+
+      public:
+        explicit FreeVariable()
+          : bits_(0)
+        { }
+
+        explicit FreeVariable(JSAtom *name)
+          : bits_(uintptr_t(name))
+        {
+            // We rely on not requiring any write barriers so we can tag the
+            // pointer. This code needs to change if we start allocating
+            // JSAtoms inside the nursery.
+            MOZ_ASSERT(!IsInsideNursery(name));
+        }
+
+        JSAtom *atom() const { return (JSAtom *)(bits_ & MASK); }
+        void setIsHoistedUse() { bits_ |= HOISTED_USE_BIT; }
+        bool isHoistedUse() const { return bool(bits_ & HOISTED_USE_BIT); }
+    };
+
+  private:
     // If non-nullptr, the script has been compiled and this is a forwarding
     // pointer to the result.
     HeapPtrScript script_;
 
     // Original function with which the lazy script is associated.
     HeapPtrFunction function_;
 
     // Function or block chain in which the script is nested, or nullptr.
@@ -1797,18 +1859,18 @@ class LazyScript : public gc::BarrieredC
         return (p_.version == JS_BIT(8) - 1) ? JSVERSION_UNKNOWN : JSVersion(p_.version);
     }
 
     void setParent(JSObject *enclosingScope, ScriptSourceObject *sourceObject);
 
     uint32_t numFreeVariables() const {
         return p_.numFreeVariables;
     }
-    HeapPtrAtom *freeVariables() {
-        return (HeapPtrAtom *)table_;
+    FreeVariable *freeVariables() {
+        return (FreeVariable *)table_;
     }
 
     uint32_t numInnerFunctions() const {
         return p_.numInnerFunctions;
     }
     HeapPtrFunction *innerFunctions() {
         return (HeapPtrFunction *)&freeVariables()[numFreeVariables()];
     }
--- a/js/src/tests/js1_7/block/order-of-operation.js
+++ b/js/src/tests/js1_7/block/order-of-operation.js
@@ -80,28 +80,29 @@ function f6() {
     var i=3;
     for (let i=i;;) { if (i != 3) throw "f6(): fail 1"; i = 7; break; }
     if (i != 3) throw "f6(): fail 2";
   }
 }
 
 try
 {
+/*
   var rv = f1(5);
   if (!isNaN(rv))
     throw "f1(5):\n" +
       "  expected:  NaN\n" +
       "  actual:    " + rv;
 
   rv = f2(5);
   if (!isNaN(rv))
     throw "f2(5):\n" +
       "  expected:  NaN\n" +
       "  actual:    " + rv;
-/*
+
   rv = f3(8);
   if (rv != 9)
   throw "f3(8):\n" +
   "  expected:  9\n" +
   "  actual:    " + rv;
 */
 
   rv = f4(13);
deleted file mode 100644
--- a/js/src/tests/js1_7/block/regress-411279.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-//-----------------------------------------------------------------------------
-var BUGNUMBER = 411279;
-var summary = 'let declaration as direct child of switch body block';
-var actual = 'No Crash';
-var expect = 'No Crash';
-
-
-//-----------------------------------------------------------------------------
-test();
-//-----------------------------------------------------------------------------
-
-function test()
-{
-  enterFunc ('test');
-  printBugNumber(BUGNUMBER);
-  printStatus (summary);
-
-  function f(x) 
-  {
-    var value = '';
-
-    switch(x)
-    {
-    case 1:
-      value = "1 " + y; 
-      break; 
-    case 2: 
-      let y = 42;
-      value = "2 " + y; 
-      break; 
-    default:
-      value = "default " + y;
-    }
-
-    return value;
-  }
-
-  expect = '1 undefined';
-  actual = f(1);
-  reportCompare(expect, actual, summary + ': f(1)');
-
-  expect = '2 42';
-  actual = f(2);
-  reportCompare(expect, actual, summary + ': f(2)');
-
-  expect = 'default undefined';
-  actual = f(3);
-  reportCompare(expect, actual, summary + ': f(3)');
- 
-  exitFunc ('test');
-}
--- a/js/src/tests/js1_8_1/extensions/regress-353214-02.js
+++ b/js/src/tests/js1_8_1/extensions/regress-353214-02.js
@@ -15,16 +15,16 @@ test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
-  var f = function ([x]) { let x; }
-  expect = 'function ([x]) { let x; }';
+  var f = function ([x]) { let y; }
+  expect = 'function ([x]) { let y; }';
   actual = f + '';
 
   compareSource(expect, actual, summary);
 
   exitFunc ('test');
 }
deleted file mode 100644
--- a/js/src/tests/js1_8_1/regress/regress-452498-103.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-//-----------------------------------------------------------------------------
-var BUGNUMBER = 452498;
-var summary = 'TM: upvar2 regression tests';
-var actual = '';
-var expect = '';
-
-
-//-----------------------------------------------------------------------------
-test();
-//-----------------------------------------------------------------------------
-
-function test()
-{
-  enterFunc ('test');
-  printBugNumber(BUGNUMBER);
-  printStatus (summary);
-
-// ------- Comment #103 From Jesse Ruderman
-
-  (function(a) { v = 5; let [v] = [3]; (function(){ v; })(); })();
-// Assertion failure: !(pn->pn_dflags & flag), at ../jsparse.h:638
-
-  reportCompare(expect, actual, summary);
-
-  exitFunc ('test');
-}
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -181,16 +181,17 @@
     macro(toJSON, toJSON, "toJSON") \
     macro(toLocaleString, toLocaleString, "toLocaleString") \
     macro(toSource, toSource, "toSource") \
     macro(toString, toString, "toString") \
     macro(toUTCString, toUTCString, "toUTCString") \
     macro(true, true_, "true") \
     macro(unescape, unescape, "unescape") \
     macro(uneval, uneval, "uneval") \
+    macro(uninitialized, uninitialized, "uninitialized") \
     macro(uint8, uint8, "uint8") \
     macro(uint8Clamped, uint8Clamped, "uint8Clamped") \
     macro(uint16, uint16, "uint16") \
     macro(uint32, uint32, "uint32") \
     macro(unsized, unsized, "unsized") \
     macro(unwatch, unwatch, "unwatch") \
     macro(url, url, "url") \
     macro(usage, usage, "usage") \
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -720,25 +720,27 @@ Debugger::wrapDebuggeeValue(JSContext *c
 
             vp.setObject(*dobj);
         }
     } else if (vp.isMagic()) {
         RootedObject optObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
         if (!optObj)
             return false;
 
-        // We handle two sentinel values: missing arguments (overloading
-        // JS_OPTIMIZED_ARGUMENTS) and optimized out slots (JS_OPTIMIZED_OUT).
+        // We handle three sentinel values: missing arguments (overloading
+        // JS_OPTIMIZED_ARGUMENTS), optimized out slots (JS_OPTIMIZED_OUT),
+        // and uninitialized bindings (JS_UNINITIALIZED_LEXICAL).
+        //
         // Other magic values should not have escaped.
         PropertyName *name;
-        if (vp.whyMagic() == JS_OPTIMIZED_ARGUMENTS) {
-            name = cx->names().missingArguments;
-        } else {
-            MOZ_ASSERT(vp.whyMagic() == JS_OPTIMIZED_OUT);
-            name = cx->names().optimizedOut;
+        switch (vp.whyMagic()) {
+          case JS_OPTIMIZED_ARGUMENTS:   name = cx->names().missingArguments; break;
+          case JS_OPTIMIZED_OUT:         name = cx->names().optimizedOut; break;
+          case JS_UNINITIALIZED_LEXICAL: name = cx->names().uninitialized; break;
+          default: MOZ_CRASH("Unsupported magic value escaped to Debugger");
         }
 
         RootedValue trueVal(cx, BooleanValue(true));
         if (!JSObject::defineProperty(cx, optObj, name, trueVal))
             return false;
 
         vp.setObject(*optObj);
     } else if (!cx->compartment()->wrap(cx, vp)) {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -493,16 +493,20 @@ class Debugger : private mozilla::Linked
      * wrapper for it. Otherwise this is the same as JSCompartment::wrap.
      *
      * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object
      * of the form { optimizedOut: true }.
      *
      * If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing
      * arguments, this produces a plain object of the form { missingArguments:
      * true }.
+     *
+     * If *vp is a magic JS_UNINITIALIZED_LEXICAL value signifying an
+     * unaccessible uninitialized binding, this produces a plain object of the
+     * form { uninitialized: true }.
      */
     bool wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp);
 
     /*
      * Unwrap a Debug.Object, without rewrapping it for any particular debuggee
      * compartment.
      *
      * Preconditions: cx is in the debugger compartment. *vp is a value in that
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -17,16 +17,17 @@
 #include "jit/Ion.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/ForkJoin.h"
 
 #include "jsatominlines.h"
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 
+#include "vm/ScopeObject-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 
 namespace js {
 
 inline bool
 ComputeThis(JSContext *cx, AbstractFramePtr frame)
 {
@@ -102,16 +103,72 @@ GuardFunApplyArgumentsOptimization(JSCon
             args[1] = ObjectValue(frame.argsObj());
         }
     }
 
     return true;
 }
 
 /*
+ * Per ES6, lexical declarations may not be accessed in any fashion until they
+ * are initialized (i.e., until the actual declaring statement is
+ * executed). The various LEXICAL opcodes need to check if the slot is an
+ * uninitialized let declaration, represented by the magic value
+ * JS_UNINITIALIZED_LEXICAL.
+ */
+static inline bool
+IsUninitializedLexical(const Value &val)
+{
+    // Use whyMagic here because JS_OPTIMIZED_ARGUMENTS could flow into here.
+    return val.isMagic() && val.whyMagic() == JS_UNINITIALIZED_LEXICAL;
+}
+
+static inline bool
+IsUninitializedLexicalSlot(HandleObject obj, HandleShape shape)
+{
+    if (obj->is<DynamicWithObject>())
+        return false;
+    // We check for IsImplicitDenseOrTypedArrayElement even though the shape
+    // is always a non-indexed property because proxy hooks may return a
+    // "non-native property found" shape, which happens to be encoded in the
+    // same way as the "dense element" shape. See MarkNonNativePropertyFound.
+    if (!shape ||
+        IsImplicitDenseOrTypedArrayElement(shape) ||
+        !shape->hasSlot() ||
+        !shape->hasDefaultGetter() ||
+        !shape->hasDefaultSetter())
+    {
+        return false;
+    }
+    MOZ_ASSERT(obj->nativeContainsPure(shape));
+    return IsUninitializedLexical(obj->nativeGetSlot(shape->slot()));
+}
+
+static inline bool
+CheckUninitializedLexical(JSContext *cx, PropertyName *name_, HandleValue val)
+{
+    if (IsUninitializedLexical(val)) {
+        RootedPropertyName name(cx, name_);
+        ReportUninitializedLexical(cx, name);
+        return false;
+    }
+    return true;
+}
+
+static inline bool
+CheckUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue val)
+{
+    if (IsUninitializedLexical(val)) {
+        ReportUninitializedLexical(cx, script, pc);
+        return false;
+    }
+    return true;
+}
+
+/*
  * Return an object on which we should look for the properties of |value|.
  * This helps us implement the custom [[Get]] method that ES5's GetValue
  * algorithm uses for primitive values, without actually constructing the
  * temporary object that the specification does.
  *
  * For objects, return the object itself. For string, boolean, and number
  * primitive values, return the appropriate constructor's prototype. For
  * undefined and null, throw an error and return nullptr, attributing the
@@ -220,16 +277,32 @@ GetIntrinsicOperation(JSContext *cx, jsb
 
 inline bool
 SetIntrinsicOperation(JSContext *cx, JSScript *script, jsbytecode *pc, HandleValue val)
 {
     RootedPropertyName name(cx, script->getName(pc));
     return cx->global()->setIntrinsicValue(cx, name, val);
 }
 
+inline void
+SetAliasedVarOperation(JSContext *cx, JSScript *script, jsbytecode *pc,
+                       ScopeObject &obj, ScopeCoordinate sc, const Value &val,
+                       MaybeCheckLexical checkLexical)
+{
+    MOZ_ASSERT_IF(checkLexical, !IsUninitializedLexical(obj.aliasedVar(sc)));
+
+    // Avoid computing the name if no type updates are needed, as this may be
+    // expensive on scopes with large numbers of variables.
+    PropertyName *name = obj.hasSingletonType()
+                         ? ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)
+                         : nullptr;
+
+    obj.setAliasedVar(cx, sc, name, val);
+}
+
 inline bool
 SetNameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, HandleObject scope,
                  HandleValue val)
 {
     JS_ASSERT(*pc == JSOP_SETNAME || *pc == JSOP_SETGNAME);
     JS_ASSERT_IF(*pc == JSOP_SETGNAME, scope == cx->global());
 
     bool strict = script->strict();
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -281,31 +281,39 @@ NameOperation(JSContext *cx, Interpreter
      */
     if (IsGlobalOp(JSOp(*pc)))
         obj = &obj->global();
 
     Shape *shape = nullptr;
     JSObject *scope = nullptr, *pobj = nullptr;
     if (LookupNameNoGC(cx, name, obj, &scope, &pobj, &shape)) {
         if (FetchNameNoGC(pobj, shape, vp))
-            return true;
+            return CheckUninitializedLexical(cx, name, vp);
     }
 
     RootedObject objRoot(cx, obj), scopeRoot(cx), pobjRoot(cx);
     RootedPropertyName nameRoot(cx, name);
     RootedShape shapeRoot(cx);
 
     if (!LookupName(cx, nameRoot, objRoot, &scopeRoot, &pobjRoot, &shapeRoot))
         return false;
 
     /* Kludge to allow (typeof foo == "undefined") tests. */
     JSOp op2 = JSOp(pc[JSOP_NAME_LENGTH]);
-    if (op2 == JSOP_TYPEOF)
-        return FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
-    return FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
+    if (op2 == JSOP_TYPEOF) {
+        if (!FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp))
+            return false;
+    } else {
+        if (!FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp))
+            return false;
+    }
+
+    // NAME operations are the slow paths already, so unconditionally check
+    // for uninitialized lets.
+    return CheckUninitializedLexical(cx, nameRoot, vp);
 }
 
 static inline bool
 SetPropertyOperation(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue lval,
                      HandleValue rval)
 {
     JS_ASSERT(*pc == JSOP_SETPROP);
 
@@ -1009,17 +1017,34 @@ HandleError(JSContext *cx, InterpreterRe
                 MOZ_CRASH("Invalid trap status");
             }
         }
 
         RootedValue exception(cx);
         for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
             JSTryNote *tn = *tni;
 
-            UnwindScope(cx, si, regs.fp()->script()->main() + tn->start);
+            // Unwind the scope to the beginning of the JSOP_TRY. We cannot
+            // unwind to *after* the JSOP_TRY, because that might be the first
+            // opcode of an inner scope. Consider the following:
+            //
+            // try {
+            //   { let x; }
+            // }
+            //
+            // This would generate
+            //
+            // 0000: try
+            // 0001: undefined
+            // 0002: initlet 0
+            //
+            // If we unwound to 0001, we would be unwinding to the inner
+            // scope, and not the scope of the try { }.
+            UnwindScope(cx, si, (regs.fp()->script()->main() + tn->start -
+                                 js_CodeSpec[JSOP_TRY].length));
 
             /*
              * Set pc to the first bytecode after the the try note to point
              * to the beginning of catch or finally or to [enditer] closing
              * the for-in loop.
              */
             regs.pc = regs.fp()->script()->main() + tn->start + tn->length;
             regs.sp = regs.spForStackDepth(tn->stackDepth);
@@ -1090,16 +1115,17 @@ HandleError(JSContext *cx, InterpreterRe
 #define PUSH_UNDEFINED()         REGS.sp++->setUndefined()
 #define PUSH_BOOLEAN(b)          REGS.sp++->setBoolean(b)
 #define PUSH_DOUBLE(d)           REGS.sp++->setDouble(d)
 #define PUSH_INT32(i)            REGS.sp++->setInt32(i)
 #define PUSH_STRING(s)           do { REGS.sp++->setString(s); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
 #define PUSH_OBJECT(obj)         do { REGS.sp++->setObject(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
 #define PUSH_OBJECT_OR_NULL(obj) do { REGS.sp++->setObjectOrNull(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
 #define PUSH_HOLE()              REGS.sp++->setMagic(JS_ELEMENTS_HOLE)
+#define PUSH_UNINITIALIZED()     REGS.sp++->setMagic(JS_UNINITIALIZED_LEXICAL)
 #define POP_COPY_TO(v)           (v) = *--REGS.sp
 #define POP_RETURN_VALUE()       REGS.fp()->setReturnValue(*--REGS.sp)
 
 #define FETCH_OBJECT(cx, n, obj)                                              \
     JS_BEGIN_MACRO                                                            \
         HandleValue val = REGS.stackHandleAt(n);                              \
         obj = ToObjectFromStack((cx), (val));                                 \
         if (!obj)                                                             \
@@ -1580,21 +1606,16 @@ CASE(JSOP_UNUSED57)
 CASE(JSOP_UNUSED83)
 CASE(JSOP_UNUSED103)
 CASE(JSOP_UNUSED104)
 CASE(JSOP_UNUSED105)
 CASE(JSOP_UNUSED107)
 CASE(JSOP_UNUSED124)
 CASE(JSOP_UNUSED125)
 CASE(JSOP_UNUSED126)
-CASE(JSOP_UNUSED138)
-CASE(JSOP_UNUSED139)
-CASE(JSOP_UNUSED140)
-CASE(JSOP_UNUSED141)
-CASE(JSOP_UNUSED142)
 CASE(JSOP_UNUSED146)
 CASE(JSOP_UNUSED147)
 CASE(JSOP_UNUSED148)
 CASE(JSOP_BACKPATCH)
 CASE(JSOP_UNUSED150)
 CASE(JSOP_UNUSED156)
 CASE(JSOP_UNUSED157)
 CASE(JSOP_UNUSED158)
@@ -1990,20 +2011,29 @@ CASE(JSOP_BINDNAME)
     RootedObject &scopeChain = rootObject0;
     scopeChain = REGS.fp()->scopeChain();
 
     RootedPropertyName &name = rootName0;
     name = script->getName(REGS.pc);
 
     /* Assigning to an undeclared name adds a property to the global object. */
     RootedObject &scope = rootObject1;
-    if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
+    RootedShape &shape = rootShape0;
+    if (!LookupNameUnqualified(cx, name, scopeChain, &scope, &shape))
         goto error;
 
-    PUSH_OBJECT(*scope);
+    // ES6 lets cannot be accessed until initialized. NAME operations, being
+    // the slow paths already, unconditionally check for uninitialized
+    // lets. The error, however, is thrown after evaluating the RHS in
+    // assignments. Thus if the LHS resolves to an uninitialized let, return a
+    // nullptr scope.
+    if (IsUninitializedLexicalSlot(scope, shape))
+        PUSH_NULL();
+    else
+        PUSH_OBJECT(*scope);
 }
 END_CASE(JSOP_BINDNAME)
 
 #define BITWISE_OP(OP)                                                        \
     JS_BEGIN_MACRO                                                            \
         int32_t i, j;                                                         \
         if (!ToInt32(cx, REGS.stackHandleAt(-2), &i))                         \
             goto error;                                                       \
@@ -2379,17 +2409,26 @@ CASE(JSOP_SETINTRINSIC)
     REGS.sp--;
 }
 END_CASE(JSOP_SETINTRINSIC)
 
 CASE(JSOP_SETGNAME)
 CASE(JSOP_SETNAME)
 {
     RootedObject &scope = rootObject0;
-    scope = &REGS.sp[-2].toObject();
+    scope = REGS.sp[-2].toObjectOrNull();
+
+    // A nullptr scope is pushed if the name is an uninitialized let. See
+    // CASE(JSOP_BINDNAME).
+    if (!scope) {
+        RootedPropertyName &name = rootName0;
+        name = script->getName(REGS.pc);
+        ReportUninitializedLexical(cx, name);
+        goto error;
+    }
 
     HandleValue value = REGS.stackHandleAt(-1);
 
     if (!SetNameOperation(cx, script, REGS.pc, scope, value))
         goto error;
 
     REGS.sp[-2] = REGS.sp[-1];
     REGS.sp--;
@@ -2617,17 +2656,18 @@ CASE(JSOP_IMPLICITTHIS)
 {
     RootedPropertyName &name = rootName0;
     name = script->getName(REGS.pc);
 
     RootedObject &scopeObj = rootObject0;
     scopeObj = REGS.fp()->scopeChain();
 
     RootedObject &scope = rootObject1;
-    if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope))
+    RootedShape &shape = rootShape0;
+    if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope, &shape))
         goto error;
 
     RootedValue &v = rootValue0;
     if (!ComputeImplicitThis(cx, scope, &v))
         goto error;
     PUSH_COPY(v);
 }
 END_CASE(JSOP_IMPLICITTHIS)
@@ -2828,36 +2868,71 @@ CASE(JSOP_REST)
         goto error;
     PUSH_COPY(ObjectValue(*rest));
 }
 END_CASE(JSOP_REST)
 
 CASE(JSOP_GETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
-    PUSH_COPY(REGS.fp()->aliasedVarScope(sc).aliasedVar(sc));
+    RootedValue &val = rootValue0;
+    val = REGS.fp()->aliasedVarScope(sc).aliasedVar(sc);
+    MOZ_ASSERT(!IsUninitializedLexical(val));
+    PUSH_COPY(val);
     TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
 }
 END_CASE(JSOP_GETALIASEDVAR)
 
 CASE(JSOP_SETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
     ScopeObject &obj = REGS.fp()->aliasedVarScope(sc);
-
-    // Avoid computing the name if no type updates are needed, as this may be
-    // expensive on scopes with large numbers of variables.
-    PropertyName *name = obj.hasSingletonType()
-                         ? ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, REGS.pc)
-                         : nullptr;
-
-    obj.setAliasedVar(cx, sc, name, REGS.sp[-1]);
+    SetAliasedVarOperation(cx, script, REGS.pc, obj, sc, REGS.sp[-1], CheckLexical);
 }
 END_CASE(JSOP_SETALIASEDVAR)
 
+CASE(JSOP_CHECKLEXICAL)
+{
+    uint32_t i = GET_LOCALNO(REGS.pc);
+    RootedValue &val = rootValue0;
+    val = REGS.fp()->unaliasedLocal(i);
+    if (!CheckUninitializedLexical(cx, script, REGS.pc, val))
+        goto error;
+}
+END_CASE(JSOP_CHECKLEXICAL)
+
+CASE(JSOP_INITLEXICAL)
+{
+    uint32_t i = GET_LOCALNO(REGS.pc);
+    REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
+}
+END_CASE(JSOP_INITLEXICAL)
+
+CASE(JSOP_CHECKALIASEDLEXICAL)
+{
+    ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
+    RootedValue &val = rootValue0;
+    val = REGS.fp()->aliasedVarScope(sc).aliasedVar(sc);
+    if (!CheckUninitializedLexical(cx, script, REGS.pc, val))
+        goto error;
+}
+END_CASE(JSOP_CHECKALIASEDLEXICAL)
+
+CASE(JSOP_INITALIASEDLEXICAL)
+{
+    ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
+    ScopeObject &obj = REGS.fp()->aliasedVarScope(sc);
+    SetAliasedVarOperation(cx, script, REGS.pc, obj, sc, REGS.sp[-1], DontCheckLexical);
+}
+END_CASE(JSOP_INITALIASEDLEXICAL)
+
+CASE(JSOP_UNINITIALIZED)
+    PUSH_UNINITIALIZED();
+END_CASE(JSOP_UNINITIALIZED)
+
 CASE(JSOP_GETARG)
 {
     unsigned i = GET_ARGNO(REGS.pc);
     if (script->argsObjAliasesFormals())
         PUSH_COPY(REGS.fp()->argsObj().arg(i));
     else
         PUSH_COPY(REGS.fp()->unaliasedFormal(i));
 }
@@ -2872,31 +2947,33 @@ CASE(JSOP_SETARG)
         REGS.fp()->unaliasedFormal(i) = REGS.sp[-1];
 }
 END_CASE(JSOP_SETARG)
 
 CASE(JSOP_GETLOCAL)
 {
     uint32_t i = GET_LOCALNO(REGS.pc);
     PUSH_COPY_SKIP_CHECK(REGS.fp()->unaliasedLocal(i));
+    MOZ_ASSERT(!IsUninitializedLexical(REGS.sp[-1]));
 
     /*
      * Skip the same-compartment assertion if the local will be immediately
      * popped. We do not guarantee sync for dead locals when coming in from the
      * method JIT, and a GETLOCAL followed by POP is not considered to be
      * a use of the variable.
      */
     if (REGS.pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP)
         assertSameCompartmentDebugOnly(cx, REGS.sp[-1]);
 }
 END_CASE(JSOP_GETLOCAL)
 
 CASE(JSOP_SETLOCAL)
 {
     uint32_t i = GET_LOCALNO(REGS.pc);
+    MOZ_ASSERT(!IsUninitializedLexical(REGS.fp()->unaliasedLocal(i)));
     REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
 }
 END_CASE(JSOP_SETLOCAL)
 
 CASE(JSOP_DEFCONST)
 CASE(JSOP_DEFVAR)
 {
     /* ES5 10.5 step 8 (with subsequent errata). */
@@ -3485,17 +3562,21 @@ js::GetScopeName(JSContext *cx, HandleOb
 
     if (!shape) {
         JSAutoByteString printable;
         if (AtomToPrintableString(cx, name, &printable))
             js_ReportIsNotDefined(cx, printable.ptr());
         return false;
     }
 
-    return JSObject::getProperty(cx, obj, obj, name, vp);
+    if (!JSObject::getProperty(cx, obj, obj, name, vp))
+        return false;
+
+    // See note in NameOperation.
+    return CheckUninitializedLexical(cx, name, vp);
 }
 
 /*
  * Alternate form for NAME opcodes followed immediately by a TYPEOF,
  * which do not report an exception on (typeof foo == "undefined") tests.
  */
 bool
 js::GetScopeNameForTypeOf(JSContext *cx, HandleObject scopeChain, HandlePropertyName name,
@@ -3506,17 +3587,21 @@ js::GetScopeNameForTypeOf(JSContext *cx,
     if (!LookupName(cx, name, scopeChain, &obj, &pobj, &shape))
         return false;
 
     if (!shape) {
         vp.set(UndefinedValue());
         return true;
     }
 
-    return JSObject::getProperty(cx, obj, obj, name, vp);
+    if (!JSObject::getProperty(cx, obj, obj, name, vp))
+        return false;
+
+    // See note in NameOperation.
+    return CheckUninitializedLexical(cx, name, vp);
 }
 
 JSObject *
 js::Lambda(JSContext *cx, HandleFunction fun, HandleObject parent)
 {
     MOZ_ASSERT(!fun->isArrow());
 
     RootedObject clone(cx, CloneFunctionObjectIfNotSingleton(cx, fun, parent));
@@ -3803,17 +3888,18 @@ js::DeleteNameOperation(JSContext *cx, H
     return true;
 }
 
 bool
 js::ImplicitThisOperation(JSContext *cx, HandleObject scopeObj, HandlePropertyName name,
                           MutableHandleValue res)
 {
     RootedObject obj(cx);
-    if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj))
+    RootedShape shape(cx);
+    if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj, &shape))
         return false;
 
     return ComputeImplicitThis(cx, obj, res);
 }
 
 bool
 js::RunOnceScriptPrologue(JSContext *cx, HandleScript script)
 {
@@ -3935,8 +4021,70 @@ js::SpreadCallOperation(JSContext *cx, H
       default:
         MOZ_CRASH("bad spread opcode");
     }
 
     res.set(args.rval());
     TypeScript::Monitor(cx, script, pc, res);
     return true;
 }
+
+void
+js::ReportUninitializedLexical(JSContext *cx, HandlePropertyName name)
+{
+    JSAutoByteString printable;
+    if (AtomToPrintableString(cx, name, &printable)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_LEXICAL,
+                             printable.ptr());
+    }
+}
+
+void
+js::ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc)
+{
+    RootedPropertyName name(cx);
+
+    if (JSOp(*pc) == JSOP_CHECKLEXICAL) {
+        uint32_t slot = GET_LOCALNO(pc);
+
+        // First search for a name among body-level lets.
+        for (BindingIter bi(script); bi; bi++) {
+            if (bi->kind() != Binding::ARGUMENT && bi.frameIndex() == slot) {
+                name = bi->name();
+                break;
+            }
+        }
+
+        // Failing that, it must be a block-local let.
+        if (!name) {
+            // Skip to the right scope.
+            Rooted<NestedScopeObject *> scope(cx, script->getStaticScope(pc));
+            MOZ_ASSERT(scope && scope->is<StaticBlockObject>());
+            Rooted<StaticBlockObject *> block(cx, &scope->as<StaticBlockObject>());
+            while (slot < block->localOffset())
+                block = &block->enclosingNestedScope()->as<StaticBlockObject>();
+
+            // Translate the frame slot to the block slot, then find the name
+            // of the slot.
+            uint32_t blockSlot = block->localIndexToSlot(slot);
+            RootedShape shape(cx, block->lastProperty());
+            Shape::Range<CanGC> r(cx, shape);
+            while (r.front().slot() != blockSlot)
+                r.popFront();
+            jsid id = r.front().propidRaw();
+            MOZ_ASSERT(JSID_IS_ATOM(id));
+            name = JSID_TO_ATOM(id)->asPropertyName();
+        }
+    } else {
+        MOZ_ASSERT(JSOp(*pc) == JSOP_CHECKALIASEDLEXICAL);
+        name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
+    }
+
+    ReportUninitializedLexical(cx, name);
+}
+
+void
+js::ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc, ScopeCoordinate sc)
+{
+    RootedPropertyName name(cx, ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache,
+                                                    script, pc));
+    ReportUninitializedLexical(cx, name);
+}
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -465,11 +465,20 @@ SpreadCallOperation(JSContext *cx, Handl
 inline bool
 SetConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName name, HandleValue rval)
 {
     return JSObject::defineProperty(cx, varobj, name, rval,
                                     JS_PropertyStub, JS_StrictPropertyStub,
                                     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
 }
 
+void
+ReportUninitializedLexical(JSContext *cx, HandlePropertyName name);
+
+void
+ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc);
+
+void
+ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc, ScopeCoordinate sc);
+
 }  /* namespace js */
 
 #endif /* vm_Interpreter_h */
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1255,24 +1255,64 @@ 1234567890123456789012345678901234567890
      * Sets aliased variable as the top of stack value.
      *   Category: Variables and Scopes
      *   Type: Aliased Variables
      *   Operands: uint8_t hops, uint24_t slot
      *   Stack: v => v
      */ \
     macro(JSOP_SETALIASEDVAR, 137,"setaliasedvar",NULL,      5,  1,  1,  JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING) \
     \
-    macro(JSOP_UNUSED138,  138, "unused138",   NULL,         1,  0,  0,  JOF_BYTE) \
-    macro(JSOP_UNUSED139,  139, "unused139",   NULL,         1,  0,  0,  JOF_BYTE) \
-    macro(JSOP_UNUSED140,  140, "unused140",   NULL,         1,  0,  0,  JOF_BYTE) \
-    macro(JSOP_UNUSED141,  141, "unused141",   NULL,         1,  0,  0,  JOF_BYTE) \
-    macro(JSOP_UNUSED142,  142, "unused142",   NULL,         1,  0,  0,  JOF_BYTE) \
-    \
+    /*
+     * Checks if the value of the local variable is the
+     * JS_UNINITIALIZED_LEXICAL magic, throwing an error if so.
+     *   Category: Variables and Scopes
+     *   Type: Local Variables
+     *   Operands: uint32_t localno
+     *   Stack: =>
+     */ \
+    macro(JSOP_CHECKLEXICAL,  138, "checklexical", NULL,     4,  0,  0, JOF_LOCAL|JOF_NAME) \
+    /*
+     * Initializes an uninitialized local lexical binding with the top of stack
+     * value.
+     *   Category: Variables and Scopes
+     *   Type: Local Variables
+     *   Operands: uint32_t localno
+     *   Stack: v => v
+     */ \
+    macro(JSOP_INITLEXICAL,   139, "initlexical",  NULL,      4,  1,  1, JOF_LOCAL|JOF_NAME|JOF_SET|JOF_DETECTING) \
     /*
-     * Pushes the value of the intrinsic onto the stack.
+     * Checks if the value of the aliased variable is the
+     * JS_UNINITIALIZED_LEXICAL magic, throwing an error if so.
+     *   Category: Variables and Scopes
+     *   Type: Aliased Variables
+     *   Operands: uint8_t hops, uint24_t slot
+     *   Stack: =>
+     */ \
+    macro(JSOP_CHECKALIASEDLEXICAL, 140, "checkaliasedlexical", NULL, 5,  0,  0, JOF_SCOPECOORD|JOF_NAME) \
+    /*
+     * Initializes an uninitialized aliased lexical binding with the top of
+     * stack value.
+     *   Category: Variables and Scopes
+     *   Type: Aliased Variables
+     *   Operands: uint8_t hops, uint24_t slot
+     *   Stack: v => v
+     */ \
+    macro(JSOP_INITALIASEDLEXICAL,  141, "initaliasedlexical",  NULL, 5,  1,  1, JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING) \
+    /*
+     * Pushes a JS_UNINITIALIZED_LEXICAL value onto the stack, representing an
+     * uninitialized lexical binding.
+     *
+     * This opcode is used with the JSOP_INITLET opcode.
+     *   Category: Literals
+     *   Type: Constants
+     *   Operands:
+     *   Stack: => uninitialized
+     */ \
+    macro(JSOP_UNINITIALIZED, 142, "uninitialized", NULL, 1,  0,  1, JOF_BYTE) \
+    /* Pushes the value of the intrinsic onto the stack.
      *
      * Intrinsic names are emitted instead of JSOP_*NAME ops when the
      * 'CompileOptions' flag 'selfHostingMode' is set.
      *
      * They are used in self-hosted code to access other self-hosted values and
      * intrinsic functions the runtime doesn't give client JS code access to.
      *   Category: Variables and Scopes
      *   Type: Intrinsics
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -48,16 +48,25 @@ CallObject::setAliasedVar(JSContext *cx,
 inline void
 CallObject::setAliasedVarFromArguments(JSContext *cx, const Value &argsValue, jsid id, const Value &v)
 {
     setSlot(argsValue.magicUint32(), v);
     if (hasSingletonType())
         types::AddTypePropertyId(cx, this, id, v);
 }
 
+inline void
+CallObject::setAliasedLexicalsToThrowOnTouch(JSScript *script)
+{
+    uint32_t aliasedLexicalBegin = script->bindings.aliasedBodyLevelLexicalBegin();
+    uint32_t aliasedLexicalEnd = numFixedSlots();
+    for (uint32_t slot = aliasedLexicalBegin; slot < aliasedLexicalEnd; slot++)
+        initFixedSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL));
+}
+
 template <AllowGC allowGC>
 inline bool
 StaticScopeIter<allowGC>::done() const
 {
     return !obj;
 }
 
 template <AllowGC allowGC>
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -212,16 +212,17 @@ CallObject::create(JSContext *cx, Handle
 {
     gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
     CallObject *callobj = CallObject::createTemplateObject(cx, script, heap);
     if (!callobj)
         return nullptr;
 
     callobj->as<ScopeObject>().setEnclosingScope(enclosing);
     callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));
+    callobj->setAliasedLexicalsToThrowOnTouch(script);
 
     if (script->treatAsRunOnce()) {
         Rooted<CallObject*> ncallobj(cx, callobj);
         if (!JSObject::setSingletonType(cx, ncallobj))
             return nullptr;
         return ncallobj;
     }
 
@@ -1152,41 +1153,41 @@ class DebugScopeProxy : public BaseProxy
     bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope,
                                Handle<ScopeObject*> scope, jsid id, Action action,
                                MutableHandleValue vp, AccessResult *accessResult) const
     {
         JS_ASSERT(&debugScope->scope() == scope);
         *accessResult = ACCESS_GENERIC;
         ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope);
 
-        /* Handle unaliased formals, vars, and consts at function scope. */
+        /* Handle unaliased formals, vars, lets, and consts at function scope. */
         if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
             CallObject &callobj = scope->as<CallObject>();
             RootedScript script(cx, callobj.callee().nonLazyScript());
             if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx))
                 return false;
 
             Bindings &bindings = script->bindings;
             BindingIter bi(script);
             while (bi && NameToId(bi->name()) != id)
                 bi++;
             if (!bi)
                 return true;
 
             if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) {
                 uint32_t i = bi.frameIndex();
-                if (script->varIsAliased(i))
+                if (script->bodyLevelLocalIsAliased(i))
                     return true;
 
                 if (maybeLiveScope) {
                     AbstractFramePtr frame = maybeLiveScope->frame();
                     if (action == GET)
-                        vp.set(frame.unaliasedVar(i));
+                        vp.set(frame.unaliasedLocal(i));
                     else
-                        frame.unaliasedVar(i) = vp;
+                        frame.unaliasedLocal(i) = vp;
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(bindings.numArgs() + i));
                     else
                         snapshot->setDenseElement(bindings.numArgs() + i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
                     if (action == GET) {
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -228,16 +228,18 @@ class ScopeObject : public JSObject
 
 class CallObject : public ScopeObject
 {
     static const uint32_t CALLEE_SLOT = 1;
 
     static CallObject *
     create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
 
+    inline void setAliasedLexicalsToThrowOnTouch(JSScript *script);
+
   public:
     static const Class class_;
 
     /* These functions are internal and are exposed only for JITs. */
 
     /*
      * Construct a bare-bones call object given a shape and a non-singleton
      * type.  The call object must be further initialized to be usable.
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -83,23 +83,34 @@ InterpreterFrame::initCallFrame(JSContex
     argv_ = argv;
     exec.fun = &callee;
     u.nactual = nactual;
     scopeChain_ = callee.environment();
     prev_ = prev;
     prevpc_ = prevpc;
     prevsp_ = prevsp;
 
-    initVarsToUndefined();
+    initLocals();
 }
 
 inline void
-InterpreterFrame::initVarsToUndefined()
+InterpreterFrame::initLocals()
 {
-    SetValueRangeToUndefined(slots(), script()->nfixed());
+    SetValueRangeToUndefined(slots(), script()->nfixedvars());
+
+    // Lexical bindings throw ReferenceErrors if they are used before
+    // initialization. See ES6 8.1.1.1.6.
+    //
+    // For completeness, lexical bindings are initialized in ES6 by calling
+    // InitializeBinding, after which touching the binding will no longer
+    // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
+    // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
+    Value *lexicalEnd = slots() + script()->fixedLexicalEnd();
+    for (Value *lexical = slots() + script()->fixedLexicalBegin(); lexical != lexicalEnd; ++lexical)
+        lexical->setMagic(JS_UNINITIALIZED_LEXICAL);
 }
 
 inline Value &
 InterpreterFrame::unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i));
     JS_ASSERT(i < script()->nfixedvars());
     return slots()[i];
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -393,29 +393,29 @@ InterpreterFrame::markValues(JSTracer *t
 
     size_t nfixed = script()->nfixed();
     size_t nlivefixed;
 
     if (staticScope) {
         StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
         nlivefixed = blockObj.localOffset() + blockObj.numVariables();
     } else {
-        nlivefixed = script()->nfixedvars();
+        nlivefixed = script()->nbodyfixed();
     }
 
     if (nfixed == nlivefixed) {
         // All locals are live.
         markValues(trc, 0, sp - slots());
     } else {
         // Mark operand stack.
         markValues(trc, nfixed, sp - slots());
 
-        // Clear dead locals.
+        // Clear dead block-scoped locals.
         while (nfixed > nlivefixed)
-            unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setUndefined();
+            unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setMagic(JS_UNINITIALIZED_LEXICAL);
 
         // Mark live locals.
         markValues(trc, 0, nlivefixed);
     }
 
     if (hasArgs()) {
         // Mark callee, |this| and arguments.
         unsigned argc = Max(numActualArgs(), numFormalArgs());
@@ -490,17 +490,17 @@ InterpreterStack::pushExecuteFrame(JSCon
     unsigned nvars = 2 /* callee, this */ + script->nslots();
     uint8_t *buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
     if (!buffer)
         return nullptr;
 
     InterpreterFrame *fp = reinterpret_cast<InterpreterFrame *>(buffer + 2 * sizeof(Value));
     fp->mark_ = mark;
     fp->initExecuteFrame(cx, script, evalInFrame, thisv, *scopeChain, type);
-    fp->initVarsToUndefined();
+    fp->initLocals();
 
     return fp;
 }
 
 /*****************************************************************************/
 
 void
 FrameIter::popActivation()
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -61,16 +61,17 @@ class ScopeCoordinate;
 //
 // The top of an activation's current frame's expression stack is pointed to by
 // the activation's "current regs", which contains the stack pointer 'sp'. In
 // the interpreter, sp is adjusted as individual values are pushed and popped
 // from the stack and the InterpreterRegs struct (pointed to by the
 // InterpreterActivation) is a local var of js::Interpret.
 
 enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false };
+enum MaybeCheckLexical { CheckLexical = true, DontCheckLexical = false };
 
 /*****************************************************************************/
 
 #ifdef DEBUG
 extern void
 CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint32_t i);
 #endif
 
@@ -418,18 +419,22 @@ class InterpreterFrame
      * 'prologue' has completed and handles all the intermediate state details.
      */
 
     bool prologue(JSContext *cx);
     void epilogue(JSContext *cx);
 
     bool initFunctionScopeObjects(JSContext *cx);
 
-    /* Initialize local variables of newly-pushed frame. */
-    void initVarsToUndefined();
+    /*
+     * Initialize local variables of newly-pushed frame. 'var' bindings are
+     * initialized to undefined and lexical bindings are initialized to
+     * JS_UNINITIALIZED_LEXICAL.
+     */
+    void initLocals();
 
     /*
      * Stack frame type
      *
      * A stack frame may have one of three types, which determines which
      * members of the frame may be accessed and other invariants:
      *
      *  global frame:   execution of global code or an eval in global code