Bug 1704744 part 7 - Use ShapeProperty more in the CacheIR code. r=jonco
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 15 Apr 2021 10:11:51 +0000
changeset 576199 c50f3fb04aa3baa887716b48bafbb9e5ea58eb0e
parent 576198 642e059b633f2fe06644be7f0681b229c50ecfaf
child 576200 9715e3dfb584fd8507f8a3ddbda7cad2b16fe703
push id141143
push userjdemooij@mozilla.com
push dateThu, 15 Apr 2021 10:14:43 +0000
treeherderautoland@4560ff4a728b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1704744
milestone89.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 1704744 part 7 - Use ShapeProperty more in the CacheIR code. r=jonco Differential Revision: https://phabricator.services.mozilla.com/D111807
js/src/jit/CacheIR.cpp
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -232,23 +232,23 @@ IRGenerator::IRGenerator(JSContext* cx, 
 
 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
                                        jsbytecode* pc, ICState::Mode mode,
                                        CacheKind cacheKind, HandleValue val,
                                        HandleValue idVal)
     : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal) {}
 
 static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
-                               NativeObject* holder, Shape* shape) {
-  if (holder->isFixedSlot(shape->slot())) {
+                               NativeObject* holder, ShapeProperty prop) {
+  if (holder->isFixedSlot(prop.slot())) {
     writer.loadFixedSlotResult(holderId,
-                               NativeObject::getFixedSlotOffset(shape->slot()));
+                               NativeObject::getFixedSlotOffset(prop.slot()));
   } else {
     size_t dynamicSlotOffset =
-        holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+        holder->dynamicSlotIndex(prop.slot()) * sizeof(Value);
     writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
   }
 }
 
 // DOM proxies
 // -----------
 //
 // DOM proxies are proxies that are used to implement various DOM objects like
@@ -457,38 +457,34 @@ static bool IsCacheableProtoChain(Native
     }
     obj = &proto->as<NativeObject>();
   }
   return true;
 }
 #endif
 
 static bool IsCacheableGetPropReadSlot(NativeObject* obj, NativeObject* holder,
-                                       Shape* shape) {
-  MOZ_ASSERT(shape);
+                                       ShapeProperty prop) {
   MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
 
-  return shape->isDataProperty();
+  return prop.isDataProperty();
 }
 
 enum NativeGetPropCacheability {
   CanAttachNone,
   CanAttachReadSlot,
   CanAttachNativeGetter,
   CanAttachScriptedGetter,
 };
 
 static NativeGetPropCacheability IsCacheableGetPropCall(NativeObject* obj,
                                                         NativeObject* holder,
-                                                        Shape* shape) {
-  MOZ_ASSERT(shape);
+                                                        ShapeProperty prop) {
   MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
 
-  ShapeProperty prop = ShapeProperty(shape);
-
   if (!prop.isAccessorProperty()) {
     return CanAttachNone;
   }
 
   JSObject* getterObject = holder->getGetter(prop);
   if (!getterObject || !getterObject->is<JSFunction>()) {
     return CanAttachNone;
   }
@@ -542,32 +538,31 @@ static bool CheckHasNoSuchProperty(JSCon
 
     curObj = curObj->staticPrototype();
   } while (curObj);
 
   return true;
 }
 
 static bool IsCacheableNoProperty(JSContext* cx, NativeObject* obj,
-                                  NativeObject* holder, Shape* shape, jsid id,
+                                  NativeObject* holder, jsid id,
                                   jsbytecode* pc) {
-  MOZ_ASSERT(!shape);
   MOZ_ASSERT(!holder);
 
   // If we're doing a name lookup, we have to throw a ReferenceError.
   if (JSOp(*pc) == JSOp::GetBoundName) {
     return false;
   }
 
   return CheckHasNoSuchProperty(cx, obj, id);
 }
 
 static NativeGetPropCacheability CanAttachNativeGetProp(
     JSContext* cx, JSObject* obj, JS::PropertyKey id, NativeObject** holder,
-    Shape** shape, jsbytecode* pc) {
+    Maybe<ShapeProperty>* shapeProp, jsbytecode* pc) {
   MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
   MOZ_ASSERT(!*holder);
 
   // 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.
   NativeObject* baseHolder = nullptr;
@@ -575,27 +570,27 @@ static NativeGetPropCacheability CanAtta
   if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
     return CanAttachNone;
   }
   auto* nobj = &obj->as<NativeObject>();
 
   if (prop.isNativeProperty()) {
     MOZ_ASSERT(baseHolder);
     *holder = baseHolder;
-    *shape = prop.shapeProperty().shapeDeprecated();
-
-    if (IsCacheableGetPropReadSlot(nobj, *holder, *shape)) {
+    *shapeProp = mozilla::Some(prop.shapeProperty());
+
+    if (IsCacheableGetPropReadSlot(nobj, *holder, shapeProp->ref())) {
       return CanAttachReadSlot;
     }
 
-    return IsCacheableGetPropCall(nobj, *holder, *shape);
+    return IsCacheableGetPropCall(nobj, *holder, shapeProp->ref());
   }
 
   if (!prop.isFound()) {
-    if (IsCacheableNoProperty(cx, nobj, *holder, *shape, id, pc)) {
+    if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) {
       return CanAttachReadSlot;
     }
   }
 
   return CanAttachNone;
 }
 
 static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj,
@@ -832,41 +827,41 @@ static void EmitReadSlotGuard(CacheIRWri
     }
   } else {
     holderId->emplace(objId);
   }
 }
 
 template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
 static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj,
-                               NativeObject* holder, Shape* shape,
+                               NativeObject* holder, Maybe<ShapeProperty> prop,
                                ObjOperandId objId) {
   Maybe<ObjOperandId> holderId;
   EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId,
                                            &holderId);
 
   // Slot access.
   if (holder) {
     MOZ_ASSERT(holderId->valid());
-    EmitLoadSlotResult(writer, *holderId, holder, shape);
+    EmitLoadSlotResult(writer, *holderId, holder, *prop);
   } else {
     MOZ_ASSERT(holderId.isNothing());
     writer.loadUndefinedResult();
   }
 }
 
 static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
                                          NativeObject* obj,
-                                         NativeObject* holder, Shape* shape,
+                                         NativeObject* holder,
+                                         ShapeProperty prop,
                                          ValOperandId receiverId) {
-  ShapeProperty prop = ShapeProperty(shape);
   JSFunction* target = &holder->getGetter(prop)->as<JSFunction>();
   bool sameRealm = cx->realm() == target->realm();
 
-  switch (IsCacheableGetPropCall(obj, holder, shape)) {
+  switch (IsCacheableGetPropCall(obj, holder, prop)) {
     case CanAttachNativeGetter: {
       writer.callNativeGetterResult(receiverId, target, sameRealm);
       writer.returnFromIC();
       break;
     }
     case CanAttachScriptedGetter: {
       writer.callScriptedGetterResult(receiverId, target, sameRealm);
       writer.returnFromIC();
@@ -878,74 +873,73 @@ static void EmitCallGetterResultNoGuards
       MOZ_ASSERT_UNREACHABLE("Can't attach getter");
       break;
   }
 }
 
 // See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter
 // properties
 static void EmitGuardGetterSetterSlot(CacheIRWriter& writer,
-                                      NativeObject* holder, Shape* shape,
+                                      NativeObject* holder, ShapeProperty prop,
                                       ObjOperandId holderId,
                                       bool holderIsConstant = false) {
   // If the holder is guaranteed to be the same object, and it never had a
   // slot holding a GetterSetter mutated or deleted, its Shape will change when
   // that does happen so we don't need to guard on the GetterSetter.
   if (holderIsConstant && !holder->hadGetterSetterChange()) {
     return;
   }
 
-  size_t slot = shape->slot();
+  size_t slot = prop.slot();
   Value slotVal = holder->getSlot(slot);
   MOZ_ASSERT(slotVal.isPrivateGCThing());
 
   if (holder->isFixedSlot(slot)) {
     size_t offset = NativeObject::getFixedSlotOffset(slot);
     writer.guardFixedSlotValue(holderId, offset, slotVal);
   } else {
     size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value);
     writer.guardDynamicSlotValue(holderId, offset, slotVal);
   }
 }
 
 static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
-                                       NativeObject* holder, Shape* shape,
+                                       NativeObject* holder, ShapeProperty prop,
                                        ObjOperandId objId, ICState::Mode mode) {
   // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
   // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
   // require outerizing).
 
   if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
     TestMatchingNativeReceiver(writer, obj, objId);
 
     if (obj != holder) {
       GeneratePrototypeGuards(writer, obj, holder, objId);
 
       // Guard on the holder's shape.
       ObjOperandId holderId = writer.loadObject(holder);
       TestMatchingHolder(writer, holder, holderId);
 
-      EmitGuardGetterSetterSlot(writer, holder, shape, holderId,
+      EmitGuardGetterSetterSlot(writer, holder, prop, holderId,
                                 /* holderIsConstant = */ true);
     } else {
-      EmitGuardGetterSetterSlot(writer, holder, shape, objId);
-    }
-  } else {
-    ShapeProperty prop = ShapeProperty(shape);
+      EmitGuardGetterSetterSlot(writer, holder, prop, objId);
+    }
+  } else {
     GetterSetter* gs = holder->getGetterSetter(prop);
-    writer.guardHasGetterSetter(objId, shape->propid(), gs);
+    writer.guardHasGetterSetter(objId, prop.shapeDeprecated()->propid(), gs);
   }
 }
 
 static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
                                  NativeObject* obj, NativeObject* holder,
-                                 Shape* shape, ObjOperandId objId,
+                                 ShapeProperty prop, ObjOperandId objId,
                                  ValOperandId receiverId, ICState::Mode mode) {
-  EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode);
-  EmitCallGetterResultNoGuards(cx, writer, obj, holder, shape, receiverId);
+  EmitCallGetterResultGuards(writer, obj, holder, prop, objId, mode);
+  EmitCallGetterResultNoGuards(cx, writer, obj, holder, prop, receiverId);
 }
 
 static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type,
                              JSObject* obj, JSFunction* fun,
                              ICState::Mode mode) {
   MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter ||
              type == JSJitInfo::Method);
 
@@ -981,45 +975,44 @@ static bool CanAttachDOMCall(JSContext* 
 
   DOMInstanceClassHasProtoAtDepth instanceChecker =
       cx->runtime()->DOMcallbacks->instanceClassMatchesProto;
   return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth);
 }
 
 static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type,
                                      NativeObject* obj, NativeObject* holder,
-                                     Shape* shape, ICState::Mode mode) {
+                                     ShapeProperty prop, ICState::Mode mode) {
   MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter);
 
-  ShapeProperty prop = ShapeProperty(shape);
   JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop)
                                                  : holder->getSetter(prop);
   JSFunction* fun = &accessor->as<JSFunction>();
 
   return CanAttachDOMCall(cx, type, obj, fun, mode);
 }
 
 static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer,
-                                            NativeObject* holder, Shape* shape,
+                                            NativeObject* holder,
+                                            ShapeProperty prop,
                                             ObjOperandId objId) {
-  ShapeProperty prop = ShapeProperty(shape);
   JSFunction* getter = &holder->getGetter(prop)->as<JSFunction>();
   writer.callDOMGetterResult(objId, getter->jitInfo());
   writer.returnFromIC();
 }
 
 static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer,
                                     NativeObject* obj, NativeObject* holder,
-                                    Shape* shape, ObjOperandId objId) {
+                                    ShapeProperty prop, ObjOperandId objId) {
   // Note: this relies on EmitCallGetterResultGuards emitting a shape guard
   // for specialized stubs.
   // The shape guard ensures the receiver's Class is valid for this DOM getter.
-  EmitCallGetterResultGuards(writer, obj, holder, shape, objId,
+  EmitCallGetterResultGuards(writer, obj, holder, prop, objId,
                              ICState::Mode::Specialized);
-  EmitCallDOMGetterResultNoGuards(writer, holder, shape, objId);
+  EmitCallDOMGetterResultNoGuards(writer, holder, prop, objId);
 }
 
 void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
                                                      jsid id,
                                                      bool handleMissing) {
   MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
 
   if (cacheKind_ == CacheKind::GetProp ||
@@ -1034,54 +1027,54 @@ void GetPropIRGenerator::attachMegamorph
 
   trackAttached("MegamorphicNativeSlot");
 }
 
 AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
                                                    ObjOperandId objId,
                                                    HandleId id,
                                                    ValOperandId receiverId) {
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeObject* holder = nullptr;
 
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
   switch (type) {
     case CanAttachNone:
       return AttachDecision::NoAction;
     case CanAttachReadSlot: {
       auto* nobj = &obj->as<NativeObject>();
 
       if (mode_ == ICState::Mode::Megamorphic) {
         attachMegamorphicNativeSlot(objId, id, holder == nullptr);
         return AttachDecision::Attach;
       }
 
       maybeEmitIdGuard(id);
-      EmitReadSlotResult(writer, nobj, holder, shape, objId);
+      EmitReadSlotResult(writer, nobj, holder, prop, objId);
       writer.returnFromIC();
 
       trackAttached("NativeSlot");
       return AttachDecision::Attach;
     }
     case CanAttachScriptedGetter:
     case CanAttachNativeGetter: {
       auto* nobj = &obj->as<NativeObject>();
 
       maybeEmitIdGuard(id);
 
       if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj,
-                                                 holder, shape, mode_)) {
-        EmitCallDOMGetterResult(cx_, writer, nobj, holder, shape, objId);
+                                                 holder, *prop, mode_)) {
+        EmitCallDOMGetterResult(cx_, writer, nobj, holder, *prop, objId);
 
         trackAttached("DOMGetter");
         return AttachDecision::Attach;
       }
 
-      EmitCallGetterResult(cx_, writer, nobj, holder, shape, objId, receiverId,
+      EmitCallGetterResult(cx_, writer, nobj, holder, *prop, objId, receiverId,
                            mode_);
 
       trackAttached("NativeGetter");
       return AttachDecision::Attach;
     }
   }
 
   MOZ_CRASH("Bad NativeGetPropCacheability");
@@ -1141,39 +1134,38 @@ AttachDecision GetPropIRGenerator::tryAt
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
     return AttachDecision::NoAction;
   }
 
   // Now try to do the lookup on the Window (the current global).
   GlobalObject* windowObj = cx_->global();
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_);
   switch (type) {
     case CanAttachNone:
       return AttachDecision::NoAction;
 
     case CanAttachReadSlot: {
       maybeEmitIdGuard(id);
       ObjOperandId windowObjId =
           GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
-      EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
+      EmitReadSlotResult(writer, windowObj, holder, prop, windowObjId);
       writer.returnFromIC();
 
       trackAttached("WindowProxySlot");
       return AttachDecision::Attach;
     }
 
     case CanAttachNativeGetter: {
       // Make sure the native getter is okay with the IC passing the Window
       // instead of the WindowProxy as |this| value.
-      ShapeProperty prop = ShapeProperty(shape);
-      JSFunction* callee = &holder->getGetter(prop)->as<JSFunction>();
+      JSFunction* callee = &holder->getGetter(*prop)->as<JSFunction>();
       MOZ_ASSERT(callee->isNativeWithoutJitEntry());
       if (!callee->hasJitInfo() ||
           callee->jitInfo()->needsOuterizedThisObject()) {
         return AttachDecision::NoAction;
       }
 
       // If a |super| access, it is not worth the complexity to attach an IC.
       if (isSuper()) {
@@ -1182,23 +1174,23 @@ AttachDecision GetPropIRGenerator::tryAt
 
       // Guard the incoming object is a WindowProxy and inline a getter call
       // based on the Window object.
       maybeEmitIdGuard(id);
       ObjOperandId windowObjId =
           GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
 
       if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder,
-                                   shape, mode_)) {
-        EmitCallDOMGetterResult(cx_, writer, windowObj, holder, shape,
+                                   *prop, mode_)) {
+        EmitCallDOMGetterResult(cx_, writer, windowObj, holder, *prop,
                                 windowObjId);
         trackAttached("WindowProxyDOMGetter");
       } else {
         ValOperandId receiverId = writer.boxObject(windowObjId);
-        EmitCallGetterResult(cx_, writer, windowObj, holder, shape, windowObjId,
+        EmitCallGetterResult(cx_, writer, windowObj, holder, *prop, windowObjId,
                              receiverId, mode_);
         trackAttached("WindowProxyGetter");
       }
 
       return AttachDecision::Attach;
     }
 
     case CanAttachScriptedGetter:
@@ -1238,25 +1230,25 @@ AttachDecision GetPropIRGenerator::tryAt
   // keep the compartment alive.
   RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
   if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
     cx_->clearPendingException();
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
 
   // Enter realm of target to prevent failing compartment assertions when doing
   // the lookup.
   {
     AutoRealm ar(cx_, unwrapped);
 
     NativeGetPropCacheability canCache =
-        CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_);
+        CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_);
     if (canCache != CanAttachReadSlot) {
       return AttachDecision::NoAction;
     }
   }
   auto* unwrappedNative = &unwrapped->as<NativeObject>();
 
   maybeEmitIdGuard(id);
   writer.guardIsProxy(objId);
@@ -1265,18 +1257,18 @@ AttachDecision GetPropIRGenerator::tryAt
   // Load the object wrapped by the CCW
   ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
 
   // If the compartment of the wrapped object is different we should fail.
   writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
                           unwrappedNative->compartment());
 
   ObjOperandId unwrappedId = wrapperTargetId;
-  EmitReadSlotResult<SlotReadType::CrossCompartment>(
-      writer, unwrappedNative, holder, shape, unwrappedId);
+  EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrappedNative,
+                                                     holder, prop, unwrappedId);
   writer.wrapResult();
   writer.returnFromIC();
 
   trackAttached("CCWSlot");
   return AttachDecision::Attach;
 }
 
 static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
@@ -1504,46 +1496,45 @@ AttachDecision GetPropIRGenerator::tryAt
     auto expandoAndGeneration =
         static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
     MOZ_ASSERT(expandoAndGeneration);
     expandoObj = &expandoAndGeneration->expando.toObject();
   }
 
   // Try to do the lookup on the expando object.
   NativeObject* holder = nullptr;
-  Shape* propShape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability canCache =
-      CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &propShape, pc_);
+      CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_);
   if (canCache == CanAttachNone) {
     return AttachDecision::NoAction;
   }
   if (!holder) {
     return AttachDecision::NoAction;
   }
   auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
 
   MOZ_ASSERT(holder == nativeExpandoObj);
 
   maybeEmitIdGuard(id);
   ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
       obj, objId, expandoVal, nativeExpandoObj);
 
   if (canCache == CanAttachReadSlot) {
     // Load from the expando's slots.
-    EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, propShape);
+    EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop);
     writer.returnFromIC();
   } else {
     // Call the getter. Note that we pass objId, the DOM proxy, as |this|
     // and not the expando object.
     MOZ_ASSERT(canCache == CanAttachNativeGetter ||
                canCache == CanAttachScriptedGetter);
-    EmitGuardGetterSetterSlot(writer, nativeExpandoObj, propShape,
-                              expandoObjId);
+    EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
     EmitCallGetterResultNoGuards(cx_, writer, nativeExpandoObj,
-                                 nativeExpandoObj, propShape, receiverId);
+                                 nativeExpandoObj, *prop, receiverId);
   }
 
   trackAttached("DOMProxyExpando");
   return AttachDecision::Attach;
 }
 
 AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(
     Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
@@ -1601,19 +1592,19 @@ AttachDecision GetPropIRGenerator::tryAt
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   JSObject* checkObj = obj->staticPrototype();
   if (!checkObj) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability canCache =
-      CanAttachNativeGetProp(cx_, checkObj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, checkObj, id, &holder, &prop, pc_);
   if (canCache == CanAttachNone) {
     return AttachDecision::NoAction;
   }
   auto* nativeCheckObj = &checkObj->as<NativeObject>();
 
   maybeEmitIdGuard(id);
 
   // Guard that our expando object hasn't started shadowing this property.
@@ -1625,29 +1616,29 @@ AttachDecision GetPropIRGenerator::tryAt
     // getprop.
     GeneratePrototypeGuards(writer, obj, holder, objId);
 
     // Guard on the holder of the property.
     ObjOperandId holderId = writer.loadObject(holder);
     TestMatchingHolder(writer, holder, holderId);
 
     if (canCache == CanAttachReadSlot) {
-      EmitLoadSlotResult(writer, holderId, holder, shape);
+      EmitLoadSlotResult(writer, holderId, holder, *prop);
       writer.returnFromIC();
     } else {
       // EmitCallGetterResultNoGuards expects |obj| to be the object the
       // property is on to do some checks. Since we actually looked at
       // checkObj, and no extra guards will be generated, we can just
       // pass that instead.
       MOZ_ASSERT(canCache == CanAttachNativeGetter ||
                  canCache == CanAttachScriptedGetter);
       MOZ_ASSERT(!isSuper());
-      EmitGuardGetterSetterSlot(writer, holder, shape, holderId,
+      EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
                                 /* holderIsConstant = */ true);
-      EmitCallGetterResultNoGuards(cx_, writer, nativeCheckObj, holder, shape,
+      EmitCallGetterResultNoGuards(cx_, writer, nativeCheckObj, holder, *prop,
                                    receiverId);
     }
   } else {
     // Property was not found on the prototype chain. Deoptimize down to
     // proxy get call.
     MOZ_ASSERT(!isSuper());
     writer.proxyGetResult(objId, id);
     writer.returnFromIC();
@@ -1755,25 +1746,24 @@ AttachDecision GetPropIRGenerator::tryAt
 
   bool isLength = id.isAtom(cx_->names().length);
   bool isByteOffset = id.isAtom(cx_->names().byteOffset);
   if (!isLength && !isByteOffset && !id.isAtom(cx_->names().byteLength)) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
   if (type != CanAttachNativeGetter) {
     return AttachDecision::NoAction;
   }
 
-  ShapeProperty prop = ShapeProperty(shape);
-  JSFunction& fun = holder->getGetter(prop)->as<JSFunction>();
+  JSFunction& fun = holder->getGetter(*prop)->as<JSFunction>();
   if (isLength) {
     if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) {
       return AttachDecision::NoAction;
     }
   } else if (isByteOffset) {
     if (!TypedArrayObject::isOriginalByteOffsetGetter(fun.native())) {
       return AttachDecision::NoAction;
     }
@@ -1783,17 +1773,17 @@ AttachDecision GetPropIRGenerator::tryAt
     }
   }
 
   auto* tarr = &obj->as<TypedArrayObject>();
 
   maybeEmitIdGuard(id);
   // Emit all the normal guards for calling this native, but specialize
   // callNativeGetterResult.
-  EmitCallGetterResultGuards(writer, tarr, holder, shape, objId, mode_);
+  EmitCallGetterResultGuards(writer, tarr, holder, *prop, objId, mode_);
   if (isLength) {
     if (tarr->length() <= INT32_MAX) {
       writer.loadArrayBufferViewLengthInt32Result(objId);
     } else {
       writer.loadArrayBufferViewLengthDoubleResult(objId);
     }
     trackAttached("TypedArrayLength");
   } else if (isByteOffset) {
@@ -1839,39 +1829,38 @@ AttachDecision GetPropIRGenerator::tryAt
   }
 
   // byteOffset and byteLength both throw when the ArrayBuffer is detached.
   if (dv->hasDetachedBuffer()) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
   if (type != CanAttachNativeGetter) {
     return AttachDecision::NoAction;
   }
 
-  ShapeProperty prop = ShapeProperty(shape);
-  auto& fun = holder->getGetter(prop)->as<JSFunction>();
+  auto& fun = holder->getGetter(*prop)->as<JSFunction>();
   if (isByteOffset) {
     if (!DataViewObject::isOriginalByteOffsetGetter(fun.native())) {
       return AttachDecision::NoAction;
     }
   } else {
     if (!DataViewObject::isOriginalByteLengthGetter(fun.native())) {
       return AttachDecision::NoAction;
     }
   }
 
   maybeEmitIdGuard(id);
   // Emit all the normal guards for calling this native, but specialize
   // callNativeGetterResult.
-  EmitCallGetterResultGuards(writer, dv, holder, shape, objId, mode_);
+  EmitCallGetterResultGuards(writer, dv, holder, *prop, objId, mode_);
   writer.guardHasAttachedArrayBuffer(objId);
   if (isByteOffset) {
     if (dv->byteOffset() <= INT32_MAX) {
       writer.arrayBufferViewByteOffsetInt32Result(objId);
     } else {
       writer.arrayBufferViewByteOffsetDoubleResult(objId);
     }
     trackAttached("DataViewByteOffset");
@@ -1904,39 +1893,38 @@ AttachDecision GetPropIRGenerator::tryAt
     return AttachDecision::NoAction;
   }
 
   if (!id.isAtom(cx_->names().byteLength)) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
   if (type != CanAttachNativeGetter) {
     return AttachDecision::NoAction;
   }
 
-  ShapeProperty prop = ShapeProperty(shape);
-  auto& fun = holder->getGetter(prop)->as<JSFunction>();
+  auto& fun = holder->getGetter(*prop)->as<JSFunction>();
   if (buf->is<ArrayBufferObject>()) {
     if (!ArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
       return AttachDecision::NoAction;
     }
   } else {
     if (!SharedArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
       return AttachDecision::NoAction;
     }
   }
 
   maybeEmitIdGuard(id);
   // Emit all the normal guards for calling this native, but specialize
   // callNativeGetterResult.
-  EmitCallGetterResultGuards(writer, buf, holder, shape, objId, mode_);
+  EmitCallGetterResultGuards(writer, buf, holder, *prop, objId, mode_);
   if (buf->byteLength() <= INT32_MAX) {
     writer.loadArrayBufferByteLengthInt32Result(objId);
   } else {
     writer.loadArrayBufferByteLengthDoubleResult(objId);
   }
   writer.returnFromIC();
 
   trackAttached("ArrayBufferMaybeSharedByteLength");
@@ -1956,34 +1944,33 @@ AttachDecision GetPropIRGenerator::tryAt
   }
 
   // Receiver should be the object.
   if (isSuper()) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
   if (type != CanAttachNativeGetter) {
     return AttachDecision::NoAction;
   }
 
-  ShapeProperty prop = ShapeProperty(shape);
-  auto& fun = holder->getGetter(prop)->as<JSFunction>();
+  auto& fun = holder->getGetter(*prop)->as<JSFunction>();
   JS::RegExpFlags flags = JS::RegExpFlag::NoFlags;
   if (!RegExpObject::isOriginalFlagGetter(fun.native(), &flags)) {
     return AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
   // Emit all the normal guards for calling this native, but specialize
   // callNativeGetterResult.
-  EmitCallGetterResultGuards(writer, regExp, holder, shape, objId, mode_);
+  EmitCallGetterResultGuards(writer, regExp, holder, *prop, objId, mode_);
 
   writer.regExpFlagResult(objId, flags.value());
   writer.returnFromIC();
 
   trackAttached("RegExpFlag");
   return AttachDecision::Attach;
 }
 
@@ -2116,17 +2103,17 @@ AttachDecision GetPropIRGenerator::tryAt
     return AttachDecision::NoAction;
   }
 
   // Check for the specific namespace object.
   maybeEmitIdGuard(id);
   writer.guardSpecificObject(objId, ns);
 
   ObjOperandId envId = writer.loadObject(env);
-  EmitLoadSlotResult(writer, envId, env, shape);
+  EmitLoadSlotResult(writer, envId, env, ShapeProperty(shape));
   writer.returnFromIC();
 
   trackAttached("ModuleNamespace");
   return AttachDecision::Attach;
 }
 
 AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId,
                                                       HandleId id) {
@@ -2164,34 +2151,34 @@ AttachDecision GetPropIRGenerator::tryAt
   }
 
   JSObject* proto = cx_->global()->maybeGetPrototype(protoKey);
   if (!proto) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
+  Maybe<ShapeProperty> prop;
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, proto, id, &holder, &shape, pc_);
+      CanAttachNativeGetProp(cx_, proto, id, &holder, &prop, pc_);
   switch (type) {
     case CanAttachNone:
       return AttachDecision::NoAction;
     case CanAttachReadSlot: {
       auto* nproto = &proto->as<NativeObject>();
 
       if (val_.isNumber()) {
         writer.guardIsNumber(valId);
       } else {
         writer.guardNonDoubleType(valId, val_.type());
       }
       maybeEmitIdGuard(id);
 
       ObjOperandId protoId = writer.loadObject(nproto);
-      EmitReadSlotResult(writer, nproto, holder, shape, protoId);
+      EmitReadSlotResult(writer, nproto, holder, prop, protoId);
       writer.returnFromIC();
 
       trackAttached("PrimitiveSlot");
       return AttachDecision::Attach;
     }
     case CanAttachScriptedGetter:
     case CanAttachNativeGetter: {
       auto* nproto = &proto->as<NativeObject>();
@@ -2199,17 +2186,17 @@ AttachDecision GetPropIRGenerator::tryAt
       if (val_.isNumber()) {
         writer.guardIsNumber(valId);
       } else {
         writer.guardNonDoubleType(valId, val_.type());
       }
       maybeEmitIdGuard(id);
 
       ObjOperandId protoId = writer.loadObject(nproto);
-      EmitCallGetterResult(cx_, writer, nproto, holder, shape, protoId, valId,
+      EmitCallGetterResult(cx_, writer, nproto, holder, *prop, protoId, valId,
                            mode_);
 
       trackAttached("PrimitiveGetter");
       return AttachDecision::Attach;
     }
   }
 
   MOZ_CRASH("Bad NativeGetPropCacheability");
@@ -2736,23 +2723,22 @@ AttachDecision GetNameIRGenerator::tryAt
 
   trackAttached(IRGenerator::NotAttached);
   return AttachDecision::NoAction;
 }
 
 static bool CanAttachGlobalName(JSContext* cx,
                                 GlobalLexicalEnvironmentObject* globalLexical,
                                 JS::PropertyKey id, NativeObject** holder,
-                                Shape** shape) {
+                                Maybe<ShapeProperty>* prop) {
   // The property must be found, and it must be found as a normal data property.
   NativeObject* current = globalLexical;
   while (true) {
-    mozilla::Maybe<ShapeProperty> prop = current->lookup(cx, id);
-    if (prop.isSome()) {
-      *shape = prop->shapeDeprecated();
+    *prop = current->lookup(cx, id);
+    if (prop->isSome()) {
       break;
     }
 
     if (current == globalLexical) {
       current = &globalLexical->global();
     } else {
       // In the browser the global prototype chain should be immutable.
       if (!current->staticPrototypeIsImmutable()) {
@@ -2776,43 +2762,43 @@ AttachDecision GetNameIRGenerator::tryAt
                                                             HandleId id) {
   if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
     return AttachDecision::NoAction;
   }
 
   auto* globalLexical = &env_->as<GlobalLexicalEnvironmentObject>();
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
-  if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) {
+  Maybe<ShapeProperty> prop;
+  if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
     return AttachDecision::NoAction;
   }
 
   // The property must be found, and it must be found as a normal data property.
-  if (!shape->isDataProperty()) {
+  if (!prop->isDataProperty()) {
     return AttachDecision::NoAction;
   }
 
   // This might still be an uninitialized lexical.
-  if (holder->getSlot(shape->slot()).isMagic()) {
+  if (holder->getSlot(prop->slot()).isMagic()) {
     return AttachDecision::NoAction;
   }
 
   if (holder == globalLexical) {
     // There is no need to guard on the shape. Lexical bindings are
     // non-configurable, and this stub cannot be shared across globals.
     size_t dynamicSlotOffset =
-        holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+        holder->dynamicSlotIndex(prop->slot()) * sizeof(Value);
     writer.loadDynamicSlotResult(objId, dynamicSlotOffset);
   } else {
     // Check the prototype chain from the global to the holder
     // prototype. Ignore the global lexical scope as it doesn't figure
     // into the prototype chain. We guard on the global lexical
     // scope's shape independently.
-    if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder, shape)) {
+    if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder, *prop)) {
       return AttachDecision::NoAction;
     }
 
     // Shape guard for global lexical.
     writer.guardShape(objId, globalLexical->lastProperty());
 
     // Guard on the shape of the GlobalObject.
     ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
@@ -2820,17 +2806,17 @@ AttachDecision GetNameIRGenerator::tryAt
 
     ObjOperandId holderId = globalId;
     if (holder != &globalLexical->global()) {
       // Shape guard holder.
       holderId = writer.loadObject(holder);
       writer.guardShape(holderId, holder->lastProperty());
     }
 
-    EmitLoadSlotResult(writer, holderId, holder, shape);
+    EmitLoadSlotResult(writer, holderId, holder, *prop);
   }
 
   writer.returnFromIC();
 
   trackAttached("GlobalNameValue");
   return AttachDecision::Attach;
 }
 
@@ -2840,59 +2826,59 @@ AttachDecision GetNameIRGenerator::tryAt
     return AttachDecision::NoAction;
   }
 
   Handle<GlobalLexicalEnvironmentObject*> globalLexical =
       env_.as<GlobalLexicalEnvironmentObject>();
   MOZ_ASSERT(globalLexical->isGlobal());
 
   NativeObject* holder = nullptr;
-  Shape* shape = nullptr;
-  if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) {
+  Maybe<ShapeProperty> prop;
+  if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
     return AttachDecision::NoAction;
   }
 
   if (holder == globalLexical) {
     return AttachDecision::NoAction;
   }
 
   GlobalObject* global = &globalLexical->global();
 
-  if (IsCacheableGetPropCall(global, holder, shape) != CanAttachNativeGetter) {
+  if (IsCacheableGetPropCall(global, holder, *prop) != CanAttachNativeGetter) {
     return AttachDecision::NoAction;
   }
 
   // Shape guard for global lexical.
   writer.guardShape(objId, globalLexical->lastProperty());
 
   // Guard on the shape of the GlobalObject.
   ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
   writer.guardShape(globalId, global->lastProperty());
 
   if (holder != global) {
     // Shape guard holder.
     ObjOperandId holderId = writer.loadObject(holder);
     writer.guardShape(holderId, holder->lastProperty());
-    EmitGuardGetterSetterSlot(writer, holder, shape, holderId,
+    EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
                               /* holderIsConstant = */ true);
   } else {
     // Note: pass true for |holderIsConstant| because the holder must be the
     // current global object.
-    EmitGuardGetterSetterSlot(writer, holder, shape, globalId,
+    EmitGuardGetterSetterSlot(writer, holder, *prop, globalId,
                               /* holderIsConstant = */ true);
   }
 
-  if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, shape,
+  if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, *prop,
                                mode_)) {
     // The global shape guard above ensures the instance JSClass is correct.
-    EmitCallDOMGetterResultNoGuards(writer, holder, shape, globalId);
+    EmitCallDOMGetterResultNoGuards(writer, holder, *prop, globalId);
     trackAttached("GlobalNameDOMGetter");
   } else {
     ValOperandId receiverId = writer.boxObject(globalId);
-    EmitCallGetterResultNoGuards(cx_, writer, global, holder, shape,
+    EmitCallGetterResultNoGuards(cx_, writer, global, holder, *prop,
                                  receiverId);
     trackAttached("GlobalNameGetter");
   }
 
   return AttachDecision::Attach;
 }
 
 static bool NeedEnvironmentShapeGuard(JSObject* envObj) {
@@ -2943,17 +2929,17 @@ AttachDecision GetNameIRGenerator::tryAt
     if (prop.isSome()) {
       break;
     }
 
     env = env->enclosingEnvironment();
   }
 
   holder = &env->as<NativeObject>();
-  if (!IsCacheableGetPropReadSlot(holder, holder, prop->shapeDeprecated())) {
+  if (!IsCacheableGetPropReadSlot(holder, holder, *prop)) {
     return AttachDecision::NoAction;
   }
   if (holder->getSlot(prop->slot()).isMagic()) {
     return AttachDecision::NoAction;
   }
 
   ObjOperandId lastObjId = objId;
   env = env_;
@@ -3620,100 +3606,102 @@ AttachDecision SetPropIRGenerator::tryAt
                                                    rhsValId));
       return AttachDecision::NoAction;
     }
   }
   return AttachDecision::NoAction;
 }
 
 static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId,
-                                   NativeObject* nobj, Shape* shape,
+                                   NativeObject* nobj, ShapeProperty prop,
                                    ValOperandId rhsId) {
-  if (nobj->isFixedSlot(shape->slot())) {
-    size_t offset = NativeObject::getFixedSlotOffset(shape->slot());
+  if (nobj->isFixedSlot(prop.slot())) {
+    size_t offset = NativeObject::getFixedSlotOffset(prop.slot());
     writer.storeFixedSlot(objId, offset, rhsId);
   } else {
-    size_t offset = nobj->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+    size_t offset = nobj->dynamicSlotIndex(prop.slot()) * sizeof(Value);
     writer.storeDynamicSlot(objId, offset, rhsId);
   }
   writer.returnFromIC();
 }
 
-static Shape* LookupShapeForSetSlot(JSOp op, NativeObject* obj, jsid id) {
+static Maybe<ShapeProperty> LookupShapeForSetSlot(JSOp op, NativeObject* obj,
+                                                  jsid id) {
   Maybe<ShapeProperty> prop = obj->lookupPure(id);
   if (prop.isNothing() || !prop->isDataProperty() || !prop->writable()) {
-    return nullptr;
+    return mozilla::Nothing();
   }
 
   // If this is an op like JSOp::InitElem / [[DefineOwnProperty]], the
   // property's attributes may have to be changed too, so make sure it's a
   // simple data property.
   if (IsPropertyInitOp(op) && (!prop->configurable() || !prop->enumerable())) {
-    return nullptr;
-  }
-
-  return prop->shapeDeprecated();
+    return mozilla::Nothing();
+  }
+
+  return prop;
 }
 
 static bool CanAttachNativeSetSlot(JSOp op, JSObject* obj, JS::PropertyKey id,
-                                   Shape** propShape) {
+                                   Maybe<ShapeProperty>* prop) {
   if (!obj->is<NativeObject>()) {
     return false;
   }
 
-  *propShape = LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id);
-  return *propShape;
+  *prop = LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id);
+  return prop->isSome();
 }
 
 // There is no need to guard on the shape. Global lexical bindings are
 // non-configurable and can not be shadowed.
-static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj, Shape* shape) {
+static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj,
+                                    ShapeProperty prop) {
   // Ensure that the env can't change.
   if (op != JSOp::SetGName && op != JSOp::StrictSetGName) {
     return false;
   }
 
   if (!obj->is<GlobalLexicalEnvironmentObject>()) {
     return false;
   }
 
   // Uninitialized let bindings use a RuntimeLexicalErrorObject.
-  MOZ_ASSERT(!obj->getSlot(shape->slot()).isMagic());
-  MOZ_ASSERT(shape->writable());
-  MOZ_ASSERT(!shape->configurable());
+  MOZ_ASSERT(!obj->getSlot(prop.slot()).isMagic());
+  MOZ_ASSERT(prop.writable());
+  MOZ_ASSERT(!prop.configurable());
   return true;
 }
 
 AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
                                                           ObjOperandId objId,
                                                           HandleId id,
                                                           ValOperandId rhsId) {
-  Shape* propShape = nullptr;
-  if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &propShape)) {
+  Maybe<ShapeProperty> prop;
+  if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &prop)) {
     return AttachDecision::NoAction;
   }
 
   // Don't attach a megamorphic store slot stub for ops like JSOp::InitElem.
   if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp &&
       IsPropertySetOp(JSOp(*pc_))) {
     writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(),
                                 rhsId);
     writer.returnFromIC();
     trackAttached("MegamorphicNativeSlot");
     return AttachDecision::Attach;
   }
 
   maybeEmitIdGuard(id);
 
   NativeObject* nobj = &obj->as<NativeObject>();
-  if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, propShape)) {
+  if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, *prop)) {
     TestMatchingNativeReceiver(writer, nobj, objId);
   }
 
-  EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId);
+  EmitStoreSlotAndReturn(writer, objId, nobj, *prop, rhsId);
 
   trackAttached("NativeSlot");
   return AttachDecision::Attach;
 }
 
 OperandId IRGenerator::emitNumericGuard(ValOperandId valId, Scalar::Type type) {
   switch (type) {
     case Scalar::Int8:
@@ -3757,21 +3745,20 @@ void SetPropIRGenerator::trackAttached(c
     sp.valueProperty("base", lhsVal_);
     sp.valueProperty("property", idVal_);
     sp.valueProperty("value", rhsVal_);
   }
 #endif
 }
 
 static bool IsCacheableSetPropCallNative(NativeObject* obj,
-                                         NativeObject* holder, Shape* shape) {
-  MOZ_ASSERT(shape);
+                                         NativeObject* holder,
+                                         ShapeProperty prop) {
   MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
 
-  ShapeProperty prop = ShapeProperty(shape);
   if (!prop.isAccessorProperty()) {
     return false;
   }
 
   JSObject* setterObject = holder->getSetter(prop);
   if (!setterObject || !setterObject->is<JSFunction>()) {
     return false;
   }
@@ -3788,25 +3775,24 @@ static bool IsCacheableSetPropCallNative
   if (setter.hasJitInfo() && !setter.jitInfo()->needsOuterizedThisObject()) {
     return true;
   }
 
   return !IsWindow(obj);
 }
 
 static bool IsCacheableSetPropCallScripted(NativeObject* obj,
-                                           NativeObject* holder, Shape* shape) {
-  MOZ_ASSERT(shape);
+                                           NativeObject* holder,
+                                           ShapeProperty prop) {
   MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
 
   if (IsWindow(obj)) {
     return false;
   }
 
-  ShapeProperty prop = ShapeProperty(shape);
   if (!prop.isAccessorProperty()) {
     return false;
   }
 
   JSObject* setterObject = holder->getSetter(prop);
   if (!setterObject || !setterObject->is<JSFunction>()) {
     return false;
   }
@@ -3817,77 +3803,75 @@ static bool IsCacheableSetPropCallScript
   }
 
   // Scripted functions and natives with JIT entry can use the scripted path.
   return setter.hasJitEntry();
 }
 
 static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, JSObject* obj,
                             JS::PropertyKey id, NativeObject** holder,
-                            Shape** propShape) {
+                            Maybe<ShapeProperty>* shapeProp) {
   // Don't attach a setter stub for ops like JSOp::InitElem.
   MOZ_ASSERT(IsPropertySetOp(JSOp(*pc)));
 
   PropertyResult prop;
   if (!LookupPropertyPure(cx, obj, id, holder, &prop)) {
     return false;
   }
   auto* nobj = &obj->as<NativeObject>();
 
   if (!prop.isNativeProperty()) {
     return false;
   }
 
-  *propShape = prop.shapeProperty().shapeDeprecated();
-  if (!IsCacheableSetPropCallScripted(nobj, *holder, *propShape) &&
-      !IsCacheableSetPropCallNative(nobj, *holder, *propShape)) {
+  if (!IsCacheableSetPropCallScripted(nobj, *holder, prop.shapeProperty()) &&
+      !IsCacheableSetPropCallNative(nobj, *holder, prop.shapeProperty())) {
     return false;
   }
 
+  *shapeProp = mozilla::Some(prop.shapeProperty());
   return true;
 }
 
 static void EmitCallSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
                                    NativeObject* obj, NativeObject* holder,
-                                   Shape* shape, ObjOperandId objId,
+                                   ShapeProperty prop, ObjOperandId objId,
                                    ValOperandId rhsId) {
-  ShapeProperty prop = ShapeProperty(shape);
   JSFunction* target = &holder->getSetter(prop)->as<JSFunction>();
   bool sameRealm = cx->realm() == target->realm();
 
   if (target->isNativeWithoutJitEntry()) {
-    MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, shape));
+    MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, prop));
     writer.callNativeSetter(objId, target, rhsId, sameRealm);
     writer.returnFromIC();
     return;
   }
 
-  MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));
+  MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, prop));
   writer.callScriptedSetter(objId, target, rhsId, sameRealm);
   writer.returnFromIC();
 }
 
 static void EmitCallDOMSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
-                                      NativeObject* holder, Shape* shape,
+                                      NativeObject* holder, ShapeProperty prop,
                                       ObjOperandId objId, ValOperandId rhsId) {
-  ShapeProperty prop = ShapeProperty(shape);
   JSFunction* setter = &holder->getSetter(prop)->as<JSFunction>();
   MOZ_ASSERT(cx->realm() == setter->realm());
 
   writer.callDOMSetter(objId, setter->jitInfo(), rhsId);
   writer.returnFromIC();
 }
 
 AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
                                                    ObjOperandId objId,
                                                    HandleId id,
                                                    ValOperandId rhsId) {
   NativeObject* holder = nullptr;
-  Shape* propShape = nullptr;
-  if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &propShape)) {
+  Maybe<ShapeProperty> prop;
+  if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &prop)) {
     return AttachDecision::NoAction;
   }
   auto* nobj = &obj->as<NativeObject>();
 
   maybeEmitIdGuard(id);
 
   // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
   // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
@@ -3897,36 +3881,35 @@ AttachDecision SetPropIRGenerator::tryAt
 
     if (nobj != holder) {
       GeneratePrototypeGuards(writer, nobj, holder, objId);
 
       // Guard on the holder's shape.
       ObjOperandId holderId = writer.loadObject(holder);
       TestMatchingHolder(writer, holder, holderId);
 
-      EmitGuardGetterSetterSlot(writer, holder, propShape, holderId,
+      EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
                                 /* holderIsConstant = */ true);
     } else {
-      EmitGuardGetterSetterSlot(writer, holder, propShape, objId);
-    }
-  } else {
-    ShapeProperty prop = ShapeProperty(propShape);
-    GetterSetter* gs = holder->getGetterSetter(prop);
+      EmitGuardGetterSetterSlot(writer, holder, *prop, objId);
+    }
+  } else {
+    GetterSetter* gs = holder->getGetterSetter(*prop);
     writer.guardHasGetterSetter(objId, id, gs);
   }
 
-  if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, propShape,
+  if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, *prop,
                                mode_)) {
-    EmitCallDOMSetterNoGuards(cx_, writer, holder, propShape, objId, rhsId);
+    EmitCallDOMSetterNoGuards(cx_, writer, holder, *prop, objId, rhsId);
 
     trackAttached("DOMSetter");
     return AttachDecision::Attach;
   }
 
-  EmitCallSetterNoGuards(cx_, writer, nobj, holder, propShape, objId, rhsId);
+  EmitCallSetterNoGuards(cx_, writer, nobj, holder, *prop, objId, rhsId);
 
   trackAttached("Setter");
   return AttachDecision::Attach;
 }
 
 AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
                                                            ObjOperandId objId,
                                                            HandleId id,
@@ -4276,41 +4259,41 @@ AttachDecision SetPropIRGenerator::tryAt
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   JSObject* proto = obj->staticPrototype();
   if (!proto) {
     return AttachDecision::NoAction;
   }
 
   NativeObject* holder = nullptr;
-  Shape* propShape = nullptr;
-  if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &propShape)) {
+  Maybe<ShapeProperty> prop;
+  if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &prop)) {
     return AttachDecision::NoAction;
   }
   auto* nproto = &proto->as<NativeObject>();
 
   maybeEmitIdGuard(id);
 
   // Guard that our expando object hasn't started shadowing this property.
   TestMatchingProxyReceiver(writer, obj, objId);
   CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
 
   GeneratePrototypeGuards(writer, obj, holder, objId);
 
   // Guard on the holder of the property.
   ObjOperandId holderId = writer.loadObject(holder);
   TestMatchingHolder(writer, holder, holderId);
 
-  EmitGuardGetterSetterSlot(writer, holder, propShape, holderId,
+  EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
                             /* holderIsConstant = */ true);
 
   // EmitCallSetterNoGuards expects |obj| to be the object the property is
   // on to do some checks. Since we actually looked at proto, and no extra
   // guards will be generated, we can just pass that instead.
-  EmitCallSetterNoGuards(cx_, writer, nproto, holder, propShape, objId, rhsId);
+  EmitCallSetterNoGuards(cx_, writer, nproto, holder, *prop, objId, rhsId);
 
   trackAttached("DOMProxyUnshadowed");
   return AttachDecision::Attach;
 }
 
 AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
     Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
     ValOperandId rhsId) {
@@ -4324,45 +4307,44 @@ AttachDecision SetPropIRGenerator::tryAt
     MOZ_ASSERT(!expandoVal.isUndefined(),
                "How did a missing expando manage to shadow things?");
     auto expandoAndGeneration =
         static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
     MOZ_ASSERT(expandoAndGeneration);
     expandoObj = &expandoAndGeneration->expando.toObject();
   }
 
-  Shape* propShape = nullptr;
-  if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &propShape)) {
+  Maybe<ShapeProperty> prop;
+  if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &prop)) {
     auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
 
     maybeEmitIdGuard(id);
     ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
         obj, objId, expandoVal, nativeExpandoObj);
 
-    EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, propShape,
+    EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, *prop,
                            rhsId);
     trackAttached("DOMProxyExpandoSlot");
     return AttachDecision::Attach;
   }
 
   NativeObject* holder = nullptr;
-  if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &propShape)) {
+  if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &prop)) {
     auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
 
     // Call the setter. Note that we pass objId, the DOM proxy, as |this|
     // and not the expando object.
     maybeEmitIdGuard(id);
     ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
         obj, objId, expandoVal, nativeExpandoObj);
 
     MOZ_ASSERT(holder == nativeExpandoObj);
-    EmitGuardGetterSetterSlot(writer, nativeExpandoObj, propShape,
-                              expandoObjId);
+    EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
     EmitCallSetterNoGuards(cx_, writer, nativeExpandoObj, nativeExpandoObj,
-                           propShape, objId, rhsId);
+                           *prop, objId, rhsId);
     trackAttached("DOMProxyExpandoSetter");
     return AttachDecision::Attach;
   }
 
   return AttachDecision::NoAction;
 }
 
 AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj,
@@ -4461,28 +4443,28 @@ AttachDecision SetPropIRGenerator::tryAt
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
     return AttachDecision::NoAction;
   }
 
   // Now try to do the set on the Window (the current global).
   GlobalObject* windowObj = cx_->global();
 
-  Shape* propShape = nullptr;
-  if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &propShape)) {
+  Maybe<ShapeProperty> prop;
+  if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &prop)) {
     return AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
 
   ObjOperandId windowObjId =
       GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
   writer.guardShape(windowObjId, windowObj->lastProperty());
 
-  EmitStoreSlotAndReturn(writer, windowObjId, windowObj, propShape, rhsId);
+  EmitStoreSlotAndReturn(writer, windowObjId, windowObj, *prop, rhsId);
 
   trackAttached("WindowProxySlot");
   return AttachDecision::Attach;
 }
 
 bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) {
   // Special-case JSFunction resolve hook to allow redefining the 'prototype'
   // property without triggering lazy expansion of property and object
@@ -4596,34 +4578,33 @@ AttachDecision SetPropIRGenerator::tryAt
     return AttachDecision::NoAction;
   }
 
   if (!obj->is<NativeObject>()) {
     return AttachDecision::NoAction;
   }
   auto* nobj = &obj->as<NativeObject>();
 
-  Shape* propShape = prop.shapeProperty().shapeDeprecated();
+  ShapeProperty shapeProp = prop.shapeProperty();
   NativeObject* holder = nobj;
 
-  MOZ_ASSERT(propShape);
-
   // The property must be the last added property of the object.
+  Shape* propShape = shapeProp.shapeDeprecated();
   MOZ_RELEASE_ASSERT(holder->lastProperty() == propShape);
 
   // Old shape should be parent of new shape. Object flag updates may make this
   // false even for simple data properties. It may be possible to support these
   // transitions in the future, but ignore now for simplicity.
   if (propShape->previous() != oldShape) {
     return AttachDecision::NoAction;
   }
 
   // Basic shape checks.
-  if (propShape->inDictionary() || !propShape->isDataProperty() ||
-      !propShape->writable()) {
+  if (propShape->inDictionary() || !shapeProp.isDataProperty() ||
+      !shapeProp.writable()) {
     return AttachDecision::NoAction;
   }
 
   ObjOperandId objId = writer.guardToObject(objValId);
   maybeEmitIdGuard(id);
 
   // Shape guard the object.
   writer.guardShape(objId, oldShape);
@@ -4632,22 +4613,22 @@ AttachDecision SetPropIRGenerator::tryAt
   // function is a non-builtin constructor. See canAttachAddSlotStub.
   if (nobj->is<JSFunction>() && id.isAtom(cx_->names().prototype)) {
     MOZ_ASSERT(nobj->as<JSFunction>().isNonBuiltinConstructor());
     writer.guardFunctionIsNonBuiltinCtor(objId);
   }
 
   ShapeGuardProtoChain(writer, nobj, objId);
 
-  if (holder->isFixedSlot(propShape->slot())) {
-    size_t offset = NativeObject::getFixedSlotOffset(propShape->slot());
+  if (holder->isFixedSlot(shapeProp.slot())) {
+    size_t offset = NativeObject::getFixedSlotOffset(shapeProp.slot());
     writer.addAndStoreFixedSlot(objId, offset, rhsValId, propShape);
     trackAttached("AddSlot");
   } else {
-    size_t offset = holder->dynamicSlotIndex(propShape->slot()) * sizeof(Value);
+    size_t offset = holder->dynamicSlotIndex(shapeProp.slot()) * sizeof(Value);
     uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldShape);
     uint32_t numNewSlots = holder->numDynamicSlots();
     if (numOldSlots == numNewSlots) {
       writer.addAndStoreDynamicSlot(objId, offset, rhsValId, propShape);
       trackAttached("AddSlot");
     } else {
       MOZ_ASSERT(numNewSlots > numOldSlots);
       writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, propShape,