Bug 1438556 - Part 1: Use shape guards on the prototype chain for cross-compartment ICs. r=tcampbell, a=RyanVM
authorMatthew Gaudet <mgaudet@mozilla.com>
Mon, 23 Apr 2018 17:25:38 -0400
changeset 473458 df95dfb226bc11073ae15883729aeb39e7fffade
parent 473457 37d3889a39a9672bf365b7ea04c34f6d2fd269ba
child 473459 e5ddad5f46fa12e89a71718ef0a51b068e6f9d58
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell, RyanVM
bugs1438556
milestone61.0
Bug 1438556 - Part 1: Use shape guards on the prototype chain for cross-compartment ICs. r=tcampbell, a=RyanVM This patch unshares some of the property access code between regular and cross-compartment cases, allowing us to use a series of shape-guards on the protochain instead of the previous shape-teleporting checks, that would end up baking in a cross-compartment pointer. This also attempts to clarify the compartment membership situation while doing the cross-compartment IC.
js/src/jit/CacheIR.cpp
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -725,16 +725,43 @@ GeneratePrototypeHoleGuards(CacheIRWrite
 
         // Also make sure there are no dense elements.
         writer.guardNoDenseElements(protoId);
 
         pobj = pobj->staticPrototype();
     }
 }
 
+// Similar to |TestMatchingReceiver|, but for the holder object (when it
+// differs from the receiver). The holder may also be the expando of the
+// receiver if it exists.
+static void
+TestMatchingHolder(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
+{
+    // The GeneratePrototypeGuards + TestMatchingHolder checks only support
+    // prototype chains composed of NativeObject (excluding the receiver
+    // itself).
+    MOZ_ASSERT(obj->is<NativeObject>());
+
+    writer.guardShapeForOwnProperties(objId, obj->as<NativeObject>().lastProperty());
+}
+
+static bool
+UncacheableProtoOnChain(JSObject* obj)
+{
+    while (true) {
+        if (obj->hasUncacheableProto())
+            return true;
+
+        obj = obj->staticPrototype();
+        if (!obj)
+            return false;
+    }
+}
+
 static void
 ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
 {
     while (true) {
         // Guard on the proto if the shape does not imply the proto.
         bool guardProto = obj->hasUncacheableProto();
 
         obj = obj->staticPrototype();
@@ -743,64 +770,91 @@ ShapeGuardProtoChain(CacheIRWriter& writ
 
         objId = writer.loadProto(objId);
         if (guardProto)
             writer.guardSpecificObject(objId, obj);
         writer.guardShape(objId, obj->as<NativeObject>().shape());
     }
 }
 
-// Similar to |TestMatchingReceiver|, but for the holder object (when it
-// differs from the receiver). The holder may also be the expando of the
-// receiver if it exists.
+// For cross compartment guards we shape-guard the prototype chain to avoid
+// referencing the holder object.
+//
+// This peels off the first layer because it's guarded against obj == holder.
 static void
-TestMatchingHolder(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
+ShapeGuardProtoChainForCrossCompartmentHolder(CacheIRWriter& writer, JSObject* obj,
+                                              ObjOperandId objId, JSObject* holder,
+                                              Maybe<ObjOperandId>* holderId)
 {
-    // The GeneratePrototypeGuards + TestMatchingHolder checks only support
-    // prototype chains composed of NativeObject (excluding the receiver
-    // itself).
-    MOZ_ASSERT(obj->is<NativeObject>());
-
-    writer.guardShapeForOwnProperties(objId, obj->as<NativeObject>().lastProperty());
+    MOZ_ASSERT(obj != holder);
+    MOZ_ASSERT(holder);
+    while (true) {
+        obj = obj->staticPrototype();
+        MOZ_ASSERT(obj);
+
+        objId = writer.loadProto(objId);
+        if (obj == holder) {
+            TestMatchingHolder(writer, obj, objId);
+            holderId->emplace(objId);
+            return;
+        } else {
+            writer.guardShapeForOwnProperties(objId, obj->as<NativeObject>().shape());
+        }
+    }
 }
 
+enum class SlotReadType {
+    Normal,
+    CrossCompartment
+};
+
+template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
 static void
 EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
                   ObjOperandId objId, Maybe<ObjOperandId>* holderId)
 {
     Maybe<ObjOperandId> expandoId;
     TestMatchingReceiver(writer, obj, objId, &expandoId);
 
     if (obj != holder) {
         if (holder) {
-            // Guard proto chain integrity.
-            GeneratePrototypeGuards(writer, obj, holder, objId);
-
-            // Guard on the holder's shape.
-            holderId->emplace(writer.loadObject(holder));
-            TestMatchingHolder(writer, holder, holderId->ref());
+            if (MaybeCrossCompartment == SlotReadType::CrossCompartment) {
+                // Guard proto chain integrity.
+                // We use a variant of guards that avoid baking in any cross-compartment
+                // object pointers.
+                ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId, holder,
+                                                              holderId);
+            } else {
+                // Guard proto chain integrity.
+                GeneratePrototypeGuards(writer, obj, holder, objId);
+
+                // Guard on the holder's shape.
+                holderId->emplace(writer.loadObject(holder));
+                TestMatchingHolder(writer, holder, holderId->ref());
+            }
         } else {
             // The property does not exist. Guard on everything in the prototype
             // chain. This is guaranteed to see only Native objects because of
             // CanAttachNativeGetProp().
             ShapeGuardProtoChain(writer, obj, objId);
         }
     } else if (obj->is<UnboxedPlainObject>()) {
         holderId->emplace(*expandoId);
     } else {
         holderId->emplace(objId);
     }
 }
 
+template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
 static void
 EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
                    Shape* shape, ObjOperandId objId)
 {
     Maybe<ObjOperandId> holderId;
-    EmitReadSlotGuard(writer, obj, holder, objId, &holderId);
+    EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId, &holderId);
 
     if (obj == holder && obj->is<UnboxedPlainObject>())
         holder = obj->as<UnboxedPlainObject>().maybeExpando();
 
     // Slot access.
     if (holder) {
         MOZ_ASSERT(holderId->valid());
         EmitLoadSlotResult(writer, *holderId, &holder->as<NativeObject>(), shape);
@@ -1037,70 +1091,87 @@ GetPropIRGenerator::tryAttachCrossCompar
 
     RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
     MOZ_ASSERT(unwrapped == UnwrapOneChecked(obj));
 
     // If we allowed different zones we would have to wrap strings.
     if (unwrapped->compartment()->zone() != cx_->compartment()->zone())
         return false;
 
-    RootedObject wrappedGlobal(cx_, &obj->global());
-    if (!cx_->compartment()->wrap(cx_, &wrappedGlobal))
-        return false;
-
-    AutoCompartment ac(cx_, unwrapped);
-
-    // The first CCW for iframes is almost always wrapping another WindowProxy
-    // so we optimize for that case as well.
-    bool isWindowProxy = IsWindowProxy(unwrapped);
-    if (isWindowProxy) {
-        MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == unwrapped->compartment()->maybeGlobal());
-        unwrapped = cx_->global();
-        MOZ_ASSERT(unwrapped);
-    }
-
+    // Take the unwrapped object's global, and wrap in a
+    // this-compartment wrapper. This is what will be stored in the IC
+    // keep the compartment alive.
+    RootedObject wrappedTargetGlobal(cx_, &unwrapped->global());
+    if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal))
+        return false;
+
+    bool isWindowProxy = false;
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
-    NativeGetPropCacheability canCache =
-        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))
-                preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
-            else
-                preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
+
+    // Enter compartment of target since some checks have side-effects
+    // such as de-lazifying type info.
+    {
+        AutoCompartment ac(cx_, unwrapped);
+
+        // The first CCW for iframes is almost always wrapping another WindowProxy
+        // so we optimize for that case as well.
+        isWindowProxy = IsWindowProxy(unwrapped);
+        if (isWindowProxy) {
+            MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == unwrapped->compartment()->maybeGlobal());
+            unwrapped = cx_->global();
+            MOZ_ASSERT(unwrapped);
+        }
+
+        NativeGetPropCacheability canCache =
+            CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_,
+                                resultFlags_, isTemporarilyUnoptimizable_);
+        if (canCache != CanAttachReadSlot)
+            return false;
+
+        if (holder) {
+            // Need to be in the compartment of the holder to
+            // call EnsureTrackPropertyTypes
+            EnsureTrackPropertyTypes(cx_, holder, id);
+            if (unwrapped == holder) {
+                // See the comment in StripPreliminaryObjectStubs.
+                if (IsPreliminaryObject(unwrapped))
+                    preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
+                else
+                    preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
+            }
+        } else {
+            // UNCACHEABLE_PROTO may result in guards against specific (cross-compartment)
+            // prototype objects, so don't try to attach IC if we see the flag at all.
+            if (UncacheableProtoOnChain(unwrapped)) {
+                return false;
+            }
         }
     }
 
     maybeEmitIdGuard(id);
     writer.guardIsProxy(objId);
     writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
 
     // 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, wrappedGlobal, unwrapped->compartment());
+    writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal, unwrapped->compartment());
 
     ObjOperandId unwrappedId = wrapperTargetId;
     if (isWindowProxy) {
         // For the WindowProxy case also unwrap the inner window.
         // We avoid loadObject, because storing cross compartment objects in
         // stubs / JIT code is tricky.
         writer.guardClass(wrapperTargetId, GuardClassKind::WindowProxy);
         unwrappedId = writer.loadWrapperTarget(wrapperTargetId);
     }
 
-    EmitReadSlotResult(writer, unwrapped, holder, shape, unwrappedId);
+    EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrapped, holder, shape, unwrappedId);
     EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
 
     trackAttached("CCWSlot");
     return true;
 }
 
 static bool
 GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray, MutableHandleObject wrapper)