using mozilla::EnumeratedArray; using mozilla::HashGeneric; using mozilla::MakeEnumeratedRange; using mozilla::Maybe; using mozilla::Nothing; using mozilla::Some;
// ============================================================================ // WebAssembly builtin C++ functions called from wasm code to implement internal // wasm operations: type descriptions.
// ============================================================================ // WebAssembly builtin C++ functions called from wasm code to implement internal // wasm operations: implementations.
// This utility function can only be called for builtins that are called // directly from wasm code. static JitActivation* CallingActivation(JSContext* cx) {
Activation* act = cx->activation();
MOZ_ASSERT(act->asJit()->hasWasmExitFP()); return act->asJit();
}
// The debug trap stub is the innermost frame. It's return address is the // actual trap site.
CallSite site;
MOZ_ALWAYS_TRUE(code.lookupCallSite(fp->returnAddress(), &site));
// Advance to the actual trapping frame.
fp = fp->wasmCaller();
DebugFrame* debugFrame = DebugFrame::from(fp);
if (site.kind() == CallSiteKind::EnterFrame) { if (!instance->debug().enterFrameTrapsEnabled()) { returntrue;
}
debugFrame->setIsDebuggee();
debugFrame->observe(cx); if (!DebugAPI::onEnterFrame(cx, js::AbstractFramePtr(debugFrame))) { if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn(); // Ignoring forced return because changing code execution order is // not yet implemented in the wasm baseline. // TODO properly handle forced return and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected resumption value from onEnterFrame");
} returnfalse;
} returntrue;
} if (site.kind() == CallSiteKind::LeaveFrame ||
site.kind() == CallSiteKind::CollapseFrame) { if (site.kind() == CallSiteKind::LeaveFrame &&
!debugFrame->updateReturnJSValue(cx)) { returnfalse;
} if (site.kind() == CallSiteKind::CollapseFrame) {
debugFrame->discardReturnJSValue();
} bool ok = DebugAPI::onLeaveFrame(cx, js::AbstractFramePtr(debugFrame),
(const jsbytecode*)nullptr, true);
debugFrame->leave(cx); return ok;
}
DebugState& debug = instance->debug();
MOZ_ASSERT(debug.hasBreakpointTrapAtOffset(site.lineOrBytecode())); if (debug.stepModeEnabled(debugFrame->funcIndex())) { if (!DebugAPI::onSingleStep(cx)) { if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn(); // TODO properly handle forced return.
JS_ReportErrorASCII(cx, "Unexpected resumption value from onSingleStep");
} returnfalse;
}
} if (debug.hasBreakpointSite(site.lineOrBytecode())) { if (!DebugAPI::onTrap(cx)) { if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn(); // TODO properly handle forced return.
JS_ReportErrorASCII(
cx, "Unexpected resumption value from breakpoint handler");
} returnfalse;
}
} returntrue;
}
// Check if the pending exception, if any, is catchable by wasm. static WasmExceptionObject* GetOrWrapWasmException(JitActivation* activation,
JSContext* cx) { if (!cx->isExceptionPending()) { return nullptr;
}
// Traps are generally not catchable as wasm exceptions. The only case in // which they are catchable is for Trap::ThrowReported, which the wasm // compiler uses to throw exceptions and is the source of exceptions from C++. bool isTrapThrowReported =
activation->isWasmTrapping() &&
activation->wasmTrapData().trap == Trap::ThrowReported; if (activation->isWasmTrapping() &&
!isTrapThrowReported #ifdef ENABLE_WASM_JSPI
&& activation->wasmTrapData().trap != Trap::ThrowSuspendError #endif
) { return nullptr;
}
if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory()) { return nullptr;
}
mozilla::Maybe<gc::AutoSuppressGC> suppress; if (isTrapThrowReported) {
suppress.emplace(cx);
}
// Write the exception out here to exn to avoid having to get the pending // exception and checking for OOM multiple times.
RootedValue exn(cx); if (cx->getPendingException(&exn)) { // Check if a JS exception originated from a wasm trap. if (exn.isObject() && exn.toObject().is<ErrorObject>()) {
ErrorObject& err = exn.toObject().as<ErrorObject>(); if (err.fromWasmTrap()) { return nullptr;
}
}
// Get or create a wasm exception to represent the pending exception
Rooted<WasmExceptionObject*> wasmExn(cx); if (exn.isObject() && exn.toObject().is<WasmExceptionObject>()) { // We're already throwing a wasm exception
wasmExn = &exn.toObject().as<WasmExceptionObject>();
// If wasm is rethrowing a wrapped JS value, then set the pending // exception on cx to be the wrapped value. This will ensure that if we // unwind out of wasm the wrapper exception will not escape. // // We also do this here, and not at the end of wasm::HandleThrow so that // any DebugAPI calls see the wrapped JS value, not the wrapper // exception. if (wasmExn->isWrappedJSValue()) { // Re-use exn to avoid needing a new root
exn = wasmExn->wrappedJSValue();
cx->setPendingException(exn, nullptr);
}
} else { // Wrap all thrown JS values in a wasm exception. This is required so // that all exceptions have tags, and the 'null' JS value becomes a // non-null wasm exception.
wasmExn = WasmExceptionObject::wrapJSValue(cx, exn);
}
// Don't turn this into a release assert - TlsContext.get() can be expensive.
MOZ_ASSERT(cx == TlsContext.get());
// Neither this routine nor the stub that calls it make any attempt to // communicate roots to the GC. This is OK because we will only be // compiling code here, which shouldn't GC. Nevertheless ..
JS::AutoAssertNoGC nogc(cx);
// Similarly, don't turn this into a release assert.
MOZ_ASSERT(instance == GetNearestEffectiveInstance(fp));
// Figure out the requesting funcIndex. We could add a field to the // Instance and, in the slow path of BaseCompiler::addHotnessCheck, write it // in there. That would avoid having to call LookupCodeBlock here, but (1) // LookupCodeBlock is pretty cheap and (2) this would make hotness checks // larger. It doesn't seem like a worthwhile tradeoff. void* resumePC = fp->returnAddress(); const CodeRange* codeRange; const CodeBlock* codeBlock = LookupCodeBlock(resumePC, &codeRange);
MOZ_RELEASE_ASSERT(codeBlock && codeRange);
uint32_t funcIndex = codeRange->funcIndex();
// See BaseCompiler::addHotnessCheck for rationale. If this fails, and // `counter` is a very large negative number (close to -2^31), it may be that // a hotness check didn't have its step patched in.
int32_t counter = instance->readHotnessCounter(funcIndex);
MOZ_RELEASE_ASSERT(counter >= -127 && counter <= -1);
// Function `funcIndex` is requesting tier-up. This can go one of three ways: // - the request is a duplicate -- ignore // - tier-up compilation succeeds -- we hope // - tier-up compilation fails (eg, OOMs). // We have no feasible way to recover. // // Regardless of the outcome, we want to defer duplicate requests as long as // possible. So set the counter to "infinity" right now.
instance->resetHotnessCounter(funcIndex);
// Submit the collected profiling information for call_ref to be available // for compilation.
instance->submitCallRefHints(funcIndex);
if (!codeBlock->code->tryClaimTierUp(funcIndex)) { return;
}
// Try to Ion-compile it. Note that `ok == true` signifies either // "duplicate request" or "not a duplicate, and compilation succeeded". bool ok = codeBlock->code->requestTierUp(funcIndex);
// If compilation failed, there's no feasible way to recover. We use the // 'off thread' logging mechanism to avoid possibly triggering a GC. if (!ok) {
wasm::LogOffThread("Failed to tier-up function=%d in instance=%p.",
funcIndex, instance);
}
}
// Unwind the activation in response to a thrown exception. This function is // responsible for notifying the debugger of each unwound frame. // // This function will look for try-catch handlers and, if not trapping or // throwing an uncatchable exception, will write the handler info in |*rfe|. // // If no try-catch handler is found, return to the caller to continue unwinding // JS JIT frames. void wasm::HandleExceptionWasm(JSContext* cx, JitFrameIter& iter,
jit::ResumeFromException* rfe) {
MOZ_ASSERT(!iter.done());
MOZ_ASSERT(iter.isWasm());
MOZ_ASSERT(CallingActivation(cx) == iter.activation());
MOZ_ASSERT(cx->activation()->asJit()->hasWasmExitFP());
MOZ_ASSERT(rfe->kind == ExceptionResumeKind::EntryFrame); #ifdef ENABLE_WASM_JSPI // This should always run on the main stack. The throw stub should perform // a stack switch if that's not the case.
MOZ_ASSERT(!cx->wasm().onContStack()); #endif
JitActivation* activation = CallingActivation(cx);
#ifdef DEBUG auto onExit = mozilla::MakeScopeExit([cx] {
MOZ_ASSERT(!cx->activation()->asJit()->isWasmTrapping(), "unwinding clears the trapping state");
MOZ_ASSERT(!cx->activation()->asJit()->hasWasmExitFP(), "unwinding leaves no wasm exit fp");
}); #endif
// Make the iterator adjust the JitActivation so that each popped frame // will not be visible to other FrameIters that are created while we're // unwinding. // // This is necessary to prevent a wasm::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).
iter.asWasm().setIsLeavingFrames();
// Get (or wrap) the exception that we'll search for catch handlers for.
Rooted<WasmExceptionObject*> wasmExn(cx,
GetOrWrapWasmException(activation, cx));
#ifdef ENABLE_WASM_JSPI // Track the previous stack we were on so that we can free it once we've // unwound past it.
wasm::ContStack* wasmPreviousStack = nullptr; #endif
for (; !iter.done() && iter.isWasm(); ++iter) { // Wasm code can enter same-compartment realms, so reset cx->realm to // this frame's realm.
WasmFrameIter& wasmFrame = iter.asWasm();
cx->setRealmForJitExceptionHandler(wasmFrame.instance()->realm());
// If we have unwound over a stack switch, then unwind the previous stack. #ifdef ENABLE_WASM_JSPI
wasm::ContStack* wasmStack = wasmFrame.contStack(); if (wasmFrame.currentFrameStackSwitched()) { if (wasmPreviousStack) {
ContStack::unwind(wasmPreviousStack->handlers());
}
wasmPreviousStack = wasmStack;
} else {
MOZ_RELEASE_ASSERT(wasmStack == wasmPreviousStack);
} #endif
// Only look for an exception handler if there's a catchable exception. if (wasmExn) { const wasm::Code& code = wasmFrame.instance()->code(); const uint8_t* pc = wasmFrame.resumePCinCurrentFrame(); const wasm::CodeBlock* codeBlock = nullptr; const wasm::TryNote* tryNote =
FindNonDelegateTryNote(code, pc, &codeBlock);
if (tryNote) { // Skip tryNote if pc is at return stub generated by // wasmCollapseFrameSlow.
CallSite site; if (code.lookupCallSite((void*)pc, &site) &&
site.kind() == CallSiteKind::ReturnStub) { continue;
}
#ifdef ENABLE_WASM_JSPI if (wasmStack) {
rfe->stackTarget = &wasmStack->stackTarget();
rfe->baseHandlers = wasmStack->findBaseHandlers();
MOZ_ASSERT(rfe->baseHandlers);
} else { # ifdef _WIN32 // wasm::GenerateJumpToCatchHandler will switch the stack limit // fields and needs the WIN32 TIB fields to have the latest values.
cx->wasm().updateWin32TibFields(); # endif // _WIN32
rfe->stackTarget = &cx->wasm().mainStackTarget();
rfe->baseHandlers = nullptr;
} #endif// ENABLE_WASM_JSPI
// Maintain the invariant that trapping and exit frame state is always // clear when we return back into wasm JIT code. if (activation->isWasmTrapping()) { // This will clear the exit fp and suspender state.
activation->finishWasmTrap();
} else { // We need to manually clear the exit fp and stack state.
activation->setWasmExitFP(nullptr);
} return;
}
}
if (wasmFrame.debugEnabled()) {
DebugFrame* frame = wasmFrame.debugFrame();
frame->clearReturnJSValue();
// Assume ResumeMode::Terminate if no exception is pending -- // no onExceptionUnwind handlers must be fired. if (cx->isExceptionPending()) { if (!DebugAPI::onExceptionUnwind(cx, AbstractFramePtr(frame))) { if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn(); // Unexpected trap return -- raising error since throw recovery // is not yet implemented in the wasm baseline. // TODO properly handle forced return and resume wasm execution.
JS_ReportErrorASCII(
cx, "Unexpected resumption value from onExceptionUnwind");
wasmExn = nullptr;
}
}
}
bool ok = DebugAPI::onLeaveFrame(cx, AbstractFramePtr(frame),
(const jsbytecode*)nullptr, false); if (ok) { // Unexpected success from the handler onLeaveFrame -- raising error // since throw recovery is not yet implemented in the wasm baseline. // TODO properly handle success and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected success from onLeaveFrame");
wasmExn = nullptr;
}
frame->leave(cx);
}
}
#ifdef ENABLE_WASM_JSPI // Assert that the entry into wasm code was on the system stack.
MOZ_RELEASE_ASSERT(!wasmPreviousStack); #endif
// Assert that any pending exception escaping to non-wasm code is not a // wrapper exception object #ifdef DEBUG if (cx->isExceptionPending()) {
Rooted<Value> pendingException(cx, cx->getPendingExceptionUnwrapped());
MOZ_ASSERT_IF(pendingException.isObject() &&
pendingException.toObject().is<WasmExceptionObject>(),
!pendingException.toObject()
.as<WasmExceptionObject>()
.isWrappedJSValue());
} #endif
}
staticvoid* WasmHandleThrow(jit::ResumeFromException* rfe) { // Return a pointer to the exception handler trampoline code to jump to from // the throw stub.
JSContext* cx = TlsContext.get(); #ifdef ENABLE_WASM_JSPI
MOZ_ASSERT(!cx->wasm().onContStack()); #endif
jit::HandleException(rfe); return cx->runtime()->jitRuntime()->getExceptionTailReturnValueCheck().value;
}
// Has the same return-value convention as HandleTrap(). staticvoid* CheckInterrupt(JSContext* cx, JitActivation* activation) {
ResetInterruptState(cx);
if (!CheckForInterrupt(cx)) { return nullptr;
}
void* resumePC = activation->wasmTrapData().resumePC;
activation->finishWasmTrap(); // Do not reset the exit frame pointer and suspender, or else we won't switch // back to the main stack. return resumePC;
}
// The calling convention between this function and its caller in the stub // generated by GenerateTrapExit() is: // - return nullptr if the stub should jump to the throw stub to unwind // the activation; // - return the (non-null) resumePC that should be jumped if execution should // resume after the trap. staticvoid* WasmHandleTrap() {
JSContext* cx = TlsContext.get(); // Cold code
JitActivation* activation = CallingActivation(cx); #ifdef ENABLE_WASM_JSPI
MOZ_ASSERT(!cx->wasm().onContStack()); #endif
switch (activation->wasmTrapData().trap) { case Trap::Unreachable: {
ReportTrapError(cx, JSMSG_WASM_UNREACHABLE); return nullptr;
} case Trap::IntegerOverflow: {
ReportTrapError(cx, JSMSG_WASM_INTEGER_OVERFLOW); return nullptr;
} case Trap::InvalidConversionToInteger: {
ReportTrapError(cx, JSMSG_WASM_INVALID_CONVERSION); return nullptr;
} case Trap::IntegerDivideByZero: {
ReportTrapError(cx, JSMSG_WASM_INT_DIVIDE_BY_ZERO); return nullptr;
} case Trap::IndirectCallToNull: {
ReportTrapError(cx, JSMSG_WASM_IND_CALL_TO_NULL); return nullptr;
} case Trap::IndirectCallBadSig: {
ReportTrapError(cx, JSMSG_WASM_IND_CALL_BAD_SIG); return nullptr;
} case Trap::NullPointerDereference: {
ReportTrapError(cx, JSMSG_WASM_DEREF_NULL); return nullptr;
} case Trap::BadCast: {
ReportTrapError(cx, JSMSG_WASM_BAD_CAST); return nullptr;
} case Trap::OutOfBounds: { const wasm::TrapData& td = activation->wasmTrapData(); if (JS::Prefs::wasm_memory_debugging() && td.faultInfo.isSome()) {
UniqueChars memIdxStr(JS_smprintf("%u", td.faultInfo->memoryIndex));
UniqueChars offsetStr(
JS_smprintf("%llu", (unsignedlonglong)td.faultInfo->byteOffset)); if (!memIdxStr || !offsetStr) {
ReportOutOfMemory(cx); return nullptr;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS_AT, memIdxStr.get(),
offsetStr.get()); if (!cx->isThrowingOutOfMemory()) {
wasm::MarkPendingExceptionAsTrap(cx);
}
} else {
ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
} return nullptr;
} case Trap::UnalignedAccess: {
ReportTrapError(cx, JSMSG_WASM_UNALIGNED_ACCESS); return nullptr;
} case Trap::CheckInterrupt: return CheckInterrupt(cx, activation); case Trap::StackOverflow: {
AutoCheckRecursionLimit recursion(cx); if (!recursion.check(cx)) { return nullptr;
}
ReportTrapError(cx, JSMSG_OVER_RECURSED); return nullptr;
} #ifdef ENABLE_WASM_JSPI case Trap::ThrowSuspendError: {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_JSPI_SUSPEND_ERROR); return nullptr;
} #endif case Trap::Unimplemented: {
ReportTrapError(cx, JSMSG_WASM_UNIMPLEMENTED); return nullptr;
} case Trap::ThrowReported: // Error was already reported under another name. return nullptr; case Trap::Limit: break;
}
for (size_t i = 0; i < funcType.args().length(); i++) {
HandleValue arg = HandleValue::fromMarkedLocation(&argv[i]); switch (funcType.args()[i].kind()) { case ValType::I32: {
int32_t i32; if (!ToInt32(cx, arg, &i32)) { returnfalse;
}
argv[i] = Int32Value(i32); break;
} case ValType::I64: { // In this case we store a BigInt value as there is no value type // corresponding directly to an I64. The conversion to I64 happens // in the JIT entry stub.
BigInt* bigint = ToBigInt(cx, arg); if (!bigint) { returnfalse;
}
argv[i] = BigIntValue(bigint); break;
} case ValType::F32: case ValType::F64: { double dbl; if (!ToNumber(cx, arg, &dbl)) { returnfalse;
} // No need to convert double-to-float for f32, it's done inline // in the wasm stub later.
argv[i] = DoubleValue(dbl); break;
} case ValType::Ref: { // Guarded against by temporarilyUnsupportedReftypeForEntry()
MOZ_RELEASE_ASSERT(funcType.args()[i].refType().isExtern()); // Perform any fallible boxing that may need to happen so that the JIT // code does not need to. if (AnyRef::valueNeedsBoxing(arg)) {
JSObject* boxedValue = AnyRef::boxValue(cx, arg); if (!boxedValue) { returnfalse;
}
argv[i] = ObjectOrNullValue(boxedValue);
} break;
} case ValType::V128: { // Guarded against by hasV128ArgOrRet()
MOZ_CRASH("unexpected input argument in CoerceInPlace_JitEntry");
} default: {
MOZ_CRASH("unexpected input argument in CoerceInPlace_JitEntry");
}
}
}
returntrue;
}
// Allocate a BigInt without GC, corresponds to the similar VMFunction. static BigInt* AllocateBigIntTenuredNoGC() {
JSContext* cx = TlsContext.get(); // Cold code (the caller is elaborate)
BigInt* bi = cx->newCell<BigInt, NoGC>(gc::Heap::Tenured); if (!bi) { // The NoGC version doesn't report OOM so we have to do this ourselves.
ReportOutOfMemory(cx); return nullptr;
} return bi;
}
template <typename T> static T Ceil(T value) { // Perform addition to ensure quiet NaNs are returned. Also try to keep the // NaN payload intact, so don't directly return a specific quiet NaN value. if (std::isnan(value)) { return value + value;
} return std::ceil(value);
}
template <typename T> static T Floor(T value) { // Perform addition to ensure quiet NaNs are returned. Also try to keep the // NaN payload intact, so don't directly return a specific quiet NaN value. if (std::isnan(value)) { return value + value;
} return std::floor(value);
}
template <typename T> static T Trunc(T value) { // Perform addition to ensure quiet NaNs are returned. Also try to keep the // NaN payload intact, so don't directly return a specific quiet NaN value. if (std::isnan(value)) { return value + value;
} return std::trunc(value);
}
template <typename T> static T NearbyInt(T value) { // Perform addition to ensure quiet NaNs are returned. Also try to keep the // NaN payload intact, so don't directly return a specific quiet NaN value. if (std::isnan(value)) { return value + value;
} return std::nearbyint(value);
}
// Stack alignment on x86 Windows is 4 byte. Align to 16 bytes when calling // rounding functions with double parameters. // // See |ABIStackAlignment| in "js/src/jit/x86/Assembler-x86.h". #ifdefined(JS_CODEGEN_X86) && (!defined(__GNUC__) || defined(__MINGW32__)) # define ALIGN_STACK_FOR_ROUNDING_FUNCTION \
__attribute__((force_align_arg_pointer)) #else # define ALIGN_STACK_FOR_ROUNDING_FUNCTION #endif
#ifdef WASM_CODEGEN_DEBUG case SymbolicAddress::PrintI32:
*abiType = Args_General1; return FuncCast(PrintI32, *abiType); case SymbolicAddress::PrintPtr:
*abiType = Args_General1; return FuncCast(PrintPtr, *abiType); case SymbolicAddress::PrintF32:
*abiType = Args_Int_Float32; return FuncCast(PrintF32, *abiType); case SymbolicAddress::PrintF64:
*abiType = Args_Int_Double; return FuncCast(PrintF64, *abiType); case SymbolicAddress::PrintText:
*abiType = Args_General1; return FuncCast(PrintText, *abiType); case SymbolicAddress::Printf:
*abiType = Args_General2; return FuncCast(Printf, *abiType); #endif #define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, needs_thunk, entry, \
...) \ case SymbolicAddress::sa_name: \
*abiType = abitype; \ return FuncCast(entry, *abiType);
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) #undef VISIT_BUILTIN_FUNC case SymbolicAddress::Limit: break;
}
MOZ_CRASH("Bad SymbolicAddress");
}
bool wasm::IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode) { switch (callee) { case SymbolicAddress::FloorD: case SymbolicAddress::FloorF:
*mode = jit::RoundingMode::Down; returntrue; case SymbolicAddress::CeilD: case SymbolicAddress::CeilF:
*mode = jit::RoundingMode::Up; returntrue; case SymbolicAddress::TruncD: case SymbolicAddress::TruncF:
*mode = jit::RoundingMode::TowardsZero; returntrue; case SymbolicAddress::NearbyIntD: case SymbolicAddress::NearbyIntF:
*mode = jit::RoundingMode::NearestTiesToEven; returntrue; default: returnfalse;
}
}
bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) { // Also see "The Wasm Builtin ABIs" in WasmFrame.h. switch (sym) { // No thunk, because they do their work within the activation case SymbolicAddress::HandleThrow: // GenerateThrowStub case SymbolicAddress::HandleTrap: // GenerateTrapExit returnfalse;
// No thunk, because some work has to be done within the activation before // the activation exit: when called, arbitrary wasm registers are live and // must be saved, and the stack pointer may not be aligned for any ABI. case SymbolicAddress::HandleDebugTrap: // GenerateDebugStub case SymbolicAddress::HandleRequestTierUp: // GenerateRequestTierUpStub
// No thunk, because their caller manages the activation exit explicitly case SymbolicAddress::CallImport_General: // GenerateImportInterpExit case SymbolicAddress::CoerceInPlace_ToInt32: // GenerateImportJitExit case SymbolicAddress::CoerceInPlace_ToNumber: // GenerateImportJitExit case SymbolicAddress::CoerceInPlace_ToBigInt: // GenerateImportJitExit case SymbolicAddress::BoxValue_Anyref: // GenerateImportJitExit returnfalse;
#ifdef WASM_CODEGEN_DEBUG // No thunk, because they call directly into C++ code that does not interact // with the rest of the VM at all. case SymbolicAddress::PrintI32: // Debug stub printers case SymbolicAddress::PrintPtr: case SymbolicAddress::PrintF32: case SymbolicAddress::PrintF64: case SymbolicAddress::PrintText: case SymbolicAddress::Printf: returnfalse; #endif
// No thunk because they're just data case SymbolicAddress::SlotsToAllocKindBytesTable: returnfalse;
// Everyone else gets a thunk to handle the exit from the activation case SymbolicAddress::ToInt32: case SymbolicAddress::DivI64: case SymbolicAddress::UDivI64: case SymbolicAddress::ModI64: case SymbolicAddress::UModI64: case SymbolicAddress::TruncateDoubleToUint64: case SymbolicAddress::TruncateDoubleToInt64: case SymbolicAddress::SaturatingTruncateDoubleToUint64: case SymbolicAddress::SaturatingTruncateDoubleToInt64: case SymbolicAddress::Uint64ToDouble: case SymbolicAddress::Uint64ToFloat32: case SymbolicAddress::Int64ToDouble: case SymbolicAddress::Int64ToFloat32: #ifdefined(JS_CODEGEN_ARM) case SymbolicAddress::aeabi_idivmod: case SymbolicAddress::aeabi_uidivmod: #endif case SymbolicAddress::AllocateBigInt: case SymbolicAddress::ModD: case SymbolicAddress::SinNativeD: case SymbolicAddress::SinFdlibmD: case SymbolicAddress::CosNativeD: case SymbolicAddress::CosFdlibmD: case SymbolicAddress::TanNativeD: case SymbolicAddress::TanFdlibmD: case SymbolicAddress::ASinD: case SymbolicAddress::ACosD: case SymbolicAddress::ATanD: case SymbolicAddress::CeilD: case SymbolicAddress::CeilF: case SymbolicAddress::FloorD: case SymbolicAddress::FloorF: case SymbolicAddress::TruncD: case SymbolicAddress::TruncF: case SymbolicAddress::NearbyIntD: case SymbolicAddress::NearbyIntF: case SymbolicAddress::ExpD: case SymbolicAddress::LogD: case SymbolicAddress::PowD: case SymbolicAddress::ATan2D: case SymbolicAddress::AddSubI128: case SymbolicAddress::MulI64Wide: case SymbolicAddress::ArrayMemMove: case SymbolicAddress::ArrayRefsMove: case SymbolicAddress::MemoryGrowM32: case SymbolicAddress::MemoryGrowM64: case SymbolicAddress::MemorySizeM32: case SymbolicAddress::MemorySizeM64: case SymbolicAddress::WaitI32M32: case SymbolicAddress::WaitI32M64: case SymbolicAddress::WaitI64M32: case SymbolicAddress::WaitI64M64: case SymbolicAddress::WakeM32: case SymbolicAddress::WakeM64: case SymbolicAddress::CoerceInPlace_JitEntry: case SymbolicAddress::ReportV128JSCall: case SymbolicAddress::MemCopyM32: case SymbolicAddress::MemCopySharedM32: case SymbolicAddress::MemCopyM64: case SymbolicAddress::MemCopySharedM64: case SymbolicAddress::MemCopyAny: case SymbolicAddress::DataDrop: case SymbolicAddress::MemFillM32: case SymbolicAddress::MemFillSharedM32: case SymbolicAddress::MemFillM64: case SymbolicAddress::MemFillSharedM64: case SymbolicAddress::MemDiscardM32: case SymbolicAddress::MemDiscardSharedM32: case SymbolicAddress::MemDiscardM64: case SymbolicAddress::MemDiscardSharedM64: case SymbolicAddress::MemInitM32: case SymbolicAddress::MemInitM64: case SymbolicAddress::TableCopy: case SymbolicAddress::ElemDrop: case SymbolicAddress::TableFill: case SymbolicAddress::TableGet: case SymbolicAddress::TableGrow: case SymbolicAddress::TableInit: case SymbolicAddress::TableSet: case SymbolicAddress::TableSize: case SymbolicAddress::RefFunc: case SymbolicAddress::PostBarrierEdge: case SymbolicAddress::PostBarrierEdgePrecise: case SymbolicAddress::PostBarrierWholeCell: #ifdef ENABLE_WASM_JSPI case SymbolicAddress::ResumeBarrier: #endif case SymbolicAddress::ExceptionNew: case SymbolicAddress::ThrowException: case SymbolicAddress::StructNewIL_true: case SymbolicAddress::StructNewIL_false: case SymbolicAddress::StructNewOOL_true: case SymbolicAddress::StructNewOOL_false: case SymbolicAddress::ArrayNew_true: case SymbolicAddress::ArrayNew_false: case SymbolicAddress::ArrayNewData: case SymbolicAddress::ArrayNewElem: case SymbolicAddress::ArrayInitData: case SymbolicAddress::ArrayInitElem: case SymbolicAddress::ArrayCopy: #ifdef ENABLE_WASM_JSPI case SymbolicAddress::ContNew: case SymbolicAddress::ContNewEmpty: case SymbolicAddress::ContUnwind: #endif returntrue;
#define VISIT_BUILTIN_FUNC(op, export, sa_name, sa_type, needs_thunk, ...) \ case SymbolicAddress::sa_name: \ return needs_thunk;
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) #undef VISIT_BUILTIN_FUNC case SymbolicAddress::Limit: break;
}
// ============================================================================ // [SMDOC] JS Fast Wasm Imports // // JS builtins that can be imported by wasm modules and called efficiently // through thunks. These thunks conform to the internal wasm ABI and thus can be // patched in for import calls. Calling a JS builtin through a thunk is much // faster than calling out through the generic import call trampoline which will // end up in the slowest C++ Instance::callImport path. // // Each JS builtin can have several overloads. These must all be enumerated in // PopulateTypedNatives() so they can be included in the process-wide thunk set. // Additionally to the traditional overloading based on types, every builtin // can also have a version implemented by fdlibm or the native math library. // This is useful for fingerprinting resistance.
// ============================================================================ // [SMDOC] Process-wide builtin thunk set // // Thunks are inserted between wasm calls and the C++ callee and achieve two // things: // - bridging the few differences between the internal wasm ABI and the // external native ABI (viz. float returns on x86 and soft-fp ARM) // - executing an exit prologue/epilogue which in turn allows any profiling // iterator to see the full stack up to the wasm operation that called out // // Thunks are created for two kinds of C++ callees, enumerated above: // - SymbolicAddress: for statically compiled calls in the wasm module // - Imported JS builtins: optimized calls to imports // // All thunks are created up front, lazily, when the first wasm module is // compiled in the process. Thunks are kept alive until the JS engine shuts down // in the process. No thunks are created at runtime after initialization. This // simple scheme allows several simplifications: // - no reference counting to keep thunks alive // - no problems toggling W^X permissions which, because of multiple executing // threads, would require each thunk allocation to be on its own page // The cost for creating all thunks at once is relatively low since all thunks // fit within the smallest executable-code allocation quantum (64k).
using TypedNativeToCodeRangeMap =
HashMap<TypedNative, uint32_t, TypedNative, SystemAllocPolicy>;
using SymbolicAddressToCodeRangeArray =
EnumeratedArray<SymbolicAddress, uint32_t, size_t(SymbolicAddress::Limit)>;
CallableOffsets offsets; if (!GenerateBuiltinThunk(masm, abiType, /*dynamicSwitchToMainStack*/ true,
exitReason, funcPtr, &offsets)) { returnfalse;
} if (!thunks->codeRanges.emplaceBack(CodeRange::BuiltinThunk, offsets)) { returnfalse;
}
}
// Provisional lazy JitEntry stub: This is a shared stub that can be installed // in the jit-entry jump table. It uses the JIT ABI and when invoked will // retrieve (via TlsContext()) and invoke the context-appropriate // invoke-from-interpreter jit stub, thus serving as the initial, unoptimized // jit-entry stub for any exported wasm function that has a jit-entry.
#ifdef DEBUG // We need to allow this machine code to bake in a C++ code pointer, so we // disable the wasm restrictions while generating this stub. bool oldFlag = jitContext.setIsCompilingWasm(false); #endif
Maybe<ABIFunctionType> abiType = ToBuiltinABIFunctionType(funcType); if (!abiType) { return nullptr;
}
const BuiltinThunks& thunks = *builtinThunks;
// If this function must use the fdlibm implementation first try to lookup // the fdlibm version. If that version doesn't exist we still fallback to // the normal native. if (math_use_fdlibm_for_sin_cos_tan() ||
f->realm()->creationOptions().alwaysUseFdlibm()) {
TypedNative typedNative(f->jitInfo()->inlinableNative, *abiType,
TypedNative::FdlibmImpl::Yes); auto p =
thunks.typedNativeToCodeRange.readonlyThreadsafeLookup(typedNative); if (p) { return thunks.codeBase + thunks.codeRanges[p->value()].begin();
}
}
TypedNative typedNative(f->jitInfo()->inlinableNative, *abiType,
TypedNative::FdlibmImpl::No); auto p = thunks.typedNativeToCodeRange.readonlyThreadsafeLookup(typedNative); if (!p) { return nullptr;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.