Bug 780973 - Try to enter outer loops rather than inner loops via OSR. r=dvander,bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 08 Aug 2012 15:02:24 +0200
changeset 106687 5e461fb2250aab1fb9ab3188b363a28ec1bd8c5e
parent 106686 c55e7945d3abfb9783e26f4e24f4692edc254a1a
child 106688 21b4797e4cb3e03e541bbe3b0bb0e79312dd9d98
push id14706
push usereakhgari@mozilla.com
push dateTue, 11 Sep 2012 20:39:52 +0000
treeherdermozilla-inbound@d50bf1edaabe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander, bhackett
bugs780973
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 780973 - Try to enter outer loops rather than inner loops via OSR. r=dvander,bhackett
js/src/ion/Ion.cpp
js/src/ion/Ion.h
js/src/ion/IonBuilder.cpp
js/src/ion/MIR.h
js/src/ion/arm/CodeGenerator-arm.cpp
js/src/ion/arm/LIR-arm.h
js/src/ion/arm/Lowering-arm.cpp
js/src/ion/shared/LIR-x86-shared.h
js/src/ion/shared/Lowering-x86-shared.cpp
js/src/ion/x64/CodeGenerator-x64.cpp
js/src/ion/x86/CodeGenerator-x86.cpp
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/methodjit/Compiler.cpp
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -1420,10 +1420,29 @@ ion::MarkFromIon(JSCompartment *comp, Va
 void
 ion::ForbidCompilation(JSScript *script)
 {
     IonSpew(IonSpew_Abort, "Disabling Ion compilation of script %s:%d",
             script->filename, script->lineno);
     script->ion = ION_DISABLED_SCRIPT;
 }
 
+uint32_t
+ion::UsesBeforeIonRecompile(JSScript *script, jsbytecode *pc)
+{
+    JS_ASSERT(pc == script->code || JSOp(*pc) == JSOP_LOOPENTRY);
+
+    uint32_t minUses = js_IonOptions.usesBeforeCompile;
+    if (JSOp(*pc) != JSOP_LOOPENTRY || !script->hasAnalysis() || js_IonOptions.eagerCompilation)
+        return minUses;
+
+    analyze::LoopAnalysis *loop = script->analysis()->getLoop(pc);
+    if (!loop)
+        return minUses;
+
+    // It's more efficient to enter outer loops, rather than inner loops, via OSR.
+    // To accomplish this, we use a slightly higher threshold for inner loops.
+    // Note that we use +1 to prefer non-OSR over OSR.
+    return minUses + (loop->depth + 1) * 100;
+}
+
 int js::ion::LabelBase::id_count = 0;
 
--- a/js/src/ion/Ion.h
+++ b/js/src/ion/Ion.h
@@ -227,14 +227,15 @@ void MarkFromIon(JSCompartment *comp, Va
 void ToggleBarriers(JSCompartment *comp, bool needs);
 
 static inline bool IsEnabled(JSContext *cx)
 {
     return cx->hasRunOption(JSOPTION_ION) && cx->typeInferenceEnabled();
 }
 
 void ForbidCompilation(JSScript *script);
+uint32_t UsesBeforeIonRecompile(JSScript *script, jsbytecode *pc);
 
 } // namespace ion
 } // namespace js
 
 #endif // jsion_ion_h__
 
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -752,16 +752,17 @@ bool
 IonBuilder::inspectOpcode(JSOp op)
 {
     // Don't compile fat opcodes, run the decomposed version instead.
     if (js_CodeSpec[op].format & JOF_DECOMPOSE)
         return true;
 
     switch (op) {
       case JSOP_LOOPENTRY:
+        insertRecompileCheck();
         return true;
 
       case JSOP_NOP:
         return true;
 
       case JSOP_LABEL:
         return true;
 
@@ -2444,17 +2445,16 @@ IonBuilder::jsop_dup2()
     current->pushSlot(rhsSlot);
     return true;
 }
 
 bool
 IonBuilder::jsop_loophead(jsbytecode *pc)
 {
     assertValidLoopHeadOp(pc);
-    insertRecompileCheck();
 
     current->add(MInterruptCheck::New());
 
     return true;
 }
 
 bool
 IonBuilder::jsop_ifeq(JSOp op)
@@ -4232,17 +4232,18 @@ IonBuilder::insertRecompileCheck()
     if (script->getUseCount() >= js_IonOptions.usesBeforeInlining)
         return;
 
     // Don't recompile if the oracle cannot provide inlining information
     // or if the script has no calls.
     if (!oracle->canInlineCalls())
         return;
 
-    MRecompileCheck *check = MRecompileCheck::New();
+    uint32_t minUses = UsesBeforeIonRecompile(script, pc);
+    MRecompileCheck *check = MRecompileCheck::New(minUses);
     current->add(check);
 }
 
 static inline bool
 TestSingletonProperty(JSContext *cx, JSObject *obj, HandleId id, bool *isKnownConstant)
 {
     // We would like to completely no-op property/global accesses which can
     // produce only a particular JSObject. When indicating the access result is
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -2803,27 +2803,33 @@ class MCheckOverRecursed : public MNulla
   public:
     INSTRUCTION_HEADER(CheckOverRecursed);
 };
 
 // Check the script's use count and trigger recompilation to inline
 // calls when the script becomes hot.
 class MRecompileCheck : public MNullaryInstruction
 {
-    MRecompileCheck() {
+    uint32_t minUses_;
+
+    MRecompileCheck(uint32 minUses)
+      : minUses_(minUses)
+    {
         setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(RecompileCheck);
 
-    static MRecompileCheck *New() {
-        return new MRecompileCheck();
-    }
-
+    uint32_t minUses() const {
+        return minUses_;
+    }
+    static MRecompileCheck *New(uint32_t minUses) {
+        return new MRecompileCheck(minUses);
+    }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 // Check whether we need to fire the interrupt handler.
 class MInterruptCheck : public MNullaryInstruction
 {
--- a/js/src/ion/arm/CodeGenerator-arm.cpp
+++ b/js/src/ion/arm/CodeGenerator-arm.cpp
@@ -1389,17 +1389,17 @@ CodeGeneratorARM::visitRecompileCheck(LR
     // Bump the script's use count. Note that it's safe to bake in this pointer
     // since scripts are never nursery allocated and jitcode will be purged before
     // doing a compacting GC.
     masm.load32(AbsoluteAddress(addr), tmp);
     masm.ma_add(Imm32(1), tmp);
     masm.store32(tmp, AbsoluteAddress(addr));
 
     // Bailout if the script is hot.
-    masm.ma_cmp(tmp, Imm32(js_IonOptions.usesBeforeInlining));
+    masm.ma_cmp(tmp, Imm32(lir->mir()->minUses()));
     if (!bailoutIf(Assembler::AboveOrEqual, lir->snapshot()))
         return false;
     return true;
 }
 
 bool
 CodeGeneratorARM::visitInterruptCheck(LInterruptCheck *lir)
 {
--- a/js/src/ion/arm/LIR-arm.h
+++ b/js/src/ion/arm/LIR-arm.h
@@ -226,16 +226,19 @@ class LRecompileCheck : public LInstruct
     LIR_HEADER(RecompileCheck);
 
     LRecompileCheck(const LDefinition &temp) {
         setTemp(0, temp);
     }
     const LAllocation *tempInt() {
         return getTemp(0)->output();
     }
+    const MRecompileCheck *mir() const {
+        return mir_->toRecompileCheck();
+    }
 };
 
 class LInterruptCheck : public LInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(InterruptCheck);
 };
 
--- a/js/src/ion/arm/Lowering-arm.cpp
+++ b/js/src/ion/arm/Lowering-arm.cpp
@@ -324,17 +324,17 @@ LIRGeneratorARM::visitGuardShape(MGuardS
     LGuardShape *guard = new LGuardShape(useRegister(ins->obj()), tempObj);
     return assignSnapshot(guard, Bailout_Invalidate) && add(guard, ins);
 }
 
 bool
 LIRGeneratorARM::visitRecompileCheck(MRecompileCheck *ins)
 {
     LRecompileCheck *lir = new LRecompileCheck(temp(LDefinition::GENERAL));
-    return assignSnapshot(lir, Bailout_RecompileCheck) && add(lir);
+    return assignSnapshot(lir, Bailout_RecompileCheck) && add(lir, ins);
 }
 
 bool
 LIRGeneratorARM::visitStoreTypedArrayElement(MStoreTypedArrayElement *ins)
 {
     JS_ASSERT(ins->elements()->type() == MIRType_Elements);
     JS_ASSERT(ins->index()->type() == MIRType_Int32);
 
--- a/js/src/ion/shared/LIR-x86-shared.h
+++ b/js/src/ion/shared/LIR-x86-shared.h
@@ -130,16 +130,20 @@ class LGuardShape : public LInstructionH
         return mir_->toGuardShape();
     }
 };
 
 class LRecompileCheck : public LInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(RecompileCheck);
+
+    const MRecompileCheck *mir() const {
+        return mir_->toRecompileCheck();
+    }
 };
 
 class LInterruptCheck : public LInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(InterruptCheck);
 };
 
--- a/js/src/ion/shared/Lowering-x86-shared.cpp
+++ b/js/src/ion/shared/Lowering-x86-shared.cpp
@@ -41,17 +41,17 @@ LIRGeneratorX86Shared::visitTableSwitch(
     }
     return add(new LTableSwitch(index, tempInt, temp(LDefinition::GENERAL), tableswitch));
 }
 
 bool
 LIRGeneratorX86Shared::visitRecompileCheck(MRecompileCheck *ins)
 {
     LRecompileCheck *lir = new LRecompileCheck();
-    return assignSnapshot(lir, Bailout_RecompileCheck) && add(lir);
+    return assignSnapshot(lir, Bailout_RecompileCheck) && add(lir, ins);
 }
 
 bool
 LIRGeneratorX86Shared::visitInterruptCheck(MInterruptCheck *ins)
 {
     LInterruptCheck *lir = new LInterruptCheck();
     if (!add(lir))
         return false;
--- a/js/src/ion/x64/CodeGenerator-x64.cpp
+++ b/js/src/ion/x64/CodeGenerator-x64.cpp
@@ -283,17 +283,17 @@ CodeGeneratorX64::visitRecompileCheck(LR
     // Bump the script's use count and bailout if the script is hot. Note that
     // it's safe to bake in this pointer since scripts are never nursery
     // allocated and jitcode will be purged before doing a compacting GC.
     const uint32_t *useCount = gen->info().script()->addressOfUseCount();
     masm.movq(ImmWord(useCount), ScratchReg);
 
     Operand addr(ScratchReg, 0);
     masm.addl(Imm32(1), addr);
-    masm.cmpl(addr, Imm32(js_IonOptions.usesBeforeInlining));
+    masm.cmpl(addr, Imm32(lir->mir()->minUses()));
     if (!bailoutIf(Assembler::AboveOrEqual, lir->snapshot()))
         return false;
     return true;
 }
 
 bool
 CodeGeneratorX64::visitInterruptCheck(LInterruptCheck *lir)
 {
--- a/js/src/ion/x86/CodeGenerator-x86.cpp
+++ b/js/src/ion/x86/CodeGenerator-x86.cpp
@@ -277,17 +277,17 @@ bool
 CodeGeneratorX86::visitRecompileCheck(LRecompileCheck *lir)
 {
     // Bump the script's use count and bailout if the script is hot. Note that
     // it's safe to bake in this pointer since scripts are never nursery
     // allocated and jitcode will be purged before doing a compacting GC.
     // Without this assumption we'd need a temp register here.
     Operand addr(gen->info().script()->addressOfUseCount());
     masm.addl(Imm32(1), addr);
-    masm.cmpl(addr, Imm32(js_IonOptions.usesBeforeInlining));
+    masm.cmpl(addr, Imm32(lir->mir()->minUses()));
     if (!bailoutIf(Assembler::AboveOrEqual, lir->snapshot()))
         return false;
     return true;
 }
 
 bool
 CodeGeneratorX86::visitInterruptCheck(LInterruptCheck *lir)
 {
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -834,16 +834,20 @@ ScriptAnalysis::analyzeLifetimes(JSConte
           case JSOP_CALL:
           case JSOP_EVAL:
           case JSOP_FUNAPPLY:
           case JSOP_FUNCALL:
             if (loop)
                 loop->hasCallsLoops = true;
             break;
 
+          case JSOP_LOOPENTRY:
+            getCode(offset).loop = loop;
+            break;
+
           default:;
         }
 
         if (IsJumpOpcode(op)) {
             /*
              * Forward jumps need to pull in all variables which are live at
              * their target offset --- the variables live before the jump are
              * the union of those live at the fallthrough and at the target.
@@ -876,18 +880,20 @@ ScriptAnalysis::analyzeLifetimes(JSConte
                 LoopAnalysis *nloop = tla.new_<LoopAnalysis>();
                 if (!nloop) {
                     cx->free_(saved);
                     setOOM(cx);
                     return;
                 }
                 PodZero(nloop);
 
-                if (loop)
+                if (loop) {
                     loop->hasCallsLoops = true;
+                    nloop->depth = loop->depth + 1;
+                }
 
                 nloop->parent = loop;
                 loop = nloop;
 
                 getCode(targetOffset).loop = loop;
                 loop->head = targetOffset;
                 loop->backedge = offset;
                 loop->lastBlock = loop->head;
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -120,17 +120,17 @@ class Bytecode
     uint32_t stackDepth;
 
   private:
 
     union {
         /* If this is a JOF_TYPESET opcode, index into the observed types for the op. */
         types::TypeSet *observedTypes;
 
-        /* If this is a loop head (TRACE or NOTRACE), information about the loop. */
+        /* If this is a JSOP_LOOPHEAD or JSOP_LOOPENTRY, information about the loop. */
         LoopAnalysis *loop;
     };
 
     /* --------- Lifetime analysis --------- */
 
     /* Any allocation computed downstream for this bytecode. */
     mjit::RegisterAllocation *allocation;
 
@@ -503,16 +503,19 @@ class LoopAnalysis
     /*
      * Start of the last basic block in the loop, excluding the initial jump to
      * entry. All code between lastBlock and the backedge runs in every
      * iteration, and if entry >= lastBlock all code between entry and the
      * backedge runs when the loop is initially entered.
      */
     uint32_t lastBlock;
 
+    /* Loop nesting depth, 0 for the outermost loop. */
+    uint16_t depth;
+
     /*
      * This loop contains safe points in its body which the interpreter might
      * join at directly.
      */
     bool hasSafePoints;
 
     /* This loop has calls or inner loops. */
     bool hasCallsLoops;
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -3220,25 +3220,26 @@ mjit::Compiler::generateMethod()
             frame.push(Value(Int32Value(GET_INT32(PC))));
           END_CASE(JSOP_INT32)
 
           BEGIN_CASE(JSOP_HOLE)
             frame.push(MagicValue(JS_ARRAY_HOLE));
           END_CASE(JSOP_HOLE)
 
           BEGIN_CASE(JSOP_LOOPHEAD)
-          {
-            if (analysis->jumpTarget(PC)) {
+            if (analysis->jumpTarget(PC))
                 interruptCheckHelper();
-                recompileCheckHelper();
-            }
-          }
           END_CASE(JSOP_LOOPHEAD)
 
           BEGIN_CASE(JSOP_LOOPENTRY)
+            // Unlike JM, IonMonkey OSR enters loops at the LOOPENTRY op.
+            // Insert the recompile check here so that we can immediately
+            // enter Ion.
+            if (loop)
+                recompileCheckHelper();
           END_CASE(JSOP_LOOPENTRY)
 
           BEGIN_CASE(JSOP_DEBUGGER)
           {
             prepareStubCall(Uses(0));
             masm.move(ImmPtr(PC), Registers::ArgReg1);
             INLINE_STUBCALL(stubs::DebuggerStatement, REJOIN_FALLTHROUGH);
           }
@@ -3922,33 +3923,42 @@ MaybeIonCompileable(JSContext *cx, JSScr
 }
 
 void
 mjit::Compiler::recompileCheckHelper()
 {
     if (inlining() || debugMode() || !globalObj || !cx->typeInferenceEnabled())
         return;
 
+    bool maybeIonCompileable = MaybeIonCompileable(cx, outerScript);
+
     // Insert a recompile check if either:
     // 1) IonMonkey is enabled, to optimize the function when it becomes hot.
     // 2) The script contains function calls JM can inline.
-    if (!MaybeIonCompileable(cx, outerScript) && !analysis->hasFunctionCalls())
+    if (!maybeIonCompileable && !analysis->hasFunctionCalls())
         return;
 
+    uint32_t minUses = USES_BEFORE_INLINING;
+
+#ifdef JS_ION
+    if (maybeIonCompileable)
+        minUses = ion::UsesBeforeIonRecompile(outerScript, PC);
+#endif
+
     uint32_t *addr = script->addressOfUseCount();
     masm.add32(Imm32(1), AbsoluteAddress(addr));
 #if defined(JS_CPU_X86) || defined(JS_CPU_ARM)
     Jump jump = masm.branch32(Assembler::GreaterThanOrEqual, AbsoluteAddress(addr),
-                              Imm32(USES_BEFORE_INLINING));
+                              Imm32(minUses));
 #else
     /* Handle processors that can't load from absolute addresses. */
     RegisterID reg = frame.allocReg();
     masm.move(ImmPtr(addr), reg);
     Jump jump = masm.branch32(Assembler::GreaterThanOrEqual, Address(reg, 0),
-                              Imm32(USES_BEFORE_INLINING));
+                              Imm32(minUses));
     frame.freeReg(reg);
 #endif
     stubcc.linkExit(jump, Uses(0));
     stubcc.leave();
 
     OOL_STUBCALL(stubs::RecompileForInline, REJOIN_RESUME);
     stubcc.rejoin(Changes(0));
 }