diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index e69e982252e..d566ded3044 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -141,15 +141,6 @@ EmitCheck(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t delta) return offset; } -static StaticBlockObject & -LastBlockAdded(BytecodeEmitter *bce, jsbytecode *pc) -{ - DebugOnly index = GET_UINT32_INDEX(pc); - JS_ASSERT(index < bce->objectList.length); - JS_ASSERT(index == bce->objectList.length - 1); - return bce->objectList.lastbox->object->as(); -} - static void UpdateDepth(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t target) { @@ -168,26 +159,8 @@ UpdateDepth(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t target) bce->maxStackDepth = depth; } - /* - * Specially handle any case in which StackUses or StackDefs would call - * NumBlockSlots, since that requires a well-formed script. This allows us - * to safely pass nullptr as the 'script' parameter to StackUses and - * StackDefs. - */ - int nuses, ndefs; - if (op == JSOP_ENTERBLOCK) { - nuses = 0; - ndefs = LastBlockAdded(bce, pc).slotCount(); - } else if (op == JSOP_ENTERLET0) { - nuses = ndefs = LastBlockAdded(bce, pc).slotCount(); - } else if (op == JSOP_ENTERLET1) { - nuses = ndefs = LastBlockAdded(bce, pc).slotCount() + 1; - } else if (op == JSOP_ENTERLET2) { - nuses = ndefs = LastBlockAdded(bce, pc).slotCount() + 2; - } else { - nuses = StackUses(nullptr, pc); - ndefs = StackDefs(nullptr, pc); - } + int nuses = StackUses(nullptr, pc); + int ndefs = StackDefs(nullptr, pc); bce->stackDepth -= nuses; JS_ASSERT(bce->stackDepth >= 0); @@ -568,7 +541,10 @@ class NonLocalExitScope { return false; if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) return false; - EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObj.slotCount()); + if (blockObj.needsClone()) { + if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) + return false; + } openScopeIndex = bce->blockScopeList.length() - 1; return true; } @@ -623,11 +599,11 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) } if (stmt->isBlockScope) { - FLUSH_POPS(); JS_ASSERT(stmt->blockObj); StaticBlockObject &blockObj = *stmt->blockObj; if (!popScopeForNonLocalExit(blockObj, stmt->blockScopeIndex)) return false; + npops += blockObj.slotCount(); } } @@ -773,6 +749,55 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, StaticBlockObjec static bool EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce); +// ~ 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::getBlockScope(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 LeaveBlockScope, which will emit +// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. Pass EnterBlockScope a fresh +// StmtInfoBCE object, and pass that same object to the corresponding +// LeaveBlockScope. 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) @@ -792,19 +817,13 @@ EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, O JS_ASSERT(depth >= 0); blockObj.setStackDepth(depth); - JSOp op; - switch (extraSlots) { - case 0: op = JSOP_ENTERLET0; break; - case 1: op = JSOP_ENTERLET1; break; - case 2: op = JSOP_ENTERLET2; break; - default: MOZ_ASSUME_UNREACHABLE("unexpected extraSlots"); - } - if (!ComputeAliasedSlots(cx, bce, blockObj)) return false; - if (!EmitInternedObjectOp(cx, scopeObjectIndex, op, bce)) - return false; + if (blockObj.needsClone()) { + if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce)) + return false; + } stmt->blockScopeIndex = bce->blockScopeList.length(); if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) @@ -837,7 +856,7 @@ PopStatementBCE(ExclusiveContext *cx, BytecodeEmitter *bce) } static bool -LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op) +LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce) { StmtInfoBCE *stmt = bce->topStmt; JS_ASSERT(stmt->isBlockScope); @@ -852,7 +871,7 @@ LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op) JS_ASSERT(blockObj == bce->blockChain); #endif - uint32_t slotCount = bce->blockChain->slotCount(); + bool blockOnChain = bce->blockChain->needsClone(); if (!PopStatementBCE(cx, bce)) return false; @@ -862,8 +881,10 @@ LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op) bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset()); - JS_ASSERT(op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR); - EMIT_UINT16_IMM_OP(op, slotCount); + if (blockOnChain) { + if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) + return false; + } return true; } @@ -2684,10 +2705,10 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) } } - if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { - if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK)) + if (!LeaveBlockScope(cx, bce)) return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount()); } else { if (!PopStatementBCE(cx, bce)) return false; @@ -3983,7 +4004,8 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (ParseNode *pn2 = pn->pn_kid2) { // The emitted code for a catch block looks like: // - // enterblock + // undefined... as many as there are locals in the catch block + // [pushblockscope] only if any local aliased // exception // if there is a catchguard: // dup @@ -3991,14 +4013,16 @@ EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) // if there is a catchguard: // < catchguard code > // ifne POST - // throwing pop exception to cx->exception // debugleaveblock - // leaveblock + // [popblockscope] only if any local aliased + // popnv leave exception on top + // throwing pop exception to cx->exception // goto // POST: pop // < catch block contents > // debugleaveblock - // leaveblock + // [popblockscope] only if any local aliased + // popn // goto non-local; finally applies // // If there's no catch block without a catchguard, the last blockChain and does not + * Note that, since pushblockscope simply changes fp->scopeChain and does not * otherwise touch the stack, evaluation of the let-var initializers must leave * the initial value in the let-var's future slot. */ @@ -4236,9 +4262,13 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) if (!EmitTree(cx, bce, letBody->pn_expr)) return false; - if (!LeaveBlockScope(cx, bce, letBody->getOp())) + if (!LeaveBlockScope(cx, bce)) return false; + JSOp leaveOp = letBody->getOp(); + JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV); + EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount()); + return true; } @@ -4250,7 +4280,7 @@ MOZ_NEVER_INLINE static bool EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); - JS_ASSERT(pn->getOp() == JSOP_LEAVEBLOCK); + JS_ASSERT(pn->getOp() == JSOP_POPN); StmtInfoBCE stmtInfo(cx); ObjectBox *objbox = pn->pn_objbox; @@ -4268,9 +4298,11 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (!EmitTree(cx, bce, pn->pn_expr)) return false; - if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK)) + if (!LeaveBlockScope(cx, bce)) return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, slots); + return true; } @@ -4440,14 +4472,14 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t if (!PopStatementBCE(cx, bce)) return false; - // Pop result and iter. - EMIT_UINT16_IMM_OP(JSOP_POPN, 2); - if (letDecl) { - if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK)) + if (!LeaveBlockScope(cx, bce)) return false; } + // Pop result, iter, and slots from the lexical block (if any). + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2); + return true; } @@ -4468,20 +4500,24 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t if (letDecl) { /* * The let's slot(s) will be under the iterator, but the block must not - * be entered (i.e. fp->blockChain set) until after evaluating the rhs. - * Thus, push to reserve space and enterblock after. The same argument - * applies when leaving the loop. Thus, a for-let-in loop looks like: + * 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 - * enterlet1 + * pushblockscope (if needed) * goto * ... loop body * ifne - * leaveforinlet + * 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) @@ -4600,8 +4636,9 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t return false; if (letDecl) { - if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK)) + if (!LeaveBlockScope(cx, bce)) return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); } return true; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index aba7792d6a4..0eb9f37b657 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -603,9 +603,9 @@ FullParseHandler::addCatchBlock(ParseNode *catchList, ParseNode *letBlock, inline void FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr) { - JS_ASSERT(block->isOp(JSOP_LEAVEBLOCK)); + JS_ASSERT(block->isOp(JSOP_POPN)); if (leaveBlockExpr) - block->setOp(JSOP_LEAVEBLOCKEXPR); + block->setOp(JSOP_POPNV); block->pn_expr = kid; } @@ -637,7 +637,7 @@ FullParseHandler::newLexicalScope(ObjectBox *blockbox) if (!pn) return nullptr; - pn->setOp(JSOP_LEAVEBLOCK); + pn->setOp(JSOP_POPN); pn->pn_objbox = blockbox; pn->pn_cookie.makeFree(); pn->pn_dflags = 0; diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 39fdd2efa17..b00c0425b7a 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -407,7 +407,7 @@ enum ParseNodeKind * PNK_NULL, * PNK_THIS * - * PNK_LEXICALSCOPE name pn_op: JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR + * 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 diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 3702596a7c0..b458d1cf67e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -3231,7 +3231,6 @@ Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInf if (!pn) return null(); - /* Tell codegen to emit JSOP_ENTERLETx (not JSOP_ENTERBLOCK). */ pn->pn_dflags |= PND_LET; /* Populate the new scope with decls found in the head with updated blockid. */ @@ -3601,7 +3600,7 @@ Parser::letDeclaration() if (!pn1) return null(); - pn1->setOp(JSOP_LEAVEBLOCK); + pn1->setOp(JSOP_POPN); pn1->pn_pos = pc->blockNode->pn_pos; pn1->pn_objbox = blockbox; pn1->pn_expr = pc->blockNode; @@ -3637,8 +3636,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_LEAVEBLOCK, - pn->isOp(JSOP_NOP)); + JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_POPNV, + pn->pn_expr->isOp(JSOP_POPN)); } else pn = letDeclaration(); return pn; @@ -6718,16 +6717,21 @@ Parser::arrayInitializer() * * Each let () {...} or for (let ...) ... compiles to: * - * JSOP_ENTERBLOCK ... JSOP_LEAVEBLOCK + * JSOP_PUSHN // Push space for block-scoped locals. + * (JSOP_PUSHBLOCKSCOPE ) // If a local is aliased, push on scope + * // chain. + * ... + * JSOP_DEBUGLEAVEBLOCK // Invalidate any DebugScope proxies. + * JSOP_POPBLOCKSCOPE? // Pop off scope chain, if needed. + * JSOP_POPN // Pop space for block-scoped locals. * * where is a literal object representing the block scope, * with properties, naming each var declared in the block. * - * Each var declaration in a let-block binds a name in at - * compile time, and allocates a slot on the operand stack at - * runtime via JSOP_ENTERBLOCK. A block-local var is accessed by - * the JSOP_GETLOCAL and JSOP_SETLOCAL ops. These ops have an - * immediate operand, the local slot's stack index from fp->spbase. + * Each var declaration in a let-block binds a name in at compile + * time. A block-local var is accessed by the JSOP_GETLOCAL and + * JSOP_SETLOCAL ops. These ops have an immediate operand, the local + * slot's stack index from fp->spbase. * * The array comprehension iteration step, array.push(i * j) in * the example above, is done by ; JSOP_ARRAYPUSH , diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 60592925193..a188d127b9a 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -860,6 +860,15 @@ BaselineCompiler::emit_JSOP_POPN() return true; } +bool +BaselineCompiler::emit_JSOP_POPNV() +{ + frame.popRegsAndSync(1); + frame.popn(GET_UINT16(pc)); + frame.push(R0); + return true; +} + bool BaselineCompiler::emit_JSOP_DUP() { @@ -2575,23 +2584,14 @@ BaselineCompiler::emit_JSOP_RETSUB() return emitOpIC(stubCompiler.getStub(&stubSpace_)); } -typedef bool (*EnterBlockFn)(JSContext *, BaselineFrame *, Handle); -static const VMFunction EnterBlockInfo = FunctionInfo(jit::EnterBlock); +typedef bool (*PushBlockScopeFn)(JSContext *, BaselineFrame *, Handle); +static const VMFunction PushBlockScopeInfo = FunctionInfo(jit::PushBlockScope); bool -BaselineCompiler::emitEnterBlock() +BaselineCompiler::emit_JSOP_PUSHBLOCKSCOPE() { StaticBlockObject &blockObj = script->getObject(pc)->as(); - if (JSOp(*pc) == JSOP_ENTERBLOCK) { - for (size_t i = 0; i < blockObj.slotCount(); i++) - frame.push(UndefinedValue()); - - // Pushed values will be accessed using GETLOCAL and SETLOCAL, so ensure - // they are synced. - frame.syncStack(0); - } - // Call a stub to push the block on the block chain. prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); @@ -2599,31 +2599,22 @@ BaselineCompiler::emitEnterBlock() pushArg(ImmGCPtr(&blockObj)); pushArg(R0.scratchReg()); - return callVM(EnterBlockInfo); + return callVM(PushBlockScopeInfo); } -bool -BaselineCompiler::emit_JSOP_ENTERBLOCK() -{ - return emitEnterBlock(); -} +typedef bool (*PopBlockScopeFn)(JSContext *, BaselineFrame *); +static const VMFunction PopBlockScopeInfo = FunctionInfo(jit::PopBlockScope); bool -BaselineCompiler::emit_JSOP_ENTERLET0() +BaselineCompiler::emit_JSOP_POPBLOCKSCOPE() { - return emitEnterBlock(); -} + // Call a stub to pop the block from the block chain. + prepareVMCall(); -bool -BaselineCompiler::emit_JSOP_ENTERLET1() -{ - return emitEnterBlock(); -} + masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); + pushArg(R0.scratchReg()); -bool -BaselineCompiler::emit_JSOP_ENTERLET2() -{ - return emitEnterBlock(); + return callVM(PopBlockScopeInfo); } typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *); @@ -2643,56 +2634,6 @@ BaselineCompiler::emit_JSOP_DEBUGLEAVEBLOCK() return callVM(DebugLeaveBlockInfo); } -typedef bool (*LeaveBlockFn)(JSContext *, BaselineFrame *); -static const VMFunction LeaveBlockInfo = FunctionInfo(jit::LeaveBlock); - -bool -BaselineCompiler::emitLeaveBlock() -{ - // Call a stub to pop the block from the block chain. - prepareVMCall(); - - masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); - pushArg(R0.scratchReg()); - - return callVM(LeaveBlockInfo); -} - -bool -BaselineCompiler::emit_JSOP_LEAVEBLOCK() -{ - if (!emitLeaveBlock()) - return false; - - // Pop slots pushed by JSOP_ENTERBLOCK. - frame.popn(GET_UINT16(pc)); - return true; -} - -bool -BaselineCompiler::emit_JSOP_LEAVEBLOCKEXPR() -{ - if (!emitLeaveBlock()) - return false; - - // Pop slots pushed by JSOP_ENTERBLOCK, but leave the topmost value - // on the stack. - frame.popRegsAndSync(1); - frame.popn(GET_UINT16(pc)); - frame.push(R0); - return true; -} - -bool -BaselineCompiler::emit_JSOP_LEAVEFORLETIN() -{ - if (!emitLeaveBlock()) - return false; - - // Another op will pop the slots (after the enditer). - return true; -} - typedef bool (*GetAndClearExceptionFn)(JSContext *, MutableHandleValue); static const VMFunction GetAndClearExceptionInfo = FunctionInfo(GetAndClearException); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index a72ceb5e358..8cc0432ddf1 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -27,6 +27,7 @@ namespace jit { _(JSOP_NOTEARG) \ _(JSOP_POP) \ _(JSOP_POPN) \ + _(JSOP_POPNV) \ _(JSOP_DUP) \ _(JSOP_DUP2) \ _(JSOP_SWAP) \ @@ -145,13 +146,8 @@ namespace jit { _(JSOP_FINALLY) \ _(JSOP_GOSUB) \ _(JSOP_RETSUB) \ - _(JSOP_ENTERBLOCK) \ - _(JSOP_ENTERLET0) \ - _(JSOP_ENTERLET1) \ - _(JSOP_ENTERLET2) \ - _(JSOP_LEAVEBLOCK) \ - _(JSOP_LEAVEBLOCKEXPR) \ - _(JSOP_LEAVEFORLETIN) \ + _(JSOP_PUSHBLOCKSCOPE) \ + _(JSOP_POPBLOCKSCOPE) \ _(JSOP_DEBUGLEAVEBLOCK) \ _(JSOP_EXCEPTION) \ _(JSOP_DEBUGGER) \ @@ -255,9 +251,6 @@ class BaselineCompiler : public BaselineCompilerSpecific bool emitFormalArgAccess(uint32_t arg, bool get); - bool emitEnterBlock(); - bool emitLeaveBlock(); - bool addPCMappingEntry(bool addIndexEntry); void getScopeCoordinateObject(Register reg); diff --git a/js/src/jit/BaselineFrame-inl.h b/js/src/jit/BaselineFrame-inl.h index 234dae4d196..d4702eaace1 100644 --- a/js/src/jit/BaselineFrame-inl.h +++ b/js/src/jit/BaselineFrame-inl.h @@ -36,31 +36,22 @@ BaselineFrame::popOffScopeChain() inline bool BaselineFrame::pushBlock(JSContext *cx, Handle block) { - JS_ASSERT_IF(hasBlockChain(), blockChain() == *block->enclosingBlock()); + JS_ASSERT(block->needsClone()); - if (block->needsClone()) { - ClonedBlockObject *clone = ClonedBlockObject::create(cx, block, this); - if (!clone) - return false; + ClonedBlockObject *clone = ClonedBlockObject::create(cx, block, this); + if (!clone) + return false; + pushOnScopeChain(*clone); - pushOnScopeChain(*clone); - } - - setBlockChain(*block); return true; } inline void BaselineFrame::popBlock(JSContext *cx) { - JS_ASSERT(hasBlockChain()); + JS_ASSERT(scopeChain_->is()); - if (blockChain_->needsClone()) { - JS_ASSERT(scopeChain_->as().staticBlock() == *blockChain_); - popOffScopeChain(); - } - - setBlockChain(*blockChain_->enclosingBlock()); + popOffScopeChain(); } inline CallObject & diff --git a/js/src/jit/BaselineFrame.h b/js/src/jit/BaselineFrame.h index 32d92b1fcaf..99504d6b4cb 100644 --- a/js/src/jit/BaselineFrame.h +++ b/js/src/jit/BaselineFrame.h @@ -169,7 +169,7 @@ class BaselineFrame Value &unaliasedLocal(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const { #ifdef DEBUG - CheckLocalUnaliased(checkAliasing, script(), maybeBlockChain(), i); + CheckLocalUnaliased(checkAliasing, script(), i); #endif return *valueSlot(i); } diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 28b2285a8fd..ef0a2e6b798 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1252,6 +1252,7 @@ IonBuilder::traverseBytecode() switch (op) { case JSOP_POP: case JSOP_POPN: + case JSOP_POPNV: case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: @@ -1504,6 +1505,15 @@ 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); + return true; + } + case JSOP_NEWINIT: if (GET_UINT8(pc) == JSProto_Array) return jsop_newarray(0); diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index da14ed83316..b7b4f061be4 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -883,13 +883,13 @@ OnDebuggerStatement(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *m } bool -EnterBlock(JSContext *cx, BaselineFrame *frame, Handle block) +PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle block) { return frame->pushBlock(cx, block); } bool -LeaveBlock(JSContext *cx, BaselineFrame *frame) +PopBlockScope(JSContext *cx, BaselineFrame *frame) { frame->popBlock(cx); return true; diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 037b76a5042..a5ccb4f255c 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -652,8 +652,8 @@ JSObject *InitRestParameter(JSContext *cx, uint32_t length, Value *rest, HandleO bool HandleDebugTrap(JSContext *cx, BaselineFrame *frame, uint8_t *retAddr, bool *mustReturn); bool OnDebuggerStatement(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn); -bool EnterBlock(JSContext *cx, BaselineFrame *frame, Handle block); -bool LeaveBlock(JSContext *cx, BaselineFrame *frame); +bool PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle block); +bool PopBlockScope(JSContext *cx, BaselineFrame *frame); bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc); bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame, diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index ba4ec3a5e30..912ea0be115 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -253,7 +253,6 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) case JSOP_EVAL: case JSOP_SPREADEVAL: - case JSOP_ENTERLET2: case JSOP_ENTERWITH: canTrackVars = false; break; diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index cb8d3f25f27..91607c264f4 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -114,18 +114,6 @@ js_GetVariableBytecodeLength(jsbytecode *pc) } } -static uint32_t -NumBlockSlots(JSScript *script, jsbytecode *pc) -{ - JS_ASSERT(*pc == JSOP_ENTERBLOCK || - *pc == JSOP_ENTERLET0 || *pc == JSOP_ENTERLET1 || *pc == JSOP_ENTERLET2); - JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET0_LENGTH); - JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET1_LENGTH); - JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET2_LENGTH); - - return script->getObject(GET_UINT32_INDEX(pc))->as().propertyCountForCompilation(); -} - unsigned js::StackUses(JSScript *script, jsbytecode *pc) { @@ -138,16 +126,8 @@ js::StackUses(JSScript *script, jsbytecode *pc) switch (op) { case JSOP_POPN: return GET_UINT16(pc); - case JSOP_LEAVEBLOCK: - return GET_UINT16(pc); - case JSOP_LEAVEBLOCKEXPR: + case JSOP_POPNV: return GET_UINT16(pc) + 1; - case JSOP_ENTERLET0: - return NumBlockSlots(script, pc); - case JSOP_ENTERLET1: - return NumBlockSlots(script, pc) + 1; - case JSOP_ENTERLET2: - return NumBlockSlots(script, pc) + 2; default: /* stack: fun, this, [argc arguments] */ JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || @@ -161,15 +141,8 @@ js::StackDefs(JSScript *script, jsbytecode *pc) { JSOp op = (JSOp) *pc; const JSCodeSpec &cs = js_CodeSpec[op]; - if (cs.ndefs >= 0) - return cs.ndefs; - - uint32_t n = NumBlockSlots(script, pc); - if (op == JSOP_ENTERLET1) - return n + 1; - if (op == JSOP_ENTERLET2) - return n + 2; - return n; + JS_ASSERT (cs.ndefs >= 0); + return cs.ndefs; } static const char * const countBaseNames[] = { diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 0e419e58a80..9bc672f5c4e 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -97,7 +97,9 @@ OPDEF(JSOP_SPREADNEW, 42, "spreadnew", NULL, 1, 3, 1, JOF_BYTE|JOF_IN /* spreadcall variant of JSOP_EVAL */ OPDEF(JSOP_SPREADEVAL,43, "spreadeval", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) -OPDEF(JSOP_UNUSED44, 44, "unused44", NULL, 1, 0, 0, JOF_BYTE) +/* Pop N values, preserving top value. */ +OPDEF(JSOP_POPNV, 44, "popnv", NULL, 3, -1, 1, JOF_UINT16) + OPDEF(JSOP_UNUSED45, 45, "unused45", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED46, 46, "unused46", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED47, 47, "unused47", NULL, 1, 0, 0, JOF_BYTE) @@ -220,9 +222,7 @@ OPDEF(JSOP_UNUSED101, 101, "unused101", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED102, 102, "unused102", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED103, 103, "unused103", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED104, 104, "unused104", NULL, 1, 0, 0, JOF_BYTE) - -/* Leave a for-let-in block leaving its storage pushed (to be popped after enditer). */ -OPDEF(JSOP_LEAVEFORLETIN, 105,"leaveforletin",NULL, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_UNUSED105, 105, "unused105", NULL, 1, 0, 0, JOF_BYTE) /* The argument is the offset to the next statement and is used by IonMonkey. */ OPDEF(JSOP_LABEL, 106,"label", NULL, 5, 0, 0, JOF_JUMP) @@ -398,13 +398,9 @@ OPDEF(JSOP_UNUSED183, 183,"unused183", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_CALLPROP, 184,"callprop", NULL, 5, 1, 1, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_TMPSLOT3) -/* Enter a let block/expr whose slots are at the top of the stack. */ -OPDEF(JSOP_ENTERLET0, 185,"enterlet0", NULL, 5, -1, -1, JOF_OBJECT) - -/* Enter a let block/expr whose slots are 1 below the top of the stack. */ -OPDEF(JSOP_ENTERLET1, 186,"enterlet1", NULL, 5, -1, -1, JOF_OBJECT) -/* Enter a let block/expr whose slots are 2 below the top of the stack. */ -OPDEF(JSOP_ENTERLET2, 187,"enterlet2", NULL, 5, -1, -1, JOF_OBJECT) +OPDEF(JSOP_UNUSED185, 185,"unused185", NULL, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_UNUSED186, 186,"unused186", NULL, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_UNUSED187, 187,"unused187", NULL, 1, 0, 0, JOF_BYTE) /* * Opcode to hold 24-bit immediate integer operands. @@ -436,8 +432,8 @@ OPDEF(JSOP_TYPEOFEXPR, 197,"typeofexpr", NULL, 1, 1, 1, JOF_BYTE|JOF_DE /* * Block-local scope support. */ -OPDEF(JSOP_ENTERBLOCK, 198,"enterblock", NULL, 5, 0, -1, JOF_OBJECT) -OPDEF(JSOP_LEAVEBLOCK, 199,"leaveblock", NULL, 3, -1, 0, JOF_UINT16) +OPDEF(JSOP_PUSHBLOCKSCOPE,198,"pushblockscope", NULL, 5, 0, 0, JOF_OBJECT) +OPDEF(JSOP_POPBLOCKSCOPE, 199,"popblockscope", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_DEBUGLEAVEBLOCK, 200,"debugleaveblock", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED201, 201,"unused201", NULL, 1, 0, 0, JOF_BYTE) @@ -459,12 +455,7 @@ OPDEF(JSOP_GETFUNNS, 205,"getfunns", NULL, 1, 0, 1, JOF_BYTE) */ OPDEF(JSOP_ENUMCONSTELEM, 206,"enumconstelem",NULL, 1, 3, 0, JOF_BYTE|JOF_SET) -/* - * Variant of JSOP_LEAVEBLOCK has a result on the stack above the locals, - * which must be moved down when the block pops. - */ -OPDEF(JSOP_LEAVEBLOCKEXPR,207,"leaveblockexpr",NULL, 3, -1, 1, JOF_UINT16) - +OPDEF(JSOP_UNUSED207, 207, "unused207", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED208, 208, "unused208", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED209, 209, "unused209", NULL, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_UNUSED210, 210, "unused210", NULL, 1, 0, 0, JOF_BYTE) diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index d25eda18bfa..7f03056a48a 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -854,8 +854,8 @@ js::UnwindScope(JSContext *cx, ScopeIter &si, uint32_t stackDepth) return; if (cx->compartment()->debugMode()) DebugScopes::onPopBlock(cx, si); - JS_ASSERT(&si.staticBlock() == si.frame().maybeBlockChain()); - si.frame().popBlock(cx); + if (si.staticBlock().needsClone()) + si.frame().popBlock(cx); break; case ScopeIter::With: if (si.scope().as().stackDepth() < stackDepth) @@ -1589,7 +1589,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_UNUSED2) -CASE(JSOP_UNUSED44) CASE(JSOP_UNUSED45) CASE(JSOP_UNUSED46) CASE(JSOP_UNUSED47) @@ -1602,6 +1601,7 @@ CASE(JSOP_UNUSED101) CASE(JSOP_UNUSED102) CASE(JSOP_UNUSED103) CASE(JSOP_UNUSED104) +CASE(JSOP_UNUSED105) CASE(JSOP_UNUSED107) CASE(JSOP_UNUSED125) CASE(JSOP_UNUSED126) @@ -1641,6 +1641,9 @@ CASE(JSOP_UNUSED180) CASE(JSOP_UNUSED181) CASE(JSOP_UNUSED182) CASE(JSOP_UNUSED183) +CASE(JSOP_UNUSED185) +CASE(JSOP_UNUSED186) +CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED189) CASE(JSOP_UNUSED190) CASE(JSOP_UNUSED191) @@ -1649,6 +1652,7 @@ CASE(JSOP_UNUSED194) CASE(JSOP_UNUSED196) CASE(JSOP_UNUSED201) CASE(JSOP_GETFUNNS) +CASE(JSOP_UNUSED207) CASE(JSOP_UNUSED208) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) @@ -1714,11 +1718,24 @@ CASE(JSOP_POPN) JS_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth()); REGS.sp -= GET_UINT16(REGS.pc); #ifdef DEBUG - if (StaticBlockObject *block = REGS.fp()->maybeBlockChain()) + if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPN_LENGTH)) JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount()); #endif END_CASE(JSOP_POPN) +CASE(JSOP_POPNV) +{ + JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth()); + Value val = REGS.sp[-1]; + REGS.sp -= GET_UINT16(REGS.pc); + REGS.sp[-1] = val; +#ifdef DEBUG + if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPNV_LENGTH)) + JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount()); +#endif +} +END_CASE(JSOP_POPNV) + CASE(JSOP_SETRVAL) POP_RETURN_VALUE(); END_CASE(JSOP_SETRVAL) @@ -3336,52 +3353,36 @@ CASE(JSOP_DEBUGGER) } END_CASE(JSOP_DEBUGGER) -CASE(JSOP_ENTERBLOCK) -CASE(JSOP_ENTERLET0) -CASE(JSOP_ENTERLET1) -CASE(JSOP_ENTERLET2) +CASE(JSOP_PUSHBLOCKSCOPE) { StaticBlockObject &blockObj = script->getObject(REGS.pc)->as(); - if (*REGS.pc == JSOP_ENTERBLOCK) { - JS_ASSERT(REGS.stackDepth() == blockObj.stackDepth()); - JS_ASSERT(REGS.stackDepth() + blockObj.slotCount() <= script->nslots()); - Value *vp = REGS.sp + blockObj.slotCount(); - SetValueRangeToUndefined(REGS.sp, vp); - REGS.sp = vp; - } + 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 iff there are any closed-over variables. */ + // Clone block and push on scope chain. if (!REGS.fp()->pushBlock(cx, blockObj)) goto error; } -END_CASE(JSOP_ENTERBLOCK) +END_CASE(JSOP_PUSHBLOCKSCOPE) -CASE(JSOP_LEAVEBLOCK) -CASE(JSOP_LEAVEFORLETIN) -CASE(JSOP_LEAVEBLOCKEXPR) +CASE(JSOP_POPBLOCKSCOPE) { - blockDepth = REGS.fp()->blockChain().stackDepth(); +#ifdef DEBUG + // Pop block from scope chain. + JS_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK); + StaticBlockObject *blockObj = script->getBlockScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH); + JS_ASSERT(blockObj && 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. REGS.fp()->popBlock(cx); - - if (*REGS.pc == JSOP_LEAVEBLOCK) { - /* Pop the block's slots. */ - REGS.sp -= GET_UINT16(REGS.pc); - JS_ASSERT(REGS.stackDepth() == blockDepth); - } else if (*REGS.pc == JSOP_LEAVEBLOCKEXPR) { - /* Pop the block's slots maintaining the topmost expr. */ - Value *vp = ®S.sp[-1]; - REGS.sp -= GET_UINT16(REGS.pc); - JS_ASSERT(REGS.stackDepth() == blockDepth + 1); - REGS.sp[-1] = *vp; - } else { - /* Another op will pop; nothing to do here. */ - ADVANCE_AND_DISPATCH(JSOP_LEAVEFORLETIN_LENGTH); - } } -END_CASE(JSOP_LEAVEBLOCK) +END_CASE(JSOP_POPBLOCKSCOPE) CASE(JSOP_DEBUGLEAVEBLOCK) { diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 4e47e88d9f4..3e0766f5b68 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -110,7 +110,7 @@ inline Value & StackFrame::unaliasedLocal(unsigned i, MaybeCheckAliasing checkAliasing) { #ifdef DEBUG - CheckLocalUnaliased(checkAliasing, script(), maybeBlockChain(), i); + CheckLocalUnaliased(checkAliasing, script(), i); #endif return slots()[i]; } diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index dcb8ea9ae6a..46066d02d7e 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -327,20 +327,14 @@ StackFrame::epilogue(JSContext *cx) bool StackFrame::pushBlock(JSContext *cx, StaticBlockObject &block) { - JS_ASSERT_IF(hasBlockChain(), blockChain_ == block.enclosingBlock()); + JS_ASSERT (block.needsClone()); - if (block.needsClone()) { - Rooted blockHandle(cx, &block); - ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this); - if (!clone) - return false; + Rooted blockHandle(cx, &block); + ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this); + if (!clone) + return false; - pushOnScopeChain(*clone); - - blockChain_ = blockHandle; - } else { - blockChain_ = █ - } + pushOnScopeChain(*clone); return true; } @@ -348,12 +342,8 @@ StackFrame::pushBlock(JSContext *cx, StaticBlockObject &block) void StackFrame::popBlock(JSContext *cx) { - if (blockChain_->needsClone()) { - JS_ASSERT(scopeChain_->as().staticBlock() == *blockChain_); - popOffScopeChain(); - } - - blockChain_ = blockChain_->enclosingBlock(); + JS_ASSERT(scopeChain_->is()); + popOffScopeChain(); } void @@ -1254,8 +1244,7 @@ AbstractFramePtr::hasPushedSPSFrame() const #ifdef DEBUG void -js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, - StaticBlockObject *maybeBlock, unsigned i) +js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, unsigned i) { if (!checkAliasing) return; @@ -1264,13 +1253,8 @@ js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, if (i < script->nfixed()) { JS_ASSERT(!script->varIsAliased(i)); } else { - unsigned depth = i - script->nfixed(); - for (StaticBlockObject *b = maybeBlock; b; b = b->enclosingBlock()) { - if (b->containsVarAtDepth(depth)) { - JS_ASSERT(!b->isAliased(depth - b->stackDepth())); - break; - } - } + // FIXME: The callers of this function do not easily have the PC of the + // current frame, and so they do not know the block scope. } } #endif diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 4f3e1a7dd3a..81540bd01af 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -75,8 +75,7 @@ enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false }; #ifdef DEBUG extern void -CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, - StaticBlockObject *maybeBlock, unsigned i); +CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, unsigned i); #endif namespace jit { @@ -591,14 +590,8 @@ class StackFrame inline void popOffScopeChain(); /* - * Block chain - * - * Entering/leaving a let (or exception) block may do 1 or 2 things: First, - * a static block object (created at compiled time and stored in the - * script) is pushed on StackFrame::blockChain. Second, if the static block - * may be cloned to hold the dynamic values if this is needed for dynamic - * scope access. A clone is created for a static block iff - * StaticBlockObject::needsClone. + * For blocks with aliased locals, these interfaces push and pop entries on + * the scope chain. */ bool hasBlockChain() const {