Bug 1349924 - Try to specialize property loads to specific function objects, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 24 Jul 2017 14:01:49 -0600
changeset 419488 7aa7d265948aedd183932c40d27da60c03beee9e
parent 419487 157445a8c76bae6dc9b8fc2449f8eb95b3cddbbd
child 419489 661014562d027e9040c8ca0bb6245b9de1614779
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
bugs1349924
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 1349924 - Try to specialize property loads to specific function objects, r=jandem.
js/public/TrackedOptimizationInfo.h
js/src/frontend/BytecodeEmitter.cpp
js/src/jit-test/tests/ion/bailoutStaticObject.js
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineInspector.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonTypes.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/vm/TypeInference.cpp
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -18,16 +18,17 @@ namespace JS {
     _(GetProp_Constant)                                 \
     _(GetProp_NotDefined)                               \
     _(GetProp_StaticName)                               \
     _(GetProp_SimdGetter)                               \
     _(GetProp_TypedObject)                              \
     _(GetProp_DefiniteSlot)                             \
     _(GetProp_Unboxed)                                  \
     _(GetProp_CommonGetter)                             \
+    _(GetProp_Static)                                   \
     _(GetProp_InlineAccess)                             \
     _(GetProp_Innerize)                                 \
     _(GetProp_InlineCache)                              \
     _(GetProp_SharedCache)                              \
     _(GetProp_ModuleNamespace)                          \
                                                         \
     _(SetProp_CommonSetter)                             \
     _(SetProp_TypedObject)                              \
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -9511,16 +9511,25 @@ BytecodeEmitter::emitCallOrNew(ParseNode
         if (!emitGetName(pn2, callop))
             return false;
         break;
       case PNK_DOT:
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         if (pn2->as<PropertyAccess>().isSuper()) {
             if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop))
                 return false;
+        } else if ((pn->getOp() == JSOP_FUNCALL || pn->getOp() == JSOP_FUNAPPLY) &&
+                   pn2->expr()->getKind() == PNK_FUNCTION &&
+                   checkRunOnceContext()) {
+            // Top level lambdas whose .call or .apply methods are immediately
+            // invoked should be treated as run once lambdas.
+            emittingRunOnceLambda = true;
+            if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
+                return false;
+            emittingRunOnceLambda = false;
         } else {
             if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
                 return false;
         }
 
         break;
       case PNK_ELEM:
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bailoutStaticObject.js
@@ -0,0 +1,28 @@
+// Test bailouts from loading particular non-singleton static objects.
+
+function wrap(fun) {
+    function wrapper() {
+	return fun.apply(this, arguments);
+    }
+    return wrapper;
+}
+
+var adder = wrap(function(a, b) { return a + b; });
+var subber = wrap(function(a, b) { return a - b; });
+var tmp = adder;
+adder = subber;
+adder = tmp;
+
+function foo() {
+    var i = 0;
+    var a = 0;
+    for (var i = 0; i < 10000; i++) {
+	a = adder(a, 1);
+	a = subber(a, 1);
+    }
+    return a;
+}
+
+assertEq(foo(), 0);
+adder = subber;
+assertEq(foo(), -20000);
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1998,16 +1998,17 @@ jit::FinishBailoutToBaseline(BaselineBai
       case Bailout_NonObjectInput:
       case Bailout_NonStringInput:
       case Bailout_NonSymbolInput:
       case Bailout_UnexpectedSimdInput:
       case Bailout_NonSharedTypedArrayInput:
       case Bailout_Debugger:
       case Bailout_UninitializedThis:
       case Bailout_BadDerivedConstructorReturn:
+      case Bailout_LoadStaticObject:
         // Do nothing.
         break;
 
       case Bailout_FirstExecution:
         // Do not return directly, as this was not frequent in the first place,
         // thus rely on the check for frequent bailouts to recompile the current
         // script.
         break;
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -1035,17 +1035,17 @@ GetMegamorphicGetterSetterFunction(ICStu
     JSObject* obj = isGetter ? propShape->getterObject() : propShape->setterObject();
     return &obj->as<JSFunction>();
 }
 
 bool
 BaselineInspector::megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter,
                                                    JSFunction** getterOrSetter)
 {
-    if (!hasBaselineScript())
+    if (!hasBaselineScript() || *pc == JSOP_SETALIASEDVAR)
         return false;
 
     *getterOrSetter = nullptr;
     const ICEntry& entry = icEntryFromPC(pc);
 
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         if (stub->isCacheIR_Monitored()) {
             MOZ_ASSERT(isGetter);
@@ -1186,17 +1186,17 @@ AddCacheIRSetPropFunction(ICCacheIR_Upda
 }
 
 bool
 BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
                                          JSFunction** commonSetter, bool* isOwnProperty,
                                          ReceiverVector& receivers,
                                          ObjectGroupVector& convertUnboxedGroups)
 {
-    if (!hasBaselineScript())
+    if (!hasBaselineScript() || *pc == JSOP_SETALIASEDVAR)
         return false;
 
     MOZ_ASSERT(receivers.empty());
     MOZ_ASSERT(convertUnboxedGroups.empty());
 
     *commonSetter = nullptr;
     const ICEntry& entry = icEntryFromPC(pc);
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7270,27 +7270,16 @@ IonBuilder::ensureDefiniteTypeSet(MDefin
     }
 
     // Create a NOP mir instruction to filter the typeset.
     MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), def, types);
     current->add(filter);
     return filter;
 }
 
-static size_t
-NumFixedSlots(JSObject* object)
-{
-    // Note: we can't use object->numFixedSlots() here, as this will read the
-    // shape and can race with the active thread if we are building off thread.
-    // The allocation kind and object class (which goes through the type) can
-    // be read freely, however.
-    gc::AllocKind kind = object->asTenured().getAllocKind();
-    return gc::GetGCKindSlots(kind, object->getClass());
-}
-
 static bool
 IsUninitializedGlobalLexicalSlot(JSObject* obj, PropertyName* name)
 {
     LexicalEnvironmentObject &globalLexical = obj->as<LexicalEnvironmentObject>();
     MOZ_ASSERT(globalLexical.isGlobal());
     Shape* shape = globalLexical.lookupPure(name);
     if (!shape)
         return false;
@@ -7302,42 +7291,58 @@ IonBuilder::getStaticName(bool* emitted,
                           MDefinition* lexicalCheck)
 {
     MOZ_ASSERT(*emitted == false);
 
     jsid id = NameToId(name);
 
     bool isGlobalLexical = staticObject->is<LexicalEnvironmentObject>() &&
                            staticObject->as<LexicalEnvironmentObject>().isGlobal();
-    MOZ_ASSERT(isGlobalLexical ||
-               staticObject->is<GlobalObject>() ||
-               staticObject->is<CallObject>() ||
-               staticObject->is<ModuleEnvironmentObject>());
-    MOZ_ASSERT(staticObject->isSingleton());
 
     // Always emit the lexical check. This could be optimized, but is
     // currently not for simplicity's sake.
     if (lexicalCheck)
         return Ok();
 
+    // Only optimize accesses on native objects.
+    if (!staticObject->isNative())
+        return Ok();
+
+    // Only optimize accesses on own data properties.
+    Shape* propertyShape = staticObject->as<NativeObject>().lastProperty()->searchLinear(NameToId(name));
+    if (!propertyShape || !propertyShape->isDataDescriptor() || !propertyShape->hasSlot())
+        return Ok();
+    uint32_t slot = propertyShape->slot();
+
     TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(staticObject);
     if (analysisContext)
         staticKey->ensureTrackedProperty(analysisContext, NameToId(name));
 
-    if (staticKey->unknownProperties())
-        return Ok();
-
-    HeapTypeSetKey property = staticKey->property(id);
-    if (!property.maybeTypes() ||
-        !property.maybeTypes()->definiteProperty() ||
-        property.nonData(constraints()))
-    {
-        // The property has been reconfigured as non-configurable, non-enumerable
-        // or non-writable.
-        return Ok();
+    // Make sure the property is a normal data property. This is not done for
+    // call objects, as they are not tracked by TI and their data properties
+    // cannot be dynamically reconfigured.
+    Maybe<HeapTypeSetKey> property;
+    if (!staticObject->is<CallObject>()) {
+        if (staticKey->unknownProperties())
+            return Ok();
+
+        property.emplace(staticKey->property(id));
+
+        if (property.ref().nonData(constraints()) ||
+            !property.ref().maybeTypes() ||
+            !property.ref().maybeTypes()->definiteProperty())
+        {
+            // We can't be sure the slot will match at runtime, so include a
+            // shape guard on the object.
+            MInstruction* obj = MConstant::NewConstraintlessObject(alloc(), staticObject);
+            current->add(obj);
+            addShapeGuard(obj, staticObject->as<NativeObject>().lastProperty(), Bailout_ShapeGuard);
+        } else {
+            MOZ_ASSERT(slot == property.ref().maybeTypes()->definiteSlot());
+        }
     }
 
     // Don't optimize global lexical bindings if they aren't initialized at
     // compile time.
     if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name))
         return Ok();
 
     *emitted = true;
@@ -7353,23 +7358,47 @@ IonBuilder::getStaticName(bool* emitted,
             if (testSingletonProperty(staticObject, id) == singleton) {
                 pushConstant(ObjectValue(*singleton));
                 return Ok();
             }
         }
 
         // Try to inline properties that have never been overwritten.
         Value constantValue;
-        if (property.constant(constraints(), &constantValue)) {
+        if (property.isSome() && property.ref().constant(constraints(), &constantValue)) {
             pushConstant(constantValue);
             return Ok();
         }
     }
 
-    MOZ_TRY(loadStaticSlot(staticObject, barrier, types, property.maybeTypes()->definiteSlot()));
+    MOZ_TRY(loadStaticSlot(staticObject, barrier, types, slot));
+
+    // If the static object has a function object stored in this property,
+    // test that the result is that specific function. This is yet another
+    // technique for trying to force a property load to be a specific value,
+    // and is included because other mechanisms (property types and observed
+    // types) do not always work, especially in polymorphic framework code.
+    // We restrict this optimization to function properties, as they are less
+    // likely to change over time and are more likely to require precise
+    // information for inlining decisions.
+    if (!outermostBuilder()->script()->hadFrequentBailouts()) {
+        Value v = staticObject->as<NativeObject>().getSlot(slot);
+        if (v.isObject() &&
+            v.toObject().is<JSFunction>() &&
+            v.toObject().as<JSFunction>().isInterpreted())
+        {
+            JSObject* result = checkNurseryObject(&v.toObject().as<JSFunction>());
+            MDefinition* load = current->pop();
+            MInstruction* expected = MConstant::NewConstraintlessObject(alloc(), result);
+            expected->setResultTypeSet(MakeSingletonTypeSet(constraints(), result));
+            current->add(expected);
+            current->add(MGuardObjectIdentity::New(alloc(), load, expected, false, Bailout_LoadStaticObject));
+            current->push(expected);
+        }
+    }
 
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::loadStaticSlot(JSObject* staticObject, BarrierKind barrier, TemporaryTypeSet* types,
                            uint32_t slot)
 {
@@ -7387,17 +7416,17 @@ IonBuilder::loadStaticSlot(JSObject* sta
     }
 
     MInstruction* obj = constant(ObjectValue(*staticObject));
 
     MIRType rvalType = types->getKnownMIRType();
     if (barrier != BarrierKind::NoBarrier)
         rvalType = MIRType::Value;
 
-    return loadSlot(obj, slot, NumFixedSlots(staticObject), rvalType, barrier, types);
+    return loadSlot(obj, slot, staticObject->as<NativeObject>().numFixedSlots(), rvalType, barrier, types);
 }
 
 // Whether a write of the given value may need a post-write barrier for GC purposes.
 bool
 jit::NeedsPostBarrier(MDefinition* value)
 {
     if (!GetJitContext()->compartment->zone()->nurseryExists())
         return false;
@@ -7452,17 +7481,18 @@ IonBuilder::setStaticName(JSObject* stat
     // If the property has a known type, we may be able to optimize typed stores by not
     // storing the type tag.
     MIRType slotType = MIRType::None;
     MIRType knownType = property.knownMIRType(constraints());
     if (knownType != MIRType::Value)
         slotType = knownType;
 
     bool needsPreBarrier = property.needsBarrier(constraints());
-    return storeSlot(obj, property.maybeTypes()->definiteSlot(), NumFixedSlots(staticObject),
+    return storeSlot(obj, property.maybeTypes()->definiteSlot(),
+                     staticObject->as<NativeObject>().numFixedSlots(),
                      value, needsPreBarrier, slotType);
 }
 
 JSObject*
 IonBuilder::testGlobalLexicalBinding(PropertyName* name)
 {
     MOZ_ASSERT(JSOp(*pc) == JSOP_BINDGNAME ||
                JSOp(*pc) == JSOP_GETGNAME ||
@@ -10393,16 +10423,22 @@ IonBuilder::jsop_getprop(PropertyName* n
             return Ok();
 
         // Try to inline a common property getter, or make a call.
         trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
         MOZ_TRY(getPropTryCommonGetter(&emitted, obj, name, types));
         if (emitted)
             return Ok();
 
+        // Try to optimize for loads from a specific object.
+        trackOptimizationAttempt(TrackedStrategy::GetProp_Static);
+        MOZ_TRY(getPropTryStaticAccess(&emitted, obj, name, barrier, types));
+        if (emitted)
+            return Ok();
+
         // Try to emit a monomorphic/polymorphic access based on baseline caches.
         trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess);
         MOZ_TRY(getPropTryInlineAccess(&emitted, obj, name, barrier, types));
         if (emitted)
             return Ok();
 
         // Try to emit loads from a module namespace.
         trackOptimizationAttempt(TrackedStrategy::GetProp_ModuleNamespace);
@@ -11193,16 +11229,27 @@ PropertyShapesHaveSameSlot(const Baselin
             return nullptr;
         }
     }
 
     return firstShape;
 }
 
 AbortReasonOr<Ok>
+IonBuilder::getPropTryStaticAccess(bool* emitted, MDefinition* obj, PropertyName* name,
+                                   BarrierKind barrier, TemporaryTypeSet* types)
+{
+    if (!obj->isConstant() || obj->type() != MIRType::Object)
+        return Ok();
+
+    obj->setImplicitlyUsedUnchecked();
+    return getStaticName(emitted, &obj->toConstant()->toObject(), name);
+}
+
+AbortReasonOr<Ok>
 IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                    BarrierKind barrier, TemporaryTypeSet* types)
 {
     MOZ_ASSERT(*emitted == false);
 
     BaselineInspector::ReceiverVector receivers(alloc());
     BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
     if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
@@ -12625,21 +12672,59 @@ IonBuilder::walkEnvironmentChain(unsigne
         MInstruction* ins = MEnclosingEnvironment::New(alloc(), env);
         current->add(ins);
         env = ins;
     }
 
     return env;
 }
 
+static bool
+SearchEnvironmentChainForCallObject(JSObject* environment, JSScript* script, JSObject** pcall)
+{
+    while (environment && !environment->is<GlobalObject>()) {
+        if (environment->is<CallObject>() &&
+            environment->as<CallObject>().callee().nonLazyScript() == script)
+        {
+            *pcall = environment;
+            return true;
+        }
+        environment = environment->enclosingEnvironment();
+    }
+    return false;
+}
+
 bool
 IonBuilder::hasStaticEnvironmentObject(EnvironmentCoordinate ec, JSObject** pcall)
 {
     JSScript* outerScript = EnvironmentCoordinateFunctionScript(script(), pc);
-    if (!outerScript || !outerScript->treatAsRunOnce())
+    if (!outerScript)
+        return false;
+
+    // JSOP_SETALIASEDVAR only emits a cache when the outer script is a run
+    // once script. To avoid problems with the generic jsop_setprop() paths,
+    // only use static environment objects when a baseline cache exists.
+    if (*pc == JSOP_SETALIASEDVAR && !outerScript->treatAsRunOnce())
+        return false;
+
+    // If the callee is a specific JSFunction then there is a specific
+    // environment object on its chain we can use.
+    if (inlineCallInfo_) {
+        MDefinition* calleeDef = inlineCallInfo_->fun();
+        if (calleeDef->isConstant()) {
+            JSFunction* callee = &calleeDef->toConstant()->toObject().template as<JSFunction>();
+            JSObject* environment = callee->environment();
+            if (SearchEnvironmentChainForCallObject(environment, outerScript, pcall))
+                return true;
+        }
+    }
+
+    // Otherwise, if the outer script will only run once then we can go looking
+    // for its call object.
+    if (!outerScript->treatAsRunOnce())
         return false;
 
     TypeSet::ObjectKey* funKey =
         TypeSet::ObjectKey::get(outerScript->functionNonDelazifying());
     if (funKey->hasFlags(constraints(), OBJECT_FLAG_RUNONCE_INVALIDATED))
         return false;
 
     // The script this aliased var operation is accessing will run only once,
@@ -12650,42 +12735,28 @@ IonBuilder::hasStaticEnvironmentObject(E
     // Look for the call object on the current script's function's env chain.
     // If the current script is inner to the outer script and the function has
     // singleton type then it should show up here.
 
     MDefinition* envDef = current->getSlot(info().environmentChainSlot());
     envDef->setImplicitlyUsedUnchecked();
 
     JSObject* environment = script()->functionNonDelazifying()->environment();
-    while (environment && !environment->is<GlobalObject>()) {
-        if (environment->is<CallObject>() &&
-            environment->as<CallObject>().callee().nonLazyScript() == outerScript)
-        {
-            MOZ_ASSERT(environment->isSingleton());
-            *pcall = environment;
-            return true;
-        }
-        environment = environment->enclosingEnvironment();
-    }
+    if (SearchEnvironmentChainForCallObject(environment, outerScript, pcall))
+        return true;
 
     // Look for the call object on the current frame, if we are compiling the
     // outer script itself. Don't do this if we are at entry to the outer
     // script, as the call object we see will not be the real one --- after
     // entering the Ion code a different call object will be created.
 
     if (script() == outerScript && baselineFrame_ && info().osrPc()) {
         JSObject* singletonScope = baselineFrame_->singletonEnvChain;
-        if (singletonScope &&
-            singletonScope->is<CallObject>() &&
-            singletonScope->as<CallObject>().callee().nonLazyScript() == outerScript)
-        {
-            MOZ_ASSERT(singletonScope->isSingleton());
-            *pcall = singletonScope;
+        if (SearchEnvironmentChainForCallObject(singletonScope, outerScript, pcall))
             return true;
-        }
     }
 
     return true;
 }
 
 MDefinition*
 IonBuilder::getAliasedVar(EnvironmentCoordinate ec)
 {
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -241,16 +241,18 @@ class IonBuilder
     AbortReasonOr<Ok> getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name,
                                              BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyName* name,
                                                 BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
                                         BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name,
                                              TemporaryTypeSet* types, bool innerized = false);
+    AbortReasonOr<Ok> getPropTryStaticAccess(bool* emitted, MDefinition* obj, PropertyName* name,
+                                             BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                              BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryTypedObject(bool* emitted, MDefinition* obj, PropertyName* name);
     AbortReasonOr<Ok> getPropTryScalarPropOfTypedObject(bool* emitted, MDefinition* typedObj,
                                                         int32_t fieldOffset,
                                                         TypedObjectPrediction fieldTypeReprs);
     AbortReasonOr<Ok> getPropTryReferencePropOfTypedObject(bool* emitted, MDefinition* typedObj,
                                                            int32_t fieldOffset,
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -137,16 +137,19 @@ enum BailoutKind
     Bailout_NonStringInputInvalidate,
 
     // Used for integer division, multiplication and modulo.
     // If there's a remainder, bails to return a double.
     // Can also signal overflow or result of -0.
     // Can also signal division by 0 (returns inf, a double).
     Bailout_DoubleOutput,
 
+    // Load of a value from a static object retrieved an unexpected value.
+    Bailout_LoadStaticObject,
+
     // END Invalid assumptions bailouts
 
 
     // A bailout at the very start of a function indicates that there may be
     // a type mismatch in the arguments that necessitates a reflow.
     Bailout_ArgumentCheck,
 
     // A bailout triggered by a bounds-check failure.
@@ -228,16 +231,18 @@ BailoutKindString(BailoutKind kind)
 
       // Bailouts caused by invalid assumptions.
       case Bailout_OverflowInvalidate:
         return "Bailout_OverflowInvalidate";
       case Bailout_NonStringInputInvalidate:
         return "Bailout_NonStringInputInvalidate";
       case Bailout_DoubleOutput:
         return "Bailout_DoubleOutput";
+      case Bailout_LoadStaticObject:
+        return "Bailout_LoadStaticObject";
 
       // Other bailouts.
       case Bailout_ArgumentCheck:
         return "Bailout_ArgumentCheck";
       case Bailout_BoundsCheck:
         return "Bailout_BoundsCheck";
       case Bailout_Detached:
         return "Bailout_Detached";
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3895,17 +3895,17 @@ LIRGenerator::visitCallBindVar(MCallBind
     define(lir, ins);
 }
 
 void
 LIRGenerator::visitGuardObjectIdentity(MGuardObjectIdentity* ins)
 {
     LGuardObjectIdentity* guard = new(alloc()) LGuardObjectIdentity(useRegister(ins->object()),
                                                                     useRegister(ins->expected()));
-    assignSnapshot(guard, Bailout_ObjectIdentityOrTypeGuard);
+    assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->object());
 }
 
 void
 LIRGenerator::visitGuardClass(MGuardClass* ins)
 {
     LDefinition t = temp();
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -6192,19 +6192,28 @@ jit::PropertyReadNeedsTypeBarrier(JSCont
             if (obj->unknownProperties())
                 break;
 
             HeapTypeSetKey property = obj->property(NameToId(name));
             if (property.maybeTypes()) {
                 TypeSet::TypeList types;
                 if (!property.maybeTypes()->enumerateTypes(&types))
                     break;
-                if (types.length() == 1) {
+                // If there is a single possible type for the property,
+                // optimistically add it to the observed set. Don't do this
+                // for the special uninitialized lexical type, which will
+                // never actually be observed here and will cause problems
+                // downstream during compilation.
+                if (types.length() == 1 &&
+                    (!types[0].isPrimitive() ||
+                     types[0].primitive() != JSVAL_TYPE_MAGIC))
+                {
                     // Note: the return value here is ignored.
                     observed->addType(types[0], GetJitContext()->temp->lifoAlloc());
+                    break;
                 }
                 break;
             }
 
             if (!obj->proto().isObject())
                 break;
             obj = TypeSet::ObjectKey::get(obj->proto().toObject());
         } while (obj);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -11485,39 +11485,47 @@ class MGuardObjectGroup
 };
 
 // Guard on an object's identity, inclusively or exclusively.
 class MGuardObjectIdentity
   : public MBinaryInstruction,
     public SingleObjectPolicy::Data
 {
     bool bailOnEquality_;
-
-    MGuardObjectIdentity(MDefinition* obj, MDefinition* expected, bool bailOnEquality)
+    BailoutKind bailoutKind_;
+
+    MGuardObjectIdentity(MDefinition* obj, MDefinition* expected, bool bailOnEquality,
+                         BailoutKind bailoutKind = Bailout_ObjectIdentityOrTypeGuard)
       : MBinaryInstruction(obj, expected),
-        bailOnEquality_(bailOnEquality)
+        bailOnEquality_(bailOnEquality),
+        bailoutKind_(bailoutKind)
     {
         setGuard();
         setMovable();
         setResultType(MIRType::Object);
     }
 
   public:
     INSTRUCTION_HEADER(GuardObjectIdentity)
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, object), (1, expected))
 
     bool bailOnEquality() const {
         return bailOnEquality_;
     }
+    BailoutKind bailoutKind() const {
+        return bailoutKind_;
+    }
     bool congruentTo(const MDefinition* ins) const override {
         if (!ins->isGuardObjectIdentity())
             return false;
         if (bailOnEquality() != ins->toGuardObjectIdentity()->bailOnEquality())
             return false;
+        if (bailoutKind() != ins->toGuardObjectIdentity()->bailoutKind())
+            return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const override {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Guard on an object's class.
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2106,17 +2106,17 @@ class ConstraintDataConstantProperty
     JSCompartment* maybeCompartment() { return nullptr; }
 };
 
 } /* anonymous namespace */
 
 bool
 HeapTypeSetKey::constant(CompilerConstraintList* constraints, Value* valOut)
 {
-    if (nonData(constraints))
+    if (nonData(constraints) || !object()->isSingleton())
         return false;
 
     // Only singleton object properties can be marked as constants.
     JSObject* obj = object()->singleton();
     if (!obj || !obj->isNative())
         return false;
 
     if (maybeTypes() && maybeTypes()->nonConstantProperty())