Bug 1640476 - Warp: Transpile Function.prototype.call. r=jandem
authorTom Schuster <evilpies@gmail.com>
Thu, 28 May 2020 10:30:14 +0000
changeset 596494 14a902630badfa3c7ddbfe3c95fc959eddd6685d
parent 596493 5100f853b479e4f21bc1be3663508588f60ba015
child 596495 e73a92c045b82e25a658e7966dd509f7a24eef53
push id13186
push userffxbld-merge
push dateMon, 01 Jun 2020 09:52:46 +0000
treeherdermozilla-beta@3e7c70a1e4a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1640476
milestone78.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 1640476 - Warp: Transpile Function.prototype.call. r=jandem I changed the CacheIR code to specialize on the actual call target. I think polymorphic targets are actually not totally common, GDocs for example uses hasOwnProperty.call() a lot. When we start supporting guardNotClassConstructor etc. this should just start working as well though. Differential Revision: https://phabricator.services.mozilla.com/D76620
js/src/jit/CacheIR.cpp
js/src/jit/MIRBuilderShared.h
js/src/jit/WarpCacheIRTranspiler.cpp
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -5423,19 +5423,19 @@ AttachDecision CallIRGenerator::tryAttac
   // These functions always return a double so we don't need type monitoring.
   writer.returnFromIC();
   cacheIRStubKind_ = BaselineCacheIRStubKind::Regular;
 
   trackAttached("MathFunction");
   return AttachDecision::Attach;
 }
 
-AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction calleeFunc) {
-  MOZ_ASSERT(calleeFunc->isNative());
-  if (calleeFunc->native() != fun_call) {
+AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) {
+  MOZ_ASSERT(callee->isNative());
+  if (callee->native() != fun_call) {
     return AttachDecision::NoAction;
   }
 
   if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
     return AttachDecision::NoAction;
   }
   RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>());
 
@@ -5446,34 +5446,48 @@ AttachDecision CallIRGenerator::tryAttac
     return AttachDecision::NoAction;
   }
   Int32OperandId argcId(writer.setInputOperandId(0));
 
   // Guard that callee is the |fun_call| native function.
   ValOperandId calleeValId =
       writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
   ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
-  writer.guardSpecificNativeFunction(calleeObjId, fun_call);
-
-  // Guard that |this| is a function.
+  writer.guardSpecificFunction(calleeObjId, callee);
+
+  // Guard that |this| is an object.
   ValOperandId thisValId =
       writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
   ObjOperandId thisObjId = writer.guardToObject(thisValId);
-  writer.guardClass(thisObjId, GuardClassKind::JSFunction);
-
-  // Guard that function is not a class constructor.
-  writer.guardNotClassConstructor(thisObjId);
-
-  CallFlags targetFlags(CallFlags::FunCall);
-  if (isScripted) {
-    writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false);
-    writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+
+  if (mode_ == ICState::Mode::Specialized) {
+    // Ensure that |this| is the expected target function.
+    writer.guardSpecificFunction(thisObjId, target);
+
+    CallFlags targetFlags(CallFlags::FunCall);
+    if (isScripted) {
+      writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+    } else {
+      writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags);
+    }
   } else {
-    writer.guardFunctionIsNative(thisObjId);
-    writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
+    // Guard that |this| is a function.
+    writer.guardClass(thisObjId, GuardClassKind::JSFunction);
+
+    // Guard that function is not a class constructor.
+    writer.guardNotClassConstructor(thisObjId);
+
+    CallFlags targetFlags(CallFlags::FunCall);
+    if (isScripted) {
+      writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false);
+      writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+    } else {
+      writer.guardFunctionIsNative(thisObjId);
+      writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
+    }
   }
 
   writer.typeMonitorResult();
   cacheIRStubKind_ = BaselineCacheIRStubKind::Monitored;
 
   if (isScripted) {
     trackAttached("Scripted fun_call");
   } else {
--- a/js/src/jit/MIRBuilderShared.h
+++ b/js/src/jit/MIRBuilderShared.h
@@ -258,16 +258,18 @@ class MOZ_STACK_CLASS CallInfo {
     return defaultValue;
   }
 
   void setArg(uint32_t i, MDefinition* def) {
     MOZ_ASSERT(i < argc());
     args_[i] = def;
   }
 
+  void removeArg(uint32_t i) { args_.erase(&args_[i]); }
+
   MDefinition* thisArg() const {
     MOZ_ASSERT(thisArg_);
     return thisArg_;
   }
 
   void setThis(MDefinition* thisArg) { thisArg_ = thisArg; }
 
   bool constructing() const { return constructing_; }
--- a/js/src/jit/WarpCacheIRTranspiler.cpp
+++ b/js/src/jit/WarpCacheIRTranspiler.cpp
@@ -1181,24 +1181,43 @@ bool WarpCacheIRTranspiler::emitCallFunc
                                              CallFlags flags, CallKind kind) {
   MDefinition* callee = getOperand(calleeId);
 #ifdef DEBUG
   MDefinition* argc = getOperand(argcId);
   MOZ_ASSERT(argc->toConstant()->toInt32() ==
              static_cast<int32_t>(callInfo_->argc()));
 #endif
 
-  // TODO: For non-normal calls the arguments need to be changed.
-  MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard);
-
   // The transpilation will add various guards to the callee.
   // We replace the callee referenced by the CallInfo, so that
   // the resulting MCall instruction depends on these guards.
   callInfo_->setCallee(callee);
 
+  MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard ||
+             flags.getArgFormat() == CallFlags::FunCall);
+
+  if (flags.getArgFormat() == CallFlags::FunCall) {
+    MOZ_ASSERT(!callInfo_->constructing());
+
+    // Note: setCallee above already changed the callee to the target
+    // function instead of the |call| function.
+
+    if (callInfo_->argc() == 0) {
+      // Special case for fun.call() with no arguments.
+      auto* undef = constant(UndefinedValue());
+      callInfo_->setThis(undef);
+    } else {
+      // The first argument for |call| is the new this value.
+      callInfo_->setThis(callInfo_->getArg(0));
+
+      // Shift down all other arguments by removing the first.
+      callInfo_->removeArg(0);
+    }
+  }
+
   // CacheIR emits the following for specialized calls:
   //     GuardSpecificFunction <callee> <func> ..
   //     Call(Native|Scripted)Function <callee> ..
   // We can use the <func> JSFunction object to specialize this call.
   WrappedFunction* wrappedTarget = nullptr;
   if (callee->isGuardSpecificFunction()) {
     auto* guard = callee->toGuardSpecificFunction();
     JSFunction* target =