Bug 1533890: Migrate call hooks to CacheIR r=mgaudet
authorIain Ireland <iireland@mozilla.com>
Tue, 19 Mar 2019 22:57:54 +0000
changeset 465222 e8af74a64a88d40dfa03cd41346cea716c623aba
parent 465221 1a502b69ad35597b718f6e06893a2bf09a28375c
child 465223 33fdefc8983102790235d45cc19bd1877c14b99e
push id80965
push useriireland@mozilla.com
push dateWed, 20 Mar 2019 14:22:19 +0000
treeherderautoland@d808eb8f5dd5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmgaudet
bugs1533890
milestone68.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 1533890: Migrate call hooks to CacheIR r=mgaudet This patch implements class hooks in CacheIR. It only supports call hooks; constructor calls are handled in a later patch. The implementation of class hooks overlaps almost completely with the implementation of native calls; the only difference is how we load the address of the callee. Comparison points: - The old logic is in TryAttachCallStub and ICCall_ClassHook::Compiler::generateStubCode. Differential Revision: https://phabricator.services.mozilla.com/D22777
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/IonCacheIRCompiler.cpp
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -60,16 +60,19 @@ class MOZ_RAII BaselineCacheIRCompiler :
                                      LiveGeneralRegisterSet saveRegs);
 
   MOZ_MUST_USE bool emitStoreSlotShared(bool isFixed);
   MOZ_MUST_USE bool emitAddAndStoreSlotShared(CacheOp op);
 
   void pushCallArguments(Register argcReg, Register scratch, bool isJitCall,
                          bool isConstructing);
 
+  enum class NativeCallType { Native, ClassHook };
+  bool emitCallNativeShared(NativeCallType callType);
+
  public:
   friend class AutoStubFrame;
 
   BaselineCacheIRCompiler(JSContext* cx, const CacheIRWriter& writer,
                           uint32_t stubDataOffset,
                           BaselineCacheIRStubKind stubKind)
       : CacheIRCompiler(cx, writer, stubDataOffset, Mode::Baseline,
                         StubFieldPolicy::Address),
@@ -82,17 +85,16 @@ class MOZ_RAII BaselineCacheIRCompiler :
   JitCode* compile();
 
   bool makesGCCalls() const { return makesGCCalls_; }
 
  private:
 #define DEFINE_OP(op, ...) MOZ_MUST_USE bool emit##op();
   CACHE_IR_OPS(DEFINE_OP)
 #undef DEFINE_OP
-
   Address stubAddress(uint32_t offset) const {
     return Address(ICStubReg, stubDataOffset_ + offset);
   }
 };
 
 #define DEFINE_SHARED_OP(op)                 \
   bool BaselineCacheIRCompiler::emit##op() { \
     return CacheIRCompiler::emit##op();      \
@@ -2483,35 +2485,35 @@ void BaselineCacheIRCompiler::pushCallAr
     masm.addPtr(Imm32(sizeof(Value)), argPtr);
 
     masm.sub32(Imm32(1), count);
     masm.jump(&loop);
   }
   masm.bind(&done);
 }
 
-bool BaselineCacheIRCompiler::emitCallNativeFunction() {
+bool BaselineCacheIRCompiler::emitCallNativeShared(NativeCallType callType) {
   AutoOutputRegister output(*this);
   AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
 
   Register calleeReg = allocator.useRegister(masm, reader.objOperandId());
   Register argcReg = allocator.useRegister(masm, reader.int32OperandId());
-  bool isCrossRealm = reader.readBool();
+  bool maybeCrossRealm = reader.readBool();
 
   // TODO: support constructors
   bool isConstructing = false;
 
   allocator.discardStack(masm);
 
   // Push a stub frame so that we can perform a non-tail call.
   // Note that this leaves the return address in TailCallReg.
   AutoStubFrame stubFrame(*this);
   stubFrame.enter(masm, scratch);
 
-  if (isCrossRealm) {
+  if (maybeCrossRealm) {
     masm.switchToObjectRealm(calleeReg, scratch);
   }
 
   // Values are on the stack left-to-right. Calling convention wants them
   // right-to-left so duplicate them on the stack in reverse order.
   // |this| and callee are pushed last.
   pushCallArguments(argcReg, scratch, /*isJitCall = */ false, isConstructing);
 
@@ -2538,50 +2540,70 @@ bool BaselineCacheIRCompiler::emitCallNa
 
   // Execute call.
   masm.setupUnalignedABICall(scratch);
   masm.loadJSContext(scratch);
   masm.passABIArg(scratch);
   masm.passABIArg(argcReg);
   masm.passABIArg(scratch2);
 
+  switch (callType) {
+    case NativeCallType::Native: {
 #ifdef JS_SIMULATOR
-  // The simulator requires VM calls to be redirected to a special
-  // swi instruction to handle them, so we store the redirected
-  // pointer in the stub and use that instead of the original one.
-  // (See CacheIRWriter::callNativeFunction.)
-  Address redirectedAddr(stubAddress(reader.stubOffset()));
-  masm.callWithABI(redirectedAddr);
+      // The simulator requires VM calls to be redirected to a special
+      // swi instruction to handle them, so we store the redirected
+      // pointer in the stub and use that instead of the original one.
+      // (See CacheIRWriter::callNativeFunction.)
+      Address redirectedAddr(stubAddress(reader.stubOffset()));
+      masm.callWithABI(redirectedAddr);
 #else
-  bool ignoresReturnValue = reader.readBool();
-  if (ignoresReturnValue) {
-    masm.loadPtr(Address(calleeReg, JSFunction::offsetOfJitInfo()), calleeReg);
-    masm.callWithABI(
-        Address(calleeReg, JSJitInfo::offsetOfIgnoresReturnValueNative()));
-  } else {
-    masm.callWithABI(Address(calleeReg, JSFunction::offsetOfNative()));
+      bool ignoresReturnValue = reader.readBool();
+      if (ignoresReturnValue) {
+        masm.loadPtr(Address(calleeReg, JSFunction::offsetOfJitInfo()),
+                     calleeReg);
+        masm.callWithABI(
+            Address(calleeReg, JSJitInfo::offsetOfIgnoresReturnValueNative()));
+      } else {
+        masm.callWithABI(Address(calleeReg, JSFunction::offsetOfNative()));
+      }
+#endif
+    } break;
+    case NativeCallType::ClassHook: {
+      Address nativeAddr(stubAddress(reader.stubOffset()));
+      masm.callWithABI(nativeAddr);
+    } break;
   }
-#endif
 
   // Test for failure.
   masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
   // Load the return value.
   masm.loadValue(
       Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()),
       output.valueReg());
 
   stubFrame.leave(masm);
 
-  if (isCrossRealm) {
+  if (maybeCrossRealm) {
     masm.switchToBaselineFrameRealm(scratch2);
   }
 
   return true;
 }
+
+bool BaselineCacheIRCompiler::emitCallNativeFunction() {
+  JitSpew(JitSpew_Codegen, __FUNCTION__);
+  return emitCallNativeShared(NativeCallType::Native);
+}
+
+bool BaselineCacheIRCompiler::emitCallClassHook() {
+  JitSpew(JitSpew_Codegen, __FUNCTION__);
+  return emitCallNativeShared(NativeCallType::ClassHook);
+}
+
 bool BaselineCacheIRCompiler::emitCallScriptedFunction() {
   JitSpew(JitSpew_Codegen, __FUNCTION__);
   AutoOutputRegister output(*this);
   AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
 
   Register calleeReg = allocator.useRegister(masm, reader.objOperandId());
   Register argcReg = allocator.useRegister(masm, reader.int32OperandId());
   bool maybeCrossRealm = reader.readBool();
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -5373,16 +5373,55 @@ bool CallIRGenerator::tryAttachCallNativ
   writer.typeMonitorResult();
 
   cacheIRStubKind_ = BaselineCacheIRStubKind::Monitored;
   trackAttached("Call native func");
 
   return true;
 }
 
+bool CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) {
+  if (JitOptions.disableCacheIRCalls) {
+    return false;
+  }
+
+  bool isSpread = IsSpreadCallPC(pc_);
+  if (op_ == JSOP_FUNAPPLY || isSpread) {
+    return false;
+  }
+
+  bool isConstructing = IsConstructorCallPC(pc_);
+  JSNative hook =
+      isConstructing ? calleeObj->constructHook() : calleeObj->callHook();
+  if (!hook) {
+    return false;
+  }
+
+  // TODO: Template objects.
+  MOZ_ASSERT(!isConstructing);
+
+  // Load argc.
+  Int32OperandId argcId(writer.setInputOperandId(0));
+
+  // Load the callee.
+  ValOperandId calleeValId = writer.loadStackValue(argc_ + 1);
+  ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
+
+  // Ensure the callee's class matches the one in this stub.
+  writer.guardAnyClass(calleeObjId, calleeObj->getClass());
+
+  writer.callClassHook(calleeObjId, argcId, hook);
+  writer.typeMonitorResult();
+
+  cacheIRStubKind_ = BaselineCacheIRStubKind::Monitored;
+  trackAttached("Call native func");
+
+  return true;
+}
+
 bool CallIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
 
   // Some opcodes are not yet supported.
   switch (op_) {
     case JSOP_CALL:
     case JSOP_CALL_IGNORES_RV:
       break;
@@ -5391,21 +5430,26 @@ bool CallIRGenerator::tryAttachStub() {
   }
 
   // Only optimize when the mode is Specialized.
   if (mode_ != ICState::Mode::Specialized) {
     return false;
   }
 
   // Ensure callee is a function.
-  if (!callee_.isObject() || !callee_.toObject().is<JSFunction>()) {
-    return false;
-  }
-
-  RootedFunction calleeFunc(cx_, &callee_.toObject().as<JSFunction>());
+  if (!callee_.isObject()) {
+    return false;
+  }
+
+  RootedObject calleeObj(cx_, &callee_.toObject());
+  if (!calleeObj->is<JSFunction>()) {
+    return tryAttachCallHook(calleeObj);
+  }
+
+  RootedFunction calleeFunc(cx_, &calleeObj->as<JSFunction>());
 
   // Check for scripted optimizations.
   if (calleeFunc->isInterpreted() || calleeFunc->isNativeWithJitEntry()) {
     return tryAttachCallScripted(calleeFunc);
   }
 
   // Check for native-function optimizations.
   if (calleeFunc->isNative()) {
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -289,16 +289,17 @@ extern const uint32_t OpLengths[];
   _(CallSetArrayLength, Id, Byte, Id)                                          \
   _(CallProxySet, Id, Id, Field, Byte)                                         \
   _(CallProxySetByValue, Id, Id, Id, Byte)                                     \
   _(CallAddOrUpdateSparseElementHelper, Id, Id, Id, Byte)                      \
   _(CallInt32ToString, Id, Id)                                                 \
   _(CallNumberToString, Id, Id)                                                \
   _(CallScriptedFunction, Id, Id, Byte)                                        \
   _(CallNativeFunction, Id, Id, Byte, IF_SIMULATOR(Field, Byte))               \
+  _(CallClassHook, Id, Id, Byte, Field)                                        \
                                                                                \
   /* The *Result ops load a value into the cache's result register. */         \
   _(LoadFixedSlotResult, Id, Field)                                            \
   _(LoadDynamicSlotResult, Id, Field)                                          \
   _(LoadUnboxedPropertyResult, Id, Byte, Field)                                \
   _(LoadTypedObjectResult, Id, Byte, Byte, Field)                              \
   _(LoadDenseElementResult, Id, Id)                                            \
   _(LoadDenseElementHoleResult, Id, Id)                                        \
@@ -1157,16 +1158,33 @@ class MOZ_RAII CacheIRWriter : public JS
     addStubField(uintptr_t(redirected), StubField::Type::RawWord);
 #else
     // If we are not running in the simulator, we generate different jitcode
     // to find the ignoresReturnValue version of a native function.
     buffer_.writeByte(ignoresReturnValue);
 #endif
   }
 
+  void callClassHook(ObjOperandId calleeId, Int32OperandId argc,
+                     JSNative hook) {
+    writeOpWithOperandId(CacheOp::CallClassHook, calleeId);
+    writeOperandId(argc);
+    buffer_.writeByte(true);  // may be cross-realm
+    void* target = JS_FUNC_TO_DATA_PTR(void*, hook);
+
+#ifdef JS_SIMULATOR
+    // The simulator requires VM calls to be redirected to a special
+    // swi instruction to handle them, so we store the redirected
+    // pointer in the stub and use that instead of the original one.
+    target = Simulator::RedirectNativeFunction(target, Args_General3);
+#endif
+
+    addStubField(uintptr_t(target), StubField::Type::RawWord);
+  }
+
   void megamorphicLoadSlotResult(ObjOperandId obj, PropertyName* name,
                                  bool handleMissing) {
     writeOpWithOperandId(CacheOp::MegamorphicLoadSlotResult, obj);
     addStubField(uintptr_t(name), StubField::Type::String);
     buffer_.writeByte(uint32_t(handleMissing));
   }
   void megamorphicLoadSlotByValueResult(ObjOperandId obj, ValOperandId id,
                                         bool handleMissing) {
@@ -2048,16 +2066,17 @@ class MOZ_RAII CallIRGenerator : public 
 
   bool tryAttachStringSplit();
   bool tryAttachArrayPush();
   bool tryAttachArrayJoin();
   bool tryAttachIsSuspendedGenerator();
   bool tryAttachCallScripted(HandleFunction calleeFunc);
   bool tryAttachSpecialCaseCallNative(HandleFunction calleeFunc);
   bool tryAttachCallNative(HandleFunction calleeFunc);
+  bool tryAttachCallHook(HandleObject calleeObj);
 
   void trackAttached(const char* name);
 
  public:
   CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, JSOp op,
                   ICState::Mode mode, uint32_t argc, HandleValue callee,
                   HandleValue thisval, HandleValueArray args);
 
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -2638,8 +2638,12 @@ bool IonCacheIRCompiler::emitCallStringO
 
 bool IonCacheIRCompiler::emitCallScriptedFunction() {
   MOZ_CRASH("Call ICs not used in ion");
 }
 
 bool IonCacheIRCompiler::emitCallNativeFunction() {
   MOZ_CRASH("Call ICs not used in ion");
 }
+
+bool IonCacheIRCompiler::emitCallClassHook() {
+  MOZ_CRASH("Call ICs not used in ion");
+}