Bug 1467124 - Fix WindowProxy optimizations in the JIT for same-compartment realms. r=bzbarsky
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 03 Jan 2019 08:52:43 +0000
changeset 452379 a97b9b4c385e526301358abadfe1eb9039312d8b
parent 452378 a5c5e322deb7b3764583332a082753b4363c1dde
child 452380 a29627aa1ef5977bff1e93dcd150a274cfe79e15
push id35304
push userdvarga@mozilla.com
push dateThu, 03 Jan 2019 16:24:35 +0000
treeherdermozilla-central@5b837856dca7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1467124
milestone66.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 1467124 - Fix WindowProxy optimizations in the JIT for same-compartment realms. r=bzbarsky Makes the following changes: * The WindowProxy optimizations in the ICs and Ion now guard the WindowProxy's global is the script's global. Other WindowProxies are harder to optimize because of potential security checks based on document.domain. * IsWindowProxyForScriptGlobal was added as helper function to consolidate the logic for this. * Removes the WindowProxy optimization for CCWs. This becomes more complicated in the new world for various reasons and it seems better to focus on getting same-compartment realms working to address that use case. Differential Revision: https://phabricator.services.mozilla.com/D15492
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/IonBuilder.cpp
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -13,16 +13,17 @@
 #include "jit/BaselineIC.h"
 #include "jit/CacheIRSpewer.h"
 #include "vm/SelfHosting.h"
 
 #include "jit/MacroAssembler-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/JSContext-inl.h"
 #include "vm/JSObject-inl.h"
+#include "vm/JSScript-inl.h"
 #include "vm/TypeInference-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
@@ -1106,53 +1107,83 @@ bool GetPropIRGenerator::tryAttachNative
       trackAttached("NativeGetter");
       return true;
     }
   }
 
   MOZ_CRASH("Bad NativeGetPropCacheability");
 }
 
+bool js::jit::IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
+  if (!IsWindowProxy(obj)) {
+    return false;
+  }
+
+  MOZ_ASSERT(obj->getClass() ==
+             script->runtimeFromMainThread()->maybeWindowProxyClass());
+
+  JSObject* window = ToWindowIfWindowProxy(obj);
+
+  // Ion relies on the WindowProxy's group changing (and the group getting
+  // marked as having unknown properties) on navigation. If we ever stop
+  // transplanting same-compartment WindowProxies, this assert will fail and we
+  // need to fix that code.
+  MOZ_ASSERT(window == &obj->nonCCWGlobal());
+
+  // This must be a WindowProxy for a global in this compartment. Else it would
+  // be a cross-compartment wrapper and IsWindowProxy returns false for
+  // those.
+  MOZ_ASSERT(script->compartment() == obj->compartment());
+
+  // Only optimize lookups on the WindowProxy for the current global. Other
+  // WindowProxies in the compartment may require security checks (based on
+  // mutable document.domain). See bug 1516775.
+  return window == &script->global();
+}
+
+// Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
+static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
+                                                  ObjOperandId objId,
+                                                  GlobalObject* windowObj) {
+  writer.guardClass(objId, GuardClassKind::WindowProxy);
+  ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
+  writer.guardSpecificObject(windowObjId, windowObj);
+  return windowObjId;
+}
+
 bool GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
                                               ObjOperandId objId, HandleId id) {
   // Attach a stub when the receiver is a WindowProxy and we can do the lookup
   // on the Window (the global object).
 
-  if (!IsWindowProxy(obj)) {
+  if (!IsWindowProxyForScriptGlobal(script_, obj)) {
     return false;
   }
 
   // If we're megamorphic prefer a generic proxy stub that handles a lot more
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
     return false;
   }
 
-  // This must be a WindowProxy for the current Window/global. Else it would
-  // be a cross-compartment wrapper and IsWindowProxy returns false for
-  // those.
-  MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass());
-  MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global());
-
   // Now try to do the lookup on the Window (the current global).
-  HandleObject windowObj = cx_->global();
+  Handle<GlobalObject*> windowObj = cx_->global();
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
   NativeGetPropCacheability type =
       CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_,
                              resultFlags_, isTemporarilyUnoptimizable_);
   switch (type) {
     case CanAttachNone:
       return false;
 
     case CanAttachReadSlot: {
       maybeEmitIdGuard(id);
-      writer.guardClass(objId, GuardClassKind::WindowProxy);
-
-      ObjOperandId windowObjId = writer.loadObject(windowObj);
+      ObjOperandId windowObjId =
+          GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
       EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
       EmitReadSlotReturn(writer, windowObj, holder, shape);
 
       trackAttached("WindowProxySlot");
       return true;
     }
 
     case CanAttachCallGetter: {
@@ -1172,18 +1203,18 @@ bool GetPropIRGenerator::tryAttachWindow
       // If a |super| access, it is not worth the complexity to attach an IC.
       if (isSuper()) {
         return false;
       }
 
       // Guard the incoming object is a WindowProxy and inline a getter call
       // based on the Window object.
       maybeEmitIdGuard(id);
-      writer.guardClass(objId, GuardClassKind::WindowProxy);
-      ObjOperandId windowObjId = writer.loadObject(windowObj);
+      ObjOperandId windowObjId =
+          GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
       EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId,
                            mode_);
 
       trackAttached("WindowProxyGetter");
       return true;
     }
   }
 
@@ -1220,35 +1251,24 @@ bool GetPropIRGenerator::tryAttachCrossC
   // this-compartment wrapper. This is what will be stored in the IC
   // keep the compartment alive.
   RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
   if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
     cx_->clearPendingException();
     return false;
   }
 
-  bool isWindowProxy = false;
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
 
   // Enter realm of target since some checks have side-effects
   // such as de-lazifying type info.
   {
     AutoRealm ar(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->nonCCWGlobal());
-      unwrapped = cx_->global();
-      MOZ_ASSERT(unwrapped);
-    }
-
     NativeGetPropCacheability canCache =
         CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_,
                                resultFlags_, isTemporarilyUnoptimizable_);
     if (canCache != CanAttachReadSlot) {
       return false;
     }
 
     if (holder) {
@@ -1280,24 +1300,16 @@ bool GetPropIRGenerator::tryAttachCrossC
   // 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,
                           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<SlotReadType::CrossCompartment>(writer, unwrapped, holder,
                                                      shape, unwrappedId);
   EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
 
   trackAttached("CCWSlot");
   return true;
 }
 
@@ -4421,46 +4433,39 @@ bool SetPropIRGenerator::tryAttachMegamo
 }
 
 bool SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
                                               ObjOperandId objId, HandleId id,
                                               ValOperandId rhsId) {
   // Attach a stub when the receiver is a WindowProxy and we can do the set
   // on the Window (the global object).
 
-  if (!IsWindowProxy(obj)) {
+  if (!IsWindowProxyForScriptGlobal(script_, obj)) {
     return false;
   }
 
   // If we're megamorphic prefer a generic proxy stub that handles a lot more
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
     return false;
   }
 
-  // This must be a WindowProxy for the current Window/global. Else it would
-  // be a cross-compartment wrapper and IsWindowProxy returns false for
-  // those.
-  MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass());
-  MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global());
-
   // Now try to do the set on the Window (the current global).
   Handle<GlobalObject*> windowObj = cx_->global();
 
   RootedShape propShape(cx_);
   if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), windowObj, id,
                               isTemporarilyUnoptimizable_, &propShape)) {
     return false;
   }
 
   maybeEmitIdGuard(id);
 
-  writer.guardClass(objId, GuardClassKind::WindowProxy);
-  ObjOperandId windowObjId = writer.loadObject(windowObj);
-
+  ObjOperandId windowObjId =
+      GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
   writer.guardShape(windowObjId, windowObj->lastProperty());
   writer.guardGroupForTypeBarrier(windowObjId, windowObj->group());
   typeCheckInfo_.set(windowObj->group(), id);
 
   EmitStoreSlotAndReturn(writer, windowObjId, windowObj, propShape, rhsId);
 
   trackAttached("WindowProxySlot");
   return true;
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -2087,12 +2087,15 @@ inline ScalarTypeDescr::Type ScalarTypeF
   return ScalarTypeDescr::Type(key >> 1);
 }
 
 inline ReferenceType ReferenceTypeFromSimpleTypeDescrKey(uint32_t key) {
   MOZ_ASSERT(!SimpleTypeDescrKeyIsScalar(key));
   return ReferenceType(key >> 1);
 }
 
+// Returns whether obj is a WindowProxy wrapping the script's global.
+extern bool IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj);
+
 }  // namespace jit
 }  // namespace js
 
 #endif /* jit_CacheIR_h */
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ScopeExit.h"
 
 #include "builtin/Eval.h"
 #include "builtin/TypedObject.h"
 #include "frontend/SourceNotes.h"
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineInspector.h"
+#include "jit/CacheIR.h"
 #include "jit/Ion.h"
 #include "jit/IonControlFlow.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/JitSpewer.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGraph.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/EnvironmentObject.h"
@@ -11859,41 +11860,37 @@ AbortReasonOr<Ok> IonBuilder::getPropAdd
   trackOptimizationSuccess();
   return Ok();
 }
 
 MDefinition* IonBuilder::tryInnerizeWindow(MDefinition* obj) {
   // Try to optimize accesses on outer window proxies (window.foo, for
   // example) to go directly to the inner window, the global.
   //
-  // Callers should be careful not to pass the inner object to getters or
-  // setters that require outerization.
+  // Callers should be careful not to pass the global object to getters or
+  // setters that require the WindowProxy.
 
   if (obj->type() != MIRType::Object) {
     return obj;
   }
 
   TemporaryTypeSet* types = obj->resultTypeSet();
   if (!types) {
     return obj;
   }
 
   JSObject* singleton = types->maybeSingleton();
   if (!singleton) {
     return obj;
   }
 
-  if (!IsWindowProxy(singleton)) {
+  if (!IsWindowProxyForScriptGlobal(script(), singleton)) {
     return obj;
   }
 
-  // This must be a WindowProxy for the current Window/global. Else it'd be
-  // a cross-compartment wrapper and IsWindowProxy returns false for those.
-  MOZ_ASSERT(ToWindowIfWindowProxy(singleton) == &script()->global());
-
   // When we navigate, the WindowProxy is brain transplanted and we'll mark
   // its ObjectGroup as having unknown properties. The type constraint we add
   // here will invalidate JIT code when this happens.
   TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton);
   if (key->hasFlags(constraints(), OBJECT_FLAG_UNKNOWN_PROPERTIES)) {
     return obj;
   }