Bug 865406 - Disable IonMonkey LICM if a script bails out frequently. r=h4writer
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 26 Apr 2013 12:10:28 +0200
changeset 130044 17b205981e76e2c6993d42b343cbc4cc76ea6ff3
parent 130043 34413bab9ad5ea4515976a0b491810d5ea46c60d
child 130045 8d69a81abff9f5d3eec4077251a2c9242c4c1e6f
push id1552
push userttaubert@mozilla.com
push dateSat, 27 Apr 2013 15:33:29 +0000
treeherderfx-team@40dafc376794 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs865406
milestone23.0a1
Bug 865406 - Disable IonMonkey LICM if a script bails out frequently. r=h4writer
js/src/ion/Bailouts.cpp
js/src/ion/Bailouts.h
js/src/ion/BaselineBailouts.cpp
js/src/ion/Ion.cpp
js/src/ion/Ion.h
js/src/ion/IonCode.h
js/src/jsscript.h
--- a/js/src/ion/Bailouts.cpp
+++ b/js/src/ion/Bailouts.cpp
@@ -239,17 +239,17 @@ ConvertFrames(JSContext *cx, IonActivati
     if (it.script()->maybeIonScript() == it.ionScript()) {
         IonSpew(IonSpew_Bailouts, " Current script use count is %u",
                 it.script()->getUseCount());
     }
 #endif
 
     // Set a flag to avoid bailing out on every iteration or function call. Ion can
     // compile and run the script again after an invalidation.
-    it.ionScript()->setBailoutExpected();
+    it.ionScript()->incNumBailouts();
     it.script()->updateBaselineOrIonRaw();
 
     // We use OffTheBooks instead of cx because at this time we cannot iterate
     // on the stack safely and the reported error attempts to walk the IonMonkey
     // frames. We cannot iterate on the stack because we have no exit frame to
     // start Ion frames iteratons.
     BailoutClosure *br = js_new<BailoutClosure>();
     if (!br)
@@ -681,8 +681,27 @@ ion::ThunkToInterpreter(Value *vp)
 
     // The BailoutFrameGuard's destructor will ensure that the frame is
     // removed.
     js_delete(br);
 
     return status;
 }
 
+bool
+ion::CheckFrequentBailouts(JSContext *cx, JSScript *script)
+{
+    // Invalidate if this script keeps bailing out without invalidation. Next time
+    // we compile this script LICM will be disabled.
+
+    if (script->hasIonScript() &&
+        script->ionScript()->numBailouts() >= js_IonOptions.frequentBailoutThreshold)
+    {
+        script->hadFrequentBailouts = true;
+
+        IonSpew(IonSpew_Invalidate, "Invalidating due to too many bailouts");
+
+        if (!Invalidate(cx, script))
+            return false;
+    }
+
+    return true;
+}
--- a/js/src/ion/Bailouts.h
+++ b/js/src/ion/Bailouts.h
@@ -223,13 +223,15 @@ uint32_t ReflowTypeInfo(uint32_t bailout
 uint32_t BoundsCheckFailure();
 
 uint32_t ShapeGuardFailure();
 
 uint32_t CachedShapeGuardFailure();
 
 uint32_t FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo);
 
+bool CheckFrequentBailouts(JSContext *cx, JSScript *script);
+
 } // namespace ion
 } // namespace js
 
 #endif // jsion_bailouts_h__
 
--- a/js/src/ion/BaselineBailouts.cpp
+++ b/js/src/ion/BaselineBailouts.cpp
@@ -1039,17 +1039,17 @@ ion::BailoutIonToBaseline(JSContext *cx,
     //      +---------------+
 
     IonSpew(IonSpew_BaselineBailouts, "Bailing to baseline %s:%u (IonScript=%p) (FrameType=%d)",
             iter.script()->filename(), iter.script()->lineno, (void *) iter.ionScript(),
             (int) prevFrameType);
     IonSpew(IonSpew_BaselineBailouts, "  Reading from snapshot offset %u size %u",
             iter.snapshotOffset(), iter.ionScript()->snapshotsSize());
 
-    iter.ionScript()->setBailoutExpected();
+    iter.ionScript()->incNumBailouts();
     iter.script()->updateBaselineOrIonRaw();
 
     // Allocate buffer to hold stack replacement data.
     BaselineStackBuilder builder(iter, 1024);
     if (!builder.init())
         return BAILOUT_RETURN_FATAL_ERROR;
     IonSpew(IonSpew_BaselineBailouts, "  Incoming frame ptr = %p", builder.startFrame());
 
@@ -1283,10 +1283,13 @@ ion::FinishBailoutToBaseline(BaselineBai
       case Bailout_CachedShapeGuard:
         if (!HandleCachedShapeGuardFailure(cx, outerScript, innerScript))
             return false;
         break;
       default:
         JS_NOT_REACHED("Unknown bailout kind!");
     }
 
+    if (!CheckFrequentBailouts(cx, outerScript))
+        return false;
+
     return true;
 }
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -530,17 +530,17 @@ IonCode::writeBarrierPost(IonCode *code,
 
 IonScript::IonScript()
   : method_(NULL),
     deoptTable_(NULL),
     osrPc_(NULL),
     osrEntryOffset_(0),
     invalidateEpilogueOffset_(0),
     invalidateEpilogueDataOffset_(0),
-    bailoutExpected_(0),
+    numBailouts_(0),
     hasInvalidatedCallTarget_(false),
     runtimeData_(0),
     runtimeSize_(0),
     cacheIndex_(0),
     cacheEntries_(0),
     safepointIndexOffset_(0),
     safepointIndexEntries_(0),
     safepointsStart_(0),
@@ -1004,24 +1004,30 @@ OptimizeMIR(MIRGenerator *mir)
         IonSpewPass("UCE");
         AssertExtendedGraphCoherency(graph);
     }
 
     if (mir->shouldCancel("UCE"))
         return false;
 
     if (js_IonOptions.licm) {
-        LICM licm(mir, graph);
-        if (!licm.analyze())
-            return false;
-        IonSpewPass("LICM");
-        AssertExtendedGraphCoherency(graph);
-
-        if (mir->shouldCancel("LICM"))
-            return false;
+        // LICM can hoist instructions from conditional branches and trigger
+        // repeated bailouts. Disable it if this script is known to bailout
+        // frequently.
+        JSScript *script = mir->info().script();
+        if (!script || !script->hadFrequentBailouts) {
+            LICM licm(mir, graph);
+            if (!licm.analyze())
+                return false;
+            IonSpewPass("LICM");
+            AssertExtendedGraphCoherency(graph);
+
+            if (mir->shouldCancel("LICM"))
+                return false;
+        }
     }
 
     if (js_IonOptions.rangeAnalysis) {
         RangeAnalysis r(graph);
         if (!r.addBetaNobes())
             return false;
         IonSpewPass("Beta");
         AssertExtendedGraphCoherency(graph);
--- a/js/src/ion/Ion.h
+++ b/js/src/ion/Ion.h
@@ -115,16 +115,22 @@ struct IonOptions
     double usesBeforeInliningFactor;
 
     // How many times we will try to enter a script via OSR before
     // invalidating the script.
     //
     // Default: 6,000
     uint32_t osrPcMismatchesBeforeRecompile;
 
+    // Number of bailouts without invalidation before we set
+    // JSScript::hadFrequentBailouts and invalidate.
+    //
+    // Default: 10
+    uint32_t frequentBailoutThreshold;
+
     // How many actual arguments are accepted on the C stack.
     //
     // Default: 4,096
     uint32_t maxStackArgs;
 
     // The maximum inlining depth.
     //
     // Default: 3
@@ -196,16 +202,17 @@ struct IonOptions
         uce(true),
         eaa(true),
         parallelCompilation(false),
         baselineUsesBeforeCompile(10),
         usesBeforeCompile(1000),
         usesBeforeCompileNoJaeger(40),
         usesBeforeInliningFactor(.125),
         osrPcMismatchesBeforeRecompile(6000),
+        frequentBailoutThreshold(10),
         maxStackArgs(4096),
         maxInlineDepth(3),
         smallFunctionMaxInlineDepth(10),
         smallFunctionMaxBytecodeLength(100),
         polyInlineMax(4),
         inlineMaxTotalBytecodeLength(1000),
         inlineUseCountRatio(128),
         eagerCompilation(false),
--- a/js/src/ion/IonCode.h
+++ b/js/src/ion/IonCode.h
@@ -165,18 +165,18 @@ struct IonScript
     uint32_t invalidateEpilogueOffset_;
 
     // The offset immediately after the IonScript immediate.
     // NOTE: technically a constant delta from
     // |invalidateEpilogueOffset_|, so we could hard-code this
     // per-platform if we want.
     uint32_t invalidateEpilogueDataOffset_;
 
-    // Flag set when we bailout, to avoid frequent bailouts.
-    uint32_t bailoutExpected_;
+    // Number of times this script bailed out without invalidation.
+    uint32_t numBailouts_;
 
     // Flag set when we bailed out in parallel execution and should ensure its
     // call targets are compiled.
     bool hasInvalidatedCallTarget_;
 
     // Any kind of data needed by the runtime, these can be either cache
     // information or profiling info.
     uint32_t runtimeData_;
@@ -297,19 +297,16 @@ struct IonScript
     static void Destroy(FreeOp *fop, IonScript *script);
 
     static inline size_t offsetOfMethod() {
         return offsetof(IonScript, method_);
     }
     static inline size_t offsetOfOsrEntryOffset() {
         return offsetof(IonScript, osrEntryOffset_);
     }
-    static size_t offsetOfBailoutExpected() {
-        return offsetof(IonScript, bailoutExpected_);
-    }
 
   public:
     IonCode *method() const {
         return method_;
     }
     void setMethod(IonCode *code) {
         JS_ASSERT(!invalidated());
         method_ = code;
@@ -349,21 +346,24 @@ struct IonScript
     void setInvalidationEpilogueDataOffset(uint32_t offset) {
         JS_ASSERT(!invalidateEpilogueDataOffset_);
         invalidateEpilogueDataOffset_ = offset;
     }
     uint32_t invalidateEpilogueDataOffset() const {
         JS_ASSERT(invalidateEpilogueDataOffset_);
         return invalidateEpilogueDataOffset_;
     }
-    void setBailoutExpected() {
-        bailoutExpected_ = 1;
+    void incNumBailouts() {
+        numBailouts_++;
+    }
+    uint32_t numBailouts() const {
+        return numBailouts_;
     }
     bool bailoutExpected() const {
-        return bailoutExpected_ ? true : false;
+        return numBailouts_ > 0;
     }
     void setHasInvalidatedCallTarget() {
         hasInvalidatedCallTarget_ = true;
     }
     void clearHasInvalidatedCallTarget() {
         hasInvalidatedCallTarget_ = false;
     }
     bool hasInvalidatedCallTarget() const {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -499,18 +499,20 @@ class JSScript : public js::gc::Cell
     bool            debugMode:1;      /* script was compiled in debug mode */
     bool            failedBoundsCheck:1; /* script has had hoisted bounds checks fail */
 #else
     bool            debugModePad:1;
     bool            failedBoundsCheckPad:1;
 #endif
 #ifdef JS_ION
     bool            failedShapeGuard:1; /* script has had hoisted shape guard fail */
+    bool            hadFrequentBailouts:1;
 #else
     bool            failedShapeGuardPad:1;
+    bool            hadFrequentBailoutsPad:1;
 #endif
     bool            invalidatedIdempotentCache:1; /* idempotent cache has triggered invalidation */
     bool            isGenerator:1;    /* is a generator */
     bool            isGeneratorExp:1; /* is a generator expression */
     bool            hasScriptCounts:1;/* script has an entry in
                                          JSCompartment::scriptCountsMap */
     bool            hasDebugScript:1; /* script has an entry in
                                          JSCompartment::debugScriptMap */