diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index d5a6f96e846a..57b9483280c6 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 350f0c504761..bec627765b75 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 f93dd7a32266..725b3ce15dd5 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 45df351ee5f1..d76c868a9714 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 8fc7b4552ab1..ec7032de31e1 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 b42493548081..532df37fd491 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 1e5c05fba690..760d3c318d1c 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 027f643a96ff..4a4d41164b32 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 81e129c20c9d..d6249f7de7a3 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 ba64591f700c..4d18eac5f217 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 35d85c02646a..3c7300b1bb11 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 ce4ad7f41fd9..8d0601b645e7 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 a237f9d97c38..4454f4de2429 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 76917670eb32..82f8b9cd1693 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 4b2d9e140bc0..5e65fbd723f6 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 4ebc8953e727..2efe444bc995 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 fc7a00a3aa14..89622331dc71 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 0bd5ac8cfb8d..2c51a84b0b30 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 2ffa3db20edd..55b0a83eb4f3 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 4f3e275d8424..fecbfd99d61f 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 423bfe13c2a7..95aa04872906 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 abb323e54fbe..fdfb5f2e8c1b 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 45b93cd9fd62..10d635699f49 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 16afe8b4769e..eab64d801bf8 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 f5829292d901..c9fdbed265df 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 f29adcb496f8..5b0f6c0ea8ea 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 7270db93c85a..55fa790d826c 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