Bug 684384 - Ion-compile break-to-labeled-scope. r=dvander

This commit is contained in:
Jan de Mooij 2013-02-15 14:52:29 +01:00
parent 0f92a37666
commit 653b0a892c
3 changed files with 151 additions and 33 deletions

View File

@ -233,6 +233,8 @@ IonBuilder::popCfgStack()
{
if (cfgStack_.back().isLoop())
loops_.popBack();
if (cfgStack_.back().state == CFGState::LABEL)
labels_.popBack();
cfgStack_.popBack();
}
@ -774,7 +776,7 @@ IonBuilder::inspectOpcode(JSOp op)
return true;
case JSOP_LABEL:
return true;
return jsop_label();
case JSOP_UNDEFINED:
return pushConstant(UndefinedValue());
@ -1199,6 +1201,9 @@ IonBuilder::processCfgEntry(CFGState &state)
case CFGState::AND_OR:
return processAndOrEnd(state);
case CFGState::LABEL:
return processLabelEnd(state);
default:
JS_NOT_REACHED("unknown cfgstate");
}
@ -1627,46 +1632,68 @@ IonBuilder::processAndOrEnd(CFGState &state)
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processLabelEnd(CFGState &state)
{
JS_ASSERT(state.state == CFGState::LABEL);
// If there are no breaks and no current, controlflow is terminated.
if (!state.label.breaks && !current)
return ControlStatus_Ended;
// If there are no breaks to this label, there's nothing to do.
if (!state.label.breaks)
return ControlStatus_Joined;
MBasicBlock *successor = createBreakCatchBlock(state.label.breaks, state.stopAt);
if (!successor)
return ControlStatus_Error;
if (current) {
current->end(MGoto::New(successor));
successor->addPredecessor(current);
}
pc = state.stopAt;
current = successor;
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processBreak(JSOp op, jssrcnote *sn)
{
JS_ASSERT(op == JSOP_GOTO);
// Find the target loop.
CFGState *found = NULL;
JS_ASSERT(SN_TYPE(sn) == SRC_BREAK ||
SN_TYPE(sn) == SRC_BREAK2LABEL);
// Find the break target.
jsbytecode *target = pc + GetJumpOffset(pc);
for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
CFGState &cfg = cfgStack_[loops_[i].cfgEntry];
if (cfg.loop.exitpc == target) {
found = &cfg;
break;
DebugOnly<bool> found = false;
if (SN_TYPE(sn) == SRC_BREAK2LABEL) {
for (size_t i = labels_.length() - 1; i < labels_.length(); i--) {
CFGState &cfg = cfgStack_[labels_[i].cfgEntry];
JS_ASSERT(cfg.state == CFGState::LABEL);
if (cfg.stopAt == target) {
cfg.label.breaks = new DeferredEdge(current, cfg.label.breaks);
found = true;
break;
}
}
} else {
for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
CFGState &cfg = cfgStack_[loops_[i].cfgEntry];
JS_ASSERT(cfg.isLoop());
if (cfg.loop.exitpc == target) {
cfg.loop.breaks = new DeferredEdge(current, cfg.loop.breaks);
found = true;
break;
}
}
}
if (!found) {
// Sometimes, we can't determine the structure of a labeled break. For
// example:
//
// 0: label: {
// 1: for (;;) {
// 2: break label;
// 3: }
// 4: stuff;
// 5: }
//
// In this case, the successor of the block is 4, but the target of the
// single-level break is actually 5. To recognize this case we'd need
// to know about the label structure at 0,5 ahead of time - and lacking
// those source notes for now, we just abort instead.
abort("could not find the target of a break");
return ControlStatus_Error;
}
// There must always be a valid target loop structure. If not, there's
// probably an off-by-something error in which pc we track.
CFGState &state = *found;
state.loop.breaks = new DeferredEdge(current, state.loop.breaks);
JS_ASSERT(found);
current = NULL;
pc += js_CodeSpec[op].length;
@ -2181,6 +2208,21 @@ IonBuilder::tableSwitch(JSOp op, jssrcnote *sn)
return ControlStatus_Jumped;
}
bool
IonBuilder::jsop_label()
{
JS_ASSERT(JSOp(*pc) == JSOP_LABEL);
jsbytecode *endpc = pc + GET_JUMP_OFFSET(pc);
JS_ASSERT(endpc > pc);
ControlFlowInfo label(cfgStack_.length(), endpc);
if (!labels_.append(label))
return false;
return cfgStack_.append(CFGState::Label(endpc));
}
bool
IonBuilder::jsop_condswitch()
{
@ -2278,6 +2320,16 @@ IonBuilder::CFGState::CondSwitch(jsbytecode *exitpc, jsbytecode *defaultTarget)
return state;
}
IonBuilder::CFGState
IonBuilder::CFGState::Label(jsbytecode *exitpc)
{
CFGState state;
state.state = LABEL;
state.stopAt = exitpc;
state.label.breaks = NULL;
return state;
}
IonBuilder::ControlStatus
IonBuilder::processCondSwitchCase(CFGState &state)
{

View File

@ -73,7 +73,8 @@ class IonBuilder : public MIRGenerator
TABLE_SWITCH, // switch() { x }
COND_SWITCH_CASE, // switch() { case X: ... }
COND_SWITCH_BODY, // switch() { case ...: X }
AND_OR // && x, || x
AND_OR, // && x, || x
LABEL // label: x
};
State state; // Current state of this control structure.
@ -140,6 +141,9 @@ class IonBuilder : public MIRGenerator
jsbytecode *exitpc;
DeferredEdge *breaks;
} condswitch;
struct {
DeferredEdge *breaks;
} label;
};
inline bool isLoop() const {
@ -162,6 +166,7 @@ class IonBuilder : public MIRGenerator
static CFGState AndOr(jsbytecode *join, MBasicBlock *joinStart);
static CFGState TableSwitch(jsbytecode *exitpc, MTableSwitch *ins);
static CFGState CondSwitch(jsbytecode *exitpc, jsbytecode *defaultTarget);
static CFGState Label(jsbytecode *exitpc);
};
static int CmpSuccessors(const void *a, const void *b);
@ -214,6 +219,7 @@ class IonBuilder : public MIRGenerator
ControlStatus processSwitchBreak(JSOp op, jssrcnote *sn);
ControlStatus processSwitchEnd(DeferredEdge *breaks, jsbytecode *exitpc);
ControlStatus processAndOrEnd(CFGState &state);
ControlStatus processLabelEnd(CFGState &state);
ControlStatus processReturn(JSOp op);
ControlStatus processThrow();
ControlStatus processContinue(JSOp op, jssrcnote *sn);
@ -329,6 +335,7 @@ class IonBuilder : public MIRGenerator
bool jsop_funapplyarguments(uint32_t argc);
bool jsop_call(uint32_t argc, bool constructing);
bool jsop_ifeq(JSOp op);
bool jsop_label();
bool jsop_condswitch();
bool jsop_andor(JSOp op);
bool jsop_dup2();
@ -509,6 +516,7 @@ class IonBuilder : public MIRGenerator
Vector<CFGState, 8, IonAllocPolicy> cfgStack_;
Vector<ControlFlowInfo, 4, IonAllocPolicy> loops_;
Vector<ControlFlowInfo, 0, IonAllocPolicy> switches_;
Vector<ControlFlowInfo, 2, IonAllocPolicy> labels_;
Vector<MInstruction *, 2, IonAllocPolicy> iterators_;
TypeOracle *oracle;

View File

@ -0,0 +1,58 @@
// Labeled break tests.
function f1() {
foo:
if ([1]) {
bar:
for (var i=0; i<100; i++) {
if (i > 60)
break foo;
}
assertEq(0, 1);
}
assertEq(i, 61);
return true;
}
assertEq(f1(), true);
// Label with no breaks.
function f2() {
foo:
if ([1]) {
for (var i=0; i<100; i++) {
}
}
assertEq(i, 100);
return true;
}
assertEq(f2(), true);
// No breaks and early return.
function f3() {
foo: {
if (true) {
for (var i=0; i<100; i++) {
}
}
return false;
}
assertEq(i, 100);
return true;
}
assertEq(f3(), false);
// Multiple breaks.
function f4() {
foo: {
if (true) {
for (var i=0; i<100; i++)
if (i > 70)
break foo;
if (i > 80)
break foo;
}
break foo;
}
assertEq(i, 71);
return true;
}
assertEq(f4(), true);