Bug 1383777 - Support idempotent ICs that access missing properties and object lengths, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 25 Jul 2017 17:18:29 -0600
changeset 419705 08bef58cdb9db462ebfaeabd6e6cbd92d7ddbe08
parent 419704 a53dcc2ca0aab660de8d418bdbe0cd92ac78cfe2
child 419706 4ede5d14ffb11baae01347fed349a94a48603e47
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1383777
milestone56.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 1383777 - Support idempotent ICs that access missing properties and object lengths, r=jandem.
js/src/jit-test/tests/ion/idempotentCache.js
js/src/jit/BaselineIC.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonIC.cpp
js/src/jit/IonIC.h
js/src/jit/SharedIC.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/idempotentCache.js
@@ -0,0 +1,34 @@
+// Test that we don't attach ICs to idempotent caches that are incompatible
+// with the cache result type.
+
+var missingObjs = [{a:1},Object.create({a:2}),{}];
+function testMissing(limit)
+{
+    var res = 0;
+    for (var i = 0; i < 1000; i++) {
+	for (var j = 0; j < missingObjs.length; j++) {
+	    var obj = missingObjs[j];
+	    if (j < limit)
+		res += obj.a;
+	}
+    }
+    return res;
+}
+assertEq(testMissing(2), 3000);
+assertEq(testMissing(3), NaN);
+
+var lengthObjs = [{length:{a:1}},Object.create({length:{a:2}}),[0,1]];
+function testArrayLength(limit)
+{
+    var res = 0;
+    for (var i = 0; i < 1000; i++) {
+	for (var j = 0; j < lengthObjs.length; j++) {
+	    var obj = lengthObjs[j];
+	    if (j < limit)
+		res += obj.length.a;
+	}
+    }
+    return res;
+}
+assertEq(testArrayLength(2), 3000);
+assertEq(testArrayLength(3), NaN);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -805,18 +805,20 @@ DoGetElemFallback(JSContext* cx, Baselin
     bool attached = false;
     bool isTemporarilyUnoptimizable = false;
 
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
-        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, stub->state().mode(),
-                               &isTemporarilyUnoptimizable, lhs, rhs, lhs, CanAttachGetter::Yes);
+        GetPropIRGenerator gen(cx, script, pc,
+                               CacheKind::GetElem, stub->state().mode(),
+                               &isTemporarilyUnoptimizable, lhs, rhs, lhs,
+                               GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         engine, script, stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
@@ -879,17 +881,17 @@ DoGetElemSuperFallback(JSContext* cx, Ba
 
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElemSuper, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, rhs, receiver,
-                               CanAttachGetter::Yes);
+                               GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         engine, script, stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -43,23 +43,23 @@ IRGenerator::IRGenerator(JSContext* cx, 
     cacheKind_(cacheKind),
     mode_(mode)
 {}
 
 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                                        CacheKind cacheKind, ICState::Mode mode,
                                        bool* isTemporarilyUnoptimizable, HandleValue val,
                                        HandleValue idVal, HandleValue receiver,
-                                       CanAttachGetter canAttachGetter)
+                                       GetPropertyResultFlags resultFlags)
   : IRGenerator(cx, script, pc, cacheKind, mode),
     val_(val),
     idVal_(idVal),
     receiver_(receiver),
     isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
-    canAttachGetter_(canAttachGetter),
+    resultFlags_(resultFlags),
     preliminaryObjectAction_(PreliminaryObjectAction::None)
 {}
 
 static void
 EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderOp, NativeObject* holder,
                    Shape* shape)
 {
     if (holder->isFixedSlot(shape->slot())) {
@@ -242,72 +242,75 @@ GetPropIRGenerator::tryAttachStub()
 
     trackNotAttached();
     return false;
 }
 
 bool
 GetPropIRGenerator::tryAttachIdempotentStub()
 {
-    // For idempotent ICs, only attach stubs for plain data properties.
-    // This ensures (1) the lookup has no side-effects and (2) Ion has complete
-    // static type information and we don't have to monitor the result. Because
-    // of (2), we don't support for instance missing properties or array
-    // lengths, as TI does not account for these cases.
+    // For idempotent ICs, only attach stubs which we can be sure have no side
+    // effects and produce a result which the MIR in the calling code is able
+    // to handle, since we do not have a pc to explicitly monitor the result.
 
     MOZ_ASSERT(idempotent());
 
     RootedObject obj(cx_, &val_.toObject());
     RootedId id(cx_, NameToId(idVal_.toString()->asAtom().asPropertyName()));
 
     ValOperandId valId(writer.setInputOperandId(0));
     ObjOperandId objId = writer.guardIsObject(valId);
     if (tryAttachNative(obj, objId, id))
         return true;
 
+    // Object lengths are supported only if int32 results are allowed.
+    if ((resultFlags_ & GetPropertyResultFlags::AllowInt32) && tryAttachObjectLength(obj, objId, id))
+        return true;
+
     // Also support native data properties on DOMProxy prototypes.
     if (GetProxyStubType(cx_, obj, id) == ProxyStubType::DOMUnshadowed)
         return tryAttachDOMProxyUnshadowed(obj, objId, id);
 
     return false;
 }
 
 static bool
 IsCacheableNoProperty(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape, jsid id,
-                      jsbytecode* pc)
+                      jsbytecode* pc, GetPropertyResultFlags resultFlags)
 {
     if (shape)
         return false;
 
     MOZ_ASSERT(!holder);
 
-    if (!pc) {
-        // This is an idempotent IC, don't attach a missing-property stub.
-        // See tryAttachStub.
+    // Idempotent ICs may only attach missing-property stubs if undefined
+    // results are explicitly allowed, since no monitoring is done of the
+    // cache result.
+    if (!pc && !(resultFlags & GetPropertyResultFlags::AllowUndefined))
         return false;
-    }
 
     // If we're doing a name lookup, we have to throw a ReferenceError. If
     // extra warnings are enabled, we may have to report a warning.
-    if (*pc == JSOP_GETBOUNDNAME || cx->compartment()->behaviors().extraWarnings(cx))
+    // Note that Ion does not generate idempotent caches for JSOP_GETBOUNDNAME.
+    if ((pc && *pc == JSOP_GETBOUNDNAME) || cx->compartment()->behaviors().extraWarnings(cx))
         return false;
 
     return CheckHasNoSuchProperty(cx, obj, id);
 }
 
 enum NativeGetPropCacheability {
     CanAttachNone,
     CanAttachReadSlot,
     CanAttachCallGetter,
 };
 
 static NativeGetPropCacheability
 CanAttachNativeGetProp(JSContext* cx, HandleObject obj, HandleId id,
                        MutableHandleNativeObject holder, MutableHandleShape shape,
-                       jsbytecode* pc, CanAttachGetter canAttachGetter,
+                       jsbytecode* pc, GetPropertyResultFlags resultFlags,
                        bool* isTemporarilyUnoptimizable)
 {
     MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
 
     // The lookup needs to be universally pure, otherwise we risk calling hooks out
     // of turn. We don't mind doing this even when purity isn't required, because we
     // only miss out on shape hashification, which is only a temporary perf cost.
     // The limits were arbitrarily set, anyways.
@@ -322,32 +325,27 @@ CanAttachNativeGetProp(JSContext* cx, Ha
             return CanAttachNone;
         holder.set(&baseHolder->as<NativeObject>());
     }
     shape.set(prop.maybeShape());
 
     if (IsCacheableGetPropReadSlotForIonOrCacheIR(obj, holder, prop))
         return CanAttachReadSlot;
 
-    // Idempotent ICs only support plain data properties, see
-    // tryAttachIdempotentStub.
-    if (!pc)
-        return CanAttachNone;
-
-    if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc))
+    if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags))
         return CanAttachReadSlot;
 
-    if (canAttachGetter == CanAttachGetter::No)
-        return CanAttachNone;
-
-    if (IsCacheableGetPropCallScripted(obj, holder, shape, isTemporarilyUnoptimizable))
-        return CanAttachCallGetter;
-
-    if (IsCacheableGetPropCallNative(obj, holder, shape))
-        return CanAttachCallGetter;
+    // Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
+    if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
+        if (IsCacheableGetPropCallScripted(obj, holder, shape, isTemporarilyUnoptimizable))
+            return CanAttachCallGetter;
+
+        if (IsCacheableGetPropCallNative(obj, holder, shape))
+            return CanAttachCallGetter;
+    }
 
     return CanAttachNone;
 }
 
 static void
 GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, ObjOperandId objId)
 {
     // The guards here protect against the effects of JSObject::swap(). If the
@@ -577,20 +575,18 @@ GetPropIRGenerator::attachMegamorphicNat
 
 bool
 GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
 
     NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_,
-                                                            canAttachGetter_,
+                                                            resultFlags_,
                                                             isTemporarilyUnoptimizable_);
-    MOZ_ASSERT_IF(idempotent(),
-                  type == CanAttachNone || (type == CanAttachReadSlot && holder));
     switch (type) {
       case CanAttachNone:
         return false;
       case CanAttachReadSlot:
         if (mode_ == ICState::Mode::Megamorphic) {
             attachMegamorphicNativeSlot(objId, id, holder == nullptr);
             return true;
         }
@@ -608,16 +604,17 @@ GetPropIRGenerator::tryAttachNative(Hand
         }
         EmitReadSlotResult(writer, obj, holder, shape, objId);
         EmitReadSlotReturn(writer, obj, holder, shape);
 
         trackAttached("NativeSlot");
         return true;
       case CanAttachCallGetter: {
         // |super.prop| accesses use a |this| value that differs from lookup object
+        MOZ_ASSERT(!idempotent());
         ObjOperandId receiverId = isSuper() ? writer.guardIsObject(getSuperReceiverValueId())
                                             : objId;
         maybeEmitIdGuard(id);
         EmitCallGetterResult(writer, obj, holder, shape, objId, receiverId, mode_);
 
         trackAttached("NativeGetter");
         return true;
       }
@@ -646,17 +643,17 @@ GetPropIRGenerator::tryAttachWindowProxy
     MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass());
     MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global());
 
     // Now try to do the lookup on the Window (the current global).
     HandleObject windowObj = cx_->global();
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
     NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_,
-                                                            canAttachGetter_,
+                                                            resultFlags_,
                                                             isTemporarilyUnoptimizable_);
     switch (type) {
       case CanAttachNone:
         return false;
 
       case CanAttachReadSlot: {
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::WindowProxy);
@@ -733,18 +730,18 @@ GetPropIRGenerator::tryAttachCrossCompar
         MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == unwrapped->compartment()->maybeGlobal());
         unwrapped = cx_->global();
         MOZ_ASSERT(unwrapped);
     }
 
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
     NativeGetPropCacheability canCache =
-        CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_, canAttachGetter_,
-                               isTemporarilyUnoptimizable_);
+        CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_,
+                               resultFlags_, isTemporarilyUnoptimizable_);
     if (canCache != CanAttachReadSlot)
         return false;
 
     if (holder) {
         EnsureTrackPropertyTypes(cx_, holder, id);
         if (unwrapped == holder) {
             // See the comment in StripPreliminaryObjectStubs.
             if (IsPreliminaryObject(unwrapped))
@@ -958,17 +955,17 @@ GetPropIRGenerator::tryAttachDOMProxyExp
         expandoObj = &expandoAndGeneration->expando.toObject();
     }
 
     // Try to do the lookup on the expando object.
     RootedNativeObject holder(cx_);
     RootedShape propShape(cx_);
     NativeGetPropCacheability canCache =
         CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &propShape, pc_,
-                               canAttachGetter_, isTemporarilyUnoptimizable_);
+                               resultFlags_, isTemporarilyUnoptimizable_);
     if (canCache != CanAttachReadSlot && canCache != CanAttachCallGetter)
         return false;
     if (!holder)
         return false;
 
     MOZ_ASSERT(holder == expandoObj);
 
     maybeEmitIdGuard(id);
@@ -1049,20 +1046,18 @@ GetPropIRGenerator::tryAttachDOMProxyUns
 
     RootedObject checkObj(cx_, obj->staticPrototype());
     if (!checkObj)
         return false;
 
     RootedNativeObject holder(cx_);
     RootedShape shape(cx_);
     NativeGetPropCacheability canCache = CanAttachNativeGetProp(cx_, checkObj, id, &holder, &shape,
-                                                                pc_, canAttachGetter_,
+                                                                pc_, resultFlags_,
                                                                 isTemporarilyUnoptimizable_);
-    MOZ_ASSERT_IF(idempotent(),
-                  canCache == CanAttachNone || (canCache == CanAttachReadSlot && holder));
     if (canCache == CanAttachNone)
         return false;
 
     maybeEmitIdGuard(id);
     writer.guardShape(objId, obj->maybeShape());
 
     // Guard that our expando object hasn't started shadowing this property.
     CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
@@ -1389,17 +1384,17 @@ GetPropIRGenerator::tryAttachPrimitive(V
         return false;
     }
     if (!proto)
         return false;
 
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
     NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, proto, id, &holder, &shape, pc_,
-                                                            canAttachGetter_,
+                                                            resultFlags_,
                                                             isTemporarilyUnoptimizable_);
     if (type != CanAttachReadSlot)
         return false;
 
     if (holder) {
         // Instantiate this property, for use during Ion compilation.
         if (IsIonEnabled(cx_))
             EnsureTrackPropertyTypes(cx_, holder, id);
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -1142,26 +1142,57 @@ class MOZ_RAII IRGenerator
   public:
     explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
                          ICState::Mode mode);
 
     const CacheIRWriter& writerRef() const { return writer; }
     CacheKind cacheKind() const { return cacheKind_; }
 };
 
-enum class CanAttachGetter { Yes, No };
+// Flags used to describe what values a GetProperty cache may produce.
+enum class GetPropertyResultFlags {
+    None            = 0,
+
+    // Values produced by this cache will go through a type barrier,
+    // so the cache may produce any type of value that is compatible with its
+    // result operand.
+    Monitored       = 1 << 0,
+
+    // Whether particular primitives may be produced by this cache.
+    AllowUndefined  = 1 << 1,
+    AllowInt32      = 1 << 2,
+    AllowDouble     = 1 << 3,
+
+    All             = Monitored | AllowUndefined | AllowInt32 | AllowDouble
+};
+
+static inline bool operator&(GetPropertyResultFlags a, GetPropertyResultFlags b)
+{
+    return static_cast<int>(a) & static_cast<int>(b);
+}
+
+static inline GetPropertyResultFlags operator|(GetPropertyResultFlags a, GetPropertyResultFlags b)
+{
+    return static_cast<GetPropertyResultFlags>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+static inline GetPropertyResultFlags& operator|=(GetPropertyResultFlags& lhs, GetPropertyResultFlags b)
+{
+    lhs = lhs | b;
+    return lhs;
+}
 
 // GetPropIRGenerator generates CacheIR for a GetProp IC.
 class MOZ_RAII GetPropIRGenerator : public IRGenerator
 {
     HandleValue val_;
     HandleValue idVal_;
     HandleValue receiver_;
     bool* isTemporarilyUnoptimizable_;
-    CanAttachGetter canAttachGetter_;
+    GetPropertyResultFlags resultFlags_;
 
     enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
     PreliminaryObjectAction preliminaryObjectAction_;
 
     bool tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id);
@@ -1228,17 +1259,18 @@ class MOZ_RAII GetPropIRGenerator : publ
     void maybeEmitIdGuard(jsid id);
 
     void trackAttached(const char* name);
     void trackNotAttached();
 
   public:
     GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
                        ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val,
-                       HandleValue idVal, HandleValue receiver, CanAttachGetter canAttachGetter);
+                       HandleValue idVal, HandleValue receiver,
+                       GetPropertyResultFlags resultFlags);
 
     bool tryAttachStub();
     bool tryAttachIdempotentStub();
 
     bool shouldUnlinkPreliminaryObjectStubs() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
     }
     bool shouldNotePreliminaryObjectStub() const {
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10280,28 +10280,27 @@ CodeGenerator::visitGetNameCache(LGetNam
     IonGetNameIC ic(liveRegs, envChain, output, temp);
     addIC(ins, allocateIC(ic));
 }
 
 void
 CodeGenerator::addGetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs,
                                    TypedOrValueRegister value, const ConstantOrRegister& id,
                                    TypedOrValueRegister output, Register maybeTemp,
-                                   bool monitoredResult, bool allowDoubleResult,
+                                   GetPropertyResultFlags resultFlags,
                                    jsbytecode* profilerLeavePc)
 {
     CacheKind kind = CacheKind::GetElem;
     if (id.constant() && id.value().isString()) {
         JSString* idString = id.value().toString();
         uint32_t dummy;
         if (idString->isAtom() && !idString->asAtom().isIndex(&dummy))
             kind = CacheKind::GetProp;
     }
-    IonGetPropertyIC cache(kind, liveRegs, value, id, output, maybeTemp, monitoredResult,
-                           allowDoubleResult);
+    IonGetPropertyIC cache(kind, liveRegs, value, id, output, maybeTemp, resultFlags);
     addIC(ins, allocateIC(cache));
 }
 
 void
 CodeGenerator::addSetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs, Register objReg,
                                    Register temp, FloatRegister tempDouble,
                                    FloatRegister tempF32, const ConstantOrRegister& id,
                                    const ConstantOrRegister& value,
@@ -10328,44 +10327,71 @@ CodeGenerator::toConstantOrRegister(LIns
 
     const LAllocation* value = lir->getOperand(n);
     if (value->isConstant())
         return ConstantOrRegister(value->toConstant()->toJSValue());
 
     return TypedOrValueRegister(type, ToAnyRegister(value));
 }
 
+static GetPropertyResultFlags
+IonGetPropertyICFlags(const MGetPropertyCache* mir)
+{
+    GetPropertyResultFlags flags = GetPropertyResultFlags::None;
+    if (mir->monitoredResult())
+        flags |= GetPropertyResultFlags::Monitored;
+
+    if (mir->type() == MIRType::Value) {
+        if (TemporaryTypeSet* types = mir->resultTypeSet()) {
+            if (types->hasType(TypeSet::UndefinedType()))
+                flags |= GetPropertyResultFlags::AllowUndefined;
+            if (types->hasType(TypeSet::Int32Type()))
+                flags |= GetPropertyResultFlags::AllowInt32;
+            if (types->hasType(TypeSet::DoubleType()))
+                flags |= GetPropertyResultFlags::AllowDouble;
+        } else {
+            flags |= GetPropertyResultFlags::AllowUndefined
+                   | GetPropertyResultFlags::AllowInt32
+                   | GetPropertyResultFlags::AllowDouble;
+        }
+    } else if (mir->type() == MIRType::Int32) {
+        flags |= GetPropertyResultFlags::AllowInt32;
+    } else if (mir->type() == MIRType::Double) {
+        flags |= GetPropertyResultFlags::AllowInt32 | GetPropertyResultFlags::AllowDouble;
+    }
+
+    return flags;
+}
+
 void
 CodeGenerator::visitGetPropertyCacheV(LGetPropertyCacheV* ins)
 {
     LiveRegisterSet liveRegs = ins->safepoint()->liveRegs();
     TypedOrValueRegister value =
         toConstantOrRegister(ins, LGetPropertyCacheV::Value, ins->mir()->value()->type()).reg();
     ConstantOrRegister id = toConstantOrRegister(ins, LGetPropertyCacheV::Id, ins->mir()->idval()->type());
-    bool monitoredResult = ins->mir()->monitoredResult();
     TypedOrValueRegister output = TypedOrValueRegister(GetValueOutput(ins));
     Register maybeTemp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
 
-    addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp, monitoredResult,
-                        ins->mir()->allowDoubleResult(), ins->mir()->profilerLeavePc());
+    addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp,
+                        IonGetPropertyICFlags(ins->mir()), ins->mir()->profilerLeavePc());
 }
 
 void
 CodeGenerator::visitGetPropertyCacheT(LGetPropertyCacheT* ins)
 {
     LiveRegisterSet liveRegs = ins->safepoint()->liveRegs();
     TypedOrValueRegister value =
         toConstantOrRegister(ins, LGetPropertyCacheV::Value, ins->mir()->value()->type()).reg();
     ConstantOrRegister id = toConstantOrRegister(ins, LGetPropertyCacheT::Id, ins->mir()->idval()->type());
-    bool monitoredResult = ins->mir()->monitoredResult();
     TypedOrValueRegister output(ins->mir()->type(), ToAnyRegister(ins->getDef(0)));
     Register maybeTemp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
 
-    addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp, monitoredResult,
-                        ins->mir()->allowDoubleResult(), ins->mir()->profilerLeavePc());
+    addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp,
+                        IonGetPropertyICFlags(ins->mir()), ins->mir()->profilerLeavePc());
 }
 
 void
 CodeGenerator::visitBindNameCache(LBindNameCache* ins)
 {
     LiveRegisterSet liveRegs = ins->safepoint()->liveRegs();
     Register envChain = ToRegister(ins->environmentChain());
     Register output = ToRegister(ins->output());
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -2,16 +2,17 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_CodeGenerator_h
 #define jit_CodeGenerator_h
 
+#include "jit/CacheIR.h"
 #include "jit/IonCaches.h"
 #if defined(JS_ION_PERF)
 # include "jit/PerfSpewer.h"
 #endif
 
 #if defined(JS_CODEGEN_X86)
 # include "jit/x86/CodeGenerator-x86.h"
 #elif defined(JS_CODEGEN_X64)
@@ -463,18 +464,18 @@ class CodeGenerator final : public CodeG
         IonScriptCounts* counts = scriptCounts_;
         scriptCounts_ = nullptr;  // prevent delete in dtor
         return counts;
     }
 
   private:
     void addGetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs,
                              TypedOrValueRegister value, const ConstantOrRegister& id,
-                             TypedOrValueRegister output, Register maybeTemp, bool monitoredResult,
-                             bool allowDoubleResult, jsbytecode* profilerLeavePc);
+                             TypedOrValueRegister output, Register maybeTemp,
+                             GetPropertyResultFlags flags, jsbytecode* profilerLeavePc);
     void addSetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs, Register objReg,
                              Register temp, FloatRegister tempDouble,
                              FloatRegister tempF32, const ConstantOrRegister& id,
                              const ConstantOrRegister& value,
                              bool strict, bool needsPostBarrier, bool needsTypeBarrier,
                              bool guardHoles, jsbytecode* profilerLeavePc);
 
     MOZ_MUST_USE bool generateBranchV(const ValueOperand& value, Label* ifTrue, Label* ifFalse,
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -130,22 +130,21 @@ IonGetPropertyIC::update(JSContext* cx, 
         ic->discardStubs(cx->zone());
 
     bool attached = false;
     if (ic->state().canAttachStub()) {
         // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it
         // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier
         // does not account for getters, so we should only attach a getter
         // stub if we inserted a type barrier.
-        CanAttachGetter canAttachGetter =
-            ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No;
         jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc();
         bool isTemporarilyUnoptimizable = false;
         GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), ic->state().mode(),
-                               &isTemporarilyUnoptimizable, val, idVal, val, canAttachGetter);
+                               &isTemporarilyUnoptimizable, val, idVal, val,
+                               ic->resultFlags());
         if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub())
             ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
 
         if (!attached && !isTemporarilyUnoptimizable)
             ic->state().trackNotAttached();
     }
 
     if (!attached && ic->idempotent()) {
--- a/js/src/jit/IonIC.h
+++ b/js/src/jit/IonIC.h
@@ -173,47 +173,47 @@ class IonIC
 
     void attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind,
                            IonScript* ionScript, bool* attached,
                            const PropertyTypeCheckInfo* typeCheckInfo = nullptr);
 };
 
 class IonGetPropertyIC : public IonIC
 {
+  private:
     LiveRegisterSet liveRegs_;
 
     TypedOrValueRegister value_;
     ConstantOrRegister id_;
     TypedOrValueRegister output_;
     Register maybeTemp_; // Might be InvalidReg.
 
-    bool monitoredResult_ : 1;
-    bool allowDoubleResult_ : 1;
+    GetPropertyResultFlags resultFlags_;
 
   public:
     IonGetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, TypedOrValueRegister value,
                      const ConstantOrRegister& id, TypedOrValueRegister output, Register maybeTemp,
-                     bool monitoredResult, bool allowDoubleResult)
+                     GetPropertyResultFlags resultFlags)
       : IonIC(kind),
         liveRegs_(liveRegs),
         value_(value),
         id_(id),
         output_(output),
         maybeTemp_(maybeTemp),
-        monitoredResult_(monitoredResult),
-        allowDoubleResult_(allowDoubleResult)
+        resultFlags_(resultFlags)
     { }
 
-    bool monitoredResult() const { return monitoredResult_; }
     TypedOrValueRegister value() const { return value_; }
     ConstantOrRegister id() const { return id_; }
     TypedOrValueRegister output() const { return output_; }
     Register maybeTemp() const { return maybeTemp_; }
     LiveRegisterSet liveRegs() const { return liveRegs_; }
-    bool allowDoubleResult() const { return allowDoubleResult_; }
+    GetPropertyResultFlags resultFlags() const { return resultFlags_; }
+    bool monitoredResult() const { return resultFlags_ & GetPropertyResultFlags::Monitored; }
+    bool allowDoubleResult() const { return resultFlags_ & GetPropertyResultFlags::AllowDouble; }
 
     static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic,
                                     HandleValue val, HandleValue idVal, MutableHandleValue res);
 };
 
 class IonSetPropertyIC : public IonIC
 {
     LiveRegisterSet liveRegs_;
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2064,17 +2064,18 @@ DoGetPropFallback(JSContext* cx, Baselin
 
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     bool attached = false;
     if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(),
-                               &isTemporarilyUnoptimizable, val, idVal, val, CanAttachGetter::Yes);
+                               &isTemporarilyUnoptimizable, val, idVal, val,
+                               GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         ICStubEngine::Baseline, script,
                                                         stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
@@ -2135,17 +2136,17 @@ DoGetPropSuperFallback(JSContext* cx, Ba
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     bool attached = false;
     if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetPropSuper, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, receiver,
-                               CanAttachGetter::Yes);
+                               GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         ICStubEngine::Baseline, script,
                                                         stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())