# HG changeset patch # User Benjamin Bouvier # Date 1518097023 -3600 # Thu Feb 08 14:37:03 2018 +0100 # Node ID cb6130865cacf4b4b9e56e303b2072b531605bbe # Parent d2a68aac6e214c0d14d7684e5bc6009b148e4dd6 Bug 1319203: Implement the jit-to-wasm entry stub and use it; r=luke, r=jandem MozReview-Commit-ID: DSsR3zY4bsd diff --git a/js/src/jit/BaselineCacheIRCompiler.cpp b/js/src/jit/BaselineCacheIRCompiler.cpp --- a/js/src/jit/BaselineCacheIRCompiler.cpp +++ b/js/src/jit/BaselineCacheIRCompiler.cpp @@ -661,17 +661,17 @@ BaselineCacheIRCompiler::emitCallScripte // First, ensure our getter is non-lazy. { FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.loadPtr(getterAddr, callee); - masm.branchIfFunctionHasNoScript(callee, failure->label()); + masm.branchIfFunctionHasNoJitEntry(callee, /* constructing */ false, failure->label()); masm.loadJitCodeRaw(callee, code); } allocator.discardStack(masm); AutoStubFrame stubFrame(*this); stubFrame.enter(masm, scratch); @@ -1761,17 +1761,17 @@ BaselineCacheIRCompiler::emitCallScripte // First, ensure our setter is non-lazy. This also loads the callee in // scratch1. { FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.loadPtr(setterAddr, scratch1); - masm.branchIfFunctionHasNoScript(scratch1, failure->label()); + masm.branchIfFunctionHasNoJitEntry(scratch1, /* constructing */ false, failure->label()); } allocator.discardStack(masm); AutoStubFrame stubFrame(*this); stubFrame.enter(masm, scratch2); // Align the stack such that the JitFrameLayout is aligned on diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -1663,21 +1663,19 @@ TryAttachFunApplyStub(JSContext* cx, ICC { if (argc != 2) return true; if (!thisv.isObject() || !thisv.toObject().is()) return true; RootedFunction target(cx, &thisv.toObject().as()); - bool isScripted = target->hasScript(); - // right now, only handle situation where second argument is |arguments| if (argv[1].isMagic(JS_OPTIMIZED_ARGUMENTS) && !script->needsArgsObj()) { - if (isScripted && !stub->hasStub(ICStub::Call_ScriptedApplyArguments)) { + if (target->hasJitEntry() && !stub->hasStub(ICStub::Call_ScriptedApplyArguments)) { JitSpew(JitSpew_BaselineIC, " Generating Call_ScriptedApplyArguments stub"); ICCall_ScriptedApplyArguments::Compiler compiler( cx, typeMonitorFallback->firstMonitorStub(), script->pcToOffset(pc)); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) return false; @@ -1685,17 +1683,17 @@ TryAttachFunApplyStub(JSContext* cx, ICC *attached = true; return true; } // TODO: handle FUNAPPLY for native targets. } if (argv[1].isObject() && argv[1].toObject().is()) { - if (isScripted && !stub->hasStub(ICStub::Call_ScriptedApplyArray)) { + if (target->hasJitEntry() && !stub->hasStub(ICStub::Call_ScriptedApplyArray)) { JitSpew(JitSpew_BaselineIC, " Generating Call_ScriptedApplyArray stub"); ICCall_ScriptedApplyArray::Compiler compiler( cx, typeMonitorFallback->firstMonitorStub(), script->pcToOffset(pc)); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) return false; @@ -1717,17 +1715,18 @@ TryAttachFunCallStub(JSContext* cx, ICCa *attached = false; if (!thisv.isObject() || !thisv.toObject().is()) return true; RootedFunction target(cx, &thisv.toObject().as()); // Attach a stub if the script can be Baseline-compiled. We do this also // if the script is not yet compiled to avoid attaching a CallNative stub // that handles everything, even after the callee becomes hot. - if (target->hasScript() && target->nonLazyScript()->canBaselineCompile() && + if (((target->hasScript() && target->nonLazyScript()->canBaselineCompile()) || + (target->isNativeWithJitEntry())) && !stub->hasStub(ICStub::Call_ScriptedFunCall)) { JitSpew(JitSpew_BaselineIC, " Generating Call_ScriptedFunCall stub"); ICCall_ScriptedFunCall::Compiler compiler(cx, typeMonitorFallback->firstMonitorStub(), script->pcToOffset(pc)); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) @@ -2007,31 +2006,32 @@ TryAttachCallStub(JSContext* cx, ICCall_ return true; } } return true; } RootedFunction fun(cx, &obj->as()); - if (fun->isInterpreted()) { + bool nativeWithJitEntry = fun->isNativeWithJitEntry(); + if (fun->isInterpreted() || nativeWithJitEntry) { // Never attach optimized scripted call stubs for JSOP_FUNAPPLY. // MagicArguments may escape the frame through them. if (op == JSOP_FUNAPPLY) return true; // If callee is not an interpreted constructor, we have to throw. if (constructing && !fun->isConstructor()) return true; // Likewise, if the callee is a class constructor, we have to throw. if (!constructing && fun->isClassConstructor()) return true; - if (!fun->hasScript()) { + if (!fun->hasJitEntry()) { // Don't treat this as an unoptimizable case, as we'll add a stub // when the callee is delazified. *handled = true; return true; } // If we're constructing, require the callee to have JIT code. This // isn't required for correctness but avoids allocating a template @@ -2111,20 +2111,27 @@ TryAttachCallStub(JSContext* cx, ICCall_ JSObject* thisObject = CreateThisForFunction(cx, fun, newTarget, TenuredObject); if (!thisObject) return false; if (thisObject->is() || thisObject->is()) templateObject = thisObject; } - JitSpew(JitSpew_BaselineIC, - " Generating Call_Scripted stub (fun=%p, %s:%zu, cons=%s, spread=%s)", - fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno(), - constructing ? "yes" : "no", isSpread ? "yes" : "no"); + if (nativeWithJitEntry) { + JitSpew(JitSpew_BaselineIC, + " Generating Call_Scripted stub (native=%p with jit entry, cons=%s, spread=%s)", + fun->native(), constructing ? "yes" : "no", isSpread ? "yes" : "no"); + } else { + JitSpew(JitSpew_BaselineIC, + " Generating Call_Scripted stub (fun=%p, %s:%zu, cons=%s, spread=%s)", + fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno(), + constructing ? "yes" : "no", isSpread ? "yes" : "no"); + } + ICCallScriptedCompiler compiler(cx, typeMonitorFallback->firstMonitorStub(), fun, templateObject, constructing, isSpread, script->pcToOffset(pc)); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) return false; stub->addNewStub(newStub); @@ -2644,17 +2651,17 @@ ICCallStubCompiler::guardFunApply(MacroA // Limit the length to something reasonable (huge number of arguments can // blow the stack limit). masm.branch32(Assembler::Above, lenReg, Imm32(ICCall_ScriptedApplyArray::MAX_ARGS_ARRAY_LENGTH), failure); // Ensure no holes. Loop through values in array and make sure none are magic. // Start address is secondArgObj, end address is secondArgObj + (lenReg * sizeof(Value)) - JS_STATIC_ASSERT(sizeof(Value) == 8); + static_assert(sizeof(Value) == 8, "shift by 3 below assumes Value is 8 bytes"); masm.lshiftPtr(Imm32(3), lenReg); masm.addPtr(secondArgObj, lenReg); Register start = secondArgObj; Register end = lenReg; Label loop; Label endLoop; masm.bind(&loop); @@ -2691,17 +2698,17 @@ ICCallStubCompiler::guardFunApply(MacroA Register target = masm.extractObject(val, ExtractTemp1); regs.add(val); regs.takeUnchecked(target); masm.branchTestObjClass(Assembler::NotEqual, target, regs.getAny(), &JSFunction::class_, failure); Register temp = regs.takeAny(); - masm.branchIfFunctionHasNoScript(target, failure); + masm.branchIfFunctionHasNoJitEntry(target, /* constructing */ false, failure); masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee, temp, failure); regs.add(temp); return target; } void ICCallStubCompiler::pushCallerArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs) { @@ -2932,25 +2939,25 @@ ICCallScriptedCompiler::generateStubCode if (callee_) { MOZ_ASSERT(kind == ICStub::Call_Scripted); // Check if the object matches this callee. Address expectedCallee(ICStubReg, ICCall_Scripted::offsetOfCallee()); masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure); // Guard against relazification. - masm.branchIfFunctionHasNoScript(callee, &failure); + masm.branchIfFunctionHasNoJitEntry(callee, isConstructing_, &failure); } else { // Ensure the object is a function. masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, &failure); if (isConstructing_) { masm.branchIfNotInterpretedConstructor(callee, regs.getAny(), &failure); } else { - masm.branchIfFunctionHasNoScript(callee, &failure); + masm.branchIfFunctionHasNoJitEntry(callee, /* constructing */ false, &failure); masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee, regs.getAny(), &failure); } } // Load the start of the target JitCode. Register code; if (!isConstructing_) { @@ -3565,17 +3572,17 @@ ICCall_ScriptedApplyArray::Compiler::gen { // Call the arguments rectifier. TrampolinePtr argumentsRectifier = cx->runtime()->jitRuntime()->getArgumentsRectifier(); masm.movePtr(argumentsRectifier, target); } masm.bind(&noUnderflow); regs.add(argcReg); - // Do call + // Do call. masm.callJit(target); leaveStubFrame(masm, true); // Enter type monitor IC to type-check result. EmitEnterTypeMonitorIC(masm); masm.bind(&failure); EmitStubGuardFailure(masm); @@ -3693,26 +3700,26 @@ ICCall_ScriptedFunCall::Compiler::genera masm.branchTestObject(Assembler::NotEqual, R1, &failure); Register callee = masm.extractObject(R1, ExtractTemp0); masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, &failure); masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrEnv()), callee); masm.branchPtr(Assembler::NotEqual, callee, ImmPtr(fun_call), &failure); - // Ensure |this| is a scripted function with JIT code. + // Ensure |this| is a function with a jit entry. BaseIndex thisSlot(masm.getStackPointer(), argcReg, TimesEight, ICStackValueOffset); masm.loadValue(thisSlot, R1); masm.branchTestObject(Assembler::NotEqual, R1, &failure); callee = masm.extractObject(R1, ExtractTemp0); masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, &failure); - masm.branchIfFunctionHasNoScript(callee, &failure); + masm.branchIfFunctionHasNoJitEntry(callee, /* constructing */ false, &failure); masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee, regs.getAny(), &failure); // Load the start of the target JitCode. Register code = regs.takeAny(); masm.loadJitCodeRaw(callee, code); // We no longer need R1. diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -339,17 +339,17 @@ IsCacheableGetPropCallNative(JSObject* o if (!shape->hasGetterValue() || !shape->getterValue().isObject()) return false; if (!shape->getterValue().toObject().is()) return false; JSFunction& getter = shape->getterValue().toObject().as(); - if (!getter.isNative()) + if (!getter.isNativeWithCppEntry()) return false; if (getter.isClassConstructor()) return false; // Check for a getter that has jitinfo and whose jitinfo says it's // OK with both inner and outer objects. if (getter.hasJitInfo() && !getter.jitInfo()->needsOuterizedThisObject()) @@ -374,18 +374,22 @@ IsCacheableGetPropCallScripted(JSObject* if (!shape->getterValue().toObject().is()) return false; // See IsCacheableGetPropCallNative. if (IsWindow(obj)) return false; JSFunction& getter = shape->getterValue().toObject().as(); - if (getter.isNative()) - return false; + if (getter.isNativeWithCppEntry()) + return false; + + // Natives with jit entry can use the scripted path. + if (getter.isNativeWithJitEntry()) + return true; if (!getter.hasScript()) { if (isTemporarilyUnoptimizable) *isTemporarilyUnoptimizable = true; return false; } if (getter.isClassConstructor()) @@ -884,26 +888,26 @@ EmitReadSlotReturn(CacheIRWriter& writer } static void EmitCallGetterResultNoGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId receiverId) { if (IsCacheableGetPropCallNative(obj, holder, shape)) { JSFunction* target = &shape->getterValue().toObject().as(); - MOZ_ASSERT(target->isNative()); + MOZ_ASSERT(target->isNativeWithCppEntry()); writer.callNativeGetterResult(receiverId, target); writer.typeMonitorResult(); return; } MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape)); JSFunction* target = &shape->getterValue().toObject().as(); - MOZ_ASSERT(target->hasScript()); + MOZ_ASSERT(target->hasJitEntry()); writer.callScriptedGetterResult(receiverId, target); writer.typeMonitorResult(); } static void EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId, ObjOperandId receiverId, ICState::Mode mode) { @@ -3423,17 +3427,17 @@ IsCacheableSetPropCallNative(JSObject* o if (!shape->hasSetterValue()) return false; if (!shape->setterObject() || !shape->setterObject()->is()) return false; JSFunction& setter = shape->setterObject()->as(); - if (!setter.isNative()) + if (!setter.isNativeWithCppEntry()) return false; if (setter.isClassConstructor()) return false; if (setter.hasJitInfo() && !setter.jitInfo()->needsOuterizedThisObject()) return true; @@ -3452,18 +3456,22 @@ IsCacheableSetPropCallScripted(JSObject* if (!shape->hasSetterValue()) return false; if (!shape->setterObject() || !shape->setterObject()->is()) return false; JSFunction& setter = shape->setterObject()->as(); - if (setter.isNative()) - return false; + if (setter.isNativeWithCppEntry()) + return false; + + // Natives with jit entry can use the scripted path. + if (setter.isNativeWithJitEntry()) + return true; if (!setter.hasScript()) { if (isTemporarilyUnoptimizable) *isTemporarilyUnoptimizable = true; return false; } if (setter.isClassConstructor()) @@ -3498,26 +3506,26 @@ CanAttachSetter(JSContext* cx, jsbytecod } static void EmitCallSetterNoGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId, ValOperandId rhsId) { if (IsCacheableSetPropCallNative(obj, holder, shape)) { JSFunction* target = &shape->setterValue().toObject().as(); - MOZ_ASSERT(target->isNative()); + MOZ_ASSERT(target->isNativeWithCppEntry()); writer.callNativeSetter(objId, target, rhsId); writer.returnFromIC(); return; } MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape)); JSFunction* target = &shape->setterValue().toObject().as(); - MOZ_ASSERT(target->hasScript()); + MOZ_ASSERT(target->hasJitEntry()); writer.callScriptedSetter(objId, target, rhsId); writer.returnFromIC(); } bool SetPropIRGenerator::tryAttachSetter(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4027,17 +4027,17 @@ CodeGenerator::visitPostWriteElementBarr visitPostWriteBarrierCommonV(lir, ool); } void CodeGenerator::visitCallNative(LCallNative* call) { WrappedFunction* target = call->getSingleTarget(); MOZ_ASSERT(target); - MOZ_ASSERT(target->isNative()); + MOZ_ASSERT(target->isNativeWithCppEntry()); int callargslot = call->argslot(); int unusedStack = StackOffsetOfPassedArg(callargslot); // Registers used for callWithABI() argument-passing. const Register argContextReg = ToRegister(call->getArgContextReg()); const Register argUintNReg = ToRegister(call->getArgUintNReg()); const Register argVpReg = ToRegister(call->getArgVpReg()); @@ -4052,18 +4052,19 @@ CodeGenerator::visitCallNative(LCallNati // Native functions have the signature: // bool (*)(JSContext*, unsigned, Value* vp) // Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward // are the function arguments. // Allocate space for the outparam, moving the StackPointer to what will be &vp[1]. masm.adjustStack(unusedStack); - // Push a Value containing the callee object: natives are allowed to access their callee before - // setitng the return value. The StackPointer is moved to &vp[0]. + // Push a Value containing the callee object: natives are allowed to access + // their callee before setting the return value. The StackPointer is moved + // to &vp[0]. masm.Push(ObjectValue(*target->rawJSFunction())); // Preload arguments into registers. masm.loadJSContext(argContextReg); masm.move32(Imm32(call->numActualArgs()), argUintNReg); masm.moveStackPtrTo(argVpReg); masm.Push(argUintNReg); @@ -4302,26 +4303,27 @@ CodeGenerator::visitCallGeneric(LCallGen // Known-target case is handled by LCallKnown. MOZ_ASSERT(!call->hasSingleTarget()); masm.checkStackAlignment(); // Guard that calleereg is actually a function object. masm.branchTestObjClass(Assembler::NotEqual, calleereg, nargsreg, &JSFunction::class_, &invoke); - // Guard that calleereg is an interpreted function with a JSScript. + // Guard that calleereg is an interpreted function with a JSScript or a + // wasm function. // If we are constructing, also ensure the callee is a constructor. if (call->mir()->isConstructing()) { masm.branchIfNotInterpretedConstructor(calleereg, nargsreg, &invoke); } else { - masm.branchIfFunctionHasNoScript(calleereg, &invoke); - masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, calleereg, objreg, &invoke); - } - - // Knowing that calleereg is a non-native function, load the jit code. + masm.branchIfFunctionHasNoJitEntry(calleereg, /* isConstructing */ false, &invoke); + masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, calleereg, objreg, + &invoke); + } + masm.loadJitCodeRaw(calleereg, objreg); // Nestle the StackPointer up to the argument vector. masm.freeStack(unusedStack); // Construct the IonFramePrefix. uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS, JitFrameLayout::Size()); @@ -4396,41 +4398,42 @@ CodeGenerator::emitCallInvokeFunctionShu void CodeGenerator::visitCallKnown(LCallKnown* call) { Register calleereg = ToRegister(call->getFunction()); Register objreg = ToRegister(call->getTempObject()); uint32_t unusedStack = StackOffsetOfPassedArg(call->argslot()); WrappedFunction* target = call->getSingleTarget(); - Label end, uncompiled; - - // Native single targets are handled by LCallNative. - MOZ_ASSERT(!target->isNative()); + + // Native single targets (except wasm) are handled by LCallNative. + MOZ_ASSERT(!target->isNativeWithCppEntry()); // Missing arguments must have been explicitly appended by the IonBuilder. DebugOnly numNonArgsOnStack = 1 + call->isConstructing(); MOZ_ASSERT(target->nargs() <= call->mir()->numStackArgs() - numNonArgsOnStack); MOZ_ASSERT_IF(call->isConstructing(), target->isConstructor()); masm.checkStackAlignment(); if (target->isClassConstructor() && !call->isConstructing()) { emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->ignoresReturnValue(), call->numActualArgs(), unusedStack); return; } MOZ_ASSERT_IF(target->isClassConstructor(), call->isConstructing()); - // The calleereg is known to be a non-native function, but might point to - // a LazyScript instead of a JSScript. - masm.branchIfFunctionHasNoScript(calleereg, &uncompiled); - - // Load non-native jitcode from the script. + Label uncompiled; + if (!target->isNativeWithJitEntry()) { + // The calleereg is known to be a non-native function, but might point + // to a LazyScript instead of a JSScript. + masm.branchIfFunctionHasNoJitEntry(calleereg, call->isConstructing(), &uncompiled); + } + if (call->mir()->needsArgCheck()) masm.loadJitCodeRaw(calleereg, objreg); else masm.loadJitCodeNoArgCheck(calleereg, objreg); // Nestle the StackPointer up to the argument vector. masm.freeStack(unusedStack); @@ -4444,27 +4447,32 @@ CodeGenerator::visitCallKnown(LCallKnown // Finally call the function in objreg. uint32_t callOffset = masm.callJit(objreg); markSafepointAt(callOffset, call); // Increment to remove IonFramePrefix; decrement to fill FrameSizeClass. // The return address has already been removed from the Ion frame. int prefixGarbage = sizeof(JitFrameLayout) - sizeof(void*); masm.adjustStack(prefixGarbage - unusedStack); - masm.jump(&end); - - // Handle uncompiled functions. - masm.bind(&uncompiled); - if (call->isConstructing() && target->nargs() > call->numActualArgs()) - emitCallInvokeFunctionShuffleNewTarget(call, calleereg, target->nargs(), unusedStack); - else - emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->ignoresReturnValue(), - call->numActualArgs(), unusedStack); - - masm.bind(&end); + + if (uncompiled.used()) { + Label end; + masm.jump(&end); + + // Handle uncompiled functions. + masm.bind(&uncompiled); + if (call->isConstructing() && target->nargs() > call->numActualArgs()) { + emitCallInvokeFunctionShuffleNewTarget(call, calleereg, target->nargs(), unusedStack); + } else { + emitCallInvokeFunction(call, calleereg, call->isConstructing(), + call->ignoresReturnValue(), call->numActualArgs(), unusedStack); + } + + masm.bind(&end); + } // If the return value of the constructing function is Primitive, // replace the return value with the Object from CreateThis. if (call->mir()->isConstructing()) { Label notPrimitive; masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, ¬Primitive); masm.loadValue(Address(masm.getStackPointer(), unusedStack), JSReturnOperand); masm.bind(¬Primitive); @@ -4708,32 +4716,32 @@ CodeGenerator::emitApplyGeneric(T* apply // objreg is dead across this call. // // extraStackSpace is garbage on entry and defined on exit. emitPushArguments(apply, extraStackSpace); masm.checkStackAlignment(); // If the function is native, only emit the call to InvokeFunction. - if (apply->hasSingleTarget() && apply->getSingleTarget()->isNative()) { + if (apply->hasSingleTarget() && apply->getSingleTarget()->isNativeWithCppEntry()) { emitCallInvokeFunction(apply, extraStackSpace); emitPopArguments(extraStackSpace); return; } Label end, invoke; // Guard that calleereg is an interpreted function with a JSScript. - masm.branchIfFunctionHasNoScript(calleereg, &invoke); + masm.branchIfFunctionHasNoJitEntry(calleereg, /* constructing */ false, &invoke); // Guard that calleereg is not a class constrcuctor masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, calleereg, objreg, &invoke); - // Knowing that calleereg is a non-native function, load script's jitcode. + // Knowing that calleereg is a non-native function, load jitcode. masm.loadJitCodeRaw(calleereg, objreg); // Call with an Ion frame or a rectifier frame. { // Create the frame descriptor. unsigned pushed = masm.framePushed(); Register stackSpace = extraStackSpace; masm.addPtr(Imm32(pushed), stackSpace); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2762,23 +2762,28 @@ InvalidateActivation(FreeOp* fop, const if (JitOptions.checkOsiPointRegisters) activations->asJit()->setCheckRegs(false); #endif size_t frameno = 1; for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter, ++frameno) { const JSJitFrameIter& frame = iter.frame(); - MOZ_ASSERT_IF(frameno == 1, frame.isExitFrame() || frame.type() == JitFrame_Bailout); + MOZ_ASSERT_IF(frameno == 1, frame.isExitFrame() || + frame.type() == JitFrame_Bailout || + frame.type() == JitFrame_JSJitToWasm); #ifdef JS_JITSPEW switch (frame.type()) { case JitFrame_Exit: JitSpew(JitSpew_IonInvalidate, "#%zu exit frame @ %p", frameno, frame.fp()); break; + case JitFrame_JSJitToWasm: + JitSpew(JitSpew_IonInvalidate, "#%zu wasm exit frame @ %p", frameno, frame.fp()); + break; case JitFrame_BaselineJS: case JitFrame_IonJS: case JitFrame_Bailout: { MOZ_ASSERT(frame.isScripted()); const char* type = "Unknown"; if (frame.isIonJS()) { type = "Optimized"; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -5036,16 +5036,23 @@ IonBuilder::createThis(JSFunction* targe return createThis; } // Native constructors build the new Object themselves. if (target->isNative()) { if (!target->isConstructor()) return nullptr; + if (target->isNativeWithJitEntry()) { + // Do not bother inlining constructor calls to asm.js, since it is + // not used much in practice. + MOZ_ASSERT(target->isWasmOptimized()); + return nullptr; + } + MConstant* magic = MConstant::New(alloc(), MagicValue(JS_IS_CONSTRUCTING)); current->add(magic); return magic; } if (target->isBoundFunction()) return constant(MagicValue(JS_UNINITIALIZED_LEXICAL)); @@ -5526,17 +5533,17 @@ IonBuilder::makeCallHelper(JSFunction* t { // This function may be called with mutated stack. // Querying TI for popped types is invalid. uint32_t targetArgs = callInfo.argc(); // Collect number of missing arguments provided that the target is // scripted. Native functions are passed an explicit 'argc' parameter. - if (target && !target->isNative()) + if (target && !target->isNativeWithCppEntry()) targetArgs = Max(target->nargs(), callInfo.argc()); bool isDOMCall = false; DOMObjectKind objKind = DOMObjectKind::Unknown; if (target && !callInfo.constructing()) { // We know we have a single call target. Check whether the "this" types // are DOM types and our function a DOM function, and if so flag the // MCall accordingly. @@ -5555,18 +5562,18 @@ IonBuilder::makeCallHelper(JSFunction* t if (!call) return abort(AbortReason::Alloc); if (callInfo.constructing()) call->addArg(targetArgs + 1, callInfo.getNewTarget()); // Explicitly pad any missing arguments with |undefined|. // This permits skipping the argumentsRectifier. + MOZ_ASSERT_IF(target && targetArgs > callInfo.argc(), !target->isNativeWithCppEntry()); for (int i = targetArgs; i > (int)callInfo.argc(); i--) { - MOZ_ASSERT_IF(target, !target->isNative()); MConstant* undef = constant(UndefinedValue()); if (!alloc().ensureBallast()) return abort(AbortReason::Alloc); call->addArg(i, undef); } // Add explicit arguments. // Skip addArg(0) because it is reserved for this diff --git a/js/src/jit/IonCacheIRCompiler.cpp b/js/src/jit/IonCacheIRCompiler.cpp --- a/js/src/jit/IonCacheIRCompiler.cpp +++ b/js/src/jit/IonCacheIRCompiler.cpp @@ -1094,19 +1094,20 @@ IonCacheIRCompiler::emitCallScriptedGett JitFrameLayout::Size()); masm.Push(Imm32(0)); // argc masm.Push(scratch); masm.Push(Imm32(descriptor)); // Check stack alignment. Add sizeof(uintptr_t) for the return address. MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0); - // The getter currently has a non-lazy script. We will only relazify when - // we do a shrinking GC and when that happens we will also purge IC stubs. - MOZ_ASSERT(target->hasScript()); + // The getter currently has a jit entry or a non-lazy script. We will only + // relazify when we do a shrinking GC and when that happens we will also + // purge IC stubs. + MOZ_ASSERT(target->hasJitEntry()); masm.loadJitCodeRaw(scratch, scratch); masm.callJit(scratch); masm.storeCallResultValue(output); masm.freeStack(masm.framePushed() - framePushedBefore); return true; } @@ -2140,19 +2141,20 @@ IonCacheIRCompiler::emitCallScriptedSett JitFrameLayout::Size()); masm.Push(Imm32(1)); // argc masm.Push(scratch); masm.Push(Imm32(descriptor)); // Check stack alignment. Add sizeof(uintptr_t) for the return address. MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0); - // The setter currently has a non-lazy script. We will only relazify when - // we do a shrinking GC and when that happens we will also purge IC stubs. - MOZ_ASSERT(target->hasScript()); + // The setter currently has a jit entry or a non-lazy script. We will only + // relazify when we do a shrinking GC and when that happens we will also + // purge IC stubs. + MOZ_ASSERT(target->hasJitEntry()); masm.loadJitCodeRaw(scratch, scratch); masm.callJit(scratch); masm.freeStack(masm.framePushed() - framePushedBefore); return true; } typedef bool (*SetArrayLengthFn)(JSContext*, HandleObject, HandleValue, bool); diff --git a/js/src/jit/JSJitFrameIter.cpp b/js/src/jit/JSJitFrameIter.cpp --- a/js/src/jit/JSJitFrameIter.cpp +++ b/js/src/jit/JSJitFrameIter.cpp @@ -26,16 +26,28 @@ JSJitFrameIter::JSJitFrameIter(const Jit current_ = activation_->bailoutData()->fp(); frameSize_ = activation_->bailoutData()->topFrameSize(); type_ = JitFrame_Bailout; } else { MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI); } } +JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, uint8_t* fp) + : current_(fp), + type_(JitFrame_JSJitToWasm), + returnAddressToFp_(nullptr), + frameSize_(0), + cachedSafepointIndex_(nullptr), + activation_(activation) +{ + MOZ_ASSERT(!activation_->bailoutData()); + MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI); +} + bool JSJitFrameIter::checkInvalidation() const { IonScript* dummy; return checkInvalidation(&dummy); } bool @@ -376,16 +388,19 @@ JSJitFrameIter::dump() const break; case JitFrame_WasmToJSJit: fprintf(stderr, " Fast wasm-to-JS entry frame\n"); fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize())); break; case JitFrame_Exit: fprintf(stderr, " Exit frame\n"); break; + case JitFrame_JSJitToWasm: + fprintf(stderr, " Wasm exit frame\n"); + break; }; fputc('\n', stderr); } #ifdef DEBUG bool JSJitFrameIter::verifyReturnAddressUsingNativeToBytecodeMap() { @@ -455,18 +470,17 @@ JSJitFrameIter::verifyReturnAddressUsing ++inlineFrames; } } return true; } #endif // DEBUG -JSJitProfilingFrameIterator::JSJitProfilingFrameIterator( - JSContext* cx, const JS::ProfilingFrameIterator::RegisterState& state) +JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(JSContext* cx, void* pc) { // If no profilingActivation is live, initialize directly to // end-of-iteration state. if (!cx->profilingActivation()) { type_ = JitFrame_CppToJSJit; fp_ = nullptr; returnAddressToFp_ = nullptr; return; @@ -483,32 +497,31 @@ JSJitProfilingFrameIterator::JSJitProfil type_ = JitFrame_CppToJSJit; fp_ = nullptr; returnAddressToFp_ = nullptr; return; } // Get the fp from the current profilingActivation fp_ = (uint8_t*) act->lastProfilingFrame(); - void* lastCallSite = act->lastProfilingCallSite(); - - JitcodeGlobalTable* table = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); // Profiler sampling must NOT be suppressed if we are here. MOZ_ASSERT(cx->isProfilerSamplingEnabled()); // Try initializing with sampler pc - if (tryInitWithPC(state.pc)) + if (tryInitWithPC(pc)) return; // Try initializing with sampler pc using native=>bytecode table. - if (tryInitWithTable(table, state.pc, cx->runtime(), /* forLastCallSite = */ false)) + JitcodeGlobalTable* table = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); + if (tryInitWithTable(table, pc, cx->runtime(), /* forLastCallSite = */ false)) return; // Try initializing with lastProfilingCallSite pc + void* lastCallSite = act->lastProfilingCallSite(); if (lastCallSite) { if (tryInitWithPC(lastCallSite)) return; // Try initializing with lastProfilingCallSite pc using native=>bytecode table. if (tryInitWithTable(table, lastCallSite, cx->runtime(), /* forLastCallSite = */ true)) return; } @@ -524,21 +537,19 @@ JSJitProfilingFrameIterator::JSJitProfil template static inline ReturnType GetPreviousRawFrame(CommonFrameLayout* frame) { size_t prevSize = frame->prevFrameLocalSize() + frame->headerSize(); return ReturnType((uint8_t*)frame + prevSize); } -JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(void* exitFrame) +JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(CommonFrameLayout* fp) { - // Skip the exit frame. - ExitFrameLayout* frame = (ExitFrameLayout*) exitFrame; - moveToNextFrame(frame); + moveToNextFrame(fp); } bool JSJitProfilingFrameIterator::tryInitWithPC(void* pc) { JSScript* callee = frameScript(); // Check for Ion first, since it's more likely for hot code. diff --git a/js/src/jit/JSJitFrameIter.h b/js/src/jit/JSJitFrameIter.h --- a/js/src/jit/JSJitFrameIter.h +++ b/js/src/jit/JSJitFrameIter.h @@ -55,16 +55,21 @@ enum FrameType // frame is always the last frame in a JitActivation iff the bailout frame // information is recorded on the JitActivation. JitFrame_Bailout, // A wasm to JS frame is constructed during fast calls from wasm to the JS // jits, used as a marker to interleave JS jit and wasm frames. From the // point of view of JS JITs, this is just another kind of entry frame. JitFrame_WasmToJSJit, + + // A JS to wasm frame is constructed during fast calls from any JS jits to + // wasm, and is a special kind of exit frame that doesn't have the exit + // footer. From the point of view of the jit, it can be skipped as an exit. + JitFrame_JSJitToWasm, }; enum ReadFrameArgsBehavior { // Only read formals (i.e. [0 ... callee()->nargs] ReadFrame_Formals, // Only read overflown args (i.e. [callee()->nargs ... numActuals()] ReadFrame_Overflown, @@ -108,16 +113,20 @@ class JSJitFrameIter const JitActivation* activation_; void dumpBaseline() const; public: // See comment above the class. explicit JSJitFrameIter(const JitActivation* activation); + // A constructor specialized for jit->wasm frames, which starts at a + // specific FP. + JSJitFrameIter(const JitActivation* activation, uint8_t* fp); + // Used only by DebugModeOSRVolatileJitFrameIter. void exchangeReturnAddressIfMatch(uint8_t* oldAddr, uint8_t* newAddr) { if (returnAddressToFp_ == oldAddr) returnAddressToFp_ = newAddr; } // Current frame information. FrameType type() const { @@ -297,19 +306,18 @@ class JSJitProfilingFrameIterator bool forLastCallSite); void fixBaselineReturnAddress(); void moveToCppEntryFrame(); void moveToWasmFrame(CommonFrameLayout* frame); void moveToNextFrame(CommonFrameLayout* frame); public: - JSJitProfilingFrameIterator(JSContext* cx, - const JS::ProfilingFrameIterator::RegisterState& state); - explicit JSJitProfilingFrameIterator(void* exitFrame); + JSJitProfilingFrameIterator(JSContext* cx, void* pc); + explicit JSJitProfilingFrameIterator(CommonFrameLayout* exitFP); void operator++(); bool done() const { return fp_ == nullptr; } void* fp() const { MOZ_ASSERT(!done()); return fp_; } void* stackAddress() const { return fp(); } FrameType frameType() const { MOZ_ASSERT(!done()); return type_; } void* returnAddressToFp() const { MOZ_ASSERT(!done()); return returnAddressToFp_; } diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -859,30 +859,32 @@ ReadAllocation(const JSJitFrameIter& fra } return *frame.jsFrame()->slotRef(SafepointSlotEntry(a)); } #endif static void TraceThisAndArguments(JSTracer* trc, const JSJitFrameIter& frame, JitFrameLayout* layout) { - // Trace |this| and any extra actual arguments for an Ion frame. Tracinging + // Trace |this| and any extra actual arguments for an Ion frame. Tracing // of formal arguments is taken care of by the frame's safepoint/snapshot, // except when the script might have lazy arguments or rest, in which case // we trace them as well. We also have to trace formals if we have a - // LazyLink frame or an InterpreterStub frame. + // LazyLink frame or an InterpreterStub frame or a special JSJit to wasm + // frame (since wasm doesn't use snapshots). if (!CalleeTokenIsFunction(layout->calleeToken())) return; size_t nargs = layout->numActualArgs(); size_t nformals = 0; JSFunction* fun = CalleeTokenToFunction(layout->calleeToken()); - if (!frame.isExitFrameLayout() && + if (frame.type() != JitFrame_JSJitToWasm && + !frame.isExitFrameLayout() && !frame.isExitFrameLayout() && !fun->nonLazyScript()->mayReadFrameArgsDirectly()) { nformals = fun->nargs(); } size_t newTargetOffset = Max(nargs, fun->nargs()); @@ -1278,16 +1280,26 @@ TraceRectifierFrame(JSTracer* trc, const // // Baseline JIT code generated as part of the ICCall_Fallback stub may use // it if we're calling a constructor that returns a primitive value. RectifierFrameLayout* layout = (RectifierFrameLayout*)frame.fp(); TraceRoot(trc, &layout->argv()[0], "ion-thisv"); } static void +TraceJSJitToWasmFrame(JSTracer* trc, const JSJitFrameIter& frame) +{ + // This is doing a subset of TraceIonJSFrame, since the callee doesn't + // have a script. + JitFrameLayout* layout = (JitFrameLayout*)frame.fp(); + layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken())); + TraceThisAndArguments(trc, frame, layout); +} + +static void TraceJitActivation(JSTracer* trc, JitActivation* activation) { #ifdef CHECK_OSIPOINT_REGISTERS if (JitOptions.checkOsiPointRegisters) { // GC can modify spilled registers, breaking our register checks. // To handle this, we disable these checks for the current VM call // when a GC happens. activation->setCheckRegs(false); @@ -1318,18 +1330,22 @@ TraceJitActivation(JSTracer* trc, JitAct break; case JitFrame_Rectifier: TraceRectifierFrame(trc, jitFrame); break; case JitFrame_IonICCall: TraceIonICCallFrame(trc, jitFrame); break; case JitFrame_WasmToJSJit: - // Ignore: this is a marked used to let the JitFrameIter the - // frame above is a wasm frame, handled in the next iteration. + // Ignore: this is a special marker used to let the + // JitFrameIter know the frame above is a wasm frame, handled + // in the next iteration. + break; + case JitFrame_JSJitToWasm: + TraceJSJitToWasmFrame(trc, jitFrame); break; default: MOZ_CRASH("unexpected frame type"); } } else { MOZ_ASSERT(frames.isWasm()); frames.asWasm().instance()->trace(trc); } diff --git a/js/src/jit/JitFrames.h b/js/src/jit/JitFrames.h --- a/js/src/jit/JitFrames.h +++ b/js/src/jit/JitFrames.h @@ -474,16 +474,17 @@ enum class ExitFrameType : uint8_t { CallNative = 0x0, ConstructNative = 0x1, IonDOMGetter = 0x2, IonDOMSetter = 0x3, IonDOMMethod = 0x4, IonOOLNative = 0x5, IonOOLProxy = 0x6, + WasmJitEntry = 0x7, InterpreterStub = 0xFC, VMFunction = 0xFD, LazyLink = 0xFE, Bare = 0xFF, }; // GC related data used to keep alive data surrounding the Exit frame. class ExitFooterFrame diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp --- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -160,17 +160,17 @@ JitcodeGlobalEntry::IonEntry::destroy() js_delete(optsAllTypes_); optsAllTypes_ = nullptr; } void* JitcodeGlobalEntry::BaselineEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const { // TODO: We can't yet normalize Baseline addresses until we unify - // BaselineScript's PCMappingEntries with JitcodeGlobalMap. + // BaselineScript's PCMappingEntries with JitcodeGlobalTable. return ptr; } bool JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const { diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -532,17 +532,17 @@ LIRGenerator::visitCall(MCall* call) GetTempRegForIntArg(1, 0, &objReg); GetTempRegForIntArg(2, 0, &privReg); mozilla::DebugOnly ok = GetTempRegForIntArg(3, 0, &argsReg); MOZ_ASSERT(ok, "How can we not have four temp registers?"); lir = new(alloc()) LCallDOMNative(tempFixed(cxReg), tempFixed(objReg), tempFixed(privReg), tempFixed(argsReg)); } else if (target) { // Call known functions. - if (target->isNative()) { + if (target->isNativeWithCppEntry()) { Register cxReg, numReg, vpReg, tmpReg; GetTempRegForIntArg(0, 0, &cxReg); GetTempRegForIntArg(1, 0, &numReg); GetTempRegForIntArg(2, 0, &vpReg); // Even though this is just a temp reg, use the same API to avoid // register collisions. mozilla::DebugOnly ok = GetTempRegForIntArg(3, 0, &tmpReg); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2036,16 +2036,17 @@ MParameter::congruentTo(const MDefinitio return ins->toParameter()->index() == index_; } WrappedFunction::WrappedFunction(JSFunction* fun) : fun_(fun), nargs_(fun->nargs()), isNative_(fun->isNative()), + isNativeWithJitEntry_(fun->isNativeWithJitEntry()), isConstructor_(fun->isConstructor()), isClassConstructor_(fun->isClassConstructor()), isSelfHostedBuiltin_(fun->isSelfHostedBuiltin()) {} MCall* MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs, bool construct, bool ignoresReturnValue, bool isDOMCall, DOMObjectKind objectKind) diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4153,24 +4153,29 @@ class MInitElemGetterSetter // In particular, a function's flags can be modified on the active thread as // functions are relazified and delazified, so we must be careful not to access // these flags off-thread. class WrappedFunction : public TempObject { CompilerFunction fun_; uint16_t nargs_; bool isNative_ : 1; + bool isNativeWithJitEntry_ : 1; bool isConstructor_ : 1; bool isClassConstructor_ : 1; bool isSelfHostedBuiltin_ : 1; public: explicit WrappedFunction(JSFunction* fun); size_t nargs() const { return nargs_; } + bool isNative() const { return isNative_; } + bool isNativeWithJitEntry() const { return isNativeWithJitEntry_; } + bool isNativeWithCppEntry() const { return isNative() && !isNativeWithJitEntry(); } + bool isConstructor() const { return isConstructor_; } bool isClassConstructor() const { return isClassConstructor_; } bool isSelfHostedBuiltin() const { return isSelfHostedBuiltin_; } // fun->native() and fun->jitInfo() can safely be called off-thread: these // fields never change. JSNative native() const { return fun_->native(); } bool hasJitInfo() const { return fun_->hasJitInfo(); } diff --git a/js/src/jit/MacroAssembler-inl.h b/js/src/jit/MacroAssembler-inl.h --- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -429,26 +429,31 @@ MacroAssembler::branchLatin1String(Regis void MacroAssembler::branchTwoByteString(Register string, Label* label) { branchTest32(Assembler::Zero, Address(string, JSString::offsetOfFlags()), Imm32(JSString::LATIN1_CHARS_BIT), label); } void -MacroAssembler::branchIfFunctionHasNoScript(Register fun, Label* label) +MacroAssembler::branchIfFunctionHasNoJitEntry(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. + static_assert(JSFunction::offsetOfNargs() % sizeof(uint32_t) == 0, "The code in this function and the ones below must change"); static_assert(JSFunction::offsetOfFlags() == JSFunction::offsetOfNargs() + 2, "The code in this function and the ones below must change"); + Address address(fun, JSFunction::offsetOfNargs()); - int32_t bit = IMM32_16ADJ(JSFunction::INTERPRETED); + int32_t bit = JSFunction::INTERPRETED; + if (!isConstructing) + bit |= JSFunction::WASM_OPTIMIZED; + bit = IMM32_16ADJ(bit); branchTest32(Assembler::Zero, address, Imm32(bit), label); } void MacroAssembler::branchIfInterpreted(Register fun, 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. diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -197,19 +197,19 @@ using mozilla::FloatingPoint; # define DEFINED_ON(...) \ DEFINED_ON_MAP_ON_ARCHS((none, __VA_ARGS__)) # define PER_ARCH DEFINED_ON(ALL_ARCH) # define PER_SHARED_ARCH DEFINED_ON(ALL_SHARED_ARCH) # define OOL_IN_HEADER #if MOZ_LITTLE_ENDIAN -#define IMM32_16ADJ(X) X << 16 +#define IMM32_16ADJ(X) (X) << 16 #else -#define IMM32_16ADJ(X) X +#define IMM32_16ADJ(X) (X) #endif namespace js { namespace jit { // Defined in JitFrames.h enum class ExitFrameType : uint8_t; @@ -1151,17 +1151,17 @@ class MacroAssembler : public MacroAssem inline void branchIfRope(Register str, Label* label); inline void branchIfRopeOrExternal(Register str, Register temp, Label* label); inline void branchIfNotRope(Register str, Label* label); inline void branchLatin1String(Register string, Label* label); inline void branchTwoByteString(Register string, Label* label); - inline void branchIfFunctionHasNoScript(Register fun, Label* label); + inline void branchIfFunctionHasNoJitEntry(Register fun, bool isConstructing, Label* label); inline void branchIfInterpreted(Register fun, 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, Label* slowCheck, diff --git a/js/src/jit/arm/Assembler-arm.h b/js/src/jit/arm/Assembler-arm.h --- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -115,16 +115,20 @@ class ABIArgGenerator bool IsUnaligned(const wasm::MemoryAccessDesc& access); // These registers may be volatile or nonvolatile. static constexpr Register ABINonArgReg0 = r4; static constexpr Register ABINonArgReg1 = r5; static constexpr Register ABINonArgReg2 = r6; +// This register may be volatile or nonvolatile. Avoid d15 which is the +// ScratchDoubleReg. +static constexpr FloatRegister ABINonArgDoubleReg { FloatRegisters::d8, VFPRegister::Double }; + // These registers may be volatile or nonvolatile. // Note: these three registers are all guaranteed to be different static constexpr Register ABINonArgReturnReg0 = r4; static constexpr Register ABINonArgReturnReg1 = r5; // This register is guaranteed to be clobberable during the prologue and // epilogue of an ABI call which must preserve both ABI argument, return // and non-volatile registers. diff --git a/js/src/jit/arm/Simulator-arm.cpp b/js/src/jit/arm/Simulator-arm.cpp --- a/js/src/jit/arm/Simulator-arm.cpp +++ b/js/src/jit/arm/Simulator-arm.cpp @@ -1575,50 +1575,47 @@ Simulator::exclusiveMonitorGetAndClear(b } void Simulator::exclusiveMonitorClear() { exclusiveMonitorHeld_ = false; } -void +bool Simulator::startWasmInterrupt(JitActivation* activation) { JS::ProfilingFrameIterator::RegisterState state; state.pc = (void*) get_pc(); state.fp = (void*) get_register(fp); state.sp = (void*) get_register(sp); state.lr = (void*) get_register(lr); - activation->startWasmInterrupt(state); + return activation->startWasmInterrupt(state); } // The signal handler only redirects the PC to the interrupt stub when the PC is // in function code. However, this guard is racy for the ARM simulator since the // signal handler samples PC in the middle of simulating an instruction and thus // the current PC may have advanced once since the signal handler's guard. So we // re-check here. void Simulator::handleWasmInterrupt() { if (!wasm::CodeExists) return; uint8_t* pc = (uint8_t*)get_pc(); - uint8_t* fp = (uint8_t*)get_register(r11); const wasm::CodeSegment* cs = nullptr; if (!wasm::InInterruptibleCode(cx_, pc, &cs)) return; - // fp can be null during the prologue/epilogue of the entry function. - if (!fp) + if (!startWasmInterrupt(cx_->activation()->asJit())) return; - startWasmInterrupt(cx_->activation()->asJit()); set_pc(int32_t(cs->interruptCode())); } static inline JitActivation* GetJitActivation(JSContext* cx) { if (!wasm::CodeExists) return nullptr; @@ -1648,17 +1645,17 @@ Simulator::handleWasmSegFault(int32_t ad return false; wasm::Instance* instance = wasm::LookupFaultingInstance(*segment, pc, fp); if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes)) return false; const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc); if (!memoryAccess) { - startWasmInterrupt(act); + MOZ_ALWAYS_TRUE(startWasmInterrupt(act)); if (!instance->code().containsCodePC(pc)) MOZ_CRASH("Cannot map PC to trap handler"); set_pc(int32_t(segment->outOfBoundsCode())); return true; } MOZ_ASSERT(memoryAccess->hasTrapOutOfLineCode()); set_pc(int32_t(memoryAccess->trapOutOfLineCode(segment->base()))); diff --git a/js/src/jit/arm/Simulator-arm.h b/js/src/jit/arm/Simulator-arm.h --- a/js/src/jit/arm/Simulator-arm.h +++ b/js/src/jit/arm/Simulator-arm.h @@ -288,17 +288,17 @@ class Simulator inline bool isEnabledStop(uint32_t bkpt_code); inline void enableStop(uint32_t bkpt_code); inline void disableStop(uint32_t bkpt_code); inline void increaseStopCounter(uint32_t bkpt_code); void printStopInfo(uint32_t code); // Handle a wasm interrupt triggered by an async signal handler. void handleWasmInterrupt(); - void startWasmInterrupt(JitActivation* act); + bool startWasmInterrupt(JitActivation* act); // Handle any wasm faults, returning true if the fault was handled. bool handleWasmSegFault(int32_t addr, unsigned numBytes); bool handleWasmIllFault(); // Read and write memory. inline uint8_t readBU(int32_t addr); inline int8_t readB(int32_t addr); diff --git a/js/src/jit/arm64/Assembler-arm64.h b/js/src/jit/arm64/Assembler-arm64.h --- a/js/src/jit/arm64/Assembler-arm64.h +++ b/js/src/jit/arm64/Assembler-arm64.h @@ -452,16 +452,20 @@ class ABIArgGenerator ABIArg current_; }; // These registers may be volatile or nonvolatile. static constexpr Register ABINonArgReg0 = r8; static constexpr Register ABINonArgReg1 = r9; static constexpr Register ABINonArgReg2 = r10; +// This register may be volatile or nonvolatile. Avoid d31 which is the +// ScratchDoubleReg. +static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::s16, FloatRegisters::Single }; + // These registers may be volatile or nonvolatile. // Note: these three registers are all guaranteed to be different static constexpr Register ABINonArgReturnReg0 = r8; static constexpr Register ABINonArgReturnReg1 = r9; // This register is guaranteed to be clobberable during the prologue and // epilogue of an ABI call which must preserve both ABI argument, return // and non-volatile registers. diff --git a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp --- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp +++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp @@ -243,26 +243,24 @@ void Simulator::trigger_wasm_interrupt() void Simulator::handle_wasm_interrupt() { uint8_t* pc = (uint8_t*)get_pc(); uint8_t* fp = (uint8_t*)xreg(30); const js::wasm::CodeSegment* cs = nullptr; if (!js::wasm::InInterruptibleCode(cx_, pc, &cs)) return; - // fp can be null during the prologue/epilogue of the entry function. - if (!fp) - return; - JS::ProfilingFrameIterator::RegisterState state; state.pc = pc; state.fp = fp; state.lr = (uint8_t*) xreg(30); state.sp = (uint8_t*) xreg(31); - cx_->activation_->asJit()->startWasmInterrupt(state); + + if (!cx_->activation_->asJit()->startWasmInterrupt(state)) + return; set_pc((Instruction*)cs->interruptCode()); } int64_t Simulator::call(uint8_t* entry, int argument_count, ...) { va_list parameters; va_start(parameters, argument_count); diff --git a/js/src/jit/mips32/Simulator-mips32.cpp b/js/src/jit/mips32/Simulator-mips32.cpp --- a/js/src/jit/mips32/Simulator-mips32.cpp +++ b/js/src/jit/mips32/Simulator-mips32.cpp @@ -1644,20 +1644,16 @@ Simulator::handleWasmInterrupt() void* pc = (void*)get_pc(); void* fp = (void*)getRegister(Register::fp); JitActivation* activation = TlsContext.get()->activation()->asJit(); const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc); if (!segment || !segment->containsCodePC(pc)) return; - // fp can be null during the prologue/epilogue of the entry function. - if (!fp) - return; - startInterrupt(activation); set_pc(int32_t(segment->interruptCode())); } // WebAssembly memories contain an extra region of guard pages (see // WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses // using a signal handler that redirects PC to a stub that safely reports an diff --git a/js/src/jit/none/MacroAssembler-none.h b/js/src/jit/none/MacroAssembler-none.h --- a/js/src/jit/none/MacroAssembler-none.h +++ b/js/src/jit/none/MacroAssembler-none.h @@ -73,20 +73,23 @@ static constexpr Register64 ReturnReg64( static constexpr ValueOperand JSReturnOperand(InvalidReg); static constexpr Register64 ReturnReg64(InvalidReg); #else #error "Bad architecture" #endif static constexpr Register ABINonArgReg0 { Registers::invalid_reg }; static constexpr Register ABINonArgReg1 { Registers::invalid_reg }; +static constexpr Register ABINonArgReg2 { Registers::invalid_reg }; static constexpr Register ABINonArgReturnReg0 { Registers::invalid_reg }; static constexpr Register ABINonArgReturnReg1 { Registers::invalid_reg }; static constexpr Register ABINonArgReturnVolatileReg { Registers::invalid_reg }; +static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::invalid_reg }; + static constexpr Register WasmTableCallScratchReg { Registers::invalid_reg }; static constexpr Register WasmTableCallSigReg { Registers::invalid_reg }; static constexpr Register WasmTableCallIndexReg { Registers::invalid_reg }; static constexpr Register WasmTlsReg { Registers::invalid_reg }; static constexpr uint32_t ABIStackAlignment = 4; static constexpr uint32_t CodeAlignment = sizeof(void*); static constexpr uint32_t JitStackAlignment = 8; @@ -315,16 +318,17 @@ class MacroAssemblerNone : public Assemb void boxDouble(FloatRegister, ValueOperand, FloatRegister) { MOZ_CRASH(); } void boxNonDouble(JSValueType, Register, ValueOperand) { MOZ_CRASH(); } template void unboxInt32(T, Register) { MOZ_CRASH(); } template void unboxBoolean(T, Register) { MOZ_CRASH(); } template void unboxString(T, Register) { MOZ_CRASH(); } template void unboxSymbol(T, Register) { MOZ_CRASH(); } template void unboxObject(T, Register) { MOZ_CRASH(); } template void unboxDouble(T, FloatRegister) { MOZ_CRASH(); } + template void unboxPrivate(T, Register) { MOZ_CRASH(); } void unboxValue(const ValueOperand&, AnyRegister, JSValueType) { MOZ_CRASH(); } void unboxNonDouble(const ValueOperand&, Register, JSValueType) { MOZ_CRASH();} void unboxNonDouble(const Address&, Register, JSValueType) { MOZ_CRASH();} void unboxGCThingForPreBarrierTrampoline(const Address&, Register) { MOZ_CRASH(); } void notBoolean(ValueOperand) { MOZ_CRASH(); } Register extractObject(Address, Register) { MOZ_CRASH(); } Register extractObject(ValueOperand, Register) { MOZ_CRASH(); } Register extractString(ValueOperand, Register) { MOZ_CRASH(); } diff --git a/js/src/jit/x64/Assembler-x64.h b/js/src/jit/x64/Assembler-x64.h --- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -190,16 +190,20 @@ class ABIArgGenerator }; // These registers may be volatile or nonvolatile. // Avoid r11, which is the MacroAssembler's ScratchReg. static constexpr Register ABINonArgReg0 = rax; static constexpr Register ABINonArgReg1 = rbx; static constexpr Register ABINonArgReg2 = r10; +// This register may be volatile or nonvolatile. Avoid xmm15 which is the +// ScratchDoubleReg. +static constexpr FloatRegister ABINonArgDoubleReg = FloatRegister(X86Encoding::xmm8, FloatRegisters::Double); + // These registers may be volatile or nonvolatile. // Note: these three registers are all guaranteed to be different static constexpr Register ABINonArgReturnReg0 = r10; static constexpr Register ABINonArgReturnReg1 = r12; static constexpr Register ABINonVolatileReg = r13; // This register is guaranteed to be clobberable during the prologue and // epilogue of an ABI call which must preserve both ABI argument, return diff --git a/js/src/jit/x86/Assembler-x86.h b/js/src/jit/x86/Assembler-x86.h --- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -79,16 +79,20 @@ class ABIArgGenerator uint32_t stackBytesConsumedSoFar() const { return stackOffset_; } }; // These registers may be volatile or nonvolatile. static constexpr Register ABINonArgReg0 = eax; static constexpr Register ABINonArgReg1 = ebx; static constexpr Register ABINonArgReg2 = ecx; +// This register may be volatile or nonvolatile. Avoid xmm7 which is the +// ScratchDoubleReg. +static constexpr FloatRegister ABINonArgDoubleReg = FloatRegister(X86Encoding::xmm0, FloatRegisters::Double); + // These registers may be volatile or nonvolatile. // Note: these three registers are all guaranteed to be different static constexpr Register ABINonArgReturnReg0 = ecx; static constexpr Register ABINonArgReturnReg1 = edx; static constexpr Register ABINonVolatileReg = ebx; // This register is guaranteed to be clobberable during the prologue and // epilogue of an ABI call which must preserve both ABI argument, return diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1996,17 +1996,20 @@ js::NewFunctionWithProto(JSContext* cx, if (fun->isInterpretedLazy()) fun->initLazyScript(nullptr); else fun->initScript(nullptr); fun->initEnvironment(enclosingEnv); } else { MOZ_ASSERT(fun->isNative()); MOZ_ASSERT(native); - fun->initNative(native, nullptr); + if (fun->isWasmOptimized()) + fun->initWasmNative(native); + else + fun->initNative(native, nullptr); } if (allocKind == AllocKind::FUNCTION_EXTENDED) fun->initializeExtended(); fun->initAtom(atom); return fun; } diff --git a/js/src/jsfun.h b/js/src/jsfun.h --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -132,17 +132,17 @@ class JSFunction : public js::NativeObje JSScript* script_; /* interpreted bytecode descriptor or null; use the accessor! */ js::LazyScript* lazy_; /* lazily compiled script, or nullptr */ } s; } scripted; class { friend class JSFunction; js::Native native_; // The native for interpreter wasm calls. - void* jitEntry_; // A pointer to a fast jit->wasm table entry. + void** jitEntry_; // A pointer to a fast jit->wasm table entry. } wasm; } u; js::GCPtrAtom atom_; /* name for diagnostics and decompiling */ public: /* Call objects must be created for each invocation of this function. */ bool needsCallObject() const { MOZ_ASSERT(!isInterpretedLazy()); @@ -188,16 +188,17 @@ class JSFunction : public js::NativeObje /* A function can be classified as either native (C++) or interpreted (JS): */ bool isInterpreted() const { return flags() & (INTERPRETED | INTERPRETED_LAZY); } bool isNative() const { return !isInterpreted(); } bool isConstructor() const { return flags() & CONSTRUCTOR; } /* Possible attributes of a native function: */ bool isAsmJSNative() const { return kind() == AsmJS; } + bool isAsmJSNonOptimizedCtor() const { return isAsmJSNative() && isConstructor() && !isWasmOptimized(); } bool isWasmOptimized() const { return (flags() & WASM_OPTIMIZED); } bool isBuiltinNative() const { return isNative() && !isAsmJSNative() && !isWasmOptimized(); } // May be called from the JIT with the jitEntry_ field. bool isNativeWithJitEntry() const { return isNative() && isWasmOptimized(); } // Must be called from the JIT with the native_ field. bool isNativeWithCppEntry() const { return isNative() && !isWasmOptimized(); } @@ -242,16 +243,19 @@ class JSFunction : public js::NativeObje bool isIntrinsic() const { return isSelfHostedOrIntrinsic() && isNative(); } bool hasJITCode() const { if (!hasScript()) return false; return nonLazyScript()->hasBaselineScript() || nonLazyScript()->hasIonScript(); } + bool hasJitEntry() const { + return hasScript() || isNativeWithJitEntry(); + } /* Compound attributes: */ bool isBuiltin() const { return isBuiltinNative() || isSelfHostedBuiltin(); } bool isNamedLambda() const { return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom(); @@ -585,39 +589,64 @@ class JSFunction : public js::NativeObje void initNative(js::Native native, const JSJitInfo* jitinfo) { MOZ_ASSERT(isNativeWithCppEntry()); MOZ_ASSERT(native); u.native.func_ = native; u.native.jitinfo_ = jitinfo; } bool hasJitInfo() const { - return isNativeWithCppEntry() && u.native.jitinfo_; + return isNativeWithCppEntry() && + u.native.jitinfo_ && + !isAsmJSNonOptimizedCtor(); } const JSJitInfo* jitInfo() const { MOZ_ASSERT(hasJitInfo()); return u.native.jitinfo_; } void setJitInfo(const JSJitInfo* data) { MOZ_ASSERT(isNativeWithCppEntry()); u.native.jitinfo_ = data; } + // Wasm natives are optimized and have a jit entry. void initWasmNative(js::Native native) { MOZ_ASSERT(isNativeWithJitEntry()); MOZ_ASSERT(native); u.wasm.native_ = native; u.wasm.jitEntry_ = nullptr; } - void setWasmJitEntry(void* entry) { + void setWasmJitEntry(void** entry) { MOZ_ASSERT(isNativeWithJitEntry()); MOZ_ASSERT(entry); MOZ_ASSERT(!u.wasm.jitEntry_); u.wasm.jitEntry_ = entry; } + void** wasmJitEntry() const { + MOZ_ASSERT(isNativeWithJitEntry()); + MOZ_ASSERT(u.wasm.jitEntry_); + return u.wasm.jitEntry_; + } + + // AsmJS non optimized ctor store the func index in the jitinfo slot, since + // asm.js functions don't have a jit info associated. + void setAsmJSCtorFuncIndex(uint32_t funcIndex) { + MOZ_ASSERT(isAsmJSNonOptimizedCtor()); + MOZ_ASSERT(!u.native.jitinfo_); + static_assert(offsetof(U, native.jitinfo_) == offsetof(U, wasm.jitEntry_), + "asm.js func index and wasm jit entry pointer must be at the same location"); + u.native.jitinfo_ = (const JSJitInfo*)uintptr_t((funcIndex << 1) | 0x1); + } + bool hasWasmFuncIndex() const { + return isAsmJSNonOptimizedCtor(); + } + uint32_t wasmFuncIndex() const { + MOZ_ASSERT(hasWasmFuncIndex()); + return uint32_t(uintptr_t(u.native.jitinfo_) >> 1); + } bool isDerivedClassConstructor(); static unsigned offsetOfNative() { static_assert(offsetof(U, native.func_) == offsetof(U, wasm.native_), "native.func_ must be at the same offset as wasm.native_"); return offsetof(JSFunction, u.native.func_); } @@ -816,20 +845,20 @@ class FunctionExtended : public JSFuncti /* * Exported asm.js/wasm functions store their WasmInstanceObject in the * first slot. */ static const unsigned WASM_INSTANCE_SLOT = 0; /* - * wasm/asm.js exported functions store the function index of the exported - * function in the original module. + * wasm/asm.js exported functions store the wasm::TlsData pointer of their + * instance. */ - static const unsigned WASM_FUNC_INDEX_SLOT = 1; + static const unsigned WASM_TLSDATA_SLOT = 1; /* * asm.js module functions store their WasmModuleObject in the first slot. */ static const unsigned ASMJS_MODULE_SLOT = 0; static inline size_t offsetOfExtendedSlot(unsigned which) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -893,16 +893,22 @@ class JSScript : public js::gc::TenuredC js::HandleScriptSource sourceObject, js::HandleFunction fun, js::MutableHandleScript scriptp); friend bool js::detail::CopyScript(JSContext* cx, js::HandleScript src, js::HandleScript dst, js::MutableHandle> scopes); private: + // Pointer to baseline->method()->raw(), ion->method()->raw(), a wasm jit + // entry, the JIT's EnterInterpreter stub, or the lazy link stub. Must be + // non-null. + uint8_t* jitCodeRaw_; + uint8_t* jitCodeSkipArgCheck_; + js::SharedScriptData* scriptData_; public: uint8_t* data; /* pointer to variable-length data array (see comment above Create() for details) */ JSCompartment* compartment_; private: @@ -924,23 +930,16 @@ class JSScript : public js::gc::TenuredC js::jit::IonScript* ion; /* Information attached by Baseline. */ js::jit::BaselineScript* baseline; /* Information used to re-lazify a lazily-parsed interpreted function. */ js::LazyScript* lazyScript; - /* - * Pointer to baseline->method()->raw(), ion->method()->raw(), the JIT's - * EnterInterpreter stub, or the lazy link stub. Must be non-null. - */ - uint8_t* jitCodeRaw_; - uint8_t* jitCodeSkipArgCheck_; - // 32-bit fields. uint32_t dataSize_; /* size of the used part of the data array */ uint32_t lineno_; /* base line number of script */ uint32_t column_; /* base column of script, optionally set */ uint32_t mainOffset_;/* offset of main entry point from code, after @@ -1592,20 +1591,20 @@ class JSScript : public js::gc::TenuredC void updateJitCodeRaw(JSRuntime* rt); static size_t offsetOfBaselineScript() { return offsetof(JSScript, baseline); } static size_t offsetOfIonScript() { return offsetof(JSScript, ion); } - static size_t offsetOfJitCodeRaw() { + static constexpr size_t offsetOfJitCodeRaw() { return offsetof(JSScript, jitCodeRaw_); } - static size_t offsetOfJitCodeSkipArgCheck() { + static constexpr size_t offsetOfJitCodeSkipArgCheck() { return offsetof(JSScript, jitCodeSkipArgCheck_); } uint8_t* jitCodeRaw() const { return jitCodeRaw_; } bool isRelazifiable() const { return (selfHosted() || lazyScript) && !hasInnerFunctions_ && !types_ && diff --git a/js/src/vm/GeckoProfiler.cpp b/js/src/vm/GeckoProfiler.cpp --- a/js/src/vm/GeckoProfiler.cpp +++ b/js/src/vm/GeckoProfiler.cpp @@ -71,24 +71,21 @@ GetTopProfilingJitFrame(Activation* act) jit::JitActivation* jitActivation = act->asJit(); // If there is no exit frame set, just return. if (!jitActivation->hasExitFP()) return nullptr; // Skip wasm frames that might be in the way. - JitFrameIter iter(jitActivation); - while (!iter.done() && iter.isWasm()) - ++iter; - - if (!iter.isJSJit()) + OnlyJSJitFrameIter iter(jitActivation); + if (iter.done()) return nullptr; - jit::JSJitProfilingFrameIterator jitIter(iter.asJSJit().fp()); + jit::JSJitProfilingFrameIterator jitIter((jit::CommonFrameLayout*) iter.frame().fp()); MOZ_ASSERT(!jitIter.done()); return jitIter.fp(); } void GeckoProfilerRuntime::enable(bool enabled) { #ifdef DEBUG diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -345,26 +345,27 @@ struct JSRuntime : public js::MallocProv // started forcing this runtime to be single threaded. Calls to these // functions must be balanced. bool beginSingleThreadedExecution(JSContext* cx); void endSingleThreadedExecution(JSContext* cx); /* * The start of the range stored in the profiler sample buffer, as measured * after the most recent sample. - * All JitcodeGlobalMap entries referenced from a given sample are assigned - * the buffer position of the START of the sample. The buffer entries that - * reference the JitcodeGlobalMap entries will only ever be read from the - * buffer while the entire sample is still inside the buffer; if some - * buffer entries at the start of the sample have left the buffer, the - * entire sample will be considered inaccessible. + * All JitcodeGlobalTable entries referenced from a given sample are + * assigned the buffer position of the START of the sample. The buffer + * entries that reference the JitcodeGlobalTable entries will only ever be + * read from the buffer while the entire sample is still inside the buffer; + * if some buffer entries at the start of the sample have left the buffer, + * the entire sample will be considered inaccessible. * This means that, once profilerSampleBufferRangeStart_ advances beyond - * the sample position that's stored on a JitcodeGlobalMap entry, the buffer - * entries that reference this JitcodeGlobalMap entry will be considered - * inaccessible, and those JitcodeGlobalMap entry can be disposed of. + * the sample position that's stored on a JitcodeGlobalTable entry, the + * buffer entries that reference this JitcodeGlobalTable entry will be + * considered inaccessible, and those JitcodeGlobalTable entry can be + * disposed of. */ mozilla::Atomic profilerSampleBufferRangeStart_; mozilla::Maybe profilerSampleBufferRangeStart() { if (beingDestroyed_ || !geckoProfiler().enabled()) { return mozilla::Nothing(); } uint64_t rangeStart = profilerSampleBufferRangeStart_; diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -563,16 +563,43 @@ JitFrameIter::settle() if (mustUnwindActivation_) act_->setWasmExitFP(prevFP); iter_.destroy(); iter_.construct(act_, prevFP); MOZ_ASSERT(!asWasm().done()); return; } + + if (isWasm()) { + const wasm::WasmFrameIter& wasmFrame = asWasm(); + if (!wasmFrame.unwoundIonCallerFP()) + return; + + // Transition from wasm frames to jit frames: we're on the + // jit-to-wasm fast path. The current stack layout is as follows: + // (stack grows downward) + // + // [--------------------] + // [JIT FRAME ] + // [WASM JIT ENTRY FRAME] <-- we're here + // + // The wasm iterator has saved the previous jit frame pointer for us. + + MOZ_ASSERT(wasmFrame.done()); + uint8_t* prevFP = wasmFrame.unwoundIonCallerFP(); + + if (mustUnwindActivation_) + act_->setJSExitFP(prevFP); + + iter_.destroy(); + iter_.construct(act_, prevFP); + MOZ_ASSERT(!asJSJit().done()); + return; + } } void JitFrameIter::operator++() { MOZ_ASSERT(isSome()); if (isJSJit()) { const jit::JSJitFrameIter& jitFrame = asJSJit(); @@ -1714,38 +1741,53 @@ jit::JitActivation::removeIonFrameRecove void jit::JitActivation::traceIonRecovery(JSTracer* trc) { for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); it++) it->trace(trc); } -void +bool jit::JitActivation::startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state) { + // fp may be null when first entering wasm code from an interpreter entry + // stub. + if (!state.fp) + return false; + MOZ_ASSERT(state.pc); - MOZ_ASSERT(state.fp); // Execution can only be interrupted in function code. Afterwards, control // flow does not reenter function code and thus there can be no // interrupt-during-interrupt. - bool ignoredUnwound; + bool unwound; wasm::UnwindState unwindState; - MOZ_ALWAYS_TRUE(wasm::StartUnwinding(state, &unwindState, &ignoredUnwound)); + MOZ_ALWAYS_TRUE(wasm::StartUnwinding(state, &unwindState, &unwound)); void* pc = unwindState.pc; - MOZ_ASSERT(wasm::LookupCode(pc)->lookupRange(pc)->isFunction()); + + if (unwound) { + // In the prologue/epilogue, FP might have been fixed up to the + // caller's FP, and the caller could be the jit entry. Ignore this + // interrupt, in this case, because FP points to a jit frame and not a + // wasm one. + const wasm::CodeRange* codeRange = wasm::LookupCode(pc)->lookupRange(pc); + if (codeRange->isJitEntry()) + return false; + MOZ_ASSERT(codeRange->isFunction()); + } cx_->runtime()->wasmUnwindData.ref().construct(pc, state.pc); setWasmExitFP(unwindState.fp); MOZ_ASSERT(compartment() == unwindState.fp->tls->instance->compartment()); MOZ_ASSERT(isWasmInterrupted()); + return true; } void jit::JitActivation::finishWasmInterrupt() { MOZ_ASSERT(isWasmInterrupted()); cx_->runtime()->wasmUnwindData.ref().destroy(); @@ -1956,16 +1998,29 @@ JS::ProfilingFrameIterator::settleFrames { // Handle transition frames (see comment in JitFrameIter::operator++). if (isJSJit() && !jsJitIter().done() && jsJitIter().frameType() == jit::JitFrame_WasmToJSJit) { wasm::Frame* fp = (wasm::Frame*) jsJitIter().fp(); iteratorDestroy(); new (storage()) wasm::ProfilingFrameIterator(*activation_->asJit(), fp); kind_ = Kind::Wasm; MOZ_ASSERT(!wasmIter().done()); + return; + } + + if (isWasm() && wasmIter().done() && wasmIter().unwoundIonCallerFP()) { + uint8_t* fp = wasmIter().unwoundIonCallerFP(); + iteratorDestroy(); + // Using this ctor will skip the first ion->wasm frame, which is + // needed because the profiling iterator doesn't know how to unwind + // when the callee has no script. + new (storage()) jit::JSJitProfilingFrameIterator((jit::CommonFrameLayout*)fp); + kind_ = Kind::JSJit; + MOZ_ASSERT(!jsJitIter().done()); + return; } } void JS::ProfilingFrameIterator::settle() { settleFrames(); while (iteratorDone()) { @@ -1994,17 +2049,17 @@ JS::ProfilingFrameIterator::iteratorCons // - in all the other cases, we're not in wasm or we haven't exited from // wasm. if (activation->hasWasmExitFP() || wasm::InCompiledCode(state.pc)) { new (storage()) wasm::ProfilingFrameIterator(*activation, state); kind_ = Kind::Wasm; return; } - new (storage()) jit::JSJitProfilingFrameIterator(cx_, state); + new (storage()) jit::JSJitProfilingFrameIterator(cx_, state.pc); kind_ = Kind::JSJit; } void JS::ProfilingFrameIterator::iteratorConstruct() { MOZ_ASSERT(!done()); MOZ_ASSERT(activation_->isJit()); @@ -2015,17 +2070,18 @@ JS::ProfilingFrameIterator::iteratorCons // here, except that it's even simpler: since this activation is higher up // on the stack, it can only have exited to C++, through wasm or ion. if (activation->hasWasmExitFP()) { new (storage()) wasm::ProfilingFrameIterator(*activation); kind_ = Kind::Wasm; return; } - new (storage()) jit::JSJitProfilingFrameIterator(activation->jsExitFP()); + auto* fp = (jit::ExitFrameLayout*) activation->jsExitFP(); + new (storage()) jit::JSJitProfilingFrameIterator(fp); kind_ = Kind::JSJit; } void JS::ProfilingFrameIterator::iteratorDestroy() { MOZ_ASSERT(!done()); MOZ_ASSERT(activation_->isJit()); diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1662,17 +1662,18 @@ class JitActivation : public Activation static size_t offsetOfEncodedWasmExitReason() { return offsetof(JitActivation, encodedWasmExitReason_); } // Interrupts are started from the interrupt signal handler (or the ARM // simulator) and cleared by WasmHandleExecutionInterrupt or WasmHandleThrow // when the interrupt is handled. - void startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state); + // Returns true iff we've entered interrupted state. + bool startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state); void finishWasmInterrupt(); bool isWasmInterrupted() const; void* wasmInterruptUnwindPC() const; void* wasmInterruptResumePC() const; void startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset, void* pc, void* fp); void finishWasmTrap(); bool isWasmTrapping() const; diff --git a/js/src/wasm/WasmBuiltins.cpp b/js/src/wasm/WasmBuiltins.cpp --- a/js/src/wasm/WasmBuiltins.cpp +++ b/js/src/wasm/WasmBuiltins.cpp @@ -172,20 +172,16 @@ wasm::HandleThrow(JSContext* cx, WasmFra { // WasmFrameIter iterates down wasm frames in the activation starting at // JitActivation::wasmExitFP(). Pass Unwind::True to pop // JitActivation::wasmExitFP() once each time WasmFrameIter is incremented, // ultimately leaving exit FP null when the WasmFrameIter is done(). This // is necessary to prevent a DebugFrame from being observed again after we // just called onLeaveFrame (which would lead to the frame being re-added // to the map of live frames, right as it becomes trash). - // - // TODO(bug 1360211): when JitActivation and WasmActivation get merged, - // we'll be able to switch to ion / other wasm state from here, and we'll - // need to do things differently. MOZ_ASSERT(CallingActivation() == iter.activation()); MOZ_ASSERT(!iter.done()); iter.setUnwind(WasmFrameIter::Unwind::True); // Live wasm code on the stack is kept alive (in TraceJitActivation) by // marking the instance of every wasm::Frame found by WasmFrameIter. // However, as explained above, we're popping frames while iterating which @@ -306,16 +302,23 @@ WasmReportOutOfBounds() static void WasmReportUnalignedAccess() { JSContext* cx = TlsContext.get(); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_UNALIGNED_ACCESS); } +static void +WasmReportInt64JSCall() +{ + JSContext* cx = TlsContext.get(); + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE); +} + static int32_t CoerceInPlace_ToInt32(Value* rawVal) { JSContext* cx = TlsContext.get(); int32_t i32; RootedValue val(cx, *rawVal); if (!ToInt32(cx, val, &i32)) { @@ -338,16 +341,53 @@ CoerceInPlace_ToNumber(Value* rawVal) *rawVal = PoisonedObjectValue(0x42); return false; } *rawVal = DoubleValue(dbl); return true; } +static int32_t +CoerceInPlace_JitEntry(int funcExportIndex, TlsData* tlsData, Value* argv) +{ + JSContext* cx = CallingActivation()->cx(); + + const Code& code = tlsData->instance->code(); + const FuncExport& fe = code.metadata(code.stableTier()).funcExports[funcExportIndex]; + + for (size_t i = 0; i < fe.sig().args().length(); i++) { + HandleValue arg = HandleValue::fromMarkedLocation(&argv[i]); + switch (fe.sig().args()[i]) { + case ValType::I32: { + int32_t i32; + if (!ToInt32(cx, arg, &i32)) + return false; + argv[i] = Int32Value(i32); + break; + } + case ValType::F32: + case ValType::F64: { + double dbl; + if (!ToNumber(cx, arg, &dbl)) + return false; + // No need to convert double-to-float for f32, it's done inline + // in the wasm stub later. + argv[i] = DoubleValue(dbl); + break; + } + default: { + MOZ_CRASH("unexpected input argument in CoerceInPlace_JitEntry"); + } + } + } + + return true; +} + static int64_t DivI64(uint32_t x_hi, uint32_t x_lo, uint32_t y_hi, uint32_t y_lo) { int64_t x = ((uint64_t)x_hi << 32) + x_lo; int64_t y = ((uint64_t)y_hi << 32) + y_lo; MOZ_ASSERT(x != INT64_MIN || y != -1); MOZ_ASSERT(y != 0); return x / y; @@ -460,16 +500,19 @@ AddressOf(SymbolicAddress imm, ABIFuncti *abiType = Args_General1; return FuncCast(WasmOldReportTrap, *abiType); case SymbolicAddress::ReportOutOfBounds: *abiType = Args_General0; return FuncCast(WasmReportOutOfBounds, *abiType); case SymbolicAddress::ReportUnalignedAccess: *abiType = Args_General0; return FuncCast(WasmReportUnalignedAccess, *abiType); + case SymbolicAddress::ReportInt64JSCall: + *abiType = Args_General0; + return FuncCast(WasmReportInt64JSCall, *abiType); case SymbolicAddress::CallImport_Void: *abiType = Args_General4; return FuncCast(Instance::callImport_void, *abiType); case SymbolicAddress::CallImport_I32: *abiType = Args_General4; return FuncCast(Instance::callImport_i32, *abiType); case SymbolicAddress::CallImport_I64: *abiType = Args_General4; @@ -478,16 +521,19 @@ AddressOf(SymbolicAddress imm, ABIFuncti *abiType = Args_General4; return FuncCast(Instance::callImport_f64, *abiType); case SymbolicAddress::CoerceInPlace_ToInt32: *abiType = Args_General1; return FuncCast(CoerceInPlace_ToInt32, *abiType); case SymbolicAddress::CoerceInPlace_ToNumber: *abiType = Args_General1; return FuncCast(CoerceInPlace_ToNumber, *abiType); + case SymbolicAddress::CoerceInPlace_JitEntry: + *abiType = Args_General3; + return FuncCast(CoerceInPlace_JitEntry, *abiType); case SymbolicAddress::ToInt32: *abiType = Args_Int_Double; return FuncCast(JS::ToInt32, *abiType); case SymbolicAddress::DivI64: *abiType = Args_General4; return FuncCast(DivI64, *abiType); case SymbolicAddress::UDivI64: *abiType = Args_General4; @@ -614,17 +660,17 @@ wasm::NeedsBuiltinThunk(SymbolicAddress // they don't have frame info. switch (sym) { case SymbolicAddress::HandleExecutionInterrupt: // GenerateInterruptExit case SymbolicAddress::HandleDebugTrap: // GenerateDebugTrapStub case SymbolicAddress::HandleThrow: // GenerateThrowStub case SymbolicAddress::ReportTrap: // GenerateTrapExit case SymbolicAddress::OldReportTrap: // GenerateOldTrapExit case SymbolicAddress::ReportOutOfBounds: // GenerateOutOfBoundsExit - case SymbolicAddress::ReportUnalignedAccess: // GeneratesUnalignedExit + case SymbolicAddress::ReportUnalignedAccess: // GenerateUnalignedExit case SymbolicAddress::CallImport_Void: // GenerateImportInterpExit case SymbolicAddress::CallImport_I32: case SymbolicAddress::CallImport_I64: case SymbolicAddress::CallImport_F64: case SymbolicAddress::CoerceInPlace_ToInt32: // GenerateImportJitExit case SymbolicAddress::CoerceInPlace_ToNumber: #if defined(JS_CODEGEN_MIPS32) case SymbolicAddress::js_jit_gAtomic64Lock: @@ -664,16 +710,18 @@ wasm::NeedsBuiltinThunk(SymbolicAddress case SymbolicAddress::LogD: case SymbolicAddress::PowD: case SymbolicAddress::ATan2D: case SymbolicAddress::GrowMemory: case SymbolicAddress::CurrentMemory: case SymbolicAddress::WaitI32: case SymbolicAddress::WaitI64: case SymbolicAddress::Wake: + case SymbolicAddress::CoerceInPlace_JitEntry: + case SymbolicAddress::ReportInt64JSCall: return true; case SymbolicAddress::Limit: break; } MOZ_CRASH("unexpected symbolic address"); } diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp --- a/js/src/wasm/WasmCode.cpp +++ b/js/src/wasm/WasmCode.cpp @@ -182,16 +182,20 @@ SendCodeRangesToProfiler(const CodeSegme if (!name.append('\0')) return; unsigned line = codeRange.funcLineOrBytecode(); writePerfSpewerWasmFunctionMap(start, size, file, line, name.begin()); } else if (codeRange.isInterpEntry()) { if (!AppendToString(" slow entry", &name)) return; writePerfSpewerWasmMap(start, size, file, name.begin()); + } else if (codeRange.isJitEntry()) { + if (!AppendToString(" fast entry", &name)) + return; + writePerfSpewerWasmMap(start, size, file, name.begin()); } else if (codeRange.isImportInterpExit()) { if (!AppendToString(" slow exit", &name)) return; writePerfSpewerWasmMap(start, size, file, name.begin()); } else if (codeRange.isImportJitExit()) { if (!AppendToString(" fast exit", &name)) return; writePerfSpewerWasmMap(start, size, file, name.begin()); @@ -710,20 +714,70 @@ Metadata::getFuncName(const Bytes* maybe const char* funcIndexStr = NumberToCString(nullptr, &cbuf, funcIndex); MOZ_ASSERT(funcIndexStr); return name->append(beforeFuncIndex, strlen(beforeFuncIndex)) && name->append(funcIndexStr, strlen(funcIndexStr)) && name->append(afterFuncIndex, strlen(afterFuncIndex)); } -Code::Code(UniqueCodeSegment tier, const Metadata& metadata, UniqueJumpTable maybeJumpTable) +bool +JumpTables::init(CompileMode mode, const CodeSegment& cs, const CodeRangeVector& codeRanges) +{ + // Note a fast jit entry has two addresses, to be compatible with + // ion/baseline functions which have the raw vs checked args entries, + // both used all over the place in jit calls. This allows the fast entries + // to be compatible with jit code pointer loading routines. + // We can use the same entry for both kinds of jit entries since a wasm + // entry knows how to convert any kind of arguments and doesn't assume + // any input types. + + static_assert(JSScript::offsetOfJitCodeRaw() == 0, + "wasm fast jit entry is at (void*) jit[2*funcIndex]"); + static_assert(JSScript::offsetOfJitCodeSkipArgCheck() == sizeof(void*), + "wasm fast jit entry is also at (void*) jit[2*funcIndex+1]"); + + mode_ = mode; + + size_t numFuncs = 0; + for (const CodeRange& cr : codeRanges) { + if (cr.isFunction()) + numFuncs++; + } + + numFuncs_ = numFuncs; + + if (mode_ == CompileMode::Tier1) { + tiering_ = TablePointer(js_pod_calloc(numFuncs)); + if (!tiering_) + return false; + } + + // The number of jit entries is overestimated, but it is simpler when + // filling/looking up the jit entries and safe (worst case we'll crash + // because of a null deref when trying to call the jit entry of an + // unexported function). + jit_ = TablePointer(js_pod_calloc(2 * numFuncs)); + if (!jit_) + return false; + + uint8_t* codeBase = cs.base(); + for (const CodeRange& cr : codeRanges) { + if (cr.isFunction()) + setTieringEntry(cr.funcIndex(), codeBase + cr.funcTierEntry()); + else if (cr.isJitEntry()) + setJitEntry(cr.funcIndex(), codeBase + cr.begin()); + } + return true; +} + +Code::Code(UniqueCodeSegment tier, const Metadata& metadata, JumpTables&& maybeJumpTables) : metadata_(&metadata), profilingLabels_(mutexid::WasmCodeProfilingLabels, CacheableCharsVector()), - jumpTable_(Move(maybeJumpTable)) + jumpTables_(Move(maybeJumpTables)) { segment1_ = takeOwnership(Move(tier)); } Code::Code() : profilingLabels_(mutexid::WasmCodeProfilingLabels, CacheableCharsVector()) { } @@ -731,16 +785,24 @@ Code::Code() void Code::setTier2(UniqueCodeSegment segment) const { MOZ_RELEASE_ASSERT(segment->tier() == Tier::Ion && segment1_->tier() != Tier::Ion); MOZ_RELEASE_ASSERT(!segment2_.get()); segment2_ = takeOwnership(Move(segment)); } +uint32_t +Code::lookupFuncIndex(JSFunction* fun) const +{ + if (fun->hasWasmFuncIndex()) + return fun->wasmFuncIndex(); + return lookupRange(*fun->wasmJitEntry())->funcIndex(); +} + Tiers Code::tiers() const { if (hasTier2()) return Tiers(segment1_->tier(), segment2_->tier()); return Tiers(segment1_->tier()); } @@ -981,17 +1043,18 @@ Code::addSizeOfMiscIfNotSeen(MallocSizeO auto p = seenCode->lookupForAdd(this); if (p) return; bool ok = seenCode->add(p, this); (void)ok; // oh well *data += mallocSizeOf(this) + metadata().sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) + - profilingLabels_.lock()->sizeOfExcludingThis(mallocSizeOf); + profilingLabels_.lock()->sizeOfExcludingThis(mallocSizeOf) + + jumpTables_.sizeOfMiscIncludingThis(mallocSizeOf); for (auto t : tiers()) segment(t).addSizeOfMisc(mallocSizeOf, code, data); } size_t Code::serializedSize() const { @@ -1024,10 +1087,14 @@ Code::deserialize(const uint8_t* cursor, cursor = codeSegment->deserialize(cursor, *bytecode, linkData.linkData(Tier::Serialized), metadata); if (!cursor) return nullptr; segment1_ = takeOwnership(Move(codeSegment)); metadata_ = &metadata; + if (!jumpTables_.init(CompileMode::Once, *segment1_, + metadata.metadata(Tier::Serialized).codeRanges)) + return nullptr; + return cursor; } diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -445,41 +445,88 @@ class Metadata : public ShareableBase MutableMetadata; typedef RefPtr SharedMetadata; -typedef mozilla::UniquePtr UniqueJumpTable; +// Jump tables to take tiering into account, when calling either from wasm to +// wasm (through rabaldr) or from jit to wasm (jit entry). + +class JumpTables +{ + using TablePointer = mozilla::UniquePtr; + + CompileMode mode_; + TablePointer tiering_; + TablePointer jit_; + size_t numFuncs_; + + public: + bool init(CompileMode mode, const CodeSegment& cs, const CodeRangeVector& codeRanges); + + void setJitEntry(size_t i, void* target) const { + // See comment in wasm::Module::finishTier2 and JumpTables::init. + MOZ_ASSERT(i < numFuncs_); + jit_.get()[2 * i] = target; + jit_.get()[2 * i + 1] = target; + } + void** getAddressOfJitEntry(size_t i) const { + MOZ_ASSERT(i < numFuncs_); + MOZ_ASSERT(jit_.get()[2 * i]); + return &jit_.get()[2 * i]; + } + + void setTieringEntry(size_t i, void* target) const { + MOZ_ASSERT(i < numFuncs_); + // See comment in wasm::Module::finishTier2. + if (mode_ == CompileMode::Tier1) + tiering_.get()[i] = target; + } + void** tiering() const { + return tiering_.get(); + } + + size_t sizeOfMiscIncludingThis(MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + + 2 * sizeof(void*) * numFuncs_ + + (tiering_ ? sizeof(void*) : numFuncs_); + } +}; // Code objects own executable code and the metadata that describe it. A single // Code object is normally shared between a module and all its instances. // // profilingLabels_ is lazily initialized, but behind a lock. class Code : public ShareableBase { UniqueConstCodeSegment segment1_; mutable UniqueConstCodeSegment segment2_; // Access only when hasTier2() is true SharedMetadata metadata_; ExclusiveData profilingLabels_; - UniqueJumpTable jumpTable_; + JumpTables jumpTables_; UniqueConstCodeSegment takeOwnership(UniqueCodeSegment segment) const { segment->initCode(this); return UniqueConstCodeSegment(segment.release()); } public: Code(); - Code(UniqueCodeSegment tier, const Metadata& metadata, UniqueJumpTable maybeJumpTable); + Code(UniqueCodeSegment tier, const Metadata& metadata, JumpTables&& maybeJumpTables); - void** jumpTable() const { return jumpTable_.get(); } + void setTieringEntry(size_t i, void* target) const { jumpTables_.setTieringEntry(i, target); } + void** tieringJumpTable() const { return jumpTables_.tiering(); } + + void setJitEntry(size_t i, void* target) const { jumpTables_.setJitEntry(i, target); } + void** getAddressOfJitEntry(size_t i) const { return jumpTables_.getAddressOfJitEntry(i); } + uint32_t lookupFuncIndex(JSFunction* fun) const; bool hasTier2() const { return metadata_->hasTier2(); } void setTier2(UniqueCodeSegment segment) const; Tiers tiers() const; bool hasTier(Tier t) const; Tier stableTier() const; // This is stable during a run Tier bestTier() const; // This may transition from Baseline -> Ion at any time diff --git a/js/src/wasm/WasmFrameIter.cpp b/js/src/wasm/WasmFrameIter.cpp --- a/js/src/wasm/WasmFrameIter.cpp +++ b/js/src/wasm/WasmFrameIter.cpp @@ -14,16 +14,17 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wasm/WasmFrameIter.h" #include "wasm/WasmInstance.h" +#include "wasm/WasmStubs.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; using namespace js::wasm; using mozilla::DebugOnly; @@ -33,16 +34,17 @@ using mozilla::Swap; // WasmFrameIter implementation WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp) : activation_(activation), code_(nullptr), codeRange_(nullptr), lineOrBytecode_(0), fp_(fp ? fp : activation->wasmExitFP()), + unwoundIonCallerFP_(nullptr), unwind_(Unwind::False) { MOZ_ASSERT(fp_); // When the stack is captured during a trap (viz., to create the .stack // for an Error object), use the pc/bytecode information captured by the // signal handler in the runtime. @@ -78,20 +80,21 @@ WasmFrameIter::WasmFrameIter(JitActivati MOZ_ASSERT(!done()); return; } // Otherwise, execution exits wasm code via an exit stub which sets exitFP // to the exit stub's frame. Thus, in this case, we want to start iteration // at the caller of the exit frame, whose Code, CodeRange and CallSite are - // indicated by the returnAddress of the exit stub's frame. + // indicated by the returnAddress of the exit stub's frame. If the caller + // was Ion, we can just skip the wasm frames. popFrame(); - MOZ_ASSERT(!done()); + MOZ_ASSERT(!done() || unwoundIonCallerFP_); } bool WasmFrameIter::done() const { MOZ_ASSERT(!!fp_ == !!code_); MOZ_ASSERT(!!fp_ == !!codeRange_); return !fp_; @@ -130,31 +133,48 @@ WasmFrameIter::popFrame() fp_ = prevFP->callerFP; MOZ_ASSERT(!(uintptr_t(fp_) & JitActivation::ExitFpWasmBit)); if (!fp_) { code_ = nullptr; codeRange_ = nullptr; if (unwind_ == Unwind::True) { - // TODO with bug 1319203, there may be other JIT frames above. + // We're exiting via the interpreter entry; we can safely reset + // exitFP. activation_->setWasmExitFP(nullptr); unwoundAddressOfReturnAddress_ = &prevFP->returnAddress; } MOZ_ASSERT(done()); return; } void* returnAddress = prevFP->returnAddress; - code_ = &fp_->tls->instance->code(); - MOZ_ASSERT(code_ == LookupCode(returnAddress)); + code_ = LookupCode(returnAddress); + codeRange_ = code_->lookupRange(returnAddress); + + if (codeRange_->isJitEntry()) { + unwoundIonCallerFP_ = (uint8_t*) fp_; + + fp_ = nullptr; + code_ = nullptr; + codeRange_ = nullptr; - codeRange_ = code_->lookupRange(returnAddress); + if (unwind_ == Unwind::True) { + activation_->setJSExitFP(unwoundIonCallerFP_); + unwoundAddressOfReturnAddress_ = &prevFP->returnAddress; + } + + MOZ_ASSERT(done()); + return; + } + + MOZ_ASSERT(code_ == &fp_->tls->instance->code()); MOZ_ASSERT(codeRange_->kind() == CodeRange::Function); const CallSite* callsite = code_->lookupCallSite(returnAddress); MOZ_ASSERT(callsite); lineOrBytecode_ = callsite->lineOrBytecode(); MOZ_ASSERT(!done()); @@ -289,16 +309,17 @@ static const unsigned PushedRetAddr = 0; static const unsigned PushedTLS = 1; static const unsigned PushedFP = 2; static const unsigned SetFP = 3; static const unsigned PoppedFP = 4; static const unsigned PoppedTLSReg = 5; #else # error "Unknown architecture!" #endif +static constexpr unsigned SetJitEntryFP = PushedRetAddr + SetFP - PushedFP; static void LoadActivation(MacroAssembler& masm, const Register& dest) { // WasmCall pushes a JitActivation. masm.loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), dest); masm.loadPtr(Address(dest, JSContext::offsetOfActivation()), dest); @@ -546,84 +567,117 @@ AssertNoWasmExitFPInJitExit(MacroAssembl masm.bind(&ok); #endif } void wasm::GenerateJitExitPrologue(MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets) { masm.haltingAlign(CodeAlignment); - GenerateCallablePrologue(masm, framePushed, ExitReason::None(), - &offsets->begin, nullptr, CompileMode::Once, 0); + GenerateCallablePrologue(masm, framePushed, ExitReason::None(), &offsets->begin, nullptr, + CompileMode::Once, 0); AssertNoWasmExitFPInJitExit(masm); masm.setFramePushed(framePushed); } void wasm::GenerateJitExitEpilogue(MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets) { // Inverse of GenerateJitExitPrologue: MOZ_ASSERT(masm.framePushed() == framePushed); AssertNoWasmExitFPInJitExit(masm); GenerateCallableEpilogue(masm, framePushed, ExitReason::None(), &offsets->ret); masm.setFramePushed(0); } +void +wasm::GenerateJitEntryPrologue(MacroAssembler& masm, Offsets* offsets) +{ + masm.haltingAlign(CodeAlignment); + + { +#if defined(JS_CODEGEN_ARM) + AutoForbidPools afp(&masm, /* number of instructions in scope = */ 2); + offsets->begin = masm.currentOffset(); + MOZ_ASSERT(BeforePushRetAddr == 0); + masm.push(lr); +#else + // The x86/x64 call instruction pushes the return address. + offsets->begin = masm.currentOffset(); +#endif + MOZ_ASSERT_IF(!masm.oom(), PushedRetAddr == masm.currentOffset() - offsets->begin); + + // Save jit frame pointer, so unwinding from wasm to jit frames is trivial. + masm.moveStackPtrTo(FramePointer); + MOZ_ASSERT_IF(!masm.oom(), SetJitEntryFP == masm.currentOffset() - offsets->begin); + } + + masm.setFramePushed(0); +} + /*****************************************************************************/ // ProfilingFrameIterator ProfilingFrameIterator::ProfilingFrameIterator() : code_(nullptr), codeRange_(nullptr), callerFP_(nullptr), callerPC_(nullptr), stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), exitReason_(ExitReason::Fixed::None) { MOZ_ASSERT(done()); } ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation) : code_(nullptr), codeRange_(nullptr), callerFP_(nullptr), callerPC_(nullptr), stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), exitReason_(activation.wasmExitReason()) { initFromExitFP(activation.wasmExitFP()); } ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation, const Frame* fp) : code_(nullptr), codeRange_(nullptr), callerFP_(nullptr), callerPC_(nullptr), stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), exitReason_(ExitReason::Fixed::ImportJit) { MOZ_ASSERT(fp); initFromExitFP(fp); } static inline void AssertMatchesCallSite(void* callerPC, Frame* callerFP) { #ifdef DEBUG const Code* code = LookupCode(callerPC); MOZ_ASSERT(code); const CodeRange* callerCodeRange = code->lookupRange(callerPC); MOZ_ASSERT(callerCodeRange); - if (callerCodeRange->kind() == CodeRange::InterpEntry) { + if (callerCodeRange->isInterpEntry()) { MOZ_ASSERT(callerFP == nullptr); return; } + if (callerCodeRange->isJitEntry()) { + MOZ_ASSERT(callerFP != nullptr); + return; + } + const CallSite* callsite = code->lookupCallSite(callerPC); MOZ_ASSERT(callsite); #endif } void ProfilingFrameIterator::initFromExitFP(const Frame* fp) { @@ -637,24 +691,30 @@ ProfilingFrameIterator::initFromExitFP(c codeRange_ = code_->lookupRange(pc); MOZ_ASSERT(codeRange_); // Since we don't have the pc for fp, start unwinding at the caller of fp. // This means that the innermost frame is skipped. This is fine because: // - for import exit calls, the innermost frame is a thunk, so the first // frame that shows up is the function calling the import; - // - for Math and other builtin calls as well as interrupts, we note the absence - // of an exit reason and inject a fake "builtin" frame; and - // - for async interrupts, we just accept that we'll lose the innermost frame. + // - for Math and other builtin calls as well as interrupts, we note the + // absence of an exit reason and inject a fake "builtin" frame; and + // - for async interrupts, we just accept that we'll lose the innermost + // frame. switch (codeRange_->kind()) { case CodeRange::InterpEntry: callerPC_ = nullptr; callerFP_ = nullptr; break; + case CodeRange::JitEntry: + callerPC_ = nullptr; + callerFP_ = nullptr; + unwoundIonCallerFP_ = (uint8_t*) fp->callerFP; + break; case CodeRange::Function: fp = fp->callerFP; callerPC_ = fp->returnAddress; callerFP_ = fp->callerFP; AssertMatchesCallSite(callerPC_, callerFP_); break; case CodeRange::ImportJitExit: case CodeRange::ImportInterpExit: @@ -833,16 +893,32 @@ js::wasm::StartUnwinding(const RegisterS *unwoundCaller = false; AssertMatchesCallSite(fp->returnAddress, fp->callerFP); break; case CodeRange::InterpEntry: // The entry trampoline is the final frame in an wasm JitActivation. The // entry trampoline also doesn't GeneratePrologue/Epilogue so we can't // use the general unwinding logic above. break; + case CodeRange::JitEntry: + // There's a jit frame above the current one; we don't care about pc + // since the Jit entry frame is a jit frame which can be considered as + // an exit frame. +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + if (offsetFromEntry < PushedRetAddr) { + // We haven't pushed the jit return address yet, thus the jit + // frame is incomplete. During profiling frame iteration, it means + // that the jit profiling frame iterator won't be able to unwind + // this frame; drop it. + return false; + } +#endif + fixedFP = offsetFromEntry < SetJitEntryFP ? (Frame*) sp : fp; + fixedPC = nullptr; + break; case CodeRange::Throw: // The throw stub executes a small number of instructions before popping // the entire activation. To simplify testing, we simply pretend throw // stubs have already popped the entire stack. return false; case CodeRange::Interrupt: // When the PC is in the async interrupt stub, the fp may be garbage and // so we cannot blindly unwind it. Since the percent of time spent in @@ -859,16 +935,17 @@ js::wasm::StartUnwinding(const RegisterS ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation, const RegisterState& state) : code_(nullptr), codeRange_(nullptr), callerFP_(nullptr), callerPC_(nullptr), stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), exitReason_(ExitReason::Fixed::None) { // Let wasmExitFP take precedence to StartUnwinding when it is set since // during the body of an exit stub, the register state may not be valid // causing StartUnwinding() to abandon unwinding this activation. if (activation.hasWasmExitFP()) { exitReason_ = activation.wasmExitReason(); initFromExitFP(activation.wasmExitFP()); @@ -885,53 +962,71 @@ ProfilingFrameIterator::ProfilingFrameIt if (unwoundCaller) { callerFP_ = unwindState.fp; callerPC_ = unwindState.pc; } else { callerFP_ = unwindState.fp->callerFP; callerPC_ = unwindState.fp->returnAddress; } + if (unwindState.codeRange->isJitEntry()) + unwoundIonCallerFP_ = (uint8_t*) callerFP_; + code_ = unwindState.code; codeRange_ = unwindState.codeRange; stackAddress_ = state.sp; MOZ_ASSERT(!done()); } void ProfilingFrameIterator::operator++() { if (!exitReason_.isNone()) { MOZ_ASSERT(codeRange_); exitReason_ = ExitReason::None(); MOZ_ASSERT(!done()); return; } + if (unwoundIonCallerFP_) { + MOZ_ASSERT(codeRange_->isJitEntry()); + callerPC_ = nullptr; + callerFP_ = nullptr; + codeRange_ = nullptr; + MOZ_ASSERT(done()); + return; + } + if (!callerPC_) { MOZ_ASSERT(!callerFP_); codeRange_ = nullptr; MOZ_ASSERT(done()); return; } if (!callerFP_) { codeRange_ = code_->lookupRange(callerPC_); MOZ_ASSERT(codeRange_->kind() == CodeRange::InterpEntry); callerPC_ = nullptr; MOZ_ASSERT(!done()); return; } - code_ = &callerFP_->tls->instance->code(); - MOZ_ASSERT(code_ == LookupCode(callerPC_)); - + code_ = LookupCode(callerPC_); codeRange_ = code_->lookupRange(callerPC_); MOZ_ASSERT(codeRange_); + if (codeRange_->isJitEntry()) { + unwoundIonCallerFP_ = (uint8_t*) callerFP_; + MOZ_ASSERT(!done()); + return; + } + + MOZ_ASSERT(code_ == &callerFP_->tls->instance->code()); + switch (codeRange_->kind()) { case CodeRange::Function: case CodeRange::ImportJitExit: case CodeRange::ImportInterpExit: case CodeRange::BuiltinThunk: case CodeRange::TrapExit: case CodeRange::OldTrapExit: case CodeRange::DebugTrap: @@ -940,16 +1035,18 @@ ProfilingFrameIterator::operator++() case CodeRange::FarJumpIsland: stackAddress_ = callerFP_; callerPC_ = callerFP_->returnAddress; AssertMatchesCallSite(callerPC_, callerFP_->callerFP); callerFP_ = callerFP_->callerFP; break; case CodeRange::InterpEntry: MOZ_CRASH("should have had null caller fp"); + case CodeRange::JitEntry: + MOZ_CRASH("should have been guarded above"); case CodeRange::Interrupt: case CodeRange::Throw: MOZ_CRASH("code range doesn't have frame"); } MOZ_ASSERT(!done()); } @@ -1044,16 +1141,20 @@ ThunkedNativeToDescription(SymbolicAddre case SymbolicAddress::CurrentMemory: return "call to native current_memory (in wasm)"; case SymbolicAddress::WaitI32: return "call to native i32.wait (in wasm)"; case SymbolicAddress::WaitI64: return "call to native i64.wait (in wasm)"; case SymbolicAddress::Wake: return "call to native wake (in wasm)"; + case SymbolicAddress::CoerceInPlace_JitEntry: + return "out-of-line coercion for jit entry arguments (in wasm)"; + case SymbolicAddress::ReportInt64JSCall: + return "jit call to int64 wasm function"; #if defined(JS_CODEGEN_MIPS32) case SymbolicAddress::js_jit_gAtomic64Lock: MOZ_CRASH(); #endif case SymbolicAddress::Limit: break; } return "?"; @@ -1061,19 +1162,19 @@ ThunkedNativeToDescription(SymbolicAddre const char* ProfilingFrameIterator::label() const { MOZ_ASSERT(!done()); // Use the same string for both time inside and under so that the two // entries will be coalesced by the profiler. - static const char* importJitDescription = "fast FFI trampoline (in wasm)"; - static const char* importInterpDescription = "slow FFI trampoline (in wasm)"; - static const char* builtinNativeDescription = "fast FFI trampoline to native (in wasm)"; + static const char* importJitDescription = "fast exit trampoline (in wasm)"; + static const char* importInterpDescription = "slow exit trampoline (in wasm)"; + static const char* builtinNativeDescription = "fast exit trampoline to native (in wasm)"; static const char* trapDescription = "trap handling (in wasm)"; static const char* debugTrapDescription = "debug trap handling (in wasm)"; if (!exitReason_.isFixed()) return ThunkedNativeToDescription(exitReason_.symbolic()); switch (exitReason_.fixed()) { case ExitReason::Fixed::None: @@ -1088,16 +1189,17 @@ ProfilingFrameIterator::label() const return trapDescription; case ExitReason::Fixed::DebugTrap: return debugTrapDescription; } switch (codeRange_->kind()) { case CodeRange::Function: return code_->profilingLabel(codeRange_->funcIndex()); case CodeRange::InterpEntry: return "slow entry trampoline (in wasm)"; + case CodeRange::JitEntry: return "fast entry trampoline (in wasm)"; case CodeRange::ImportJitExit: return importJitDescription; case CodeRange::BuiltinThunk: return builtinNativeDescription; case CodeRange::ImportInterpExit: return importInterpDescription; case CodeRange::TrapExit: return trapDescription; case CodeRange::OldTrapExit: return trapDescription; case CodeRange::DebugTrap: return debugTrapDescription; case CodeRange::OutOfBoundsExit: return "out-of-bounds stub (in wasm)"; case CodeRange::UnalignedExit: return "unaligned trap stub (in wasm)"; diff --git a/js/src/wasm/WasmFrameIter.h b/js/src/wasm/WasmFrameIter.h --- a/js/src/wasm/WasmFrameIter.h +++ b/js/src/wasm/WasmFrameIter.h @@ -24,16 +24,17 @@ class JSAtom; namespace js { namespace jit { class MacroAssembler; struct Register; +class Label; } // namespace jit namespace wasm { class CallSite; class Code; class CodeRange; class CodeSegment; @@ -64,16 +65,17 @@ class WasmFrameIter enum class Unwind { True, False }; private: jit::JitActivation* activation_; const Code* code_; const CodeRange* codeRange_; unsigned lineOrBytecode_; Frame* fp_; + uint8_t* unwoundIonCallerFP_; Unwind unwind_; void** unwoundAddressOfReturnAddress_; void popFrame(); public: // See comment above this class definition. explicit WasmFrameIter(jit::JitActivation* activation, Frame* fp = nullptr); @@ -86,16 +88,17 @@ class WasmFrameIter bool mutedErrors() const; JSAtom* functionDisplayAtom() const; unsigned lineOrBytecode() const; const CodeRange* codeRange() const { return codeRange_; } Instance* instance() const; void** unwoundAddressOfReturnAddress() const; bool debugEnabled() const; DebugFrame* debugFrame() const; + uint8_t* unwoundIonCallerFP() const { return unwoundIonCallerFP_; } }; enum class SymbolicAddress; // An ExitReason describes the possible reasons for leaving compiled wasm // code or the state of not having left compiled wasm code // (ExitReason::None). It is either a known reason, or a enumeration to a native // function that is used for better display in the profiler. @@ -159,16 +162,17 @@ class ExitReason // asynchronously-interrupted thread's state. class ProfilingFrameIterator { const Code* code_; const CodeRange* codeRange_; Frame* callerFP_; void* callerPC_; void* stackAddress_; + uint8_t* unwoundIonCallerFP_; ExitReason exitReason_; void initFromExitFP(const Frame* fp); public: ProfilingFrameIterator(); // Start unwinding at a non-innermost activation that has necessarily been @@ -183,36 +187,42 @@ class ProfilingFrameIterator // the thread was suspended. ProfilingFrameIterator(const jit::JitActivation& activation, const JS::ProfilingFrameIterator::RegisterState& state); void operator++(); bool done() const { return !codeRange_; } void* stackAddress() const { MOZ_ASSERT(!done()); return stackAddress_; } + uint8_t* unwoundIonCallerFP() const { MOZ_ASSERT(done()); return unwoundIonCallerFP_; } const char* label() const; }; // Prologue/epilogue code generation void SetExitFP(jit::MacroAssembler& masm, ExitReason reason, jit::Register scratch); void ClearExitFP(jit::MacroAssembler& masm, jit::Register scratch); void GenerateExitPrologue(jit::MacroAssembler& masm, unsigned framePushed, ExitReason reason, CallableOffsets* offsets); void GenerateExitEpilogue(jit::MacroAssembler& masm, unsigned framePushed, ExitReason reason, CallableOffsets* offsets); + void GenerateJitExitPrologue(jit::MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets); void GenerateJitExitEpilogue(jit::MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets); + +void +GenerateJitEntryPrologue(jit::MacroAssembler& masm, Offsets* offsets); + void GenerateFunctionPrologue(jit::MacroAssembler& masm, unsigned framePushed, const SigIdDesc& sigId, FuncOffsets* offsets, CompileMode mode = CompileMode::Once, uint32_t funcIndex = 0); void GenerateFunctionEpilogue(jit::MacroAssembler& masm, unsigned framePushed, FuncOffsets* offsets); // Given a fault at pc with register fp, return the faulting instance if there diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -505,16 +505,19 @@ ModuleGenerator::noteCodeRange(uint32_t switch (codeRange.kind()) { case CodeRange::Function: MOZ_ASSERT(funcToCodeRange_[codeRange.funcIndex()] == BAD_CODE_RANGE); funcToCodeRange_[codeRange.funcIndex()] = codeRangeIndex; break; case CodeRange::InterpEntry: metadataTier_->lookupFuncExport(codeRange.funcIndex()).initInterpEntryOffset(codeRange.begin()); break; + case CodeRange::JitEntry: + // Nothing to do: jit entries are linked in the jump tables. + break; case CodeRange::ImportJitExit: metadataTier_->funcImports[codeRange.funcIndex()].initJitExitOffset(codeRange.begin()); break; case CodeRange::ImportInterpExit: metadataTier_->funcImports[codeRange.funcIndex()].initInterpExitOffset(codeRange.begin()); break; case CodeRange::OldTrapExit: MOZ_ASSERT(!oldTrapCodeOffsets_[codeRange.trap()]); @@ -967,65 +970,42 @@ ModuleGenerator::finish(const ShareableB return nullptr; if (!finishMetadata(bytecode)) return nullptr; return CodeSegment::create(tier(), masm_, bytecode, *linkDataTier_, *metadata_); } -UniqueJumpTable -ModuleGenerator::createJumpTable(const CodeSegment& codeSegment) -{ - MOZ_ASSERT(mode() == CompileMode::Tier1); - MOZ_ASSERT(!isAsmJS()); - - uint32_t tableSize = env_->numFuncs(); - UniqueJumpTable jumpTable(js_pod_calloc(tableSize)); - if (!jumpTable) - return nullptr; - - uint8_t* codeBase = codeSegment.base(); - for (const CodeRange& codeRange : metadataTier_->codeRanges) { - if (codeRange.isFunction()) - jumpTable[codeRange.funcIndex()] = codeBase + codeRange.funcTierEntry(); - } - - return jumpTable; -} - SharedModule ModuleGenerator::finishModule(const ShareableBytes& bytecode) { MOZ_ASSERT(mode() == CompileMode::Once || mode() == CompileMode::Tier1); UniqueCodeSegment codeSegment = finish(bytecode); if (!codeSegment) return nullptr; - UniqueJumpTable maybeJumpTable; - if (mode() == CompileMode::Tier1) { - maybeJumpTable = createJumpTable(*codeSegment); - if (!maybeJumpTable) - return nullptr; - } + JumpTables jumpTables; + if (!jumpTables.init(mode(), *codeSegment, metadataTier_->codeRanges)) + return nullptr; UniqueConstBytes maybeDebuggingBytes; if (env_->debugEnabled()) { MOZ_ASSERT(mode() == CompileMode::Once); Bytes bytes; if (!bytes.resize(masm_.bytesNeeded())) return nullptr; masm_.executableCopy(bytes.begin(), /* flushICache = */ false); maybeDebuggingBytes = js::MakeUnique(Move(bytes)); if (!maybeDebuggingBytes) return nullptr; } - SharedCode code = js_new(Move(codeSegment), *metadata_, Move(maybeJumpTable)); + SharedCode code = js_new(Move(codeSegment), *metadata_, Move(jumpTables)); if (!code) return nullptr; SharedModule module(js_new(Move(assumptions_), *code, Move(maybeDebuggingBytes), Move(linkData_), Move(env_->imports), diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -199,17 +199,16 @@ class MOZ_STACK_CLASS ModuleGenerator void noteCodeRange(uint32_t codeRangeIndex, const CodeRange& codeRange); bool linkCompiledCode(const CompiledCode& code); bool finishTask(CompileTask* task); bool launchBatchCompile(); bool finishOutstandingTask(); bool finishCode(); bool finishMetadata(const ShareableBytes& bytecode); UniqueCodeSegment finish(const ShareableBytes& bytecode); - UniqueJumpTable createJumpTable(const CodeSegment& codeSegment); bool isAsmJS() const { return env_->isAsmJS(); } Tier tier() const { return env_->tier; } CompileMode mode() const { return env_->mode; } bool debugEnabled() const { return env_->debugEnabled(); } public: ModuleGenerator(const CompileArgs& args, ModuleEnvironment* env, diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -386,17 +386,16 @@ Instance::Instance(JSContext* cx, UniqueDebugState debug, UniqueTlsData tlsDataIn, HandleWasmMemoryObject memory, SharedTableVector&& tables, Handle funcImports, const ValVector& globalImports) : compartment_(cx->compartment()), object_(object), - jsJitArgsRectifier_(), code_(code), debug_(Move(debug)), tlsData_(Move(tlsDataIn)), memory_(memory), tables_(Move(tables)), enterFrameTrapsEnabled_(false) { #ifdef DEBUG @@ -407,17 +406,17 @@ Instance::Instance(JSContext* cx, tlsData()->memoryBase = memory ? memory->buffer().dataPointerEither().unwrap() : nullptr; #ifndef WASM_HUGE_MEMORY tlsData()->boundsCheckLimit = memory ? memory->buffer().wasmBoundsCheckLimit() : 0; #endif tlsData()->instance = this; tlsData()->cx = cx; tlsData()->stackLimit = cx->stackLimitForJitCode(JS::StackForUntrustedScript); - tlsData()->jumpTable = code_->jumpTable(); + tlsData()->jumpTable = code_->tieringJumpTable(); Tier callerTier = code_->bestTier(); for (size_t i = 0; i < metadata(callerTier).funcImports.length(); i++) { HandleFunction f = funcImports[i]; const FuncImport& fi = metadata(callerTier).funcImports[i]; FuncImportTls& import = funcImportTls(fi); if (!isAsmJS() && IsExportedWasmFunction(f)) { @@ -503,23 +502,21 @@ Instance::init(JSContext* cx) const void* sigId; if (!lockedSigIdSet->allocateSigId(cx, sig, &sigId)) return false; *addressOfSigId(sig.id) = sigId; } } - if (!metadata(code_->bestTier()).funcImports.empty()) { - JitRuntime* jitRuntime = cx->runtime()->getJitRuntime(cx); - if (!jitRuntime) - return false; - jsJitArgsRectifier_ = jitRuntime->getArgumentsRectifier(); - } - + JitRuntime* jitRuntime = cx->runtime()->getJitRuntime(cx); + if (!jitRuntime) + return false; + jsJitArgsRectifier_ = jitRuntime->getArgumentsRectifier(); + jsJitExceptionHandler_ = jitRuntime->getExceptionTail(); return true; } Instance::~Instance() { compartment_->wasm.unregisterInstance(*this); const FuncImportVector& funcImports = metadata(code().stableTier()).funcImports; diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h --- a/js/src/wasm/WasmInstance.h +++ b/js/src/wasm/WasmInstance.h @@ -41,16 +41,17 @@ namespace wasm { // those instances are being debugged. Instances that are being debugged own // their code. class Instance { JSCompartment* const compartment_; ReadBarrieredWasmInstanceObject object_; jit::TrampolinePtr jsJitArgsRectifier_; + jit::TrampolinePtr jsJitExceptionHandler_; const SharedCode code_; const UniqueDebugState debug_; const UniqueTlsData tlsData_; GCPtrWasmMemoryObject memory_; SharedTableVector tables_; bool enterFrameTrapsEnabled_; // Internal helpers: @@ -94,17 +95,22 @@ class Instance SharedMem memoryBase() const; WasmMemoryObject* memory() const; size_t memoryMappedSize() const; SharedArrayRawBuffer* sharedMemoryBuffer() const; // never null #ifdef JS_SIMULATOR bool memoryAccessInGuardRegion(uint8_t* addr, unsigned numBytes) const; #endif - static size_t offsetOfJSJitArgsRectifier() { return offsetof(Instance, jsJitArgsRectifier_); } + static constexpr size_t offsetOfJSJitArgsRectifier() { + return offsetof(Instance, jsJitArgsRectifier_); + } + static constexpr size_t offsetOfJSJitExceptionHandler() { + return offsetof(Instance, jsJitExceptionHandler_); + } // This method returns a pointer to the GC object that owns this Instance. // Instances may be reached via weak edges (e.g., Compartment::instances_) // so this perform a read-barrier on the returned object unless the barrier // is explicitly waived. WasmInstanceObject* object() const; WasmInstanceObject* objectUnbarriered() const; diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -31,16 +31,17 @@ #include "jit/JitOptions.h" #include "vm/Interpreter.h" #include "vm/String.h" #include "vm/StringBuffer.h" #include "wasm/WasmCompile.h" #include "wasm/WasmInstance.h" #include "wasm/WasmModule.h" #include "wasm/WasmSignalHandlers.h" +#include "wasm/WasmStubs.h" #include "wasm/WasmValidate.h" #include "jsobjinlines.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; @@ -1144,41 +1145,53 @@ WasmInstanceObject::getExportedFunction( { if (ExportMap::Ptr p = instanceObj->exports().lookup(funcIndex)) { fun.set(p->value()); return true; } const Instance& instance = instanceObj->instance(); auto tier = instance.code().stableTier(); - unsigned numArgs = instance.metadata(tier).lookupFuncExport(funcIndex).sig().args().length(); + const Sig& sig = instance.metadata(tier).lookupFuncExport(funcIndex).sig(); + unsigned numArgs = sig.args().length(); // asm.js needs to act like a normal JS function which means having the name // from the original source and being callable as a constructor. + bool optimized = false; if (instance.isAsmJS()) { RootedAtom name(cx, instance.getFuncAtom(cx, funcIndex)); if (!name) return false; + optimized = CanBeJitOptimized(sig); + JSFunction::Flags flags = optimized ? JSFunction::ASMJS_OPT_CTOR : JSFunction::ASMJS_CTOR; fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED, - SingletonObject, JSFunction::ASMJS_CTOR)); - if (!fun) - return false; + SingletonObject, flags)); } else { RootedAtom name(cx, NumberToAtom(cx, funcIndex)); if (!name) return false; - fun.set(NewNativeFunction(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED)); - if (!fun) - return false; + optimized = true; + fun.set(NewNativeFunction(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED, + SingletonObject, JSFunction::WASM_FUN)); } + if (!fun) + return false; + + if (optimized) + fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex)); + else + fun->setAsmJSCtorFuncIndex(funcIndex); + fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT, ObjectValue(*instanceObj)); - fun->setExtendedSlot(FunctionExtended::WASM_FUNC_INDEX_SLOT, Int32Value(funcIndex)); + + void* tlsData = instanceObj->instance().tlsData(); + fun->setExtendedSlot(FunctionExtended::WASM_TLSDATA_SLOT, PrivateValue(tlsData)); if (!instanceObj->exports().putNew(funcIndex, fun)) { ReportOutOfMemory(cx); return false; } return true; } @@ -1269,18 +1282,18 @@ wasm::ExportedFunctionToInstanceObject(J const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT); return &v.toObject().as(); } uint32_t wasm::ExportedFunctionToFuncIndex(JSFunction* fun) { MOZ_ASSERT(IsExportedFunction(fun)); - const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_FUNC_INDEX_SLOT); - return v.toInt32(); + Instance& instance = ExportedFunctionToInstanceObject(fun)->instance(); + return instance.code().lookupFuncIndex(fun); } // ============================================================================ // WebAssembly.Memory class and methods const ClassOps WasmMemoryObject::classOps_ = { nullptr, /* addProperty */ diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -125,16 +125,17 @@ class WasmModuleObject : public NativeOb class WasmInstanceObject : public NativeObject { static const unsigned INSTANCE_SLOT = 0; static const unsigned EXPORTS_OBJ_SLOT = 1; static const unsigned EXPORTS_SLOT = 2; static const unsigned SCOPES_SLOT = 3; static const unsigned INSTANCE_SCOPE_SLOT = 4; + static const ClassOps classOps_; static bool exportsGetterImpl(JSContext* cx, const CallArgs& args); static bool exportsGetter(JSContext* cx, unsigned argc, Value* vp); bool isNewborn() const; static void finalize(FreeOp* fop, JSObject* obj); static void trace(JSTracer* trc, JSObject* obj); // ExportMap maps from function index to exported function object. diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp --- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -290,28 +290,26 @@ Module::finishTier2(UniqueLinkDataTier l // Now that all the code and metadata is valid, make tier 2 code visible and // unblock anyone waiting on it. metadata().commitTier2(); notifyCompilationListeners(); // And we update the jump vector. - void** jumpTable = code().jumpTable(); uint8_t* base = code().segment(Tier::Ion).base(); - for (auto cr : metadata(Tier::Ion).codeRanges) { - if (!cr.isFunction()) - continue; - - // This is a racy write that we just want to be visible, atomically, + // These are racy writes that we just want to be visible, atomically, // eventually. All hardware we care about will do this right. But - // we depend on the compiler not splitting the store. - - jumpTable[cr.funcIndex()] = base + cr.funcTierEntry(); + // we depend on the compiler not splitting the stores hidden inside the + // set*Entry functions. + if (cr.isFunction()) + code().setTieringEntry(cr.funcIndex(), base + cr.funcTierEntry()); + else if (cr.isJitEntry()) + code().setJitEntry(cr.funcIndex(), base + cr.begin()); } } void Module::blockOnTier2Complete() const { auto tiering = tiering_.lock(); while (tiering->active) @@ -1172,18 +1170,21 @@ Module::instantiate(JSContext* cx, *bytecode_, linkData_.linkData(Tier::Baseline), metadata()); if (!codeSegment) { ReportOutOfMemory(cx); return false; } - UniqueJumpTable maybeJumpTable; - code = js_new(Move(codeSegment), metadata(), Move(maybeJumpTable)); + JumpTables jumpTables; + if (!jumpTables.init(CompileMode::Once, *codeSegment, metadata(Tier::Baseline).codeRanges)) + return false; + + code = js_new(Move(codeSegment), metadata(), Move(jumpTables)); if (!code) { ReportOutOfMemory(cx); return false; } } } // To support viewing the source of an instance (Instance::createText), the diff --git a/js/src/wasm/WasmSignalHandlers.cpp b/js/src/wasm/WasmSignalHandlers.cpp --- a/js/src/wasm/WasmSignalHandlers.cpp +++ b/js/src/wasm/WasmSignalHandlers.cpp @@ -794,17 +794,17 @@ HandleMemoryAccess(EMULATOR_CONTEXT* con MOZ_RELEASE_ASSERT(instance.code().containsCodePC(pc)); const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc); if (!memoryAccess) { // If there is no associated MemoryAccess for the faulting PC, this must be // experimental SIMD.js or Atomics. When these are converted to // non-experimental wasm features, this case, as well as outOfBoundsCode, // can be removed. - activation->startWasmInterrupt(ToRegisterState(context)); + MOZ_ALWAYS_TRUE(activation->startWasmInterrupt(ToRegisterState(context))); *ppc = segment->outOfBoundsCode(); return; } MOZ_RELEASE_ASSERT(memoryAccess->insnOffset() == (pc - segment->base())); // On WASM_HUGE_MEMORY platforms, asm.js code may fault. asm.js does not // trap on fault and so has no trap out-of-line path. Instead, stores are @@ -947,17 +947,17 @@ HandleMemoryAccess(EMULATOR_CONTEXT* con const CodeSegment* segment, const Instance& instance, JitActivation* activation, uint8_t** ppc) { MOZ_RELEASE_ASSERT(instance.code().containsCodePC(pc)); const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc); if (!memoryAccess) { // See explanation in the WASM_HUGE_MEMORY HandleMemoryAccess. - activation->startWasmInterrupt(ToRegisterState(context)); + MOZ_ALWAYS_TRUE(activation->startWasmInterrupt(ToRegisterState(context))); *ppc = segment->outOfBoundsCode(); return; } MOZ_RELEASE_ASSERT(memoryAccess->hasTrapOutOfLineCode()); *ppc = memoryAccess->trapOutOfLineCode(segment->base()); } @@ -1410,17 +1410,17 @@ HandleFault(int signum, siginfo_t* info, } #ifdef JS_CODEGEN_ARM if (signum == SIGBUS) { // TODO: We may see a bus error for something that is an unaligned access that // partly overlaps the end of the heap. In this case, it is an out-of-bounds // error and we should signal that properly, but to do so we must inspect // the operand of the failed access. - activation->startWasmInterrupt(ToRegisterState(context)); + MOZ_ALWAYS_TRUE(activation->startWasmInterrupt(ToRegisterState(context))); *ppc = segment->unalignedAccessCode(); return true; } #endif HandleMemoryAccess(context, pc, faultingAddress, segment, *instance, activation, ppc); return true; } @@ -1492,18 +1492,17 @@ wasm::InInterruptibleCode(JSContext* cx, if (!cx->compartment()) return false; *cs = LookupCodeSegment(pc); if (!*cs) return false; - const Code& code = (*cs)->code(); - const CodeRange* codeRange = code.lookupRange(pc); + const CodeRange* codeRange = (*cs)->code().lookupRange(pc); return codeRange && codeRange->isFunction(); } // The return value indicates whether the PC was changed, not whether there was // a failure. static bool RedirectJitCodeToInterruptCheck(JSContext* cx, CONTEXT* context) { @@ -1531,28 +1530,25 @@ RedirectJitCodeToInterruptCheck(JSContex // The checks performed by the !JS_SIMULATOR path happen in // Simulator::handleWasmInterrupt. cx->simulator()->trigger_wasm_interrupt(); #else // Only probe cx->activation() after we know the pc is in wasm code. This // way we don't depend on signal-safe update of cx->activation(). JitActivation* activation = cx->activation()->asJit(); - // fp may be null when first entering wasm code from an entry stub. - uint8_t* fp = ContextToFP(context); - if (!fp) - return false; - // The out-of-bounds/unaligned trap paths which call startWasmInterrupt() go // through function code, so test if already interrupted. These paths are // temporary though, so this case can be removed later. if (activation->isWasmInterrupted()) return false; - activation->startWasmInterrupt(ToRegisterState(context)); + if (!activation->startWasmInterrupt(ToRegisterState(context))) + return false; + *ContextToPC(context) = codeSegment->interruptCode(); #endif return true; } #if !defined(XP_WIN) // For the interrupt signal, pick a signal number that: diff --git a/js/src/wasm/WasmStubs.cpp b/js/src/wasm/WasmStubs.cpp --- a/js/src/wasm/WasmStubs.cpp +++ b/js/src/wasm/WasmStubs.cpp @@ -223,17 +223,17 @@ StoreABIReturn(MacroAssembler& masm, con MOZ_CRASH("Limit"); } } #if defined(JS_CODEGEN_ARM) // The ARM system ABI also includes d15 & s31 in the non volatile float registers. // Also exclude lr (a.k.a. r14) as we preserve it manually) static const LiveRegisterSet NonVolatileRegs = - LiveRegisterSet(GeneralRegisterSet(Registers::NonVolatileMask& + LiveRegisterSet(GeneralRegisterSet(Registers::NonVolatileMask & ~(uint32_t(1) << Registers::lr)), FloatRegisterSet(FloatRegisters::NonVolatileMask | (1ULL << FloatRegisters::d15) | (1ULL << FloatRegisters::s31))); #else static const LiveRegisterSet NonVolatileRegs = LiveRegisterSet(GeneralRegisterSet(Registers::NonVolatileMask), FloatRegisterSet(FloatRegisters::NonVolatileMask)); @@ -365,16 +365,352 @@ GenerateInterpEntry(MacroAssembler& masm masm.PopRegsInMask(NonVolatileRegs); MOZ_ASSERT(masm.framePushed() == 0); masm.ret(); return FinishOffsets(masm, offsets); } +#ifdef JS_PUNBOX64 +static const ValueOperand ScratchValIonEntry = ValueOperand(ABINonArgReg0); +#else +static const ValueOperand ScratchValIonEntry = ValueOperand(ABINonArgReg0, ABINonArgReg1); +#endif +static const Register ScratchIonEntry = ABINonArgReg2; + +bool +wasm::CanBeJitOptimized(const Sig& sig) +{ + // Don't make a fast path for asm.js functions that take or return SIMD + // objects, to avoid complex boxing/unboxing on the stack. + if (IsSimdType(sig.ret())) + return false; + for (ValType t : sig.args()) { + if (IsSimdType(t)) + return false; + } + return true; +} + +// Load instance's TLS from the callee. +static void +GenerateJitEntryLoadTls(MacroAssembler& masm, unsigned frameSize) +{ + // ScratchIonEntry := callee => JSFunction* + unsigned offset = frameSize + JitFrameLayout::offsetOfCalleeToken(); + masm.loadFunctionFromCalleeToken(Address(masm.getStackPointer(), offset), ScratchIonEntry); + + // ScratchValIonEntry := callee->getExtendedSlot(WASM_TLSDATA_SLOT) + // => Private(TlsData*) + offset = FunctionExtended::offsetOfExtendedSlot(FunctionExtended::WASM_TLSDATA_SLOT); + masm.loadValue(Address(ScratchIonEntry, offset), ScratchValIonEntry); + + // ScratchIonEntry := ScratchIonEntry->toPrivate() => TlsData* + masm.unboxPrivate(ScratchValIonEntry, WasmTlsReg); + // \o/ +} + +// Creates a JS fake exit frame for wasm, so the frame iterators just use +// JSJit frame iteration. +static void +GenerateJitEntryThrow(MacroAssembler& masm, unsigned frameSize) +{ + MOZ_ASSERT(masm.framePushed() == frameSize); + + GenerateJitEntryLoadTls(masm, frameSize); + + masm.freeStack(frameSize); + + masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, cx)), ScratchIonEntry); + masm.enterFakeExitFrame(ScratchIonEntry, ScratchIonEntry, ExitFrameType::WasmJitEntry); + + masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, instance)), ScratchIonEntry); + masm.loadPtr(Address(ScratchIonEntry, Instance::offsetOfJSJitExceptionHandler()), + ScratchIonEntry); + masm.jump(ScratchIonEntry); +} + +// Generate a stub that enters wasm from a jit code caller via the jit ABI. +static bool +GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex, const FuncExport& fe, + Offsets* offsets) +{ + MOZ_ASSERT(CanBeJitOptimized(fe.sig())); + + RegisterOrSP sp = masm.getStackPointer(); + + GenerateJitEntryPrologue(masm, offsets); + + // The jit caller has set up the following stack layout (sp grows to the + // left): + // <-- retAddr | descriptor | callee | argc | this | arg1..N + + unsigned normalBytesNeeded = StackArgBytes(fe.sig().args()); + + MIRTypeVector coerceArgTypes; + MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Int32)); + MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer)); + MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer)); + unsigned oolBytesNeeded = StackArgBytes(coerceArgTypes); + + unsigned bytesNeeded = Max(normalBytesNeeded, oolBytesNeeded); + + // Note the jit caller ensures the stack is aligned *after* the call + // instruction. + unsigned frameSize = StackDecrementForCall(WasmStackAlignment, 0, bytesNeeded); + + // Reserve stack space for wasm ABI arguments, set up like this: + // <-- ABI args | padding + masm.reserveStack(frameSize); + + GenerateJitEntryLoadTls(masm, frameSize); + + if (fe.sig().hasI64ArgOrRet()) { + masm.call(SymbolicAddress::ReportInt64JSCall); + GenerateJitEntryThrow(masm, frameSize); + return FinishOffsets(masm, offsets); + } + + FloatRegister scratchF = ABINonArgDoubleReg; + Register scratchG = ScratchIonEntry; + ValueOperand scratchV = ScratchValIonEntry; + + // We do two loops: + // - one loop up-front will make sure that all the Value tags fit the + // expected signature argument types. If at least one inline conversion + // fails, we just jump to the OOL path which will call into C++. Inline + // conversions are ordered in the way we expect them to happen the most. + // - the second loop will unbox the arguments into the right registers. + Label oolCall; + for (size_t i = 0; i < fe.sig().args().length(); i++) { + unsigned jitArgOffset = frameSize + JitFrameLayout::offsetOfActualArg(i); + masm.loadValue(Address(sp, jitArgOffset), scratchV); + + Label next; + switch (fe.sig().args()[i]) { + case ValType::I32: { + Register tag = masm.splitTagForTest(scratchV); + + // For int32 inputs, just skip. + masm.branchTestInt32(Assembler::Equal, tag, &next); + + // For double inputs, unbox, truncate and store back. + Label storeBack, notDouble; + masm.branchTestDouble(Assembler::NotEqual, tag, ¬Double); + masm.unboxDouble(scratchV, scratchF); + masm.branchTruncateDoubleMaybeModUint32(scratchF, scratchG, &oolCall); + masm.jump(&storeBack); + masm.bind(¬Double); + + // For null or undefined, store 0. + Label nullOrUndefined, notNullOrUndefined; + masm.branchTestUndefined(Assembler::Equal, tag, &nullOrUndefined); + masm.branchTestNull(Assembler::NotEqual, tag, ¬NullOrUndefined); + masm.bind(&nullOrUndefined); + masm.mov(ImmWord(0), scratchG); + masm.jump(&storeBack); + masm.bind(¬NullOrUndefined); + + // For booleans, store the number value back. Other types (symbol, + // object, strings) go to the C++ call. + masm.branchTestBoolean(Assembler::NotEqual, tag, &oolCall); + masm.unboxBoolean(scratchV, scratchG); + // fallthrough: + + masm.bind(&storeBack); + masm.boxNonDouble(JSVAL_TYPE_INT32, scratchG, scratchV); + masm.storeValue(scratchV, Address(sp, jitArgOffset)); + break; + } + case ValType::F32: + case ValType::F64: { + // Note we can reuse the same code for f32/f64 here, since for the + // case of f32, the conversion of f64 to f32 will happen in the + // second loop. + Register tag = masm.splitTagForTest(scratchV); + + // For double inputs, just skip (to next or coercion). + masm.branchTestDouble(Assembler::Equal, tag, &next); + + // For int32 inputs, convert and rebox. + Label storeBack, notInt32; + masm.branchTestInt32(Assembler::NotEqual, scratchV, ¬Int32); + masm.int32ValueToDouble(scratchV, scratchF); + masm.jump(&storeBack); + masm.bind(¬Int32); + + // For undefined (missing argument), store NaN. + Label notUndefined; + masm.branchTestUndefined(Assembler::NotEqual, tag, ¬Undefined); + masm.loadConstantDouble(JS::GenericNaN(), scratchF); + masm.jump(&storeBack); + masm.bind(¬Undefined); + + // +null is 0. + Label notNull; + masm.branchTestNull(Assembler::NotEqual, tag, ¬Null); + masm.loadConstantDouble(0.0, scratchF); + masm.jump(&storeBack); + masm.bind(¬Null); + + // For booleans, store the number value back. Other types (symbol, + // object, strings) go to the C++ call. + masm.branchTestBoolean(Assembler::NotEqual, tag, &oolCall); + masm.boolValueToDouble(scratchV, scratchF); + // fallthrough: + + masm.bind(&storeBack); + masm.boxDouble(scratchF, scratchV, scratchF); + masm.storeValue(scratchV, Address(sp, jitArgOffset)); + break; + } + default: { + MOZ_CRASH("unexpected argument type when calling from the jit"); + } + } + masm.nopAlign(CodeAlignment); + masm.bind(&next); + } + + Label rejoinBeforeCall; + masm.bind(&rejoinBeforeCall); + + // Convert all the expected values to unboxed values on the stack. + for (ABIArgValTypeIter iter(fe.sig().args()); !iter.done(); iter++) { + unsigned jitArgOffset = frameSize + JitFrameLayout::offsetOfActualArg(iter.index()); + Address argv(sp, jitArgOffset); + bool isStackArg = iter->kind() == ABIArg::Stack; + switch (iter.mirType()) { + case MIRType::Int32: { + Register target = isStackArg ? ScratchIonEntry : iter->gpr(); + masm.unboxInt32(argv, target); + if (isStackArg) + masm.storePtr(target, Address(sp, iter->offsetFromArgBase())); + break; + } + case MIRType::Float32: { + FloatRegister target = isStackArg ? ABINonArgDoubleReg : iter->fpu(); + masm.unboxDouble(argv, ABINonArgDoubleReg); + masm.convertDoubleToFloat32(ABINonArgDoubleReg, target); + if (isStackArg) + masm.storeFloat32(target, Address(sp, iter->offsetFromArgBase())); + break; + } + case MIRType::Double: { + FloatRegister target = isStackArg ? ABINonArgDoubleReg : iter->fpu(); + masm.unboxDouble(argv, target); + if (isStackArg) + masm.storeDouble(target, Address(sp, iter->offsetFromArgBase())); + break; + } + default: { + MOZ_CRASH("unexpected input argument when calling from jit"); + } + } + } + + // Setup wasm register state. + masm.loadWasmPinnedRegsFromTls(); + + // Call into the real function. Note that, due to the throw stub, fp, tls + // and pinned registers may be clobbered. + masm.assertStackAlignment(WasmStackAlignment); + masm.call(CallSiteDesc(CallSiteDesc::Func), fe.funcIndex()); + masm.assertStackAlignment(WasmStackAlignment); + + // If fp is equal to the FailFP magic value (set by the throw stub), then + // report the exception to the JIT caller by jumping into the exception + // stub; otherwise the FP value is still set to the parent ion frame value. + Label exception; + masm.branchPtr(Assembler::Equal, FramePointer, Imm32(FailFP), &exception); + + // Pop arguments. + masm.freeStack(frameSize); + + // Store the return value in the JSReturnOperand. + switch (fe.sig().ret()) { + case ExprType::Void: + masm.moveValue(UndefinedValue(), JSReturnOperand); + break; + case ExprType::I32: + masm.boxNonDouble(JSVAL_TYPE_INT32, ReturnReg, JSReturnOperand); + break; + case ExprType::F32: + masm.canonicalizeFloat(ReturnFloat32Reg); + masm.convertFloat32ToDouble(ReturnFloat32Reg, ReturnDoubleReg); + masm.boxDouble(ReturnDoubleReg, JSReturnOperand, ScratchDoubleReg); + break; + case ExprType::F64: + masm.canonicalizeDouble(ReturnDoubleReg); + masm.boxDouble(ReturnDoubleReg, JSReturnOperand, ScratchDoubleReg); + break; + case ExprType::I64: + case ExprType::I8x16: + case ExprType::I16x8: + case ExprType::I32x4: + case ExprType::B8x16: + case ExprType::B16x8: + case ExprType::B32x4: + case ExprType::F32x4: + MOZ_CRASH("unexpected return type when calling from ion to wasm"); + case ExprType::Limit: + MOZ_CRASH("Limit"); + } + + MOZ_ASSERT(masm.framePushed() == 0); + masm.ret(); + + // Generate an OOL call to the C++ conversion path. + if (fe.sig().args().length()) { + masm.bind(&oolCall); + masm.setFramePushed(frameSize); + + ABIArgMIRTypeIter argsIter(coerceArgTypes); + + // argument 0: function export index. + if (argsIter->kind() == ABIArg::GPR) + masm.movePtr(ImmWord(funcExportIndex), argsIter->gpr()); + else + masm.storePtr(ImmWord(funcExportIndex), Address(sp, argsIter->offsetFromArgBase())); + argsIter++; + + // argument 1: tlsData + if (argsIter->kind() == ABIArg::GPR) + masm.movePtr(WasmTlsReg, argsIter->gpr()); + else + masm.storePtr(WasmTlsReg, Address(sp, argsIter->offsetFromArgBase())); + argsIter++; + + // argument 2: effective address of start of argv + Address argv(sp, masm.framePushed() + JitFrameLayout::offsetOfActualArg(0)); + if (argsIter->kind() == ABIArg::GPR) { + masm.computeEffectiveAddress(argv, argsIter->gpr()); + } else { + masm.computeEffectiveAddress(argv, ScratchIonEntry); + masm.storePtr(ScratchIonEntry, Address(sp, argsIter->offsetFromArgBase())); + } + argsIter++; + MOZ_ASSERT(argsIter.done()); + + masm.assertStackAlignment(ABIStackAlignment); + masm.call(SymbolicAddress::CoerceInPlace_JitEntry); + masm.assertStackAlignment(ABIStackAlignment); + + masm.branchTest32(Assembler::NonZero, ReturnReg, ReturnReg, &rejoinBeforeCall); + } + + // Prepare to throw: reload WasmTlsReg from the frame. + masm.bind(&exception); + masm.setFramePushed(frameSize); + GenerateJitEntryThrow(masm, frameSize); + + return FinishOffsets(masm, offsets); +} + static void StackCopy(MacroAssembler& masm, MIRType type, Register scratch, Address src, Address dst) { if (type == MIRType::Int32) { masm.load32(src, scratch); masm.store32(scratch, dst); } else if (type == MIRType::Int64) { #if JS_BITS_PER_WORD == 32 @@ -845,17 +1181,17 @@ GenerateImportJitExit(MacroAssembler& ma if (oolConvert.used()) { masm.bind(&oolConvert); masm.setFramePushed(nativeFramePushed); // Coercion calls use the following stack layout (sp grows to the left): // | args | padding | Value argv[1] | padding | exit Frame | MIRTypeVector coerceArgTypes; - JS_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer)); + MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer)); unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(Value)); MOZ_ASSERT(nativeFramePushed >= offsetToCoerceArgv + sizeof(Value)); AssertStackAlignment(masm, ABIStackAlignment); // Store return value into argv[0] masm.storeValue(JSReturnOperand, Address(masm.getStackPointer(), offsetToCoerceArgv)); // From this point, it's safe to reuse the scratch register (which @@ -1380,22 +1716,31 @@ wasm::GenerateStubs(const ModuleEnvironm if (!GenerateImportJitExit(masm, fi, &throwLabel, &jitOffsets)) return false; if (!code->codeRanges.emplaceBack(funcIndex, jitOffsets)) return false; } JitSpew(JitSpew_Codegen, "# Emitting wasm export stubs"); - for (const FuncExport& fe : exports) { + for (size_t i = 0; i < exports.length(); i++) { + const FuncExport& fe = exports[i]; + Offsets offsets; if (!GenerateInterpEntry(masm, fe, &offsets)) return false; if (!code->codeRanges.emplaceBack(CodeRange::InterpEntry, fe.funcIndex(), offsets)) return false; + + if (!CanBeJitOptimized(fe.sig())) + continue; + if (!GenerateJitEntry(masm, i, fe, &offsets)) + return false; + if (!code->codeRanges.emplaceBack(CodeRange::JitEntry, fe.funcIndex(), offsets)) + return false; } JitSpew(JitSpew_Codegen, "# Emitting wasm trap stubs"); for (Trap trap : MakeEnumeratedRange(Trap::Limit)) { switch (trap) { case Trap::Unreachable: break; diff --git a/js/src/wasm/WasmStubs.h b/js/src/wasm/WasmStubs.h --- a/js/src/wasm/WasmStubs.h +++ b/js/src/wasm/WasmStubs.h @@ -31,12 +31,15 @@ GenerateBuiltinThunk(jit::MacroAssembler extern bool GenerateImportFunctions(const ModuleEnvironment& env, const FuncImportVector& imports, CompiledCode* code); extern bool GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& imports, const FuncExportVector& exports, CompiledCode* code); +extern bool +CanBeJitOptimized(const Sig& sig); + } // namespace wasm } // namespace js #endif // wasm_stubs_h diff --git a/js/src/wasm/WasmTypes.cpp b/js/src/wasm/WasmTypes.cpp --- a/js/src/wasm/WasmTypes.cpp +++ b/js/src/wasm/WasmTypes.cpp @@ -788,17 +788,17 @@ CodeRange::CodeRange(Kind kind, uint32_t ret_(0), end_(offsets.end), kind_(kind) { u.funcIndex_ = funcIndex; u.func.lineOrBytecode_ = 0; u.func.beginToNormalEntry_ = 0; u.func.beginToTierEntry_ = 0; - MOZ_ASSERT(kind == InterpEntry); + MOZ_ASSERT(isEntry()); MOZ_ASSERT(begin_ <= end_); } CodeRange::CodeRange(Kind kind, CallableOffsets offsets) : begin_(offsets.begin), ret_(offsets.ret), end_(offsets.end), kind_(kind) diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h --- a/js/src/wasm/WasmTypes.h +++ b/js/src/wasm/WasmTypes.h @@ -1048,18 +1048,19 @@ typedef Vector