Bug 1533890: Add native fun_call support to CacheIR r=mgaudet
authorIain Ireland <iireland@mozilla.com>
Mon, 08 Apr 2019 16:13:31 +0000
changeset 468391 f05cdb03558fb1bdf501bfb77312e864c355ebdf
parent 468390 6d8643e52d18d6850bf29d531696935b840edfe1
child 468392 66ea618f7b136044ceb240b1ed8190f6fe42a4b9
push id35835
push useraciure@mozilla.com
push dateMon, 08 Apr 2019 19:00:29 +0000
treeherdermozilla-central@40456af7da1c [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: Add native fun_call support to CacheIR r=mgaudet This patch adds support for FunCall with a native target, which the old implementation lacked. Notes: 1. MacroAssembler::branchIfInterpreted had been unused for quite a while. I updated it to match MacroAssembler::branchIfFunctionHasNoJitEntry (and check WASM_OPTIMIZED in the non-constructor case). 2. GuardIsNativeFunction was a confusing name, especially after I added GuardFunctionIsNative. I renamed GuardIsNativeFunction to GuardSpecificNativeFunction to better represent what it does. 3. Native function calls in the simulator need to be wrapped up to trigger a software interrupt. To call an arbitrary native function, we need a trampoline, so I added CallAnyNative. Differential Revision: https://phabricator.services.mozilla.com/D25871
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/MacroAssembler-inl.h
js/src/jit/MacroAssembler.h
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -2660,16 +2660,20 @@ bool BaselineCacheIRCompiler::emitCallNa
     case CallFlags::Standard:
       pushCallArguments(argcReg, scratch, scratch2, /*isJitCall =*/false,
                         isConstructing);
       break;
     case CallFlags::Spread:
       pushSpreadCallArguments(argcReg, scratch, scratch2, /*isJitCall =*/false,
                               isConstructing);
       break;
+    case CallFlags::FunCall:
+      pushFunCallArguments(argcReg, calleeReg, scratch, scratch2,
+                           /*isJitCall = */ false);
+      break;
     default:
       MOZ_CRASH("Invalid arg format");
   }
 
   // Native functions have the signature:
   //
   //    bool (*)(JSContext*, unsigned, Value* vp)
   //
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -4713,17 +4713,18 @@ bool CallIRGenerator::tryAttachStringSpl
 
   // Ensure argc == 2.
   writer.guardSpecificInt32Immediate(argcId, 2);
 
   // Ensure callee is the |String_split| native function.
   ValOperandId calleeValId =
       writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_);
   ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
-  writer.guardIsNativeFunction(calleeObjId, js::intrinsic_StringSplitString);
+  writer.guardSpecificNativeFunction(calleeObjId,
+                                     js::intrinsic_StringSplitString);
 
   // Ensure arg0 is a string.
   ValOperandId arg0ValId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
   StringOperandId arg0StrId = writer.guardIsString(arg0ValId);
 
   // Ensure arg1 is a string.
   ValOperandId arg1ValId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
   StringOperandId arg1StrId = writer.guardIsString(arg1ValId);
@@ -4784,17 +4785,17 @@ bool CallIRGenerator::tryAttachArrayPush
 
   // Ensure argc == 1.
   writer.guardSpecificInt32Immediate(argcId, 1);
 
   // Guard callee is the |js::array_push| native function.
   ValOperandId calleeValId =
       writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_);
   ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
-  writer.guardIsNativeFunction(calleeObjId, js::array_push);
+  writer.guardSpecificNativeFunction(calleeObjId, js::array_push);
 
   // Guard this is an array object.
   ValOperandId thisValId = writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
   ObjOperandId thisObjId = writer.guardIsObject(thisValId);
 
   // This is a soft assert, documenting the fact that we pass 'true'
   // for needsTypeBarrier when constructing typeCheckInfo_ for CallIRGenerator.
   // Can be removed safely if the assumption becomes false.
@@ -4858,17 +4859,17 @@ bool CallIRGenerator::tryAttachArrayJoin
 
   // Generate code.
   Int32OperandId argcId(writer.setInputOperandId(0));
 
   // Guard callee is the |js::array_join| native function.
   ValOperandId calleeValId =
       writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_);
   ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
-  writer.guardIsNativeFunction(calleeObjId, js::array_join);
+  writer.guardSpecificNativeFunction(calleeObjId, js::array_join);
 
   if (argc_ == 1) {
     // If argcount is 1, guard that the argument is a string.
     ValOperandId argValId =
         writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
     writer.guardIsString(argValId);
   }
 
@@ -4931,47 +4932,40 @@ bool CallIRGenerator::tryAttachFunCall()
   if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
     return false;
   }
   RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>());
 
   bool isScripted = target->isInterpreted() || target->isNativeWithJitEntry();
   MOZ_ASSERT_IF(!isScripted, target->isNative());
 
-  if (!isScripted) {
-    // TODO: Support fun_call of native functions
-    return false;
-  }
-
   Int32OperandId argcId(writer.setInputOperandId(0));
 
   // Guard that callee is the |fun_call| native function.
   ValOperandId calleeValId =
       writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
   ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
-  writer.guardIsNativeFunction(calleeObjId, fun_call);
+  writer.guardSpecificNativeFunction(calleeObjId, fun_call);
 
   // Guard that |this| is a function.
   ValOperandId thisValId =
       writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
   ObjOperandId thisObjId = writer.guardIsObject(thisValId);
   writer.guardClass(thisObjId, GuardClassKind::JSFunction);
 
+  // Guard that function is not a class constructor.
+  writer.guardNotClassConstructor(thisObjId);
+
   CallFlags targetFlags(CallFlags::FunCall);
   if (isScripted) {
-    // Guard that function is scripted.
     writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false);
-
-    // Guard that function is not a class constructor.
-    writer.guardNotClassConstructor(thisObjId);
-
     writer.callScriptedFunction(thisObjId, argcId, targetFlags);
   } else {
-    // TODO: guard that function is (any?) native.
-    // writer.callNativeFunction(thisObjId, argcId, op_, calleeFunc, flags);
+    writer.guardFunctionIsNative(thisObjId);
+    writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
   }
 
   writer.typeMonitorResult();
   cacheIRStubKind_ = BaselineCacheIRStubKind::Monitored;
 
   if (isScripted) {
     trackAttached("Scripted fun_call");
   } else {
@@ -6451,8 +6445,22 @@ bool NewObjectIRGenerator::tryAttachStub
   writer.guardNoAllocationMetadataBuilder();
   writer.guardObjectGroupNotPretenured(templateObject_->group());
   writer.loadNewObjectFromTemplateResult(templateObject_);
   writer.returnFromIC();
 
   trackAttached("NewObjectWithTemplate");
   return true;
 }
+
+#ifdef JS_SIMULATOR
+bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  RootedObject calleeObj(cx, &args.callee());
+
+  MOZ_ASSERT(calleeObj->is<JSFunction>());
+  RootedFunction calleeFunc(cx, &calleeObj->as<JSFunction>());
+  MOZ_ASSERT(calleeFunc->isNative());
+
+  JSNative native = calleeFunc->native();
+  return native(cx, args.length(), args.base());
+}
+#endif
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -214,25 +214,25 @@ extern const uint32_t ArgLengths[];
   _(GuardType, Id, Byte)                                                       \
   _(GuardShape, Id, Field)                                                     \
   _(GuardGroup, Id, Field)                                                     \
   _(GuardProto, Id, Field)                                                     \
   _(GuardClass, Id, Byte)     /* Guard per GuardClassKind */                   \
   _(GuardAnyClass, Id, Field) /* Guard an arbitrary class */                   \
   _(GuardCompartment, Id, Field, Field)                                        \
   _(GuardIsExtensible, Id)                                                     \
-  _(GuardIsNativeFunction, Id, Word)                                           \
   _(GuardIsNativeObject, Id)                                                   \
   _(GuardIsProxy, Id)                                                          \
   _(GuardHasProxyHandler, Id, Field)                                           \
   _(GuardNotDOMProxy, Id)                                                      \
   _(GuardSpecificObject, Id, Field)                                            \
   _(GuardSpecificAtom, Id, Field)                                              \
   _(GuardSpecificSymbol, Id, Field)                                            \
   _(GuardSpecificInt32Immediate, Id, Int32, Byte)                              \
+  _(GuardSpecificNativeFunction, Id, Word)                                     \
   _(GuardNoDetachedTypedObjects, None)                                         \
   _(GuardMagicValue, Id, Byte)                                                 \
   _(GuardFrameHasNoArgumentsObject, None)                                      \
   _(GuardNoDenseElements, Id)                                                  \
   _(GuardAndGetIndexFromString, Id, Id)                                        \
   _(GuardAndGetNumberFromString, Id, Id)                                       \
   _(GuardAndGetIterator, Id, Id, Field, Field)                                 \
   _(GuardHasGetterSetter, Id, Field)                                           \
@@ -243,16 +243,17 @@ extern const uint32_t ArgLengths[];
   _(GuardIndexIsValidUpdateOrAdd, Id, Id)                                      \
   _(GuardIndexGreaterThanDenseInitLength, Id, Id)                              \
   _(GuardTagNotEqual, Id, Id)                                                  \
   _(GuardXrayExpandoShapeAndDefaultProto, Id, Byte, Field)                     \
   _(GuardFunctionPrototype, Id, Id, Field)                                     \
   _(GuardNoAllocationMetadataBuilder, None)                                    \
   _(GuardObjectGroupNotPretenured, Field)                                      \
   _(GuardFunctionHasJitEntry, Id, Byte)                                        \
+  _(GuardFunctionIsNative, Id)                                                 \
   _(GuardNotClassConstructor, Id)                                              \
   _(LoadObject, Id, Field)                                                     \
   _(LoadProto, Id, Id)                                                         \
   _(LoadEnclosingEnvironment, Id, Id)                                          \
   _(LoadWrapperTarget, Id, Id)                                                 \
   _(LoadValueTag, Id, Id)                                                      \
   _(LoadArgumentFixedSlot, Id, Byte)                                           \
   _(LoadArgumentDynamicSlot, Id, Id, Byte)                                     \
@@ -585,16 +586,20 @@ void LoadShapeWrapperContents(MacroAssem
                               Label* failure);
 
 enum class MetaTwoByteKind : uint8_t {
   NativeTemplateObject,
   ScriptedTemplateObject,
   ClassTemplateObject,
 };
 
+#ifdef JS_SIMULATOR
+bool CallAnyNative(JSContext* cx, unsigned argc, Value* vp);
+#endif
+
 // Class to record CacheIR + some additional metadata for code generation.
 class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
   JSContext* cx_;
   CompactBufferWriter buffer_;
 
   uint32_t nextOperandId_;
   uint32_t nextInstructionId_;
   uint32_t numInputOperands_;
@@ -885,18 +890,21 @@ class MOZ_RAII CacheIRWriter : public JS
                   "GuardClassKind must fit in a byte");
     writeOpWithOperandId(CacheOp::GuardClass, obj);
     buffer_.writeByte(uint32_t(kind));
   }
   FieldOffset guardAnyClass(ObjOperandId obj, const Class* clasp) {
     writeOpWithOperandId(CacheOp::GuardAnyClass, obj);
     return addStubField(uintptr_t(clasp), StubField::Type::RawWord);
   }
-  void guardIsNativeFunction(ObjOperandId obj, JSNative nativeFunc) {
-    writeOpWithOperandId(CacheOp::GuardIsNativeFunction, obj);
+  void guardFunctionIsNative(ObjOperandId obj) {
+    writeOpWithOperandId(CacheOp::GuardFunctionIsNative, obj);
+  }
+  void guardSpecificNativeFunction(ObjOperandId obj, JSNative nativeFunc) {
+    writeOpWithOperandId(CacheOp::GuardSpecificNativeFunction, obj);
     writePointer(JS_FUNC_TO_DATA_PTR(void*, nativeFunc));
   }
   void guardIsNativeObject(ObjOperandId obj) {
     writeOpWithOperandId(CacheOp::GuardIsNativeObject, obj);
   }
   void guardIsProxy(ObjOperandId obj) {
     writeOpWithOperandId(CacheOp::GuardIsProxy, obj);
   }
@@ -1298,16 +1306,37 @@ 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 callAnyNativeFunction(ObjOperandId calleeId, Int32OperandId argc,
+                             CallFlags flags) {
+    MOZ_ASSERT(!flags.isSameRealm());
+    writeOpWithOperandId(CacheOp::CallNativeFunction, calleeId);
+    writeOperandId(argc);
+    writeCallFlags(flags);
+#ifdef JS_SIMULATOR
+    // The simulator requires native calls to be redirected to a
+    // special swi instruction. If we are calling an arbitrary native
+    // function, we can't wrap the real target ahead of time, so we
+    // call a wrapper function (CallAnyNative) that calls the target
+    // itself, and redirect that wrapper.
+    JSNative target = CallAnyNative;
+    void* rawPtr = JS_FUNC_TO_DATA_PTR(void*, target);
+    void* redirected = Simulator::RedirectNativeFunction(rawPtr, Args_General3);
+    addStubField(uintptr_t(redirected), StubField::Type::RawWord);
+#else
+    buffer_.writeByte(/*ignoresReturnValue = */ false);
+#endif
+  }
+
   void callClassHook(ObjOperandId calleeId, Int32OperandId argc, JSNative hook,
                      CallFlags flags) {
     writeOpWithOperandId(CacheOp::CallClassHook, calleeId);
     writeOperandId(argc);
     MOZ_ASSERT(!flags.isSameRealm());
     writeCallFlags(flags);
     void* target = JS_FUNC_TO_DATA_PTR(void*, hook);
 
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1685,17 +1685,17 @@ bool CacheIRCompiler::emitGuardIsExtensi
 
   // Spectre-style checks are not needed here because we do not
   // interpret data based on this check.
   masm.branch32(Assembler::Equal, scratch, Imm32(js::BaseShape::NOT_EXTENSIBLE),
                 failure->label());
   return true;
 }
 
-bool CacheIRCompiler::emitGuardIsNativeFunction() {
+bool CacheIRCompiler::emitGuardSpecificNativeFunction() {
   JitSpew(JitSpew_Codegen, __FUNCTION__);
   Register obj = allocator.useRegister(masm, reader.objOperandId());
   JSNative nativeFunc = reinterpret_cast<JSNative>(reader.pointer());
   AutoScratchRegister scratch(allocator, masm);
 
   FailurePath* failure;
   if (!addFailurePath(&failure)) {
     return false;
@@ -3053,16 +3053,31 @@ bool CacheIRCompiler::emitGuardFunctionH
   if (!addFailurePath(&failure)) {
     return false;
   }
 
   masm.branchIfFunctionHasNoJitEntry(fun, isConstructing, failure->label());
   return true;
 }
 
+bool CacheIRCompiler::emitGuardFunctionIsNative() {
+  JitSpew(JitSpew_Codegen, __FUNCTION__);
+  Register obj = allocator.useRegister(masm, reader.objOperandId());
+  AutoScratchRegister scratch(allocator, masm);
+
+  FailurePath* failure;
+  if (!addFailurePath(&failure)) {
+    return false;
+  }
+
+  // Ensure obj is not an interpreted function.
+  masm.branchIfInterpreted(obj, /*isConstructing =*/false, failure->label());
+  return true;
+}
+
 bool CacheIRCompiler::emitGuardNotClassConstructor() {
   Register fun = allocator.useRegister(masm, reader.objOperandId());
   AutoScratchRegister scratch(allocator, masm);
 
   FailurePath* failure;
   if (!addFailurePath(&failure)) {
     return false;
   }
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -29,17 +29,18 @@ namespace jit {
   _(GuardIsBigInt)                        \
   _(GuardIsNumber)                        \
   _(GuardIsInt32)                         \
   _(GuardIsInt32Index)                    \
   _(GuardType)                            \
   _(GuardClass)                           \
   _(GuardGroupHasUnanalyzedNewScript)     \
   _(GuardIsExtensible)                    \
-  _(GuardIsNativeFunction)                \
+  _(GuardFunctionIsNative)                \
+  _(GuardSpecificNativeFunction)          \
   _(GuardFunctionPrototype)               \
   _(GuardIsNativeObject)                  \
   _(GuardIsProxy)                         \
   _(GuardNotDOMProxy)                     \
   _(GuardSpecificInt32Immediate)          \
   _(GuardMagicValue)                      \
   _(GuardNoDetachedTypedObjects)          \
   _(GuardNoDenseElements)                 \
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -364,21 +364,27 @@ void MacroAssembler::branchIfFunctionHas
   int32_t bit = JSFunction::INTERPRETED;
   if (!isConstructing) {
     bit |= JSFunction::WASM_JIT_ENTRY;
   }
   bit = IMM32_16ADJ(bit);
   branchTest32(Assembler::Zero, address, Imm32(bit), label);
 }
 
-void MacroAssembler::branchIfInterpreted(Register fun, Label* label) {
+void MacroAssembler::branchIfInterpreted(Register fun, bool isConstructing,
+                                         Label* label) {
   // 16-bit loads are slow and unaligned 32-bit loads may be too so
   // perform an aligned 32-bit load and adjust the bitmask accordingly.
+
   Address address(fun, JSFunction::offsetOfNargs());
-  int32_t bit = IMM32_16ADJ(JSFunction::INTERPRETED);
+  int32_t bit = JSFunction::INTERPRETED | JSFunction::INTERPRETED_LAZY;
+  if (!isConstructing) {
+    bit |= JSFunction::WASM_JIT_ENTRY;
+  }
+  bit = IMM32_16ADJ(bit);
   branchTest32(Assembler::NonZero, address, Imm32(bit), label);
 }
 
 void MacroAssembler::branchIfObjectEmulatesUndefined(Register objReg,
                                                      Register scratch,
                                                      Label* slowCheck,
                                                      Label* label) {
   // The branches to out-of-line code here implement a conservative version
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1292,17 +1292,18 @@ class MacroAssembler : public MacroAssem
 
   inline void branchIfNotRope(Register str, Label* label);
 
   inline void branchLatin1String(Register string, Label* label);
   inline void branchTwoByteString(Register string, Label* label);
 
   inline void branchIfFunctionHasNoJitEntry(Register fun, bool isConstructing,
                                             Label* label);
-  inline void branchIfInterpreted(Register fun, Label* label);
+  inline void branchIfInterpreted(Register fun, bool isConstructing,
+                                  Label* label);
 
   inline void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind,
                                  Register fun, Register scratch, Label* label);
 
   void branchIfNotInterpretedConstructor(Register fun, Register scratch,
                                          Label* label);
 
   inline void branchIfObjectEmulatesUndefined(Register objReg, Register scratch,