From 7460fb764d96f28b6759b6fa73c5cac138678607 Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Wed, 12 Feb 2014 18:46:24 +0100 Subject: [PATCH 01/40] Bug 962599 - Store let-bound variables in the fixed part of stack frames r=luke r=jandem --- js/src/frontend/BytecodeCompiler.cpp | 27 +- js/src/frontend/BytecodeEmitter.cpp | 372 ++++++++---------- js/src/frontend/FullParseHandler.h | 21 +- js/src/frontend/ParseNode.h | 3 +- js/src/frontend/Parser.cpp | 104 ++++- js/src/frontend/Parser.h | 15 +- js/src/frontend/SyntaxParseHandler.h | 5 +- js/src/jit-test/tests/basic/testBug579647.js | 4 +- js/src/jit/AsmJS.cpp | 3 +- js/src/jit/BaselineCompiler.cpp | 24 +- js/src/jit/BaselineCompiler.h | 2 +- js/src/jit/BaselineFrame.h | 2 +- js/src/jit/BaselineFrameInfo.h | 11 +- js/src/jit/CompileInfo.h | 61 ++- js/src/jit/IonBuilder.cpp | 30 +- js/src/jsanalyze.cpp | 36 +- js/src/jsopcode.cpp | 86 ++-- js/src/jsscript.cpp | 25 +- js/src/jsscript.h | 56 ++- js/src/jsscriptinlines.h | 3 +- .../tests/js1_8_1/regress/regress-420399.js | 2 +- js/src/vm/Interpreter.cpp | 18 +- js/src/vm/Opcodes.h | 4 +- js/src/vm/ScopeObject.cpp | 35 +- js/src/vm/ScopeObject.h | 57 +-- js/src/vm/Stack-inl.h | 3 +- js/src/vm/Stack.cpp | 4 +- 27 files changed, 551 insertions(+), 462 deletions(-) diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index d5a6f96e846..57b9483280c 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -268,12 +268,6 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco if (!script) return nullptr; - // Global/eval script bindings are always empty (all names are added to the - // scope dynamically via JSOP_DEFFUN/VAR). - InternalHandle bindings(script, &script->bindings); - if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr)) - return nullptr; - // 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; @@ -293,7 +287,8 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco Maybe > pc; pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc, - (Directives *) nullptr, staticLevel, /* bodyid = */ 0); + (Directives *) nullptr, staticLevel, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!pc.ref().init(parser.tokenStream)) return nullptr; @@ -360,10 +355,12 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco pc.destroy(); pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, - &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0); + &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0, + script->bindings.numBlockScoped()); if (!pc.ref().init(parser.tokenStream)) return nullptr; JS_ASSERT(parser.pc == pc.addr()); + pn = parser.statement(); } if (!pn) { @@ -372,6 +369,11 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco } } + // Accumulate the maximum block scope depth, so that EmitTree can assert + // when emitting JSOP_GETLOCAL that the local is indeed within the fixed + // part of the stack frame. + script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth); + if (canHaveDirectives) { if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives)) return nullptr; @@ -414,6 +416,15 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco 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(script, &script->bindings); + if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr, + pc.ref().blockScopeDepth)) + return nullptr; + if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return nullptr; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 350f0c50476..bec627765b7 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -257,6 +257,34 @@ EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc) return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc)); } +// Dup the var in operand stack slot "slot". The first item on the operand +// stack is one slot past the last fixed slot. The last (most recent) item is +// slot bce->stackDepth - 1. +// +// The instruction that is written (JSOP_DUPAT) switches the depth around so +// that it is addressed from the sp instead of from the fp. This is useful when +// you don't know the size of the fixed stack segment (nfixed), as is the case +// when compiling scripts (because each statement is parsed and compiled +// separately, but they all together form one script with one fixed stack +// frame). +static bool +EmitDupAt(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned slot) +{ + JS_ASSERT(slot < unsigned(bce->stackDepth)); + // The slot's position on the operand stack, measured from the top. + unsigned slotFromTop = bce->stackDepth - 1 - slot; + if (slotFromTop >= JS_BIT(24)) { + bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + ptrdiff_t off = EmitN(cx, bce, JSOP_DUPAT, 3); + if (off < 0) + return false; + jsbytecode *pc = bce->code(off); + SET_UINT24(pc, slotFromTop); + return true; +} + /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ const char js_with_statement_str[] = "with statement"; const char js_finally_block_str[] = "finally block"; @@ -583,7 +611,6 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) return false; } - npops += blockObj.slotCount(); } } @@ -658,25 +685,6 @@ EnclosingStaticScope(BytecodeEmitter *bce) return bce->sc->asFunctionBox()->function(); } -// In a stack frame, block-scoped locals follow hoisted var-bound locals. If -// the current compilation unit is a function, add the number of "fixed slots" -// (var-bound locals) to the given block-scoped index, to arrive at its final -// position in the call frame. -// -static bool -AdjustBlockSlot(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t *slot) -{ - JS_ASSERT(*slot < bce->maxStackDepth); - if (bce->sc->isFunctionBox()) { - *slot += bce->script->bindings.numVars(); - if (*slot >= StaticBlockObject::VAR_INDEX_LIMIT) { - bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); - return false; - } - } - return true; -} - #ifdef DEBUG static bool AllLocalsAliased(StaticBlockObject &obj) @@ -691,10 +699,6 @@ AllLocalsAliased(StaticBlockObject &obj) static bool ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) { - uint32_t depthPlusFixed = blockObj->stackDepth(); - if (!AdjustBlockSlot(cx, bce, &depthPlusFixed)) - return false; - for (unsigned i = 0; i < blockObj->slotCount(); i++) { Definition *dn = blockObj->maybeDefinitionParseNode(i); @@ -706,7 +710,7 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, HandleisDefn()); if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(), - dn->frameSlot() + depthPlusFixed)) + blockObj->varToLocalIndex(dn->frameSlot()))) { return false; } @@ -730,6 +734,79 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) +{ + unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0; + unsigned localOffset = nfixedvars; + + if (bce->staticScope) { + Rooted outer(cx, bce->staticScope); + for (; outer; outer = outer->enclosingNestedScope()) { + if (outer->is()) { + StaticBlockObject &outerBlock = outer->as(); + localOffset = outerBlock.localOffset() + outerBlock.slotCount(); + break; + } + } + } + + JS_ASSERT(localOffset + blockObj->slotCount() + <= nfixedvars + 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 +// "with" object or a "block" object. "With" objects represent "with" scopes. +// Block objects represent lexical scopes, and contain named block-scoped +// bindings, for example "let" bindings or the exception in a catch block. +// Those variables may be local and thus accessible directly from the stack, or +// "aliased" (accessed by name from nested functions, or dynamically via nested +// "eval" or "with") and only accessible through the scope chain. +// +// All nested scopes are present on the "static scope chain". A nested scope +// that is a "with" scope will be present on the scope chain at run-time as +// well. A block scope may or may not have a corresponding link on the run-time +// scope chain; if no variable declared in the block scope is "aliased", then no +// scope chain node is allocated. +// +// To help debuggers, the bytecode emitter arranges to record the PC ranges +// comprehended by a nested scope, and ultimately attach them to the JSScript. +// An element in the "block scope array" specifies the PC range, and links to a +// NestedScopeObject in the object list of the script. That scope object is +// linked to the previous link in the static scope chain, if any. The static +// scope chain at any pre-retire PC can be retrieved using +// JSScript::getStaticScope(jsbytecode *pc). +// +// Block scopes store their locals in the fixed part of a stack frame, after the +// "fixed var" bindings. A fixed var binding is a "var" or legacy "const" +// binding that occurs in a function (as opposed to a script or in eval code). +// Only functions have fixed var bindings. +// +// To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a +// block scope, even if the block has no aliased locals. This allows +// DebugScopes to invalidate any association between a debugger scope object, +// which can proxy access to unaliased stack locals, and the actual live frame. +// In normal, non-debug mode, this opcode does not cause any baseline code to be +// emitted. +// +// Enter a nested scope with EnterNestedScope. It will emit +// PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of +// the scope. Leave a nested scope with LeaveNestedScope, which, for blocks, +// will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. (For "with" scopes it +// emits LEAVEWITH, of course.) Pass EnterNestedScope a fresh StmtInfoBCE +// object, and pass that same object to the corresponding LeaveNestedScope. If +// the statement is a block scope, pass STMT_BLOCK as stmtType; otherwise for +// with scopes pass STMT_WITH. +// static bool EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, StmtType stmtType) @@ -741,6 +818,8 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, case STMT_BLOCK: { Rooted blockObj(cx, &scopeObj->as()); + ComputeLocalOffset(cx, bce, blockObj); + if (!ComputeAliasedSlots(cx, bce, blockObj)) return false; @@ -760,8 +839,7 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, } uint32_t parent = BlockScopeNote::NoBlockScopeIndex; - if (bce->staticScope) { - StmtInfoBCE *stmt = bce->topScopeStmt; + if (StmtInfoBCE *stmt = bce->topScopeStmt) { for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {} parent = stmt->blockScopeIndex; } @@ -779,71 +857,6 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, return true; } -// ~ Block Scopes ~ -// -// A block scope is a region of a script with an additional set of named -// variables. Those variables may be local and thus accessible directly from -// the stack, or "aliased" and only accessible through the scope chain. -// -// A block scope may or may not have a corresponding link on the scope chain. -// If no variable declared in the scope is "aliased", then no scope chain node -// is allocated. -// -// To help debuggers, the bytecode emitter arranges to record the PC ranges -// comprehended by a block scope, and ultimately attach them to the JSScript. -// An element in the "block scope array" specifies the PC range, and links to a -// StaticBlockObject in the object list of the script. That block is linked to -// the previous block in the scope, if any. The static block chain at any -// pre-retire PC can be retrieved using JSScript::getStaticScope(jsbytecode *pc). -// -// When PUSHBLOCKSCOPE is executed, it assumes that the block's locals are -// already on the stack. Initial values of "aliased" locals are copied from the -// stack to the ClonedBlockObject, and no further access is made to the stack -// slot. -// -// Likewise after leaving a POPBLOCKSCOPE, we will need to emit code to pop the -// stack values. -// -// Finally, to assist the debugger, we also emit a DEBUGLEAVEBLOCK opcode before -// POPBLOCKSCOPE in all cases -- even if the block has no aliased locals. This -// allows DebugScopes to invalidate any association between a debugger scope -// object, which can proxy access to unaliased stack locals, and the actual live -// frame. In normal, non-debug mode, this opcode does not cause any baseline -// code to be emitted. -// -// In this function "extraSlots" indicates the number of slots that are -// "floating" on the stack above the scope's slots. This will only be nonzero -// in the case of for-let-in and for-let-of loops, where loop iterator state -// floats above the block scopes. It would be nice to fix this eventually so -// that loop iterator state gets assigned to block-scoped fp-addressable -// temporaries, instead of being addressable only via the sp. This would also -// make generators more efficient, as the loop state could be heap-allocated, so -// that the value stack would likely be empty at yield points inside for-of / -// for-in loops. -// -// Summary: Enter block scopes with EnterBlockScope. It will emit -// PUSHBLOCKSCOPE if needed. Leave them with LeaveNestedScope, which will emit -// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. Pass EnterBlockScope a fresh -// StmtInfoBCE object, and pass that same object to the corresponding -// LeaveNestedScope. Push locals before entering a scope, and pop them -// afterwards. Brush your teeth, and clean behind your ears! -// -static bool -EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, - unsigned extraSlots) -{ - Rooted blockObj(cx, &objbox->object->as()); - - // FIXME: Once bug 962599 lands, we won't care about the stack depth, so we - // won't have extraSlots and thus invocations of EnterBlockScope can become - // invocations of EnterNestedScope. - int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots); - JS_ASSERT(depth >= 0); - blockObj->setStackDepth(depth); - - return EnterNestedScope(cx, bce, stmt, objbox, STMT_BLOCK); -} - // Patches |breaks| and |continues| unless the top statement info record // represents a try-catch-finally suite. May fail if a jump offset overflows. static bool @@ -1140,17 +1153,17 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter * return false; JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc)); } else { - uint32_t depth = local - bceOfDef->script->bindings.numVars(); + JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals()); JS_ASSERT(bceOfDef->staticScope->is()); Rooted b(cx, &bceOfDef->staticScope->as()); - while (!b->containsVarAtDepth(depth)) { + while (local < b->localOffset()) { if (b->needsClone()) skippedScopes++; b = &b->enclosingNestedScope()->as(); } if (!AssignHops(bce, pn, skippedScopes, &sc)) return false; - sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local)); + sc.setSlot(b->localIndexToSlot(local)); } } @@ -2415,6 +2428,55 @@ SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off) SET_JUMP_OFFSET(bce->code(off), bce->offset() - off); } +static bool +PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n) +{ + for (unsigned i = 0; i < n; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + return true; +} + +static bool +InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce, + Handle blockObj) +{ + for (unsigned i = blockObj->slotCount(); 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)) + return false; + } else { + if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, blockObj->varToLocalIndex(i - 1), 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. + Rooted blockObj(cx, &objbox->object->as()); + if (!PushUndefinedValues(cx, bce, blockObj->slotCount() - alreadyPushed)) + return false; + + if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK)) + return false; + + if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj)) + return false; + + return true; +} + /* * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. * LLVM is deciding to inline this function which uses a lot of stack space @@ -2440,27 +2502,15 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) pn2 = pn->pn_right; JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST)); - /* - * If there are hoisted let declarations, their stack slots go under the - * discriminant's value so push their slots now and enter the block later. - */ - Rooted blockObj(cx, nullptr); - if (pn2->isKind(PNK_LEXICALSCOPE)) { - blockObj = &pn2->pn_objbox->object->as(); - for (uint32_t i = 0; i < blockObj->slotCount(); ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - } - /* 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, 1)) + if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 0)) return false; + stmtInfo.type = STMT_SWITCH; stmtInfo.update = top = bce->offset(); /* Advance pn2 to refer to the switch case list. */ @@ -2746,7 +2796,6 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount()); } else { if (!PopStatementBCE(cx, bce)) return false; @@ -3277,11 +3326,8 @@ EmitGroupAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, for (pn = lhs->pn_head; pn; pn = pn->pn_next, ++i) { /* MaybeEmitGroupAssignment requires lhs->pn_count <= rhs->pn_count. */ JS_ASSERT(i < limit); - uint32_t slot = i; - if (!AdjustBlockSlot(cx, bce, &slot)) - return false; - if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce)) + if (!EmitDupAt(cx, bce, i)) return false; if (pn->isKind(PNK_ELISION)) { @@ -4036,7 +4082,6 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (ParseNode *pn2 = pn->pn_kid2) { // The emitted code for a catch block looks like: // - // undefined... as many as there are locals in the catch block // [pushblockscope] only if any local aliased // exception // if there is a catchguard: @@ -4047,14 +4092,12 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) // ifne POST // debugleaveblock // [popblockscope] only if any local aliased - // popnv leave exception on top // throwing pop exception to cx->exception // goto // POST: pop // < catch block contents > // debugleaveblock // [popblockscope] only if any local aliased - // popn // goto non-local; finally applies // // If there's no catch block without a catchguard, the last scopeChain and does not * otherwise touch the stack, evaluation of the let-var initializers must leave @@ -4272,7 +4317,6 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) JS_ASSERT(varList->isArity(PN_LIST)); ParseNode *letBody = pnLet->pn_right; JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE)); - Rooted blockObj(cx, &letBody->pn_objbox->object->as()); int letHeadDepth = bce->stackDepth; @@ -4281,14 +4325,8 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */ uint32_t alreadyPushed = bce->stackDepth - letHeadDepth; - uint32_t blockObjCount = blockObj->slotCount(); - for (uint32_t i = alreadyPushed; i < blockObjCount; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - StmtInfoBCE stmtInfo(cx); - if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0)) + if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed)) return false; if (!EmitTree(cx, bce, letBody->pn_expr)) @@ -4297,10 +4335,6 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - JSOp leaveOp = letBody->getOp(); - JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV); - EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount()); - return true; } @@ -4312,19 +4346,9 @@ MOZ_NEVER_INLINE static bool EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); - JS_ASSERT(pn->getOp() == JSOP_POPN); StmtInfoBCE stmtInfo(cx); - ObjectBox *objbox = pn->pn_objbox; - StaticBlockObject &blockObj = objbox->object->as(); - size_t slots = blockObj.slotCount(); - - for (size_t n = 0; n < slots; ++n) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - - if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0)) + if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0)) return false; if (!EmitTree(cx, bce, pn->pn_expr)) @@ -4333,8 +4357,6 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - EMIT_UINT16_IMM_OP(JSOP_POPN, slots); - return true; } @@ -4363,18 +4385,6 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); JS_ASSERT_IF(letDecl, pn1->isLet()); - Rooted - blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr); - uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0; - - // For-of loops run with two values on the stack: the iterator and the - // current result object. If the loop also has a lexical block, those - // lexicals are deeper on the stack than the iterator. - for (uint32_t i = 0; i < blockObjCount; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - // 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) { @@ -4386,6 +4396,9 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bce->emittingForInit = false; } + // 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; @@ -4408,7 +4421,7 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t // Enter the block before the loop body, after evaluating the obj. StmtInfoBCE letStmt(cx); if (letDecl) { - if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2)) + if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) return false; } @@ -4501,8 +4514,8 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t return false; } - // Pop result, iter, and slots from the lexical block (if any). - EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2); + // Pop the result and the iter. + EMIT_UINT16_IMM_OP(JSOP_POPN, 2); return true; } @@ -4517,38 +4530,6 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); JS_ASSERT_IF(letDecl, pn1->isLet()); - Rooted - blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr); - uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0; - - if (letDecl) { - /* - * The let's slot(s) will be under the iterator, but the block must not - * be entered until after evaluating the rhs. So, we reserve space for - * the block scope now, and only push the block onto the scope chain - * later. Thus, a for-let-in loop looks like: - * - * push x N - * eval rhs - * iter - * pushblockscope (if needed) - * goto - * ... loop body - * ifne - * debugleaveblock - * popblockscope (if needed) - * enditer - * popn(N) - * - * Note that pushblockscope and popblockscope only get emitted if some - * of the variables in the block are captured. - */ - for (uint32_t i = 0; i < blockObjCount; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - } - /* * 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 @@ -4580,7 +4561,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t /* Enter the block before the loop body, after evaluating the obj. */ StmtInfoBCE letStmt(cx); if (letDecl) { - if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1)) + if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) return false; } @@ -4662,7 +4643,6 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t if (letDecl) { if (!LeaveNestedScope(cx, bce, &letStmt)) return false; - EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); } return true; @@ -4954,7 +4934,8 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) BindingIter bi(bce->script); while (bi->name() != fun->atom()) bi++; - JS_ASSERT(bi->kind() == VARIABLE || bi->kind() == CONSTANT || bi->kind() == ARGUMENT); + JS_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT || + bi->kind() == Binding::ARGUMENT); JS_ASSERT(bi.frameIndex() < JS_BIT(20)); #endif pn->pn_index = index; @@ -6523,10 +6504,7 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) */ if (!EmitTree(cx, bce, pn->pn_kid)) return false; - uint32_t slot = bce->arrayCompDepth; - if (!AdjustBlockSlot(cx, bce, &slot)) - return false; - if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce)) + if (!EmitDupAt(cx, bce, bce->arrayCompDepth)) return false; if (Emit1(cx, bce, JSOP_ARRAYPUSH) < 0) return false; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index f93dd7a3226..725b3ce15dd 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -421,8 +421,6 @@ class FullParseHandler inline bool addCatchBlock(ParseNode *catchList, ParseNode *letBlock, ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody); - inline void setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr); - inline void setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *pn); inline ParseNode *newFunctionDefinition(); void setFunctionBody(ParseNode *pn, ParseNode *kid) { @@ -435,7 +433,10 @@ class FullParseHandler void addFunctionArgument(ParseNode *pn, ParseNode *argpn) { pn->pn_body->append(argpn); } + inline ParseNode *newLexicalScope(ObjectBox *blockbox); + inline void setLexicalScopeBody(ParseNode *block, ParseNode *body); + bool isOperationWithoutParens(ParseNode *pn, ParseNodeKind kind) { return pn->isKind(kind) && !pn->isInParens(); } @@ -597,15 +598,6 @@ FullParseHandler::addCatchBlock(ParseNode *catchList, ParseNode *letBlock, return true; } -inline void -FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr) -{ - JS_ASSERT(block->isOp(JSOP_POPN)); - if (leaveBlockExpr) - block->setOp(JSOP_POPNV); - block->pn_expr = kid; -} - inline void FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue) { @@ -634,13 +626,18 @@ FullParseHandler::newLexicalScope(ObjectBox *blockbox) if (!pn) return nullptr; - pn->setOp(JSOP_POPN); pn->pn_objbox = blockbox; pn->pn_cookie.makeFree(); pn->pn_dflags = 0; return pn; } +inline void +FullParseHandler::setLexicalScopeBody(ParseNode *block, ParseNode *kid) +{ + block->pn_expr = kid; +} + inline bool FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op) { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 45df351ee5f..d76c868a971 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -404,8 +404,7 @@ enum ParseNodeKind * PNK_NULL, * PNK_THIS * - * PNK_LEXICALSCOPE name pn_op: JSOP_POPN or JSOP_POPNV - * pn_objbox: block object in ObjectBox holder + * PNK_LEXICALSCOPE name pn_objbox: block object in ObjectBox holder * pn_expr: block body * PNK_ARRAYCOMP list pn_count: 1 * pn_head: list of 1 element, which is block diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 8fc7b4552ab..ec7032de31e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -272,16 +272,16 @@ AppendPackedBindings(const ParseContext *pc, const DeclVector &vec Definition *dn = vec[i]; PropertyName *name = dn->name(); - BindingKind kind; + Binding::Kind kind; switch (dn->kind()) { case Definition::VAR: - kind = VARIABLE; + kind = Binding::VARIABLE; break; case Definition::CONST: - kind = CONSTANT; + kind = Binding::CONSTANT; break; case Definition::ARG: - kind = ARGUMENT; + kind = Binding::ARGUMENT; break; default: MOZ_ASSUME_UNREACHABLE("unexpected dn->kind"); @@ -329,7 +329,7 @@ ParseContext::generateFunctionBindings(ExclusiveContext *cx, Token AppendPackedBindings(this, vars_, packedBindings + args_.length()); return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(), - packedBindings); + packedBindings, blockScopeDepth); } template @@ -615,7 +615,8 @@ Parser::parse(JSObject *chain) GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption); ParseContext globalpc(this, /* parent = */ nullptr, ParseHandler::null(), &globalsc, /* newDirectives = */ nullptr, - /* staticLevel = */ 0, /* bodyid = */ 0); + /* staticLevel = */ 0, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!globalpc.init(tokenStream)) return null(); @@ -877,7 +878,8 @@ Parser::standaloneFunctionBody(HandleFunction fun, const AutoN handler.setFunctionBox(fn, funbox); ParseContext funpc(this, pc, fn, funbox, newDirectives, - /* staticLevel = */ 0, /* bodyid = */ 0); + /* staticLevel = */ 0, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return null(); @@ -2125,7 +2127,8 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, ParseContext funpc(parser, outerpc, SyntaxParseHandler::null(), funbox, newDirectives, outerpc->staticLevel + 1, - outerpc->blockidGen); + outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return false; @@ -2160,7 +2163,8 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, // Continue doing a full parse for this inner function. ParseContext funpc(this, pc, pn, funbox, newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return false; @@ -2199,7 +2203,8 @@ Parser::functionArgsAndBody(Node pn, HandleFunction fun, // Initialize early for possible flags mutation via destructuringExpr. ParseContext funpc(this, pc, handler.null(), funbox, newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return false; @@ -2234,7 +2239,8 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st Directives newDirectives = directives; ParseContext funpc(this, /* parent = */ nullptr, pn, funbox, - &newDirectives, staticLevel, /* bodyid = */ 0); + &newDirectives, staticLevel, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return null(); @@ -2688,7 +2694,7 @@ Parser::bindLet(BindData *data, Rooted blockObj(cx, data->let.blockObj); unsigned index = blockObj->slotCount(); - if (index >= StaticBlockObject::VAR_INDEX_LIMIT) { + if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) { parser->report(ParseError, false, pn, data->let.overflow); return false; } @@ -2769,6 +2775,33 @@ struct PopLetDecl { } }; +// We compute the maximum block scope depth, in slots, of a compilation unit at +// parse-time. Each nested statement has a field indicating the maximum block +// scope depth that is nested inside it. When we leave a nested statement, we +// add the number of slots in the statement to the nested depth, and use that to +// update the maximum block scope depth of the outer statement or parse +// context. In the end, pc->blockScopeDepth will indicate the number of slots +// to reserve in the fixed part of a stack frame. +// +template +static void +AccumulateBlockScopeDepth(ParseContext *pc) +{ + uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth; + StmtInfoPC *outer = pc->topStmt->down; + + if (pc->topStmt->isBlockScope) + innerDepth += pc->topStmt->staticScope->template as().slotCount(); + + if (outer) { + if (outer->innerBlockScopeDepth < innerDepth) + outer->innerBlockScopeDepth = innerDepth; + } else { + if (pc->blockScopeDepth < innerDepth) + pc->blockScopeDepth = innerDepth; + } +} + template static void PopStatementPC(TokenStream &ts, ParseContext *pc) @@ -2776,6 +2809,7 @@ PopStatementPC(TokenStream &ts, ParseContext *pc) RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope); JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope); + AccumulateBlockScopeDepth(pc); FinishPopStatement(pc); if (scopeObj) { @@ -2823,7 +2857,7 @@ LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::Stmt JS_ASSERT(shape->hasShortID()); if (slotp) - *slotp = blockObj.stackDepth() + shape->shortid(); + *slotp = shape->shortid(); return stmt; } } @@ -3334,7 +3368,7 @@ Parser::letBlock(LetContext letContext) if (!expr) return null(); } - handler.setLeaveBlockResult(block, expr, letContext != LetStatement); + handler.setLexicalScopeBody(block, expr); PopStatementPC(tokenStream, pc); handler.setEndPosition(pnlet, pos().end); @@ -3612,7 +3646,6 @@ Parser::letDeclaration() if (!pn1) return null(); - pn1->setOp(JSOP_POPN); pn1->pn_pos = pc->blockNode->pn_pos; pn1->pn_objbox = blockbox; pn1->pn_expr = pc->blockNode; @@ -3648,8 +3681,6 @@ Parser::letStatement() if (tokenStream.peekToken() == TOK_LP) { pn = letBlock(LetStatement); JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); - JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_POPNV, - pn->pn_expr->isOp(JSOP_POPN)); } else { pn = letDeclaration(); } @@ -6002,6 +6033,31 @@ CompExprTransplanter::transplant(ParseNode *pn) return true; } +// Parsing JS1.7-style comprehensions is terrible: we parse the head expression +// as if it's part of a comma expression, then when we see the "for" we +// transplant the parsed expression into the inside of a constructed +// for-of/for-in/for-each tail. Transplanting an already-parsed expression is +// tricky, but the CompExprTransplanter handles most of that. +// +// The one remaining thing to patch up is the block scope depth. We need to +// compute the maximum block scope depth of a function, so we know how much +// space to reserve in the fixed part of a stack frame. Normally this is done +// whenever we leave a statement, via AccumulateBlockScopeDepth. However if the +// head has a let expression, we need to re-assign that depth to the tail of the +// comprehension. +// +// Thing is, we don't actually know what that depth is, because the only +// information we keep is the maximum nested depth within a statement, so we +// just conservatively propagate the maximum nested depth from the top statement +// to the comprehension tail. +// +template +static unsigned +ComprehensionHeadBlockScopeDepth(ParseContext *pc) +{ + return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth; +} + /* * Starting from a |for| keyword after the first array initialiser element or * an expression in an open parenthesis, parse the tail of the comprehension @@ -6015,7 +6071,8 @@ template <> ParseNode * Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, ParseContext *outerpc, - ParseNodeKind kind, JSOp op) + ParseNodeKind kind, JSOp op, + unsigned innerBlockScopeDepth) { /* * If we saw any inner functions while processing the generator expression @@ -6240,6 +6297,7 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bo pn2->pn_kid = kid; *pnp = pn2; + pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth; PopStatementPC(tokenStream, pc); return pn; } @@ -6263,7 +6321,8 @@ Parser::arrayInitializerComprehensionTail(ParseNode *pn) *pn->pn_tail = nullptr; ParseNode *pntop = comprehensionTail(pnexp, pn->pn_blockid, false, nullptr, - PNK_ARRAYPUSH, JSOP_ARRAYPUSH); + PNK_ARRAYPUSH, JSOP_ARRAYPUSH, + ComprehensionHeadBlockScopeDepth(pc)); if (!pntop) return false; pn->append(pntop); @@ -6333,7 +6392,8 @@ Parser::generatorExpr(ParseNode *kid) ParseContext genpc(this, outerpc, genfn, genFunbox, /* newDirectives = */ nullptr, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!genpc.init(tokenStream)) return null(); @@ -6351,7 +6411,9 @@ Parser::generatorExpr(ParseNode *kid) genFunbox->inGenexpLambda = true; genfn->pn_blockid = genpc.bodyid; - ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc); + ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc, + PNK_SEMI, JSOP_NOP, + ComprehensionHeadBlockScopeDepth(outerpc)); if (!body) return null(); JS_ASSERT(!genfn->pn_body); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index b4249354808..532df37fd49 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -28,8 +28,9 @@ struct StmtInfoPC : public StmtInfoBase { StmtInfoPC *downScope; /* next enclosing lexical scope */ uint32_t blockid; /* for simplified dominance computation */ + uint32_t innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */ - StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx) {} + StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {} }; typedef HashSet FuncStmtSet; @@ -118,6 +119,7 @@ struct ParseContext : public GenericParseContext bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == StarGenerator; } + 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: @@ -135,11 +137,6 @@ struct ParseContext : public GenericParseContext return args_.length(); } - uint32_t numVars() const { - JS_ASSERT(sc->isFunctionBox()); - return vars_.length(); - } - /* * This function adds a definition to the lexical scope represented by this * ParseContext. @@ -243,7 +240,7 @@ struct ParseContext : public GenericParseContext ParseContext(Parser *prs, GenericParseContext *parent, Node maybeFunction, SharedContext *sc, Directives *newDirectives, - unsigned staticLevel, uint32_t bodyid) + unsigned staticLevel, uint32_t bodyid, uint32_t blockScopeDepth) : GenericParseContext(parent, sc), bodyid(0), // initialized in init() blockidGen(bodyid), // used to set |bodyid| and subsequently incremented in init() @@ -253,6 +250,7 @@ struct ParseContext : public GenericParseContext maybeFunction(maybeFunction), staticLevel(staticLevel), lastYieldOffset(NoYieldOffset), + blockScopeDepth(blockScopeDepth), blockNode(ParseHandler::null()), decls_(prs->context, prs->alloc), args_(prs->context), @@ -547,7 +545,8 @@ class Parser : private AutoGCRooter, public StrictModeGetter Node condition(); Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp, ParseContext *outerpc, - ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP); + ParseNodeKind kind, JSOp op, + unsigned innerBlockScopeDepth); bool arrayInitializerComprehensionTail(Node pn); Node generatorExpr(Node kid); bool argumentList(Node listNode, bool *isSpread); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 1e5c05fba69..760d3c318d1 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -157,14 +157,15 @@ class SyntaxParseHandler bool addCatchBlock(Node catchList, Node letBlock, Node catchName, Node catchGuard, Node catchBody) { return true; } - void setLeaveBlockResult(Node block, Node kid, bool leaveBlockExpr) {} - void setLastFunctionArgumentDefault(Node funcpn, Node pn) {} Node newFunctionDefinition() { return NodeGeneric; } void setFunctionBody(Node pn, Node kid) {} void setFunctionBox(Node pn, FunctionBox *funbox) {} void addFunctionArgument(Node pn, Node argpn) {} + Node newLexicalScope(ObjectBox *blockbox) { return NodeGeneric; } + void setLexicalScopeBody(Node block, Node body) {} + bool isOperationWithoutParens(Node pn, ParseNodeKind kind) { // It is OK to return false here, callers should only use this method // for reporting strict option warnings and parsing code which the diff --git a/js/src/jit-test/tests/basic/testBug579647.js b/js/src/jit-test/tests/basic/testBug579647.js index 027f643a96f..4a4d41164b3 100644 --- a/js/src/jit-test/tests/basic/testBug579647.js +++ b/js/src/jit-test/tests/basic/testBug579647.js @@ -1,4 +1,4 @@ -expected = "TypeError: NaN is not a function"; +expected = "TypeError: a is not a function"; actual = ""; try { @@ -10,4 +10,4 @@ try { actual = '' + e; } -assertEq(expected, actual); +assertEq(actual, expected); diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index 81e129c20c9..d6249f7de7a 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -5269,7 +5269,8 @@ ParseFunction(ModuleCompiler &m, ParseNode **fnOut) Directives newDirectives = directives; AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(m.parser().tokenStream)) return false; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index ba64591f700..4d18eac5f21 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -854,10 +854,16 @@ BaselineCompiler::emit_JSOP_POPN() } bool -BaselineCompiler::emit_JSOP_POPNV() +BaselineCompiler::emit_JSOP_DUPAT() { - frame.popRegsAndSync(1); - frame.popn(GET_UINT16(pc)); + frame.syncStack(0); + + // DUPAT takes a value on the stack and re-pushes it on top. It's like + // GETLOCAL but it addresses from the top of the stack instead of from the + // stack frame. + + int depth = -(GET_UINT24(pc) + 1); + masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); frame.push(R0); return true; } @@ -2290,17 +2296,7 @@ BaselineCompiler::emit_JSOP_INITELEM_SETTER() bool BaselineCompiler::emit_JSOP_GETLOCAL() { - uint32_t local = GET_LOCALNO(pc); - - if (local >= frame.nlocals()) { - // Destructuring assignments may use GETLOCAL to access stack values. - frame.syncStack(0); - masm.loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)), R0); - frame.push(R0); - return true; - } - - frame.pushLocal(local); + frame.pushLocal(GET_LOCALNO(pc)); return true; } diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 35d85c02646..3c7300b1bb1 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -26,7 +26,7 @@ namespace jit { _(JSOP_LABEL) \ _(JSOP_POP) \ _(JSOP_POPN) \ - _(JSOP_POPNV) \ + _(JSOP_DUPAT) \ _(JSOP_ENTERWITH) \ _(JSOP_LEAVEWITH) \ _(JSOP_DUP) \ diff --git a/js/src/jit/BaselineFrame.h b/js/src/jit/BaselineFrame.h index ce4ad7f41fd..8d0601b645e 100644 --- a/js/src/jit/BaselineFrame.h +++ b/js/src/jit/BaselineFrame.h @@ -152,8 +152,8 @@ class BaselineFrame } Value &unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const { + JS_ASSERT(i < script()->nfixedvars()); JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i)); - JS_ASSERT(i < script()->nfixed()); return *valueSlot(i); } diff --git a/js/src/jit/BaselineFrameInfo.h b/js/src/jit/BaselineFrameInfo.h index a237f9d97c3..4454f4de242 100644 --- a/js/src/jit/BaselineFrameInfo.h +++ b/js/src/jit/BaselineFrameInfo.h @@ -243,6 +243,7 @@ class FrameInfo sv->setRegister(val, knownType); } inline void pushLocal(uint32_t local) { + JS_ASSERT(local < nlocals()); StackValue *sv = rawPush(); sv->setLocalSlot(local); } @@ -260,15 +261,7 @@ class FrameInfo sv->setStack(); } inline Address addressOfLocal(size_t local) const { -#ifdef DEBUG - if (local >= nlocals()) { - // GETLOCAL and SETLOCAL can be used to access stack values. This is - // fine, as long as they are synced. - size_t slot = local - nlocals(); - JS_ASSERT(slot < stackDepth()); - JS_ASSERT(stack[slot].kind() == StackValue::Stack); - } -#endif + JS_ASSERT(local < nlocals()); return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)); } Address addressOfArg(size_t arg) const { diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h index 76917670eb3..82f8b9cd169 100644 --- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -10,6 +10,7 @@ #include "jsfun.h" #include "jit/Registers.h" +#include "vm/ScopeObject.h" namespace js { namespace jit { @@ -60,20 +61,24 @@ 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(); nlocals_ = script->nfixed(); nstack_ = script->nslots() - script->nfixed(); nslots_ = nimplicit_ + nargs_ + nlocals_ + nstack_; } CompileInfo(unsigned nlocals, ExecutionMode executionMode) - : script_(nullptr), fun_(nullptr), osrPc_(nullptr), constructing_(false), - executionMode_(executionMode), scriptNeedsArgsObj_(false) + : script_(nullptr), fun_(nullptr), osrPc_(nullptr), osrStaticScope_(nullptr), + constructing_(false), executionMode_(executionMode), scriptNeedsArgsObj_(false) { nimplicit_ = 0; nargs_ = 0; + nfixedvars_ = 0; nlocals_ = nlocals; nstack_ = 1; /* For FunctionCompiler::pushPhiInput/popPhiOutput */ nslots_ = nlocals_ + nstack_; @@ -91,6 +96,9 @@ class CompileInfo jsbytecode *osrPc() { return osrPc_; } + NestedScopeObject *osrStaticScope() const { + return osrStaticScope_; + } bool hasOsrAt(jsbytecode *pc) { JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); @@ -155,7 +163,13 @@ class CompileInfo unsigned nargs() const { return nargs_; } - // Number of slots needed for local variables. + // 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 all local variables. This includes "fixed + // vars" (see above) and also block-scoped locals. unsigned nlocals() const { return nlocals_; } @@ -223,21 +237,33 @@ class CompileInfo return nimplicit() + nargs() + nlocals(); } - bool isSlotAliased(uint32_t index) const { + bool isSlotAliased(uint32_t index, NestedScopeObject *staticScope) const { if (funMaybeLazy() && index == thisSlot()) return false; uint32_t arg = index - firstArgSlot(); - if (arg < nargs()) { - if (script()->formalIsAliased(arg)) - return true; - return false; - } + if (arg < nargs()) + return script()->formalIsAliased(arg); - uint32_t var = index - firstLocalSlot(); - if (var < nlocals()) { - if (script()->varIsAliased(var)) - return true; + uint32_t local = index - firstLocalSlot(); + if (local < nlocals()) { + // First, check if this local is a var. + if (local < nfixedvars()) + return script()->varIsAliased(local); + + // Otherwise, it might be part of a block scope. + for (; staticScope; staticScope = staticScope->enclosingNestedScope()) { + if (!staticScope->is()) + continue; + StaticBlockObject &blockObj = staticScope->as(); + if (blockObj.localOffset() < local) { + if (local - blockObj.localOffset() < blockObj.slotCount()) + return blockObj.isAliased(local - blockObj.localOffset()); + return false; + } + } + + // In this static scope, this var is dead. return false; } @@ -245,6 +271,13 @@ class CompileInfo return false; } + bool isSlotAliasedAtEntry(uint32_t index) const { + return isSlotAliased(index, nullptr); + } + bool isSlotAliasedAtOsr(uint32_t index) const { + return isSlotAliased(index, osrStaticScope()); + } + bool hasArguments() const { return script()->argumentsHasVarBinding(); } @@ -269,12 +302,14 @@ class CompileInfo private: unsigned nimplicit_; unsigned nargs_; + unsigned nfixedvars_; unsigned nlocals_; unsigned nstack_; unsigned nslots_; JSScript *script_; JSFunction *fun_; jsbytecode *osrPc_; + NestedScopeObject *osrStaticScope_; bool constructing_; ExecutionMode executionMode_; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 4b2d9e140bc..5e65fbd723f 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -90,12 +90,18 @@ jit::NewBaselineFrameInspector(TempAllocator *temp, BaselineFrame *frame) if (!inspector->varTypes.reserve(frame->script()->nfixed())) return nullptr; - for (size_t i = 0; i < frame->script()->nfixed(); i++) { + for (size_t i = 0; i < frame->script()->nfixedvars(); i++) { if (script->varIsAliased(i)) inspector->varTypes.infallibleAppend(types::Type::UndefinedType()); else inspector->varTypes.infallibleAppend(types::GetValueType(frame->unaliasedVar(i))); } + for (size_t i = frame->script()->nfixedvars(); i < frame->script()->nfixed(); i++) { + // FIXME: If this slot corresponds to a scope that is active at this PC, + // and the slot is unaliased, we should initialize the type from the + // slot value, as above. + inspector->varTypes.infallibleAppend(types::Type::UndefinedType()); + } return inspector; } @@ -1153,11 +1159,10 @@ IonBuilder::maybeAddOsrTypeBarriers() headerPhi++; for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++, headerPhi++) { - // Aliased slots are never accessed, since they need to go through // the callobject. The typebarriers are added there and can be - // discared here. - if (info().isSlotAliased(i)) + // discarded here. + if (info().isSlotAliasedAtOsr(i)) continue; MInstruction *def = osrBlock->getSlot(i)->toInstruction(); @@ -1302,7 +1307,7 @@ IonBuilder::traverseBytecode() switch (op) { case JSOP_POP: case JSOP_POPN: - case JSOP_POPNV: + case JSOP_DUPAT: case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: @@ -1552,14 +1557,9 @@ IonBuilder::inspectOpcode(JSOp op) current->pop(); return true; - case JSOP_POPNV: - { - MDefinition *mins = current->pop(); - for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) - current->pop(); - current->push(mins); + case JSOP_DUPAT: + current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc)); return true; - } case JSOP_NEWINIT: if (GET_UINT8(pc) == JSProto_Array) @@ -5837,11 +5837,11 @@ IonBuilder::newOsrPreheader(MBasicBlock *predecessor, jsbytecode *loopEntry) for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++) { MDefinition *existing = current->getSlot(i); MDefinition *def = osrBlock->getSlot(i); - JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliased(i), def->type() == MIRType_Value); + JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliasedAtOsr(i), def->type() == MIRType_Value); // Aliased slots are never accessed, since they need to go through // the callobject. No need to type them here. - if (info().isSlotAliased(i)) + if (info().isSlotAliasedAtOsr(i)) continue; def->setResultType(existing->type()); @@ -5881,7 +5881,7 @@ IonBuilder::newPendingLoopHeader(MBasicBlock *predecessor, jsbytecode *pc, bool // The value of aliased args and slots are in the callobject. So we can't // the value from the baseline frame. - if (info().isSlotAliased(i)) + if (info().isSlotAliasedAtOsr(i)) continue; // Don't bother with expression stack values. The stack should be diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 4ebc8953e72..2efe444bc99 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -766,7 +766,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) RootedScript script(cx, script_); for (BindingIter bi(script); bi; bi++) { - if (bi->kind() == ARGUMENT) + if (bi->kind() == Binding::ARGUMENT) escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased(); else escapedSlots[LocalSlot(script_, bi.frameIndex())] = allVarsAliased || bi->aliased(); @@ -928,32 +928,11 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) break; } - case JSOP_GETLOCAL: { - /* - * Watch for uses of variables not known to be defined, and mark - * them as having possible uses before definitions. Ignore GETLOCAL - * followed by a POP, these are generated for, e.g. 'var x;' - */ - jsbytecode *next = pc + JSOP_GETLOCAL_LENGTH; - if (JSOp(*next) != JSOP_POP || jumpTarget(next)) { - uint32_t local = GET_LOCALNO(pc); - if (local >= script_->nfixed()) { - localsAliasStack_ = true; - break; - } - } - break; - } - + case JSOP_GETLOCAL: case JSOP_CALLLOCAL: - case JSOP_SETLOCAL: { - uint32_t local = GET_LOCALNO(pc); - if (local >= script_->nfixed()) { - localsAliasStack_ = true; - break; - } + case JSOP_SETLOCAL: + JS_ASSERT(GET_LOCALNO(pc) < script_->nfixed()); break; - } case JSOP_PUSHBLOCKSCOPE: localsAliasStack_ = true; @@ -1822,6 +1801,13 @@ ScriptAnalysis::analyzeSSA(JSContext *cx) stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1]; break; + case JSOP_DUPAT: { + unsigned pickedDepth = GET_UINT24 (pc); + JS_ASSERT(pickedDepth < stackDepth - 1); + stack[stackDepth - 1].v = stack[stackDepth - 2 - pickedDepth].v; + break; + } + case JSOP_SWAP: /* Swap is like pick 1. */ case JSOP_PICK: { diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index fc7a00a3aa1..89622331dc7 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -117,8 +117,6 @@ js::StackUses(JSScript *script, jsbytecode *pc) switch (op) { case JSOP_POPN: return GET_UINT16(pc); - case JSOP_POPNV: - return GET_UINT16(pc) + 1; default: /* stack: fun, this, [argc arguments] */ JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || @@ -468,6 +466,16 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint } break; + case JSOP_DUPAT: { + JS_ASSERT(ndefs == 1); + jsbytecode *pc = script_->offsetToPC(offset); + unsigned n = GET_UINT24(pc); + JS_ASSERT(n < stackDepth); + if (offsetStack) + offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; + break; + } + case JSOP_SWAP: JS_ASSERT(ndefs == 2); if (offsetStack) { @@ -832,9 +840,9 @@ ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes) if (!JSVAL_IS_PRIMITIVE(v)) { JSObject *obj = JSVAL_TO_OBJECT(v); - if (obj->is()) { + if (obj->is()) { char *source = JS_sprintf_append(nullptr, "depth %d {", - obj->as().stackDepth()); + obj->as().localOffset()); if (!source) return false; @@ -1025,7 +1033,8 @@ js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc, goto print_int; case JOF_UINT24: - JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY); + JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY || + op == JSOP_DUPAT); i = (int)GET_UINT24(pc); goto print_int; @@ -1436,9 +1445,8 @@ struct ExpressionDecompiler bool init(); bool decompilePCForStackOperand(jsbytecode *pc, int i); bool decompilePC(jsbytecode *pc); - JSAtom *getVar(uint32_t slot); + JSAtom *getFixed(uint32_t slot, jsbytecode *pc); JSAtom *getArg(unsigned slot); - JSAtom *findLetVar(jsbytecode *pc, unsigned depth); JSAtom *loadAtom(jsbytecode *pc); bool quote(JSString *s, uint32_t quote); bool write(const char *s); @@ -1504,18 +1512,9 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc) case JSOP_GETLOCAL: case JSOP_CALLLOCAL: { uint32_t i = GET_LOCALNO(pc); - JSAtom *atom; - if (i >= script->nfixed()) { - i -= script->nfixed(); - JS_ASSERT(i < unsigned(parser.stackDepthAtPC(pc))); - atom = findLetVar(pc, i); - if (!atom) - return decompilePCForStackOperand(pc, i); // Destructing temporary - } else { - atom = getVar(i); - } - JS_ASSERT(atom); - return write(atom); + if (JSAtom *atom = getFixed(i, pc)) + return write(atom); + return write("(intermediate value)"); } case JSOP_CALLALIASEDVAR: case JSOP_GETALIASEDVAR: { @@ -1640,26 +1639,6 @@ ExpressionDecompiler::loadAtom(jsbytecode *pc) return script->getAtom(GET_UINT32_INDEX(pc)); } -JSAtom * -ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth) -{ - for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) { - if (!chain->is()) - continue; - StaticBlockObject &block = chain->as(); - uint32_t blockDepth = block.stackDepth(); - uint32_t blockCount = block.slotCount(); - if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) { - for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { - const Shape &shape = r.front(); - if (shape.shortid() == int(depth - blockDepth)) - return JSID_TO_ATOM(shape.propid()); - } - } - } - return nullptr; -} - JSAtom * ExpressionDecompiler::getArg(unsigned slot) { @@ -1669,12 +1648,31 @@ ExpressionDecompiler::getArg(unsigned slot) } JSAtom * -ExpressionDecompiler::getVar(uint32_t slot) +ExpressionDecompiler::getFixed(uint32_t slot, jsbytecode *pc) { - JS_ASSERT(fun); - slot += fun->nargs(); - JS_ASSERT(slot < script->bindings.count()); - return (*localNames)[slot].name(); + if (slot < script->nfixedvars()) { + JS_ASSERT(fun); + slot += fun->nargs(); + JS_ASSERT(slot < script->bindings.count()); + return (*localNames)[slot].name(); + } + for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) { + if (!chain->is()) + continue; + StaticBlockObject &block = chain->as(); + if (slot < block.localOffset()) + continue; + slot -= block.localOffset(); + if (slot >= block.slotCount()) + return nullptr; + for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { + const Shape &shape = r.front(); + if (shape.shortid() == int(slot)) + return JSID_TO_ATOM(shape.propid()); + } + break; + } + return nullptr; } bool diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 0bd5ac8cfb8..2c51a84b0b3 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -72,18 +72,21 @@ Bindings::argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle binding bool Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, unsigned numArgs, uint32_t numVars, - Binding *bindingArray) + Binding *bindingArray, uint32_t numBlockScoped) { 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(UINT32_MAX - numArgs >= numVars); + JS_ASSERT(numBlockScoped <= LOCALNO_LIMIT); + JS_ASSERT(numVars <= LOCALNO_LIMIT - numBlockScoped); + JS_ASSERT(UINT32_MAX - numArgs >= numVars + numBlockScoped); self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT; self->numArgs_ = numArgs; self->numVars_ = numVars; + 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 @@ -142,7 +145,7 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE | - (bi->kind() == CONSTANT ? JSPROP_READONLY : 0); + (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0); StackShape child(base, NameToId(bi->name()), slot, attrs, 0, 0); shape = cx->compartment()->propertyTree.getChild(cx, shape, child); @@ -186,7 +189,8 @@ Bindings::clone(JSContext *cx, InternalBindingsHandle self, * 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())) + if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray(), + src.numBlockScoped())) return false; self->switchToScriptStorage(dstPackedBindings); return true; @@ -201,7 +205,7 @@ GCMethods::initial() template static bool XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars, - HandleScript script) + HandleScript script, unsigned numBlockScoped) { JSContext *cx = xdr->cx(); @@ -239,14 +243,15 @@ XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, ui return false; PropertyName *name = atoms[i].toString()->asAtom().asPropertyName(); - BindingKind kind = BindingKind(u8 >> 1); + 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)) + if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray, + numBlockScoped)) return false; } @@ -481,16 +486,20 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc /* XDR arguments and vars. */ uint16_t nargs = 0; + uint16_t nblocklocals = 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(); nvars = script->bindings.numVars(); } if (!xdr->codeUint16(&nargs)) return false; + if (!xdr->codeUint16(&nblocklocals)) + return false; if (!xdr->codeUint32(&nvars)) return false; @@ -630,7 +639,7 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc /* JSScript::partiallyInit assumes script->bindings is fully initialized. */ LifoAllocScope las(&cx->tempLifoAlloc()); - if (!XDRScriptBindings(xdr, las, nargs, nvars, script)) + if (!XDRScriptBindings(xdr, las, nargs, nvars, script, nblocklocals)) return false; if (mode == XDR_DECODE) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 2ffa3db20ed..55b0a83eb4f 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -127,19 +127,10 @@ struct BlockScopeArray { uint32_t length; // Count of indexed try notes. }; -/* - * A "binding" is a formal, 'var' or 'const' declaration. A function's lexical - * scope is composed of these three kinds of bindings. - */ - -enum BindingKind { ARGUMENT, VARIABLE, CONSTANT }; - class Binding { - /* - * One JSScript stores one Binding per formal/variable so we use a - * packed-word representation. - */ + // One JSScript stores one Binding per formal/variable so we use a + // packed-word representation. uintptr_t bits_; static const uintptr_t KIND_MASK = 0x3; @@ -147,9 +138,13 @@ class Binding static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT); public: + // A "binding" is a formal, 'var', 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, BindingKind kind, bool aliased) { + Binding(PropertyName *name, Kind kind, bool aliased) { JS_STATIC_ASSERT(CONSTANT <= KIND_MASK); JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0); JS_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0); @@ -160,8 +155,8 @@ class Binding return (PropertyName *)(bits_ & NAME_MASK); } - BindingKind kind() const { - return BindingKind(bits_ & KIND_MASK); + Kind kind() const { + return Kind(bits_ & KIND_MASK); } bool aliased() const { @@ -188,6 +183,7 @@ class Bindings HeapPtr callObjShape_; uintptr_t bindingArrayAndFlag_; uint16_t numArgs_; + uint16_t numBlockScoped_; uint32_t numVars_; /* @@ -220,7 +216,21 @@ class Bindings */ static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, unsigned numArgs, uint32_t numVars, - Binding *bindingArray); + Binding *bindingArray, unsigned numBlockScoped); + + // 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) { + JS_ASSERT(!callObjShape_); + JS_ASSERT(numVars_ == 0); + JS_ASSERT(numBlockScoped < LOCALNO_LIMIT); + JS_ASSERT(numBlockScoped >= numBlockScoped_); + numBlockScoped_ = numBlockScoped; + } uint8_t *switchToScriptStorage(Binding *newStorage); @@ -233,6 +243,10 @@ class Bindings unsigned numArgs() const { return numArgs_; } uint32_t numVars() const { return numVars_; } + unsigned numBlockScoped() const { return numBlockScoped_; } + uint32_t numLocals() const { return numVars() + numBlockScoped(); } + + // Return the size of the bindingArray. uint32_t count() const { return numArgs() + numVars(); } /* Return the initial shape of call objects created for this scope. */ @@ -924,7 +938,15 @@ class JSScript : public js::gc::BarrieredCell 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 { + js::AutoThreadSafeAccess ts(this); + return function_ ? bindings.numLocals() : bindings.numBlockScoped(); + } + + // Number of fixed slots reserved for vars. Only nonzero for function code. + size_t nfixedvars() const { js::AutoThreadSafeAccess ts(this); return function_ ? bindings.numVars() : 0; } @@ -1574,7 +1596,7 @@ namespace js { * Iterator over a script's bindings (formals and variables). * The order of iteration is: * - first, formal arguments, from index 0 to numArgs - * - next, variables, from index 0 to numVars + * - next, variables, from index 0 to numLocals */ class BindingIter { @@ -1614,7 +1636,7 @@ FillBindingVector(HandleScript fromScript, BindingVector *vec); /* * Iterator over the aliased formal bindings in ascending index order. This can * be veiwed as a filtering of BindingIter with predicate - * bi->aliased() && bi->kind() == ARGUMENT + * bi->aliased() && bi->kind() == Binding::ARGUMENT */ class AliasedFormalIter { diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index 4f3e275d842..fecbfd99d61 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -21,7 +21,8 @@ namespace js { inline Bindings::Bindings() - : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), numArgs_(0), numVars_(0) + : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), + numArgs_(0), numBlockScoped_(0), numVars_(0) {} inline diff --git a/js/src/tests/js1_8_1/regress/regress-420399.js b/js/src/tests/js1_8_1/regress/regress-420399.js index 423bfe13c2a..95aa0487290 100644 --- a/js/src/tests/js1_8_1/regress/regress-420399.js +++ b/js/src/tests/js1_8_1/regress/regress-420399.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = "TypeError: undefined has no properties"; + expect = "TypeError: a is undefined"; try { (let (a=undefined) a).b = 3; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index abb323e54fb..fdfb5f2e8c1 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1744,14 +1744,14 @@ CASE(JSOP_POPN) REGS.sp -= GET_UINT16(REGS.pc); END_CASE(JSOP_POPN) -CASE(JSOP_POPNV) +CASE(JSOP_DUPAT) { - JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth()); - Value val = REGS.sp[-1]; - REGS.sp -= GET_UINT16(REGS.pc); - REGS.sp[-1] = val; + JS_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth()); + unsigned i = GET_UINT24(REGS.pc); + const Value &rref = REGS.sp[-int(i + 1)]; + PUSH_COPY(rref); } -END_CASE(JSOP_POPNV) +END_CASE(JSOP_DUPAT) CASE(JSOP_SETRVAL) POP_RETURN_VALUE(); @@ -3352,9 +3352,6 @@ CASE(JSOP_PUSHBLOCKSCOPE) StaticBlockObject &blockObj = script->getObject(REGS.pc)->as(); JS_ASSERT(blockObj.needsClone()); - // FIXME: "Aliased" slots don't need to be on the stack. - JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); - // Clone block and push on scope chain. if (!REGS.fp()->pushBlock(cx, blockObj)) goto error; @@ -3370,9 +3367,6 @@ CASE(JSOP_POPBLOCKSCOPE) JS_ASSERT(scope && scope->is()); StaticBlockObject &blockObj = scope->as(); JS_ASSERT(blockObj.needsClone()); - - // FIXME: "Aliased" slots don't need to be on the stack. - JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); #endif // Pop block from scope chain. diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 45b93cd9fd6..10d635699f4 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -100,8 +100,8 @@ /* spreadcall variant of JSOP_EVAL */ \ macro(JSOP_SPREADEVAL,43, "spreadeval", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ \ - /* Pop N values, preserving top value. */ \ - macro(JSOP_POPNV, 44, "popnv", NULL, 3, -1, 1, JOF_UINT16) \ + /* Dup the Nth value from the top. */ \ + macro(JSOP_DUPAT, 44, "dupat", NULL, 4, 0, 1, JOF_UINT24) \ \ macro(JSOP_UNUSED45, 45, "unused45", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED46, 46, "unused46", NULL, 1, 0, 0, JOF_BYTE) \ diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 16afe8b4769..eab64d801bf 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -667,17 +667,15 @@ ClonedBlockObject::create(JSContext *cx, Handle block, Abst JS_ASSERT(obj->slotSpan() >= block->slotCount() + RESERVED_SLOTS); obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain())); - obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(block->stackDepth())); /* * Copy in the closed-over locals. Closed-over locals don't need * any fixup since the initial value is 'undefined'. */ unsigned nslots = block->slotCount(); - unsigned base = frame.script()->nfixed() + block->stackDepth(); for (unsigned i = 0; i < nslots; ++i) { if (block->isAliased(i)) - obj->as().setVar(i, frame.unaliasedLocal(base + i)); + obj->as().setVar(i, frame.unaliasedLocal(block->varToLocalIndex(i))); } JS_ASSERT(obj->isDelegate()); @@ -689,10 +687,9 @@ void ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) { StaticBlockObject &block = staticBlock(); - unsigned base = frame.script()->nfixed() + block.stackDepth(); for (unsigned i = 0; i < slotCount(); ++i) { if (!block.isAliased(i)) - setVar(i, frame.unaliasedLocal(base + i), DONT_CHECK_ALIASING); + setVar(i, frame.unaliasedLocal(block.varToLocalIndex(i)), DONT_CHECK_ALIASING); } } @@ -721,7 +718,7 @@ StaticBlockObject::addVar(ExclusiveContext *cx, Handle block unsigned index, bool *redeclared) { JS_ASSERT(JSID_IS_ATOM(id) || (JSID_IS_INT(id) && JSID_TO_INT(id) == (int)index)); - JS_ASSERT(index < VAR_INDEX_LIMIT); + JS_ASSERT(index < LOCAL_INDEX_LIMIT); *redeclared = false; @@ -769,16 +766,12 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, JSContext *cx = xdr->cx(); Rooted obj(cx); - uint32_t count = 0; - uint32_t depthAndCount = 0; + uint32_t count = 0, offset = 0; if (mode == XDR_ENCODE) { obj = *objp; - uint32_t depth = obj->stackDepth(); - JS_ASSERT(depth <= UINT16_MAX); count = obj->slotCount(); - JS_ASSERT(count <= UINT16_MAX); - depthAndCount = (depth << 16) | uint16_t(count); + offset = obj->localOffset(); } if (mode == XDR_DECODE) { @@ -789,13 +782,13 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, *objp = obj; } - if (!xdr->codeUint32(&depthAndCount)) + if (!xdr->codeUint32(&count)) + return false; + if (!xdr->codeUint32(&offset)) return false; if (mode == XDR_DECODE) { - uint32_t depth = uint16_t(depthAndCount >> 16); - count = uint16_t(depthAndCount); - obj->setStackDepth(depth); + obj->setLocalOffset(offset); /* * XDR the block object's properties. We know that there are 'count' @@ -880,7 +873,7 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, HandleinitEnclosingNestedScope(enclosingScope); - clone->setStackDepth(srcBlock->stackDepth()); + clone->setLocalOffset(srcBlock->localOffset()); /* Shape::Range is reverse order, so build a list in forward order. */ AutoShapeVector shapes(cx); @@ -1194,7 +1187,7 @@ class DebugScopeProxy : public BaseProxyHandler if (!bi) return false; - if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) { + if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) { uint32_t i = bi.frameIndex(); if (script->varIsAliased(i)) return false; @@ -1216,7 +1209,7 @@ class DebugScopeProxy : public BaseProxyHandler vp.set(UndefinedValue()); } } else { - JS_ASSERT(bi->kind() == ARGUMENT); + JS_ASSERT(bi->kind() == Binding::ARGUMENT); unsigned i = bi.frameIndex(); if (script->formalIsAliased(i)) return false; @@ -1266,12 +1259,12 @@ class DebugScopeProxy : public BaseProxyHandler if (maybeLiveScope) { AbstractFramePtr frame = maybeLiveScope->frame(); JSScript *script = frame.script(); - uint32_t local = block->slotToLocalIndex(script->bindings, shape->slot()); + uint32_t local = block->staticBlock().varToLocalIndex(i); if (action == GET) vp.set(frame.unaliasedLocal(local)); else frame.unaliasedLocal(local) = vp; - JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script)); + JS_ASSERT(analyze::LocalSlot(script, local) < analyze::TotalSlots(script)); } else { if (action == GET) vp.set(block->var(i, DONT_CHECK_ALIASING)); diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index f5829292d90..c9fdbed265d 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -413,20 +413,6 @@ class BlockObject : public NestedScopeObject return propertyCountForCompilation(); } - /* - * Return the local corresponding to the ith binding where i is in the - * range [0, slotCount()) and the return local index is in the range - * [script->nfixed, script->nfixed + script->nslots). - */ - uint32_t slotToLocalIndex(const Bindings &bindings, uint32_t slot) { - JS_ASSERT(slot < RESERVED_SLOTS + slotCount()); - return bindings.numVars() + stackDepth() + (slot - RESERVED_SLOTS); - } - - uint32_t localIndexToSlot(const Bindings &bindings, uint32_t i) { - return RESERVED_SLOTS + (i - (bindings.numVars() + stackDepth())); - } - protected: /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */ const Value &slotValue(unsigned i) { @@ -440,15 +426,42 @@ class BlockObject : public NestedScopeObject class StaticBlockObject : public BlockObject { + static const unsigned LOCAL_OFFSET_SLOT = 1; + public: static StaticBlockObject *create(ExclusiveContext *cx); + /* See StaticScopeIter comment. */ + JSObject *enclosingStaticScope() const { + AutoThreadSafeAccess ts(this); + return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); + } + /* - * Return whether this StaticBlockObject contains a variable stored at - * the given stack depth (i.e., fp->base()[depth]). + * A refinement of enclosingStaticScope that returns nullptr if the enclosing + * static scope is a JSFunction. */ - bool containsVarAtDepth(uint32_t depth) { - return depth >= stackDepth() && depth < stackDepth() + slotCount(); + inline StaticBlockObject *enclosingBlock() const; + + uint32_t localOffset() { + return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32(); + } + + // Return the local corresponding to the 'var'th binding where 'var' is in the + // range [0, slotCount()). + uint32_t varToLocalIndex(uint32_t var) { + JS_ASSERT(var < slotCount()); + return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + var; + } + + // Return the slot corresponding to local variable 'local', where 'local' is + // in the range [localOffset(), localOffset() + slotCount()). The result is + // in the range [RESERVED_SLOTS, RESERVED_SLOTS + slotCount()). + uint32_t localIndexToSlot(uint32_t local) { + JS_ASSERT(local >= localOffset()); + local -= localOffset(); + JS_ASSERT(local < slotCount()); + return RESERVED_SLOTS + local; } /* @@ -482,9 +495,9 @@ class StaticBlockObject : public BlockObject } } - void setStackDepth(uint32_t depth) { - JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined()); - initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth)); + void setLocalOffset(uint32_t offset) { + JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined()); + initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset)); } /* @@ -508,7 +521,7 @@ class StaticBlockObject : public BlockObject * associated Shape. If we could remove the block dependencies on shape->shortid, we could * remove INDEX_LIMIT. */ - static const unsigned VAR_INDEX_LIMIT = JS_BIT(16); + static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16); static Shape *addVar(ExclusiveContext *cx, Handle block, HandleId id, unsigned index, bool *redeclared); diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index f29adcb496f..5b0f6c0ea8e 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -100,13 +100,14 @@ inline Value & StackFrame::unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing) { JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i)); - JS_ASSERT(i < script()->nfixed()); + JS_ASSERT(i < script()->nfixedvars()); return slots()[i]; } inline Value & StackFrame::unaliasedLocal(uint32_t i, MaybeCheckAliasing checkAliasing) { + JS_ASSERT(i < script()->nfixed()); #ifdef DEBUG CheckLocalUnaliased(checkAliasing, script(), i); #endif diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 7270db93c85..55fa790d826 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1268,8 +1268,8 @@ js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint if (!checkAliasing) return; - JS_ASSERT(i < script->nslots()); - if (i < script->nfixed()) { + JS_ASSERT(i < script->nfixed()); + if (i < script->bindings.numVars()) { JS_ASSERT(!script->varIsAliased(i)); } else { // FIXME: The callers of this function do not easily have the PC of the From 5a2ad7aa757804295b36055cfdab88e3a4b0ebc4 Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Wed, 12 Feb 2014 19:27:27 +0100 Subject: [PATCH 02/40] Bug 832437 - Ensure SPS frame has a valid pc when calling into the VM. r=djvj --- js/src/jit/BaselineBailouts.cpp | 10 ++++++++++ js/src/jit/IonCaches.cpp | 18 +++++++++--------- js/src/jit/IonMacroAssembler.h | 18 ++++++++++++++++-- js/src/vm/SPSProfiler.cpp | 13 +++++++++++-- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 7a4cc4cace1..0f9774794a4 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -975,6 +975,16 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, IonSpew(IonSpew_BaselineBailouts, " Set resumeAddr=%p", opReturnAddr); } + if (cx->runtime()->spsProfiler.enabled() && blFrame->hasPushedSPSFrame()) { + // Set PC index to 0 for the innermost frame to match what the + // interpreter and Baseline do: they update the SPS pc for + // JSOP_CALL ops but set it to 0 when running other ops. Ion code + // can set the pc to NullPCIndex and this will confuse SPS when + // Baseline calls into the VM at non-CALL ops and re-enters JS. + IonSpew(IonSpew_BaselineBailouts, " Setting PCidx for last frame to 0"); + cx->runtime()->spsProfiler.updatePC(script, script->code()); + } + return true; } diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 9476c84b426..4e7590e411e 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -1213,7 +1213,7 @@ GetPropertyIC::tryAttachNative(JSContext *cx, IonScript *ion, HandleObject obj, *emitted = true; - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); SkipRoot skip(cx, &masm); RepatchStubAppender attacher(*this); @@ -1370,7 +1370,7 @@ GetPropertyIC::tryAttachDOMProxyShadowed(JSContext *cx, IonScript *ion, *emitted = true; Label failures; - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); // Guard on the shape of the object. @@ -1437,7 +1437,7 @@ GetPropertyIC::tryAttachDOMProxyUnshadowed(JSContext *cx, IonScript *ion, Handle } Label failures; - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); // Guard on the shape of the object. @@ -1552,7 +1552,7 @@ GetPropertyIC::tryAttachGenericProxy(JSContext *cx, IonScript *ion, HandleObject *emitted = true; Label failures; - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); Register scratchReg = output().valueReg().scratchReg(); @@ -2120,7 +2120,7 @@ SetPropertyIC::attachGenericProxy(JSContext *cx, IonScript *ion, void *returnAdd { JS_ASSERT(!hasGenericProxyStub()); - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); Label failures; @@ -2177,7 +2177,7 @@ SetPropertyIC::attachDOMProxyShadowed(JSContext *cx, IonScript *ion, HandleObjec JS_ASSERT(IsCacheableDOMProxy(obj)); Label failures; - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); // Guard on the shape of the object. @@ -2407,7 +2407,7 @@ SetPropertyIC::attachDOMProxyUnshadowed(JSContext *cx, IonScript *ion, HandleObj JS_ASSERT(IsCacheableDOMProxy(obj)); Label failures; - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); // Guard on the shape of the object. @@ -2462,7 +2462,7 @@ SetPropertyIC::attachCallSetter(JSContext *cx, IonScript *ion, { JS_ASSERT(obj->isNative()); - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); Label failure; @@ -4243,7 +4243,7 @@ bool NameIC::attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, HandleShape shape, void *returnAddr) { - MacroAssembler masm(cx, ion); + MacroAssembler masm(cx, ion, script_, pc_); RepatchStubAppender attacher(*this); if (!GenerateCallGetter(cx, ion, masm, attacher, obj, name(), holder, shape, liveRegs_, diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index 9d230292c25..bbde4788ac4 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -172,6 +172,10 @@ class MacroAssembler : public MacroAssemblerSpecific bool enoughMemory_; bool embedsNurseryPointers_; + // SPS instrumentation, only used for Ion caches. + mozilla::Maybe spsInstrumentation_; + jsbytecode *spsPc_; + private: // This field is used to manage profiling instrumentation output. If // provided and enabled, then instrumentation will be emitted around call @@ -212,7 +216,8 @@ class MacroAssembler : public MacroAssemblerSpecific // This constructor should only be used when there is no IonContext active // (for example, Trampoline-$(ARCH).cpp and IonCaches.cpp). - MacroAssembler(JSContext *cx, IonScript *ion = nullptr) + MacroAssembler(JSContext *cx, IonScript *ion = nullptr, + JSScript *script = nullptr, jsbytecode *pc = nullptr) : enoughMemory_(true), embedsNurseryPointers_(false), sps_(nullptr) @@ -225,8 +230,17 @@ class MacroAssembler : public MacroAssemblerSpecific initWithAllocator(); m_buffer.id = GetIonContext()->getNextAssemblerId(); #endif - if (ion) + if (ion) { setFramePushed(ion->frameSize()); + if (pc && cx->runtime()->spsProfiler.enabled()) { + // We have to update the SPS pc when this IC stub calls into + // the VM. + spsPc_ = pc; + spsInstrumentation_.construct(&cx->runtime()->spsProfiler, &spsPc_); + sps_ = spsInstrumentation_.addr(); + sps_->setPushed(script); + } + } } // asm.js compilation handles its own IonContet-pushing diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp index 762ade86ed9..a9fa6461083 100644 --- a/js/src/vm/SPSProfiler.cpp +++ b/js/src/vm/SPSProfiler.cpp @@ -130,8 +130,17 @@ SPSProfiler::enter(JSScript *script, JSFunction *maybeFun) if (str == nullptr) return false; - JS_ASSERT_IF(*size_ > 0 && *size_ - 1 < max_ && stack_[*size_ - 1].js(), - stack_[*size_ - 1].pc() != nullptr); +#ifdef DEBUG + // In debug builds, assert the JS pseudo frames already on the stack + // have a non-null pc. Only look at the top frames to avoid quadratic + // behavior. + if (*size_ > 0 && *size_ - 1 < max_) { + size_t start = (*size_ > 4) ? *size_ - 4 : 0; + for (size_t i = start; i < *size_ - 1; i++) + MOZ_ASSERT_IF(stack_[i].js(), stack_[i].pc() != nullptr); + } +#endif + push(str, nullptr, script, script->code()); return true; } From 7be525bbcba0c6014b0b598ddeb4be708a39ef57 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Wed, 12 Feb 2014 19:31:34 +0100 Subject: [PATCH 03/40] Bug 971834: Exclude dom/plugins from b2g desktop mochitests. r=ahal --- testing/mochitest/b2g-desktop.json | 64 +----------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/testing/mochitest/b2g-desktop.json b/testing/mochitest/b2g-desktop.json index 3b4530acf0d..f218c830776 100644 --- a/testing/mochitest/b2g-desktop.json +++ b/testing/mochitest/b2g-desktop.json @@ -12,6 +12,7 @@ "b2g/chrome/content/test/mochitest": "require OOP support for mochitest-b2g-desktop, Bug 957554", "content/xul":"tests that use xul", "layout/xul" : "", + "dom/plugins": "tests that use plugins", "dom/tests/mochitest/general/test_focusrings.xul":"", "layout/base/tests/test_bug465448.xul":"", @@ -639,69 +640,6 @@ "dom/indexedDB/test/test_webapp_clearBrowserData_oop_inproc.html": "Bug 931116, b2g desktop specific, initial triage", "dom/permission/tests/test_embed-apps.html": "Bug 931116, b2g desktop specific, initial triage", "dom/permission/tests/test_wifi-manage.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_GCrace.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_NPNVdocumentOrigin.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_NPPVpluginWantsAllNetworkStreams.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug532208.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug539565-1.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug539565-2.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug771202.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug777098.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug784131.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug813906.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug854082.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug863792.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_bug967694.html": "Bug 967694, b2g desktop doesn't allow plugins", - "dom/plugins/test/mochitest/test_cookies.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_copyText.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_crash_nested_loop.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_crashing.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_defaultValue.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_enumerate.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_fullpage.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_getauthenticationinfo.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_hanging.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_instance_re-parent.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_instance_unparent1.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_instance_unparent2.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_instance_unparent3.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_instantiation.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_mixed_case_mime.html": "Bug 931116, b2g desktop specific", - "dom/plugins/test/mochitest/test_multipleinstanceobjects.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_newstreamondestroy.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npn_asynccall.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npn_timers.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npobject_getters.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npruntime_construct.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npruntime_identifiers.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npruntime_npnevaluate.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npruntime_npninvoke.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npruntime_npninvokedefault.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_npruntime_npnsetexception.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_painting.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_asfile.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_asfileonly.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_err.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_geturl.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_newstream.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_post.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_poststream.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_referer.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_seek.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_seek_close.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_src.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_src_dynamic.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_pluginstream_src_referer.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_propertyAndMethod.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_redirect_handling.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_secondPlugin.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_src_url_change.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_streamNotify.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_streamatclose.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_twostreams.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_visibility.html": "Bug 931116, b2g desktop specific, initial triage", - "dom/plugins/test/mochitest/test_zero_opacity.html": "Bug 931116, b2g desktop specific, initial triage", "dom/tests/mochitest/bugs/test_bug346659.html": "Bug 931116, b2g desktop specific, initial triage", "dom/tests/mochitest/bugs/test_bug38959.html": "Bug 931116, b2g desktop specific, initial triage", "dom/tests/mochitest/bugs/test_bug458091.html": "Bug 931116, b2g desktop specific, initial triage", From 9b84bf5b0d83fbc271df05e90ee682b2ab046d68 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Wed, 12 Feb 2014 13:49:49 -0500 Subject: [PATCH 04/40] Backed out changeset d59a1cce18e5 (bug 962599) for crashtest crashes. --- js/src/frontend/BytecodeCompiler.cpp | 27 +- js/src/frontend/BytecodeEmitter.cpp | 372 ++++++++++-------- js/src/frontend/FullParseHandler.h | 21 +- js/src/frontend/ParseNode.h | 3 +- js/src/frontend/Parser.cpp | 104 +---- js/src/frontend/Parser.h | 15 +- js/src/frontend/SyntaxParseHandler.h | 5 +- js/src/jit-test/tests/basic/testBug579647.js | 4 +- js/src/jit/AsmJS.cpp | 3 +- js/src/jit/BaselineCompiler.cpp | 24 +- js/src/jit/BaselineCompiler.h | 2 +- js/src/jit/BaselineFrame.h | 2 +- js/src/jit/BaselineFrameInfo.h | 11 +- js/src/jit/CompileInfo.h | 61 +-- js/src/jit/IonBuilder.cpp | 30 +- js/src/jsanalyze.cpp | 38 +- js/src/jsopcode.cpp | 86 ++-- js/src/jsscript.cpp | 25 +- js/src/jsscript.h | 56 +-- js/src/jsscriptinlines.h | 3 +- .../tests/js1_8_1/regress/regress-420399.js | 2 +- js/src/vm/Interpreter.cpp | 18 +- js/src/vm/Opcodes.h | 4 +- js/src/vm/ScopeObject.cpp | 35 +- js/src/vm/ScopeObject.h | 57 ++- js/src/vm/Stack-inl.h | 3 +- js/src/vm/Stack.cpp | 4 +- 27 files changed, 463 insertions(+), 552 deletions(-) diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 57b9483280c..d5a6f96e846 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -268,6 +268,12 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco if (!script) return nullptr; + // Global/eval script bindings are always empty (all names are added to the + // scope dynamically via JSOP_DEFFUN/VAR). + InternalHandle bindings(script, &script->bindings); + if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr)) + return nullptr; + // 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; @@ -287,8 +293,7 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco Maybe > pc; pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc, - (Directives *) nullptr, staticLevel, /* bodyid = */ 0, - /* blockScopeDepth = */ 0); + (Directives *) nullptr, staticLevel, /* bodyid = */ 0); if (!pc.ref().init(parser.tokenStream)) return nullptr; @@ -355,12 +360,10 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco pc.destroy(); pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, - &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0, - script->bindings.numBlockScoped()); + &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0); if (!pc.ref().init(parser.tokenStream)) return nullptr; JS_ASSERT(parser.pc == pc.addr()); - pn = parser.statement(); } if (!pn) { @@ -369,11 +372,6 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco } } - // Accumulate the maximum block scope depth, so that EmitTree can assert - // when emitting JSOP_GETLOCAL that the local is indeed within the fixed - // part of the stack frame. - script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth); - if (canHaveDirectives) { if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives)) return nullptr; @@ -416,15 +414,6 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco 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(script, &script->bindings); - if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr, - pc.ref().blockScopeDepth)) - return nullptr; - if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return nullptr; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index bec627765b7..350f0c50476 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -257,34 +257,6 @@ EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc) return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc)); } -// Dup the var in operand stack slot "slot". The first item on the operand -// stack is one slot past the last fixed slot. The last (most recent) item is -// slot bce->stackDepth - 1. -// -// The instruction that is written (JSOP_DUPAT) switches the depth around so -// that it is addressed from the sp instead of from the fp. This is useful when -// you don't know the size of the fixed stack segment (nfixed), as is the case -// when compiling scripts (because each statement is parsed and compiled -// separately, but they all together form one script with one fixed stack -// frame). -static bool -EmitDupAt(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned slot) -{ - JS_ASSERT(slot < unsigned(bce->stackDepth)); - // The slot's position on the operand stack, measured from the top. - unsigned slotFromTop = bce->stackDepth - 1 - slot; - if (slotFromTop >= JS_BIT(24)) { - bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); - return false; - } - ptrdiff_t off = EmitN(cx, bce, JSOP_DUPAT, 3); - if (off < 0) - return false; - jsbytecode *pc = bce->code(off); - SET_UINT24(pc, slotFromTop); - return true; -} - /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ const char js_with_statement_str[] = "with statement"; const char js_finally_block_str[] = "finally block"; @@ -611,6 +583,7 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) return false; } + npops += blockObj.slotCount(); } } @@ -685,6 +658,25 @@ EnclosingStaticScope(BytecodeEmitter *bce) return bce->sc->asFunctionBox()->function(); } +// In a stack frame, block-scoped locals follow hoisted var-bound locals. If +// the current compilation unit is a function, add the number of "fixed slots" +// (var-bound locals) to the given block-scoped index, to arrive at its final +// position in the call frame. +// +static bool +AdjustBlockSlot(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t *slot) +{ + JS_ASSERT(*slot < bce->maxStackDepth); + if (bce->sc->isFunctionBox()) { + *slot += bce->script->bindings.numVars(); + if (*slot >= StaticBlockObject::VAR_INDEX_LIMIT) { + bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + } + return true; +} + #ifdef DEBUG static bool AllLocalsAliased(StaticBlockObject &obj) @@ -699,6 +691,10 @@ AllLocalsAliased(StaticBlockObject &obj) static bool ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) { + uint32_t depthPlusFixed = blockObj->stackDepth(); + if (!AdjustBlockSlot(cx, bce, &depthPlusFixed)) + return false; + for (unsigned i = 0; i < blockObj->slotCount(); i++) { Definition *dn = blockObj->maybeDefinitionParseNode(i); @@ -710,7 +706,7 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, HandleisDefn()); if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(), - blockObj->varToLocalIndex(dn->frameSlot()))) + dn->frameSlot() + depthPlusFixed)) { return false; } @@ -734,79 +730,6 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) -{ - unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0; - unsigned localOffset = nfixedvars; - - if (bce->staticScope) { - Rooted outer(cx, bce->staticScope); - for (; outer; outer = outer->enclosingNestedScope()) { - if (outer->is()) { - StaticBlockObject &outerBlock = outer->as(); - localOffset = outerBlock.localOffset() + outerBlock.slotCount(); - break; - } - } - } - - JS_ASSERT(localOffset + blockObj->slotCount() - <= nfixedvars + 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 -// "with" object or a "block" object. "With" objects represent "with" scopes. -// Block objects represent lexical scopes, and contain named block-scoped -// bindings, for example "let" bindings or the exception in a catch block. -// Those variables may be local and thus accessible directly from the stack, or -// "aliased" (accessed by name from nested functions, or dynamically via nested -// "eval" or "with") and only accessible through the scope chain. -// -// All nested scopes are present on the "static scope chain". A nested scope -// that is a "with" scope will be present on the scope chain at run-time as -// well. A block scope may or may not have a corresponding link on the run-time -// scope chain; if no variable declared in the block scope is "aliased", then no -// scope chain node is allocated. -// -// To help debuggers, the bytecode emitter arranges to record the PC ranges -// comprehended by a nested scope, and ultimately attach them to the JSScript. -// An element in the "block scope array" specifies the PC range, and links to a -// NestedScopeObject in the object list of the script. That scope object is -// linked to the previous link in the static scope chain, if any. The static -// scope chain at any pre-retire PC can be retrieved using -// JSScript::getStaticScope(jsbytecode *pc). -// -// Block scopes store their locals in the fixed part of a stack frame, after the -// "fixed var" bindings. A fixed var binding is a "var" or legacy "const" -// binding that occurs in a function (as opposed to a script or in eval code). -// Only functions have fixed var bindings. -// -// To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a -// block scope, even if the block has no aliased locals. This allows -// DebugScopes to invalidate any association between a debugger scope object, -// which can proxy access to unaliased stack locals, and the actual live frame. -// In normal, non-debug mode, this opcode does not cause any baseline code to be -// emitted. -// -// Enter a nested scope with EnterNestedScope. It will emit -// PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of -// the scope. Leave a nested scope with LeaveNestedScope, which, for blocks, -// will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. (For "with" scopes it -// emits LEAVEWITH, of course.) Pass EnterNestedScope a fresh StmtInfoBCE -// object, and pass that same object to the corresponding LeaveNestedScope. If -// the statement is a block scope, pass STMT_BLOCK as stmtType; otherwise for -// with scopes pass STMT_WITH. -// static bool EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, StmtType stmtType) @@ -818,8 +741,6 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, case STMT_BLOCK: { Rooted blockObj(cx, &scopeObj->as()); - ComputeLocalOffset(cx, bce, blockObj); - if (!ComputeAliasedSlots(cx, bce, blockObj)) return false; @@ -839,7 +760,8 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, } uint32_t parent = BlockScopeNote::NoBlockScopeIndex; - if (StmtInfoBCE *stmt = bce->topScopeStmt) { + if (bce->staticScope) { + StmtInfoBCE *stmt = bce->topScopeStmt; for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {} parent = stmt->blockScopeIndex; } @@ -857,6 +779,71 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, return true; } +// ~ Block Scopes ~ +// +// A block scope is a region of a script with an additional set of named +// variables. Those variables may be local and thus accessible directly from +// the stack, or "aliased" and only accessible through the scope chain. +// +// A block scope may or may not have a corresponding link on the scope chain. +// If no variable declared in the scope is "aliased", then no scope chain node +// is allocated. +// +// To help debuggers, the bytecode emitter arranges to record the PC ranges +// comprehended by a block scope, and ultimately attach them to the JSScript. +// An element in the "block scope array" specifies the PC range, and links to a +// StaticBlockObject in the object list of the script. That block is linked to +// the previous block in the scope, if any. The static block chain at any +// pre-retire PC can be retrieved using JSScript::getStaticScope(jsbytecode *pc). +// +// When PUSHBLOCKSCOPE is executed, it assumes that the block's locals are +// already on the stack. Initial values of "aliased" locals are copied from the +// stack to the ClonedBlockObject, and no further access is made to the stack +// slot. +// +// Likewise after leaving a POPBLOCKSCOPE, we will need to emit code to pop the +// stack values. +// +// Finally, to assist the debugger, we also emit a DEBUGLEAVEBLOCK opcode before +// POPBLOCKSCOPE in all cases -- even if the block has no aliased locals. This +// allows DebugScopes to invalidate any association between a debugger scope +// object, which can proxy access to unaliased stack locals, and the actual live +// frame. In normal, non-debug mode, this opcode does not cause any baseline +// code to be emitted. +// +// In this function "extraSlots" indicates the number of slots that are +// "floating" on the stack above the scope's slots. This will only be nonzero +// in the case of for-let-in and for-let-of loops, where loop iterator state +// floats above the block scopes. It would be nice to fix this eventually so +// that loop iterator state gets assigned to block-scoped fp-addressable +// temporaries, instead of being addressable only via the sp. This would also +// make generators more efficient, as the loop state could be heap-allocated, so +// that the value stack would likely be empty at yield points inside for-of / +// for-in loops. +// +// Summary: Enter block scopes with EnterBlockScope. It will emit +// PUSHBLOCKSCOPE if needed. Leave them with LeaveNestedScope, which will emit +// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. Pass EnterBlockScope a fresh +// StmtInfoBCE object, and pass that same object to the corresponding +// LeaveNestedScope. Push locals before entering a scope, and pop them +// afterwards. Brush your teeth, and clean behind your ears! +// +static bool +EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, + unsigned extraSlots) +{ + Rooted blockObj(cx, &objbox->object->as()); + + // FIXME: Once bug 962599 lands, we won't care about the stack depth, so we + // won't have extraSlots and thus invocations of EnterBlockScope can become + // invocations of EnterNestedScope. + int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots); + JS_ASSERT(depth >= 0); + blockObj->setStackDepth(depth); + + return EnterNestedScope(cx, bce, stmt, objbox, STMT_BLOCK); +} + // Patches |breaks| and |continues| unless the top statement info record // represents a try-catch-finally suite. May fail if a jump offset overflows. static bool @@ -1153,17 +1140,17 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter * return false; JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc)); } else { - JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals()); + uint32_t depth = local - bceOfDef->script->bindings.numVars(); JS_ASSERT(bceOfDef->staticScope->is()); Rooted b(cx, &bceOfDef->staticScope->as()); - while (local < b->localOffset()) { + while (!b->containsVarAtDepth(depth)) { if (b->needsClone()) skippedScopes++; b = &b->enclosingNestedScope()->as(); } if (!AssignHops(bce, pn, skippedScopes, &sc)) return false; - sc.setSlot(b->localIndexToSlot(local)); + sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local)); } } @@ -2428,55 +2415,6 @@ SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off) SET_JUMP_OFFSET(bce->code(off), bce->offset() - off); } -static bool -PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n) -{ - for (unsigned i = 0; i < n; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - return true; -} - -static bool -InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce, - Handle blockObj) -{ - for (unsigned i = blockObj->slotCount(); 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)) - return false; - } else { - if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, blockObj->varToLocalIndex(i - 1), 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. - Rooted blockObj(cx, &objbox->object->as()); - if (!PushUndefinedValues(cx, bce, blockObj->slotCount() - alreadyPushed)) - return false; - - if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK)) - return false; - - if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj)) - return false; - - return true; -} - /* * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. * LLVM is deciding to inline this function which uses a lot of stack space @@ -2502,15 +2440,27 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) pn2 = pn->pn_right; JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST)); + /* + * If there are hoisted let declarations, their stack slots go under the + * discriminant's value so push their slots now and enter the block later. + */ + Rooted blockObj(cx, nullptr); + if (pn2->isKind(PNK_LEXICALSCOPE)) { + blockObj = &pn2->pn_objbox->object->as(); + for (uint32_t i = 0; i < blockObj->slotCount(); ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + } + /* 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, 1)) return false; - stmtInfo.type = STMT_SWITCH; stmtInfo.update = top = bce->offset(); /* Advance pn2 to refer to the switch case list. */ @@ -2796,6 +2746,7 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount()); } else { if (!PopStatementBCE(cx, bce)) return false; @@ -3326,8 +3277,11 @@ EmitGroupAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, for (pn = lhs->pn_head; pn; pn = pn->pn_next, ++i) { /* MaybeEmitGroupAssignment requires lhs->pn_count <= rhs->pn_count. */ JS_ASSERT(i < limit); + uint32_t slot = i; + if (!AdjustBlockSlot(cx, bce, &slot)) + return false; - if (!EmitDupAt(cx, bce, i)) + if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce)) return false; if (pn->isKind(PNK_ELISION)) { @@ -4082,6 +4036,7 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (ParseNode *pn2 = pn->pn_kid2) { // The emitted code for a catch block looks like: // + // undefined... as many as there are locals in the catch block // [pushblockscope] only if any local aliased // exception // if there is a catchguard: @@ -4092,12 +4047,14 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) // ifne POST // debugleaveblock // [popblockscope] only if any local aliased + // popnv leave exception on top // throwing pop exception to cx->exception // goto // POST: pop // < catch block contents > // debugleaveblock // [popblockscope] only if any local aliased + // popn // goto non-local; finally applies // // If there's no catch block without a catchguard, the last scopeChain and does not * otherwise touch the stack, evaluation of the let-var initializers must leave @@ -4317,6 +4272,7 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) JS_ASSERT(varList->isArity(PN_LIST)); ParseNode *letBody = pnLet->pn_right; JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE)); + Rooted blockObj(cx, &letBody->pn_objbox->object->as()); int letHeadDepth = bce->stackDepth; @@ -4325,8 +4281,14 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */ uint32_t alreadyPushed = bce->stackDepth - letHeadDepth; + uint32_t blockObjCount = blockObj->slotCount(); + for (uint32_t i = alreadyPushed; i < blockObjCount; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + StmtInfoBCE stmtInfo(cx); - if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed)) + if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0)) return false; if (!EmitTree(cx, bce, letBody->pn_expr)) @@ -4335,6 +4297,10 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; + JSOp leaveOp = letBody->getOp(); + JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV); + EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount()); + return true; } @@ -4346,9 +4312,19 @@ MOZ_NEVER_INLINE static bool EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); + JS_ASSERT(pn->getOp() == JSOP_POPN); StmtInfoBCE stmtInfo(cx); - if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0)) + ObjectBox *objbox = pn->pn_objbox; + StaticBlockObject &blockObj = objbox->object->as(); + size_t slots = blockObj.slotCount(); + + for (size_t n = 0; n < slots; ++n) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + + if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0)) return false; if (!EmitTree(cx, bce, pn->pn_expr)) @@ -4357,6 +4333,8 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, slots); + return true; } @@ -4385,6 +4363,18 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); JS_ASSERT_IF(letDecl, pn1->isLet()); + Rooted + blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr); + uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0; + + // For-of loops run with two values on the stack: the iterator and the + // current result object. If the loop also has a lexical block, those + // lexicals are deeper on the stack than the iterator. + for (uint32_t i = 0; i < blockObjCount; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + // 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) { @@ -4396,9 +4386,6 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bce->emittingForInit = false; } - // 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; @@ -4421,7 +4408,7 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t // Enter the block before the loop body, after evaluating the obj. StmtInfoBCE letStmt(cx); if (letDecl) { - if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) + if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2)) return false; } @@ -4514,8 +4501,8 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t return false; } - // Pop the result and the iter. - EMIT_UINT16_IMM_OP(JSOP_POPN, 2); + // Pop result, iter, and slots from the lexical block (if any). + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2); return true; } @@ -4530,6 +4517,38 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); JS_ASSERT_IF(letDecl, pn1->isLet()); + Rooted + blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr); + uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0; + + if (letDecl) { + /* + * The let's slot(s) will be under the iterator, but the block must not + * be entered until after evaluating the rhs. So, we reserve space for + * the block scope now, and only push the block onto the scope chain + * later. Thus, a for-let-in loop looks like: + * + * push x N + * eval rhs + * iter + * pushblockscope (if needed) + * goto + * ... loop body + * ifne + * debugleaveblock + * popblockscope (if needed) + * enditer + * popn(N) + * + * Note that pushblockscope and popblockscope only get emitted if some + * of the variables in the block are captured. + */ + for (uint32_t i = 0; i < blockObjCount; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + } + /* * 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 @@ -4561,7 +4580,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t /* Enter the block before the loop body, after evaluating the obj. */ StmtInfoBCE letStmt(cx); if (letDecl) { - if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) + if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1)) return false; } @@ -4643,6 +4662,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t if (letDecl) { if (!LeaveNestedScope(cx, bce, &letStmt)) return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); } return true; @@ -4934,8 +4954,7 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) BindingIter bi(bce->script); while (bi->name() != fun->atom()) bi++; - JS_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT || - bi->kind() == Binding::ARGUMENT); + JS_ASSERT(bi->kind() == VARIABLE || bi->kind() == CONSTANT || bi->kind() == ARGUMENT); JS_ASSERT(bi.frameIndex() < JS_BIT(20)); #endif pn->pn_index = index; @@ -6504,7 +6523,10 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) */ if (!EmitTree(cx, bce, pn->pn_kid)) return false; - if (!EmitDupAt(cx, bce, bce->arrayCompDepth)) + uint32_t slot = bce->arrayCompDepth; + if (!AdjustBlockSlot(cx, bce, &slot)) + return false; + if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce)) return false; if (Emit1(cx, bce, JSOP_ARRAYPUSH) < 0) return false; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 725b3ce15dd..f93dd7a3226 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -421,6 +421,8 @@ class FullParseHandler inline bool addCatchBlock(ParseNode *catchList, ParseNode *letBlock, ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody); + inline void setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr); + inline void setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *pn); inline ParseNode *newFunctionDefinition(); void setFunctionBody(ParseNode *pn, ParseNode *kid) { @@ -433,10 +435,7 @@ class FullParseHandler void addFunctionArgument(ParseNode *pn, ParseNode *argpn) { pn->pn_body->append(argpn); } - inline ParseNode *newLexicalScope(ObjectBox *blockbox); - inline void setLexicalScopeBody(ParseNode *block, ParseNode *body); - bool isOperationWithoutParens(ParseNode *pn, ParseNodeKind kind) { return pn->isKind(kind) && !pn->isInParens(); } @@ -598,6 +597,15 @@ FullParseHandler::addCatchBlock(ParseNode *catchList, ParseNode *letBlock, return true; } +inline void +FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr) +{ + JS_ASSERT(block->isOp(JSOP_POPN)); + if (leaveBlockExpr) + block->setOp(JSOP_POPNV); + block->pn_expr = kid; +} + inline void FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue) { @@ -626,18 +634,13 @@ FullParseHandler::newLexicalScope(ObjectBox *blockbox) if (!pn) return nullptr; + pn->setOp(JSOP_POPN); pn->pn_objbox = blockbox; pn->pn_cookie.makeFree(); pn->pn_dflags = 0; return pn; } -inline void -FullParseHandler::setLexicalScopeBody(ParseNode *block, ParseNode *kid) -{ - block->pn_expr = kid; -} - inline bool FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op) { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index d76c868a971..45df351ee5f 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -404,7 +404,8 @@ enum ParseNodeKind * PNK_NULL, * PNK_THIS * - * PNK_LEXICALSCOPE name pn_objbox: block object in ObjectBox holder + * PNK_LEXICALSCOPE name pn_op: JSOP_POPN or JSOP_POPNV + * pn_objbox: block object in ObjectBox holder * pn_expr: block body * PNK_ARRAYCOMP list pn_count: 1 * pn_head: list of 1 element, which is block diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index ec7032de31e..8fc7b4552ab 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -272,16 +272,16 @@ AppendPackedBindings(const ParseContext *pc, const DeclVector &vec Definition *dn = vec[i]; PropertyName *name = dn->name(); - Binding::Kind kind; + BindingKind kind; switch (dn->kind()) { case Definition::VAR: - kind = Binding::VARIABLE; + kind = VARIABLE; break; case Definition::CONST: - kind = Binding::CONSTANT; + kind = CONSTANT; break; case Definition::ARG: - kind = Binding::ARGUMENT; + kind = ARGUMENT; break; default: MOZ_ASSUME_UNREACHABLE("unexpected dn->kind"); @@ -329,7 +329,7 @@ ParseContext::generateFunctionBindings(ExclusiveContext *cx, Token AppendPackedBindings(this, vars_, packedBindings + args_.length()); return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(), - packedBindings, blockScopeDepth); + packedBindings); } template @@ -615,8 +615,7 @@ Parser::parse(JSObject *chain) GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption); ParseContext globalpc(this, /* parent = */ nullptr, ParseHandler::null(), &globalsc, /* newDirectives = */ nullptr, - /* staticLevel = */ 0, /* bodyid = */ 0, - /* blockScopeDepth = */ 0); + /* staticLevel = */ 0, /* bodyid = */ 0); if (!globalpc.init(tokenStream)) return null(); @@ -878,8 +877,7 @@ Parser::standaloneFunctionBody(HandleFunction fun, const AutoN handler.setFunctionBox(fn, funbox); ParseContext funpc(this, pc, fn, funbox, newDirectives, - /* staticLevel = */ 0, /* bodyid = */ 0, - /* blockScopeDepth = */ 0); + /* staticLevel = */ 0, /* bodyid = */ 0); if (!funpc.init(tokenStream)) return null(); @@ -2127,8 +2125,7 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, ParseContext funpc(parser, outerpc, SyntaxParseHandler::null(), funbox, newDirectives, outerpc->staticLevel + 1, - outerpc->blockidGen, - /* blockScopeDepth = */ 0); + outerpc->blockidGen); if (!funpc.init(tokenStream)) return false; @@ -2163,8 +2160,7 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, // Continue doing a full parse for this inner function. ParseContext funpc(this, pc, pn, funbox, newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen, - /* blockScopeDepth = */ 0); + outerpc->staticLevel + 1, outerpc->blockidGen); if (!funpc.init(tokenStream)) return false; @@ -2203,8 +2199,7 @@ Parser::functionArgsAndBody(Node pn, HandleFunction fun, // Initialize early for possible flags mutation via destructuringExpr. ParseContext funpc(this, pc, handler.null(), funbox, newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen, - /* blockScopeDepth = */ 0); + outerpc->staticLevel + 1, outerpc->blockidGen); if (!funpc.init(tokenStream)) return false; @@ -2239,8 +2234,7 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st Directives newDirectives = directives; ParseContext funpc(this, /* parent = */ nullptr, pn, funbox, - &newDirectives, staticLevel, /* bodyid = */ 0, - /* blockScopeDepth = */ 0); + &newDirectives, staticLevel, /* bodyid = */ 0); if (!funpc.init(tokenStream)) return null(); @@ -2694,7 +2688,7 @@ Parser::bindLet(BindData *data, Rooted blockObj(cx, data->let.blockObj); unsigned index = blockObj->slotCount(); - if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) { + if (index >= StaticBlockObject::VAR_INDEX_LIMIT) { parser->report(ParseError, false, pn, data->let.overflow); return false; } @@ -2775,33 +2769,6 @@ struct PopLetDecl { } }; -// We compute the maximum block scope depth, in slots, of a compilation unit at -// parse-time. Each nested statement has a field indicating the maximum block -// scope depth that is nested inside it. When we leave a nested statement, we -// add the number of slots in the statement to the nested depth, and use that to -// update the maximum block scope depth of the outer statement or parse -// context. In the end, pc->blockScopeDepth will indicate the number of slots -// to reserve in the fixed part of a stack frame. -// -template -static void -AccumulateBlockScopeDepth(ParseContext *pc) -{ - uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth; - StmtInfoPC *outer = pc->topStmt->down; - - if (pc->topStmt->isBlockScope) - innerDepth += pc->topStmt->staticScope->template as().slotCount(); - - if (outer) { - if (outer->innerBlockScopeDepth < innerDepth) - outer->innerBlockScopeDepth = innerDepth; - } else { - if (pc->blockScopeDepth < innerDepth) - pc->blockScopeDepth = innerDepth; - } -} - template static void PopStatementPC(TokenStream &ts, ParseContext *pc) @@ -2809,7 +2776,6 @@ PopStatementPC(TokenStream &ts, ParseContext *pc) RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope); JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope); - AccumulateBlockScopeDepth(pc); FinishPopStatement(pc); if (scopeObj) { @@ -2857,7 +2823,7 @@ LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::Stmt JS_ASSERT(shape->hasShortID()); if (slotp) - *slotp = shape->shortid(); + *slotp = blockObj.stackDepth() + shape->shortid(); return stmt; } } @@ -3368,7 +3334,7 @@ Parser::letBlock(LetContext letContext) if (!expr) return null(); } - handler.setLexicalScopeBody(block, expr); + handler.setLeaveBlockResult(block, expr, letContext != LetStatement); PopStatementPC(tokenStream, pc); handler.setEndPosition(pnlet, pos().end); @@ -3646,6 +3612,7 @@ Parser::letDeclaration() if (!pn1) return null(); + pn1->setOp(JSOP_POPN); pn1->pn_pos = pc->blockNode->pn_pos; pn1->pn_objbox = blockbox; pn1->pn_expr = pc->blockNode; @@ -3681,6 +3648,8 @@ Parser::letStatement() if (tokenStream.peekToken() == TOK_LP) { pn = letBlock(LetStatement); JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); + JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_POPNV, + pn->pn_expr->isOp(JSOP_POPN)); } else { pn = letDeclaration(); } @@ -6033,31 +6002,6 @@ CompExprTransplanter::transplant(ParseNode *pn) return true; } -// Parsing JS1.7-style comprehensions is terrible: we parse the head expression -// as if it's part of a comma expression, then when we see the "for" we -// transplant the parsed expression into the inside of a constructed -// for-of/for-in/for-each tail. Transplanting an already-parsed expression is -// tricky, but the CompExprTransplanter handles most of that. -// -// The one remaining thing to patch up is the block scope depth. We need to -// compute the maximum block scope depth of a function, so we know how much -// space to reserve in the fixed part of a stack frame. Normally this is done -// whenever we leave a statement, via AccumulateBlockScopeDepth. However if the -// head has a let expression, we need to re-assign that depth to the tail of the -// comprehension. -// -// Thing is, we don't actually know what that depth is, because the only -// information we keep is the maximum nested depth within a statement, so we -// just conservatively propagate the maximum nested depth from the top statement -// to the comprehension tail. -// -template -static unsigned -ComprehensionHeadBlockScopeDepth(ParseContext *pc) -{ - return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth; -} - /* * Starting from a |for| keyword after the first array initialiser element or * an expression in an open parenthesis, parse the tail of the comprehension @@ -6071,8 +6015,7 @@ template <> ParseNode * Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, ParseContext *outerpc, - ParseNodeKind kind, JSOp op, - unsigned innerBlockScopeDepth) + ParseNodeKind kind, JSOp op) { /* * If we saw any inner functions while processing the generator expression @@ -6297,7 +6240,6 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bo pn2->pn_kid = kid; *pnp = pn2; - pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth; PopStatementPC(tokenStream, pc); return pn; } @@ -6321,8 +6263,7 @@ Parser::arrayInitializerComprehensionTail(ParseNode *pn) *pn->pn_tail = nullptr; ParseNode *pntop = comprehensionTail(pnexp, pn->pn_blockid, false, nullptr, - PNK_ARRAYPUSH, JSOP_ARRAYPUSH, - ComprehensionHeadBlockScopeDepth(pc)); + PNK_ARRAYPUSH, JSOP_ARRAYPUSH); if (!pntop) return false; pn->append(pntop); @@ -6392,8 +6333,7 @@ Parser::generatorExpr(ParseNode *kid) ParseContext genpc(this, outerpc, genfn, genFunbox, /* newDirectives = */ nullptr, - outerpc->staticLevel + 1, outerpc->blockidGen, - /* blockScopeDepth = */ 0); + outerpc->staticLevel + 1, outerpc->blockidGen); if (!genpc.init(tokenStream)) return null(); @@ -6411,9 +6351,7 @@ Parser::generatorExpr(ParseNode *kid) genFunbox->inGenexpLambda = true; genfn->pn_blockid = genpc.bodyid; - ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc, - PNK_SEMI, JSOP_NOP, - ComprehensionHeadBlockScopeDepth(outerpc)); + ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc); if (!body) return null(); JS_ASSERT(!genfn->pn_body); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 532df37fd49..b4249354808 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -28,9 +28,8 @@ struct StmtInfoPC : public StmtInfoBase { StmtInfoPC *downScope; /* next enclosing lexical scope */ uint32_t blockid; /* for simplified dominance computation */ - uint32_t innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */ - StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {} + StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx) {} }; typedef HashSet FuncStmtSet; @@ -119,7 +118,6 @@ struct ParseContext : public GenericParseContext bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == StarGenerator; } - 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: @@ -137,6 +135,11 @@ struct ParseContext : public GenericParseContext return args_.length(); } + uint32_t numVars() const { + JS_ASSERT(sc->isFunctionBox()); + return vars_.length(); + } + /* * This function adds a definition to the lexical scope represented by this * ParseContext. @@ -240,7 +243,7 @@ struct ParseContext : public GenericParseContext ParseContext(Parser *prs, GenericParseContext *parent, Node maybeFunction, SharedContext *sc, Directives *newDirectives, - unsigned staticLevel, uint32_t bodyid, uint32_t blockScopeDepth) + unsigned staticLevel, uint32_t bodyid) : GenericParseContext(parent, sc), bodyid(0), // initialized in init() blockidGen(bodyid), // used to set |bodyid| and subsequently incremented in init() @@ -250,7 +253,6 @@ struct ParseContext : public GenericParseContext maybeFunction(maybeFunction), staticLevel(staticLevel), lastYieldOffset(NoYieldOffset), - blockScopeDepth(blockScopeDepth), blockNode(ParseHandler::null()), decls_(prs->context, prs->alloc), args_(prs->context), @@ -545,8 +547,7 @@ class Parser : private AutoGCRooter, public StrictModeGetter Node condition(); Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp, ParseContext *outerpc, - ParseNodeKind kind, JSOp op, - unsigned innerBlockScopeDepth); + ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP); bool arrayInitializerComprehensionTail(Node pn); Node generatorExpr(Node kid); bool argumentList(Node listNode, bool *isSpread); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 760d3c318d1..1e5c05fba69 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -157,15 +157,14 @@ class SyntaxParseHandler bool addCatchBlock(Node catchList, Node letBlock, Node catchName, Node catchGuard, Node catchBody) { return true; } + void setLeaveBlockResult(Node block, Node kid, bool leaveBlockExpr) {} + void setLastFunctionArgumentDefault(Node funcpn, Node pn) {} Node newFunctionDefinition() { return NodeGeneric; } void setFunctionBody(Node pn, Node kid) {} void setFunctionBox(Node pn, FunctionBox *funbox) {} void addFunctionArgument(Node pn, Node argpn) {} - Node newLexicalScope(ObjectBox *blockbox) { return NodeGeneric; } - void setLexicalScopeBody(Node block, Node body) {} - bool isOperationWithoutParens(Node pn, ParseNodeKind kind) { // It is OK to return false here, callers should only use this method // for reporting strict option warnings and parsing code which the diff --git a/js/src/jit-test/tests/basic/testBug579647.js b/js/src/jit-test/tests/basic/testBug579647.js index 4a4d41164b3..027f643a96f 100644 --- a/js/src/jit-test/tests/basic/testBug579647.js +++ b/js/src/jit-test/tests/basic/testBug579647.js @@ -1,4 +1,4 @@ -expected = "TypeError: a is not a function"; +expected = "TypeError: NaN is not a function"; actual = ""; try { @@ -10,4 +10,4 @@ try { actual = '' + e; } -assertEq(actual, expected); +assertEq(expected, actual); diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index d6249f7de7a..81e129c20c9 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -5269,8 +5269,7 @@ ParseFunction(ModuleCompiler &m, ParseNode **fnOut) Directives newDirectives = directives; AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen, - /* blockScopeDepth = */ 0); + outerpc->staticLevel + 1, outerpc->blockidGen); if (!funpc.init(m.parser().tokenStream)) return false; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 4d18eac5f21..ba64591f700 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -854,16 +854,10 @@ BaselineCompiler::emit_JSOP_POPN() } bool -BaselineCompiler::emit_JSOP_DUPAT() +BaselineCompiler::emit_JSOP_POPNV() { - frame.syncStack(0); - - // DUPAT takes a value on the stack and re-pushes it on top. It's like - // GETLOCAL but it addresses from the top of the stack instead of from the - // stack frame. - - int depth = -(GET_UINT24(pc) + 1); - masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); + frame.popRegsAndSync(1); + frame.popn(GET_UINT16(pc)); frame.push(R0); return true; } @@ -2296,7 +2290,17 @@ BaselineCompiler::emit_JSOP_INITELEM_SETTER() bool BaselineCompiler::emit_JSOP_GETLOCAL() { - frame.pushLocal(GET_LOCALNO(pc)); + uint32_t local = GET_LOCALNO(pc); + + if (local >= frame.nlocals()) { + // Destructuring assignments may use GETLOCAL to access stack values. + frame.syncStack(0); + masm.loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)), R0); + frame.push(R0); + return true; + } + + frame.pushLocal(local); return true; } diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 3c7300b1bb1..35d85c02646 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -26,7 +26,7 @@ namespace jit { _(JSOP_LABEL) \ _(JSOP_POP) \ _(JSOP_POPN) \ - _(JSOP_DUPAT) \ + _(JSOP_POPNV) \ _(JSOP_ENTERWITH) \ _(JSOP_LEAVEWITH) \ _(JSOP_DUP) \ diff --git a/js/src/jit/BaselineFrame.h b/js/src/jit/BaselineFrame.h index 8d0601b645e..ce4ad7f41fd 100644 --- a/js/src/jit/BaselineFrame.h +++ b/js/src/jit/BaselineFrame.h @@ -152,8 +152,8 @@ class BaselineFrame } Value &unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const { - JS_ASSERT(i < script()->nfixedvars()); JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i)); + JS_ASSERT(i < script()->nfixed()); return *valueSlot(i); } diff --git a/js/src/jit/BaselineFrameInfo.h b/js/src/jit/BaselineFrameInfo.h index 4454f4de242..a237f9d97c3 100644 --- a/js/src/jit/BaselineFrameInfo.h +++ b/js/src/jit/BaselineFrameInfo.h @@ -243,7 +243,6 @@ class FrameInfo sv->setRegister(val, knownType); } inline void pushLocal(uint32_t local) { - JS_ASSERT(local < nlocals()); StackValue *sv = rawPush(); sv->setLocalSlot(local); } @@ -261,7 +260,15 @@ class FrameInfo sv->setStack(); } inline Address addressOfLocal(size_t local) const { - JS_ASSERT(local < nlocals()); +#ifdef DEBUG + if (local >= nlocals()) { + // GETLOCAL and SETLOCAL can be used to access stack values. This is + // fine, as long as they are synced. + size_t slot = local - nlocals(); + JS_ASSERT(slot < stackDepth()); + JS_ASSERT(stack[slot].kind() == StackValue::Stack); + } +#endif return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)); } Address addressOfArg(size_t arg) const { diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h index 82f8b9cd169..76917670eb3 100644 --- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -10,7 +10,6 @@ #include "jsfun.h" #include "jit/Registers.h" -#include "vm/ScopeObject.h" namespace js { namespace jit { @@ -61,24 +60,20 @@ 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(); 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) + : script_(nullptr), fun_(nullptr), osrPc_(nullptr), constructing_(false), + executionMode_(executionMode), scriptNeedsArgsObj_(false) { nimplicit_ = 0; nargs_ = 0; - nfixedvars_ = 0; nlocals_ = nlocals; nstack_ = 1; /* For FunctionCompiler::pushPhiInput/popPhiOutput */ nslots_ = nlocals_ + nstack_; @@ -96,9 +91,6 @@ class CompileInfo jsbytecode *osrPc() { return osrPc_; } - NestedScopeObject *osrStaticScope() const { - return osrStaticScope_; - } bool hasOsrAt(jsbytecode *pc) { JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); @@ -163,13 +155,7 @@ class CompileInfo 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 all local variables. This includes "fixed - // vars" (see above) and also block-scoped locals. + // Number of slots needed for local variables. unsigned nlocals() const { return nlocals_; } @@ -237,33 +223,21 @@ class CompileInfo return nimplicit() + nargs() + nlocals(); } - bool isSlotAliased(uint32_t index, NestedScopeObject *staticScope) const { + bool isSlotAliased(uint32_t index) const { if (funMaybeLazy() && index == thisSlot()) return false; uint32_t arg = index - firstArgSlot(); - if (arg < nargs()) - return script()->formalIsAliased(arg); + if (arg < nargs()) { + if (script()->formalIsAliased(arg)) + return true; + return false; + } - uint32_t local = index - firstLocalSlot(); - if (local < nlocals()) { - // First, check if this local is a var. - if (local < nfixedvars()) - return script()->varIsAliased(local); - - // Otherwise, it might be part of a block scope. - for (; staticScope; staticScope = staticScope->enclosingNestedScope()) { - if (!staticScope->is()) - continue; - StaticBlockObject &blockObj = staticScope->as(); - if (blockObj.localOffset() < local) { - if (local - blockObj.localOffset() < blockObj.slotCount()) - return blockObj.isAliased(local - blockObj.localOffset()); - return false; - } - } - - // In this static scope, this var is dead. + uint32_t var = index - firstLocalSlot(); + if (var < nlocals()) { + if (script()->varIsAliased(var)) + return true; return false; } @@ -271,13 +245,6 @@ class CompileInfo return false; } - bool isSlotAliasedAtEntry(uint32_t index) const { - return isSlotAliased(index, nullptr); - } - bool isSlotAliasedAtOsr(uint32_t index) const { - return isSlotAliased(index, osrStaticScope()); - } - bool hasArguments() const { return script()->argumentsHasVarBinding(); } @@ -302,14 +269,12 @@ class CompileInfo private: unsigned nimplicit_; unsigned nargs_; - unsigned nfixedvars_; unsigned nlocals_; unsigned nstack_; unsigned nslots_; JSScript *script_; JSFunction *fun_; jsbytecode *osrPc_; - NestedScopeObject *osrStaticScope_; bool constructing_; ExecutionMode executionMode_; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 5e65fbd723f..4b2d9e140bc 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -90,18 +90,12 @@ jit::NewBaselineFrameInspector(TempAllocator *temp, BaselineFrame *frame) if (!inspector->varTypes.reserve(frame->script()->nfixed())) return nullptr; - for (size_t i = 0; i < frame->script()->nfixedvars(); i++) { + for (size_t i = 0; i < frame->script()->nfixed(); i++) { if (script->varIsAliased(i)) inspector->varTypes.infallibleAppend(types::Type::UndefinedType()); else inspector->varTypes.infallibleAppend(types::GetValueType(frame->unaliasedVar(i))); } - for (size_t i = frame->script()->nfixedvars(); i < frame->script()->nfixed(); i++) { - // FIXME: If this slot corresponds to a scope that is active at this PC, - // and the slot is unaliased, we should initialize the type from the - // slot value, as above. - inspector->varTypes.infallibleAppend(types::Type::UndefinedType()); - } return inspector; } @@ -1159,10 +1153,11 @@ IonBuilder::maybeAddOsrTypeBarriers() headerPhi++; for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++, headerPhi++) { + // Aliased slots are never accessed, since they need to go through // the callobject. The typebarriers are added there and can be - // discarded here. - if (info().isSlotAliasedAtOsr(i)) + // discared here. + if (info().isSlotAliased(i)) continue; MInstruction *def = osrBlock->getSlot(i)->toInstruction(); @@ -1307,7 +1302,7 @@ IonBuilder::traverseBytecode() switch (op) { case JSOP_POP: case JSOP_POPN: - case JSOP_DUPAT: + case JSOP_POPNV: case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: @@ -1557,9 +1552,14 @@ IonBuilder::inspectOpcode(JSOp op) current->pop(); return true; - case JSOP_DUPAT: - current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc)); + case JSOP_POPNV: + { + MDefinition *mins = current->pop(); + for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) + current->pop(); + current->push(mins); return true; + } case JSOP_NEWINIT: if (GET_UINT8(pc) == JSProto_Array) @@ -5837,11 +5837,11 @@ IonBuilder::newOsrPreheader(MBasicBlock *predecessor, jsbytecode *loopEntry) for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++) { MDefinition *existing = current->getSlot(i); MDefinition *def = osrBlock->getSlot(i); - JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliasedAtOsr(i), def->type() == MIRType_Value); + JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliased(i), def->type() == MIRType_Value); // Aliased slots are never accessed, since they need to go through // the callobject. No need to type them here. - if (info().isSlotAliasedAtOsr(i)) + if (info().isSlotAliased(i)) continue; def->setResultType(existing->type()); @@ -5881,7 +5881,7 @@ IonBuilder::newPendingLoopHeader(MBasicBlock *predecessor, jsbytecode *pc, bool // The value of aliased args and slots are in the callobject. So we can't // the value from the baseline frame. - if (info().isSlotAliasedAtOsr(i)) + if (info().isSlotAliased(i)) continue; // Don't bother with expression stack values. The stack should be diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 2efe444bc99..4ebc8953e72 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -766,7 +766,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) RootedScript script(cx, script_); for (BindingIter bi(script); bi; bi++) { - if (bi->kind() == Binding::ARGUMENT) + if (bi->kind() == ARGUMENT) escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased(); else escapedSlots[LocalSlot(script_, bi.frameIndex())] = allVarsAliased || bi->aliased(); @@ -928,11 +928,32 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) break; } - case JSOP_GETLOCAL: - case JSOP_CALLLOCAL: - case JSOP_SETLOCAL: - JS_ASSERT(GET_LOCALNO(pc) < script_->nfixed()); + case JSOP_GETLOCAL: { + /* + * Watch for uses of variables not known to be defined, and mark + * them as having possible uses before definitions. Ignore GETLOCAL + * followed by a POP, these are generated for, e.g. 'var x;' + */ + jsbytecode *next = pc + JSOP_GETLOCAL_LENGTH; + if (JSOp(*next) != JSOP_POP || jumpTarget(next)) { + uint32_t local = GET_LOCALNO(pc); + if (local >= script_->nfixed()) { + localsAliasStack_ = true; + break; + } + } break; + } + + case JSOP_CALLLOCAL: + case JSOP_SETLOCAL: { + uint32_t local = GET_LOCALNO(pc); + if (local >= script_->nfixed()) { + localsAliasStack_ = true; + break; + } + break; + } case JSOP_PUSHBLOCKSCOPE: localsAliasStack_ = true; @@ -1801,13 +1822,6 @@ ScriptAnalysis::analyzeSSA(JSContext *cx) stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1]; break; - case JSOP_DUPAT: { - unsigned pickedDepth = GET_UINT24 (pc); - JS_ASSERT(pickedDepth < stackDepth - 1); - stack[stackDepth - 1].v = stack[stackDepth - 2 - pickedDepth].v; - break; - } - case JSOP_SWAP: /* Swap is like pick 1. */ case JSOP_PICK: { diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 89622331dc7..fc7a00a3aa1 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -117,6 +117,8 @@ js::StackUses(JSScript *script, jsbytecode *pc) switch (op) { case JSOP_POPN: return GET_UINT16(pc); + case JSOP_POPNV: + return GET_UINT16(pc) + 1; default: /* stack: fun, this, [argc arguments] */ JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || @@ -466,16 +468,6 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint } break; - case JSOP_DUPAT: { - JS_ASSERT(ndefs == 1); - jsbytecode *pc = script_->offsetToPC(offset); - unsigned n = GET_UINT24(pc); - JS_ASSERT(n < stackDepth); - if (offsetStack) - offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; - break; - } - case JSOP_SWAP: JS_ASSERT(ndefs == 2); if (offsetStack) { @@ -840,9 +832,9 @@ ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes) if (!JSVAL_IS_PRIMITIVE(v)) { JSObject *obj = JSVAL_TO_OBJECT(v); - if (obj->is()) { + if (obj->is()) { char *source = JS_sprintf_append(nullptr, "depth %d {", - obj->as().localOffset()); + obj->as().stackDepth()); if (!source) return false; @@ -1033,8 +1025,7 @@ js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc, goto print_int; case JOF_UINT24: - JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY || - op == JSOP_DUPAT); + JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY); i = (int)GET_UINT24(pc); goto print_int; @@ -1445,8 +1436,9 @@ struct ExpressionDecompiler bool init(); bool decompilePCForStackOperand(jsbytecode *pc, int i); bool decompilePC(jsbytecode *pc); - JSAtom *getFixed(uint32_t slot, jsbytecode *pc); + JSAtom *getVar(uint32_t slot); JSAtom *getArg(unsigned slot); + JSAtom *findLetVar(jsbytecode *pc, unsigned depth); JSAtom *loadAtom(jsbytecode *pc); bool quote(JSString *s, uint32_t quote); bool write(const char *s); @@ -1512,9 +1504,18 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc) case JSOP_GETLOCAL: case JSOP_CALLLOCAL: { uint32_t i = GET_LOCALNO(pc); - if (JSAtom *atom = getFixed(i, pc)) - return write(atom); - return write("(intermediate value)"); + JSAtom *atom; + if (i >= script->nfixed()) { + i -= script->nfixed(); + JS_ASSERT(i < unsigned(parser.stackDepthAtPC(pc))); + atom = findLetVar(pc, i); + if (!atom) + return decompilePCForStackOperand(pc, i); // Destructing temporary + } else { + atom = getVar(i); + } + JS_ASSERT(atom); + return write(atom); } case JSOP_CALLALIASEDVAR: case JSOP_GETALIASEDVAR: { @@ -1639,6 +1640,26 @@ ExpressionDecompiler::loadAtom(jsbytecode *pc) return script->getAtom(GET_UINT32_INDEX(pc)); } +JSAtom * +ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth) +{ + for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) { + if (!chain->is()) + continue; + StaticBlockObject &block = chain->as(); + uint32_t blockDepth = block.stackDepth(); + uint32_t blockCount = block.slotCount(); + if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) { + for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { + const Shape &shape = r.front(); + if (shape.shortid() == int(depth - blockDepth)) + return JSID_TO_ATOM(shape.propid()); + } + } + } + return nullptr; +} + JSAtom * ExpressionDecompiler::getArg(unsigned slot) { @@ -1648,31 +1669,12 @@ ExpressionDecompiler::getArg(unsigned slot) } JSAtom * -ExpressionDecompiler::getFixed(uint32_t slot, jsbytecode *pc) +ExpressionDecompiler::getVar(uint32_t slot) { - if (slot < script->nfixedvars()) { - JS_ASSERT(fun); - slot += fun->nargs(); - JS_ASSERT(slot < script->bindings.count()); - return (*localNames)[slot].name(); - } - for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) { - if (!chain->is()) - continue; - StaticBlockObject &block = chain->as(); - if (slot < block.localOffset()) - continue; - slot -= block.localOffset(); - if (slot >= block.slotCount()) - return nullptr; - for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { - const Shape &shape = r.front(); - if (shape.shortid() == int(slot)) - return JSID_TO_ATOM(shape.propid()); - } - break; - } - return nullptr; + JS_ASSERT(fun); + slot += fun->nargs(); + JS_ASSERT(slot < script->bindings.count()); + return (*localNames)[slot].name(); } bool diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 2c51a84b0b3..0bd5ac8cfb8 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -72,21 +72,18 @@ Bindings::argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle binding bool Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, unsigned numArgs, uint32_t numVars, - Binding *bindingArray, 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(UINT32_MAX - numArgs >= numVars); self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT; self->numArgs_ = numArgs; self->numVars_ = numVars; - 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 @@ -145,7 +142,7 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE | - (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0); + (bi->kind() == CONSTANT ? JSPROP_READONLY : 0); StackShape child(base, NameToId(bi->name()), slot, attrs, 0, 0); shape = cx->compartment()->propertyTree.getChild(cx, shape, child); @@ -189,8 +186,7 @@ Bindings::clone(JSContext *cx, InternalBindingsHandle self, * 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.bindingArray())) return false; self->switchToScriptStorage(dstPackedBindings); return true; @@ -205,7 +201,7 @@ GCMethods::initial() template static bool XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars, - HandleScript script, unsigned numBlockScoped) + HandleScript script) { JSContext *cx = xdr->cx(); @@ -243,15 +239,14 @@ XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, ui return false; PropertyName *name = atoms[i].toString()->asAtom().asPropertyName(); - Binding::Kind kind = Binding::Kind(u8 >> 1); + BindingKind kind = BindingKind(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, bindingArray)) return false; } @@ -486,20 +481,16 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc /* XDR arguments and vars. */ uint16_t nargs = 0; - uint16_t nblocklocals = 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(); nvars = script->bindings.numVars(); } if (!xdr->codeUint16(&nargs)) return false; - if (!xdr->codeUint16(&nblocklocals)) - return false; if (!xdr->codeUint32(&nvars)) return false; @@ -639,7 +630,7 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc /* 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, script)) return false; if (mode == XDR_DECODE) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 55b0a83eb4f..2ffa3db20ed 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -127,10 +127,19 @@ struct BlockScopeArray { uint32_t length; // Count of indexed try notes. }; +/* + * A "binding" is a formal, 'var' or 'const' declaration. A function's lexical + * scope is composed of these three kinds of bindings. + */ + +enum BindingKind { ARGUMENT, VARIABLE, CONSTANT }; + class Binding { - // One JSScript stores one Binding per formal/variable so we use a - // packed-word representation. + /* + * One JSScript stores one Binding per formal/variable so we use a + * packed-word representation. + */ uintptr_t bits_; static const uintptr_t KIND_MASK = 0x3; @@ -138,13 +147,9 @@ class Binding static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT); public: - // A "binding" is a formal, 'var', 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) { + Binding(PropertyName *name, BindingKind kind, bool aliased) { JS_STATIC_ASSERT(CONSTANT <= KIND_MASK); JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0); JS_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0); @@ -155,8 +160,8 @@ class Binding return (PropertyName *)(bits_ & NAME_MASK); } - Kind kind() const { - return Kind(bits_ & KIND_MASK); + BindingKind kind() const { + return BindingKind(bits_ & KIND_MASK); } bool aliased() const { @@ -183,7 +188,6 @@ class Bindings HeapPtr callObjShape_; uintptr_t bindingArrayAndFlag_; uint16_t numArgs_; - uint16_t numBlockScoped_; uint32_t numVars_; /* @@ -216,21 +220,7 @@ class Bindings */ static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, unsigned numArgs, uint32_t numVars, - Binding *bindingArray, unsigned numBlockScoped); - - // 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) { - JS_ASSERT(!callObjShape_); - JS_ASSERT(numVars_ == 0); - JS_ASSERT(numBlockScoped < LOCALNO_LIMIT); - JS_ASSERT(numBlockScoped >= numBlockScoped_); - numBlockScoped_ = numBlockScoped; - } + Binding *bindingArray); uint8_t *switchToScriptStorage(Binding *newStorage); @@ -243,10 +233,6 @@ class Bindings unsigned numArgs() const { return numArgs_; } uint32_t numVars() const { return numVars_; } - unsigned numBlockScoped() const { return numBlockScoped_; } - uint32_t numLocals() const { return numVars() + numBlockScoped(); } - - // Return the size of the bindingArray. uint32_t count() const { return numArgs() + numVars(); } /* Return the initial shape of call objects created for this scope. */ @@ -938,15 +924,7 @@ class JSScript : public js::gc::BarrieredCell 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 { - js::AutoThreadSafeAccess ts(this); - return function_ ? bindings.numLocals() : bindings.numBlockScoped(); - } - - // Number of fixed slots reserved for vars. Only nonzero for function code. - size_t nfixedvars() const { js::AutoThreadSafeAccess ts(this); return function_ ? bindings.numVars() : 0; } @@ -1596,7 +1574,7 @@ namespace js { * Iterator over a script's bindings (formals and variables). * The order of iteration is: * - first, formal arguments, from index 0 to numArgs - * - next, variables, from index 0 to numLocals + * - next, variables, from index 0 to numVars */ class BindingIter { @@ -1636,7 +1614,7 @@ FillBindingVector(HandleScript fromScript, BindingVector *vec); /* * Iterator over the aliased formal bindings in ascending index order. This can * be veiwed as a filtering of BindingIter with predicate - * bi->aliased() && bi->kind() == Binding::ARGUMENT + * bi->aliased() && bi->kind() == ARGUMENT */ class AliasedFormalIter { diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index fecbfd99d61..4f3e275d842 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -21,8 +21,7 @@ namespace js { inline Bindings::Bindings() - : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), - numArgs_(0), numBlockScoped_(0), numVars_(0) + : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), numArgs_(0), numVars_(0) {} inline diff --git a/js/src/tests/js1_8_1/regress/regress-420399.js b/js/src/tests/js1_8_1/regress/regress-420399.js index 95aa0487290..423bfe13c2a 100644 --- a/js/src/tests/js1_8_1/regress/regress-420399.js +++ b/js/src/tests/js1_8_1/regress/regress-420399.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = "TypeError: a is undefined"; + expect = "TypeError: undefined has no properties"; try { (let (a=undefined) a).b = 3; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index fdfb5f2e8c1..abb323e54fb 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1744,14 +1744,14 @@ CASE(JSOP_POPN) REGS.sp -= GET_UINT16(REGS.pc); END_CASE(JSOP_POPN) -CASE(JSOP_DUPAT) +CASE(JSOP_POPNV) { - JS_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth()); - unsigned i = GET_UINT24(REGS.pc); - const Value &rref = REGS.sp[-int(i + 1)]; - PUSH_COPY(rref); + JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth()); + Value val = REGS.sp[-1]; + REGS.sp -= GET_UINT16(REGS.pc); + REGS.sp[-1] = val; } -END_CASE(JSOP_DUPAT) +END_CASE(JSOP_POPNV) CASE(JSOP_SETRVAL) POP_RETURN_VALUE(); @@ -3352,6 +3352,9 @@ CASE(JSOP_PUSHBLOCKSCOPE) StaticBlockObject &blockObj = script->getObject(REGS.pc)->as(); JS_ASSERT(blockObj.needsClone()); + // FIXME: "Aliased" slots don't need to be on the stack. + JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); + // Clone block and push on scope chain. if (!REGS.fp()->pushBlock(cx, blockObj)) goto error; @@ -3367,6 +3370,9 @@ CASE(JSOP_POPBLOCKSCOPE) JS_ASSERT(scope && scope->is()); StaticBlockObject &blockObj = scope->as(); JS_ASSERT(blockObj.needsClone()); + + // FIXME: "Aliased" slots don't need to be on the stack. + JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); #endif // Pop block from scope chain. diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 10d635699f4..45b93cd9fd6 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -100,8 +100,8 @@ /* spreadcall variant of JSOP_EVAL */ \ macro(JSOP_SPREADEVAL,43, "spreadeval", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ \ - /* Dup the Nth value from the top. */ \ - macro(JSOP_DUPAT, 44, "dupat", NULL, 4, 0, 1, JOF_UINT24) \ + /* Pop N values, preserving top value. */ \ + macro(JSOP_POPNV, 44, "popnv", NULL, 3, -1, 1, JOF_UINT16) \ \ macro(JSOP_UNUSED45, 45, "unused45", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED46, 46, "unused46", NULL, 1, 0, 0, JOF_BYTE) \ diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index eab64d801bf..16afe8b4769 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -667,15 +667,17 @@ ClonedBlockObject::create(JSContext *cx, Handle block, Abst JS_ASSERT(obj->slotSpan() >= block->slotCount() + RESERVED_SLOTS); obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain())); + obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(block->stackDepth())); /* * Copy in the closed-over locals. Closed-over locals don't need * any fixup since the initial value is 'undefined'. */ unsigned nslots = block->slotCount(); + unsigned base = frame.script()->nfixed() + block->stackDepth(); for (unsigned i = 0; i < nslots; ++i) { if (block->isAliased(i)) - obj->as().setVar(i, frame.unaliasedLocal(block->varToLocalIndex(i))); + obj->as().setVar(i, frame.unaliasedLocal(base + i)); } JS_ASSERT(obj->isDelegate()); @@ -687,9 +689,10 @@ void ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) { StaticBlockObject &block = staticBlock(); + unsigned base = frame.script()->nfixed() + block.stackDepth(); for (unsigned i = 0; i < slotCount(); ++i) { if (!block.isAliased(i)) - setVar(i, frame.unaliasedLocal(block.varToLocalIndex(i)), DONT_CHECK_ALIASING); + setVar(i, frame.unaliasedLocal(base + i), DONT_CHECK_ALIASING); } } @@ -718,7 +721,7 @@ StaticBlockObject::addVar(ExclusiveContext *cx, Handle block unsigned index, bool *redeclared) { JS_ASSERT(JSID_IS_ATOM(id) || (JSID_IS_INT(id) && JSID_TO_INT(id) == (int)index)); - JS_ASSERT(index < LOCAL_INDEX_LIMIT); + JS_ASSERT(index < VAR_INDEX_LIMIT); *redeclared = false; @@ -766,12 +769,16 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, JSContext *cx = xdr->cx(); Rooted obj(cx); - uint32_t count = 0, offset = 0; + uint32_t count = 0; + uint32_t depthAndCount = 0; if (mode == XDR_ENCODE) { obj = *objp; + uint32_t depth = obj->stackDepth(); + JS_ASSERT(depth <= UINT16_MAX); count = obj->slotCount(); - offset = obj->localOffset(); + JS_ASSERT(count <= UINT16_MAX); + depthAndCount = (depth << 16) | uint16_t(count); } if (mode == XDR_DECODE) { @@ -782,13 +789,13 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, *objp = obj; } - if (!xdr->codeUint32(&count)) - return false; - if (!xdr->codeUint32(&offset)) + if (!xdr->codeUint32(&depthAndCount)) return false; if (mode == XDR_DECODE) { - obj->setLocalOffset(offset); + uint32_t depth = uint16_t(depthAndCount >> 16); + count = uint16_t(depthAndCount); + obj->setStackDepth(depth); /* * XDR the block object's properties. We know that there are 'count' @@ -873,7 +880,7 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, HandleinitEnclosingNestedScope(enclosingScope); - clone->setLocalOffset(srcBlock->localOffset()); + clone->setStackDepth(srcBlock->stackDepth()); /* Shape::Range is reverse order, so build a list in forward order. */ AutoShapeVector shapes(cx); @@ -1187,7 +1194,7 @@ class DebugScopeProxy : public BaseProxyHandler if (!bi) return false; - if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) { + if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) { uint32_t i = bi.frameIndex(); if (script->varIsAliased(i)) return false; @@ -1209,7 +1216,7 @@ class DebugScopeProxy : public BaseProxyHandler vp.set(UndefinedValue()); } } else { - JS_ASSERT(bi->kind() == Binding::ARGUMENT); + JS_ASSERT(bi->kind() == ARGUMENT); unsigned i = bi.frameIndex(); if (script->formalIsAliased(i)) return false; @@ -1259,12 +1266,12 @@ class DebugScopeProxy : public BaseProxyHandler if (maybeLiveScope) { AbstractFramePtr frame = maybeLiveScope->frame(); JSScript *script = frame.script(); - uint32_t local = block->staticBlock().varToLocalIndex(i); + uint32_t local = block->slotToLocalIndex(script->bindings, shape->slot()); if (action == GET) vp.set(frame.unaliasedLocal(local)); else frame.unaliasedLocal(local) = vp; - JS_ASSERT(analyze::LocalSlot(script, local) < analyze::TotalSlots(script)); + JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script)); } else { if (action == GET) vp.set(block->var(i, DONT_CHECK_ALIASING)); diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index c9fdbed265d..f5829292d90 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -413,6 +413,20 @@ class BlockObject : public NestedScopeObject return propertyCountForCompilation(); } + /* + * Return the local corresponding to the ith binding where i is in the + * range [0, slotCount()) and the return local index is in the range + * [script->nfixed, script->nfixed + script->nslots). + */ + uint32_t slotToLocalIndex(const Bindings &bindings, uint32_t slot) { + JS_ASSERT(slot < RESERVED_SLOTS + slotCount()); + return bindings.numVars() + stackDepth() + (slot - RESERVED_SLOTS); + } + + uint32_t localIndexToSlot(const Bindings &bindings, uint32_t i) { + return RESERVED_SLOTS + (i - (bindings.numVars() + stackDepth())); + } + protected: /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */ const Value &slotValue(unsigned i) { @@ -426,42 +440,15 @@ class BlockObject : public NestedScopeObject class StaticBlockObject : public BlockObject { - static const unsigned LOCAL_OFFSET_SLOT = 1; - public: static StaticBlockObject *create(ExclusiveContext *cx); - /* See StaticScopeIter comment. */ - JSObject *enclosingStaticScope() const { - AutoThreadSafeAccess ts(this); - return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); - } - /* - * A refinement of enclosingStaticScope that returns nullptr if the enclosing - * static scope is a JSFunction. + * Return whether this StaticBlockObject contains a variable stored at + * the given stack depth (i.e., fp->base()[depth]). */ - inline StaticBlockObject *enclosingBlock() const; - - uint32_t localOffset() { - return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32(); - } - - // Return the local corresponding to the 'var'th binding where 'var' is in the - // range [0, slotCount()). - uint32_t varToLocalIndex(uint32_t var) { - JS_ASSERT(var < slotCount()); - return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + var; - } - - // Return the slot corresponding to local variable 'local', where 'local' is - // in the range [localOffset(), localOffset() + slotCount()). The result is - // in the range [RESERVED_SLOTS, RESERVED_SLOTS + slotCount()). - uint32_t localIndexToSlot(uint32_t local) { - JS_ASSERT(local >= localOffset()); - local -= localOffset(); - JS_ASSERT(local < slotCount()); - return RESERVED_SLOTS + local; + bool containsVarAtDepth(uint32_t depth) { + return depth >= stackDepth() && depth < stackDepth() + slotCount(); } /* @@ -495,9 +482,9 @@ class StaticBlockObject : public BlockObject } } - void setLocalOffset(uint32_t offset) { - JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined()); - initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset)); + void setStackDepth(uint32_t depth) { + JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined()); + initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth)); } /* @@ -521,7 +508,7 @@ class StaticBlockObject : public BlockObject * associated Shape. If we could remove the block dependencies on shape->shortid, we could * remove INDEX_LIMIT. */ - static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16); + static const unsigned VAR_INDEX_LIMIT = JS_BIT(16); static Shape *addVar(ExclusiveContext *cx, Handle block, HandleId id, unsigned index, bool *redeclared); diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 5b0f6c0ea8e..f29adcb496f 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -100,14 +100,13 @@ inline Value & StackFrame::unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing) { JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i)); - JS_ASSERT(i < script()->nfixedvars()); + JS_ASSERT(i < script()->nfixed()); return slots()[i]; } inline Value & StackFrame::unaliasedLocal(uint32_t i, MaybeCheckAliasing checkAliasing) { - JS_ASSERT(i < script()->nfixed()); #ifdef DEBUG CheckLocalUnaliased(checkAliasing, script(), i); #endif diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 55fa790d826..7270db93c85 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1268,8 +1268,8 @@ js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint if (!checkAliasing) return; - JS_ASSERT(i < script->nfixed()); - if (i < script->bindings.numVars()) { + JS_ASSERT(i < script->nslots()); + if (i < script->nfixed()) { JS_ASSERT(!script->varIsAliased(i)); } else { // FIXME: The callers of this function do not easily have the PC of the From a6a022cfe3d8084988180628eb2fada20fd9c47a Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Wed, 12 Feb 2014 10:55:25 -0800 Subject: [PATCH 05/40] Bug 928808: Suppress clang warnings (in newer clang versions) about inline 'new' and 'delete'. r=glandium --- configure.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configure.in b/configure.in index 91b1493b3d7..4aa5d915d53 100644 --- a/configure.in +++ b/configure.in @@ -1455,8 +1455,12 @@ if test "$GNU_CXX"; then # Turn off the following warnings that -Wall turns on: # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently + # -Wno-inline-new-delete - we inline 'new' and 'delete' in mozalloc + # for performance reasons, and because GCC and clang accept it (though + # clang warns about it). # MOZ_CXX_SUPPORTS_WARNING(-Wno-, invalid-offsetof, ac_cxx_has_wno_invalid_offsetof) + MOZ_CXX_SUPPORTS_WARNING(-Wno-, inline-new-delete, ac_cxx_has_wno_inline_new_delete) if test -z "$INTEL_CXX" -a -z "$CLANG_CXX"; then # Don't use -Wcast-align with ICC or clang From d547534f9c4e956b5fd311d2fa654175476499b8 Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Wed, 12 Feb 2014 18:46:24 +0100 Subject: [PATCH 06/40] Bug 962599 - Store let-bound variables in the fixed part of stack frames r=luke r=jandem --- js/src/frontend/BytecodeCompiler.cpp | 27 +- js/src/frontend/BytecodeEmitter.cpp | 372 ++++++++---------- js/src/frontend/FullParseHandler.h | 21 +- js/src/frontend/ParseNode.h | 3 +- js/src/frontend/Parser.cpp | 104 ++++- js/src/frontend/Parser.h | 15 +- js/src/frontend/SyntaxParseHandler.h | 5 +- js/src/jit-test/tests/basic/testBug579647.js | 4 +- js/src/jit/AsmJS.cpp | 3 +- js/src/jit/BaselineCompiler.cpp | 24 +- js/src/jit/BaselineCompiler.h | 2 +- js/src/jit/BaselineFrame.h | 2 +- js/src/jit/BaselineFrameInfo.h | 11 +- js/src/jit/CompileInfo.h | 61 ++- js/src/jit/IonBuilder.cpp | 30 +- js/src/jsanalyze.cpp | 36 +- js/src/jsopcode.cpp | 86 ++-- js/src/jsscript.cpp | 25 +- js/src/jsscript.h | 56 ++- js/src/jsscriptinlines.h | 3 +- .../tests/js1_8_1/regress/regress-420399.js | 2 +- js/src/vm/Interpreter.cpp | 18 +- js/src/vm/Opcodes.h | 4 +- js/src/vm/ScopeObject.cpp | 35 +- js/src/vm/ScopeObject.h | 57 +-- js/src/vm/Stack-inl.h | 3 +- js/src/vm/Stack.cpp | 4 +- js/src/vm/Xdr.h | 2 +- 28 files changed, 552 insertions(+), 463 deletions(-) diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index d5a6f96e846..57b9483280c 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -268,12 +268,6 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco if (!script) return nullptr; - // Global/eval script bindings are always empty (all names are added to the - // scope dynamically via JSOP_DEFFUN/VAR). - InternalHandle bindings(script, &script->bindings); - if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr)) - return nullptr; - // 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; @@ -293,7 +287,8 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco Maybe > pc; pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc, - (Directives *) nullptr, staticLevel, /* bodyid = */ 0); + (Directives *) nullptr, staticLevel, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!pc.ref().init(parser.tokenStream)) return nullptr; @@ -360,10 +355,12 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco pc.destroy(); pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, - &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0); + &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0, + script->bindings.numBlockScoped()); if (!pc.ref().init(parser.tokenStream)) return nullptr; JS_ASSERT(parser.pc == pc.addr()); + pn = parser.statement(); } if (!pn) { @@ -372,6 +369,11 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco } } + // Accumulate the maximum block scope depth, so that EmitTree can assert + // when emitting JSOP_GETLOCAL that the local is indeed within the fixed + // part of the stack frame. + script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth); + if (canHaveDirectives) { if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives)) return nullptr; @@ -414,6 +416,15 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco 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(script, &script->bindings); + if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr, + pc.ref().blockScopeDepth)) + return nullptr; + if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return nullptr; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 350f0c50476..bec627765b7 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -257,6 +257,34 @@ EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc) return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc)); } +// Dup the var in operand stack slot "slot". The first item on the operand +// stack is one slot past the last fixed slot. The last (most recent) item is +// slot bce->stackDepth - 1. +// +// The instruction that is written (JSOP_DUPAT) switches the depth around so +// that it is addressed from the sp instead of from the fp. This is useful when +// you don't know the size of the fixed stack segment (nfixed), as is the case +// when compiling scripts (because each statement is parsed and compiled +// separately, but they all together form one script with one fixed stack +// frame). +static bool +EmitDupAt(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned slot) +{ + JS_ASSERT(slot < unsigned(bce->stackDepth)); + // The slot's position on the operand stack, measured from the top. + unsigned slotFromTop = bce->stackDepth - 1 - slot; + if (slotFromTop >= JS_BIT(24)) { + bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + ptrdiff_t off = EmitN(cx, bce, JSOP_DUPAT, 3); + if (off < 0) + return false; + jsbytecode *pc = bce->code(off); + SET_UINT24(pc, slotFromTop); + return true; +} + /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ const char js_with_statement_str[] = "with statement"; const char js_finally_block_str[] = "finally block"; @@ -583,7 +611,6 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) return false; } - npops += blockObj.slotCount(); } } @@ -658,25 +685,6 @@ EnclosingStaticScope(BytecodeEmitter *bce) return bce->sc->asFunctionBox()->function(); } -// In a stack frame, block-scoped locals follow hoisted var-bound locals. If -// the current compilation unit is a function, add the number of "fixed slots" -// (var-bound locals) to the given block-scoped index, to arrive at its final -// position in the call frame. -// -static bool -AdjustBlockSlot(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t *slot) -{ - JS_ASSERT(*slot < bce->maxStackDepth); - if (bce->sc->isFunctionBox()) { - *slot += bce->script->bindings.numVars(); - if (*slot >= StaticBlockObject::VAR_INDEX_LIMIT) { - bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); - return false; - } - } - return true; -} - #ifdef DEBUG static bool AllLocalsAliased(StaticBlockObject &obj) @@ -691,10 +699,6 @@ AllLocalsAliased(StaticBlockObject &obj) static bool ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) { - uint32_t depthPlusFixed = blockObj->stackDepth(); - if (!AdjustBlockSlot(cx, bce, &depthPlusFixed)) - return false; - for (unsigned i = 0; i < blockObj->slotCount(); i++) { Definition *dn = blockObj->maybeDefinitionParseNode(i); @@ -706,7 +710,7 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, HandleisDefn()); if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(), - dn->frameSlot() + depthPlusFixed)) + blockObj->varToLocalIndex(dn->frameSlot()))) { return false; } @@ -730,6 +734,79 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) +{ + unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0; + unsigned localOffset = nfixedvars; + + if (bce->staticScope) { + Rooted outer(cx, bce->staticScope); + for (; outer; outer = outer->enclosingNestedScope()) { + if (outer->is()) { + StaticBlockObject &outerBlock = outer->as(); + localOffset = outerBlock.localOffset() + outerBlock.slotCount(); + break; + } + } + } + + JS_ASSERT(localOffset + blockObj->slotCount() + <= nfixedvars + 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 +// "with" object or a "block" object. "With" objects represent "with" scopes. +// Block objects represent lexical scopes, and contain named block-scoped +// bindings, for example "let" bindings or the exception in a catch block. +// Those variables may be local and thus accessible directly from the stack, or +// "aliased" (accessed by name from nested functions, or dynamically via nested +// "eval" or "with") and only accessible through the scope chain. +// +// All nested scopes are present on the "static scope chain". A nested scope +// that is a "with" scope will be present on the scope chain at run-time as +// well. A block scope may or may not have a corresponding link on the run-time +// scope chain; if no variable declared in the block scope is "aliased", then no +// scope chain node is allocated. +// +// To help debuggers, the bytecode emitter arranges to record the PC ranges +// comprehended by a nested scope, and ultimately attach them to the JSScript. +// An element in the "block scope array" specifies the PC range, and links to a +// NestedScopeObject in the object list of the script. That scope object is +// linked to the previous link in the static scope chain, if any. The static +// scope chain at any pre-retire PC can be retrieved using +// JSScript::getStaticScope(jsbytecode *pc). +// +// Block scopes store their locals in the fixed part of a stack frame, after the +// "fixed var" bindings. A fixed var binding is a "var" or legacy "const" +// binding that occurs in a function (as opposed to a script or in eval code). +// Only functions have fixed var bindings. +// +// To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a +// block scope, even if the block has no aliased locals. This allows +// DebugScopes to invalidate any association between a debugger scope object, +// which can proxy access to unaliased stack locals, and the actual live frame. +// In normal, non-debug mode, this opcode does not cause any baseline code to be +// emitted. +// +// Enter a nested scope with EnterNestedScope. It will emit +// PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of +// the scope. Leave a nested scope with LeaveNestedScope, which, for blocks, +// will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. (For "with" scopes it +// emits LEAVEWITH, of course.) Pass EnterNestedScope a fresh StmtInfoBCE +// object, and pass that same object to the corresponding LeaveNestedScope. If +// the statement is a block scope, pass STMT_BLOCK as stmtType; otherwise for +// with scopes pass STMT_WITH. +// static bool EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, StmtType stmtType) @@ -741,6 +818,8 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, case STMT_BLOCK: { Rooted blockObj(cx, &scopeObj->as()); + ComputeLocalOffset(cx, bce, blockObj); + if (!ComputeAliasedSlots(cx, bce, blockObj)) return false; @@ -760,8 +839,7 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, } uint32_t parent = BlockScopeNote::NoBlockScopeIndex; - if (bce->staticScope) { - StmtInfoBCE *stmt = bce->topScopeStmt; + if (StmtInfoBCE *stmt = bce->topScopeStmt) { for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {} parent = stmt->blockScopeIndex; } @@ -779,71 +857,6 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, return true; } -// ~ Block Scopes ~ -// -// A block scope is a region of a script with an additional set of named -// variables. Those variables may be local and thus accessible directly from -// the stack, or "aliased" and only accessible through the scope chain. -// -// A block scope may or may not have a corresponding link on the scope chain. -// If no variable declared in the scope is "aliased", then no scope chain node -// is allocated. -// -// To help debuggers, the bytecode emitter arranges to record the PC ranges -// comprehended by a block scope, and ultimately attach them to the JSScript. -// An element in the "block scope array" specifies the PC range, and links to a -// StaticBlockObject in the object list of the script. That block is linked to -// the previous block in the scope, if any. The static block chain at any -// pre-retire PC can be retrieved using JSScript::getStaticScope(jsbytecode *pc). -// -// When PUSHBLOCKSCOPE is executed, it assumes that the block's locals are -// already on the stack. Initial values of "aliased" locals are copied from the -// stack to the ClonedBlockObject, and no further access is made to the stack -// slot. -// -// Likewise after leaving a POPBLOCKSCOPE, we will need to emit code to pop the -// stack values. -// -// Finally, to assist the debugger, we also emit a DEBUGLEAVEBLOCK opcode before -// POPBLOCKSCOPE in all cases -- even if the block has no aliased locals. This -// allows DebugScopes to invalidate any association between a debugger scope -// object, which can proxy access to unaliased stack locals, and the actual live -// frame. In normal, non-debug mode, this opcode does not cause any baseline -// code to be emitted. -// -// In this function "extraSlots" indicates the number of slots that are -// "floating" on the stack above the scope's slots. This will only be nonzero -// in the case of for-let-in and for-let-of loops, where loop iterator state -// floats above the block scopes. It would be nice to fix this eventually so -// that loop iterator state gets assigned to block-scoped fp-addressable -// temporaries, instead of being addressable only via the sp. This would also -// make generators more efficient, as the loop state could be heap-allocated, so -// that the value stack would likely be empty at yield points inside for-of / -// for-in loops. -// -// Summary: Enter block scopes with EnterBlockScope. It will emit -// PUSHBLOCKSCOPE if needed. Leave them with LeaveNestedScope, which will emit -// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. Pass EnterBlockScope a fresh -// StmtInfoBCE object, and pass that same object to the corresponding -// LeaveNestedScope. Push locals before entering a scope, and pop them -// afterwards. Brush your teeth, and clean behind your ears! -// -static bool -EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, - unsigned extraSlots) -{ - Rooted blockObj(cx, &objbox->object->as()); - - // FIXME: Once bug 962599 lands, we won't care about the stack depth, so we - // won't have extraSlots and thus invocations of EnterBlockScope can become - // invocations of EnterNestedScope. - int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots); - JS_ASSERT(depth >= 0); - blockObj->setStackDepth(depth); - - return EnterNestedScope(cx, bce, stmt, objbox, STMT_BLOCK); -} - // Patches |breaks| and |continues| unless the top statement info record // represents a try-catch-finally suite. May fail if a jump offset overflows. static bool @@ -1140,17 +1153,17 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter * return false; JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc)); } else { - uint32_t depth = local - bceOfDef->script->bindings.numVars(); + JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals()); JS_ASSERT(bceOfDef->staticScope->is()); Rooted b(cx, &bceOfDef->staticScope->as()); - while (!b->containsVarAtDepth(depth)) { + while (local < b->localOffset()) { if (b->needsClone()) skippedScopes++; b = &b->enclosingNestedScope()->as(); } if (!AssignHops(bce, pn, skippedScopes, &sc)) return false; - sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local)); + sc.setSlot(b->localIndexToSlot(local)); } } @@ -2415,6 +2428,55 @@ SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off) SET_JUMP_OFFSET(bce->code(off), bce->offset() - off); } +static bool +PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n) +{ + for (unsigned i = 0; i < n; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + return true; +} + +static bool +InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce, + Handle blockObj) +{ + for (unsigned i = blockObj->slotCount(); 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)) + return false; + } else { + if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, blockObj->varToLocalIndex(i - 1), 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. + Rooted blockObj(cx, &objbox->object->as()); + if (!PushUndefinedValues(cx, bce, blockObj->slotCount() - alreadyPushed)) + return false; + + if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK)) + return false; + + if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj)) + return false; + + return true; +} + /* * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. * LLVM is deciding to inline this function which uses a lot of stack space @@ -2440,27 +2502,15 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) pn2 = pn->pn_right; JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST)); - /* - * If there are hoisted let declarations, their stack slots go under the - * discriminant's value so push their slots now and enter the block later. - */ - Rooted blockObj(cx, nullptr); - if (pn2->isKind(PNK_LEXICALSCOPE)) { - blockObj = &pn2->pn_objbox->object->as(); - for (uint32_t i = 0; i < blockObj->slotCount(); ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - } - /* 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, 1)) + if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 0)) return false; + stmtInfo.type = STMT_SWITCH; stmtInfo.update = top = bce->offset(); /* Advance pn2 to refer to the switch case list. */ @@ -2746,7 +2796,6 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount()); } else { if (!PopStatementBCE(cx, bce)) return false; @@ -3277,11 +3326,8 @@ EmitGroupAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, for (pn = lhs->pn_head; pn; pn = pn->pn_next, ++i) { /* MaybeEmitGroupAssignment requires lhs->pn_count <= rhs->pn_count. */ JS_ASSERT(i < limit); - uint32_t slot = i; - if (!AdjustBlockSlot(cx, bce, &slot)) - return false; - if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce)) + if (!EmitDupAt(cx, bce, i)) return false; if (pn->isKind(PNK_ELISION)) { @@ -4036,7 +4082,6 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (ParseNode *pn2 = pn->pn_kid2) { // The emitted code for a catch block looks like: // - // undefined... as many as there are locals in the catch block // [pushblockscope] only if any local aliased // exception // if there is a catchguard: @@ -4047,14 +4092,12 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) // ifne POST // debugleaveblock // [popblockscope] only if any local aliased - // popnv leave exception on top // throwing pop exception to cx->exception // goto // POST: pop // < catch block contents > // debugleaveblock // [popblockscope] only if any local aliased - // popn // goto non-local; finally applies // // If there's no catch block without a catchguard, the last scopeChain and does not * otherwise touch the stack, evaluation of the let-var initializers must leave @@ -4272,7 +4317,6 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) JS_ASSERT(varList->isArity(PN_LIST)); ParseNode *letBody = pnLet->pn_right; JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE)); - Rooted blockObj(cx, &letBody->pn_objbox->object->as()); int letHeadDepth = bce->stackDepth; @@ -4281,14 +4325,8 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */ uint32_t alreadyPushed = bce->stackDepth - letHeadDepth; - uint32_t blockObjCount = blockObj->slotCount(); - for (uint32_t i = alreadyPushed; i < blockObjCount; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - StmtInfoBCE stmtInfo(cx); - if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0)) + if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed)) return false; if (!EmitTree(cx, bce, letBody->pn_expr)) @@ -4297,10 +4335,6 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - JSOp leaveOp = letBody->getOp(); - JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV); - EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount()); - return true; } @@ -4312,19 +4346,9 @@ MOZ_NEVER_INLINE static bool EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); - JS_ASSERT(pn->getOp() == JSOP_POPN); StmtInfoBCE stmtInfo(cx); - ObjectBox *objbox = pn->pn_objbox; - StaticBlockObject &blockObj = objbox->object->as(); - size_t slots = blockObj.slotCount(); - - for (size_t n = 0; n < slots; ++n) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - - if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0)) + if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0)) return false; if (!EmitTree(cx, bce, pn->pn_expr)) @@ -4333,8 +4357,6 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - EMIT_UINT16_IMM_OP(JSOP_POPN, slots); - return true; } @@ -4363,18 +4385,6 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); JS_ASSERT_IF(letDecl, pn1->isLet()); - Rooted - blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr); - uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0; - - // For-of loops run with two values on the stack: the iterator and the - // current result object. If the loop also has a lexical block, those - // lexicals are deeper on the stack than the iterator. - for (uint32_t i = 0; i < blockObjCount; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - // 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) { @@ -4386,6 +4396,9 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bce->emittingForInit = false; } + // 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; @@ -4408,7 +4421,7 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t // Enter the block before the loop body, after evaluating the obj. StmtInfoBCE letStmt(cx); if (letDecl) { - if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2)) + if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) return false; } @@ -4501,8 +4514,8 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t return false; } - // Pop result, iter, and slots from the lexical block (if any). - EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2); + // Pop the result and the iter. + EMIT_UINT16_IMM_OP(JSOP_POPN, 2); return true; } @@ -4517,38 +4530,6 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); JS_ASSERT_IF(letDecl, pn1->isLet()); - Rooted - blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr); - uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0; - - if (letDecl) { - /* - * The let's slot(s) will be under the iterator, but the block must not - * be entered until after evaluating the rhs. So, we reserve space for - * the block scope now, and only push the block onto the scope chain - * later. Thus, a for-let-in loop looks like: - * - * push x N - * eval rhs - * iter - * pushblockscope (if needed) - * goto - * ... loop body - * ifne - * debugleaveblock - * popblockscope (if needed) - * enditer - * popn(N) - * - * Note that pushblockscope and popblockscope only get emitted if some - * of the variables in the block are captured. - */ - for (uint32_t i = 0; i < blockObjCount; ++i) { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - } - /* * 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 @@ -4580,7 +4561,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t /* Enter the block before the loop body, after evaluating the obj. */ StmtInfoBCE letStmt(cx); if (letDecl) { - if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1)) + if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) return false; } @@ -4662,7 +4643,6 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t if (letDecl) { if (!LeaveNestedScope(cx, bce, &letStmt)) return false; - EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); } return true; @@ -4954,7 +4934,8 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) BindingIter bi(bce->script); while (bi->name() != fun->atom()) bi++; - JS_ASSERT(bi->kind() == VARIABLE || bi->kind() == CONSTANT || bi->kind() == ARGUMENT); + JS_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT || + bi->kind() == Binding::ARGUMENT); JS_ASSERT(bi.frameIndex() < JS_BIT(20)); #endif pn->pn_index = index; @@ -6523,10 +6504,7 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) */ if (!EmitTree(cx, bce, pn->pn_kid)) return false; - uint32_t slot = bce->arrayCompDepth; - if (!AdjustBlockSlot(cx, bce, &slot)) - return false; - if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce)) + if (!EmitDupAt(cx, bce, bce->arrayCompDepth)) return false; if (Emit1(cx, bce, JSOP_ARRAYPUSH) < 0) return false; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index f93dd7a3226..725b3ce15dd 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -421,8 +421,6 @@ class FullParseHandler inline bool addCatchBlock(ParseNode *catchList, ParseNode *letBlock, ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody); - inline void setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr); - inline void setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *pn); inline ParseNode *newFunctionDefinition(); void setFunctionBody(ParseNode *pn, ParseNode *kid) { @@ -435,7 +433,10 @@ class FullParseHandler void addFunctionArgument(ParseNode *pn, ParseNode *argpn) { pn->pn_body->append(argpn); } + inline ParseNode *newLexicalScope(ObjectBox *blockbox); + inline void setLexicalScopeBody(ParseNode *block, ParseNode *body); + bool isOperationWithoutParens(ParseNode *pn, ParseNodeKind kind) { return pn->isKind(kind) && !pn->isInParens(); } @@ -597,15 +598,6 @@ FullParseHandler::addCatchBlock(ParseNode *catchList, ParseNode *letBlock, return true; } -inline void -FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr) -{ - JS_ASSERT(block->isOp(JSOP_POPN)); - if (leaveBlockExpr) - block->setOp(JSOP_POPNV); - block->pn_expr = kid; -} - inline void FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue) { @@ -634,13 +626,18 @@ FullParseHandler::newLexicalScope(ObjectBox *blockbox) if (!pn) return nullptr; - pn->setOp(JSOP_POPN); pn->pn_objbox = blockbox; pn->pn_cookie.makeFree(); pn->pn_dflags = 0; return pn; } +inline void +FullParseHandler::setLexicalScopeBody(ParseNode *block, ParseNode *kid) +{ + block->pn_expr = kid; +} + inline bool FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op) { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 45df351ee5f..d76c868a971 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -404,8 +404,7 @@ enum ParseNodeKind * PNK_NULL, * PNK_THIS * - * PNK_LEXICALSCOPE name pn_op: JSOP_POPN or JSOP_POPNV - * pn_objbox: block object in ObjectBox holder + * PNK_LEXICALSCOPE name pn_objbox: block object in ObjectBox holder * pn_expr: block body * PNK_ARRAYCOMP list pn_count: 1 * pn_head: list of 1 element, which is block diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 8fc7b4552ab..ec7032de31e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -272,16 +272,16 @@ AppendPackedBindings(const ParseContext *pc, const DeclVector &vec Definition *dn = vec[i]; PropertyName *name = dn->name(); - BindingKind kind; + Binding::Kind kind; switch (dn->kind()) { case Definition::VAR: - kind = VARIABLE; + kind = Binding::VARIABLE; break; case Definition::CONST: - kind = CONSTANT; + kind = Binding::CONSTANT; break; case Definition::ARG: - kind = ARGUMENT; + kind = Binding::ARGUMENT; break; default: MOZ_ASSUME_UNREACHABLE("unexpected dn->kind"); @@ -329,7 +329,7 @@ ParseContext::generateFunctionBindings(ExclusiveContext *cx, Token AppendPackedBindings(this, vars_, packedBindings + args_.length()); return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(), - packedBindings); + packedBindings, blockScopeDepth); } template @@ -615,7 +615,8 @@ Parser::parse(JSObject *chain) GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption); ParseContext globalpc(this, /* parent = */ nullptr, ParseHandler::null(), &globalsc, /* newDirectives = */ nullptr, - /* staticLevel = */ 0, /* bodyid = */ 0); + /* staticLevel = */ 0, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!globalpc.init(tokenStream)) return null(); @@ -877,7 +878,8 @@ Parser::standaloneFunctionBody(HandleFunction fun, const AutoN handler.setFunctionBox(fn, funbox); ParseContext funpc(this, pc, fn, funbox, newDirectives, - /* staticLevel = */ 0, /* bodyid = */ 0); + /* staticLevel = */ 0, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return null(); @@ -2125,7 +2127,8 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, ParseContext funpc(parser, outerpc, SyntaxParseHandler::null(), funbox, newDirectives, outerpc->staticLevel + 1, - outerpc->blockidGen); + outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return false; @@ -2160,7 +2163,8 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, // Continue doing a full parse for this inner function. ParseContext funpc(this, pc, pn, funbox, newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return false; @@ -2199,7 +2203,8 @@ Parser::functionArgsAndBody(Node pn, HandleFunction fun, // Initialize early for possible flags mutation via destructuringExpr. ParseContext funpc(this, pc, handler.null(), funbox, newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return false; @@ -2234,7 +2239,8 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st Directives newDirectives = directives; ParseContext funpc(this, /* parent = */ nullptr, pn, funbox, - &newDirectives, staticLevel, /* bodyid = */ 0); + &newDirectives, staticLevel, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); if (!funpc.init(tokenStream)) return null(); @@ -2688,7 +2694,7 @@ Parser::bindLet(BindData *data, Rooted blockObj(cx, data->let.blockObj); unsigned index = blockObj->slotCount(); - if (index >= StaticBlockObject::VAR_INDEX_LIMIT) { + if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) { parser->report(ParseError, false, pn, data->let.overflow); return false; } @@ -2769,6 +2775,33 @@ struct PopLetDecl { } }; +// We compute the maximum block scope depth, in slots, of a compilation unit at +// parse-time. Each nested statement has a field indicating the maximum block +// scope depth that is nested inside it. When we leave a nested statement, we +// add the number of slots in the statement to the nested depth, and use that to +// update the maximum block scope depth of the outer statement or parse +// context. In the end, pc->blockScopeDepth will indicate the number of slots +// to reserve in the fixed part of a stack frame. +// +template +static void +AccumulateBlockScopeDepth(ParseContext *pc) +{ + uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth; + StmtInfoPC *outer = pc->topStmt->down; + + if (pc->topStmt->isBlockScope) + innerDepth += pc->topStmt->staticScope->template as().slotCount(); + + if (outer) { + if (outer->innerBlockScopeDepth < innerDepth) + outer->innerBlockScopeDepth = innerDepth; + } else { + if (pc->blockScopeDepth < innerDepth) + pc->blockScopeDepth = innerDepth; + } +} + template static void PopStatementPC(TokenStream &ts, ParseContext *pc) @@ -2776,6 +2809,7 @@ PopStatementPC(TokenStream &ts, ParseContext *pc) RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope); JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope); + AccumulateBlockScopeDepth(pc); FinishPopStatement(pc); if (scopeObj) { @@ -2823,7 +2857,7 @@ LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::Stmt JS_ASSERT(shape->hasShortID()); if (slotp) - *slotp = blockObj.stackDepth() + shape->shortid(); + *slotp = shape->shortid(); return stmt; } } @@ -3334,7 +3368,7 @@ Parser::letBlock(LetContext letContext) if (!expr) return null(); } - handler.setLeaveBlockResult(block, expr, letContext != LetStatement); + handler.setLexicalScopeBody(block, expr); PopStatementPC(tokenStream, pc); handler.setEndPosition(pnlet, pos().end); @@ -3612,7 +3646,6 @@ Parser::letDeclaration() if (!pn1) return null(); - pn1->setOp(JSOP_POPN); pn1->pn_pos = pc->blockNode->pn_pos; pn1->pn_objbox = blockbox; pn1->pn_expr = pc->blockNode; @@ -3648,8 +3681,6 @@ Parser::letStatement() if (tokenStream.peekToken() == TOK_LP) { pn = letBlock(LetStatement); JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); - JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_POPNV, - pn->pn_expr->isOp(JSOP_POPN)); } else { pn = letDeclaration(); } @@ -6002,6 +6033,31 @@ CompExprTransplanter::transplant(ParseNode *pn) return true; } +// Parsing JS1.7-style comprehensions is terrible: we parse the head expression +// as if it's part of a comma expression, then when we see the "for" we +// transplant the parsed expression into the inside of a constructed +// for-of/for-in/for-each tail. Transplanting an already-parsed expression is +// tricky, but the CompExprTransplanter handles most of that. +// +// The one remaining thing to patch up is the block scope depth. We need to +// compute the maximum block scope depth of a function, so we know how much +// space to reserve in the fixed part of a stack frame. Normally this is done +// whenever we leave a statement, via AccumulateBlockScopeDepth. However if the +// head has a let expression, we need to re-assign that depth to the tail of the +// comprehension. +// +// Thing is, we don't actually know what that depth is, because the only +// information we keep is the maximum nested depth within a statement, so we +// just conservatively propagate the maximum nested depth from the top statement +// to the comprehension tail. +// +template +static unsigned +ComprehensionHeadBlockScopeDepth(ParseContext *pc) +{ + return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth; +} + /* * Starting from a |for| keyword after the first array initialiser element or * an expression in an open parenthesis, parse the tail of the comprehension @@ -6015,7 +6071,8 @@ template <> ParseNode * Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, ParseContext *outerpc, - ParseNodeKind kind, JSOp op) + ParseNodeKind kind, JSOp op, + unsigned innerBlockScopeDepth) { /* * If we saw any inner functions while processing the generator expression @@ -6240,6 +6297,7 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bo pn2->pn_kid = kid; *pnp = pn2; + pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth; PopStatementPC(tokenStream, pc); return pn; } @@ -6263,7 +6321,8 @@ Parser::arrayInitializerComprehensionTail(ParseNode *pn) *pn->pn_tail = nullptr; ParseNode *pntop = comprehensionTail(pnexp, pn->pn_blockid, false, nullptr, - PNK_ARRAYPUSH, JSOP_ARRAYPUSH); + PNK_ARRAYPUSH, JSOP_ARRAYPUSH, + ComprehensionHeadBlockScopeDepth(pc)); if (!pntop) return false; pn->append(pntop); @@ -6333,7 +6392,8 @@ Parser::generatorExpr(ParseNode *kid) ParseContext genpc(this, outerpc, genfn, genFunbox, /* newDirectives = */ nullptr, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!genpc.init(tokenStream)) return null(); @@ -6351,7 +6411,9 @@ Parser::generatorExpr(ParseNode *kid) genFunbox->inGenexpLambda = true; genfn->pn_blockid = genpc.bodyid; - ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc); + ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc, + PNK_SEMI, JSOP_NOP, + ComprehensionHeadBlockScopeDepth(outerpc)); if (!body) return null(); JS_ASSERT(!genfn->pn_body); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index b4249354808..532df37fd49 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -28,8 +28,9 @@ struct StmtInfoPC : public StmtInfoBase { StmtInfoPC *downScope; /* next enclosing lexical scope */ uint32_t blockid; /* for simplified dominance computation */ + uint32_t innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */ - StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx) {} + StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {} }; typedef HashSet FuncStmtSet; @@ -118,6 +119,7 @@ struct ParseContext : public GenericParseContext bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == StarGenerator; } + 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: @@ -135,11 +137,6 @@ struct ParseContext : public GenericParseContext return args_.length(); } - uint32_t numVars() const { - JS_ASSERT(sc->isFunctionBox()); - return vars_.length(); - } - /* * This function adds a definition to the lexical scope represented by this * ParseContext. @@ -243,7 +240,7 @@ struct ParseContext : public GenericParseContext ParseContext(Parser *prs, GenericParseContext *parent, Node maybeFunction, SharedContext *sc, Directives *newDirectives, - unsigned staticLevel, uint32_t bodyid) + unsigned staticLevel, uint32_t bodyid, uint32_t blockScopeDepth) : GenericParseContext(parent, sc), bodyid(0), // initialized in init() blockidGen(bodyid), // used to set |bodyid| and subsequently incremented in init() @@ -253,6 +250,7 @@ struct ParseContext : public GenericParseContext maybeFunction(maybeFunction), staticLevel(staticLevel), lastYieldOffset(NoYieldOffset), + blockScopeDepth(blockScopeDepth), blockNode(ParseHandler::null()), decls_(prs->context, prs->alloc), args_(prs->context), @@ -547,7 +545,8 @@ class Parser : private AutoGCRooter, public StrictModeGetter Node condition(); Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp, ParseContext *outerpc, - ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP); + ParseNodeKind kind, JSOp op, + unsigned innerBlockScopeDepth); bool arrayInitializerComprehensionTail(Node pn); Node generatorExpr(Node kid); bool argumentList(Node listNode, bool *isSpread); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 1e5c05fba69..760d3c318d1 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -157,14 +157,15 @@ class SyntaxParseHandler bool addCatchBlock(Node catchList, Node letBlock, Node catchName, Node catchGuard, Node catchBody) { return true; } - void setLeaveBlockResult(Node block, Node kid, bool leaveBlockExpr) {} - void setLastFunctionArgumentDefault(Node funcpn, Node pn) {} Node newFunctionDefinition() { return NodeGeneric; } void setFunctionBody(Node pn, Node kid) {} void setFunctionBox(Node pn, FunctionBox *funbox) {} void addFunctionArgument(Node pn, Node argpn) {} + Node newLexicalScope(ObjectBox *blockbox) { return NodeGeneric; } + void setLexicalScopeBody(Node block, Node body) {} + bool isOperationWithoutParens(Node pn, ParseNodeKind kind) { // It is OK to return false here, callers should only use this method // for reporting strict option warnings and parsing code which the diff --git a/js/src/jit-test/tests/basic/testBug579647.js b/js/src/jit-test/tests/basic/testBug579647.js index 027f643a96f..4a4d41164b3 100644 --- a/js/src/jit-test/tests/basic/testBug579647.js +++ b/js/src/jit-test/tests/basic/testBug579647.js @@ -1,4 +1,4 @@ -expected = "TypeError: NaN is not a function"; +expected = "TypeError: a is not a function"; actual = ""; try { @@ -10,4 +10,4 @@ try { actual = '' + e; } -assertEq(expected, actual); +assertEq(actual, expected); diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index 81e129c20c9..d6249f7de7a 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -5269,7 +5269,8 @@ ParseFunction(ModuleCompiler &m, ParseNode **fnOut) Directives newDirectives = directives; AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives, - outerpc->staticLevel + 1, outerpc->blockidGen); + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); if (!funpc.init(m.parser().tokenStream)) return false; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index ba64591f700..4d18eac5f21 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -854,10 +854,16 @@ BaselineCompiler::emit_JSOP_POPN() } bool -BaselineCompiler::emit_JSOP_POPNV() +BaselineCompiler::emit_JSOP_DUPAT() { - frame.popRegsAndSync(1); - frame.popn(GET_UINT16(pc)); + frame.syncStack(0); + + // DUPAT takes a value on the stack and re-pushes it on top. It's like + // GETLOCAL but it addresses from the top of the stack instead of from the + // stack frame. + + int depth = -(GET_UINT24(pc) + 1); + masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); frame.push(R0); return true; } @@ -2290,17 +2296,7 @@ BaselineCompiler::emit_JSOP_INITELEM_SETTER() bool BaselineCompiler::emit_JSOP_GETLOCAL() { - uint32_t local = GET_LOCALNO(pc); - - if (local >= frame.nlocals()) { - // Destructuring assignments may use GETLOCAL to access stack values. - frame.syncStack(0); - masm.loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)), R0); - frame.push(R0); - return true; - } - - frame.pushLocal(local); + frame.pushLocal(GET_LOCALNO(pc)); return true; } diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 35d85c02646..3c7300b1bb1 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -26,7 +26,7 @@ namespace jit { _(JSOP_LABEL) \ _(JSOP_POP) \ _(JSOP_POPN) \ - _(JSOP_POPNV) \ + _(JSOP_DUPAT) \ _(JSOP_ENTERWITH) \ _(JSOP_LEAVEWITH) \ _(JSOP_DUP) \ diff --git a/js/src/jit/BaselineFrame.h b/js/src/jit/BaselineFrame.h index ce4ad7f41fd..8d0601b645e 100644 --- a/js/src/jit/BaselineFrame.h +++ b/js/src/jit/BaselineFrame.h @@ -152,8 +152,8 @@ class BaselineFrame } Value &unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const { + JS_ASSERT(i < script()->nfixedvars()); JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i)); - JS_ASSERT(i < script()->nfixed()); return *valueSlot(i); } diff --git a/js/src/jit/BaselineFrameInfo.h b/js/src/jit/BaselineFrameInfo.h index a237f9d97c3..4454f4de242 100644 --- a/js/src/jit/BaselineFrameInfo.h +++ b/js/src/jit/BaselineFrameInfo.h @@ -243,6 +243,7 @@ class FrameInfo sv->setRegister(val, knownType); } inline void pushLocal(uint32_t local) { + JS_ASSERT(local < nlocals()); StackValue *sv = rawPush(); sv->setLocalSlot(local); } @@ -260,15 +261,7 @@ class FrameInfo sv->setStack(); } inline Address addressOfLocal(size_t local) const { -#ifdef DEBUG - if (local >= nlocals()) { - // GETLOCAL and SETLOCAL can be used to access stack values. This is - // fine, as long as they are synced. - size_t slot = local - nlocals(); - JS_ASSERT(slot < stackDepth()); - JS_ASSERT(stack[slot].kind() == StackValue::Stack); - } -#endif + JS_ASSERT(local < nlocals()); return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)); } Address addressOfArg(size_t arg) const { diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h index 76917670eb3..82f8b9cd169 100644 --- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -10,6 +10,7 @@ #include "jsfun.h" #include "jit/Registers.h" +#include "vm/ScopeObject.h" namespace js { namespace jit { @@ -60,20 +61,24 @@ 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(); nlocals_ = script->nfixed(); nstack_ = script->nslots() - script->nfixed(); nslots_ = nimplicit_ + nargs_ + nlocals_ + nstack_; } CompileInfo(unsigned nlocals, ExecutionMode executionMode) - : script_(nullptr), fun_(nullptr), osrPc_(nullptr), constructing_(false), - executionMode_(executionMode), scriptNeedsArgsObj_(false) + : script_(nullptr), fun_(nullptr), osrPc_(nullptr), osrStaticScope_(nullptr), + constructing_(false), executionMode_(executionMode), scriptNeedsArgsObj_(false) { nimplicit_ = 0; nargs_ = 0; + nfixedvars_ = 0; nlocals_ = nlocals; nstack_ = 1; /* For FunctionCompiler::pushPhiInput/popPhiOutput */ nslots_ = nlocals_ + nstack_; @@ -91,6 +96,9 @@ class CompileInfo jsbytecode *osrPc() { return osrPc_; } + NestedScopeObject *osrStaticScope() const { + return osrStaticScope_; + } bool hasOsrAt(jsbytecode *pc) { JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); @@ -155,7 +163,13 @@ class CompileInfo unsigned nargs() const { return nargs_; } - // Number of slots needed for local variables. + // 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 all local variables. This includes "fixed + // vars" (see above) and also block-scoped locals. unsigned nlocals() const { return nlocals_; } @@ -223,21 +237,33 @@ class CompileInfo return nimplicit() + nargs() + nlocals(); } - bool isSlotAliased(uint32_t index) const { + bool isSlotAliased(uint32_t index, NestedScopeObject *staticScope) const { if (funMaybeLazy() && index == thisSlot()) return false; uint32_t arg = index - firstArgSlot(); - if (arg < nargs()) { - if (script()->formalIsAliased(arg)) - return true; - return false; - } + if (arg < nargs()) + return script()->formalIsAliased(arg); - uint32_t var = index - firstLocalSlot(); - if (var < nlocals()) { - if (script()->varIsAliased(var)) - return true; + uint32_t local = index - firstLocalSlot(); + if (local < nlocals()) { + // First, check if this local is a var. + if (local < nfixedvars()) + return script()->varIsAliased(local); + + // Otherwise, it might be part of a block scope. + for (; staticScope; staticScope = staticScope->enclosingNestedScope()) { + if (!staticScope->is()) + continue; + StaticBlockObject &blockObj = staticScope->as(); + if (blockObj.localOffset() < local) { + if (local - blockObj.localOffset() < blockObj.slotCount()) + return blockObj.isAliased(local - blockObj.localOffset()); + return false; + } + } + + // In this static scope, this var is dead. return false; } @@ -245,6 +271,13 @@ class CompileInfo return false; } + bool isSlotAliasedAtEntry(uint32_t index) const { + return isSlotAliased(index, nullptr); + } + bool isSlotAliasedAtOsr(uint32_t index) const { + return isSlotAliased(index, osrStaticScope()); + } + bool hasArguments() const { return script()->argumentsHasVarBinding(); } @@ -269,12 +302,14 @@ class CompileInfo private: unsigned nimplicit_; unsigned nargs_; + unsigned nfixedvars_; unsigned nlocals_; unsigned nstack_; unsigned nslots_; JSScript *script_; JSFunction *fun_; jsbytecode *osrPc_; + NestedScopeObject *osrStaticScope_; bool constructing_; ExecutionMode executionMode_; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 4b2d9e140bc..5e65fbd723f 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -90,12 +90,18 @@ jit::NewBaselineFrameInspector(TempAllocator *temp, BaselineFrame *frame) if (!inspector->varTypes.reserve(frame->script()->nfixed())) return nullptr; - for (size_t i = 0; i < frame->script()->nfixed(); i++) { + for (size_t i = 0; i < frame->script()->nfixedvars(); i++) { if (script->varIsAliased(i)) inspector->varTypes.infallibleAppend(types::Type::UndefinedType()); else inspector->varTypes.infallibleAppend(types::GetValueType(frame->unaliasedVar(i))); } + for (size_t i = frame->script()->nfixedvars(); i < frame->script()->nfixed(); i++) { + // FIXME: If this slot corresponds to a scope that is active at this PC, + // and the slot is unaliased, we should initialize the type from the + // slot value, as above. + inspector->varTypes.infallibleAppend(types::Type::UndefinedType()); + } return inspector; } @@ -1153,11 +1159,10 @@ IonBuilder::maybeAddOsrTypeBarriers() headerPhi++; for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++, headerPhi++) { - // Aliased slots are never accessed, since they need to go through // the callobject. The typebarriers are added there and can be - // discared here. - if (info().isSlotAliased(i)) + // discarded here. + if (info().isSlotAliasedAtOsr(i)) continue; MInstruction *def = osrBlock->getSlot(i)->toInstruction(); @@ -1302,7 +1307,7 @@ IonBuilder::traverseBytecode() switch (op) { case JSOP_POP: case JSOP_POPN: - case JSOP_POPNV: + case JSOP_DUPAT: case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: @@ -1552,14 +1557,9 @@ IonBuilder::inspectOpcode(JSOp op) current->pop(); return true; - case JSOP_POPNV: - { - MDefinition *mins = current->pop(); - for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) - current->pop(); - current->push(mins); + case JSOP_DUPAT: + current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc)); return true; - } case JSOP_NEWINIT: if (GET_UINT8(pc) == JSProto_Array) @@ -5837,11 +5837,11 @@ IonBuilder::newOsrPreheader(MBasicBlock *predecessor, jsbytecode *loopEntry) for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++) { MDefinition *existing = current->getSlot(i); MDefinition *def = osrBlock->getSlot(i); - JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliased(i), def->type() == MIRType_Value); + JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliasedAtOsr(i), def->type() == MIRType_Value); // Aliased slots are never accessed, since they need to go through // the callobject. No need to type them here. - if (info().isSlotAliased(i)) + if (info().isSlotAliasedAtOsr(i)) continue; def->setResultType(existing->type()); @@ -5881,7 +5881,7 @@ IonBuilder::newPendingLoopHeader(MBasicBlock *predecessor, jsbytecode *pc, bool // The value of aliased args and slots are in the callobject. So we can't // the value from the baseline frame. - if (info().isSlotAliased(i)) + if (info().isSlotAliasedAtOsr(i)) continue; // Don't bother with expression stack values. The stack should be diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 4ebc8953e72..2efe444bc99 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -766,7 +766,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) RootedScript script(cx, script_); for (BindingIter bi(script); bi; bi++) { - if (bi->kind() == ARGUMENT) + if (bi->kind() == Binding::ARGUMENT) escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased(); else escapedSlots[LocalSlot(script_, bi.frameIndex())] = allVarsAliased || bi->aliased(); @@ -928,32 +928,11 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) break; } - case JSOP_GETLOCAL: { - /* - * Watch for uses of variables not known to be defined, and mark - * them as having possible uses before definitions. Ignore GETLOCAL - * followed by a POP, these are generated for, e.g. 'var x;' - */ - jsbytecode *next = pc + JSOP_GETLOCAL_LENGTH; - if (JSOp(*next) != JSOP_POP || jumpTarget(next)) { - uint32_t local = GET_LOCALNO(pc); - if (local >= script_->nfixed()) { - localsAliasStack_ = true; - break; - } - } - break; - } - + case JSOP_GETLOCAL: case JSOP_CALLLOCAL: - case JSOP_SETLOCAL: { - uint32_t local = GET_LOCALNO(pc); - if (local >= script_->nfixed()) { - localsAliasStack_ = true; - break; - } + case JSOP_SETLOCAL: + JS_ASSERT(GET_LOCALNO(pc) < script_->nfixed()); break; - } case JSOP_PUSHBLOCKSCOPE: localsAliasStack_ = true; @@ -1822,6 +1801,13 @@ ScriptAnalysis::analyzeSSA(JSContext *cx) stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1]; break; + case JSOP_DUPAT: { + unsigned pickedDepth = GET_UINT24 (pc); + JS_ASSERT(pickedDepth < stackDepth - 1); + stack[stackDepth - 1].v = stack[stackDepth - 2 - pickedDepth].v; + break; + } + case JSOP_SWAP: /* Swap is like pick 1. */ case JSOP_PICK: { diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index fc7a00a3aa1..89622331dc7 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -117,8 +117,6 @@ js::StackUses(JSScript *script, jsbytecode *pc) switch (op) { case JSOP_POPN: return GET_UINT16(pc); - case JSOP_POPNV: - return GET_UINT16(pc) + 1; default: /* stack: fun, this, [argc arguments] */ JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || @@ -468,6 +466,16 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint } break; + case JSOP_DUPAT: { + JS_ASSERT(ndefs == 1); + jsbytecode *pc = script_->offsetToPC(offset); + unsigned n = GET_UINT24(pc); + JS_ASSERT(n < stackDepth); + if (offsetStack) + offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; + break; + } + case JSOP_SWAP: JS_ASSERT(ndefs == 2); if (offsetStack) { @@ -832,9 +840,9 @@ ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes) if (!JSVAL_IS_PRIMITIVE(v)) { JSObject *obj = JSVAL_TO_OBJECT(v); - if (obj->is()) { + if (obj->is()) { char *source = JS_sprintf_append(nullptr, "depth %d {", - obj->as().stackDepth()); + obj->as().localOffset()); if (!source) return false; @@ -1025,7 +1033,8 @@ js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc, goto print_int; case JOF_UINT24: - JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY); + JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY || + op == JSOP_DUPAT); i = (int)GET_UINT24(pc); goto print_int; @@ -1436,9 +1445,8 @@ struct ExpressionDecompiler bool init(); bool decompilePCForStackOperand(jsbytecode *pc, int i); bool decompilePC(jsbytecode *pc); - JSAtom *getVar(uint32_t slot); + JSAtom *getFixed(uint32_t slot, jsbytecode *pc); JSAtom *getArg(unsigned slot); - JSAtom *findLetVar(jsbytecode *pc, unsigned depth); JSAtom *loadAtom(jsbytecode *pc); bool quote(JSString *s, uint32_t quote); bool write(const char *s); @@ -1504,18 +1512,9 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc) case JSOP_GETLOCAL: case JSOP_CALLLOCAL: { uint32_t i = GET_LOCALNO(pc); - JSAtom *atom; - if (i >= script->nfixed()) { - i -= script->nfixed(); - JS_ASSERT(i < unsigned(parser.stackDepthAtPC(pc))); - atom = findLetVar(pc, i); - if (!atom) - return decompilePCForStackOperand(pc, i); // Destructing temporary - } else { - atom = getVar(i); - } - JS_ASSERT(atom); - return write(atom); + if (JSAtom *atom = getFixed(i, pc)) + return write(atom); + return write("(intermediate value)"); } case JSOP_CALLALIASEDVAR: case JSOP_GETALIASEDVAR: { @@ -1640,26 +1639,6 @@ ExpressionDecompiler::loadAtom(jsbytecode *pc) return script->getAtom(GET_UINT32_INDEX(pc)); } -JSAtom * -ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth) -{ - for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) { - if (!chain->is()) - continue; - StaticBlockObject &block = chain->as(); - uint32_t blockDepth = block.stackDepth(); - uint32_t blockCount = block.slotCount(); - if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) { - for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { - const Shape &shape = r.front(); - if (shape.shortid() == int(depth - blockDepth)) - return JSID_TO_ATOM(shape.propid()); - } - } - } - return nullptr; -} - JSAtom * ExpressionDecompiler::getArg(unsigned slot) { @@ -1669,12 +1648,31 @@ ExpressionDecompiler::getArg(unsigned slot) } JSAtom * -ExpressionDecompiler::getVar(uint32_t slot) +ExpressionDecompiler::getFixed(uint32_t slot, jsbytecode *pc) { - JS_ASSERT(fun); - slot += fun->nargs(); - JS_ASSERT(slot < script->bindings.count()); - return (*localNames)[slot].name(); + if (slot < script->nfixedvars()) { + JS_ASSERT(fun); + slot += fun->nargs(); + JS_ASSERT(slot < script->bindings.count()); + return (*localNames)[slot].name(); + } + for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) { + if (!chain->is()) + continue; + StaticBlockObject &block = chain->as(); + if (slot < block.localOffset()) + continue; + slot -= block.localOffset(); + if (slot >= block.slotCount()) + return nullptr; + for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { + const Shape &shape = r.front(); + if (shape.shortid() == int(slot)) + return JSID_TO_ATOM(shape.propid()); + } + break; + } + return nullptr; } bool diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 0bd5ac8cfb8..2c51a84b0b3 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -72,18 +72,21 @@ Bindings::argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle binding bool Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, unsigned numArgs, uint32_t numVars, - Binding *bindingArray) + Binding *bindingArray, uint32_t numBlockScoped) { 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(UINT32_MAX - numArgs >= numVars); + JS_ASSERT(numBlockScoped <= LOCALNO_LIMIT); + JS_ASSERT(numVars <= LOCALNO_LIMIT - numBlockScoped); + JS_ASSERT(UINT32_MAX - numArgs >= numVars + numBlockScoped); self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT; self->numArgs_ = numArgs; self->numVars_ = numVars; + 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 @@ -142,7 +145,7 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE | - (bi->kind() == CONSTANT ? JSPROP_READONLY : 0); + (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0); StackShape child(base, NameToId(bi->name()), slot, attrs, 0, 0); shape = cx->compartment()->propertyTree.getChild(cx, shape, child); @@ -186,7 +189,8 @@ Bindings::clone(JSContext *cx, InternalBindingsHandle self, * 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())) + if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray(), + src.numBlockScoped())) return false; self->switchToScriptStorage(dstPackedBindings); return true; @@ -201,7 +205,7 @@ GCMethods::initial() template static bool XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars, - HandleScript script) + HandleScript script, unsigned numBlockScoped) { JSContext *cx = xdr->cx(); @@ -239,14 +243,15 @@ XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, ui return false; PropertyName *name = atoms[i].toString()->asAtom().asPropertyName(); - BindingKind kind = BindingKind(u8 >> 1); + 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)) + if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray, + numBlockScoped)) return false; } @@ -481,16 +486,20 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc /* XDR arguments and vars. */ uint16_t nargs = 0; + uint16_t nblocklocals = 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(); nvars = script->bindings.numVars(); } if (!xdr->codeUint16(&nargs)) return false; + if (!xdr->codeUint16(&nblocklocals)) + return false; if (!xdr->codeUint32(&nvars)) return false; @@ -630,7 +639,7 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc /* JSScript::partiallyInit assumes script->bindings is fully initialized. */ LifoAllocScope las(&cx->tempLifoAlloc()); - if (!XDRScriptBindings(xdr, las, nargs, nvars, script)) + if (!XDRScriptBindings(xdr, las, nargs, nvars, script, nblocklocals)) return false; if (mode == XDR_DECODE) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 2ffa3db20ed..55b0a83eb4f 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -127,19 +127,10 @@ struct BlockScopeArray { uint32_t length; // Count of indexed try notes. }; -/* - * A "binding" is a formal, 'var' or 'const' declaration. A function's lexical - * scope is composed of these three kinds of bindings. - */ - -enum BindingKind { ARGUMENT, VARIABLE, CONSTANT }; - class Binding { - /* - * One JSScript stores one Binding per formal/variable so we use a - * packed-word representation. - */ + // One JSScript stores one Binding per formal/variable so we use a + // packed-word representation. uintptr_t bits_; static const uintptr_t KIND_MASK = 0x3; @@ -147,9 +138,13 @@ class Binding static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT); public: + // A "binding" is a formal, 'var', 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, BindingKind kind, bool aliased) { + Binding(PropertyName *name, Kind kind, bool aliased) { JS_STATIC_ASSERT(CONSTANT <= KIND_MASK); JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0); JS_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0); @@ -160,8 +155,8 @@ class Binding return (PropertyName *)(bits_ & NAME_MASK); } - BindingKind kind() const { - return BindingKind(bits_ & KIND_MASK); + Kind kind() const { + return Kind(bits_ & KIND_MASK); } bool aliased() const { @@ -188,6 +183,7 @@ class Bindings HeapPtr callObjShape_; uintptr_t bindingArrayAndFlag_; uint16_t numArgs_; + uint16_t numBlockScoped_; uint32_t numVars_; /* @@ -220,7 +216,21 @@ class Bindings */ static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, unsigned numArgs, uint32_t numVars, - Binding *bindingArray); + Binding *bindingArray, unsigned numBlockScoped); + + // 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) { + JS_ASSERT(!callObjShape_); + JS_ASSERT(numVars_ == 0); + JS_ASSERT(numBlockScoped < LOCALNO_LIMIT); + JS_ASSERT(numBlockScoped >= numBlockScoped_); + numBlockScoped_ = numBlockScoped; + } uint8_t *switchToScriptStorage(Binding *newStorage); @@ -233,6 +243,10 @@ class Bindings unsigned numArgs() const { return numArgs_; } uint32_t numVars() const { return numVars_; } + unsigned numBlockScoped() const { return numBlockScoped_; } + uint32_t numLocals() const { return numVars() + numBlockScoped(); } + + // Return the size of the bindingArray. uint32_t count() const { return numArgs() + numVars(); } /* Return the initial shape of call objects created for this scope. */ @@ -924,7 +938,15 @@ class JSScript : public js::gc::BarrieredCell 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 { + js::AutoThreadSafeAccess ts(this); + return function_ ? bindings.numLocals() : bindings.numBlockScoped(); + } + + // Number of fixed slots reserved for vars. Only nonzero for function code. + size_t nfixedvars() const { js::AutoThreadSafeAccess ts(this); return function_ ? bindings.numVars() : 0; } @@ -1574,7 +1596,7 @@ namespace js { * Iterator over a script's bindings (formals and variables). * The order of iteration is: * - first, formal arguments, from index 0 to numArgs - * - next, variables, from index 0 to numVars + * - next, variables, from index 0 to numLocals */ class BindingIter { @@ -1614,7 +1636,7 @@ FillBindingVector(HandleScript fromScript, BindingVector *vec); /* * Iterator over the aliased formal bindings in ascending index order. This can * be veiwed as a filtering of BindingIter with predicate - * bi->aliased() && bi->kind() == ARGUMENT + * bi->aliased() && bi->kind() == Binding::ARGUMENT */ class AliasedFormalIter { diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index 4f3e275d842..fecbfd99d61 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -21,7 +21,8 @@ namespace js { inline Bindings::Bindings() - : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), numArgs_(0), numVars_(0) + : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), + numArgs_(0), numBlockScoped_(0), numVars_(0) {} inline diff --git a/js/src/tests/js1_8_1/regress/regress-420399.js b/js/src/tests/js1_8_1/regress/regress-420399.js index 423bfe13c2a..95aa0487290 100644 --- a/js/src/tests/js1_8_1/regress/regress-420399.js +++ b/js/src/tests/js1_8_1/regress/regress-420399.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = "TypeError: undefined has no properties"; + expect = "TypeError: a is undefined"; try { (let (a=undefined) a).b = 3; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index abb323e54fb..fdfb5f2e8c1 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1744,14 +1744,14 @@ CASE(JSOP_POPN) REGS.sp -= GET_UINT16(REGS.pc); END_CASE(JSOP_POPN) -CASE(JSOP_POPNV) +CASE(JSOP_DUPAT) { - JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth()); - Value val = REGS.sp[-1]; - REGS.sp -= GET_UINT16(REGS.pc); - REGS.sp[-1] = val; + JS_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth()); + unsigned i = GET_UINT24(REGS.pc); + const Value &rref = REGS.sp[-int(i + 1)]; + PUSH_COPY(rref); } -END_CASE(JSOP_POPNV) +END_CASE(JSOP_DUPAT) CASE(JSOP_SETRVAL) POP_RETURN_VALUE(); @@ -3352,9 +3352,6 @@ CASE(JSOP_PUSHBLOCKSCOPE) StaticBlockObject &blockObj = script->getObject(REGS.pc)->as(); JS_ASSERT(blockObj.needsClone()); - // FIXME: "Aliased" slots don't need to be on the stack. - JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); - // Clone block and push on scope chain. if (!REGS.fp()->pushBlock(cx, blockObj)) goto error; @@ -3370,9 +3367,6 @@ CASE(JSOP_POPBLOCKSCOPE) JS_ASSERT(scope && scope->is()); StaticBlockObject &blockObj = scope->as(); JS_ASSERT(blockObj.needsClone()); - - // FIXME: "Aliased" slots don't need to be on the stack. - JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); #endif // Pop block from scope chain. diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 45b93cd9fd6..10d635699f4 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -100,8 +100,8 @@ /* spreadcall variant of JSOP_EVAL */ \ macro(JSOP_SPREADEVAL,43, "spreadeval", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ \ - /* Pop N values, preserving top value. */ \ - macro(JSOP_POPNV, 44, "popnv", NULL, 3, -1, 1, JOF_UINT16) \ + /* Dup the Nth value from the top. */ \ + macro(JSOP_DUPAT, 44, "dupat", NULL, 4, 0, 1, JOF_UINT24) \ \ macro(JSOP_UNUSED45, 45, "unused45", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED46, 46, "unused46", NULL, 1, 0, 0, JOF_BYTE) \ diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 16afe8b4769..eab64d801bf 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -667,17 +667,15 @@ ClonedBlockObject::create(JSContext *cx, Handle block, Abst JS_ASSERT(obj->slotSpan() >= block->slotCount() + RESERVED_SLOTS); obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain())); - obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(block->stackDepth())); /* * Copy in the closed-over locals. Closed-over locals don't need * any fixup since the initial value is 'undefined'. */ unsigned nslots = block->slotCount(); - unsigned base = frame.script()->nfixed() + block->stackDepth(); for (unsigned i = 0; i < nslots; ++i) { if (block->isAliased(i)) - obj->as().setVar(i, frame.unaliasedLocal(base + i)); + obj->as().setVar(i, frame.unaliasedLocal(block->varToLocalIndex(i))); } JS_ASSERT(obj->isDelegate()); @@ -689,10 +687,9 @@ void ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) { StaticBlockObject &block = staticBlock(); - unsigned base = frame.script()->nfixed() + block.stackDepth(); for (unsigned i = 0; i < slotCount(); ++i) { if (!block.isAliased(i)) - setVar(i, frame.unaliasedLocal(base + i), DONT_CHECK_ALIASING); + setVar(i, frame.unaliasedLocal(block.varToLocalIndex(i)), DONT_CHECK_ALIASING); } } @@ -721,7 +718,7 @@ StaticBlockObject::addVar(ExclusiveContext *cx, Handle block unsigned index, bool *redeclared) { JS_ASSERT(JSID_IS_ATOM(id) || (JSID_IS_INT(id) && JSID_TO_INT(id) == (int)index)); - JS_ASSERT(index < VAR_INDEX_LIMIT); + JS_ASSERT(index < LOCAL_INDEX_LIMIT); *redeclared = false; @@ -769,16 +766,12 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, JSContext *cx = xdr->cx(); Rooted obj(cx); - uint32_t count = 0; - uint32_t depthAndCount = 0; + uint32_t count = 0, offset = 0; if (mode == XDR_ENCODE) { obj = *objp; - uint32_t depth = obj->stackDepth(); - JS_ASSERT(depth <= UINT16_MAX); count = obj->slotCount(); - JS_ASSERT(count <= UINT16_MAX); - depthAndCount = (depth << 16) | uint16_t(count); + offset = obj->localOffset(); } if (mode == XDR_DECODE) { @@ -789,13 +782,13 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, *objp = obj; } - if (!xdr->codeUint32(&depthAndCount)) + if (!xdr->codeUint32(&count)) + return false; + if (!xdr->codeUint32(&offset)) return false; if (mode == XDR_DECODE) { - uint32_t depth = uint16_t(depthAndCount >> 16); - count = uint16_t(depthAndCount); - obj->setStackDepth(depth); + obj->setLocalOffset(offset); /* * XDR the block object's properties. We know that there are 'count' @@ -880,7 +873,7 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, HandleinitEnclosingNestedScope(enclosingScope); - clone->setStackDepth(srcBlock->stackDepth()); + clone->setLocalOffset(srcBlock->localOffset()); /* Shape::Range is reverse order, so build a list in forward order. */ AutoShapeVector shapes(cx); @@ -1194,7 +1187,7 @@ class DebugScopeProxy : public BaseProxyHandler if (!bi) return false; - if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) { + if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) { uint32_t i = bi.frameIndex(); if (script->varIsAliased(i)) return false; @@ -1216,7 +1209,7 @@ class DebugScopeProxy : public BaseProxyHandler vp.set(UndefinedValue()); } } else { - JS_ASSERT(bi->kind() == ARGUMENT); + JS_ASSERT(bi->kind() == Binding::ARGUMENT); unsigned i = bi.frameIndex(); if (script->formalIsAliased(i)) return false; @@ -1266,12 +1259,12 @@ class DebugScopeProxy : public BaseProxyHandler if (maybeLiveScope) { AbstractFramePtr frame = maybeLiveScope->frame(); JSScript *script = frame.script(); - uint32_t local = block->slotToLocalIndex(script->bindings, shape->slot()); + uint32_t local = block->staticBlock().varToLocalIndex(i); if (action == GET) vp.set(frame.unaliasedLocal(local)); else frame.unaliasedLocal(local) = vp; - JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script)); + JS_ASSERT(analyze::LocalSlot(script, local) < analyze::TotalSlots(script)); } else { if (action == GET) vp.set(block->var(i, DONT_CHECK_ALIASING)); diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index f5829292d90..c9fdbed265d 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -413,20 +413,6 @@ class BlockObject : public NestedScopeObject return propertyCountForCompilation(); } - /* - * Return the local corresponding to the ith binding where i is in the - * range [0, slotCount()) and the return local index is in the range - * [script->nfixed, script->nfixed + script->nslots). - */ - uint32_t slotToLocalIndex(const Bindings &bindings, uint32_t slot) { - JS_ASSERT(slot < RESERVED_SLOTS + slotCount()); - return bindings.numVars() + stackDepth() + (slot - RESERVED_SLOTS); - } - - uint32_t localIndexToSlot(const Bindings &bindings, uint32_t i) { - return RESERVED_SLOTS + (i - (bindings.numVars() + stackDepth())); - } - protected: /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */ const Value &slotValue(unsigned i) { @@ -440,15 +426,42 @@ class BlockObject : public NestedScopeObject class StaticBlockObject : public BlockObject { + static const unsigned LOCAL_OFFSET_SLOT = 1; + public: static StaticBlockObject *create(ExclusiveContext *cx); + /* See StaticScopeIter comment. */ + JSObject *enclosingStaticScope() const { + AutoThreadSafeAccess ts(this); + return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); + } + /* - * Return whether this StaticBlockObject contains a variable stored at - * the given stack depth (i.e., fp->base()[depth]). + * A refinement of enclosingStaticScope that returns nullptr if the enclosing + * static scope is a JSFunction. */ - bool containsVarAtDepth(uint32_t depth) { - return depth >= stackDepth() && depth < stackDepth() + slotCount(); + inline StaticBlockObject *enclosingBlock() const; + + uint32_t localOffset() { + return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32(); + } + + // Return the local corresponding to the 'var'th binding where 'var' is in the + // range [0, slotCount()). + uint32_t varToLocalIndex(uint32_t var) { + JS_ASSERT(var < slotCount()); + return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + var; + } + + // Return the slot corresponding to local variable 'local', where 'local' is + // in the range [localOffset(), localOffset() + slotCount()). The result is + // in the range [RESERVED_SLOTS, RESERVED_SLOTS + slotCount()). + uint32_t localIndexToSlot(uint32_t local) { + JS_ASSERT(local >= localOffset()); + local -= localOffset(); + JS_ASSERT(local < slotCount()); + return RESERVED_SLOTS + local; } /* @@ -482,9 +495,9 @@ class StaticBlockObject : public BlockObject } } - void setStackDepth(uint32_t depth) { - JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined()); - initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth)); + void setLocalOffset(uint32_t offset) { + JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined()); + initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset)); } /* @@ -508,7 +521,7 @@ class StaticBlockObject : public BlockObject * associated Shape. If we could remove the block dependencies on shape->shortid, we could * remove INDEX_LIMIT. */ - static const unsigned VAR_INDEX_LIMIT = JS_BIT(16); + static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16); static Shape *addVar(ExclusiveContext *cx, Handle block, HandleId id, unsigned index, bool *redeclared); diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index f29adcb496f..5b0f6c0ea8e 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -100,13 +100,14 @@ inline Value & StackFrame::unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing) { JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i)); - JS_ASSERT(i < script()->nfixed()); + JS_ASSERT(i < script()->nfixedvars()); return slots()[i]; } inline Value & StackFrame::unaliasedLocal(uint32_t i, MaybeCheckAliasing checkAliasing) { + JS_ASSERT(i < script()->nfixed()); #ifdef DEBUG CheckLocalUnaliased(checkAliasing, script(), i); #endif diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 7270db93c85..55fa790d826 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1268,8 +1268,8 @@ js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint if (!checkAliasing) return; - JS_ASSERT(i < script->nslots()); - if (i < script->nfixed()) { + JS_ASSERT(i < script->nfixed()); + if (i < script->bindings.numVars()) { JS_ASSERT(!script->varIsAliased(i)); } else { // FIXME: The callers of this function do not easily have the PC of the diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 1b14b51488d..991fd93c16a 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -23,7 +23,7 @@ namespace js { * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 166); +static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 167); class XDRBuffer { public: From 983645174604d512fda60d5dcaf479f58603f218 Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Wed, 12 Feb 2014 15:25:52 -0500 Subject: [PATCH 07/40] Bug 971875 - Fix regression to HWC on b2g. r=sotaro --HG-- extra : rebase_source : 8aaa3818d1dbfe1e07137010a7734a2da8dc3379 --- gfx/layers/composite/LayerManagerComposite.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index 9aed7277630..3a49718773b 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -444,10 +444,12 @@ LayerManagerComposite::Render() /** Our more efficient but less powerful alter ego, if one is available. */ nsRefPtr composer2D = mCompositor->GetWidget()->GetComposer2D(); - if (mFPS && composer2D && composer2D->TryRender(mRoot, mWorldMatrix)) { - double fps = mFPS->mCompositionFps.AddFrameAndGetFps(TimeStamp::Now()); - if (gfxPlatform::GetPrefLayersDrawFPS()) { - printf_stderr("HWComposer: FPS is %g\n", fps); + if (composer2D && composer2D->TryRender(mRoot, mWorldMatrix)) { + if (mFPS) { + double fps = mFPS->mCompositionFps.AddFrameAndGetFps(TimeStamp::Now()); + if (gfxPlatform::GetPrefLayersDrawFPS()) { + printf_stderr("HWComposer: FPS is %g\n", fps); + } } mCompositor->EndFrameForExternalComposition(mWorldMatrix); return; From e03e48ef8d3ef6d1111ff1ac4058a03559252719 Mon Sep 17 00:00:00 2001 From: Chris Pearce Date: Thu, 13 Feb 2014 10:17:47 +1300 Subject: [PATCH 08/40] Bug 971229 - Ensure test_videocontrols starts (and doesn't time out) if it loads real quick. r=jaws --- .../tests/widgets/test_videocontrols.html | 51 +++++++------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/toolkit/content/tests/widgets/test_videocontrols.html b/toolkit/content/tests/widgets/test_videocontrols.html index 5b5ac628e48..83143e94026 100644 --- a/toolkit/content/tests/widgets/test_videocontrols.html +++ b/toolkit/content/tests/widgets/test_videocontrols.html @@ -2,7 +2,7 @@ Video controls test - + @@ -10,7 +10,7 @@

- +
@@ -166,7 +166,7 @@ function runTest(event) {
       synthesizeMouse(video, endDragX,   scrubberCenterY, { type: "mousemove", button: 0 });
       synthesizeMouse(video, endDragX,   scrubberCenterY, { type: "mouseup",   button: 0 });
       break;
-                      
+
     case 12:
       is(event.type, "seeking", "checking event type");
       ok(true, "video position is at " + video.currentTime);
@@ -211,47 +211,34 @@ function runTest(event) {
   testnum++;
 }
 
-var canplaythroughsavedevent = null;
-var gotcanplaythroughevent = false;
-var gotloadevent = false;
-
-function canplaythroughevent(event) {
-  canplaythroughsavedevent = event;
-  gotcanplaythroughevent = true;
-  video.removeEventListener("canplaythrough",  canplaythroughevent, false);
-  maybeStartTest();
-}
-
-function loadevent(event) {
-  gotloadevent = true;
-  maybeStartTest();
-}
-
-// setTimeout so that test starts after paint suppression ends
-function maybeStartTest() {
-  if (!gotcanplaythroughevent || !gotloadevent)
-    return;
-
-  setTimeout("runTest(canplaythroughsavedevent);", 0);
-}
 
 var testnum = 1;
 var video = document.getElementById("video");
 
-SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest);
-function startTest() {
-  // Kick off test once video has loaded.
-  video.addEventListener("canplaythrough",  canplaythroughevent, false);
-  window.addEventListener("load",  loadevent, false);
-
+function canplaythroughevent(event) {
+  video.removeEventListener("canplaythrough",  canplaythroughevent, false);
   // Other events expected by the test.
   video.addEventListener("play",  runTest, false);
   video.addEventListener("pause", runTest, false);
   video.addEventListener("volumechange", runTest, false);
   video.addEventListener("seeking", runTest, false);
   video.addEventListener("seeked", runTest, false);
+  // Begin the test.
+  runTest(event);
 }
 
+function startMediaLoad() {
+  // Kick off test once video has loaded, in its canplaythrough event handler.
+  video.src = "seek_with_sound.ogg";
+  video.addEventListener("canplaythrough", canplaythroughevent, false);
+}
+
+function loadevent(event) {
+  SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad);
+}
+
+window.addEventListener("load",  loadevent, false);
+
 SimpleTest.waitForExplicitFinish();
 
 

From 42055a8e060f4ef40704a6ed0cf1eed315157284 Mon Sep 17 00:00:00 2001
From: Neil Rashbrook 
Date: Wed, 12 Feb 2014 21:22:07 +0000
Subject: [PATCH 09/40] Bug 966911 Part 1: Improve DOMString and AString
 conversion code paths r=bholley

---
 js/xpconnect/src/XPCConvert.cpp       | 137 ++++++++++++--------------
 js/xpconnect/src/XPCJSRuntime.cpp     |  36 +++----
 js/xpconnect/src/XPCWrappedNative.cpp |   8 +-
 js/xpconnect/src/xpcprivate.h         |  47 +--------
 xpcom/string/public/nsTString.h       |   5 +
 5 files changed, 93 insertions(+), 140 deletions(-)

diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp
index b718e502a95..feea642bda3 100644
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -379,8 +379,6 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
 {
     NS_PRECONDITION(d, "bad param");
 
-    bool isDOMString = true;
-
     AutoJSContext cx;
     if (pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
@@ -473,81 +471,62 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
 
     case nsXPTType::T_ASTRING:
     {
-        isDOMString = false;
+        if (JSVAL_IS_VOID(s)) {
+            if (useAllocator)
+                *((const nsAString**)d) = &EmptyString();
+            else
+                (**((nsAString**)d)).SetIsVoid(true);
+            return true;
+        }
         // Fall through to T_DOMSTRING case.
     }
     case nsXPTType::T_DOMSTRING:
     {
-        static const char16_t EMPTY_STRING[] = { '\0' };
-        static const char16_t VOID_STRING[] = { 'u', 'n', 'd', 'e', 'f', 'i', 'n', 'e', 'd', '\0' };
-
+        if (JSVAL_IS_NULL(s)) {
+            if (useAllocator)
+                *((const nsAString**)d) = &NullString();
+            else
+                (**((nsAString**)d)).SetIsVoid(true);
+            return true;
+        }
+        size_t length = 0;
         const char16_t* chars = nullptr;
         JSString* str = nullptr;
-        bool isNewString = false;
-        uint32_t length = 0;
-
-        if (JSVAL_IS_VOID(s)) {
-            if (isDOMString) {
-                chars  = VOID_STRING;
-                length = ArrayLength(VOID_STRING) - 1;
-            } else {
-                chars = EMPTY_STRING;
-                length = 0;
-            }
-        } else if (!JSVAL_IS_NULL(s)) {
+        if (!JSVAL_IS_VOID(s)) {
             str = ToString(cx, s);
             if (!str)
                 return false;
 
-            length = (uint32_t) JS_GetStringLength(str);
-            if (length) {
-                chars = JS_GetStringCharsZ(cx, str);
-                if (!chars)
-                    return false;
-                if (STRING_TO_JSVAL(str) != s)
-                    isNewString = true;
-            } else {
-                str = nullptr;
-                chars = EMPTY_STRING;
+            chars = useAllocator ? JS_GetStringCharsZAndLength(cx, str, &length)
+                                 : JS_GetStringCharsAndLength(cx, str, &length);
+            if (!chars)
+                return false;
+
+            if (!length) {
+                if (useAllocator)
+                    *((const nsAString**)d) = &EmptyString();
+                else
+                    (**((nsAString**)d)).Truncate();
+                return true;
             }
         }
 
+        nsString* ws;
         if (useAllocator) {
-            // XXX extra string copy when isNewString
-            if (str && !isNewString) {
-                size_t strLength;
-                const jschar *strChars = JS_GetStringCharsZAndLength(cx, str, &strLength);
-                if (!strChars)
-                    return false;
-
-                XPCReadableJSStringWrapper *wrapper =
-                    nsXPConnect::GetRuntimeInstance()->NewStringWrapper(strChars, strLength);
-                if (!wrapper)
-                    return false;
-
-                *((const nsAString**)d) = wrapper;
-            } else if (JSVAL_IS_NULL(s)) {
-                XPCReadableJSStringWrapper *wrapper =
-                    new XPCReadableJSStringWrapper();
-                if (!wrapper)
-                    return false;
-
-                *((const nsAString**)d) = wrapper;
-            } else {
-                // use nsString to encourage sharing
-                const nsAString *rs = new nsString(chars, length);
-                if (!rs)
-                    return false;
-                *((const nsAString**)d) = rs;
-            }
+            ws = nsXPConnect::GetRuntimeInstance()->NewShortLivedString();
+            *((const nsString**)d) = ws;
         } else {
-            nsAString* ws = *((nsAString**)d);
+            ws = *((nsString**)d);
+        }
 
-            if (JSVAL_IS_NULL(s) || (!isDOMString && JSVAL_IS_VOID(s))) {
-                ws->Truncate();
-                ws->SetIsVoid(true);
-            } else
-                ws->Assign(chars, length);
+        if (!str) {
+            ws->AssignLiteral(MOZ_UTF16("undefined"));
+        } else if (useAllocator && STRING_TO_JSVAL(str) == s) {
+            // The JS string will exist over the function call.
+            // We don't need to copy the characters in this case.
+            ws->Rebind(chars, length);
+        } else {
+            ws->Assign(chars, length);
         }
         return true;
     }
@@ -622,20 +601,14 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
     case nsXPTType::T_UTF8STRING:
     {
         const jschar* chars;
-        uint32_t length;
+        size_t length;
         JSString* str;
 
         if (JSVAL_IS_NULL(s) || JSVAL_IS_VOID(s)) {
             if (useAllocator) {
-                nsACString *rs = new nsCString();
-                if (!rs)
-                    return false;
-
-                rs->SetIsVoid(true);
-                *((nsACString**)d) = rs;
+                *((const nsACString**)d) = &NullCString();
             } else {
                 nsCString* rs = *((nsCString**)d);
-                rs->Truncate();
                 rs->SetIsVoid(true);
             }
             return true;
@@ -644,11 +617,19 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
         // The JS val is neither null nor void...
 
         if (!(str = ToString(cx, s))||
-            !(chars = JS_GetStringCharsZ(cx, str))) {
+            !(chars = JS_GetStringCharsAndLength(cx, str, &length))) {
             return false;
         }
 
-        length = JS_GetStringLength(str);
+        if (!length) {
+            if (useAllocator) {
+                *((const nsACString**)d) = &EmptyCString();
+            } else {
+                nsCString* rs = *((nsCString**)d);
+                rs->Truncate();
+            }
+            return true;
+        }
 
         nsCString *rs;
         if (useAllocator) {
@@ -661,9 +642,7 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
         } else {
             rs = *((nsCString**)d);
         }
-        const char16_t* start = (const char16_t*)chars;
-        const char16_t* end = start + length;
-        CopyUTF16toUTF8(nsDependentSubstring(start, end), *rs);
+        CopyUTF16toUTF8(Substring(chars, length), *rs);
         return true;
     }
 
@@ -696,6 +675,16 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
             return false;
         }
 
+        if (!length) {
+            if (useAllocator) {
+                *((const nsACString**)d) = &EmptyCString();
+            } else {
+                nsCString* rs = *((nsCString**)d);
+                rs->Truncate();
+            }
+            return true;
+        }
+
         nsACString *rs;
         if (useAllocator) {
             rs = new nsCString();
diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp
index 775b958f65d..177dc2877e2 100644
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1415,38 +1415,32 @@ XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf)
     return n;
 }
 
-XPCReadableJSStringWrapper *
-XPCJSRuntime::NewStringWrapper(const char16_t *str, uint32_t len)
+nsString*
+XPCJSRuntime::NewShortLivedString()
 {
     for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) {
-        StringWrapperEntry& ent = mScratchStrings[i];
-
-        if (!ent.mInUse) {
-            ent.mInUse = true;
-
-            // Construct the string using placement new.
-
-            return new (ent.mString.addr()) XPCReadableJSStringWrapper(str, len);
+        if (mScratchStrings[i].empty()) {
+            mScratchStrings[i].construct();
+            return mScratchStrings[i].addr();
         }
     }
 
     // All our internal string wrappers are used, allocate a new string.
-
-    return new XPCReadableJSStringWrapper(str, len);
+    return new nsString();
 }
 
 void
-XPCJSRuntime::DeleteString(nsAString *string)
+XPCJSRuntime::DeleteShortLivedString(nsString *string)
 {
+    if (string == &EmptyString() || string == &NullString())
+        return;
+
     for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) {
-        StringWrapperEntry& ent = mScratchStrings[i];
-        if (string == ent.mString.addr()) {
+        if (!mScratchStrings[i].empty() &&
+            mScratchStrings[i].addr() == string) {
             // One of our internal strings is no longer in use, mark
-            // it as such and destroy the string.
-
-            ent.mInUse = false;
-            ent.mString.addr()->~XPCReadableJSStringWrapper();
-
+            // it as such and free its data.
+            mScratchStrings[i].destroy();
             return;
         }
     }
@@ -1566,7 +1560,7 @@ XPCJSRuntime::~XPCJSRuntime()
 
 #ifdef DEBUG
     for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) {
-        MOZ_ASSERT(!mScratchStrings[i].mInUse, "Uh, string wrapper still in use!");
+        MOZ_ASSERT(mScratchStrings[i].empty(), "Short lived string still in use");
     }
 #endif
 }
diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp
index a4b79d927cf..f3e16887e33 100644
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -2311,11 +2311,15 @@ CallMethodHelper::CleanupParam(nsXPTCMiniVariant& param, nsXPTType& type)
             break;
         case nsXPTType::T_ASTRING:
         case nsXPTType::T_DOMSTRING:
-            nsXPConnect::GetRuntimeInstance()->DeleteString((nsAString*)param.val.p);
+            nsXPConnect::GetRuntimeInstance()->DeleteShortLivedString((nsString*)param.val.p);
             break;
         case nsXPTType::T_UTF8STRING:
         case nsXPTType::T_CSTRING:
-            delete (nsCString*) param.val.p;
+            {
+                nsCString* rs = (nsCString*)param.val.p;
+                if (rs != &EmptyCString() && rs != &NullCString())
+                    delete rs;
+            }
             break;
         default:
             MOZ_ASSERT(!type.IsArithmetic(), "Cleanup requested on unexpected type.");
diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h
index 93486e06d14..d4702ef664f 100644
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -80,6 +80,7 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/GuardObjects.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 
 #include 
@@ -393,33 +394,6 @@ private:
 
 /***************************************************************************/
 
-// class to export a JSString as an const nsAString, no refcounting :(
-class XPCReadableJSStringWrapper : public nsDependentString
-{
-public:
-    typedef nsDependentString::char_traits char_traits;
-
-    XPCReadableJSStringWrapper(const char16_t *chars, size_t length) :
-        nsDependentString(chars, length)
-    { }
-
-    XPCReadableJSStringWrapper() :
-        nsDependentString(char_traits::sEmptyBuffer, char_traits::sEmptyBuffer)
-    { SetIsVoid(true); }
-
-    bool init(JSContext* aContext, JSString* str)
-    {
-        size_t length;
-        const jschar* chars = JS_GetStringCharsZAndLength(aContext, str, &length);
-        if (!chars)
-            return false;
-
-        MOZ_ASSERT(IsEmpty(), "init() on initialized string");
-        new(static_cast(this)) nsDependentString(chars, length);
-        return true;
-    }
-};
-
 // In the current xpconnect system there can only be one XPCJSRuntime.
 // So, xpconnect can only be used on one JSRuntime within the process.
 
@@ -580,8 +554,8 @@ public:
 
     ~XPCJSRuntime();
 
-    XPCReadableJSStringWrapper *NewStringWrapper(const char16_t *str, uint32_t len);
-    void DeleteString(nsAString *string);
+    nsString* NewShortLivedString();
+    void DeleteShortLivedString(nsString *string);
 
     void AddGCCallback(xpcGCCallback cb);
     void RemoveGCCallback(xpcGCCallback cb);
@@ -646,20 +620,7 @@ private:
 
 #define XPCCCX_STRING_CACHE_SIZE 2
 
-    // String wrapper entry, holds a string, and a boolean that tells
-    // whether the string is in use or not.
-    //
-    // NB: The string is not stored by value so that we avoid the cost of
-    // construction/destruction.
-    struct StringWrapperEntry
-    {
-        StringWrapperEntry() : mInUse(false) { }
-
-        mozilla::AlignedStorage2 mString;
-        bool mInUse;
-    };
-
-    StringWrapperEntry mScratchStrings[XPCCCX_STRING_CACHE_SIZE];
+    mozilla::Maybe mScratchStrings[XPCCCX_STRING_CACHE_SIZE];
 
     friend class Watchdog;
     friend class AutoLockWatchdog;
diff --git a/xpcom/string/public/nsTString.h b/xpcom/string/public/nsTString.h
index 43c4090303d..2668db92b2b 100644
--- a/xpcom/string/public/nsTString.h
+++ b/xpcom/string/public/nsTString.h
@@ -380,6 +380,11 @@ class nsTString_CharT : public nsTSubstring_CharT
 
 #endif // !MOZ_STRING_WITH_OBSOLETE_API
 
+        /**
+         * Allow this string to be bound to a character buffer
+         * until the string is rebound or mutated; the caller
+         * must ensure that the buffer outlives the string.
+         */
       void Rebind( const char_type* data, size_type length );
 
         /**

From 3d7b67cd401be5f47ccf436523d99f8a452757ab Mon Sep 17 00:00:00 2001
From: Wes Kocher 
Date: Wed, 12 Feb 2014 13:25:15 -0800
Subject: [PATCH 10/40] Backed out changeset c80de8d196af (bug 962599) for
 crashtest failures

---
 js/src/frontend/BytecodeCompiler.cpp          |  27 +-
 js/src/frontend/BytecodeEmitter.cpp           | 372 ++++++++++--------
 js/src/frontend/FullParseHandler.h            |  21 +-
 js/src/frontend/ParseNode.h                   |   3 +-
 js/src/frontend/Parser.cpp                    | 104 +----
 js/src/frontend/Parser.h                      |  15 +-
 js/src/frontend/SyntaxParseHandler.h          |   5 +-
 js/src/jit-test/tests/basic/testBug579647.js  |   4 +-
 js/src/jit/AsmJS.cpp                          |   3 +-
 js/src/jit/BaselineCompiler.cpp               |  24 +-
 js/src/jit/BaselineCompiler.h                 |   2 +-
 js/src/jit/BaselineFrame.h                    |   2 +-
 js/src/jit/BaselineFrameInfo.h                |  11 +-
 js/src/jit/CompileInfo.h                      |  61 +--
 js/src/jit/IonBuilder.cpp                     |  30 +-
 js/src/jsanalyze.cpp                          |  38 +-
 js/src/jsopcode.cpp                           |  86 ++--
 js/src/jsscript.cpp                           |  25 +-
 js/src/jsscript.h                             |  56 +--
 js/src/jsscriptinlines.h                      |   3 +-
 .../tests/js1_8_1/regress/regress-420399.js   |   2 +-
 js/src/vm/Interpreter.cpp                     |  18 +-
 js/src/vm/Opcodes.h                           |   4 +-
 js/src/vm/ScopeObject.cpp                     |  35 +-
 js/src/vm/ScopeObject.h                       |  57 ++-
 js/src/vm/Stack-inl.h                         |   3 +-
 js/src/vm/Stack.cpp                           |   4 +-
 js/src/vm/Xdr.h                               |   2 +-
 28 files changed, 464 insertions(+), 553 deletions(-)

diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp
index 57b9483280c..d5a6f96e846 100644
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -268,6 +268,12 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
     if (!script)
         return nullptr;
 
+    // Global/eval script bindings are always empty (all names are added to the
+    // scope dynamically via JSOP_DEFFUN/VAR).
+    InternalHandle bindings(script, &script->bindings);
+    if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr))
+        return nullptr;
+
     // 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;
@@ -287,8 +293,7 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
     Maybe > pc;
 
     pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc,
-                 (Directives *) nullptr, staticLevel, /* bodyid = */ 0,
-                 /* blockScopeDepth = */ 0);
+                 (Directives *) nullptr, staticLevel, /* bodyid = */ 0);
     if (!pc.ref().init(parser.tokenStream))
         return nullptr;
 
@@ -355,12 +360,10 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
 
                 pc.destroy();
                 pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr,
-                             &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0,
-                             script->bindings.numBlockScoped());
+                             &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0);
                 if (!pc.ref().init(parser.tokenStream))
                     return nullptr;
                 JS_ASSERT(parser.pc == pc.addr());
-
                 pn = parser.statement();
             }
             if (!pn) {
@@ -369,11 +372,6 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
             }
         }
 
-        // Accumulate the maximum block scope depth, so that EmitTree can assert
-        // when emitting JSOP_GETLOCAL that the local is indeed within the fixed
-        // part of the stack frame.
-        script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth);
-
         if (canHaveDirectives) {
             if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives))
                 return nullptr;
@@ -416,15 +414,6 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
     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(script, &script->bindings);
-    if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr,
-                                            pc.ref().blockScopeDepth))
-        return nullptr;
-
     if (!JSScript::fullyInitFromEmitter(cx, script, &bce))
         return nullptr;
 
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index bec627765b7..350f0c50476 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -257,34 +257,6 @@ EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc)
     return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc));
 }
 
-// Dup the var in operand stack slot "slot".  The first item on the operand
-// stack is one slot past the last fixed slot.  The last (most recent) item is
-// slot bce->stackDepth - 1.
-//
-// The instruction that is written (JSOP_DUPAT) switches the depth around so
-// that it is addressed from the sp instead of from the fp.  This is useful when
-// you don't know the size of the fixed stack segment (nfixed), as is the case
-// when compiling scripts (because each statement is parsed and compiled
-// separately, but they all together form one script with one fixed stack
-// frame).
-static bool
-EmitDupAt(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned slot)
-{
-    JS_ASSERT(slot < unsigned(bce->stackDepth));
-    // The slot's position on the operand stack, measured from the top.
-    unsigned slotFromTop = bce->stackDepth - 1 - slot;
-    if (slotFromTop >= JS_BIT(24)) {
-        bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
-        return false;
-    }
-    ptrdiff_t off = EmitN(cx, bce, JSOP_DUPAT, 3);
-    if (off < 0)
-        return false;
-    jsbytecode *pc = bce->code(off);
-    SET_UINT24(pc, slotFromTop);
-    return true;
-}
-
 /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */
 const char js_with_statement_str[] = "with statement";
 const char js_finally_block_str[]  = "finally block";
@@ -611,6 +583,7 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt)
                 if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0)
                     return false;
             }
+            npops += blockObj.slotCount();
         }
     }
 
@@ -685,6 +658,25 @@ EnclosingStaticScope(BytecodeEmitter *bce)
     return bce->sc->asFunctionBox()->function();
 }
 
+// In a stack frame, block-scoped locals follow hoisted var-bound locals.  If
+// the current compilation unit is a function, add the number of "fixed slots"
+// (var-bound locals) to the given block-scoped index, to arrive at its final
+// position in the call frame.
+//
+static bool
+AdjustBlockSlot(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t *slot)
+{
+    JS_ASSERT(*slot < bce->maxStackDepth);
+    if (bce->sc->isFunctionBox()) {
+        *slot += bce->script->bindings.numVars();
+        if (*slot >= StaticBlockObject::VAR_INDEX_LIMIT) {
+            bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+            return false;
+        }
+    }
+    return true;
+}
+
 #ifdef DEBUG
 static bool
 AllLocalsAliased(StaticBlockObject &obj)
@@ -699,6 +691,10 @@ AllLocalsAliased(StaticBlockObject &obj)
 static bool
 ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj)
 {
+    uint32_t depthPlusFixed = blockObj->stackDepth();
+    if (!AdjustBlockSlot(cx, bce, &depthPlusFixed))
+        return false;
+
     for (unsigned i = 0; i < blockObj->slotCount(); i++) {
         Definition *dn = blockObj->maybeDefinitionParseNode(i);
 
@@ -710,7 +706,7 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, HandleisDefn());
         if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(),
-                               blockObj->varToLocalIndex(dn->frameSlot())))
+                               dn->frameSlot() + depthPlusFixed))
         {
             return false;
         }
@@ -734,79 +730,6 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj)
-{
-    unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0;
-    unsigned localOffset = nfixedvars;
-
-    if (bce->staticScope) {
-        Rooted outer(cx, bce->staticScope);
-        for (; outer; outer = outer->enclosingNestedScope()) {
-            if (outer->is()) {
-                StaticBlockObject &outerBlock = outer->as();
-                localOffset = outerBlock.localOffset() + outerBlock.slotCount();
-                break;
-            }
-        }
-    }
-
-    JS_ASSERT(localOffset + blockObj->slotCount()
-              <= nfixedvars + 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
-// "with" object or a "block" object.  "With" objects represent "with" scopes.
-// Block objects represent lexical scopes, and contain named block-scoped
-// bindings, for example "let" bindings or the exception in a catch block.
-// Those variables may be local and thus accessible directly from the stack, or
-// "aliased" (accessed by name from nested functions, or dynamically via nested
-// "eval" or "with") and only accessible through the scope chain.
-//
-// All nested scopes are present on the "static scope chain".  A nested scope
-// that is a "with" scope will be present on the scope chain at run-time as
-// well.  A block scope may or may not have a corresponding link on the run-time
-// scope chain; if no variable declared in the block scope is "aliased", then no
-// scope chain node is allocated.
-//
-// To help debuggers, the bytecode emitter arranges to record the PC ranges
-// comprehended by a nested scope, and ultimately attach them to the JSScript.
-// An element in the "block scope array" specifies the PC range, and links to a
-// NestedScopeObject in the object list of the script.  That scope object is
-// linked to the previous link in the static scope chain, if any.  The static
-// scope chain at any pre-retire PC can be retrieved using
-// JSScript::getStaticScope(jsbytecode *pc).
-//
-// Block scopes store their locals in the fixed part of a stack frame, after the
-// "fixed var" bindings.  A fixed var binding is a "var" or legacy "const"
-// binding that occurs in a function (as opposed to a script or in eval code).
-// Only functions have fixed var bindings.
-//
-// To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a
-// block scope, even if the block has no aliased locals.  This allows
-// DebugScopes to invalidate any association between a debugger scope object,
-// which can proxy access to unaliased stack locals, and the actual live frame.
-// In normal, non-debug mode, this opcode does not cause any baseline code to be
-// emitted.
-//
-// Enter a nested scope with EnterNestedScope.  It will emit
-// PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of
-// the scope.  Leave a nested scope with LeaveNestedScope, which, for blocks,
-// will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE.  (For "with" scopes it
-// emits LEAVEWITH, of course.)  Pass EnterNestedScope a fresh StmtInfoBCE
-// object, and pass that same object to the corresponding LeaveNestedScope.  If
-// the statement is a block scope, pass STMT_BLOCK as stmtType; otherwise for
-// with scopes pass STMT_WITH.
-//
 static bool
 EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
                  StmtType stmtType)
@@ -818,8 +741,6 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt,
       case STMT_BLOCK: {
         Rooted blockObj(cx, &scopeObj->as());
 
-        ComputeLocalOffset(cx, bce, blockObj);
-
         if (!ComputeAliasedSlots(cx, bce, blockObj))
             return false;
 
@@ -839,7 +760,8 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt,
     }
 
     uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
-    if (StmtInfoBCE *stmt = bce->topScopeStmt) {
+    if (bce->staticScope) {
+        StmtInfoBCE *stmt = bce->topScopeStmt;
         for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {}
         parent = stmt->blockScopeIndex;
     }
@@ -857,6 +779,71 @@ EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt,
     return true;
 }
 
+// ~ Block Scopes ~
+//
+// A block scope is a region of a script with an additional set of named
+// variables.  Those variables may be local and thus accessible directly from
+// the stack, or "aliased" and only accessible through the scope chain.
+//
+// A block scope may or may not have a corresponding link on the scope chain.
+// If no variable declared in the scope is "aliased", then no scope chain node
+// is allocated.
+//
+// To help debuggers, the bytecode emitter arranges to record the PC ranges
+// comprehended by a block scope, and ultimately attach them to the JSScript.
+// An element in the "block scope array" specifies the PC range, and links to a
+// StaticBlockObject in the object list of the script.  That block is linked to
+// the previous block in the scope, if any.  The static block chain at any
+// pre-retire PC can be retrieved using JSScript::getStaticScope(jsbytecode *pc).
+//
+// When PUSHBLOCKSCOPE is executed, it assumes that the block's locals are
+// already on the stack.  Initial values of "aliased" locals are copied from the
+// stack to the ClonedBlockObject, and no further access is made to the stack
+// slot.
+//
+// Likewise after leaving a POPBLOCKSCOPE, we will need to emit code to pop the
+// stack values.
+//
+// Finally, to assist the debugger, we also emit a DEBUGLEAVEBLOCK opcode before
+// POPBLOCKSCOPE in all cases -- even if the block has no aliased locals.  This
+// allows DebugScopes to invalidate any association between a debugger scope
+// object, which can proxy access to unaliased stack locals, and the actual live
+// frame.  In normal, non-debug mode, this opcode does not cause any baseline
+// code to be emitted.
+//
+// In this function "extraSlots" indicates the number of slots that are
+// "floating" on the stack above the scope's slots.  This will only be nonzero
+// in the case of for-let-in and for-let-of loops, where loop iterator state
+// floats above the block scopes.  It would be nice to fix this eventually so
+// that loop iterator state gets assigned to block-scoped fp-addressable
+// temporaries, instead of being addressable only via the sp.  This would also
+// make generators more efficient, as the loop state could be heap-allocated, so
+// that the value stack would likely be empty at yield points inside for-of /
+// for-in loops.
+//
+// Summary: Enter block scopes with EnterBlockScope.  It will emit
+// PUSHBLOCKSCOPE if needed.  Leave them with LeaveNestedScope, which will emit
+// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE.  Pass EnterBlockScope a fresh
+// StmtInfoBCE object, and pass that same object to the corresponding
+// LeaveNestedScope.  Push locals before entering a scope, and pop them
+// afterwards.  Brush your teeth, and clean behind your ears!
+//
+static bool
+EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
+                unsigned extraSlots)
+{
+    Rooted blockObj(cx, &objbox->object->as());
+
+    // FIXME: Once bug 962599 lands, we won't care about the stack depth, so we
+    // won't have extraSlots and thus invocations of EnterBlockScope can become
+    // invocations of EnterNestedScope.
+    int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
+    JS_ASSERT(depth >= 0);
+    blockObj->setStackDepth(depth);
+
+    return EnterNestedScope(cx, bce, stmt, objbox, STMT_BLOCK);
+}
+
 // Patches |breaks| and |continues| unless the top statement info record
 // represents a try-catch-finally suite. May fail if a jump offset overflows.
 static bool
@@ -1153,17 +1140,17 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *
                 return false;
             JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
         } else {
-            JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals());
+            uint32_t depth = local - bceOfDef->script->bindings.numVars();
             JS_ASSERT(bceOfDef->staticScope->is());
             Rooted b(cx, &bceOfDef->staticScope->as());
-            while (local < b->localOffset()) {
+            while (!b->containsVarAtDepth(depth)) {
                 if (b->needsClone())
                     skippedScopes++;
                 b = &b->enclosingNestedScope()->as();
             }
             if (!AssignHops(bce, pn, skippedScopes, &sc))
                 return false;
-            sc.setSlot(b->localIndexToSlot(local));
+            sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local));
         }
     }
 
@@ -2428,55 +2415,6 @@ SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off)
     SET_JUMP_OFFSET(bce->code(off), bce->offset() - off);
 }
 
-static bool
-PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n)
-{
-    for (unsigned i = 0; i < n; ++i) {
-        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
-            return false;
-    }
-    return true;
-}
-
-static bool
-InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce,
-                                     Handle blockObj)
-{
-    for (unsigned i = blockObj->slotCount(); 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))
-                return false;
-        } else {
-            if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, blockObj->varToLocalIndex(i - 1), 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.
-    Rooted blockObj(cx, &objbox->object->as());
-    if (!PushUndefinedValues(cx, bce, blockObj->slotCount() - alreadyPushed))
-        return false;
-
-    if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK))
-        return false;
-
-    if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj))
-        return false;
-
-    return true;
-}
-
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
  * LLVM is deciding to inline this function which uses a lot of stack space
@@ -2502,15 +2440,27 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
     pn2 = pn->pn_right;
     JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST));
 
+    /*
+     * If there are hoisted let declarations, their stack slots go under the
+     * discriminant's value so push their slots now and enter the block later.
+     */
+    Rooted blockObj(cx, nullptr);
+    if (pn2->isKind(PNK_LEXICALSCOPE)) {
+        blockObj = &pn2->pn_objbox->object->as();
+        for (uint32_t i = 0; i < blockObj->slotCount(); ++i) {
+            if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+                return false;
+        }
+    }
+
     /* 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, 1))
             return false;
-
         stmtInfo.type = STMT_SWITCH;
         stmtInfo.update = top = bce->offset();
         /* Advance pn2 to refer to the switch case list. */
@@ -2796,6 +2746,7 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
     if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) {
         if (!LeaveNestedScope(cx, bce, &stmtInfo))
             return false;
+        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount());
     } else {
         if (!PopStatementBCE(cx, bce))
             return false;
@@ -3326,8 +3277,11 @@ EmitGroupAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
     for (pn = lhs->pn_head; pn; pn = pn->pn_next, ++i) {
         /* MaybeEmitGroupAssignment requires lhs->pn_count <= rhs->pn_count. */
         JS_ASSERT(i < limit);
+        uint32_t slot = i;
+        if (!AdjustBlockSlot(cx, bce, &slot))
+            return false;
 
-        if (!EmitDupAt(cx, bce, i))
+        if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce))
             return false;
 
         if (pn->isKind(PNK_ELISION)) {
@@ -4082,6 +4036,7 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
     if (ParseNode *pn2 = pn->pn_kid2) {
         // The emitted code for a catch block looks like:
         //
+        // undefined...                 as many as there are locals in the catch block
         // [pushblockscope]             only if any local aliased
         // exception
         // if there is a catchguard:
@@ -4092,12 +4047,14 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
         //   ifne POST
         //   debugleaveblock
         //   [popblockscope]            only if any local aliased
+        //   popnv    leave exception on top
         //   throwing                   pop exception to cx->exception
         //   goto 
         //   POST: pop
         // < catch block contents >
         // debugleaveblock
         // [popblockscope]              only if any local aliased
+        // popn 
         // goto    non-local; finally applies
         //
         // If there's no catch block without a catchguard, the last scopeChain and does not
  * otherwise touch the stack, evaluation of the let-var initializers must leave
@@ -4317,6 +4272,7 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
     JS_ASSERT(varList->isArity(PN_LIST));
     ParseNode *letBody = pnLet->pn_right;
     JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE));
+    Rooted blockObj(cx, &letBody->pn_objbox->object->as());
 
     int letHeadDepth = bce->stackDepth;
 
@@ -4325,8 +4281,14 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
 
     /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */
     uint32_t alreadyPushed = bce->stackDepth - letHeadDepth;
+    uint32_t blockObjCount = blockObj->slotCount();
+    for (uint32_t i = alreadyPushed; i < blockObjCount; ++i) {
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+    }
+
     StmtInfoBCE stmtInfo(cx);
-    if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed))
+    if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, letBody->pn_expr))
@@ -4335,6 +4297,10 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
     if (!LeaveNestedScope(cx, bce, &stmtInfo))
         return false;
 
+    JSOp leaveOp = letBody->getOp();
+    JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV);
+    EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount());
+
     return true;
 }
 
@@ -4346,9 +4312,19 @@ MOZ_NEVER_INLINE static bool
 EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
+    JS_ASSERT(pn->getOp() == JSOP_POPN);
 
     StmtInfoBCE stmtInfo(cx);
-    if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0))
+    ObjectBox *objbox = pn->pn_objbox;
+    StaticBlockObject &blockObj = objbox->object->as();
+    size_t slots = blockObj.slotCount();
+
+    for (size_t n = 0; n < slots; ++n) {
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+    }
+
+    if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_expr))
@@ -4357,6 +4333,8 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
     if (!LeaveNestedScope(cx, bce, &stmtInfo))
         return false;
 
+    EMIT_UINT16_IMM_OP(JSOP_POPN, slots);
+
     return true;
 }
 
@@ -4385,6 +4363,18 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
     bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
     JS_ASSERT_IF(letDecl, pn1->isLet());
 
+    Rooted
+        blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr);
+    uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0;
+
+    // For-of loops run with two values on the stack: the iterator and the
+    // current result object.  If the loop also has a lexical block, those
+    // lexicals are deeper on the stack than the iterator.
+    for (uint32_t i = 0; i < blockObjCount; ++i) {
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+    }
+
     // 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) {
@@ -4396,9 +4386,6 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
         bce->emittingForInit = false;
     }
 
-    // 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;
@@ -4421,7 +4408,7 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
     // Enter the block before the loop body, after evaluating the obj.
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2))
             return false;
     }
 
@@ -4514,8 +4501,8 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
             return false;
     }
 
-    // Pop the result and the iter.
-    EMIT_UINT16_IMM_OP(JSOP_POPN, 2);
+    // Pop result, iter, and slots from the lexical block (if any).
+    EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2);
 
     return true;
 }
@@ -4530,6 +4517,38 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
     bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
     JS_ASSERT_IF(letDecl, pn1->isLet());
 
+    Rooted
+        blockObj(cx, letDecl ? &pn1->pn_objbox->object->as() : nullptr);
+    uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0;
+
+    if (letDecl) {
+        /*
+         * The let's slot(s) will be under the iterator, but the block must not
+         * be entered until after evaluating the rhs.  So, we reserve space for
+         * the block scope now, and only push the block onto the scope chain
+         * later.  Thus, a for-let-in loop looks like:
+         *
+         *   push x N
+         *   eval rhs
+         *   iter
+         *   pushblockscope (if needed)
+         *   goto
+         *     ... loop body
+         *   ifne
+         *   debugleaveblock
+         *   popblockscope (if needed)
+         *   enditer
+         *   popn(N)
+         *
+         * Note that pushblockscope and popblockscope only get emitted if some
+         * of the variables in the block are captured.
+         */
+        for (uint32_t i = 0; i < blockObjCount; ++i) {
+            if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+                return false;
+        }
+    }
+
     /*
      * 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
@@ -4561,7 +4580,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
     /* Enter the block before the loop body, after evaluating the obj. */
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1))
             return false;
     }
 
@@ -4643,6 +4662,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
     if (letDecl) {
         if (!LeaveNestedScope(cx, bce, &letStmt))
             return false;
+        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount);
     }
 
     return true;
@@ -4934,8 +4954,7 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
         BindingIter bi(bce->script);
         while (bi->name() != fun->atom())
             bi++;
-        JS_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT ||
-                  bi->kind() == Binding::ARGUMENT);
+        JS_ASSERT(bi->kind() == VARIABLE || bi->kind() == CONSTANT || bi->kind() == ARGUMENT);
         JS_ASSERT(bi.frameIndex() < JS_BIT(20));
 #endif
         pn->pn_index = index;
@@ -6504,7 +6523,10 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
          */
         if (!EmitTree(cx, bce, pn->pn_kid))
             return false;
-        if (!EmitDupAt(cx, bce, bce->arrayCompDepth))
+        uint32_t slot = bce->arrayCompDepth;
+        if (!AdjustBlockSlot(cx, bce, &slot))
+            return false;
+        if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce))
             return false;
         if (Emit1(cx, bce, JSOP_ARRAYPUSH) < 0)
             return false;
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
index 725b3ce15dd..f93dd7a3226 100644
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -421,6 +421,8 @@ class FullParseHandler
     inline bool addCatchBlock(ParseNode *catchList, ParseNode *letBlock,
                               ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody);
 
+    inline void setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr);
+
     inline void setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *pn);
     inline ParseNode *newFunctionDefinition();
     void setFunctionBody(ParseNode *pn, ParseNode *kid) {
@@ -433,10 +435,7 @@ class FullParseHandler
     void addFunctionArgument(ParseNode *pn, ParseNode *argpn) {
         pn->pn_body->append(argpn);
     }
-
     inline ParseNode *newLexicalScope(ObjectBox *blockbox);
-    inline void setLexicalScopeBody(ParseNode *block, ParseNode *body);
-
     bool isOperationWithoutParens(ParseNode *pn, ParseNodeKind kind) {
         return pn->isKind(kind) && !pn->isInParens();
     }
@@ -598,6 +597,15 @@ FullParseHandler::addCatchBlock(ParseNode *catchList, ParseNode *letBlock,
     return true;
 }
 
+inline void
+FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr)
+{
+    JS_ASSERT(block->isOp(JSOP_POPN));
+    if (leaveBlockExpr)
+        block->setOp(JSOP_POPNV);
+    block->pn_expr = kid;
+}
+
 inline void
 FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue)
 {
@@ -626,18 +634,13 @@ FullParseHandler::newLexicalScope(ObjectBox *blockbox)
     if (!pn)
         return nullptr;
 
+    pn->setOp(JSOP_POPN);
     pn->pn_objbox = blockbox;
     pn->pn_cookie.makeFree();
     pn->pn_dflags = 0;
     return pn;
 }
 
-inline void
-FullParseHandler::setLexicalScopeBody(ParseNode *block, ParseNode *kid)
-{
-    block->pn_expr = kid;
-}
-
 inline bool
 FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op)
 {
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index d76c868a971..45df351ee5f 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -404,7 +404,8 @@ enum ParseNodeKind
  * PNK_NULL,
  * PNK_THIS
  *
- * PNK_LEXICALSCOPE name    pn_objbox: block object in ObjectBox holder
+ * PNK_LEXICALSCOPE name    pn_op: JSOP_POPN or JSOP_POPNV
+ *                          pn_objbox: block object in ObjectBox holder
  *                          pn_expr: block body
  * PNK_ARRAYCOMP    list    pn_count: 1
  *                          pn_head: list of 1 element, which is block
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index ec7032de31e..8fc7b4552ab 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -272,16 +272,16 @@ AppendPackedBindings(const ParseContext *pc, const DeclVector &vec
         Definition *dn = vec[i];
         PropertyName *name = dn->name();
 
-        Binding::Kind kind;
+        BindingKind kind;
         switch (dn->kind()) {
           case Definition::VAR:
-            kind = Binding::VARIABLE;
+            kind = VARIABLE;
             break;
           case Definition::CONST:
-            kind = Binding::CONSTANT;
+            kind = CONSTANT;
             break;
           case Definition::ARG:
-            kind = Binding::ARGUMENT;
+            kind = ARGUMENT;
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("unexpected dn->kind");
@@ -329,7 +329,7 @@ ParseContext::generateFunctionBindings(ExclusiveContext *cx, Token
     AppendPackedBindings(this, vars_, packedBindings + args_.length());
 
     return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(),
-                                              packedBindings, blockScopeDepth);
+                                              packedBindings);
 }
 
 template 
@@ -615,8 +615,7 @@ Parser::parse(JSObject *chain)
     GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption);
     ParseContext globalpc(this, /* parent = */ nullptr, ParseHandler::null(),
                                         &globalsc, /* newDirectives = */ nullptr,
-                                        /* staticLevel = */ 0, /* bodyid = */ 0,
-                                        /* blockScopeDepth = */ 0);
+                                        /* staticLevel = */ 0, /* bodyid = */ 0);
     if (!globalpc.init(tokenStream))
         return null();
 
@@ -878,8 +877,7 @@ Parser::standaloneFunctionBody(HandleFunction fun, const AutoN
     handler.setFunctionBox(fn, funbox);
 
     ParseContext funpc(this, pc, fn, funbox, newDirectives,
-                                         /* staticLevel = */ 0, /* bodyid = */ 0,
-                                         /* blockScopeDepth = */ 0);
+                                         /* staticLevel = */ 0, /* bodyid = */ 0);
     if (!funpc.init(tokenStream))
         return null();
 
@@ -2127,8 +2125,7 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
 
             ParseContext funpc(parser, outerpc, SyntaxParseHandler::null(), funbox,
                                                    newDirectives, outerpc->staticLevel + 1,
-                                                   outerpc->blockidGen,
-                                                   /* blockScopeDepth = */ 0);
+                                                   outerpc->blockidGen);
             if (!funpc.init(tokenStream))
                 return false;
 
@@ -2163,8 +2160,7 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
 
     // Continue doing a full parse for this inner function.
     ParseContext funpc(this, pc, pn, funbox, newDirectives,
-                                         outerpc->staticLevel + 1, outerpc->blockidGen,
-                                         /* blockScopeDepth = */ 0);
+                                         outerpc->staticLevel + 1, outerpc->blockidGen);
     if (!funpc.init(tokenStream))
         return false;
 
@@ -2203,8 +2199,7 @@ Parser::functionArgsAndBody(Node pn, HandleFunction fun,
 
     // Initialize early for possible flags mutation via destructuringExpr.
     ParseContext funpc(this, pc, handler.null(), funbox, newDirectives,
-                                           outerpc->staticLevel + 1, outerpc->blockidGen,
-                                           /* blockScopeDepth = */ 0);
+                                           outerpc->staticLevel + 1, outerpc->blockidGen);
     if (!funpc.init(tokenStream))
         return false;
 
@@ -2239,8 +2234,7 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st
 
     Directives newDirectives = directives;
     ParseContext funpc(this, /* parent = */ nullptr, pn, funbox,
-                                         &newDirectives, staticLevel, /* bodyid = */ 0,
-                                         /* blockScopeDepth = */ 0);
+                                         &newDirectives, staticLevel, /* bodyid = */ 0);
     if (!funpc.init(tokenStream))
         return null();
 
@@ -2694,7 +2688,7 @@ Parser::bindLet(BindData *data,
 
     Rooted blockObj(cx, data->let.blockObj);
     unsigned index = blockObj->slotCount();
-    if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) {
+    if (index >= StaticBlockObject::VAR_INDEX_LIMIT) {
         parser->report(ParseError, false, pn, data->let.overflow);
         return false;
     }
@@ -2775,33 +2769,6 @@ struct PopLetDecl {
     }
 };
 
-// We compute the maximum block scope depth, in slots, of a compilation unit at
-// parse-time.  Each nested statement has a field indicating the maximum block
-// scope depth that is nested inside it.  When we leave a nested statement, we
-// add the number of slots in the statement to the nested depth, and use that to
-// update the maximum block scope depth of the outer statement or parse
-// context.  In the end, pc->blockScopeDepth will indicate the number of slots
-// to reserve in the fixed part of a stack frame.
-//
-template 
-static void
-AccumulateBlockScopeDepth(ParseContext *pc)
-{
-    uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth;
-    StmtInfoPC *outer = pc->topStmt->down;
-
-    if (pc->topStmt->isBlockScope)
-        innerDepth += pc->topStmt->staticScope->template as().slotCount();
-
-    if (outer) {
-        if (outer->innerBlockScopeDepth < innerDepth)
-            outer->innerBlockScopeDepth = innerDepth;
-    } else {
-        if (pc->blockScopeDepth < innerDepth)
-            pc->blockScopeDepth = innerDepth;
-    }
-}
-
 template 
 static void
 PopStatementPC(TokenStream &ts, ParseContext *pc)
@@ -2809,7 +2776,6 @@ PopStatementPC(TokenStream &ts, ParseContext *pc)
     RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope);
     JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope);
 
-    AccumulateBlockScopeDepth(pc);
     FinishPopStatement(pc);
 
     if (scopeObj) {
@@ -2857,7 +2823,7 @@ LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::Stmt
             JS_ASSERT(shape->hasShortID());
 
             if (slotp)
-                *slotp = shape->shortid();
+                *slotp = blockObj.stackDepth() + shape->shortid();
             return stmt;
         }
     }
@@ -3368,7 +3334,7 @@ Parser::letBlock(LetContext letContext)
         if (!expr)
             return null();
     }
-    handler.setLexicalScopeBody(block, expr);
+    handler.setLeaveBlockResult(block, expr, letContext != LetStatement);
     PopStatementPC(tokenStream, pc);
 
     handler.setEndPosition(pnlet, pos().end);
@@ -3646,6 +3612,7 @@ Parser::letDeclaration()
             if (!pn1)
                 return null();
 
+            pn1->setOp(JSOP_POPN);
             pn1->pn_pos = pc->blockNode->pn_pos;
             pn1->pn_objbox = blockbox;
             pn1->pn_expr = pc->blockNode;
@@ -3681,6 +3648,8 @@ Parser::letStatement()
     if (tokenStream.peekToken() == TOK_LP) {
         pn = letBlock(LetStatement);
         JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI));
+        JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_POPNV,
+                     pn->pn_expr->isOp(JSOP_POPN));
     } else {
         pn = letDeclaration();
     }
@@ -6033,31 +6002,6 @@ CompExprTransplanter::transplant(ParseNode *pn)
     return true;
 }
 
-// Parsing JS1.7-style comprehensions is terrible: we parse the head expression
-// as if it's part of a comma expression, then when we see the "for" we
-// transplant the parsed expression into the inside of a constructed
-// for-of/for-in/for-each tail.  Transplanting an already-parsed expression is
-// tricky, but the CompExprTransplanter handles most of that.
-//
-// The one remaining thing to patch up is the block scope depth.  We need to
-// compute the maximum block scope depth of a function, so we know how much
-// space to reserve in the fixed part of a stack frame.  Normally this is done
-// whenever we leave a statement, via AccumulateBlockScopeDepth.  However if the
-// head has a let expression, we need to re-assign that depth to the tail of the
-// comprehension.
-//
-// Thing is, we don't actually know what that depth is, because the only
-// information we keep is the maximum nested depth within a statement, so we
-// just conservatively propagate the maximum nested depth from the top statement
-// to the comprehension tail.
-//
-template 
-static unsigned
-ComprehensionHeadBlockScopeDepth(ParseContext *pc)
-{
-    return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth;
-}
-
 /*
  * Starting from a |for| keyword after the first array initialiser element or
  * an expression in an open parenthesis, parse the tail of the comprehension
@@ -6071,8 +6015,7 @@ template <>
 ParseNode *
 Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp,
                                             ParseContext *outerpc,
-                                            ParseNodeKind kind, JSOp op,
-                                            unsigned innerBlockScopeDepth)
+                                            ParseNodeKind kind, JSOp op)
 {
     /*
      * If we saw any inner functions while processing the generator expression
@@ -6297,7 +6240,6 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bo
     pn2->pn_kid = kid;
     *pnp = pn2;
 
-    pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth;
     PopStatementPC(tokenStream, pc);
     return pn;
 }
@@ -6321,8 +6263,7 @@ Parser::arrayInitializerComprehensionTail(ParseNode *pn)
     *pn->pn_tail = nullptr;
 
     ParseNode *pntop = comprehensionTail(pnexp, pn->pn_blockid, false, nullptr,
-                                         PNK_ARRAYPUSH, JSOP_ARRAYPUSH,
-                                         ComprehensionHeadBlockScopeDepth(pc));
+                                         PNK_ARRAYPUSH, JSOP_ARRAYPUSH);
     if (!pntop)
         return false;
     pn->append(pntop);
@@ -6392,8 +6333,7 @@ Parser::generatorExpr(ParseNode *kid)
 
         ParseContext genpc(this, outerpc, genfn, genFunbox,
                                              /* newDirectives = */ nullptr,
-                                             outerpc->staticLevel + 1, outerpc->blockidGen,
-                                             /* blockScopeDepth = */ 0);
+                                             outerpc->staticLevel + 1, outerpc->blockidGen);
         if (!genpc.init(tokenStream))
             return null();
 
@@ -6411,9 +6351,7 @@ Parser::generatorExpr(ParseNode *kid)
         genFunbox->inGenexpLambda = true;
         genfn->pn_blockid = genpc.bodyid;
 
-        ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc,
-                                            PNK_SEMI, JSOP_NOP,
-                                            ComprehensionHeadBlockScopeDepth(outerpc));
+        ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc);
         if (!body)
             return null();
         JS_ASSERT(!genfn->pn_body);
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
index 532df37fd49..b4249354808 100644
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -28,9 +28,8 @@ struct StmtInfoPC : public StmtInfoBase {
     StmtInfoPC      *downScope;     /* next enclosing lexical scope */
 
     uint32_t        blockid;        /* for simplified dominance computation */
-    uint32_t        innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */
 
-    StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {}
+    StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx) {}
 };
 
 typedef HashSet FuncStmtSet;
@@ -119,7 +118,6 @@ struct ParseContext : public GenericParseContext
     bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
     bool isStarGenerator() const { return generatorKind() == StarGenerator; }
 
-    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:
@@ -137,6 +135,11 @@ struct ParseContext : public GenericParseContext
         return args_.length();
     }
 
+    uint32_t numVars() const {
+        JS_ASSERT(sc->isFunctionBox());
+        return vars_.length();
+    }
+
     /*
      * This function adds a definition to the lexical scope represented by this
      * ParseContext.
@@ -240,7 +243,7 @@ struct ParseContext : public GenericParseContext
     ParseContext(Parser *prs, GenericParseContext *parent,
                  Node maybeFunction, SharedContext *sc,
                  Directives *newDirectives,
-                 unsigned staticLevel, uint32_t bodyid, uint32_t blockScopeDepth)
+                 unsigned staticLevel, uint32_t bodyid)
       : GenericParseContext(parent, sc),
         bodyid(0),           // initialized in init()
         blockidGen(bodyid),  // used to set |bodyid| and subsequently incremented in init()
@@ -250,7 +253,6 @@ struct ParseContext : public GenericParseContext
         maybeFunction(maybeFunction),
         staticLevel(staticLevel),
         lastYieldOffset(NoYieldOffset),
-        blockScopeDepth(blockScopeDepth),
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
@@ -545,8 +547,7 @@ class Parser : private AutoGCRooter, public StrictModeGetter
     Node condition();
     Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp,
                            ParseContext *outerpc,
-                           ParseNodeKind kind, JSOp op,
-                           unsigned innerBlockScopeDepth);
+                           ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP);
     bool arrayInitializerComprehensionTail(Node pn);
     Node generatorExpr(Node kid);
     bool argumentList(Node listNode, bool *isSpread);
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
index 760d3c318d1..1e5c05fba69 100644
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -157,15 +157,14 @@ class SyntaxParseHandler
     bool addCatchBlock(Node catchList, Node letBlock,
                        Node catchName, Node catchGuard, Node catchBody) { return true; }
 
+    void setLeaveBlockResult(Node block, Node kid, bool leaveBlockExpr) {}
+
     void setLastFunctionArgumentDefault(Node funcpn, Node pn) {}
     Node newFunctionDefinition() { return NodeGeneric; }
     void setFunctionBody(Node pn, Node kid) {}
     void setFunctionBox(Node pn, FunctionBox *funbox) {}
     void addFunctionArgument(Node pn, Node argpn) {}
-
     Node newLexicalScope(ObjectBox *blockbox) { return NodeGeneric; }
-    void setLexicalScopeBody(Node block, Node body) {}
-
     bool isOperationWithoutParens(Node pn, ParseNodeKind kind) {
         // It is OK to return false here, callers should only use this method
         // for reporting strict option warnings and parsing code which the
diff --git a/js/src/jit-test/tests/basic/testBug579647.js b/js/src/jit-test/tests/basic/testBug579647.js
index 4a4d41164b3..027f643a96f 100644
--- a/js/src/jit-test/tests/basic/testBug579647.js
+++ b/js/src/jit-test/tests/basic/testBug579647.js
@@ -1,4 +1,4 @@
-expected = "TypeError: a is not a function";
+expected = "TypeError: NaN is not a function";
 actual = "";
 
 try {
@@ -10,4 +10,4 @@ try {
     actual = '' + e;
 }
 
-assertEq(actual, expected);
+assertEq(expected, actual);
diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp
index d6249f7de7a..81e129c20c9 100644
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -5269,8 +5269,7 @@ ParseFunction(ModuleCompiler &m, ParseNode **fnOut)
 
     Directives newDirectives = directives;
     AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives,
-                            outerpc->staticLevel + 1, outerpc->blockidGen,
-                            /* blockScopeDepth = */ 0);
+                            outerpc->staticLevel + 1, outerpc->blockidGen);
     if (!funpc.init(m.parser().tokenStream))
         return false;
 
diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp
index 4d18eac5f21..ba64591f700 100644
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -854,16 +854,10 @@ BaselineCompiler::emit_JSOP_POPN()
 }
 
 bool
-BaselineCompiler::emit_JSOP_DUPAT()
+BaselineCompiler::emit_JSOP_POPNV()
 {
-    frame.syncStack(0);
-
-    // DUPAT takes a value on the stack and re-pushes it on top.  It's like
-    // GETLOCAL but it addresses from the top of the stack instead of from the
-    // stack frame.
-
-    int depth = -(GET_UINT24(pc) + 1);
-    masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0);
+    frame.popRegsAndSync(1);
+    frame.popn(GET_UINT16(pc));
     frame.push(R0);
     return true;
 }
@@ -2296,7 +2290,17 @@ BaselineCompiler::emit_JSOP_INITELEM_SETTER()
 bool
 BaselineCompiler::emit_JSOP_GETLOCAL()
 {
-    frame.pushLocal(GET_LOCALNO(pc));
+    uint32_t local = GET_LOCALNO(pc);
+
+    if (local >= frame.nlocals()) {
+        // Destructuring assignments may use GETLOCAL to access stack values.
+        frame.syncStack(0);
+        masm.loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)), R0);
+        frame.push(R0);
+        return true;
+    }
+
+    frame.pushLocal(local);
     return true;
 }
 
diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h
index 3c7300b1bb1..35d85c02646 100644
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -26,7 +26,7 @@ namespace jit {
     _(JSOP_LABEL)              \
     _(JSOP_POP)                \
     _(JSOP_POPN)               \
-    _(JSOP_DUPAT)              \
+    _(JSOP_POPNV)              \
     _(JSOP_ENTERWITH)          \
     _(JSOP_LEAVEWITH)          \
     _(JSOP_DUP)                \
diff --git a/js/src/jit/BaselineFrame.h b/js/src/jit/BaselineFrame.h
index 8d0601b645e..ce4ad7f41fd 100644
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -152,8 +152,8 @@ class BaselineFrame
     }
 
     Value &unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const {
-        JS_ASSERT(i < script()->nfixedvars());
         JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i));
+        JS_ASSERT(i < script()->nfixed());
         return *valueSlot(i);
     }
 
diff --git a/js/src/jit/BaselineFrameInfo.h b/js/src/jit/BaselineFrameInfo.h
index 4454f4de242..a237f9d97c3 100644
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -243,7 +243,6 @@ class FrameInfo
         sv->setRegister(val, knownType);
     }
     inline void pushLocal(uint32_t local) {
-        JS_ASSERT(local < nlocals());
         StackValue *sv = rawPush();
         sv->setLocalSlot(local);
     }
@@ -261,7 +260,15 @@ class FrameInfo
         sv->setStack();
     }
     inline Address addressOfLocal(size_t local) const {
-        JS_ASSERT(local < nlocals());
+#ifdef DEBUG
+        if (local >= nlocals()) {
+            // GETLOCAL and SETLOCAL can be used to access stack values. This is
+            // fine, as long as they are synced.
+            size_t slot = local - nlocals();
+            JS_ASSERT(slot < stackDepth());
+            JS_ASSERT(stack[slot].kind() == StackValue::Stack);
+        }
+#endif
         return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local));
     }
     Address addressOfArg(size_t arg) const {
diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h
index 82f8b9cd169..76917670eb3 100644
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -10,7 +10,6 @@
 #include "jsfun.h"
 
 #include "jit/Registers.h"
-#include "vm/ScopeObject.h"
 
 namespace js {
 namespace jit {
@@ -61,24 +60,20 @@ 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();
         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)
+      : script_(nullptr), fun_(nullptr), osrPc_(nullptr), constructing_(false),
+        executionMode_(executionMode), scriptNeedsArgsObj_(false)
     {
         nimplicit_ = 0;
         nargs_ = 0;
-        nfixedvars_ = 0;
         nlocals_ = nlocals;
         nstack_ = 1;  /* For FunctionCompiler::pushPhiInput/popPhiOutput */
         nslots_ = nlocals_ + nstack_;
@@ -96,9 +91,6 @@ class CompileInfo
     jsbytecode *osrPc() {
         return osrPc_;
     }
-    NestedScopeObject *osrStaticScope() const {
-        return osrStaticScope_;
-    }
 
     bool hasOsrAt(jsbytecode *pc) {
         JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
@@ -163,13 +155,7 @@ class CompileInfo
     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 all local variables.  This includes "fixed
-    // vars" (see above) and also block-scoped locals.
+    // Number of slots needed for local variables.
     unsigned nlocals() const {
         return nlocals_;
     }
@@ -237,33 +223,21 @@ class CompileInfo
         return nimplicit() + nargs() + nlocals();
     }
 
-    bool isSlotAliased(uint32_t index, NestedScopeObject *staticScope) const {
+    bool isSlotAliased(uint32_t index) const {
         if (funMaybeLazy() && index == thisSlot())
             return false;
 
         uint32_t arg = index - firstArgSlot();
-        if (arg < nargs())
-            return script()->formalIsAliased(arg);
+        if (arg < nargs()) {
+            if (script()->formalIsAliased(arg))
+                return true;
+            return false;
+        }
 
-        uint32_t local = index - firstLocalSlot();
-        if (local < nlocals()) {
-            // First, check if this local is a var.
-            if (local < nfixedvars())
-                return script()->varIsAliased(local);
-
-            // Otherwise, it might be part of a block scope.
-            for (; staticScope; staticScope = staticScope->enclosingNestedScope()) {
-                if (!staticScope->is())
-                    continue;
-                StaticBlockObject &blockObj = staticScope->as();
-                if (blockObj.localOffset() < local) {
-                    if (local - blockObj.localOffset() < blockObj.slotCount())
-                        return blockObj.isAliased(local - blockObj.localOffset());
-                    return false;
-                }
-            }
-
-            // In this static scope, this var is dead.
+        uint32_t var = index - firstLocalSlot();
+        if (var < nlocals()) {
+            if (script()->varIsAliased(var))
+                return true;
             return false;
         }
 
@@ -271,13 +245,6 @@ class CompileInfo
         return false;
     }
 
-    bool isSlotAliasedAtEntry(uint32_t index) const {
-        return isSlotAliased(index, nullptr);
-    }
-    bool isSlotAliasedAtOsr(uint32_t index) const {
-        return isSlotAliased(index, osrStaticScope());
-    }
-
     bool hasArguments() const {
         return script()->argumentsHasVarBinding();
     }
@@ -302,14 +269,12 @@ class CompileInfo
   private:
     unsigned nimplicit_;
     unsigned nargs_;
-    unsigned nfixedvars_;
     unsigned nlocals_;
     unsigned nstack_;
     unsigned nslots_;
     JSScript *script_;
     JSFunction *fun_;
     jsbytecode *osrPc_;
-    NestedScopeObject *osrStaticScope_;
     bool constructing_;
     ExecutionMode executionMode_;
 
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp
index 5e65fbd723f..4b2d9e140bc 100644
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -90,18 +90,12 @@ jit::NewBaselineFrameInspector(TempAllocator *temp, BaselineFrame *frame)
 
     if (!inspector->varTypes.reserve(frame->script()->nfixed()))
         return nullptr;
-    for (size_t i = 0; i < frame->script()->nfixedvars(); i++) {
+    for (size_t i = 0; i < frame->script()->nfixed(); i++) {
         if (script->varIsAliased(i))
             inspector->varTypes.infallibleAppend(types::Type::UndefinedType());
         else
             inspector->varTypes.infallibleAppend(types::GetValueType(frame->unaliasedVar(i)));
     }
-    for (size_t i = frame->script()->nfixedvars(); i < frame->script()->nfixed(); i++) {
-        // FIXME: If this slot corresponds to a scope that is active at this PC,
-        // and the slot is unaliased, we should initialize the type from the
-        // slot value, as above.
-        inspector->varTypes.infallibleAppend(types::Type::UndefinedType());
-    }
 
     return inspector;
 }
@@ -1159,10 +1153,11 @@ IonBuilder::maybeAddOsrTypeBarriers()
         headerPhi++;
 
     for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++, headerPhi++) {
+
         // Aliased slots are never accessed, since they need to go through
         // the callobject. The typebarriers are added there and can be
-        // discarded here.
-        if (info().isSlotAliasedAtOsr(i))
+        // discared here.
+        if (info().isSlotAliased(i))
             continue;
 
         MInstruction *def = osrBlock->getSlot(i)->toInstruction();
@@ -1307,7 +1302,7 @@ IonBuilder::traverseBytecode()
             switch (op) {
               case JSOP_POP:
               case JSOP_POPN:
-              case JSOP_DUPAT:
+              case JSOP_POPNV:
               case JSOP_DUP:
               case JSOP_DUP2:
               case JSOP_PICK:
@@ -1557,9 +1552,14 @@ IonBuilder::inspectOpcode(JSOp op)
             current->pop();
         return true;
 
-      case JSOP_DUPAT:
-        current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc));
+      case JSOP_POPNV:
+      {
+        MDefinition *mins = current->pop();
+        for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++)
+            current->pop();
+        current->push(mins);
         return true;
+      }
 
       case JSOP_NEWINIT:
         if (GET_UINT8(pc) == JSProto_Array)
@@ -5837,11 +5837,11 @@ IonBuilder::newOsrPreheader(MBasicBlock *predecessor, jsbytecode *loopEntry)
     for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++) {
         MDefinition *existing = current->getSlot(i);
         MDefinition *def = osrBlock->getSlot(i);
-        JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliasedAtOsr(i), def->type() == MIRType_Value);
+        JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliased(i), def->type() == MIRType_Value);
 
         // Aliased slots are never accessed, since they need to go through
         // the callobject. No need to type them here.
-        if (info().isSlotAliasedAtOsr(i))
+        if (info().isSlotAliased(i))
             continue;
 
         def->setResultType(existing->type());
@@ -5881,7 +5881,7 @@ IonBuilder::newPendingLoopHeader(MBasicBlock *predecessor, jsbytecode *pc, bool
 
             // The value of aliased args and slots are in the callobject. So we can't
             // the value from the baseline frame.
-            if (info().isSlotAliasedAtOsr(i))
+            if (info().isSlotAliased(i))
                 continue;
 
             // Don't bother with expression stack values. The stack should be
diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp
index 2efe444bc99..4ebc8953e72 100644
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -766,7 +766,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
 
     RootedScript script(cx, script_);
     for (BindingIter bi(script); bi; bi++) {
-        if (bi->kind() == Binding::ARGUMENT)
+        if (bi->kind() == ARGUMENT)
             escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased();
         else
             escapedSlots[LocalSlot(script_, bi.frameIndex())] = allVarsAliased || bi->aliased();
@@ -928,11 +928,32 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
             break;
           }
 
-          case JSOP_GETLOCAL:
-          case JSOP_CALLLOCAL:
-          case JSOP_SETLOCAL:
-            JS_ASSERT(GET_LOCALNO(pc) < script_->nfixed());
+          case JSOP_GETLOCAL: {
+            /*
+             * Watch for uses of variables not known to be defined, and mark
+             * them as having possible uses before definitions.  Ignore GETLOCAL
+             * followed by a POP, these are generated for, e.g. 'var x;'
+             */
+            jsbytecode *next = pc + JSOP_GETLOCAL_LENGTH;
+            if (JSOp(*next) != JSOP_POP || jumpTarget(next)) {
+                uint32_t local = GET_LOCALNO(pc);
+                if (local >= script_->nfixed()) {
+                    localsAliasStack_ = true;
+                    break;
+                }
+            }
             break;
+          }
+
+          case JSOP_CALLLOCAL:
+          case JSOP_SETLOCAL: {
+            uint32_t local = GET_LOCALNO(pc);
+            if (local >= script_->nfixed()) {
+                localsAliasStack_ = true;
+                break;
+            }
+            break;
+          }
 
           case JSOP_PUSHBLOCKSCOPE:
             localsAliasStack_ = true;
@@ -1801,13 +1822,6 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
             stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1];
             break;
 
-          case JSOP_DUPAT: {
-            unsigned pickedDepth = GET_UINT24 (pc);
-            JS_ASSERT(pickedDepth < stackDepth - 1);
-            stack[stackDepth - 1].v = stack[stackDepth - 2 - pickedDepth].v;
-            break;
-          }
-
           case JSOP_SWAP:
             /* Swap is like pick 1. */
           case JSOP_PICK: {
diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp
index 89622331dc7..fc7a00a3aa1 100644
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -117,6 +117,8 @@ js::StackUses(JSScript *script, jsbytecode *pc)
     switch (op) {
       case JSOP_POPN:
         return GET_UINT16(pc);
+      case JSOP_POPNV:
+        return GET_UINT16(pc) + 1;
       default:
         /* stack: fun, this, [argc arguments] */
         JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
@@ -466,16 +468,6 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint
         }
         break;
 
-      case JSOP_DUPAT: {
-        JS_ASSERT(ndefs == 1);
-        jsbytecode *pc = script_->offsetToPC(offset);
-        unsigned n = GET_UINT24(pc);
-        JS_ASSERT(n < stackDepth);
-        if (offsetStack)
-            offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
-        break;
-      }
-
       case JSOP_SWAP:
         JS_ASSERT(ndefs == 2);
         if (offsetStack) {
@@ -840,9 +832,9 @@ ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes)
 
     if (!JSVAL_IS_PRIMITIVE(v)) {
         JSObject *obj = JSVAL_TO_OBJECT(v);
-        if (obj->is()) {
+        if (obj->is()) {
             char *source = JS_sprintf_append(nullptr, "depth %d {",
-                                             obj->as().localOffset());
+                                             obj->as().stackDepth());
             if (!source)
                 return false;
 
@@ -1033,8 +1025,7 @@ js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc,
         goto print_int;
 
       case JOF_UINT24:
-        JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY ||
-                  op == JSOP_DUPAT);
+        JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY);
         i = (int)GET_UINT24(pc);
         goto print_int;
 
@@ -1445,8 +1436,9 @@ struct ExpressionDecompiler
     bool init();
     bool decompilePCForStackOperand(jsbytecode *pc, int i);
     bool decompilePC(jsbytecode *pc);
-    JSAtom *getFixed(uint32_t slot, jsbytecode *pc);
+    JSAtom *getVar(uint32_t slot);
     JSAtom *getArg(unsigned slot);
+    JSAtom *findLetVar(jsbytecode *pc, unsigned depth);
     JSAtom *loadAtom(jsbytecode *pc);
     bool quote(JSString *s, uint32_t quote);
     bool write(const char *s);
@@ -1512,9 +1504,18 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc)
       case JSOP_GETLOCAL:
       case JSOP_CALLLOCAL: {
         uint32_t i = GET_LOCALNO(pc);
-        if (JSAtom *atom = getFixed(i, pc))
-            return write(atom);
-        return write("(intermediate value)");
+        JSAtom *atom;
+        if (i >= script->nfixed()) {
+            i -= script->nfixed();
+            JS_ASSERT(i < unsigned(parser.stackDepthAtPC(pc)));
+            atom = findLetVar(pc, i);
+            if (!atom)
+                return decompilePCForStackOperand(pc, i); // Destructing temporary
+        } else {
+            atom = getVar(i);
+        }
+        JS_ASSERT(atom);
+        return write(atom);
       }
       case JSOP_CALLALIASEDVAR:
       case JSOP_GETALIASEDVAR: {
@@ -1639,6 +1640,26 @@ ExpressionDecompiler::loadAtom(jsbytecode *pc)
     return script->getAtom(GET_UINT32_INDEX(pc));
 }
 
+JSAtom *
+ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth)
+{
+    for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) {
+        if (!chain->is())
+            continue;
+        StaticBlockObject &block = chain->as();
+        uint32_t blockDepth = block.stackDepth();
+        uint32_t blockCount = block.slotCount();
+        if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
+            for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) {
+                const Shape &shape = r.front();
+                if (shape.shortid() == int(depth - blockDepth))
+                    return JSID_TO_ATOM(shape.propid());
+            }
+        }
+    }
+    return nullptr;
+}
+
 JSAtom *
 ExpressionDecompiler::getArg(unsigned slot)
 {
@@ -1648,31 +1669,12 @@ ExpressionDecompiler::getArg(unsigned slot)
 }
 
 JSAtom *
-ExpressionDecompiler::getFixed(uint32_t slot, jsbytecode *pc)
+ExpressionDecompiler::getVar(uint32_t slot)
 {
-    if (slot < script->nfixedvars()) {
-        JS_ASSERT(fun);
-        slot += fun->nargs();
-        JS_ASSERT(slot < script->bindings.count());
-        return (*localNames)[slot].name();
-    }
-    for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) {
-        if (!chain->is())
-            continue;
-        StaticBlockObject &block = chain->as();
-        if (slot < block.localOffset())
-            continue;
-        slot -= block.localOffset();
-        if (slot >= block.slotCount())
-            return nullptr;
-        for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) {
-            const Shape &shape = r.front();
-            if (shape.shortid() == int(slot))
-                return JSID_TO_ATOM(shape.propid());
-        }
-        break;
-    }
-    return nullptr;
+    JS_ASSERT(fun);
+    slot += fun->nargs();
+    JS_ASSERT(slot < script->bindings.count());
+    return (*localNames)[slot].name();
 }
 
 bool
diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp
index 2c51a84b0b3..0bd5ac8cfb8 100644
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -72,21 +72,18 @@ Bindings::argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle binding
 bool
 Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
                                    unsigned numArgs, uint32_t numVars,
-                                   Binding *bindingArray, 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(UINT32_MAX - numArgs >= numVars);
 
     self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT;
     self->numArgs_ = numArgs;
     self->numVars_ = numVars;
-    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
@@ -145,7 +142,7 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle
 
         unsigned attrs = JSPROP_PERMANENT |
                          JSPROP_ENUMERATE |
-                         (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0);
+                         (bi->kind() == CONSTANT ? JSPROP_READONLY : 0);
         StackShape child(base, NameToId(bi->name()), slot, attrs, 0, 0);
 
         shape = cx->compartment()->propertyTree.getChild(cx, shape, child);
@@ -189,8 +186,7 @@ Bindings::clone(JSContext *cx, InternalBindingsHandle self,
      * 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.bindingArray()))
         return false;
     self->switchToScriptStorage(dstPackedBindings);
     return true;
@@ -205,7 +201,7 @@ GCMethods::initial()
 template
 static bool
 XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars,
-                  HandleScript script, unsigned numBlockScoped)
+                  HandleScript script)
 {
     JSContext *cx = xdr->cx();
 
@@ -243,15 +239,14 @@ XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, ui
                 return false;
 
             PropertyName *name = atoms[i].toString()->asAtom().asPropertyName();
-            Binding::Kind kind = Binding::Kind(u8 >> 1);
+            BindingKind kind = BindingKind(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, bindingArray))
             return false;
     }
 
@@ -486,20 +481,16 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc
 
     /* XDR arguments and vars. */
     uint16_t nargs = 0;
-    uint16_t nblocklocals = 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();
         nvars = script->bindings.numVars();
     }
     if (!xdr->codeUint16(&nargs))
         return false;
-    if (!xdr->codeUint16(&nblocklocals))
-        return false;
     if (!xdr->codeUint32(&nvars))
         return false;
 
@@ -639,7 +630,7 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc
 
     /* 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, script))
         return false;
 
     if (mode == XDR_DECODE) {
diff --git a/js/src/jsscript.h b/js/src/jsscript.h
index 55b0a83eb4f..2ffa3db20ed 100644
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -127,10 +127,19 @@ struct BlockScopeArray {
     uint32_t        length;     // Count of indexed try notes.
 };
 
+/*
+ * A "binding" is a formal, 'var' or 'const' declaration. A function's lexical
+ * scope is composed of these three kinds of bindings.
+ */
+
+enum BindingKind { ARGUMENT, VARIABLE, CONSTANT };
+
 class Binding
 {
-    // One JSScript stores one Binding per formal/variable so we use a
-    // packed-word representation.
+    /*
+     * One JSScript stores one Binding per formal/variable so we use a
+     * packed-word representation.
+     */
     uintptr_t bits_;
 
     static const uintptr_t KIND_MASK = 0x3;
@@ -138,13 +147,9 @@ class Binding
     static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT);
 
   public:
-    // A "binding" is a formal, 'var', 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) {
+    Binding(PropertyName *name, BindingKind kind, bool aliased) {
         JS_STATIC_ASSERT(CONSTANT <= KIND_MASK);
         JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0);
         JS_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0);
@@ -155,8 +160,8 @@ class Binding
         return (PropertyName *)(bits_ & NAME_MASK);
     }
 
-    Kind kind() const {
-        return Kind(bits_ & KIND_MASK);
+    BindingKind kind() const {
+        return BindingKind(bits_ & KIND_MASK);
     }
 
     bool aliased() const {
@@ -183,7 +188,6 @@ class Bindings
     HeapPtr callObjShape_;
     uintptr_t bindingArrayAndFlag_;
     uint16_t numArgs_;
-    uint16_t numBlockScoped_;
     uint32_t numVars_;
 
     /*
@@ -216,21 +220,7 @@ class Bindings
      */
     static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
                                          unsigned numArgs, uint32_t numVars,
-                                         Binding *bindingArray, unsigned numBlockScoped);
-
-    // 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) {
-        JS_ASSERT(!callObjShape_);
-        JS_ASSERT(numVars_ == 0);
-        JS_ASSERT(numBlockScoped < LOCALNO_LIMIT);
-        JS_ASSERT(numBlockScoped >= numBlockScoped_);
-        numBlockScoped_ = numBlockScoped;
-    }
+                                         Binding *bindingArray);
 
     uint8_t *switchToScriptStorage(Binding *newStorage);
 
@@ -243,10 +233,6 @@ class Bindings
 
     unsigned numArgs() const { return numArgs_; }
     uint32_t numVars() const { return numVars_; }
-    unsigned numBlockScoped() const { return numBlockScoped_; }
-    uint32_t numLocals() const { return numVars() + numBlockScoped(); }
-
-    // Return the size of the bindingArray.
     uint32_t count() const { return numArgs() + numVars(); }
 
     /* Return the initial shape of call objects created for this scope. */
@@ -938,15 +924,7 @@ class JSScript : public js::gc::BarrieredCell
 
     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 {
-        js::AutoThreadSafeAccess ts(this);
-        return function_ ? bindings.numLocals() : bindings.numBlockScoped();
-    }
-
-    // Number of fixed slots reserved for vars.  Only nonzero for function code.
-    size_t nfixedvars() const {
         js::AutoThreadSafeAccess ts(this);
         return function_ ? bindings.numVars() : 0;
     }
@@ -1596,7 +1574,7 @@ namespace js {
  * Iterator over a script's bindings (formals and variables).
  * The order of iteration is:
  *  - first, formal arguments, from index 0 to numArgs
- *  - next, variables, from index 0 to numLocals
+ *  - next, variables, from index 0 to numVars
  */
 class BindingIter
 {
@@ -1636,7 +1614,7 @@ FillBindingVector(HandleScript fromScript, BindingVector *vec);
 /*
  * Iterator over the aliased formal bindings in ascending index order. This can
  * be veiwed as a filtering of BindingIter with predicate
- *   bi->aliased() && bi->kind() == Binding::ARGUMENT
+ *   bi->aliased() && bi->kind() == ARGUMENT
  */
 class AliasedFormalIter
 {
diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h
index fecbfd99d61..4f3e275d842 100644
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -21,8 +21,7 @@ namespace js {
 
 inline
 Bindings::Bindings()
-    : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT),
-      numArgs_(0), numBlockScoped_(0), numVars_(0)
+    : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), numArgs_(0), numVars_(0)
 {}
 
 inline
diff --git a/js/src/tests/js1_8_1/regress/regress-420399.js b/js/src/tests/js1_8_1/regress/regress-420399.js
index 95aa0487290..423bfe13c2a 100644
--- a/js/src/tests/js1_8_1/regress/regress-420399.js
+++ b/js/src/tests/js1_8_1/regress/regress-420399.js
@@ -20,7 +20,7 @@ function test()
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
-  expect = "TypeError: a is undefined";
+  expect = "TypeError: undefined has no properties";
   try
   {
     (let (a=undefined) a).b = 3;
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index fdfb5f2e8c1..abb323e54fb 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1744,14 +1744,14 @@ CASE(JSOP_POPN)
     REGS.sp -= GET_UINT16(REGS.pc);
 END_CASE(JSOP_POPN)
 
-CASE(JSOP_DUPAT)
+CASE(JSOP_POPNV)
 {
-    JS_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth());
-    unsigned i = GET_UINT24(REGS.pc);
-    const Value &rref = REGS.sp[-int(i + 1)];
-    PUSH_COPY(rref);
+    JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth());
+    Value val = REGS.sp[-1];
+    REGS.sp -= GET_UINT16(REGS.pc);
+    REGS.sp[-1] = val;
 }
-END_CASE(JSOP_DUPAT)
+END_CASE(JSOP_POPNV)
 
 CASE(JSOP_SETRVAL)
     POP_RETURN_VALUE();
@@ -3352,6 +3352,9 @@ CASE(JSOP_PUSHBLOCKSCOPE)
     StaticBlockObject &blockObj = script->getObject(REGS.pc)->as();
 
     JS_ASSERT(blockObj.needsClone());
+    // FIXME: "Aliased" slots don't need to be on the stack.
+    JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
+
     // Clone block and push on scope chain.
     if (!REGS.fp()->pushBlock(cx, blockObj))
         goto error;
@@ -3367,6 +3370,9 @@ CASE(JSOP_POPBLOCKSCOPE)
     JS_ASSERT(scope && scope->is());
     StaticBlockObject &blockObj = scope->as();
     JS_ASSERT(blockObj.needsClone());
+
+    // FIXME: "Aliased" slots don't need to be on the stack.
+    JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
 #endif
 
     // Pop block from scope chain.
diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h
index 10d635699f4..45b93cd9fd6 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -100,8 +100,8 @@
     /* spreadcall variant of JSOP_EVAL */ \
     macro(JSOP_SPREADEVAL,43, "spreadeval", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \
     \
-    /* Dup the Nth value from the top. */ \
-    macro(JSOP_DUPAT,     44, "dupat",      NULL,         4,  0,  1,  JOF_UINT24) \
+    /* Pop N values, preserving top value. */ \
+    macro(JSOP_POPNV,     44, "popnv",      NULL,         3, -1,  1,  JOF_UINT16) \
     \
     macro(JSOP_UNUSED45,  45, "unused45",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED46,  46, "unused46",   NULL,         1,  0,  0,  JOF_BYTE) \
diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp
index eab64d801bf..16afe8b4769 100644
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -667,15 +667,17 @@ ClonedBlockObject::create(JSContext *cx, Handle block, Abst
     JS_ASSERT(obj->slotSpan() >= block->slotCount() + RESERVED_SLOTS);
 
     obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain()));
+    obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(block->stackDepth()));
 
     /*
      * Copy in the closed-over locals. Closed-over locals don't need
      * any fixup since the initial value is 'undefined'.
      */
     unsigned nslots = block->slotCount();
+    unsigned base = frame.script()->nfixed() + block->stackDepth();
     for (unsigned i = 0; i < nslots; ++i) {
         if (block->isAliased(i))
-            obj->as().setVar(i, frame.unaliasedLocal(block->varToLocalIndex(i)));
+            obj->as().setVar(i, frame.unaliasedLocal(base + i));
     }
 
     JS_ASSERT(obj->isDelegate());
@@ -687,9 +689,10 @@ void
 ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame)
 {
     StaticBlockObject &block = staticBlock();
+    unsigned base = frame.script()->nfixed() + block.stackDepth();
     for (unsigned i = 0; i < slotCount(); ++i) {
         if (!block.isAliased(i))
-            setVar(i, frame.unaliasedLocal(block.varToLocalIndex(i)), DONT_CHECK_ALIASING);
+            setVar(i, frame.unaliasedLocal(base + i), DONT_CHECK_ALIASING);
     }
 }
 
@@ -718,7 +721,7 @@ StaticBlockObject::addVar(ExclusiveContext *cx, Handle block
                           unsigned index, bool *redeclared)
 {
     JS_ASSERT(JSID_IS_ATOM(id) || (JSID_IS_INT(id) && JSID_TO_INT(id) == (int)index));
-    JS_ASSERT(index < LOCAL_INDEX_LIMIT);
+    JS_ASSERT(index < VAR_INDEX_LIMIT);
 
     *redeclared = false;
 
@@ -766,12 +769,16 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope,
     JSContext *cx = xdr->cx();
 
     Rooted obj(cx);
-    uint32_t count = 0, offset = 0;
+    uint32_t count = 0;
+    uint32_t depthAndCount = 0;
 
     if (mode == XDR_ENCODE) {
         obj = *objp;
+        uint32_t depth = obj->stackDepth();
+        JS_ASSERT(depth <= UINT16_MAX);
         count = obj->slotCount();
-        offset = obj->localOffset();
+        JS_ASSERT(count <= UINT16_MAX);
+        depthAndCount = (depth << 16) | uint16_t(count);
     }
 
     if (mode == XDR_DECODE) {
@@ -782,13 +789,13 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope,
         *objp = obj;
     }
 
-    if (!xdr->codeUint32(&count))
-        return false;
-    if (!xdr->codeUint32(&offset))
+    if (!xdr->codeUint32(&depthAndCount))
         return false;
 
     if (mode == XDR_DECODE) {
-        obj->setLocalOffset(offset);
+        uint32_t depth = uint16_t(depthAndCount >> 16);
+        count = uint16_t(depthAndCount);
+        obj->setStackDepth(depth);
 
         /*
          * XDR the block object's properties. We know that there are 'count'
@@ -873,7 +880,7 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, HandleinitEnclosingNestedScope(enclosingScope);
-    clone->setLocalOffset(srcBlock->localOffset());
+    clone->setStackDepth(srcBlock->stackDepth());
 
     /* Shape::Range is reverse order, so build a list in forward order. */
     AutoShapeVector shapes(cx);
@@ -1187,7 +1194,7 @@ class DebugScopeProxy : public BaseProxyHandler
             if (!bi)
                 return false;
 
-            if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) {
+            if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) {
                 uint32_t i = bi.frameIndex();
                 if (script->varIsAliased(i))
                     return false;
@@ -1209,7 +1216,7 @@ class DebugScopeProxy : public BaseProxyHandler
                         vp.set(UndefinedValue());
                 }
             } else {
-                JS_ASSERT(bi->kind() == Binding::ARGUMENT);
+                JS_ASSERT(bi->kind() == ARGUMENT);
                 unsigned i = bi.frameIndex();
                 if (script->formalIsAliased(i))
                     return false;
@@ -1259,12 +1266,12 @@ class DebugScopeProxy : public BaseProxyHandler
             if (maybeLiveScope) {
                 AbstractFramePtr frame = maybeLiveScope->frame();
                 JSScript *script = frame.script();
-                uint32_t local = block->staticBlock().varToLocalIndex(i);
+                uint32_t local = block->slotToLocalIndex(script->bindings, shape->slot());
                 if (action == GET)
                     vp.set(frame.unaliasedLocal(local));
                 else
                     frame.unaliasedLocal(local) = vp;
-                JS_ASSERT(analyze::LocalSlot(script, local) < analyze::TotalSlots(script));
+                JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script));
             } else {
                 if (action == GET)
                     vp.set(block->var(i, DONT_CHECK_ALIASING));
diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h
index c9fdbed265d..f5829292d90 100644
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -413,6 +413,20 @@ class BlockObject : public NestedScopeObject
         return propertyCountForCompilation();
     }
 
+    /*
+     * Return the local corresponding to the ith binding where i is in the
+     * range [0, slotCount()) and the return local index is in the range
+     * [script->nfixed, script->nfixed + script->nslots).
+     */
+    uint32_t slotToLocalIndex(const Bindings &bindings, uint32_t slot) {
+        JS_ASSERT(slot < RESERVED_SLOTS + slotCount());
+        return bindings.numVars() + stackDepth() + (slot - RESERVED_SLOTS);
+    }
+
+    uint32_t localIndexToSlot(const Bindings &bindings, uint32_t i) {
+        return RESERVED_SLOTS + (i - (bindings.numVars() + stackDepth()));
+    }
+
   protected:
     /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
     const Value &slotValue(unsigned i) {
@@ -426,42 +440,15 @@ class BlockObject : public NestedScopeObject
 
 class StaticBlockObject : public BlockObject
 {
-    static const unsigned LOCAL_OFFSET_SLOT = 1;
-
   public:
     static StaticBlockObject *create(ExclusiveContext *cx);
 
-    /* See StaticScopeIter comment. */
-    JSObject *enclosingStaticScope() const {
-        AutoThreadSafeAccess ts(this);
-        return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
-    }
-
     /*
-     * A refinement of enclosingStaticScope that returns nullptr if the enclosing
-     * static scope is a JSFunction.
+     * Return whether this StaticBlockObject contains a variable stored at
+     * the given stack depth (i.e., fp->base()[depth]).
      */
-    inline StaticBlockObject *enclosingBlock() const;
-
-    uint32_t localOffset() {
-        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
-    }
-
-    // Return the local corresponding to the 'var'th binding where 'var' is in the
-    // range [0, slotCount()).
-    uint32_t varToLocalIndex(uint32_t var) {
-        JS_ASSERT(var < slotCount());
-        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + var;
-    }
-
-    // Return the slot corresponding to local variable 'local', where 'local' is
-    // in the range [localOffset(), localOffset() + slotCount()).  The result is
-    // in the range [RESERVED_SLOTS, RESERVED_SLOTS + slotCount()).
-    uint32_t localIndexToSlot(uint32_t local) {
-        JS_ASSERT(local >= localOffset());
-        local -= localOffset();
-        JS_ASSERT(local < slotCount());
-        return RESERVED_SLOTS + local;
+    bool containsVarAtDepth(uint32_t depth) {
+        return depth >= stackDepth() && depth < stackDepth() + slotCount();
     }
 
     /*
@@ -495,9 +482,9 @@ class StaticBlockObject : public BlockObject
         }
     }
 
-    void setLocalOffset(uint32_t offset) {
-        JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined());
-        initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset));
+    void setStackDepth(uint32_t depth) {
+        JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined());
+        initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth));
     }
 
     /*
@@ -521,7 +508,7 @@ class StaticBlockObject : public BlockObject
      * associated Shape. If we could remove the block dependencies on shape->shortid, we could
      * remove INDEX_LIMIT.
      */
-    static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16);
+    static const unsigned VAR_INDEX_LIMIT = JS_BIT(16);
 
     static Shape *addVar(ExclusiveContext *cx, Handle block, HandleId id,
                          unsigned index, bool *redeclared);
diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h
index 5b0f6c0ea8e..f29adcb496f 100644
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -100,14 +100,13 @@ inline Value &
 StackFrame::unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i));
-    JS_ASSERT(i < script()->nfixedvars());
+    JS_ASSERT(i < script()->nfixed());
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedLocal(uint32_t i, MaybeCheckAliasing checkAliasing)
 {
-    JS_ASSERT(i < script()->nfixed());
 #ifdef DEBUG
     CheckLocalUnaliased(checkAliasing, script(), i);
 #endif
diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp
index 55fa790d826..7270db93c85 100644
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1268,8 +1268,8 @@ js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint
     if (!checkAliasing)
         return;
 
-    JS_ASSERT(i < script->nfixed());
-    if (i < script->bindings.numVars()) {
+    JS_ASSERT(i < script->nslots());
+    if (i < script->nfixed()) {
         JS_ASSERT(!script->varIsAliased(i));
     } else {
         // FIXME: The callers of this function do not easily have the PC of the
diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h
index 991fd93c16a..1b14b51488d 100644
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -23,7 +23,7 @@ namespace js {
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 167);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 166);
 
 class XDRBuffer {
   public:

From 82549fea2aeb0158868001aae67c80856bc23af1 Mon Sep 17 00:00:00 2001
From: Gregory Szorc 
Date: Wed, 12 Feb 2014 14:20:15 -0800
Subject: [PATCH 11/40] Bug 971683 - Remove tools/l10n; r=bsmedberg

--HG--
extra : rebase_source : 12c3b29df1e7333c5abc6768f853ed41291b1ddb
extra : amend_source : 7d0566143465dea662e05bd9351cb02c3eb9e8d4
---
 tools/l10n/l10n.mk |  27 ----------
 tools/l10n/l10n.py | 121 ---------------------------------------------
 2 files changed, 148 deletions(-)
 delete mode 100644 tools/l10n/l10n.mk
 delete mode 100755 tools/l10n/l10n.py

diff --git a/tools/l10n/l10n.mk b/tools/l10n/l10n.mk
deleted file mode 100644
index 1554627e1e3..00000000000
--- a/tools/l10n/l10n.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# 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/.
-
-include client.mk
-
-.PHONY : check-l10n
-
-check-l10n:
-	for loc in $(MOZ_CO_LOCALES); do \
-	for mod in $(sort $(foreach project,$(MOZ_PROJECT_LIST),$(LOCALES_$(project)))); do \
-	  echo Comparing $(TOPSRCDIR)/$$mod/locales/en-US with $(TOPSRCDIR)/../l10n/$$loc/$$mod; \
-	  perl $(TOPSRCDIR)/toolkit/locales/compare-locales.pl $(TOPSRCDIR)/$$mod/locales/en-US $(TOPSRCDIR)/../l10n/$$loc/$$mod; \
-	done; \
-	done;
-
-create-%:
-	for mod in $(sort $(foreach project,$(MOZ_PROJECT_LIST),$(LOCALES_$(project)))); do \
-	  if test -d $(TOPSRCDIR)/../l10n/$*/$$mod; then \
-	    echo $(TOPSRCDIR)/../l10n/$*/$$mod already exists; \
-	  else \
-	    echo Creating $(TOPSRCDIR)/../l10n/$*/$$mod from $(TOPSRCDIR)/$$mod/locales/en-US; \
-	    mkdir -p ../l10n/$*/$$mod; \
-	    cp -r $(TOPSRCDIR)/$$mod/locales/en-US/* $(TOPSRCDIR)/../l10n/$*/$$mod; \
-	    find $(TOPSRCDIR)/../l10n/$*/$$mod -name CVS | xargs rm -rf; \
-	  fi; \
-	done;
diff --git a/tools/l10n/l10n.py b/tools/l10n/l10n.py
deleted file mode 100755
index d7d7b215264..00000000000
--- a/tools/l10n/l10n.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/env python
-
-# 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/.
-
-import logging
-from optparse import OptionParser
-import os
-import os.path
-import re
-from subprocess import Popen, PIPE
-from shutil import copy2
-import sys
-
-def walk(base):
-  for root, dirs, files in os.walk(base):
-    try:
-      dirs.remove('CVS')
-    except ValueError:
-      pass
-    yield (os.path.normpath(root[len(base)+1:]), files)
-
-def createLocalization(source, dest, apps, exceptions = {}):
-  '''
-  Creates a new localization.
-
-  @type  source: string
-  @param source: path to the mozilla sources to use
-
-  @type  dest: string
-  @param dest: path to the localization to create or update
-
-  @type  apps: array of strings
-  @param apps: the applications for which to create or update the
-               localization
-
-  @type  exceptions: mapping
-  @param exceptions: stuff to ignore
-  '''
-
-  assert os.path.isdir(source), "source directory is not a directory"
-  clientmk = os.path.join(source,'client.mk')
-  assert os.path.isfile(clientmk), "client.mk missing"
-
-  if not apps or not len(apps):
-    apps=['browser']
-
-  if not os.path.isdir(dest):
-    os.makedirs(dest)
-
-  assert os.path.isdir(dest), "target should be a directory"
-
-  # get the directories to iterate over
-  dirs = set()
-  cmd = ['make', '-f', clientmk] + \
-        ['echo-variable-LOCALES_' + app for app in apps]
-  p = Popen(cmd, stdout = PIPE)
-  for ln in p.stdout.readlines():
-    dirs.update(ln.strip().split())
-  dirs = sorted(list(dirs))
-
-  for d in dirs:
-    assert os.path.isdir(os.path.join(source, d)), \
-           "expecting source directory %s" % d
-
-  for d in dirs:
-    logging.debug('processing %s' % d)
-    if d in exceptions and exceptions[d] == 'all':
-      continue
-
-    basepath = os.path.join(source, d, 'locales', 'en-US')
-    
-    ign_mod = {}
-    if d in exceptions:
-      ign_mod = exceptions[d]
-      logging.debug('using exceptions: %s' % str(ign_mod))
-
-    l10nbase = os.path.join(dest, d)
-    if not os.path.isdir(l10nbase):
-      os.makedirs(l10nbase)
-    
-    for root, files in walk(basepath):
-      ignore = None
-      if root in ign_mod:
-        if ign_mod[root] == '.':
-          continue
-        ignore = re.compile(ign_mod[root])
-      l10npath = os.path.join(l10nbase, root)
-      if not os.path.isdir(l10npath):
-        os.mkdir(l10npath)
-
-      for f in files:
-        if ignore and ignore.search(f):
-          # ignoring some files
-          continue
-        if not os.path.exists(os.path.join(l10npath,f)):
-          copy2(os.path.join(basepath, root, f), l10npath)
-
-if __name__ == '__main__':
-  p = OptionParser()
-  p.add_option('--source', default = '.',
-               help='Mozilla sources')
-  p.add_option('--dest', default = '../l10n',
-               help='Localization target directory')
-  p.add_option('--app', action="append",
-               help='Create localization for this application ' + \
-               '(multiple applications allowed, default: browser)')
-  p.add_option('-v', '--verbose', action="store_true", default=False,
-               help='report debugging information')
-  (opts, args) = p.parse_args()
-  if opts.verbose:
-    logging.basicConfig(level=logging.DEBUG)
-  assert len(args) == 1, "language code expected"
-  
-  # hardcoding exceptions, work for both trunk and 1.8 branch
-  exceptions = {'browser':
-                {'searchplugins': '\\.xml$'},
-                'extensions/spellcheck': 'all'}
-  createLocalization(opts.source, os.path.join(opts.dest, args[0]),
-                     opts.app, exceptions=exceptions)

From 2619346f9f71e0780e48ddf19d600ef54b8baa35 Mon Sep 17 00:00:00 2001
From: Jeff Walden 
Date: Thu, 6 Feb 2014 22:17:07 -0800
Subject: [PATCH 12/40] Bug 969165 - Convert Atomic where T != bool but is
 used as a bool over to Atomic, now that it's supported, in
 security/manager/.  r=bsmith

--HG--
extra : rebase_source : 3632af6471e41d099a0948542d26a7df527efaad
---
 security/manager/ssl/src/SharedSSLState.cpp | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/security/manager/ssl/src/SharedSSLState.cpp b/security/manager/ssl/src/SharedSSLState.cpp
index 698efe914ce..3ff2d684e73 100644
--- a/security/manager/ssl/src/SharedSSLState.cpp
+++ b/security/manager/ssl/src/SharedSSLState.cpp
@@ -27,8 +27,8 @@ using mozilla::unused;
 
 namespace {
 
-static Atomic sCertOverrideSvcExists(0);
-static Atomic sCertDBExists(0);
+static Atomic sCertOverrideSvcExists(false);
+static Atomic sCertDBExists(false);
 
 class MainThreadClearer : public SyncRunnableBase
 {
@@ -40,9 +40,9 @@ public:
     // is in progress. We want to avoid this, since they do not handle the situation well,
     // hence the flags to avoid instantiating the services if they don't already exist.
 
-    bool certOverrideSvcExists = (bool)sCertOverrideSvcExists.exchange(0);
+    bool certOverrideSvcExists = sCertOverrideSvcExists.exchange(false);
     if (certOverrideSvcExists) {
-      sCertOverrideSvcExists = 1;
+      sCertOverrideSvcExists = true;
       nsCOMPtr icos = do_GetService(NS_CERTOVERRIDE_CONTRACTID);
       if (icos) {
         icos->ClearValidityOverride(
@@ -51,9 +51,9 @@ public:
       }
     }
 
-    bool certDBExists = (bool)sCertDBExists.exchange(0);
+    bool certDBExists = sCertDBExists.exchange(false);
     if (certDBExists) {
-      sCertDBExists = 1;
+      sCertDBExists = true;
       nsCOMPtr certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
       if (certdb) {
         nsCOMPtr badCerts;
@@ -206,13 +206,13 @@ SharedSSLState::GlobalCleanup()
 /*static*/ void
 SharedSSLState::NoteCertOverrideServiceInstantiated()
 {
-  sCertOverrideSvcExists = 1;
+  sCertOverrideSvcExists = true;
 }
 
 /*static*/ void
 SharedSSLState::NoteCertDBServiceInstantiated()
 {
-  sCertDBExists = 1;
+  sCertDBExists = true;
 }
 
 void

From dc4d5d1f248ad84cfdeaf5285ab47e280b5edb27 Mon Sep 17 00:00:00 2001
From: Jeff Walden 
Date: Thu, 6 Feb 2014 22:17:07 -0800
Subject: [PATCH 13/40] Bug 969165 - Convert Atomic where T != bool but is
 used as a bool over to Atomic, now that it's supported, in widget/. 
 r=vlad

--HG--
extra : rebase_source : 29bf1f696e8952ea7056d2417522dcc0f88cecc4
---
 widget/xpwidgets/nsBaseAppShell.cpp | 10 +++++-----
 widget/xpwidgets/nsBaseAppShell.h   |  4 ++--
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/widget/xpwidgets/nsBaseAppShell.cpp b/widget/xpwidgets/nsBaseAppShell.cpp
index 1193a2a9e50..5bfebfa42e2 100644
--- a/widget/xpwidgets/nsBaseAppShell.cpp
+++ b/widget/xpwidgets/nsBaseAppShell.cpp
@@ -23,7 +23,7 @@ nsBaseAppShell::nsBaseAppShell()
   , mEventloopNestingLevel(0)
   , mBlockedWait(nullptr)
   , mFavorPerf(0)
-  , mNativeEventPending(0)
+  , mNativeEventPending(false)
   , mStarvationDelay(0)
   , mSwitchTime(0)
   , mLastNativeEventTime(0)
@@ -61,7 +61,7 @@ nsBaseAppShell::Init()
 void
 nsBaseAppShell::NativeEventCallback()
 {
-  if (!mNativeEventPending.exchange(0))
+  if (!mNativeEventPending.exchange(false))
     return;
 
   // If DoProcessNextNativeEvent is on the stack, then we assume that we can
@@ -106,7 +106,7 @@ nsBaseAppShell::NativeEventCallback()
 }
 
 // Note, this is currently overidden on windows, see comments in nsAppShell for
-// details. 
+// details.
 void
 nsBaseAppShell::DoProcessMoreGeckoEvents()
 {
@@ -225,7 +225,7 @@ nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
   if (mBlockNativeEvent)
     return NS_OK;
 
-  if (mNativeEventPending.exchange(1))
+  if (mNativeEventPending.exchange(true))
     return NS_OK;
 
   // Returns on the main thread in NativeEventCallback above
@@ -402,7 +402,7 @@ nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
                                       uint32_t recursionDepth,
                                       bool eventWasProcessed)
 {
-  // We've just finished running an event, so we're in a stable state. 
+  // We've just finished running an event, so we're in a stable state.
   RunSyncSections(true, recursionDepth);
   return NS_OK;
 }
diff --git a/widget/xpwidgets/nsBaseAppShell.h b/widget/xpwidgets/nsBaseAppShell.h
index c976a2286ee..3cf5f0a5a13 100644
--- a/widget/xpwidgets/nsBaseAppShell.h
+++ b/widget/xpwidgets/nsBaseAppShell.h
@@ -120,7 +120,7 @@ private:
    */
   bool *mBlockedWait;
   int32_t mFavorPerf;
-  mozilla::Atomic mNativeEventPending;
+  mozilla::Atomic mNativeEventPending;
   PRIntervalTime mStarvationDelay;
   PRIntervalTime mSwitchTime;
   PRIntervalTime mLastNativeEventTime;
@@ -147,7 +147,7 @@ private:
    * Tracks whether we have processed any gecko events in NativeEventCallback so
    * that we can avoid erroneously entering a blocking loop waiting for gecko
    * events to show up during OnProcessNextEvent.  This is required because on
-   * OS X ProcessGeckoEvents may be invoked inside the context of 
+   * OS X ProcessGeckoEvents may be invoked inside the context of
    * ProcessNextNativeEvent and may result in NativeEventCallback being invoked
    * and in turn invoking NS_ProcessPendingEvents.  Because
    * ProcessNextNativeEvent may be invoked prior to the NS_HasPendingEvents

From cea21bc5a90a293eccc30b01f4249848d5f6c49b Mon Sep 17 00:00:00 2001
From: Jeff Walden 
Date: Wed, 12 Feb 2014 13:21:16 -0800
Subject: [PATCH 14/40] Bug 961494 - Adjust an assertion to properly handle
 objects with built-in properties stored in reserved slots, where the last
 property of such an object may use a reserved slot that's not the last
 reserved slot.  r=jorendorff, f=bhackett

---
 js/src/jsapi-tests/moz.build                  |  1 +
 .../testFreshGlobalEvalRedefinition.cpp       | 47 +++++++++++++++++++
 .../Exceptions/error-expando-reconfigure.js   | 28 +++++++++++
 js/src/vm/Shape.cpp                           | 14 +++++-
 4 files changed, 88 insertions(+), 2 deletions(-)
 create mode 100644 js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
 create mode 100644 js/src/tests/ecma_5/Exceptions/error-expando-reconfigure.js

diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build
index 4bd7d131dbb..35c99aefa02 100644
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -29,6 +29,7 @@ UNIFIED_SOURCES += [
     'testException.cpp',
     'testExternalStrings.cpp',
     'testFindSCCs.cpp',
+    'testFreshGlobalEvalRedefinition.cpp',
     'testFuncCallback.cpp',
     'testFunctionProperties.cpp',
     'testGCExactRooting.cpp',
diff --git a/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
new file mode 100644
index 00000000000..58489a1f0c2
--- /dev/null
+++ b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* 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/. */
+
+#include "jsapi-tests/tests.h"
+
+static bool
+GlobalEnumerate(JSContext *cx, JS::Handle obj)
+{
+    return JS_EnumerateStandardClasses(cx, obj);
+}
+
+static bool
+GlobalResolve(JSContext *cx, JS::Handle obj, JS::Handle id)
+{
+    bool resolved = false;
+    return JS_ResolveStandardClass(cx, obj, id, &resolved);
+}
+
+BEGIN_TEST(testRedefineGlobalEval)
+{
+    static const JSClass cls = {
+        "global", JSCLASS_GLOBAL_FLAGS,
+        JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
+        GlobalEnumerate, GlobalResolve, JS_ConvertStub
+    };
+
+    /* Create the global object. */
+    JS::CompartmentOptions options;
+    options.setVersion(JSVERSION_LATEST);
+    JS::Rooted g(cx, JS_NewGlobalObject(cx, &cls, nullptr, JS::FireOnNewGlobalHook, options));
+    if (!g)
+        return false;
+
+    JSAutoCompartment ac(cx, g);
+    JS::Rooted v(cx);
+    CHECK(JS_GetProperty(cx, g, "Object", &v));
+
+    static const char data[] = "Object.defineProperty(this, 'eval', { configurable: false });";
+    CHECK(JS_EvaluateScript(cx, g, data, mozilla::ArrayLength(data) - 1, __FILE__, __LINE__, v.address()));
+
+    return true;
+}
+END_TEST(testRedefineGlobalEval)
diff --git a/js/src/tests/ecma_5/Exceptions/error-expando-reconfigure.js b/js/src/tests/ecma_5/Exceptions/error-expando-reconfigure.js
new file mode 100644
index 00000000000..c70ff5f853b
--- /dev/null
+++ b/js/src/tests/ecma_5/Exceptions/error-expando-reconfigure.js
@@ -0,0 +1,28 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = "error-expando-reconfigure.js"
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 961494;
+var summary =
+  "Reconfiguring the first expando property added to an Error object " +
+  "shouldn't assert";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var err = new Error(); // no message argument => no err.message property
+err.expando = 17;
+Object.defineProperty(err, "expando", { configurable: false });
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp
index 440a7e7232b..ef033ceeccf 100644
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -352,10 +352,20 @@ JSObject::getChildPropertyOnDictionary(ThreadSafeContext *cx, JS::HandleObject o
                 return nullptr;
             child.setSlot(slot);
         } else {
-            /* Slots can only be allocated out of order on objects in dictionary mode. */
+            /*
+             * Slots can only be allocated out of order on objects in
+             * dictionary mode.  Otherwise the child's slot must be after the
+             * parent's slot (if it has one), because slot number determines
+             * slot span for objects with that shape.  Usually child slot
+             * *immediately* follows parent slot, but there may be a slot gap
+             * when the object uses some -- but not all -- of its reserved
+             * slots to store properties.
+             */
             JS_ASSERT(obj->inDictionaryMode() ||
                       parent->hasMissingSlot() ||
-                      child.slot() == parent->maybeSlot() + 1);
+                      child.slot() == parent->maybeSlot() + 1 ||
+                      (parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) &&
+                       child.slot() == JSSLOT_FREE(obj->getClass())));
         }
     }
 

From e42c89316bd93da0dd15978500d690d7c28d858d Mon Sep 17 00:00:00 2001
From: Andrew McCreight 
Date: Wed, 12 Feb 2014 15:19:32 -0800
Subject: [PATCH 15/40] Bug 733636, part 1 - Change the
 nsContentUtils::WrapNative aAllowWrapping default to true. r=bholley

---
 content/base/public/nsContentUtils.h       | 9 ++++-----
 content/base/src/nsContentUtils.cpp        | 2 +-
 content/base/src/nsDocument.cpp            | 8 +++++---
 content/base/src/nsFrameMessageManager.cpp | 4 ++--
 content/base/src/nsXMLHttpRequest.cpp      | 6 ++----
 dom/base/Navigator.cpp                     | 2 +-
 dom/xbl/nsXBLProtoImpl.cpp                 | 3 ++-
 dom/xbl/nsXBLPrototypeHandler.cpp          | 3 +--
 js/xpconnect/public/nsTArrayHelpers.h      | 2 +-
 9 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h
index a5db1874c64..f36934b398b 100644
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1640,17 +1640,16 @@ public:
   MOZ_WARN_UNUSED_RESULT
   static nsresult WrapNative(JSContext *cx, JS::Handle scope,
                              nsISupports *native, const nsIID* aIID,
-                             JS::MutableHandle vp,
-                             bool aAllowWrapping = false)
+                             JS::MutableHandle vp)
   {
-    return WrapNative(cx, scope, native, nullptr, aIID, vp, aAllowWrapping);
+    return WrapNative(cx, scope, native, nullptr, aIID, vp, true);
   }
 
   // Same as the WrapNative above, but use this one if aIID is nsISupports' IID.
   MOZ_WARN_UNUSED_RESULT
   static nsresult WrapNative(JSContext *cx, JS::Handle scope,
                              nsISupports *native, JS::MutableHandle vp,
-                             bool aAllowWrapping = false)
+                             bool aAllowWrapping = true)
   {
     return WrapNative(cx, scope, native, nullptr, nullptr, vp, aAllowWrapping);
   }
@@ -1659,7 +1658,7 @@ public:
   static nsresult WrapNative(JSContext *cx, JS::Handle scope,
                              nsISupports *native, nsWrapperCache *cache,
                              JS::MutableHandle vp,
-                             bool aAllowWrapping = false)
+                             bool aAllowWrapping = true)
   {
     return WrapNative(cx, scope, native, cache, nullptr, vp, aAllowWrapping);
   }
diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp
index 0d29e400111..0266f88b3da 100644
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5672,7 +5672,7 @@ nsContentUtils::CreateBlobBuffer(JSContext* aCx,
     return NS_ERROR_OUT_OF_MEMORY;
   }
   JS::Rooted scope(aCx, JS::CurrentGlobalOrNull(aCx));
-  return nsContentUtils::WrapNative(aCx, scope, blob, aBlob, true);
+  return nsContentUtils::WrapNative(aCx, scope, blob, aBlob);
 }
 
 void
diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp
index 687d901f657..b4f104101cb 100644
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -11447,7 +11447,7 @@ nsDocument::Evaluate(const nsAString& aExpression, nsIDOMNode* aContextNode,
 {
   return XPathEvaluator()->Evaluate(aExpression, aContextNode, aResolver, aType,
                                     aInResult, aResult);
-} 
+}
 
 // This is just a hack around the fact that window.document is not
 // [Unforgeable] yet.
@@ -11477,13 +11477,15 @@ nsIDocument::WrapObject(JSContext *aCx, JS::Handle aScope)
   JS::Rooted winVal(aCx);
   nsresult rv = nsContentUtils::WrapNative(aCx, obj, win,
                                            &NS_GET_IID(nsIDOMWindow),
-                                           &winVal,
-                                           false);
+                                           &winVal);
   if (NS_FAILED(rv)) {
     Throw(aCx, rv);
     return nullptr;
   }
 
+  MOZ_ASSERT(&winVal.toObject() == js::UncheckedUnwrap(&winVal.toObject()),
+             "WrapNative shouldn't create a cross-compartment wrapper");
+
   NS_NAMED_LITERAL_STRING(doc_str, "document");
 
   if (!JS_DefineUCProperty(aCx, JSVAL_TO_OBJECT(winVal), doc_str.get(),
diff --git a/content/base/src/nsFrameMessageManager.cpp b/content/base/src/nsFrameMessageManager.cpp
index a98441a1486..50a26508d5c 100644
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -927,7 +927,7 @@ nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
 
       JS::Rooted targetv(cx);
       JS::Rooted global(cx, JS_GetGlobalForObject(cx, object));
-      nsresult rv = nsContentUtils::WrapNative(cx, global, aTarget, &targetv, true);
+      nsresult rv = nsContentUtils::WrapNative(cx, global, aTarget, &targetv);
       NS_ENSURE_SUCCESS(rv, rv);
 
       JS::Rooted cpows(cx);
@@ -1018,7 +1018,7 @@ nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
           defaultThisValue = aTarget;
         }
         JS::Rooted global(cx, JS_GetGlobalForObject(cx, object));
-        nsresult rv = nsContentUtils::WrapNative(cx, global, defaultThisValue, &thisValue, true);
+        nsresult rv = nsContentUtils::WrapNative(cx, global, defaultThisValue, &thisValue);
         NS_ENSURE_SUCCESS(rv, rv);
       } else {
         // If the listener is a JS object which has receiveMessage function:
diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp
index b6e4ffc4396..853fd909cf1 100644
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -992,8 +992,7 @@ nsXMLHttpRequest::GetResponse(JSContext* aCx, ErrorResult& aRv)
 
     JS::Rooted result(aCx, JSVAL_NULL);
     JS::Rooted scope(aCx, JS::CurrentGlobalOrNull(aCx));
-    aRv = nsContentUtils::WrapNative(aCx, scope, mResponseBlob, &result,
-                                     true);
+    aRv = nsContentUtils::WrapNative(aCx, scope, mResponseBlob, &result);
     return result;
   }
   case XML_HTTP_RESPONSE_TYPE_DOCUMENT:
@@ -1004,8 +1003,7 @@ nsXMLHttpRequest::GetResponse(JSContext* aCx, ErrorResult& aRv)
 
     JS::Rooted scope(aCx, JS::CurrentGlobalOrNull(aCx));
     JS::Rooted result(aCx, JSVAL_NULL);
-    aRv = nsContentUtils::WrapNative(aCx, scope, mResponseXML, &result,
-                                     true);
+    aRv = nsContentUtils::WrapNative(aCx, scope, mResponseXML, &result);
     return result;
   }
   case XML_HTTP_RESPONSE_TYPE_JSON:
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index 6c7a81ee69d..0bca612c5e7 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1602,7 +1602,7 @@ Navigator::DoNewResolve(JSContext* aCx, JS::Handle aObject,
   }
 
   if (JSVAL_IS_PRIMITIVE(prop_val) && !JSVAL_IS_NULL(prop_val)) {
-    rv = nsContentUtils::WrapNative(aCx, aObject, native, &prop_val, true);
+    rv = nsContentUtils::WrapNative(aCx, aObject, native, &prop_val);
 
     if (NS_FAILED(rv)) {
       return Throw(aCx, rv);
diff --git a/dom/xbl/nsXBLProtoImpl.cpp b/dom/xbl/nsXBLProtoImpl.cpp
index b998e68f94f..acec3a0b189 100644
--- a/dom/xbl/nsXBLProtoImpl.cpp
+++ b/dom/xbl/nsXBLProtoImpl.cpp
@@ -178,7 +178,8 @@ nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
   bool defineOnGlobal = dom::XULElementBinding::ConstructorEnabled(cx, global);
   dom::XULElementBinding::GetConstructorObject(cx, global, defineOnGlobal);
 
-  rv = nsContentUtils::WrapNative(cx, global, aBoundElement, &v);
+  rv = nsContentUtils::WrapNative(cx, global, aBoundElement, &v,
+                                  /* aAllowWrapping = */ false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   JS::Rooted value(cx, &v.toObject());
diff --git a/dom/xbl/nsXBLPrototypeHandler.cpp b/dom/xbl/nsXBLPrototypeHandler.cpp
index 6dece96626c..789b64a7cbd 100644
--- a/dom/xbl/nsXBLPrototypeHandler.cpp
+++ b/dom/xbl/nsXBLPrototypeHandler.cpp
@@ -307,8 +307,7 @@ nsXBLPrototypeHandler::ExecuteHandler(EventTarget* aTarget,
   // scope if one doesn't already exist, and potentially wraps it cross-
   // compartment into our scope (via aAllowWrapping=true).
   JS::Rooted targetV(cx, JS::UndefinedValue());
-  rv = nsContentUtils::WrapNative(cx, scopeObject, scriptTarget, &targetV,
-                                  /* aAllowWrapping = */ true);
+  rv = nsContentUtils::WrapNative(cx, scopeObject, scriptTarget, &targetV);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Next, clone the generic handler to be parented to the target.
diff --git a/js/xpconnect/public/nsTArrayHelpers.h b/js/xpconnect/public/nsTArrayHelpers.h
index e072a6a8282..374e964aa91 100644
--- a/js/xpconnect/public/nsTArrayHelpers.h
+++ b/js/xpconnect/public/nsTArrayHelpers.h
@@ -32,7 +32,7 @@ nsTArrayToJSArray(JSContext* aCx, const nsTArray& aSourceArray,
     NS_ENSURE_SUCCESS(rv, rv);
 
     JS::RootedValue wrappedVal(aCx);
-    rv = nsContentUtils::WrapNative(aCx, global, obj, &wrappedVal, true);
+    rv = nsContentUtils::WrapNative(aCx, global, obj, &wrappedVal);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!JS_SetElement(aCx, arrayObj, index, wrappedVal)) {

From 58c4c1b64c8e3e7150e7cf4e63894de405187bfd Mon Sep 17 00:00:00 2001
From: Andrew McCreight 
Date: Wed, 12 Feb 2014 15:19:32 -0800
Subject: [PATCH 16/40] Bug 733636, part 2 - nsXPConnect::WrapNative should
 pass aAllowWrapping=true. r=bholley

---
 js/xpconnect/src/nsXPConnect.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp
index 14409e66a19..c672cbabc32 100644
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -548,7 +548,7 @@ nsXPConnect::WrapNative(JSContext * aJSContext,
     RootedObject aScope(aJSContext, aScopeArg);
     RootedValue v(aJSContext);
     return NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
-                                    false, &v, aHolder);
+                                    true, &v, aHolder);
 }
 
 /* void wrapNativeToJSVal (in JSContextPtr aJSContext, in JSObjectPtr aScope, in nsISupports aCOMObj, in nsIIDPtr aIID, out jsval aVal, out nsIXPConnectJSObjectHolder aHolder); */

From 3d72becf3362f42e649ce465533b66f6871bbbc2 Mon Sep 17 00:00:00 2001
From: Neil Rashbrook 
Date: Thu, 13 Feb 2014 00:08:57 +0000
Subject: [PATCH 17/40] Bug 966911 Part 2: An AString out param should use a
 short lived string instead of wastefully creating an nsAutoString on the heap
 r=bholley

---
 js/xpconnect/src/XPCWrappedNative.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp
index f3e16887e33..2035f59a21d 100644
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -2367,10 +2367,10 @@ CallMethodHelper::HandleDipperParam(nsXPTCVariant* dp,
                type_tag == nsXPTType::T_CSTRING,
                "Unexpected dipper type!");
 
-    // ASTRING and DOMSTRING are very similar, and both use nsAutoString.
+    // ASTRING and DOMSTRING are very similar, and both use nsString.
     // UTF8_STRING and CSTRING are also quite similar, and both use nsCString.
     if (type_tag == nsXPTType::T_ASTRING || type_tag == nsXPTType::T_DOMSTRING)
-        dp->val.p = new nsAutoString();
+        dp->val.p = nsXPConnect::GetRuntimeInstance()->NewShortLivedString();
     else
         dp->val.p = new nsCString();
 

From df10f3f2af230b86971d41e67e503f9817dede46 Mon Sep 17 00:00:00 2001
From: Neil Rashbrook 
Date: Thu, 13 Feb 2014 00:26:28 +0000
Subject: [PATCH 18/40] Bug 966911 Part 3: Passing undefined to an AString
 param should (like ACString) convert to null, not the empty string r=bholley

---
 js/xpconnect/src/XPCConvert.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp
index feea642bda3..9e4fc5abec3 100644
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -473,7 +473,7 @@ XPCConvert::JSData2Native(void* d, HandleValue s,
     {
         if (JSVAL_IS_VOID(s)) {
             if (useAllocator)
-                *((const nsAString**)d) = &EmptyString();
+                *((const nsAString**)d) = &NullString();
             else
                 (**((nsAString**)d)).SetIsVoid(true);
             return true;

From b26e16149d5a3d19b6fd7e1f71d7ad01acad3983 Mon Sep 17 00:00:00 2001
From: Bas Schouten 
Date: Thu, 13 Feb 2014 02:59:12 +0100
Subject: [PATCH 19/40] Bug 806406: Remove some lingering references to
 gfxD2DSurface. r=jrmuizel

---
 widget/windows/nsWindow.cpp          | 23 ----------------------
 widget/windows/nsWindow.h            |  8 --------
 widget/windows/nsWindowGfx.cpp       | 29 +---------------------------
 widget/windows/winrt/FrameworkView.h |  1 -
 4 files changed, 1 insertion(+), 60 deletions(-)

diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
index f29699fddda..ccde0dcca72 100644
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -3343,11 +3343,6 @@ nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
 
 gfxASurface *nsWindow::GetThebesSurface()
 {
-#ifdef CAIRO_HAS_D2D_SURFACE
-  if (mD2DWindowSurface) {
-    return mD2DWindowSurface;
-  }
-#endif
   if (mPaintDC)
     return (new gfxWindowsSurface(mPaintDC));
 
@@ -6442,13 +6437,6 @@ void nsWindow::OnDestroy()
 // Send a resize message to the listener
 bool nsWindow::OnResize(nsIntRect &aWindowRect)
 {
-#ifdef CAIRO_HAS_D2D_SURFACE
-  if (mD2DWindowSurface) {
-    mD2DWindowSurface = nullptr;
-    Invalidate();
-  }
-#endif
-
   bool result = mWidgetListener ?
                 mWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height) : false;
 
@@ -6836,14 +6824,6 @@ nsresult nsWindow::UpdateTranslucentWindow()
     ::UpdateLayeredWindow(hWnd, nullptr, (POINT*)&winRect, &winSize, mMemoryDC,
                           &srcPos, 0, &bf, ULW_ALPHA);
 
-#ifdef CAIRO_HAS_D2D_SURFACE
-  if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
-      gfxWindowsPlatform::RENDER_DIRECT2D) {
-    nsIntRect r(0, 0, 0, 0);
-    static_cast(mTransparentSurface.get())->ReleaseDC(&r);
-  }
-#endif
-
   if (!updateSuccesful) {
     return NS_ERROR_FAILURE;
   }
@@ -7091,9 +7071,6 @@ BOOL CALLBACK nsWindow::ClearResourcesCallback(HWND aWnd, LPARAM aMsg)
 void
 nsWindow::ClearCachedResources()
 {
-#ifdef CAIRO_HAS_D2D_SURFACE
-    mD2DWindowSurface = nullptr;
-#endif
     if (mLayerManager &&
         mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
       mLayerManager->ClearCachedResources();
diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
index cc41f98d93a..91229de428f 100644
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -29,10 +29,6 @@
 #include "mozilla/TimeStamp.h"
 #include "nsMargin.h"
 
-#ifdef CAIRO_HAS_D2D_SURFACE
-#include "gfxD2DSurface.h"
-#endif
-
 #include "nsWinGesture.h"
 
 #include "WindowHook.h"
@@ -548,10 +544,6 @@ protected:
 
   nsIntRect             mLastPaintBounds;
 
-#ifdef CAIRO_HAS_D2D_SURFACE
-  nsRefPtr    mD2DWindowSurface; // Surface for this window.
-#endif
-
   // Transparency
 #ifdef MOZ_XUL
   // Use layered windows to support full 256 level alpha translucency
diff --git a/widget/windows/nsWindowGfx.cpp b/widget/windows/nsWindowGfx.cpp
index 8eb49da8677..4579f52d9ef 100644
--- a/widget/windows/nsWindowGfx.cpp
+++ b/widget/windows/nsWindowGfx.cpp
@@ -323,27 +323,6 @@ bool nsWindow::OnPaint(HDC aDC, uint32_t aNestingLevel)
           }
 #endif
 
-#ifdef CAIRO_HAS_D2D_SURFACE
-          if (!targetSurface &&
-              IsRenderMode(gfxWindowsPlatform::RENDER_DIRECT2D))
-          {
-            if (!mD2DWindowSurface) {
-              gfxContentType content = gfxContentType::COLOR;
-#if defined(MOZ_XUL)
-              if (mTransparencyMode != eTransparencyOpaque) {
-                content = gfxContentType::COLOR_ALPHA;
-              }
-#endif
-              mD2DWindowSurface = new gfxD2DSurface(mWnd, content);
-            }
-            if (!mD2DWindowSurface->CairoStatus()) {
-              targetSurface = mD2DWindowSurface;
-            } else {
-              mD2DWindowSurface = nullptr;
-            }
-          }
-#endif
-
           nsRefPtr targetSurfaceWin;
           if (!targetSurface &&
               (IsRenderMode(gfxWindowsPlatform::RENDER_GDI) ||
@@ -451,13 +430,7 @@ bool nsWindow::OnPaint(HDC aDC, uint32_t aNestingLevel)
             UpdateTranslucentWindow();
           } else
 #endif
-#ifdef CAIRO_HAS_D2D_SURFACE
-          if (result) {
-            if (mD2DWindowSurface) {
-              mD2DWindowSurface->Present();
-            }
-          }
-#endif
+
           if (result) {
             if (IsRenderMode(gfxWindowsPlatform::RENDER_IMAGE_STRETCH24) ||
                 IsRenderMode(gfxWindowsPlatform::RENDER_IMAGE_STRETCH32))
diff --git a/widget/windows/winrt/FrameworkView.h b/widget/windows/winrt/FrameworkView.h
index d95d8e1ad20..3d7748a9b1c 100644
--- a/widget/windows/winrt/FrameworkView.h
+++ b/widget/windows/winrt/FrameworkView.h
@@ -8,7 +8,6 @@
 #include "MetroWidget.h"
 #include "MetroInput.h"
 #include "gfxWindowsPlatform.h"
-#include "gfxD2DSurface.h"
 #include "nsDataHashtable.h"
 
 #include "mozwrlbase.h"

From fcdbc60cd4f918e6fb113a1fcedd19d81e012967 Mon Sep 17 00:00:00 2001
From: Jan Steffens 
Date: Wed, 12 Feb 2014 18:17:52 -0800
Subject: [PATCH 20/40] Bug 806917 followup: Fix gstreamer chunk of
 configure.in to handle --disable-gstreamer properly. r=gps

---
 configure.in | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/configure.in b/configure.in
index 4aa5d915d53..0d813a4498c 100644
--- a/configure.in
+++ b/configure.in
@@ -5513,6 +5513,8 @@ MOZ_ARG_ENABLE_STRING(gstreamer,
   # API version, eg 0.10, 1.0 etc
   if test -z "$enableval" -o "$enableval" = "yes"; then
     GST_API_VERSION=0.10
+  elif test "$enableval" = "no"; then
+    MOZ_GSTREAMER=
   else
     GST_API_VERSION=$enableval
   fi],

From 393ad9ee794ba280d3f5a7a7f0861544d4f2e50b Mon Sep 17 00:00:00 2001
From: Mike Hommey 
Date: Tue, 11 Feb 2014 14:09:33 +0900
Subject: [PATCH 21/40] Bug 970757 - Fixup MOZ_JS_LIBS after bug 969164. r=gps

--HG--
extra : amend_source : 8b56fb4e59fb59fc483bd1339782c111ae90ee29
---
 configure.in | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/configure.in b/configure.in
index 0d813a4498c..863d4b77048 100644
--- a/configure.in
+++ b/configure.in
@@ -7689,14 +7689,6 @@ dnl =
 dnl ========================================================
 MOZ_ARG_HEADER(Static build options)
 
-if test -n "$JS_SHARED_LIBRARY"; then
-  MOZ_JS_LIBS="$MOZ_JS_SHARED_LIBS"
-else
-  MOZ_JS_LIBS="$MOZ_JS_STATIC_LIBS"
-  AC_DEFINE(MOZ_STATIC_JS)
-fi
-AC_SUBST(JS_SHARED_LIBRARY)
-
 AC_SUBST(LIBXUL_LIBS)
 XPCOM_LIBS="$LIBXUL_LIBS"
 
@@ -8752,6 +8744,14 @@ if test -n "$MOZ_NATIVE_ICU"; then
     MOZ_JS_STATIC_LIBS="$MOZ_JS_STATIC_LIBS $MOZ_ICU_LIBS"
 fi
 
+if test -n "$JS_SHARED_LIBRARY"; then
+  MOZ_JS_LIBS="$MOZ_JS_SHARED_LIBS"
+else
+  MOZ_JS_LIBS="$MOZ_JS_STATIC_LIBS"
+  AC_DEFINE(MOZ_STATIC_JS)
+fi
+AC_SUBST(JS_SHARED_LIBRARY)
+
 MOZ_CREATE_CONFIG_STATUS()
 
 # No need to run subconfigures when building with LIBXUL_SDK_DIR

From 43a82e9808bc38cd088412cf019c0b138a790e9a Mon Sep 17 00:00:00 2001
From: Chris Pearce 
Date: Thu, 13 Feb 2014 17:08:55 +1300
Subject: [PATCH 22/40] Bug 903051 - Prevent aborted loads from messing with
 ChannelMediaResource state. r=roc

---
 content/media/MediaResource.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/content/media/MediaResource.cpp b/content/media/MediaResource.cpp
index c1f15ad657d..1a5f88da330 100644
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -153,6 +153,16 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
   nsresult rv = aRequest->GetStatus(&status);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (status == NS_BINDING_ABORTED) {
+    // Request was aborted before we had a chance to receive any data, or
+    // even an OnStartRequest(). Close the channel. This is important, as
+    // we don't want to mess up our state, as if we're cloned that would
+    // cause the clone to copy incorrect metadata (like whether we're
+    // infinite for example).
+    CloseChannel();
+    return status;
+  }
+
   if (element->ShouldCheckAllowOrigin()) {
     // If the request was cancelled by nsCORSListenerProxy due to failing
     // the CORS security check, send an error through to the media element.

From f0c8f08ba5991cb844bb417b058e0148d7f6fe87 Mon Sep 17 00:00:00 2001
From: Shu-yu Guo 
Date: Wed, 12 Feb 2014 20:31:35 -0800
Subject: [PATCH 23/40] Bug 971385 - Skip ForkJoin activations in
 ScriptFrameIter. (r=luke)

---
 js/src/jit-test/tests/parallel/bug971385.js | 7 +++++++
 js/src/vm/Stack.cpp                         | 6 ++++++
 2 files changed, 13 insertions(+)
 create mode 100644 js/src/jit-test/tests/parallel/bug971385.js

diff --git a/js/src/jit-test/tests/parallel/bug971385.js b/js/src/jit-test/tests/parallel/bug971385.js
new file mode 100644
index 00000000000..bb91e11b81d
--- /dev/null
+++ b/js/src/jit-test/tests/parallel/bug971385.js
@@ -0,0 +1,7 @@
+function f() {
+    Array.buildPar(6, function() {});
+    f();
+}
+
+if (getBuildConfiguration().parallelJS)
+  f();
diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp
index 7270db93c85..664e779af95 100644
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -549,6 +549,12 @@ ScriptFrameIter::settleOnActivation()
             data_.state_ = JIT;
             return;
         }
+
+        // ForkJoin activations don't contain iterable frames, so skip them.
+        if (activation->isForkJoin()) {
+            ++data_.activations_;
+            continue;
+        }
 #endif
 
         JS_ASSERT(activation->isInterpreter());

From ca4193e55458bcd17bcb6129ed169fe426b72e3e Mon Sep 17 00:00:00 2001
From: Mike Hommey 
Date: Thu, 13 Feb 2014 13:29:31 +0900
Subject: [PATCH 24/40] Bug 971426 - Define IMPL_MFBT when building standalone
 js. r=gps

---
 js/src/moz.build | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/js/src/moz.build b/js/src/moz.build
index 9e6f3cbb3a2..1a83fb49bd5 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -27,7 +27,9 @@ CONFIGURE_SUBST_FILES += [
     'js.pc',
 ]
 
-if not CONFIG['JS_STANDALONE']:
+if CONFIG['JS_STANDALONE']:
+    DEFINES['IMPL_MFBT'] = True
+else:
     CONFIGURE_SUBST_FILES += [
         '../../config/autoconf-js.mk',
         '../../config/emptyvars-js.mk',

From 5845a0c5984724b10dd8e48afd5d864992b1a700 Mon Sep 17 00:00:00 2001
From: Luke Wagner 
Date: Wed, 12 Feb 2014 22:50:15 -0600
Subject: [PATCH 25/40] Bug 936236 - Notify memory-pressure observers when
 allocating a large ArrayBuffer fails (r=mccr8)

--HG--
extra : rebase_source : 49eadf76ae73e5e4f1c24149839bdc7200e00494
---
 dom/base/nsJSEnvironment.cpp   | 12 ++++++++++++
 js/src/jsapi.cpp               |  6 ++++++
 js/src/jsapi.h                 | 14 ++++++++++++++
 js/src/vm/Runtime.cpp          |  5 +++--
 js/src/vm/Runtime.h            | 28 ++++++++++++++++++++++++++++
 js/src/vm/TypedArrayObject.cpp |  6 ++++--
 6 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
index 095cc41cc47..7963b57fddd 100644
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2971,6 +2971,16 @@ AsmJSCacheOpenEntryForWrite(JS::Handle aGlobal,
                                        aHandle);
 }
 
+static void
+OnLargeAllocationFailure()
+{
+  nsCOMPtr os =
+    mozilla::services::GetObserverService();
+  if (os) {
+    os->NotifyObservers(nullptr, "memory-pressure", MOZ_UTF16("heap-minimize"));
+  }
+}
+
 static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 void
@@ -3027,6 +3037,8 @@ nsJSContext::EnsureStatics()
   };
   JS::SetAsmJSCacheOps(sRuntime, &asmJSCacheOps);
 
+  JS::SetLargeAllocationFailureCallback(sRuntime, OnLargeAllocationFailure);
+
   // Set these global xpconnect options...
   Preferences::RegisterCallbackAndCall(ReportAllJSExceptionsPrefChangedCallback,
                                        "dom.report_all_js_exceptions");
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 97ca884a16c..a1dade57cac 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6312,3 +6312,9 @@ JSAutoByteString::encodeLatin1(ExclusiveContext *cx, JSString *str)
     mBytes = LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str();
     return mBytes;
 }
+
+JS_PUBLIC_API(void)
+JS::SetLargeAllocationFailureCallback(JSRuntime *rt, JS::LargeAllocationFailureCallback lafc)
+{
+    rt->largeAllocationFailureCallback = lafc;
+}
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index e7092ed4c0b..239779a8283 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4896,6 +4896,20 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) {
     }
 };
 
+
+/*
+ * If a large allocation fails, the JS engine may call the large-allocation-
+ * failure callback, if set, to allow the embedding to flush caches, possibly
+ * perform shrinking GCs, etc. to make some room so that the allocation will
+ * succeed if retried.
+ */
+
+typedef void
+(* LargeAllocationFailureCallback)();
+
+extern JS_PUBLIC_API(void)
+SetLargeAllocationFailureCallback(JSRuntime *rt, LargeAllocationFailureCallback afc);
+
 } /* namespace JS */
 
 #endif /* jsapi_h */
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 1ef33f30af4..232df034282 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -302,10 +302,11 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
     useHelperThreads_(useHelperThreads),
     parallelIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
-    isWorkerRuntime_(false)
+    isWorkerRuntime_(false),
 #ifdef DEBUG
-    , enteredPolicy(nullptr)
+    enteredPolicy(nullptr),
 #endif
+    largeAllocationFailureCallback(nullptr)
 {
     liveRuntimesCount++;
 
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index d1336f8df95..d3bfaaba0cf 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1787,6 +1787,34 @@ struct JSRuntime : public JS::shadow::Runtime,
   public:
     js::AutoEnterPolicy *enteredPolicy;
 #endif
+
+    /*
+     * These variations of malloc/calloc/realloc will call the
+     * large-allocation-failure callback on OOM and retry the allocation.
+     */
+    JS::LargeAllocationFailureCallback largeAllocationFailureCallback;
+
+    static const unsigned LARGE_ALLOCATION = 25 * 1024 * 1024;
+
+    void *callocCanGC(size_t bytes) {
+        void *p = calloc_(bytes);
+        if (MOZ_LIKELY(!!p))
+            return p;
+        if (!largeAllocationFailureCallback || bytes < LARGE_ALLOCATION)
+            return nullptr;
+        largeAllocationFailureCallback();
+        return js_calloc(bytes);
+    }
+
+    void *reallocCanGC(void *p, size_t bytes) {
+        void *p2 = realloc_(p, bytes);
+        if (MOZ_LIKELY(!!p2))
+            return p2;
+        if (!largeAllocationFailureCallback || bytes < LARGE_ALLOCATION)
+            return nullptr;
+        largeAllocationFailureCallback();
+        return js_realloc(p, bytes);
+    }
 };
 
 namespace js {
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
index ae36dc073c7..fc4546df2e5 100644
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -240,13 +240,15 @@ AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void *oldptr =
     if (oldptr) {
         ObjectElements *oldheader = static_cast(oldptr);
         uint32_t oldnbytes = ArrayBufferObject::headerInitializedLength(oldheader);
-        newheader = static_cast(maybecx ? maybecx->realloc_(oldptr, size) : js_realloc(oldptr, size));
+        void *p = maybecx ? maybecx->runtime()->reallocCanGC(oldptr, size) : js_realloc(oldptr, size);
+        newheader = static_cast(p);
 
         // if we grew the array, we need to set the new bytes to 0
         if (newheader && nbytes > oldnbytes)
             memset(reinterpret_cast(newheader->elements()) + oldnbytes, 0, nbytes - oldnbytes);
     } else {
-        newheader = static_cast(maybecx ? maybecx->calloc_(size) : js_calloc(size));
+        void *p = maybecx ? maybecx->runtime()->callocCanGC(size) : js_calloc(size);
+        newheader = static_cast(p);
     }
     if (!newheader) {
         if (maybecx)

From b2e477fe81127f64368ff709d7781c639920f9ab Mon Sep 17 00:00:00 2001
From: Shu-yu Guo 
Date: Wed, 12 Feb 2014 21:31:06 -0800
Subject: [PATCH 26/40] Bug 971385 - Followup: fix test. (r=test-only)

---
 js/src/jit-test/tests/parallel/bug971385.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/js/src/jit-test/tests/parallel/bug971385.js b/js/src/jit-test/tests/parallel/bug971385.js
index bb91e11b81d..6bb0eac1ede 100644
--- a/js/src/jit-test/tests/parallel/bug971385.js
+++ b/js/src/jit-test/tests/parallel/bug971385.js
@@ -1,7 +1,10 @@
+// |jit-test| slow;
+load(libdir + "asserts.js");
+
 function f() {
     Array.buildPar(6, function() {});
     f();
 }
 
 if (getBuildConfiguration().parallelJS)
-  f();
+  assertThrowsInstanceOf(f, InternalError);

From cc1b0d4865eafa9b99abae97754371d5c790d679 Mon Sep 17 00:00:00 2001
From: Bas Schouten 
Date: Thu, 13 Feb 2014 07:31:51 +0100
Subject: [PATCH 27/40] Bug 972161: Add ability to output font data for
 freetype fonts. r=jrmuizel

---
 gfx/2d/DrawTargetRecording.cpp |  9 -----
 gfx/2d/ScaledFontBase.cpp      | 64 ++++++++++++++++++++++++++++++++++
 gfx/2d/ScaledFontBase.h        |  2 ++
 3 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp
index 865a4e62ec2..823b21ed66b 100644
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -294,10 +294,7 @@ void RecordingFontUserDataDestroyFunc(void *aUserData)
   RecordingFontUserData *userData =
     static_cast(aUserData);
 
-  // TODO support font in b2g recordings
-#ifndef MOZ_WIDGET_GONK
   userData->recorder->RecordEvent(RecordedScaledFontDestruction(userData->refPtr));
-#endif
 
   delete userData;
 }
@@ -310,10 +307,7 @@ DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
                                 const GlyphRenderingOptions *aRenderingOptions)
 {
   if (!aFont->GetUserData(reinterpret_cast(mRecorder.get()))) {
-  // TODO support font in b2g recordings
-#ifndef MOZ_WIDGET_GONK
     mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, aFont));
-#endif
     RecordingFontUserData *userData = new RecordingFontUserData;
     userData->refPtr = aFont;
     userData->recorder = mRecorder;
@@ -321,10 +315,7 @@ DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
                        &RecordingFontUserDataDestroyFunc);
   }
 
-  // TODO support font in b2g recordings
-#ifndef MOZ_WIDGET_GONK
   mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
-#endif
   mFinalDT->FillGlyphs(aFont, aBuffer, aPattern, aOptions, aRenderingOptions);
 }
 
diff --git a/gfx/2d/ScaledFontBase.cpp b/gfx/2d/ScaledFontBase.cpp
index a2bb31d8e4c..c892e402e90 100644
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -17,6 +17,13 @@
 #include "HelpersCairo.h"
 #endif
 
+#ifdef CAIRO_HAS_FT_FONT
+#include 
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TABLES_H
+#include "cairo-ft.h"
+#endif
+
 #include 
 #include 
 
@@ -184,6 +191,63 @@ ScaledFontBase::SetCairoScaledFont(cairo_scaled_font_t* font)
   mScaledFont = font;
   cairo_scaled_font_reference(mScaledFont);
 }
+
+bool
+ScaledFontBase::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton)
+{
+#ifdef USE_CAIRO_SCALED_FONT
+
+  switch (cairo_scaled_font_get_type(mScaledFont)) {
+#ifdef CAIRO_HAS_FT_FONT
+  case CAIRO_FONT_TYPE_FT:
+    {
+      FT_Face face = cairo_ft_scaled_font_lock_face(mScaledFont);
+
+      if (!face) {
+	gfxWarning() << "Failed to lock FT_Face for cairo font.";
+	cairo_ft_scaled_font_unlock_face(mScaledFont);	
+	return false;
+      }
+
+      if (!FT_IS_SFNT(face)) {
+	if (face->stream && face->stream->base) {
+	  aDataCallback((uint8_t*)&face->stream->base, face->stream->size, 0, mSize, aBaton);
+	  cairo_ft_scaled_font_unlock_face(mScaledFont);
+	  return true;
+	}
+
+	gfxWarning() << "Non sfnt font with non-memory stream.";
+	cairo_ft_scaled_font_unlock_face(mScaledFont);
+	return false;
+      }
+
+      // Get the length of the whole font
+      FT_ULong length = 0;
+      FT_Error error = FT_Load_Sfnt_Table(face, 0, 0, nullptr, &length);
+      if (error || !length) {
+	cairo_ft_scaled_font_unlock_face(mScaledFont);
+	gfxWarning() << "Failed to get font file from Freetype font. " << error;
+	return false;
+      }
+
+      // Get the font data
+      std::vector buffer(length);
+      FT_Load_Sfnt_Table(face, 0, 0, &buffer.front(), &length);
+
+      aDataCallback((uint8_t*)&buffer.front(), length, 0, mSize, aBaton);
+
+      cairo_ft_scaled_font_unlock_face(mScaledFont);
+
+      return true;
+    }
+#endif
+  default:
+    return false;
+  }
+#endif
+
+  return false;
+}
 #endif
 
 }
diff --git a/gfx/2d/ScaledFontBase.h b/gfx/2d/ScaledFontBase.h
index 92b912f5c84..1983526ad9c 100644
--- a/gfx/2d/ScaledFontBase.h
+++ b/gfx/2d/ScaledFontBase.h
@@ -35,6 +35,8 @@ public:
   virtual TemporaryRef GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget);
 
   virtual void CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, BackendType aBackendType, const Matrix *aTransformHint);
+  
+  virtual bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton);
 
   float GetSize() { return mSize; }
 

From 722e1a29e584ed70ae2c05c36f86fa8be9b03145 Mon Sep 17 00:00:00 2001
From: Peiyong Lin 
Date: Wed, 12 Feb 2014 22:55:28 -0800
Subject: [PATCH 28/40] Bug 970821 - Use an array of observer topic strings in
 ContentParent. r=njn.

--HG--
extra : rebase_source : 92c9a0e2f1f1737cc074549aeeb41e7dd4514adc
---
 dom/ipc/ContentParent.cpp | 60 ++++++++++++++++++---------------------
 1 file changed, 28 insertions(+), 32 deletions(-)

diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index d0a76e3c836..aa9a7292fd7 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -276,6 +276,25 @@ static uint64_t gContentChildID = 1;
 // Can't be a static constant.
 #define MAGIC_PREALLOCATED_APP_MANIFEST_URL NS_LITERAL_STRING("{{template}}")
 
+static const char* sObserverTopics[] = {
+    "xpcom-shutdown",
+    NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
+    "child-memory-reporter-request",
+    "memory-pressure",
+    "child-gc-request",
+    "child-cc-request",
+    "child-mmu-request",
+    "last-pb-context-exited",
+    "file-watcher-update",
+#ifdef MOZ_WIDGET_GONK
+    NS_VOLUME_STATE_CHANGED,
+    "phone-state-changed",
+#endif
+#ifdef ACCESSIBILITY
+    "a11y-init-or-shutdown",
+#endif
+};
+
 /* static */ already_AddRefed
 ContentParent::RunNuwaProcess()
 {
@@ -652,22 +671,10 @@ ContentParent::Init()
 {
     nsCOMPtr obs = mozilla::services::GetObserverService();
     if (obs) {
-        obs->AddObserver(this, "xpcom-shutdown", false);
-        obs->AddObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, false);
-        obs->AddObserver(this, "child-memory-reporter-request", false);
-        obs->AddObserver(this, "memory-pressure", false);
-        obs->AddObserver(this, "child-gc-request", false);
-        obs->AddObserver(this, "child-cc-request", false);
-        obs->AddObserver(this, "child-mmu-request", false);
-        obs->AddObserver(this, "last-pb-context-exited", false);
-        obs->AddObserver(this, "file-watcher-update", false);
-#ifdef MOZ_WIDGET_GONK
-        obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, false);
-        obs->AddObserver(this, "phone-state-changed", false);
-#endif
-#ifdef ACCESSIBILITY
-        obs->AddObserver(this, "a11y-init-or-shutdown", false);
-#endif
+        size_t length = ArrayLength(sObserverTopics);
+        for (size_t i = 0; i < length; ++i) {
+            obs->AddObserver(this, sObserverTopics[i], false);
+        }
     }
     Preferences::AddStrongObserver(this, "");
     nsCOMPtr
@@ -1036,22 +1043,11 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
         kungFuDeathGrip(static_cast(this));
     nsCOMPtr obs = mozilla::services::GetObserverService();
     if (obs) {
-        obs->RemoveObserver(static_cast(this), "xpcom-shutdown");
-        obs->RemoveObserver(static_cast(this), "memory-pressure");
-        obs->RemoveObserver(static_cast(this), "child-memory-reporter-request");
-        obs->RemoveObserver(static_cast(this), NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC);
-        obs->RemoveObserver(static_cast(this), "child-gc-request");
-        obs->RemoveObserver(static_cast(this), "child-cc-request");
-        obs->RemoveObserver(static_cast(this), "child-mmu-request");
-        obs->RemoveObserver(static_cast(this), "last-pb-context-exited");
-        obs->RemoveObserver(static_cast(this), "file-watcher-update");
-#ifdef MOZ_WIDGET_GONK
-        obs->RemoveObserver(static_cast(this), NS_VOLUME_STATE_CHANGED);
-        obs->RemoveObserver(static_cast(this), "phone-state-changed");
-#endif
-#ifdef ACCESSIBILITY
-        obs->RemoveObserver(static_cast(this), "a11y-init-or-shutdown");
-#endif
+        size_t length = ArrayLength(sObserverTopics);
+        for (size_t i = 0; i < length; ++i) {
+            obs->RemoveObserver(static_cast(this),
+                                sObserverTopics[i]);
+        }
     }
 
     if (ppm) {

From 2b174ca83331a68486e37587766d691799f1e85c Mon Sep 17 00:00:00 2001
From: Phil Ringnalda 
Date: Wed, 12 Feb 2014 23:26:28 -0800
Subject: [PATCH 29/40] Back out d54433699f2e (bug 936236) for adding 5 rooting
 hazards CLOSED TREE

---
 dom/base/nsJSEnvironment.cpp   | 12 ------------
 js/src/jsapi.cpp               |  6 ------
 js/src/jsapi.h                 | 14 --------------
 js/src/vm/Runtime.cpp          |  5 ++---
 js/src/vm/Runtime.h            | 28 ----------------------------
 js/src/vm/TypedArrayObject.cpp |  6 ++----
 6 files changed, 4 insertions(+), 67 deletions(-)

diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
index 7963b57fddd..095cc41cc47 100644
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2971,16 +2971,6 @@ AsmJSCacheOpenEntryForWrite(JS::Handle aGlobal,
                                        aHandle);
 }
 
-static void
-OnLargeAllocationFailure()
-{
-  nsCOMPtr os =
-    mozilla::services::GetObserverService();
-  if (os) {
-    os->NotifyObservers(nullptr, "memory-pressure", MOZ_UTF16("heap-minimize"));
-  }
-}
-
 static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 void
@@ -3037,8 +3027,6 @@ nsJSContext::EnsureStatics()
   };
   JS::SetAsmJSCacheOps(sRuntime, &asmJSCacheOps);
 
-  JS::SetLargeAllocationFailureCallback(sRuntime, OnLargeAllocationFailure);
-
   // Set these global xpconnect options...
   Preferences::RegisterCallbackAndCall(ReportAllJSExceptionsPrefChangedCallback,
                                        "dom.report_all_js_exceptions");
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index a1dade57cac..97ca884a16c 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6312,9 +6312,3 @@ JSAutoByteString::encodeLatin1(ExclusiveContext *cx, JSString *str)
     mBytes = LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str();
     return mBytes;
 }
-
-JS_PUBLIC_API(void)
-JS::SetLargeAllocationFailureCallback(JSRuntime *rt, JS::LargeAllocationFailureCallback lafc)
-{
-    rt->largeAllocationFailureCallback = lafc;
-}
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 239779a8283..e7092ed4c0b 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4896,20 +4896,6 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) {
     }
 };
 
-
-/*
- * If a large allocation fails, the JS engine may call the large-allocation-
- * failure callback, if set, to allow the embedding to flush caches, possibly
- * perform shrinking GCs, etc. to make some room so that the allocation will
- * succeed if retried.
- */
-
-typedef void
-(* LargeAllocationFailureCallback)();
-
-extern JS_PUBLIC_API(void)
-SetLargeAllocationFailureCallback(JSRuntime *rt, LargeAllocationFailureCallback afc);
-
 } /* namespace JS */
 
 #endif /* jsapi_h */
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 232df034282..1ef33f30af4 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -302,11 +302,10 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
     useHelperThreads_(useHelperThreads),
     parallelIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
-    isWorkerRuntime_(false),
+    isWorkerRuntime_(false)
 #ifdef DEBUG
-    enteredPolicy(nullptr),
+    , enteredPolicy(nullptr)
 #endif
-    largeAllocationFailureCallback(nullptr)
 {
     liveRuntimesCount++;
 
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index d3bfaaba0cf..d1336f8df95 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1787,34 +1787,6 @@ struct JSRuntime : public JS::shadow::Runtime,
   public:
     js::AutoEnterPolicy *enteredPolicy;
 #endif
-
-    /*
-     * These variations of malloc/calloc/realloc will call the
-     * large-allocation-failure callback on OOM and retry the allocation.
-     */
-    JS::LargeAllocationFailureCallback largeAllocationFailureCallback;
-
-    static const unsigned LARGE_ALLOCATION = 25 * 1024 * 1024;
-
-    void *callocCanGC(size_t bytes) {
-        void *p = calloc_(bytes);
-        if (MOZ_LIKELY(!!p))
-            return p;
-        if (!largeAllocationFailureCallback || bytes < LARGE_ALLOCATION)
-            return nullptr;
-        largeAllocationFailureCallback();
-        return js_calloc(bytes);
-    }
-
-    void *reallocCanGC(void *p, size_t bytes) {
-        void *p2 = realloc_(p, bytes);
-        if (MOZ_LIKELY(!!p2))
-            return p2;
-        if (!largeAllocationFailureCallback || bytes < LARGE_ALLOCATION)
-            return nullptr;
-        largeAllocationFailureCallback();
-        return js_realloc(p, bytes);
-    }
 };
 
 namespace js {
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
index fc4546df2e5..ae36dc073c7 100644
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -240,15 +240,13 @@ AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void *oldptr =
     if (oldptr) {
         ObjectElements *oldheader = static_cast(oldptr);
         uint32_t oldnbytes = ArrayBufferObject::headerInitializedLength(oldheader);
-        void *p = maybecx ? maybecx->runtime()->reallocCanGC(oldptr, size) : js_realloc(oldptr, size);
-        newheader = static_cast(p);
+        newheader = static_cast(maybecx ? maybecx->realloc_(oldptr, size) : js_realloc(oldptr, size));
 
         // if we grew the array, we need to set the new bytes to 0
         if (newheader && nbytes > oldnbytes)
             memset(reinterpret_cast(newheader->elements()) + oldnbytes, 0, nbytes - oldnbytes);
     } else {
-        void *p = maybecx ? maybecx->runtime()->callocCanGC(size) : js_calloc(size);
-        newheader = static_cast(p);
+        newheader = static_cast(maybecx ? maybecx->calloc_(size) : js_calloc(size));
     }
     if (!newheader) {
         if (maybecx)

From 11a4d1792bef662524bfe1e4544a0ecfb5a70779 Mon Sep 17 00:00:00 2001
From: Phil Ringnalda 
Date: Wed, 12 Feb 2014 23:27:40 -0800
Subject: [PATCH 30/40] Back out bcaadd45b905 (bug 972161) for adding (fatal)
 warnings on Windows CLOSED TREE

---
 gfx/2d/DrawTargetRecording.cpp |  9 +++++
 gfx/2d/ScaledFontBase.cpp      | 64 ----------------------------------
 gfx/2d/ScaledFontBase.h        |  2 --
 3 files changed, 9 insertions(+), 66 deletions(-)

diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp
index 823b21ed66b..865a4e62ec2 100644
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -294,7 +294,10 @@ void RecordingFontUserDataDestroyFunc(void *aUserData)
   RecordingFontUserData *userData =
     static_cast(aUserData);
 
+  // TODO support font in b2g recordings
+#ifndef MOZ_WIDGET_GONK
   userData->recorder->RecordEvent(RecordedScaledFontDestruction(userData->refPtr));
+#endif
 
   delete userData;
 }
@@ -307,7 +310,10 @@ DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
                                 const GlyphRenderingOptions *aRenderingOptions)
 {
   if (!aFont->GetUserData(reinterpret_cast(mRecorder.get()))) {
+  // TODO support font in b2g recordings
+#ifndef MOZ_WIDGET_GONK
     mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, aFont));
+#endif
     RecordingFontUserData *userData = new RecordingFontUserData;
     userData->refPtr = aFont;
     userData->recorder = mRecorder;
@@ -315,7 +321,10 @@ DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
                        &RecordingFontUserDataDestroyFunc);
   }
 
+  // TODO support font in b2g recordings
+#ifndef MOZ_WIDGET_GONK
   mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
+#endif
   mFinalDT->FillGlyphs(aFont, aBuffer, aPattern, aOptions, aRenderingOptions);
 }
 
diff --git a/gfx/2d/ScaledFontBase.cpp b/gfx/2d/ScaledFontBase.cpp
index c892e402e90..a2bb31d8e4c 100644
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -17,13 +17,6 @@
 #include "HelpersCairo.h"
 #endif
 
-#ifdef CAIRO_HAS_FT_FONT
-#include 
-#include FT_FREETYPE_H
-#include FT_TRUETYPE_TABLES_H
-#include "cairo-ft.h"
-#endif
-
 #include 
 #include 
 
@@ -191,63 +184,6 @@ ScaledFontBase::SetCairoScaledFont(cairo_scaled_font_t* font)
   mScaledFont = font;
   cairo_scaled_font_reference(mScaledFont);
 }
-
-bool
-ScaledFontBase::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton)
-{
-#ifdef USE_CAIRO_SCALED_FONT
-
-  switch (cairo_scaled_font_get_type(mScaledFont)) {
-#ifdef CAIRO_HAS_FT_FONT
-  case CAIRO_FONT_TYPE_FT:
-    {
-      FT_Face face = cairo_ft_scaled_font_lock_face(mScaledFont);
-
-      if (!face) {
-	gfxWarning() << "Failed to lock FT_Face for cairo font.";
-	cairo_ft_scaled_font_unlock_face(mScaledFont);	
-	return false;
-      }
-
-      if (!FT_IS_SFNT(face)) {
-	if (face->stream && face->stream->base) {
-	  aDataCallback((uint8_t*)&face->stream->base, face->stream->size, 0, mSize, aBaton);
-	  cairo_ft_scaled_font_unlock_face(mScaledFont);
-	  return true;
-	}
-
-	gfxWarning() << "Non sfnt font with non-memory stream.";
-	cairo_ft_scaled_font_unlock_face(mScaledFont);
-	return false;
-      }
-
-      // Get the length of the whole font
-      FT_ULong length = 0;
-      FT_Error error = FT_Load_Sfnt_Table(face, 0, 0, nullptr, &length);
-      if (error || !length) {
-	cairo_ft_scaled_font_unlock_face(mScaledFont);
-	gfxWarning() << "Failed to get font file from Freetype font. " << error;
-	return false;
-      }
-
-      // Get the font data
-      std::vector buffer(length);
-      FT_Load_Sfnt_Table(face, 0, 0, &buffer.front(), &length);
-
-      aDataCallback((uint8_t*)&buffer.front(), length, 0, mSize, aBaton);
-
-      cairo_ft_scaled_font_unlock_face(mScaledFont);
-
-      return true;
-    }
-#endif
-  default:
-    return false;
-  }
-#endif
-
-  return false;
-}
 #endif
 
 }
diff --git a/gfx/2d/ScaledFontBase.h b/gfx/2d/ScaledFontBase.h
index 1983526ad9c..92b912f5c84 100644
--- a/gfx/2d/ScaledFontBase.h
+++ b/gfx/2d/ScaledFontBase.h
@@ -35,8 +35,6 @@ public:
   virtual TemporaryRef GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget);
 
   virtual void CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, BackendType aBackendType, const Matrix *aTransformHint);
-  
-  virtual bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton);
 
   float GetSize() { return mSize; }
 

From 1d8c23fa21d8ed3e499e3621add45507c56928df Mon Sep 17 00:00:00 2001
From: Daniel Holbert 
Date: Wed, 12 Feb 2014 23:47:01 -0800
Subject: [PATCH 31/40] Bug 971913: Use DISPLAY_MIN_WIDTH / DISPLAY_PREF_WIDTH
 logging macros in nsFlexContainerFrame. r=jwatt

---
 layout/generic/nsFlexContainerFrame.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp
index af52abb7f05..24ed03a49c7 100644
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -3087,9 +3087,11 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext*           aPresContext,
 /* virtual */ nscoord
 nsFlexContainerFrame::GetMinWidth(nsRenderingContext* aRenderingContext)
 {
+  nscoord minWidth = 0;
+  DISPLAY_MIN_WIDTH(this, minWidth);
+
   FlexboxAxisTracker axisTracker(this);
 
-  nscoord minWidth = 0;
   for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
     nscoord childMinWidth =
       nsLayoutUtils::IntrinsicForContainer(aRenderingContext, e.get(),
@@ -3111,6 +3113,9 @@ nsFlexContainerFrame::GetMinWidth(nsRenderingContext* aRenderingContext)
 /* virtual */ nscoord
 nsFlexContainerFrame::GetPrefWidth(nsRenderingContext* aRenderingContext)
 {
+  nscoord prefWidth = 0;
+  DISPLAY_PREF_WIDTH(this, prefWidth);
+
   // XXXdholbert Optimization: We could cache our intrinsic widths like
   // nsBlockFrame does (and return it early from this function if it's set).
   // Whenever anything happens that might change it, set it to
@@ -3118,7 +3123,6 @@ nsFlexContainerFrame::GetPrefWidth(nsRenderingContext* aRenderingContext)
   // does)
   FlexboxAxisTracker axisTracker(this);
 
-  nscoord prefWidth = 0;
   for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
     nscoord childPrefWidth =
       nsLayoutUtils::IntrinsicForContainer(aRenderingContext, e.get(),

From b5a3a27830fb353511bb39b57012e75a8954f19a Mon Sep 17 00:00:00 2001
From: Daniel Holbert 
Date: Wed, 12 Feb 2014 23:47:02 -0800
Subject: [PATCH 32/40] Bug 972119: Simplify logic in
 nsCSSFrameConstructor::ConstructDocElementFrame. r=bz

---
 layout/base/nsCSSFrameConstructor.cpp | 114 +++++++++++++-------------
 1 file changed, 55 insertions(+), 59 deletions(-)

diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
index f7cebb3553b..9797db06673 100644
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -2392,68 +2392,64 @@ nsCSSFrameConstructor::ConstructDocElementFrame(Element*                 aDocEle
   else
 #endif
   if (aDocElement->IsSVG()) {
-    if (aDocElement->Tag() == nsGkAtoms::svg) {
-      // We're going to call the right function ourselves, so no need to give a
-      // function to this FrameConstructionData.
-
-      // XXXbz on the other hand, if we converted this whole function to
-      // FrameConstructionData/Item, then we'd need the right function
-      // here... but would probably be able to get away with less code in this
-      // function in general.
-      // Use a null PendingBinding, since our binding is not in fact pending.
-      static const FrameConstructionData rootSVGData = FCDATA_DECL(0, nullptr);
-      nsRefPtr extraRef(styleContext);
-      FrameConstructionItem item(&rootSVGData, aDocElement,
-                                 aDocElement->Tag(), kNameSpaceID_SVG,
-                                 nullptr, extraRef.forget(), true, nullptr);
-
-      nsFrameItems frameItems;
-      contentFrame = ConstructOuterSVG(state, item, mDocElementContainingBlock,
-                                       styleContext->StyleDisplay(),
-                                       frameItems);
-      newFrame = frameItems.FirstChild();
-      NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
-    } else {
+    if (aDocElement->Tag() != nsGkAtoms::svg) {
       return nullptr;
     }
+    // We're going to call the right function ourselves, so no need to give a
+    // function to this FrameConstructionData.
+
+    // XXXbz on the other hand, if we converted this whole function to
+    // FrameConstructionData/Item, then we'd need the right function
+    // here... but would probably be able to get away with less code in this
+    // function in general.
+    // Use a null PendingBinding, since our binding is not in fact pending.
+    static const FrameConstructionData rootSVGData = FCDATA_DECL(0, nullptr);
+    nsRefPtr extraRef(styleContext);
+    FrameConstructionItem item(&rootSVGData, aDocElement,
+                               aDocElement->Tag(), kNameSpaceID_SVG,
+                               nullptr, extraRef.forget(), true, nullptr);
+
+    nsFrameItems frameItems;
+    contentFrame = ConstructOuterSVG(state, item, mDocElementContainingBlock,
+                                     styleContext->StyleDisplay(),
+                                     frameItems);
+    newFrame = frameItems.FirstChild();
+    NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
+  } else if (display->mDisplay == NS_STYLE_DISPLAY_TABLE) {
+    // We're going to call the right function ourselves, so no need to give a
+    // function to this FrameConstructionData.
+
+    // XXXbz on the other hand, if we converted this whole function to
+    // FrameConstructionData/Item, then we'd need the right function
+    // here... but would probably be able to get away with less code in this
+    // function in general.
+    // Use a null PendingBinding, since our binding is not in fact pending.
+    static const FrameConstructionData rootTableData = FCDATA_DECL(0, nullptr);
+    nsRefPtr extraRef(styleContext);
+    FrameConstructionItem item(&rootTableData, aDocElement,
+                               aDocElement->Tag(), kNameSpaceID_None,
+                               nullptr, extraRef.forget(), true, nullptr);
+
+    nsFrameItems frameItems;
+    // if the document is a table then just populate it.
+    contentFrame = ConstructTable(state, item, mDocElementContainingBlock,
+                                  styleContext->StyleDisplay(),
+                                  frameItems);
+    newFrame = frameItems.FirstChild();
+    NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
   } else {
-    bool docElemIsTable = (display->mDisplay == NS_STYLE_DISPLAY_TABLE);
-    if (docElemIsTable) {
-      // We're going to call the right function ourselves, so no need to give a
-      // function to this FrameConstructionData.
-
-      // XXXbz on the other hand, if we converted this whole function to
-      // FrameConstructionData/Item, then we'd need the right function
-      // here... but would probably be able to get away with less code in this
-      // function in general.
-      // Use a null PendingBinding, since our binding is not in fact pending.
-      static const FrameConstructionData rootTableData = FCDATA_DECL(0, nullptr);
-      nsRefPtr extraRef(styleContext);
-      FrameConstructionItem item(&rootTableData, aDocElement,
-                                 aDocElement->Tag(), kNameSpaceID_None,
-                                 nullptr, extraRef.forget(), true, nullptr);
-
-      nsFrameItems frameItems;
-      // if the document is a table then just populate it.
-      contentFrame = ConstructTable(state, item, mDocElementContainingBlock,
-                                    styleContext->StyleDisplay(),
-                                    frameItems);
-      newFrame = frameItems.FirstChild();
-      NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
-    } else {
-      contentFrame = NS_NewBlockFormattingContext(mPresShell, styleContext);
-      nsFrameItems frameItems;
-      // Use a null PendingBinding, since our binding is not in fact pending.
-      ConstructBlock(state, display, aDocElement,
-                     state.GetGeometricParent(display,
-                                              mDocElementContainingBlock),
-                     mDocElementContainingBlock, styleContext,
-                     &contentFrame, frameItems,
-                     display->IsPositioned(contentFrame) ? contentFrame : nullptr,
-                     nullptr);
-      newFrame = frameItems.FirstChild();
-      NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
-    }
+    contentFrame = NS_NewBlockFormattingContext(mPresShell, styleContext);
+    nsFrameItems frameItems;
+    // Use a null PendingBinding, since our binding is not in fact pending.
+    ConstructBlock(state, display, aDocElement,
+                   state.GetGeometricParent(display,
+                                            mDocElementContainingBlock),
+                   mDocElementContainingBlock, styleContext,
+                   &contentFrame, frameItems,
+                   display->IsPositioned(contentFrame) ? contentFrame : nullptr,
+                   nullptr);
+    newFrame = frameItems.FirstChild();
+    NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
   }
 
   MOZ_ASSERT(newFrame);

From c4fb327c6ab1555327077da284d652277cb7eefa Mon Sep 17 00:00:00 2001
From: Simon Montagu 
Date: Wed, 12 Feb 2014 23:55:06 -0800
Subject: [PATCH 33/40] Add some logical setters/getters to nsIFrame.h. Bug
 735577, r=jfkthame

---
 layout/generic/nsIFrame.h | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h
index ab7b82503d0..5d1ddb6d04b 100644
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -686,6 +686,15 @@ public:
   nsRect GetRectRelativeToSelf() const {
     return nsRect(nsPoint(0, 0), mRect.Size());
   }
+  /**
+   * Rect and position in logical coordinates in the frame's writing mode
+   */
+  mozilla::LogicalRect GetLogicalRect(nscoord aContainerWidth) const {
+    return mozilla::LogicalRect(GetWritingMode(), GetRect(), aContainerWidth);
+  }
+  mozilla::LogicalPoint GetLogicalPosition(nscoord aContainerWidth) const {
+    return GetLogicalRect(aContainerWidth).Origin(GetWritingMode());
+  }
 
   /**
    * When we change the size of the frame's border-box rect, we may need to
@@ -703,6 +712,22 @@ public:
       mRect = aRect;
     }
   }
+  /**
+   * Set this frame's rect from a logical rect in its own writing direction
+   */
+  void SetRectFromLogicalRect(const mozilla::LogicalRect& aRect,
+                              nscoord aContainerWidth) {
+    SetRectFromLogicalRect(GetWritingMode(), aRect, aContainerWidth);
+  }
+  /**
+   * Set this frame's rect from a logical rect in a different writing direction
+   * (GetPhysicalRect will assert if the writing mode doesn't match)
+   */
+  void SetRectFromLogicalRect(mozilla::WritingMode aWritingMode,
+                              const mozilla::LogicalRect& aRect,
+                              nscoord aContainerWidth) {
+    SetRect(aRect.GetPhysicalRect(aWritingMode, aContainerWidth));
+  }
   void SetSize(const nsSize& aSize) {
     SetRect(nsRect(mRect.TopLeft(), aSize));
   }

From c66497e14c79271b85e5c2dfcfd755a3b926a066 Mon Sep 17 00:00:00 2001
From: Simon Montagu 
Date: Wed, 12 Feb 2014 23:55:08 -0800
Subject: [PATCH 34/40] Extend the WritingModes API. Bug 735577, r=jfkthame

---
 layout/generic/WritingModes.h | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/layout/generic/WritingModes.h b/layout/generic/WritingModes.h
index df4b9839066..082ae4177da 100644
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -192,6 +192,21 @@ public:
     }
   }
 
+  // For unicode-bidi: plaintext, reset the direction of the writing mode from
+  // the bidi paragraph level of the content
+
+  //XXX change uint8_t to UBiDiLevel after bug 924851
+  void SetDirectionFromBidiLevel(uint8_t level)
+  {
+    if (level & 1) {
+      // odd level, set RTL
+      mWritingMode |= eBidiMask;
+    } else {
+      // even level, set LTR
+      mWritingMode &= ~eBidiMask;
+    }
+  }
+
   /**
    * Compare two WritingModes for equality.
    */

From 8f1906d8c50244e08702dbdee19bb03c90e0eb94 Mon Sep 17 00:00:00 2001
From: Jonathan Kew 
Date: Thu, 13 Feb 2014 08:38:48 +0000
Subject: [PATCH 35/40] bug 970710 - ensure GetTrimmedOffsets is called with
 consistent parameters from PropertyProvider::InitializeForMeasure and
 SetupJustificationSpacing. r=roc

---
 layout/generic/nsTextFrame.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp
index 6681a2c7014..4f8ef853d22 100644
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -2890,7 +2890,7 @@ public:
   const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
 
 protected:
-  void SetupJustificationSpacing();
+  void SetupJustificationSpacing(bool aPostReflow);
 
   void InitFontGroupAndFontMetrics() {
     float inflation = (mWhichTextRun == nsTextFrame::eInflated)
@@ -3274,7 +3274,7 @@ PropertyProvider::InitializeForDisplay(bool aTrimAfter)
     mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
   mStart.SetOriginalOffset(trimmed.mStart);
   mLength = trimmed.mLength;
-  SetupJustificationSpacing();
+  SetupJustificationSpacing(true);
 }
 
 void
@@ -3284,7 +3284,7 @@ PropertyProvider::InitializeForMeasure()
     mFrame->GetTrimmedOffsets(mFrag, true, false);
   mStart.SetOriginalOffset(trimmed.mStart);
   mLength = trimmed.mLength;
-  SetupJustificationSpacing();
+  SetupJustificationSpacing(false);
 }
 
 
@@ -3326,7 +3326,7 @@ PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart,
 }
 
 void
-PropertyProvider::SetupJustificationSpacing()
+PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
 {
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
@@ -3338,7 +3338,7 @@ PropertyProvider::SetupJustificationSpacing()
   // called with false for aTrimAfter, we still shouldn't be assigning
   // justification space to any trailing whitespace.
   nsTextFrame::TrimmedOffsets trimmed =
-    mFrame->GetTrimmedOffsets(mFrag, true);
+    mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
   end.AdvanceOriginal(trimmed.mLength);
   gfxSkipCharsIterator realEnd(end);
   FindJustificationRange(&start, &end);

From 811c597f289834c004282204766534b461d44608 Mon Sep 17 00:00:00 2001
From: Jan de Mooij 
Date: Thu, 13 Feb 2014 10:22:58 +0100
Subject: [PATCH 36/40] Bug 931861 - Fix xpcshell to install breakpad signal
 handlers before AsmJS/Ion signal handlers. r=ted

---
 js/xpconnect/src/XPCShellImpl.cpp | 57 ++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 23 deletions(-)

diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp
index 1e9ebb792b0..50753f0882c 100644
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -51,6 +51,7 @@
 #endif
 
 #ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
 #include "nsICrashReporter.h"
 #endif
 
@@ -68,7 +69,7 @@ public:
     ~XPCShellDirProvider() { }
 
     // The platform resource folder
-    bool SetGREDir(const char *dir);
+    void SetGREDir(nsIFile *greDir);
     void ClearGREDir() { mGREDir = nullptr; }
     // The application resource folder
     void SetAppDir(nsIFile *appFile);
@@ -1364,16 +1365,32 @@ XRE_XPCShellMain(int argc, char **argv, char **envp)
 
     dirprovider.SetAppFile(appFile);
 
+    nsCOMPtr greDir;
     if (argc > 1 && !strcmp(argv[1], "-g")) {
         if (argc < 3)
             return usage();
 
-        if (!dirprovider.SetGREDir(argv[2])) {
-            printf("SetGREDir failed.\n");
+        rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir));
+        if (NS_FAILED(rv)) {
+            printf("Couldn't use given GRE dir.\n");
             return 1;
         }
+
+        dirprovider.SetGREDir(greDir);
+
         argc -= 2;
         argv += 2;
+    } else {
+        nsAutoString workingDir;
+        if (!GetCurrentWorkingDirectory(workingDir)) {
+            printf("GetCurrentWorkingDirectory failed.\n");
+            return 1;
+        }
+        rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir));
+        if (NS_FAILED(rv)) {
+            printf("NS_NewLocalFile failed.\n");
+            return 1;
+        }
     }
 
     if (argc > 1 && !strcmp(argv[1], "-a")) {
@@ -1411,10 +1428,15 @@ XRE_XPCShellMain(int argc, char **argv, char **envp)
     }
 
 #ifdef MOZ_CRASHREPORTER
-    // This is needed during startup and also shutdown, so keep it out
-    // of the nested scope.
-    // Special exception: will remain usable after NS_ShutdownXPCOM
-    nsCOMPtr crashReporter;
+    const char *val = getenv("MOZ_CRASHREPORTER");
+    if (val && *val) {
+        rv = CrashReporter::SetExceptionHandler(greDir, true);
+        if (NS_FAILED(rv)) {
+            printf("CrashReporter::SetExceptionHandler failed!\n");
+            return 1;
+        }
+        MOZ_ASSERT(CrashReporter::GetEnabled());
+    }
 #endif
 
     {
@@ -1442,14 +1464,6 @@ XRE_XPCShellMain(int argc, char **argv, char **envp)
             return 1;
         }
 
-#ifdef MOZ_CRASHREPORTER
-        const char *val = getenv("MOZ_CRASHREPORTER");
-        crashReporter = do_GetService("@mozilla.org/toolkit/crash-reporter;1");
-        if (val && *val) {
-            crashReporter->SetEnabled(true);
-        }
-#endif
-
         nsCOMPtr rtsvc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
         // get the JSRuntime from the runtime svc
         if (!rtsvc) {
@@ -1618,10 +1632,8 @@ XRE_XPCShellMain(int argc, char **argv, char **envp)
 
 #ifdef MOZ_CRASHREPORTER
     // Shut down the crashreporter service to prevent leaking some strings it holds.
-    if (crashReporter) {
-        crashReporter->SetEnabled(false);
-        crashReporter = nullptr;
-    }
+    if (CrashReporter::GetEnabled())
+        CrashReporter::UnsetExceptionHandler();
 #endif
 
     NS_LogTerm();
@@ -1629,11 +1641,10 @@ XRE_XPCShellMain(int argc, char **argv, char **envp)
     return result;
 }
 
-bool
-XPCShellDirProvider::SetGREDir(const char *dir)
+void
+XPCShellDirProvider::SetGREDir(nsIFile* greDir)
 {
-    nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir));
-    return NS_SUCCEEDED(rv);
+    mGREDir = greDir;
 }
 
 void

From 510234b402f046e4bc94acec77c09941d5780203 Mon Sep 17 00:00:00 2001
From: Till Schneidereit 
Date: Tue, 22 Oct 2013 10:53:59 +0200
Subject: [PATCH 37/40] Bug 929374 - Enable TI and IonMonkey for chrome script
 by default. r=jandem

---
 modules/libpref/src/init/all.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js
index cff7a0724f2..1a4ca0928c7 100644
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -738,12 +738,12 @@ pref("javascript.options.strict.debug",     true);
 pref("javascript.options.baselinejit.content", true);
 pref("javascript.options.baselinejit.chrome",  true);
 pref("javascript.options.ion.content",      true);
-pref("javascript.options.ion.chrome",       false);
+pref("javascript.options.ion.chrome",       true);
 pref("javascript.options.asmjs",            true);
 pref("javascript.options.parallel_parsing", true);
 pref("javascript.options.ion.parallel_compilation", true);
 pref("javascript.options.typeinference.content", true);
-pref("javascript.options.typeinference.chrome", false);
+pref("javascript.options.typeinference.chrome", true);
 // This preference limits the memory usage of javascript.
 // If you want to change these values for your device,
 // please find Bug 417052 comment 17 and Bug 456721

From eeb0e699713dd83780e5ef2661c281aa6f48ac1b Mon Sep 17 00:00:00 2001
From: Ms2ger 
Date: Thu, 13 Feb 2014 10:31:09 +0100
Subject: [PATCH 38/40] Backout changeset c8cd1f6b6d2d because it's not
 web-compatible.

---
 .../html/document/src/HTMLAllCollection.cpp   | 24 +++++++++++++------
 dom/base/test/test_nondomexception.html       |  2 +-
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/content/html/document/src/HTMLAllCollection.cpp b/content/html/document/src/HTMLAllCollection.cpp
index 287b1c54c96..ca3e92257f6 100644
--- a/content/html/document/src/HTMLAllCollection.cpp
+++ b/content/html/document/src/HTMLAllCollection.cpp
@@ -37,10 +37,11 @@ const JSClass sHTMLDocumentAllClass = {
   JS_DeletePropertyStub,                                   /* delProperty */
   nsHTMLDocumentSH::DocumentAllGetProperty,                /* getProperty */
   JS_StrictPropertyStub,                                   /* setProperty */
-  JS_EnumerateStub,                                        /* enumerate */
-  (JSResolveOp)nsHTMLDocumentSH::DocumentAllNewResolve,    /* resolve */
-  JS_ConvertStub,                                          /* convert */
-  nsHTMLDocumentSH::ReleaseDocument                        /* finalize */
+  JS_EnumerateStub,
+  (JSResolveOp)nsHTMLDocumentSH::DocumentAllNewResolve,
+  JS_ConvertStub,
+  nsHTMLDocumentSH::ReleaseDocument,
+  nsHTMLDocumentSH::CallToGetPropMapper
 };
 
 namespace mozilla {
@@ -371,12 +372,14 @@ bool
 nsHTMLDocumentSH::CallToGetPropMapper(JSContext *cx, unsigned argc, jsval *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  // Handle document.all("foo") style access to document.all.
 
   if (args.length() != 1) {
     // XXX: Should throw NS_ERROR_XPC_NOT_ENOUGH_ARGS for argc < 1,
     // and create a new NS_ERROR_XPC_TOO_MANY_ARGS for argc > 1? IE
     // accepts nothing other than one arg.
     xpc::Throw(cx, NS_ERROR_INVALID_ARG);
+
     return false;
   }
 
@@ -386,9 +389,16 @@ nsHTMLDocumentSH::CallToGetPropMapper(JSContext *cx, unsigned argc, jsval *vp)
     return false;
   }
 
-  JS::Rooted self(cx, JS_THIS_OBJECT(cx, vp));
-  if (!self) {
-    return false;
+  // If we are called via document.all(id) instead of document.all.item(i) or
+  // another method, use the document.all callee object as self.
+  JS::Rooted self(cx);
+  if (args.calleev().isObject() &&
+      JS_GetClass(&args.calleev().toObject()) == &sHTMLDocumentAllClass) {
+    self = &args.calleev().toObject();
+  } else {
+    self = JS_THIS_OBJECT(cx, vp);
+    if (!self)
+      return false;
   }
 
   size_t length;
diff --git a/dom/base/test/test_nondomexception.html b/dom/base/test/test_nondomexception.html
index 04c669718f6..558db04e7a2 100644
--- a/dom/base/test/test_nondomexception.html
+++ b/dom/base/test/test_nondomexception.html
@@ -15,7 +15,7 @@
 "use strict";
 
 try {
-  document.all.item();
+  document.all();
 } catch (e) {
   is(typeof e, "object");
   is(e.filename, location);

From 90a4336ee84ae9bf1cea57decdd36f4a99fec416 Mon Sep 17 00:00:00 2001
From: Jacek Caban 
Date: Thu, 13 Feb 2014 11:25:26 +0100
Subject: [PATCH 39/40] Bug 971646 - FileUtilsWin.h fails to compile on GCC.
 r=bsmedberg

---
 xpcom/glue/nsStringAPI.h                    | 15 +++++++++++++++
 xpcom/io/FileUtilsWin.cpp                   |  2 +-
 xpcom/io/FileUtilsWin.h                     |  6 +++---
 xpcom/tests/windows/TestNtPathToDosPath.cpp | 18 ++++++++++--------
 4 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/xpcom/glue/nsStringAPI.h b/xpcom/glue/nsStringAPI.h
index a1e941d5267..4072e0e8a67 100644
--- a/xpcom/glue/nsStringAPI.h
+++ b/xpcom/glue/nsStringAPI.h
@@ -116,6 +116,12 @@ public:
   {
     NS_StringSetData(*this, &aChar, 1);
   }
+#ifdef MOZ_USE_CHAR16_WRAPPER
+  NS_HIDDEN_(void) Assign(char16ptr_t aData, size_type aLength = UINT32_MAX)
+  {
+    NS_StringSetData(*this, aData, aLength);
+  }
+#endif
 
   NS_HIDDEN_(void) AssignLiteral(const char *aStr);
   NS_HIDDEN_(void) AssignASCII(const char *aStr) { AssignLiteral(aStr); }
@@ -123,6 +129,9 @@ public:
   NS_HIDDEN_(self_type&) operator=(const self_type& aString) { Assign(aString);   return *this; }
   NS_HIDDEN_(self_type&) operator=(const char_type* aPtr)    { Assign(aPtr);      return *this; }
   NS_HIDDEN_(self_type&) operator=(char_type aChar)          { Assign(aChar);     return *this; }
+#ifdef MOZ_USE_CHAR16_WRAPPER
+  NS_HIDDEN_(self_type&) operator=(char16ptr_t aPtr)         { Assign(aPtr);      return *this; }
+#endif
 
   NS_HIDDEN_(void) Replace( index_type cutStart, size_type cutLength, const char_type* data, size_type length = size_type(-1) )
   {
@@ -224,6 +233,12 @@ public:
   {
     return Equals(other);
   }
+#ifdef MOZ_USE_CHAR16_WRAPPER
+  NS_HIDDEN_(bool) operator == (char16ptr_t other) const
+  {
+    return Equals(other);
+  }
+#endif
 
   NS_HIDDEN_(bool) operator >= (const self_type &other) const
   {
diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp
index 3e9c5019e1c..d12ec8b243b 100644
--- a/xpcom/io/FileUtilsWin.cpp
+++ b/xpcom/io/FileUtilsWin.cpp
@@ -49,7 +49,7 @@ HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset,
   do {
     mappedFilename.SetLength(mappedFilename.Length() + MAX_PATH);
     len = GetMappedFileNameW(GetCurrentProcess(), view,
-                             mappedFilename.BeginWriting(),
+                             wwc(mappedFilename.BeginWriting()),
                              mappedFilename.Length());
   } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
   if (!len) {
diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h
index 01a551fa7a1..dc94e3a7d3b 100644
--- a/xpcom/io/FileUtilsWin.h
+++ b/xpcom/io/FileUtilsWin.h
@@ -35,7 +35,7 @@ NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath)
   nsAutoString logicalDrives;
   DWORD len = 0;
   while(true) {
-    len = GetLogicalDriveStringsW(len, logicalDrives.BeginWriting());
+    len = GetLogicalDriveStringsW(len, reinterpret_cast(logicalDrives.BeginWriting()));
     if (!len) {
       return false;
     } else if (len > logicalDrives.Length()) {
@@ -56,7 +56,7 @@ NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath)
     DWORD targetPathLen = 0;
     SetLastError(ERROR_SUCCESS);
     while (true) {
-      targetPathLen = QueryDosDeviceW(driveTemplate, targetPath.BeginWriting(),
+      targetPathLen = QueryDosDeviceW(driveTemplate, reinterpret_cast(targetPath.BeginWriting()),
                                       targetPath.Length());
       if (targetPathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
         break;
@@ -68,7 +68,7 @@ NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath)
       size_t firstTargetPathLen = wcslen(targetPath.get());
       const char16_t* pathComponent = aNtPath.BeginReading() +
                                       firstTargetPathLen;
-      bool found = _wcsnicmp(aNtPath.BeginReading(), targetPath.get(),
+      bool found = _wcsnicmp(char16ptr_t(aNtPath.BeginReading()), targetPath.get(),
                              firstTargetPathLen) == 0 &&
                    *pathComponent == L'\\';
       if (found) {
diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp
index 4533f7528df..1f1d75bc41d 100644
--- a/xpcom/tests/windows/TestNtPathToDosPath.cpp
+++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp
@@ -10,6 +10,8 @@
 #include 
 
 #include "mozilla/FileUtilsWin.h"
+#include "mozilla/DebugOnly.h"
+#include "nsCRTGlue.h"
 
 class DriveMapping
 {
@@ -35,8 +37,8 @@ private:
 };
 
 DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath)
-  : mRemoteUNCPath(aRemoteUNCPath)
-  , mDriveLetter(0)
+  : mDriveLetter(0)
+  , mRemoteUNCPath(aRemoteUNCPath)
 {
 }
 
@@ -56,7 +58,7 @@ DriveMapping::DoMapping()
   NETRESOURCEW netRes = {0};
   netRes.dwType = RESOURCETYPE_DISK;
   netRes.lpLocalName = drvTemplate;
-  netRes.lpRemoteName = mRemoteUNCPath.BeginWriting();
+  netRes.lpRemoteName = reinterpret_cast(mRemoteUNCPath.BeginWriting());
   wchar_t driveLetter = L'D';
   DWORD result = NO_ERROR;
   do {
@@ -86,7 +88,7 @@ void
 DriveMapping::Disconnect(wchar_t aDriveLetter)
 {
   wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'};
-  DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE);
+  mozilla::DebugOnly result = WNetCancelConnection2W(drvTemplate, 0, TRUE);
   MOZ_ASSERT(result == NO_ERROR);
 }
 
@@ -104,7 +106,7 @@ DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath)
   aNtPath.SetLength(MAX_PATH);
   DWORD pathLen;
   while (true) {
-    pathLen = QueryDosDeviceW(drvTpl, aNtPath.BeginWriting(), aNtPath.Length());
+    pathLen = QueryDosDeviceW(drvTpl, reinterpret_cast(aNtPath.BeginWriting()), aNtPath.Length());
     if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
       break;
     }
@@ -115,7 +117,7 @@ DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath)
   }
   // aNtPath contains embedded NULLs, so we need to figure out the real length
   // via wcslen.
-  aNtPath.SetLength(wcslen(aNtPath.BeginReading()));
+  aNtPath.SetLength(NS_strlen(aNtPath.BeginReading()));
   return true;
 }
 
@@ -191,7 +193,7 @@ int main(int argc, char* argv[])
       fail("Querying network drive");
       return 1;
     }
-    networkPath += L"\\";
+    networkPath += MOZ_UTF16("\\");
     if (!TestNtPathToDosPath(networkPath.get(), expected)) {
       fail("Mapped UNC path");
       result = 1;
@@ -208,7 +210,7 @@ int main(int argc, char* argv[])
       fail("Querying second network drive");
       return 1;
     }
-    networkPath += L"\\";
+    networkPath += MOZ_UTF16("\\");
     if (!TestNtPathToDosPath(networkPath.get(), expected)) {
       fail("Re-mapped UNC path");
       result = 1;

From f3df1dc1c369c33b42852c28fca94ef2edc10442 Mon Sep 17 00:00:00 2001
From: Nicolas Silva 
Date: Thu, 13 Feb 2014 11:45:13 +0100
Subject: [PATCH 40/40] Bug 971744 - Move checks from GetAsDrawTarget to Lock
 in D3D9 TextureClient. r=mattwoodrow

---
 gfx/layers/d3d9/TextureD3D9.cpp | 38 +++++++++++++++++----------------
 1 file changed, 20 insertions(+), 18 deletions(-)

diff --git a/gfx/layers/d3d9/TextureD3D9.cpp b/gfx/layers/d3d9/TextureD3D9.cpp
index 3bdd4e5b63b..646b1e8685f 100644
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -1274,12 +1274,30 @@ CairoTextureClientD3D9::Lock(OpenMode)
   if (!IsValid() || !IsAllocated()) {
     return false;
   }
+
+  if (!gfxWindowsPlatform::GetPlatform()->GetD3D9Device()) {
+    // If the device has failed then we should not lock the surface,
+    // even if we could.
+    mD3D9Surface = nullptr;
+    return false;
+  }
+
+  if (!mD3D9Surface) {
+    HRESULT hr = mTexture->GetSurfaceLevel(0, getter_AddRefs(mD3D9Surface));
+    if (FAILED(hr)) {
+      NS_WARNING("Failed to get texture surface level.");
+      return false;
+    }
+  }
+
+  mIsLocked = true;
+
   if (mNeedsClear) {
     mDrawTarget = GetAsDrawTarget();
     mDrawTarget->ClearRect(Rect(0, 0, GetSize().width, GetSize().height));
     mNeedsClear = false;
   }
-  mIsLocked = true;
+
   return true;
 }
 
@@ -1318,27 +1336,11 @@ CairoTextureClientD3D9::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor)
 TemporaryRef
 CairoTextureClientD3D9::GetAsDrawTarget()
 {
-  MOZ_ASSERT(mIsLocked && mTexture);
+  MOZ_ASSERT(mIsLocked && mD3D9Surface);
   if (mDrawTarget) {
     return mDrawTarget;
   }
 
-  if (!gfxWindowsPlatform::GetPlatform()->GetD3D9Device()) {
-    // If the device has failed then we should not lock the surface,
-    // even if we could.
-    mD3D9Surface = nullptr;
-    mTexture = nullptr;
-    return nullptr;
-  }
-
-  if (!mD3D9Surface) {
-    HRESULT hr = mTexture->GetSurfaceLevel(0, getter_AddRefs(mD3D9Surface));
-    if (FAILED(hr)) {
-      NS_WARNING("Failed to get texture surface level.");
-      return nullptr;
-    }
-  }
-
   if (ContentForFormat(mFormat) == gfxContentType::COLOR) {
     mSurface = new gfxWindowsSurface(mD3D9Surface);
     if (!mSurface || mSurface->CairoStatus()) {