Bug 1448039 - Remove UNOPTIMIZEABLE bits from baseline IC fallback stubs. r=tcampbell
authorAshley Hauck <khyperia@mozilla.com>
Thu, 11 Oct 2018 01:49:42 +0000
changeset 499114 57de113fc71266c6a8afc5a5faacb74fece75702
parent 499088 9605a2bb0c596ed1badeead50ebd3f47d8378d6f
child 499115 419218259bc1a6f5f1cd527225f1f200fbf55126
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1448039
milestone64.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 1448039 - Remove UNOPTIMIZEABLE bits from baseline IC fallback stubs. r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D7794
js/src/jit-test/tests/ion/bug913749.js
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineInspector.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/ICState.h
js/src/jit/IonIC.cpp
--- a/js/src/jit-test/tests/ion/bug913749.js
+++ b/js/src/jit-test/tests/ion/bug913749.js
@@ -11,11 +11,11 @@ this.toSource();
 y = undefined;
 
 for (var i = 0; i < 3; i++) {
     try {
 	x.toString();
 	assertEq(0, 1);
     } catch (e) {
 	assertEq(e.message === `y is undefined; can't access its "length" property` ||
-		 e.message === "undefined has no properties", true);
+		 e.message === `can't access property "length" of undefined`, true);
     }
 }
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -331,16 +331,43 @@ ICStub::trace(JSTracer* trc)
         TraceCacheIRStub(trc, this, stub->stubInfo());
         break;
       }
       default:
         break;
     }
 }
 
+// This helper handles ICState updates/transitions while attaching CacheIR stubs.
+template<typename IRGenerator, typename... Args>
+static void
+TryAttachStub(const char *name, JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub, BaselineCacheIRStubKind kind, Args&&... args)
+{
+    if (stub->state().maybeTransition()) {
+        stub->discardStubs(cx);
+    }
+
+    if (stub->state().canAttachStub()) {
+        RootedScript script(cx, frame->script());
+        jsbytecode* pc = stub->icEntry()->pc(script);
+
+        bool attached = false;
+        IRGenerator gen(cx, script, pc, stub->state().mode(), std::forward<Args>(args)...);
+        if (gen.tryAttachStub()) {
+            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        kind, script, stub, &attached);
+            if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  %s %s CacheIR stub", attached ? "Attached" : "Failed to attach", name);
+            }
+        }
+        if (!attached) {
+            stub->state().trackNotAttached();
+        }
+    }
+}
 
 
 //
 // WarmUpCounter_Fallback
 //
 
 
 //
@@ -1579,40 +1606,17 @@ ICTypeUpdate_AnyValue::Compiler::generat
 static bool
 DoToBoolFallback(JSContext* cx, BaselineFrame* frame, ICToBool_Fallback* stub, HandleValue arg,
                  MutableHandleValue ret)
 {
     FallbackICSpew(cx, stub, "ToBool");
 
     MOZ_ASSERT(!arg.isBoolean());
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        ToBoolIRGenerator gen(cx, script, pc, stub->state().mode(),
-                              arg);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached ToBool CacheIR stub, attached is now %d", attached);
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<ToBoolIRGenerator>("ToBool", cx, frame, stub, BaselineCacheIRStubKind::Regular, arg);
 
     bool cond = ToBoolean(arg);
     ret.setBoolean(cond);
 
     return true;
 }
 
 typedef bool (*pf)(JSContext*, BaselineFrame*, ICToBool_Fallback*, HandleValue,
@@ -1781,20 +1785,16 @@ DoGetElemFallback(JSContext* cx, Baselin
 
     // GetElem operations which could access negative indexes generally can't
     // be optimized without the potential for bailouts, as we can't statically
     // determine that an object has no properties on such indexes.
     if (rhs.isNumber() && rhs.toNumber() < 0) {
         stub->noteNegativeIndex();
     }
 
-    if (!attached && !isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 static bool
 DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_,
                        HandleValue lhs, HandleValue rhs, HandleValue receiver,
                        MutableHandleValue res)
 {
@@ -1862,20 +1862,16 @@ DoGetElemSuperFallback(JSContext* cx, Ba
 
     // GetElem operations which could access negative indexes generally can't
     // be optimized without the potential for bailouts, as we can't statically
     // determine that an object has no properties on such indexes.
     if (rhs.isNumber() && rhs.toNumber() < 0) {
         stub->noteNegativeIndex();
     }
 
-    if (!attached && !isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 typedef bool (*DoGetElemFallbackFn)(JSContext*, BaselineFrame*, ICGetElem_Fallback*,
                                     HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoGetElemFallbackInfo =
     FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, "DoGetElemFallback", TailCall,
                                       PopValues(2));
@@ -2058,24 +2054,25 @@ DoSetElemFallback(JSContext* cx, Baselin
     if (stub->state().canAttachStub()) {
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(),
                                &isTemporarilyUnoptimizable, objv, index, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Updated,
                                                         frame->script(), stub, &attached);
             if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
+
+                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
-
-                JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
-                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
                 return true;
             }
         } else {
             gen.trackAttached(IRGenerator::NotAttached);
         }
         if (!attached && !isTemporarilyUnoptimizable) {
             stub->state().trackNotAttached();
         }
@@ -2243,38 +2240,17 @@ DoInFallback(JSContext* cx, BaselineFram
 
     FallbackICSpew(cx, stub, "In");
 
     if (!objValue.isObject()) {
         ReportInNotObjectError(cx, key, -2, objValue, -1);
         return false;
     }
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::In, stub->state().mode(), key, objValue);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached In CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<HasPropIRGenerator>("In", cx, frame, stub, BaselineCacheIRStubKind::Regular, CacheKind::In, key, objValue);
 
     RootedObject obj(cx, &objValue.toObject());
     bool cond = false;
     if (!OperatorIn(cx, key, obj, &cond)) {
         return false;
     }
     res.setBoolean(cond);
 
@@ -2312,39 +2288,19 @@ static bool
 DoHasOwnFallback(JSContext* cx, BaselineFrame* frame, ICHasOwn_Fallback* stub_,
                  HandleValue keyValue, HandleValue objValue, MutableHandleValue res)
 {
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICIn_Fallback*> stub(frame, stub_);
 
     FallbackICSpew(cx, stub, "HasOwn");
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::HasOwn,
-                               stub->state().mode(), keyValue, objValue);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached HasOwn CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<HasPropIRGenerator>("HasOwn", cx, frame, stub,
+        BaselineCacheIRStubKind::Regular, CacheKind::HasOwn,
+        keyValue, objValue);
 
     bool found;
     if (!HasOwnProperty(cx, objValue, keyValue, &found)) {
         return false;
     }
 
     res.setBoolean(found);
     return true;
@@ -2388,36 +2344,18 @@ DoGetNameFallback(JSContext* cx, Baselin
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_GETNAME || op == JSOP_GETGNAME);
 
     RootedPropertyName name(cx, script->getName(pc));
-    bool attached = false;
-
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        GetNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Monitored,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached GetName CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+
+    TryAttachStub<GetNameIRGenerator>("GetName", cx, frame, stub, BaselineCacheIRStubKind::Monitored, envChain, name);
 
     static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH,
                   "Otherwise our check for JSOP_TYPEOF isn't ok");
     if (JSOp(pc[JSOP_GETGNAME_LENGTH]) == JSOP_TYPEOF) {
         if (!GetEnvironmentName<GetNameMode::TypeOf>(cx, envChain, name, res)) {
             return false;
         }
     } else {
@@ -2434,19 +2372,16 @@ DoGetNameFallback(JSContext* cx, Baselin
         return true;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
-    if (!attached) {
-        stub->noteUnoptimizableAccess();
-    }
     return true;
 }
 
 typedef bool (*DoGetNameFallbackFn)(JSContext*, BaselineFrame*, ICGetName_Fallback*,
                                     HandleObject, MutableHandleValue);
 static const VMFunction DoGetNameFallbackInfo =
     FunctionInfo<DoGetNameFallbackFn>(DoGetNameFallback, "DoGetNameFallback", TailCall);
 
@@ -2475,36 +2410,17 @@ DoBindNameFallback(JSContext* cx, Baseli
     jsbytecode* pc = stub->icEntry()->pc(frame->script());
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "BindName(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_BINDNAME || op == JSOP_BINDGNAME);
 
     RootedPropertyName name(cx, frame->script()->getName(pc));
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        bool attached = false;
-        RootedScript script(cx, frame->script());
-        BindNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached BindName CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<BindNameIRGenerator>("BindName", cx, frame, stub, BaselineCacheIRStubKind::Regular, envChain, name);
 
     RootedObject scope(cx);
     if (!LookupNameUnqualified(cx, name, envChain, &scope)) {
         return false;
     }
 
     res.setObject(*scope);
     return true;
@@ -2557,36 +2473,17 @@ DoGetIntrinsicFallback(JSContext* cx, Ba
 
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid()) {
         return true;
     }
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        bool attached = false;
-        RootedScript script(cx, frame->script());
-        GetIntrinsicIRGenerator gen(cx, script, pc, stub->state().mode(), res);
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached GetIntrinsic CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<GetIntrinsicIRGenerator>("GetIntrinsic", cx, frame, stub, BaselineCacheIRStubKind::Regular, res);
 
     return true;
 }
 
 typedef bool (*DoGetIntrinsicFallbackFn)(JSContext*, BaselineFrame*, ICGetIntrinsic_Fallback*,
                                          MutableHandleValue);
 static const VMFunction DoGetIntrinsicFallbackInfo =
     FunctionInfo<DoGetIntrinsicFallbackFn>(DoGetIntrinsicFallback, "DoGetIntrinsicFallback",
@@ -2674,17 +2571,17 @@ DoGetPropFallback(JSContext* cx, Baselin
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, val,
                                GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         script, stub, &attached);
             if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
+                JitSpew(JitSpew_BaselineIC, "  Attached GetProp CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
             }
         }
         if (!attached && !isTemporarilyUnoptimizable) {
@@ -2703,26 +2600,16 @@ DoGetPropFallback(JSContext* cx, Baselin
     if (stub.invalid()) {
         return true;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
-
-    if (attached) {
-        return true;
-    }
-
-    MOZ_ASSERT(!attached);
-    if (!isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 static bool
 DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_,
                        HandleValue receiver, MutableHandleValue val, MutableHandleValue res)
 {
     // This fallback stub may trigger debug mode toggling.
@@ -2752,17 +2639,17 @@ DoGetPropSuperFallback(JSContext* cx, Ba
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetPropSuper, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, receiver,
                                GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         script, stub, &attached);
             if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
+                JitSpew(JitSpew_BaselineIC, "  Attached GetPropSuper CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
             }
         }
         if (!attached && !isTemporarilyUnoptimizable) {
@@ -2784,25 +2671,16 @@ DoGetPropSuperFallback(JSContext* cx, Ba
         return true;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
-    if (attached) {
-        return true;
-    }
-
-    MOZ_ASSERT(!attached);
-    if (!isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 typedef bool (*DoGetPropFallbackFn)(JSContext*, BaselineFrame*, ICGetProp_Fallback*,
                                     MutableHandleValue, MutableHandleValue);
 static const VMFunction DoGetPropFallbackInfo =
     FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, "DoGetPropFallback", TailCall,
                                       PopValues(1));
@@ -3020,37 +2898,34 @@ DoSetPropFallback(JSContext* cx, Baselin
         RootedValue idVal(cx, StringValue(name));
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, idVal, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Updated,
                                                         frame->script(), stub, &attached);
             if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
+
+                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
-
-                JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
-                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
             }
         } else {
             gen.trackAttached(IRGenerator::NotAttached);
         }
         if (!attached && !isTemporarilyUnoptimizable) {
             stub->state().trackNotAttached();
         }
     }
 
-    if (!attached && !isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 typedef bool (*DoSetPropFallbackFn)(JSContext*, BaselineFrame*, ICSetProp_Fallback*, Value*,
                                     HandleValue, HandleValue);
 static const VMFunction DoSetPropFallbackInfo =
     FunctionInfo<DoSetPropFallbackFn>(DoSetPropFallback, "DoSetPropFallback", TailCall,
                                       PopValues(1));
@@ -3838,17 +3713,16 @@ DoCallFallback(JSContext* cx, BaselineFr
         // optimized ConstStringSplit stub. Note that vp[0] now holds the return value
         // instead of the callee, so we pass the callee as well.
         if (!TryAttachConstStringSplit(cx, stub, script, argc, callee, vp, pc, res, &handled)) {
             return false;
         }
     }
 
     if (!handled) {
-        stub->noteUnoptimizableCall();
         if (canAttachStub) {
             stub->state().trackNotAttached();
         }
     }
     return true;
 }
 
 static bool
@@ -3891,19 +3765,16 @@ DoSpreadCallFallback(JSContext* cx, Base
     }
 
     // Add a type monitor stub for the resulting value.
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
-    if (!handled) {
-        stub->noteUnoptimizableCall();
-    }
     return true;
 }
 
 void
 ICCallStubCompiler::pushCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs,
                                       Register argcReg, bool isJitCall, bool isConstructing)
 {
     MOZ_ASSERT(!regs.has(argcReg));
@@ -5390,38 +5261,17 @@ ICTableSwitch::fixupJumpTable(JSScript* 
 //
 
 static bool
 DoGetIteratorFallback(JSContext* cx, BaselineFrame* frame, ICGetIterator_Fallback* stub,
                       HandleValue value, MutableHandleValue res)
 {
     FallbackICSpew(cx, stub, "GetIterator");
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        GetIteratorIRGenerator gen(cx, script, pc, stub->state().mode(), value);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached GetIterator CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<GetIteratorIRGenerator>("GetIterator", cx, frame, stub, BaselineCacheIRStubKind::Regular, value);
 
     JSObject* iterobj = ValueToIterator(cx, value);
     if (!iterobj) {
         return false;
     }
 
     res.setObject(*iterobj);
     return true;
@@ -5581,51 +5431,16 @@ ICIteratorClose_Fallback::Compiler::gene
     return tailCallVM(DoIteratorCloseFallbackInfo, masm);
 }
 
 //
 // InstanceOf_Fallback
 //
 
 static bool
-TryAttachInstanceOfStub(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub,
-                        HandleValue lhs, HandleObject rhs, bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-    FallbackICSpew(cx, stub, "InstanceOf");
-
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        InstanceOfIRGenerator gen(cx, script, pc, stub->state().mode(),
-                                  lhs,
-                                  rhs);
-
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached InstanceOf CacheIR stub, attached is now %d", *attached);
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
-
-    return true;
-}
-
-static bool
 DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub_,
                      HandleValue lhs, HandleValue rhs, MutableHandleValue res)
 {
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICInstanceOf_Fallback*> stub(frame, stub_);
 
     FallbackICSpew(cx, stub, "InstanceOf");
 
@@ -5643,31 +5458,28 @@ DoInstanceOfFallback(JSContext* cx, Base
     res.setBoolean(cond);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid()) {
         return true;
     }
 
     if (!obj->is<JSFunction>()) {
-        stub->noteUnoptimizableAccess();
+        // ensure we've recorded at least one failure, so we can detect there was a non-optimizable case
+        if (!stub->state().hasFailures()) {
+            stub->state().trackNotAttached();
+        }
         return true;
     }
 
     // For functions, keep track of the |prototype| property in type information,
     // for use during Ion compilation.
     EnsureTrackPropertyTypes(cx, obj, NameToId(cx->names().prototype));
 
-    bool attached = false;
-    if (!TryAttachInstanceOfStub(cx, frame, stub, lhs, obj, &attached)) {
-        return false;
-    }
-    if (!attached) {
-        stub->noteUnoptimizableAccess();
-    }
+    TryAttachStub<InstanceOfIRGenerator>("InstanceOf", cx, frame, stub, BaselineCacheIRStubKind::Regular, lhs, obj);
     return true;
 }
 
 typedef bool (*DoInstanceOfFallbackFn)(JSContext*, BaselineFrame*, ICInstanceOf_Fallback*,
                                        HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoInstanceOfFallbackInfo =
     FunctionInfo<DoInstanceOfFallbackFn>(DoInstanceOfFallback, "DoInstanceOfFallback", TailCall,
                                          PopValues(2));
@@ -5694,38 +5506,17 @@ ICInstanceOf_Fallback::Compiler::generat
 //
 
 static bool
 DoTypeOfFallback(JSContext* cx, BaselineFrame* frame, ICTypeOf_Fallback* stub, HandleValue val,
                  MutableHandleValue res)
 {
     FallbackICSpew(cx, stub, "TypeOf");
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        TypeOfIRGenerator gen(cx, script, pc, stub->state().mode(), val);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached TypeOf CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<TypeOfIRGenerator>("TypeOf", cx, frame, stub, BaselineCacheIRStubKind::Regular, val);
 
     JSType type = js::TypeOfValue(val);
     RootedString string(cx, TypeName(type, cx->names()));
     res.setString(string);
     return true;
 }
 
 typedef bool (*DoTypeOfFallbackFn)(JSContext*, BaselineFrame* frame, ICTypeOf_Fallback*,
@@ -6056,34 +5847,17 @@ DoUnaryArithFallback(JSContext* cx, Base
     if (debug_stub.invalid()) {
         return true;
     }
 
     if (res.isDouble()) {
         stub->setSawDoubleResult();
     }
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        UnaryArithIRGenerator gen(cx, script, pc, stub->state().mode(),
-                                    op, val, res);
-        if (gen.tryAttachStub()) {
-            bool attached = false;
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached UnaryArith CacheIR stub for %s", CodeName[op]);
-            }
-        }
-    }
-
+    TryAttachStub<UnaryArithIRGenerator>("UniaryArith", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, val, res);
     return true;
 }
 
 typedef bool (*DoUnaryArithFallbackFn)(JSContext*, BaselineFrame*, ICUnaryArith_Fallback*,
                                        HandleValue, MutableHandleValue);
 static const VMFunction DoUnaryArithFallbackInfo =
     FunctionInfo<DoUnaryArithFallbackFn>(DoUnaryArithFallback, "DoUnaryArithFallback", TailCall,
                                          PopValues(1));
@@ -6207,45 +5981,17 @@ DoBinaryArithFallback(JSContext* cx, Bas
     if (stub.invalid()) {
         return true;
     }
 
     if (ret.isDouble()) {
         stub->setSawDoubleResult();
     }
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
-    if (ret.isDouble()) {
-        stub->setSawDoubleResult();
-    }
-
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        BinaryArithIRGenerator gen(cx, script, pc, stub->state().mode(),
-                                   op, lhs, rhs, ret);
-        if (gen.tryAttachStub()) {
-            bool attached = false;
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached BinaryArith CacheIR stub for %s", CodeName[op]);
-            }
-
-        } else {
-            stub->noteUnoptimizableOperands();
-        }
-    }
+    TryAttachStub<BinaryArithIRGenerator>("BinaryArith", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, lhs, rhs, ret);
     return true;
 }
 
 typedef bool (*DoBinaryArithFallbackFn)(JSContext*, BaselineFrame*, ICBinaryArith_Fallback*,
                                         HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoBinaryArithFallbackInfo =
     FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback, "DoBinaryArithFallback",
                                           TailCall, PopValues(2));
@@ -6347,38 +6093,17 @@ DoCompareFallback(JSContext* cx, Baselin
 
     ret.setBoolean(out);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid()) {
         return true;
     }
 
-    // Check to see if a new stub should be generated.
-    if (stub->numOptimizedStubs() >= ICCompare_Fallback::MAX_OPTIMIZED_STUBS) {
-        // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
-        // But for now we just bail.
-        return true;
-    }
-
-    if (stub->state().canAttachStub()) {
-        CompareIRGenerator gen(cx, script, pc, stub->state().mode(), op, lhs, rhs);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                    JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-            }
-            return true;
-        }
-    }
-
-    stub->noteUnoptimizableAccess();
+    TryAttachStub<CompareIRGenerator>("Compare", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, lhs, rhs);
     return true;
 }
 
 typedef bool (*DoCompareFallbackFn)(JSContext*, BaselineFrame*, ICCompare_Fallback*,
                                     HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoCompareFallbackInfo =
     FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, "DoCompareFallback", TailCall,
                                       PopValues(2));
@@ -6481,28 +6206,18 @@ DoNewObject(JSContext* cx, BaselineFrame
         if (obj && !obj->isSingleton() &&
             !obj->group()->maybePreliminaryObjectsDontCheckGeneration())
         {
             templateObject = NewObjectOperation(cx, script, pc, TenuredObject);
             if (!templateObject) {
                 return false;
             }
 
-            if (!JitOptions.disableCacheIR) {
-                bool attached = false;
-                NewObjectIRGenerator gen(cx, script, pc, stub->state().mode(), JSOp(*pc), templateObject);
-                if (gen.tryAttachStub()) {
-                    ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                                BaselineCacheIRStubKind::Regular,
-                                                                script, stub, &attached);
-                    if (newStub) {
-                        JitSpew(JitSpew_BaselineIC, "  NewObject Attached CacheIR stub");
-                    }
-                }
-            }
+            TryAttachStub<NewObjectIRGenerator>("NewObject", cx, frame, stub, BaselineCacheIRStubKind::Regular, JSOp(*pc), templateObject);
+
             stub->setTemplateObject(templateObject);
         }
     }
 
     if (!obj) {
         return false;
     }
 
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1792,31 +1792,24 @@ class ICGetElem_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetElem_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetElem_Fallback, stubCode)
     { }
 
     static const uint16_t EXTRA_NEGATIVE_INDEX = 0x1;
-    static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x2;
 
   public:
     void noteNegativeIndex() {
         extra_ |= EXTRA_NEGATIVE_INDEX;
     }
     bool hasNegativeIndex() const {
         return extra_ & EXTRA_NEGATIVE_INDEX;
     }
-    void noteUnoptimizableAccess() {
-        extra_ |= EXTRA_UNOPTIMIZABLE_ACCESS;
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & EXTRA_UNOPTIMIZABLE_ACCESS;
-    }
 
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
       protected:
         bool hasReceiver_;
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
         virtual int32_t getKey() const override {
@@ -1933,25 +1926,16 @@ class ICGetName_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetName_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetName_Fallback, stubCode)
     { }
 
   public:
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetName_Fallback)
         { }
@@ -2022,26 +2006,18 @@ class ICGetProp_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetProp_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetProp_Fallback, stubCode)
     { }
 
   public:
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
     static const size_t ACCESSED_GETTER_BIT = 1;
 
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     void noteAccessedGetter() {
         extra_ |= (1u << ACCESSED_GETTER_BIT);
     }
     bool hasAccessedGetter() const {
         return extra_ & (1u << ACCESSED_GETTER_BIT);
     }
 
     class Compiler : public ICStubCompiler {
@@ -2079,24 +2055,16 @@ class ICSetProp_Fallback : public ICFall
 {
     friend class ICStubSpace;
 
     explicit ICSetProp_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::SetProp_Fallback, stubCode)
     { }
 
   public:
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     class Compiler : public ICStubCompiler {
       protected:
         CodeOffset bailoutReturnOffset_;
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
         void postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code) override;
 
       public:
         explicit Compiler(JSContext* cx)
@@ -2144,33 +2112,24 @@ class ICCallStubCompiler : public ICStub
     void pushArrayArguments(MacroAssembler& masm, Address arrayVal,
                             AllocatableGeneralRegisterSet regs);
 };
 
 class ICCall_Fallback : public ICMonitoredFallbackStub
 {
     friend class ICStubSpace;
   public:
-    static const unsigned UNOPTIMIZABLE_CALL_FLAG = 0x1;
-
     static const uint32_t MAX_OPTIMIZED_STUBS = 16;
 
   private:
     explicit ICCall_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::Call_Fallback, stubCode)
     {}
 
   public:
-    void noteUnoptimizableCall() {
-        extra_ |= UNOPTIMIZABLE_CALL_FLAG;
-    }
-    bool hadUnoptimizableCall() const {
-        return extra_ & UNOPTIMIZABLE_CALL_FLAG;
-    }
-
     bool scriptedStubsAreGeneralized() const {
         return hasStub(Call_AnyScripted);
     }
     bool nativeStubsAreGeneralized() const {
         // Return hasStub(Call_AnyNative) after Call_AnyNative stub is added.
         return false;
     }
 
@@ -2851,27 +2810,17 @@ class ICIteratorClose_Fallback : public 
 class ICInstanceOf_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
     explicit ICInstanceOf_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::InstanceOf_Fallback, stubCode)
     { }
 
-    static const uint16_t UNOPTIMIZABLE_ACCESS_BIT = 0x1;
-
   public:
-
-    void noteUnoptimizableAccess() {
-        extra_ |= UNOPTIMIZABLE_ACCESS_BIT;
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & UNOPTIMIZABLE_ACCESS_BIT;
-    }
-
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::InstanceOf_Fallback)
         { }
@@ -3066,26 +3015,16 @@ class ICUnaryArith_Fallback : public ICF
 class ICCompare_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
     explicit ICCompare_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::Compare_Fallback, stubCode) {}
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
-
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::Compare_Fallback) {}
@@ -3108,33 +3047,26 @@ class ICBinaryArith_Fallback : public IC
 
     explicit ICBinaryArith_Fallback(JitCode* stubCode)
       : ICFallbackStub(BinaryArith_Fallback, stubCode)
     {
         extra_ = 0;
     }
 
     static const uint16_t SAW_DOUBLE_RESULT_BIT = 0x1;
-    static const uint16_t UNOPTIMIZABLE_OPERANDS_BIT = 0x2;
 
   public:
     static const uint32_t MAX_OPTIMIZED_STUBS = 8;
 
     bool sawDoubleResult() const {
         return extra_ & SAW_DOUBLE_RESULT_BIT;
     }
     void setSawDoubleResult() {
         extra_ |= SAW_DOUBLE_RESULT_BIT;
     }
-    bool hadUnoptimizableOperands() const {
-        return extra_ & UNOPTIMIZABLE_OPERANDS_BIT;
-    }
-    void noteUnoptimizableOperands() {
-        extra_ |= UNOPTIMIZABLE_OPERANDS_BIT;
-    }
 
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -279,24 +279,18 @@ BaselineInspector::maybeInfoForPropertyO
 
         if (!AddReceiver(receiver, receivers, convertUnboxedGroups)) {
             return false;
         }
 
         stub = stub->next();
     }
 
-    if (stub->isGetProp_Fallback()) {
-        if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-            receivers.clear();
-        }
-    } else {
-        if (stub->toSetProp_Fallback()->hadUnoptimizableAccess()) {
-            receivers.clear();
-        }
+    if (stub->toFallbackStub()->state().hasFailures()) {
+        receivers.clear();
     }
 
     // Don't inline if there are more than 5 receivers.
     if (receivers.length() > 5) {
         receivers.clear();
     }
 
     return true;
@@ -614,17 +608,17 @@ BaselineInspector::expectedCompareType(j
     ICStub* first = monomorphicStub(pc);
     ICStub* second = nullptr;
     if (!first && !dimorphicStub(pc, &first, &second)) {
         return MCompare::Compare_Unknown;
     }
 
     if (ICStub* fallback = second ? second->next() : first->next()) {
         MOZ_ASSERT(fallback->isFallback());
-        if (fallback->toCompare_Fallback()->hadUnoptimizableAccess()) {
+        if (fallback->toFallbackStub()->state().hasFailures()) {
             return MCompare::Compare_Unknown;
         }
     }
 
     MCompare::CompareType first_type = ParseCacheIRStubForCompareType(first->toCacheIR_Regular());
     if (!second) {
         return first_type;
     }
@@ -692,19 +686,18 @@ BaselineInspector::expectedBinaryArithSp
     if (!hasBaselineScript()) {
         return MIRType::None;
     }
 
     MIRType result;
     ICStub* stubs[2];
 
     const ICEntry& entry = icEntryFromPC(pc);
-    ICStub* stub = entry.fallbackStub();
-    if (stub->isBinaryArith_Fallback() &&
-        stub->toBinaryArith_Fallback()->hadUnoptimizableOperands())
+    ICFallbackStub* stub = entry.fallbackStub();
+    if (stub->state().hasFailures())
     {
         return MIRType::None;
     }
 
     stubs[0] = monomorphicStub(pc);
     if (stubs[0]) {
         if (TryToSpecializeBinaryArithOp(stubs, 1, &result)) {
             return result;
@@ -842,17 +835,17 @@ BaselineInspector::getSingleCallee(jsbyt
 
     if (!hasBaselineScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.firstStub();
 
-    if (entry.fallbackStub()->toCall_Fallback()->hadUnoptimizableCall()) {
+    if (entry.fallbackStub()->state().hasFailures()) {
         return nullptr;
     }
 
     if (!stub->isCall_Scripted() || stub->next() != entry.fallbackStub()) {
         return nullptr;
     }
 
     return stub->toCall_Scripted()->callee();
@@ -1244,23 +1237,19 @@ BaselineInspector::commonGetPropFunction
         if (stub->isCacheIR_Monitored()) {
             if (!AddCacheIRGetPropFunction(stub->toCacheIR_Monitored(), innerized,
                                            holder, holderShape,
                                            commonGetter, globalShape, isOwnProperty, receivers,
                                            convertUnboxedGroups, script))
             {
                 return false;
             }
-        } else if (stub->isGetProp_Fallback()) {
+        } else if (stub->isFallback()) {
             // If we have an unoptimizable access, don't try to optimize.
-            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-                return false;
-            }
-        } else if (stub->isGetName_Fallback()) {
-            if (stub->toGetName_Fallback()->hadUnoptimizableAccess()) {
+            if (stub->toFallbackStub()->state().hasFailures()) {
                 return false;
             }
         } else {
             return false;
         }
     }
 
     if (!*commonGetter) {
@@ -1329,30 +1318,21 @@ BaselineInspector::megamorphicGetterSett
                                                    stub->toCacheIR_Updated()->stubInfo(),
                                                    isGetter);
             if (!setter || (*getterOrSetter && *getterOrSetter != setter)) {
                 return false;
             }
             *getterOrSetter = setter;
             continue;
         }
-        if (stub->isGetProp_Fallback()) {
-            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-                return false;
-            }
-            if (stub->toGetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic) {
+        if (stub->isFallback()) {
+            if (stub->toFallbackStub()->state().hasFailures()) {
                 return false;
             }
-            continue;
-        }
-        if (stub->isSetProp_Fallback()) {
-            if (stub->toSetProp_Fallback()->hadUnoptimizableAccess()) {
-                return false;
-            }
-            if (stub->toSetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic) {
+            if (stub->toFallbackStub()->state().mode() != ICState::Mode::Megamorphic) {
                 return false;
             }
             continue;
         }
 
         return false;
     }
 
@@ -1482,18 +1462,18 @@ BaselineInspector::commonSetPropFunction
         if (stub->isCacheIR_Updated()) {
             if (!AddCacheIRSetPropFunction(stub->toCacheIR_Updated(),
                                            holder, holderShape,
                                            commonSetter, isOwnProperty, receivers,
                                            convertUnboxedGroups))
             {
                 return false;
             }
-        } else if (!stub->isSetProp_Fallback() ||
-                   stub->toSetProp_Fallback()->hadUnoptimizableAccess())
+        } else if (!stub->isFallback() ||
+                   stub->toFallbackStub()->state().hasFailures())
         {
             // We have an unoptimizable access, so don't try to optimize.
             return false;
         }
     }
 
     if (!*commonSetter) {
         return false;
@@ -1592,17 +1572,17 @@ BaselineInspector::maybeInfoForProtoRead
 
         if (!AddReceiver(receiver, receivers, convertUnboxedGroups)) {
             return false;
         }
 
         stub = stub->next();
     }
 
-    if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
+    if (stub->toFallbackStub()->state().hasFailures()) {
         receivers.clear();
     }
 
     // Don't inline if there are more than 5 receivers.
     if (receivers.length() > 5) {
         receivers.clear();
     }
 
@@ -1636,38 +1616,28 @@ BaselineInspector::expectedPropertyAcces
     if (!hasBaselineScript()) {
         return MIRType::Value;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     MIRType type = MIRType::None;
 
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
-        MIRType stubType;
-        switch (stub->kind()) {
-          case ICStub::GetProp_Fallback:
-            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-                return MIRType::Value;
-            }
-            continue;
-
-          case ICStub::GetElem_Fallback:
-            if (stub->toGetElem_Fallback()->hadUnoptimizableAccess()) {
-                return MIRType::Value;
-            }
-            continue;
-
-          case ICStub::CacheIR_Monitored:
+        MIRType stubType = MIRType::None;
+        if (stub->isCacheIR_Monitored()) {
             stubType = GetCacheIRExpectedInputType(stub->toCacheIR_Monitored());
             if (stubType == MIRType::Value) {
                 return MIRType::Value;
             }
-            break;
-
-          default:
+        } else if (stub->isGetElem_Fallback() || stub->isGetProp_Fallback()) {
+            // If we have an unoptimizable access, don't try to optimize.
+            if (stub->toFallbackStub()->state().hasFailures()) {
+                return MIRType::Value;
+            }
+        } else {
             MOZ_CRASH("Unexpected stub");
         }
 
         if (type != MIRType::None) {
             if (type != stubType) {
                 return MIRType::Value;
             }
         } else {
@@ -1689,17 +1659,17 @@ BaselineInspector::instanceOfData(jsbyte
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* firstStub = entry.firstStub();
 
     // Ensure singleton instanceof stub
     if (!firstStub->next() ||
         !firstStub->isCacheIR_Regular() ||
         !firstStub->next()->isInstanceOf_Fallback() ||
-         firstStub->next()->toInstanceOf_Fallback()->hadUnoptimizableAccess())
+         firstStub->next()->toInstanceOf_Fallback()->state().hasFailures())
          {
              return false;
          }
 
     ICCacheIR_Regular* stub = entry.firstStub()->toCacheIR_Regular();
     CacheIRReader reader(stub->stubInfo());
 
     ObjOperandId rhsId = ObjOperandId(1);
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -2877,17 +2877,17 @@ BindNameIRGenerator::trackAttached(const
     if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
         sp.valueProperty("base", ObjectValue(*env_));
         sp.valueProperty("property", StringValue(name_));
     }
 #endif
 }
 
 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                       CacheKind cacheKind, ICState::Mode mode,
+                                       ICState::Mode mode, CacheKind cacheKind,
                                        HandleValue idVal, HandleValue val)
   : IRGenerator(cx, script, pc, cacheKind, mode),
     val_(val),
     idVal_(idVal)
 { }
 
 bool
 HasPropIRGenerator::tryAttachDense(HandleObject obj, ObjOperandId objId,
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -1819,18 +1819,18 @@ class MOZ_RAII HasPropIRGenerator : publ
                                HandleId key, ValOperandId keyId);
     bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId,
                                ValOperandId keyId);
 
     void trackAttached(const char* name);
 
   public:
     // NOTE: Argument order is PROPERTY, OBJECT
-    HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
-                       ICState::Mode mode, HandleValue idVal, HandleValue val);
+    HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
+                       CacheKind cacheKind, HandleValue idVal, HandleValue val);
 
     bool tryAttachStub();
 };
 
 class MOZ_RAII InstanceOfIRGenerator : public IRGenerator
 {
     HandleValue lhsVal_;
     HandleObject rhsObj_;
--- a/js/src/jit/ICState.h
+++ b/js/src/jit/ICState.h
@@ -59,16 +59,17 @@ class ICState
     ICState()
       : invalid_(false)
     {
         reset();
     }
 
     Mode mode() const { return mode_; }
     size_t numOptimizedStubs() const { return numOptimizedStubs_; }
+    bool hasFailures() const { return (numFailures_ != 0); }
 
     MOZ_ALWAYS_INLINE bool canAttachStub() const {
         // Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs
         // because old-style baseline ICs may attach more stubs than
         // MaxOptimizedStubs allows.
         if (mode_ == Mode::Generic || JitOptions.disableCacheIR) {
             return false;
         }
@@ -105,17 +106,20 @@ class ICState
     }
     void trackAttached() {
         // We'd like to assert numOptimizedStubs_ < MaxOptimizedStubs, but
         // since this code is also used for non-CacheIR Baseline stubs, assert
         // < 16 for now. Note that we do have the stronger assert in other
         // methods, because they are only used by CacheIR ICs.
         MOZ_ASSERT(numOptimizedStubs_ < 16);
         numOptimizedStubs_++;
-        numFailures_ = 0;
+        // As a heuristic, reduce the failure count after each successful attach
+        // to delay hitting Generic mode. Reset to 1 instead of 0 so that
+        // BaselineInspector can distinguish no-failures from rare-failures.
+        numFailures_ = std::min(numFailures_, static_cast<uint8_t>(1));
     }
     void trackNotAttached() {
         // Note: we can't assert numFailures_ < maxFailures() because
         // maxFailures() depends on numOptimizedStubs_ and it's possible a
         // GC discarded stubs before we got here.
         numFailures_++;
         MOZ_ASSERT(numFailures_ > 0, "numFailures_ should not overflow");
     }
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -475,17 +475,17 @@ IonHasOwnIC::update(JSContext* cx, Handl
         ic->discardStubs(cx->zone());
     }
 
     jsbytecode* pc = ic->pc();
 
     if (ic->state().canAttachStub()) {
         bool attached = false;
         RootedScript script(cx, ic->script());
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::HasOwn, ic->state().mode(), idVal, val);
+        HasPropIRGenerator gen(cx, script, pc, ic->state().mode(), CacheKind::HasOwn, idVal, val);
         if (gen.tryAttachStub()) {
             ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
         }
 
         if (!attached) {
             ic->state().trackNotAttached();
         }
     }
@@ -509,17 +509,17 @@ IonInIC::update(JSContext* cx, HandleScr
         ic->discardStubs(cx->zone());
     }
 
     if (ic->state().canAttachStub()) {
         bool attached = false;
         RootedScript script(cx, ic->script());
         RootedValue objV(cx, ObjectValue(*obj));
         jsbytecode* pc = ic->pc();
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::In, ic->state().mode(), key, objV);
+        HasPropIRGenerator gen(cx, script, pc, ic->state().mode(), CacheKind::In, key, objV);
         if (gen.tryAttachStub()) {
             ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
         }
 
         if (!attached) {
             ic->state().trackNotAttached();
         }
     }