Bug 927782 - Part 8: Record block scope ranges more precisely. r=luke

This commit is contained in:
Andy Wingo 2013-12-06 18:27:55 +01:00
parent 3b8c9f30e6
commit e1f6dcf132
5 changed files with 142 additions and 51 deletions

View File

@ -523,19 +523,65 @@ PopIterator(ExclusiveContext *cx, BytecodeEmitter *bce)
return true;
}
namespace {
class NonLocalExitScope {
ExclusiveContext *cx;
BytecodeEmitter *bce;
const uint32_t savedScopeIndex;
const int savedDepth;
uint32_t openScopeIndex;
NonLocalExitScope(const NonLocalExitScope &) MOZ_DELETE;
public:
explicit NonLocalExitScope(ExclusiveContext *cx_, BytecodeEmitter *bce_)
: cx(cx_),
bce(bce_),
savedScopeIndex(bce->blockScopeList.length()),
savedDepth(bce->stackDepth),
openScopeIndex(UINT32_MAX) {
if (bce->blockChain) {
StmtInfoBCE *stmt = bce->topStmt;
while (1) {
JS_ASSERT(stmt);
if (stmt->isBlockScope) {
openScopeIndex = stmt->blockScopeIndex;
break;
}
stmt = stmt->down;
}
}
}
~NonLocalExitScope() {
for (uint32_t n = savedScopeIndex; n < bce->blockScopeList.length(); n++)
bce->blockScopeList.recordEnd(n, bce->offset());
bce->stackDepth = savedDepth;
}
bool popScopeForNonLocalExit(StaticBlockObject &blockObj, uint32_t blockScopeIndex) {
uint32_t scopeObjectIndex = bce->blockScopeList.findEnclosingScope(blockScopeIndex);
uint32_t parent = openScopeIndex;
if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
return false;
if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
return false;
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObj.slotCount());
openScopeIndex = bce->blockScopeList.length() - 1;
return true;
}
bool prepareForNonLocalJump(StmtInfoBCE *toStmt);
};
/*
* Emit additional bytecode(s) for non-local jumps.
*/
static bool
EmitNonLocalJumpFixup(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *toStmt)
bool
NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt)
{
/*
* The non-local jump fixup we emit will unbalance bce->stackDepth, because
* the fixup replicates balanced code such as JSOP_LEAVEWITH emitted at the
* end of a with statement, so we save bce->stackDepth here and restore it
* just before a successful return.
*/
int depth = bce->stackDepth;
int npops = 0;
#define FLUSH_POPS() if (npops && !FlushPops(cx, bce, &npops)) return false
@ -580,24 +626,26 @@ EmitNonLocalJumpFixup(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *t
FLUSH_POPS();
JS_ASSERT(stmt->blockObj);
StaticBlockObject &blockObj = *stmt->blockObj;
if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
if (!popScopeForNonLocalExit(blockObj, stmt->blockScopeIndex))
return false;
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObj.slotCount());
}
}
FLUSH_POPS();
bce->stackDepth = depth;
return true;
#undef FLUSH_POPS
}
} // anonymous namespace
static ptrdiff_t
EmitGoto(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *toStmt, ptrdiff_t *lastp,
SrcNoteType noteType = SRC_NULL)
{
if (!EmitNonLocalJumpFixup(cx, bce, toStmt))
NonLocalExitScope nle(cx, bce);
if (!nle.prepareForNonLocalJump(toStmt))
return -1;
if (noteType != SRC_NULL) {
@ -729,7 +777,7 @@ static bool
EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
unsigned extraSlots)
{
uint32_t parent = UINT32_MAX;
uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
if (bce->blockChain) {
StmtInfoBCE *stmt = bce->topScopeStmt;
for (; stmt->blockObj != bce->blockChain; stmt = stmt->down) {}
@ -739,9 +787,6 @@ EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, O
StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
uint32_t scopeObjectIndex = bce->objectList.add(objbox);
stmt->blockScopeIndex = bce->blockScopeList.length();
if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
return false;
int depth = bce->stackDepth - (blockObj.slotCount() + extraSlots);
JS_ASSERT(depth >= 0);
@ -761,6 +806,10 @@ EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, O
if (!EmitInternedObjectOp(cx, scopeObjectIndex, op, bce))
return false;
stmt->blockScopeIndex = bce->blockScopeList.length();
if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
return false;
PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset());
blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce));
FinishPushBlockScope(bce, stmt, blockObj);
@ -783,9 +832,6 @@ PopStatementBCE(ExclusiveContext *cx, BytecodeEmitter *bce)
return false;
}
if (stmt->isBlockScope)
bce->blockScopeList.recordEnd(stmt->blockScopeIndex, bce->offset());
FinishPopStatement(bce);
return true;
}
@ -793,10 +839,11 @@ PopStatementBCE(ExclusiveContext *cx, BytecodeEmitter *bce)
static bool
LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op)
{
#ifdef DEBUG
StmtInfoBCE *stmt = bce->topStmt;
uint32_t blockScopeIndex = stmt->blockScopeIndex;
JS_ASSERT(stmt->isBlockScope);
uint32_t blockScopeIndex = stmt->blockScopeIndex;
#ifdef DEBUG
JS_ASSERT(bce->blockScopeList.list[blockScopeIndex].length == 0);
uint32_t blockObjIndex = bce->blockScopeList.list[blockScopeIndex].index;
ObjectBox *blockObjBox = bce->objectList.find(blockObjIndex);
@ -813,6 +860,8 @@ LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op)
if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
return false;
bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset());
JS_ASSERT(op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR);
EMIT_UINT16_IMM_OP(op, slotCount);
@ -3845,35 +3894,26 @@ EmitCatch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
if (guardCheck < 0)
return false;
#ifdef DEBUG
uint32_t blockScopeIndex = bce->topStmt->blockScopeIndex;
uint32_t blockObjIndex = bce->blockScopeList.list[blockScopeIndex].index;
ObjectBox *blockObjBox = bce->objectList.find(blockObjIndex);
StaticBlockObject *blockObj = &blockObjBox->object->as<StaticBlockObject>();
JS_ASSERT(blockObj == bce->blockChain);
#endif
{
NonLocalExitScope nle(cx, bce);
// Save stack depth before popping the block scope.
int savedDepth = bce->stackDepth;
// Move exception back to cx->exception to prepare for
// the next catch.
if (Emit1(cx, bce, JSOP_THROWING) < 0)
return false;
// Move exception back to cx->exception to prepare for
// the next catch.
if (Emit1(cx, bce, JSOP_THROWING) < 0)
return false;
// Leave the scope for this catch block.
if (!nle.prepareForNonLocalJump(stmt))
return false;
// Leave the scope for this catch block
if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
return false;
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, bce->blockChain->slotCount());
// Jump to the next handler. The jump target is backpatched by EmitTry.
ptrdiff_t guardJump = EmitJump(cx, bce, JSOP_GOTO, 0);
if (guardJump < 0)
return false;
stmt->guardJump() = guardJump;
}
// Jump to the next handler. The jump target is backpatched by EmitTry.
ptrdiff_t guardJump = EmitJump(cx, bce, JSOP_GOTO, 0);
if (guardJump < 0)
return false;
stmt->guardJump() = guardJump;
// Back to normal control flow. restore stack depth after nonlocal exit.
bce->stackDepth = savedDepth;
// Back to normal control flow.
SetJumpOffsetAt(bce, guardCheck);
// Pop duplicated exception object as we no longer need it.
@ -5066,8 +5106,12 @@ EmitReturn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
if (Emit1(cx, bce, JSOP_RETURN) < 0)
return false;
if (!EmitNonLocalJumpFixup(cx, bce, nullptr))
NonLocalExitScope nle(cx, bce);
if (!nle.prepareForNonLocalJump(nullptr))
return false;
if (top + JSOP_RETURN_LENGTH != bce->offset()) {
bce->code()[top] = JSOP_SETRVAL;
if (Emit1(cx, bce, JSOP_RETRVAL) < 0)
@ -6884,6 +6928,30 @@ CGBlockScopeList::append(uint32_t scopeObject, uint32_t offset, uint32_t parent)
return list.append(note);
}
uint32_t
CGBlockScopeList::findEnclosingScope(uint32_t index)
{
JS_ASSERT(index < length());
JS_ASSERT(list[index].index != BlockScopeNote::NoBlockScopeIndex);
DebugOnly<uint32_t> pos = list[index].start;
while (index--) {
JS_ASSERT(list[index].start <= pos);
if (list[index].length == 0) {
// We are looking for the nearest enclosing live scope. If the
// scope contains POS, it should still be open, so its length should
// be zero.
return list[index].index;
} else {
// Conversely, if the length is not zero, it should not contain
// POS.
JS_ASSERT(list[index].start + list[index].length <= pos);
}
}
return BlockScopeNote::NoBlockScopeIndex;
}
void
CGBlockScopeList::recordEnd(uint32_t index, uint32_t offset)
{

View File

@ -63,6 +63,7 @@ struct CGBlockScopeList {
CGBlockScopeList(ExclusiveContext *cx) : list(cx) {}
bool append(uint32_t scopeObject, uint32_t offset, uint32_t parent);
uint32_t findEnclosingScope(uint32_t index);
void recordEnd(uint32_t index, uint32_t offset);
size_t length() const { return list.length(); }
void finish(BlockScopeArray *array);

View File

@ -2930,7 +2930,10 @@ JSScript::getBlockScope(jsbytecode *pc)
if (offset < checkNote->start + checkNote->length) {
// We found a matching block chain but there may be inner ones
// at a higher block chain index than mid. Continue the binary search.
blockChain = &getObject(checkNote->index)->as<StaticBlockObject>();
if (checkNote->index == BlockScopeNote::NoBlockScopeIndex)
blockChain = nullptr;
else
blockChain = &getObject(checkNote->index)->as<StaticBlockObject>();
break;
}
if (checkNote->parent == UINT32_MAX)

View File

@ -82,9 +82,27 @@ struct JSTryNote {
namespace js {
// A block scope has a range in bytecode: it is entered at some offset, and left
// at some later offset. Scopes can be nested. Given an offset, the
// BlockScopeNote containing that offset whose with the highest start value
// indicates the block scope. The block scope list is sorted by increasing
// start value.
//
// It is possible to leave a scope nonlocally, for example via a "break"
// statement, so there may be short bytecode ranges in a block scope in which we
// are popping the block chain in preparation for a goto. These exits are also
// nested with respect to outer scopes. The scopes in these exits are indicated
// by the "index" field, just like any other block. If a nonlocal exit pops the
// last block scope, the index will be NoBlockScopeIndex.
//
struct BlockScopeNote {
uint32_t index; // Index of StaticScopeObject in the object array.
uint32_t start; // Bytecode offset at which this scope starts.
static const uint32_t NoBlockScopeIndex = UINT32_MAX;
uint32_t index; // Index of StaticScopeObject in the object
// array, or NoBlockScopeIndex if there is no
// block scope in this range.
uint32_t start; // Bytecode offset at which this scope starts,
// from script->main().
uint32_t length; // Bytecode length of scope.
uint32_t parent; // Index of parent block scope in notes, or UINT32_MAX.
};

View File

@ -50,6 +50,7 @@ js::ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc)
StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc));
ScopeCoordinate sc(pc);
while (true) {
JS_ASSERT(!ssi.done());
if (ssi.hasDynamicScopeObject()) {
if (!sc.hops)
break;