Bug 927782 - Part 11: Optimize block scopes without aliased locals. r=luke

This commit is contained in:
Andy Wingo 2013-11-26 12:07:02 +01:00
parent 52ca0ec4da
commit 4ba25cc8a5
18 changed files with 234 additions and 317 deletions

View File

@ -141,15 +141,6 @@ EmitCheck(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t delta)
return offset;
}
static StaticBlockObject &
LastBlockAdded(BytecodeEmitter *bce, jsbytecode *pc)
{
DebugOnly<uint32_t> index = GET_UINT32_INDEX(pc);
JS_ASSERT(index < bce->objectList.length);
JS_ASSERT(index == bce->objectList.length - 1);
return bce->objectList.lastbox->object->as<StaticBlockObject>();
}
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 <num block locals> leave exception on top
// throwing pop exception to cx->exception
// goto <next catch block>
// POST: pop
// < catch block contents >
// debugleaveblock
// leaveblock
// [popblockscope] only if any local aliased
// popn <num block locals>
// goto <end of catch blocks> non-local; finally applies
//
// If there's no catch block without a catchguard, the last <next catch
@ -4194,11 +4218,13 @@ EmitIf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
* destructure z
* pick 1
* pop -1
* enterlet0
* pushblockscope (if needed)
* evaluate e +1
* leaveblockexpr -3
* debugleaveblock
* popblockscope (if needed)
* popnv 3 -3
*
* Note that, since enterlet0 simply changes fp->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;

View File

@ -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;

View File

@ -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

View File

@ -3231,7 +3231,6 @@ Parser<FullParseHandler>::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<FullParseHandler>::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<FullParseHandler>::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<ParseHandler>::arrayInitializer()
*
* Each let () {...} or for (let ...) ... compiles to:
*
* JSOP_ENTERBLOCK <o> ... JSOP_LEAVEBLOCK <n>
* JSOP_PUSHN <N> // Push space for block-scoped locals.
* (JSOP_PUSHBLOCKSCOPE <O>) // 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 <N> // Pop space for block-scoped locals.
*
* where <o> is a literal object representing the block scope,
* with <n> properties, naming each var declared in the block.
*
* Each var declaration in a let-block binds a name in <o> 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 <o> 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 <i * j>; JSOP_ARRAYPUSH <array>,

View File

@ -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<StaticBlockObject *>);
static const VMFunction EnterBlockInfo = FunctionInfo<EnterBlockFn>(jit::EnterBlock);
typedef bool (*PushBlockScopeFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>);
static const VMFunction PushBlockScopeInfo = FunctionInfo<PushBlockScopeFn>(jit::PushBlockScope);
bool
BaselineCompiler::emitEnterBlock()
BaselineCompiler::emit_JSOP_PUSHBLOCKSCOPE()
{
StaticBlockObject &blockObj = script->getObject(pc)->as<StaticBlockObject>();
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<PopBlockScopeFn>(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<LeaveBlockFn>(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<GetAndClearExceptionFn>(GetAndClearException);

View File

@ -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);

View File

@ -36,31 +36,22 @@ BaselineFrame::popOffScopeChain()
inline bool
BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> 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<ClonedBlockObject>());
if (blockChain_->needsClone()) {
JS_ASSERT(scopeChain_->as<ClonedBlockObject>().staticBlock() == *blockChain_);
popOffScopeChain();
}
setBlockChain(*blockChain_->enclosingBlock());
popOffScopeChain();
}
inline CallObject &

View File

@ -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);
}

View File

@ -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);

View File

@ -883,13 +883,13 @@ OnDebuggerStatement(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *m
}
bool
EnterBlock(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block)
PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block)
{
return frame->pushBlock(cx, block);
}
bool
LeaveBlock(JSContext *cx, BaselineFrame *frame)
PopBlockScope(JSContext *cx, BaselineFrame *frame)
{
frame->popBlock(cx);
return true;

View File

@ -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<StaticBlockObject *> block);
bool LeaveBlock(JSContext *cx, BaselineFrame *frame);
bool PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
bool PopBlockScope(JSContext *cx, BaselineFrame *frame);
bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc);
bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame,

View File

@ -253,7 +253,6 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
case JSOP_EVAL:
case JSOP_SPREADEVAL:
case JSOP_ENTERLET2:
case JSOP_ENTERWITH:
canTrackVars = false;
break;

View File

@ -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<StaticBlockObject>().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[] = {

View File

@ -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)

View File

@ -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<WithObject>().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<StaticBlockObject>();
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 = &REGS.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)
{

View File

@ -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];
}

View File

@ -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<StaticBlockObject *> blockHandle(cx, &block);
ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this);
if (!clone)
return false;
Rooted<StaticBlockObject *> blockHandle(cx, &block);
ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this);
if (!clone)
return false;
pushOnScopeChain(*clone);
blockChain_ = blockHandle;
} else {
blockChain_ = &block;
}
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<ClonedBlockObject>().staticBlock() == *blockChain_);
popOffScopeChain();
}
blockChain_ = blockChain_->enclosingBlock();
JS_ASSERT(scopeChain_->is<ClonedBlockObject>());
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

View File

@ -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 {