diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index 869505a30529..ffa21e6725d2 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -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 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) { diff --git a/js/src/ion/IonBuilder.h b/js/src/ion/IonBuilder.h index 7912a53d4b76..f124e35d39b3 100644 --- a/js/src/ion/IonBuilder.h +++ b/js/src/ion/IonBuilder.h @@ -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 cfgStack_; Vector loops_; Vector switches_; + Vector labels_; Vector iterators_; TypeOracle *oracle; diff --git a/js/src/jit-test/tests/ion/bug684384.js b/js/src/jit-test/tests/ion/bug684384.js new file mode 100644 index 000000000000..b3f3f4543747 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug684384.js @@ -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);