blob: c7aefac7a4e8121888b5ffe76650d89ea5ff5e0c [file] [log] [blame]
/*
* Copyright (C) 2023-2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WasmIPIntSlowPaths.h"
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
#if ENABLE(WEBASSEMBLY)
#include "BytecodeStructs.h"
#include "FrameTracers.h"
#include "JITExceptions.h"
#include "JSWebAssemblyArrayInlines.h"
#include "JSWebAssemblyException.h"
#include "JSWebAssemblyInstance.h"
#include "LLIntData.h"
#include "LLIntExceptions.h"
#include "WasmBBQPlan.h"
#include "WasmBaselineData.h"
#include "WasmCallProfile.h"
#include "WasmCallee.h"
#include "WasmCallingConvention.h"
#include "WasmDebugServer.h"
#include "WasmIPIntGenerator.h"
#include "WasmModuleInformation.h"
#include "WasmOSREntryPlan.h"
#include "WasmOperationsInlines.h"
#include "WasmTypeDefinitionInlines.h"
#include "WasmWorklist.h"
#include "WebAssemblyFunction.h"
#include <bit>
namespace JSC { namespace IPInt {
#define WASM_RETURN_TWO(first, second) do { \
return encodeResult(first, second); \
} while (false)
#define WASM_CALL_RETURN(targetInstance, callTarget) do { \
static_assert(callTarget.getTag() == WasmEntryPtrTag); \
callTarget.validate(); \
WASM_RETURN_TWO(callTarget.taggedPtr(), targetInstance); \
} while (false)
#define IPINT_CALLEE(callFrame) \
(uncheckedDowncast<Wasm::IPIntCallee>(uncheckedDowncast<Wasm::Callee>(callFrame->callee().asNativeCallee())))
// Sets a breakpoint at the callee entry when stepping into a call.
// Should Call this before WASM_CALL_RETURN in prepare_call* functions.
#define IPINT_HANDLE_STEP_INTO_CALL(vmValue, boxedCalleeValue, targetInstanceValue) do { \
if (Options::enableWasmDebugger()) [[unlikely]] \
Wasm::DebugServer::singleton().setStepIntoBreakpointForCall((vmValue), (boxedCalleeValue), (targetInstanceValue)); \
} while (false)
// Sets a breakpoint at the exception handler when stepping into a throw.
// Should Call this after genericUnwind() in throw/rethrow/throw_ref functions.
#define IPINT_HANDLE_STEP_INTO_THROW(vm, instance) do { \
if (Options::enableWasmDebugger()) [[unlikely]] \
Wasm::DebugServer::singleton().setStepIntoBreakpointForThrow((vm), (instance)); \
} while (false)
// For operation calls that may throw an exception, we return (<val>, 0)
// if it is fine, and (<exception value>, SlowPathExceptionTag) if it is not
#define EXCEPTION_VALUE(type) \
std::bit_cast<void*>(static_cast<uintptr_t>(type))
#define IPINT_THROW(type) \
WASM_RETURN_TWO(EXCEPTION_VALUE(type), std::bit_cast<void*>(SlowPathExceptionTag))
#define IPINT_END() WASM_RETURN_TWO(0, 0);
#if CPU(ADDRESS64)
#define IPINT_RETURN(value) \
WASM_RETURN_TWO(std::bit_cast<void*>(value), 0);
#else
#define IPINT_RETURN(value) \
WASM_RETURN_TWO(std::bit_cast<void*>(JSValue::decode(value).payload()), std::bit_cast<void*>(JSValue::decode(value).tag()));
#endif
#if ENABLE(WEBASSEMBLY_BBQJIT)
static inline bool shouldJIT(Wasm::IPIntCallee* callee)
{
if (!Options::useBBQJIT() || !Wasm::BBQPlan::ensureGlobalBBQAllowlist().containsWasmFunction(callee->functionIndex()))
return false;
if (!Options::wasmFunctionIndexRangeToCompile().isInRange(callee->functionIndex()))
return false;
return true;
}
enum class OSRFor { Prologue, Epilogue, Loop };
static inline RefPtr<Wasm::JITCallee> jitCompileAndSetHeuristics(Wasm::IPIntCallee& callee, JSWebAssemblyInstance* instance, OSRFor osrFor)
{
Wasm::IPIntTierUpCounter& tierUpCounter = callee.tierUpCounter();
if (!tierUpCounter.checkIfOptimizationThresholdReached()) {
dataLogLnIf(Options::verboseOSR(), " JIT threshold should be lifted.");
return nullptr;
}
MemoryMode memoryMode = instance->memory()->mode();
Wasm::CalleeGroup& calleeGroup = *instance->calleeGroup();
ASSERT(instance->memoryMode() == memoryMode);
ASSERT(memoryMode == calleeGroup.mode());
Wasm::FunctionCodeIndex functionIndex = callee.functionIndex();
const Wasm::ModuleInformation& moduleInformation = instance->module().moduleInformation();
bool needsSIMDReplacement = !Options::useWasmIPIntSIMD() && moduleInformation.usesSIMD(functionIndex);
auto getReplacement = [&] () -> RefPtr<Wasm::JITCallee> {
switch (osrFor) {
case OSRFor::Prologue: {
if (!Options::useWasmIPInt() || needsSIMDReplacement) [[unlikely]]
return calleeGroup.tryGetReplacementConcurrently(functionIndex);
return nullptr;
}
case OSRFor::Epilogue: {
return nullptr;
}
case OSRFor::Loop: {
return calleeGroup.tryGetBBQCalleeForLoopOSRConcurrently(instance->vm(), functionIndex);
}
}
RELEASE_ASSERT_NOT_REACHED();
return nullptr;
};
if (RefPtr replacement = getReplacement()) {
dataLogLnIf(Options::verboseOSR(), " Code was already compiled.");
// FIXME: This should probably be some optimizeNow() for calls or checkIfOptimizationThresholdReached() should have a different threshold for calls.
tierUpCounter.optimizeSoon();
return replacement;
}
bool compile = false;
{
Locker locker { tierUpCounter.m_lock };
switch (tierUpCounter.compilationStatus(memoryMode)) {
case Wasm::IPIntTierUpCounter::CompilationStatus::NotCompiled:
compile = true;
tierUpCounter.setCompilationStatus(memoryMode, Wasm::IPIntTierUpCounter::CompilationStatus::Compiling);
break;
case Wasm::IPIntTierUpCounter::CompilationStatus::Compiling:
tierUpCounter.optimizeAfterWarmUp();
break;
case Wasm::IPIntTierUpCounter::CompilationStatus::Compiled:
break;
case Wasm::IPIntTierUpCounter::CompilationStatus::Failed:
return nullptr;
}
}
if (compile) {
if (Wasm::BBQPlan::ensureGlobalBBQAllowlist().containsWasmFunction(functionIndex)) {
auto plan = Wasm::BBQPlan::create(instance->vm(), const_cast<Wasm::ModuleInformation&>(moduleInformation), functionIndex, Ref { callee }, Ref { instance->module() }, Ref(*instance->calleeGroup()), Wasm::Plan::dontFinalize());
Wasm::ensureWorklist().enqueue(plan.get());
if (!Options::useConcurrentJIT() || !Options::useWasmIPInt() || needsSIMDReplacement) [[unlikely]]
plan->waitForCompletion();
else
tierUpCounter.optimizeAfterWarmUp();
}
}
return getReplacement();
}
WASM_IPINT_EXTERN_CPP_DECL(prologue_osr, CallFrame* callFrame)
{
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
if (!shouldJIT(callee)) {
callee->tierUpCounter().deferIndefinitely();
WASM_RETURN_TWO(nullptr, nullptr);
}
if (!Options::useWasmIPIntPrologueOSR())
WASM_RETURN_TWO(nullptr, nullptr);
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered prologue_osr with tierUpCounter = ", callee->tierUpCounter());
if (RefPtr replacement = jitCompileAndSetHeuristics(*callee, instance, OSRFor::Prologue)) {
instance->ensureBaselineData(callee->functionIndex());
WASM_RETURN_TWO(replacement->entrypoint().taggedPtr(), nullptr);
}
WASM_RETURN_TWO(nullptr, nullptr);
}
// This needs to be kept in sync with BBQJIT::makeStackMap.
template<SavedFPWidth savedFPWidth>
static ALWAYS_INLINE uint64_t* buildEntryBufferForLoopOSR(Wasm::IPIntCallee* ipintCallee, Wasm::BBQCallee* bbqCallee, JSWebAssemblyInstance* instance, const Wasm::IPIntTierUpCounter::OSREntryData& osrEntryData, IPIntLocal* pl)
{
ASSERT(bbqCallee->compilationMode() == Wasm::CompilationMode::BBQMode);
size_t osrEntryScratchBufferSize = bbqCallee->osrEntryScratchBufferSize();
constexpr unsigned valueSize = Wasm::Context::scratchBufferSlotsPerValue(savedFPWidth);
RELEASE_ASSERT(osrEntryScratchBufferSize >= valueSize * (ipintCallee->numLocals() + osrEntryData.numberOfStackValues + osrEntryData.tryDepth + Wasm::BBQCallee::extraOSRValuesForLoopIndex));
uint64_t* buffer = instance->vm().wasmContext.scratchBufferForSize(osrEntryScratchBufferSize);
if (!buffer)
return nullptr;
size_t bufferIndex = 0;
auto copyValueToBuffer = [&](const IPIntLocal& local) ALWAYS_INLINE_LAMBDA {
if constexpr (savedFPWidth == SavedFPWidth::SaveVectors)
*std::bit_cast<v128_t*>(buffer + bufferIndex) = local.v128;
else
buffer[bufferIndex] = local.i64;
bufferIndex += valueSize;
};
// The loop index isn't really an IPIntLocal value, but it occupies the first slot of the OSR scratch buffer
IPIntLocal loopIndexLocal = { };
loopIndexLocal.v128.u64x2[0] = osrEntryData.loopIndex;
loopIndexLocal.v128.u64x2[1] = 0;
copyValueToBuffer(loopIndexLocal);
for (uint32_t i = 0; i < ipintCallee->numLocals(); ++i)
copyValueToBuffer(pl[i]);
if (ipintCallee->rethrowSlots()) {
ASSERT(osrEntryData.tryDepth <= ipintCallee->rethrowSlots());
for (uint32_t i = 0; i < osrEntryData.tryDepth; ++i)
copyValueToBuffer(pl[ipintCallee->localSizeToAlloc() + i]);
} else {
// If there's no rethrow slots just 0 fill the buffer.
IPIntLocal zeroValue = { };
zeroValue.v128 = vectorAllZeros();
for (uint32_t i = 0; i < osrEntryData.tryDepth; ++i)
copyValueToBuffer(zeroValue);
}
for (uint32_t i = 0; i < osrEntryData.numberOfStackValues; ++i) {
pl -= 1;
copyValueToBuffer(*pl);
}
return buffer;
}
WASM_IPINT_EXTERN_CPP_DECL(loop_osr, CallFrame* callFrame, uint8_t* pc, IPIntLocal* pl)
{
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
Wasm::IPIntTierUpCounter& tierUpCounter = callee->tierUpCounter();
if (!Options::useWasmOSR() || !Options::useWasmIPIntLoopOSR() || !shouldJIT(callee)) {
ipint_extern_prologue_osr(instance, callFrame);
WASM_RETURN_TWO(nullptr, nullptr);
}
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered loop_osr with tierUpCounter = ", callee->tierUpCounter());
if (!tierUpCounter.checkIfOptimizationThresholdReached()) {
dataLogLnIf(Options::verboseOSR(), " JIT threshold should be lifted.");
WASM_RETURN_TWO(nullptr, nullptr);
}
unsigned loopOSREntryBytecodeOffset = pc - callee->bytecode();
const auto& osrEntryData = tierUpCounter.osrEntryDataForLoop(loopOSREntryBytecodeOffset);
if (!Options::useBBQJIT())
WASM_RETURN_TWO(nullptr, nullptr);
RefPtr compiledCallee = jitCompileAndSetHeuristics(*callee, instance, OSRFor::Loop);
if (!compiledCallee)
WASM_RETURN_TWO(nullptr, nullptr);
auto* bbqCallee = uncheckedDowncast<Wasm::BBQCallee>(compiledCallee.get());
ASSERT(bbqCallee->compilationMode() == Wasm::CompilationMode::BBQMode);
uint64_t* buffer;
if (bbqCallee->savedFPWidth() == SavedFPWidth::SaveVectors)
buffer = buildEntryBufferForLoopOSR<SavedFPWidth::SaveVectors>(callee, bbqCallee, instance, osrEntryData, pl);
else
buffer = buildEntryBufferForLoopOSR<SavedFPWidth::DontSaveVectors>(callee, bbqCallee, instance, osrEntryData, pl);
if (!buffer)
WASM_RETURN_TWO(nullptr, nullptr);
auto sharedLoopEntrypoint = bbqCallee->sharedLoopEntrypoint();
RELEASE_ASSERT(sharedLoopEntrypoint);
instance->ensureBaselineData(callee->functionIndex());
WASM_RETURN_TWO(buffer, sharedLoopEntrypoint->taggedPtr());
}
WASM_IPINT_EXTERN_CPP_DECL(epilogue_osr, CallFrame* callFrame)
{
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
if (!shouldJIT(callee)) {
callee->tierUpCounter().deferIndefinitely();
WASM_RETURN_TWO(nullptr, nullptr);
}
if (!Options::useWasmIPIntEpilogueOSR())
WASM_RETURN_TWO(nullptr, nullptr);
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered epilogue_osr with tierUpCounter = ", callee->tierUpCounter());
jitCompileAndSetHeuristics(*callee, instance, OSRFor::Epilogue);
WASM_RETURN_TWO(nullptr, nullptr);
}
#endif
static void copyExceptionStackToPayload(const Wasm::FunctionSignature& tagType, const IPIntStackEntry* stackPointer, FixedVector<uint64_t>& payload)
{
unsigned payloadIndex = payload.size();
for (unsigned i = 0; i < tagType.argumentCount(); ++i) {
unsigned argIndex = tagType.argumentCount() - i - 1;
if (tagType.argumentType(argIndex).isV128()) {
payload[--payloadIndex] = stackPointer[i].v128.u64x2[1];
payload[--payloadIndex] = stackPointer[i].v128.u64x2[0];
} else
payload[--payloadIndex] = stackPointer[i].i64;
}
ASSERT(!payloadIndex);
}
static void copyExceptionPayloadToStack(const Wasm::FunctionSignature& tagType, const FixedVector<uint64_t>& payload, IPIntStackEntry* stackPointer)
{
unsigned payloadIndex = payload.size();
for (unsigned i = 0; i < tagType.argumentCount(); ++i) {
unsigned argIndex = tagType.argumentCount() - i - 1;
if (tagType.argumentType(argIndex).isV128()) {
stackPointer[i].v128.u64x2[1] = payload[--payloadIndex];
stackPointer[i].v128.u64x2[0] = payload[--payloadIndex];
} else
stackPointer[i].i64 = payload[--payloadIndex];
}
ASSERT(!payloadIndex);
}
WASM_IPINT_EXTERN_CPP_DECL(retrieve_and_clear_exception, CallFrame* callFrame, IPIntStackEntry* stackPointer, IPIntLocal* pl)
{
VM& vm = instance->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT(!!throwScope.exception());
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
if (callee->rethrowSlots()) {
RELEASE_ASSERT(vm.targetTryDepthForThrow <= callee->rethrowSlots());
pl[callee->localSizeToAlloc() + vm.targetTryDepthForThrow - 1].i64 = std::bit_cast<uint64_t>(throwScope.exception()->value());
}
if (stackPointer) {
// We only have a stack pointer if we're doing a catch not a catch_all
Exception* exception = throwScope.exception();
auto* wasmException = jsSecureCast<JSWebAssemblyException*>(exception->value());
copyExceptionPayloadToStack(wasmException->tag().type(), wasmException->payload(), stackPointer);
}
// We want to clear the exception here rather than in the catch prologue
// JIT code because clearing it also entails clearing a bit in an Atomic
// bit field in VMTraps.
throwScope.clearException();
WASM_RETURN_TWO(nullptr, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(retrieve_clear_and_push_exception, CallFrame* callFrame, IPIntStackEntry* stackPointer, IPIntLocal* pl)
{
VM& vm = instance->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT(!!throwScope.exception());
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
if (callee->rethrowSlots()) {
RELEASE_ASSERT(vm.targetTryDepthForThrow <= callee->rethrowSlots());
pl[callee->localSizeToAlloc() + vm.targetTryDepthForThrow - 1].i64 = std::bit_cast<uint64_t>(throwScope.exception()->value());
}
Exception* exception = throwScope.exception();
stackPointer[0].ref = JSValue::encode(exception->value());
// We want to clear the exception here rather than in the catch prologue
// JIT code because clearing it also entails clearing a bit in an Atomic
// bit field in VMTraps.
throwScope.clearException();
WASM_RETURN_TWO(nullptr, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(retrieve_clear_and_push_exception_and_arguments, CallFrame* callFrame, IPIntStackEntry* stackPointer, IPIntLocal* pl)
{
VM& vm = instance->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT(!!throwScope.exception());
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
if (callee->rethrowSlots()) {
RELEASE_ASSERT(vm.targetTryDepthForThrow <= callee->rethrowSlots());
pl[callee->localSizeToAlloc() + vm.targetTryDepthForThrow - 1].i64 = std::bit_cast<uint64_t>(throwScope.exception()->value());
}
Exception* exception = throwScope.exception();
auto* wasmException = jsSecureCast<JSWebAssemblyException*>(exception->value());
ASSERT(wasmException->payload().size() == wasmException->tag().parameterBufferSize());
stackPointer[0].ref = JSValue::encode(exception->value());
copyExceptionPayloadToStack(wasmException->tag().type(), wasmException->payload(), stackPointer + 1);
// We want to clear the exception here rather than in the catch prologue
// JIT code because clearing it also entails clearing a bit in an Atomic
// bit field in VMTraps.
throwScope.clearException();
WASM_RETURN_TWO(nullptr, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(throw_exception, CallFrame* callFrame, IPIntStackEntry* arguments, unsigned exceptionIndex)
{
VM& vm = instance->vm();
SlowPathFrameTracer tracer(vm, callFrame);
auto throwScope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT(!throwScope.exception());
JSGlobalObject* globalObject = instance->globalObject();
Ref<const Wasm::Tag> tag = instance->tag(exceptionIndex);
FixedVector<uint64_t> values(tag->parameterBufferSize());
copyExceptionStackToPayload(tag->type(), arguments, values);
ASSERT(tag->type().returnsVoid());
JSWebAssemblyException* exception = JSWebAssemblyException::create(vm, globalObject->webAssemblyExceptionStructure(), WTFMove(tag), WTFMove(values));
throwException(globalObject, throwScope, exception);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
IPINT_HANDLE_STEP_INTO_THROW(vm, instance);
WASM_RETURN_TWO(vm.targetMachinePCForThrow, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(rethrow_exception, CallFrame* callFrame, IPIntStackEntry* pl, unsigned tryDepth)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
RELEASE_ASSERT(tryDepth <= callee->rethrowSlots());
#if CPU(ADDRESS64)
JSWebAssemblyException* exception = std::bit_cast<JSWebAssemblyException*>(pl[callee->localSizeToAlloc() + tryDepth - 1].i64);
#else
JSWebAssemblyException* exception = std::bit_cast<JSWebAssemblyException*>(pl[callee->localSizeToAlloc() + tryDepth - 1].i32);
#endif
RELEASE_ASSERT(exception);
throwException(globalObject, throwScope, exception);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
IPINT_HANDLE_STEP_INTO_THROW(vm, instance);
WASM_RETURN_TWO(vm.targetMachinePCForThrow, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(throw_ref, CallFrame* callFrame, EncodedJSValue exnref)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* exception = jsSecureCast<JSWebAssemblyException*>(JSValue::decode(exnref));
RELEASE_ASSERT(exception);
throwException(globalObject, throwScope, exception);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
IPINT_HANDLE_STEP_INTO_THROW(vm, instance);
WASM_RETURN_TWO(vm.targetMachinePCForThrow, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(table_get, unsigned tableIndex, unsigned index)
{
EncodedJSValue result = Wasm::tableGet(instance, tableIndex, index);
if (!result)
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
IPINT_RETURN(result);
}
WASM_IPINT_EXTERN_CPP_DECL(table_set, unsigned tableIndex, unsigned index, EncodedJSValue value)
{
if (!Wasm::tableSet(instance, tableIndex, index, value))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_init, IPIntStackEntry* sp, TableInitMetadata* metadata)
{
int32_t n = sp[0].i32;
int32_t src = sp[1].i32;
int32_t dst = sp[2].i32;
if (!Wasm::tableInit(instance, metadata->elementIndex, metadata->tableIndex, dst, src, n))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_fill, IPIntStackEntry* sp, TableFillMetadata* metadata)
{
int32_t n = sp[0].i32;
EncodedJSValue fill = sp[1].ref;
int32_t offset = sp[2].i32;
if (!Wasm::tableFill(instance, metadata->tableIndex, offset, fill, n))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_grow, IPIntStackEntry* sp, TableGrowMetadata* metadata)
{
int32_t n = sp[0].i32;
EncodedJSValue fill = sp[1].ref;
WASM_RETURN_TWO(std::bit_cast<void*>(Wasm::tableGrow(instance, metadata->tableIndex, fill, n)), 0);
}
WASM_IPINT_EXTERN_CPP_DECL(memory_grow, int64_t delta)
{
WASM_RETURN_TWO(reinterpret_cast<void*>(Wasm::growMemory(instance, delta)), 0);
}
WASM_IPINT_EXTERN_CPP_DECL(memory_init, int32_t dataIndex, IPIntStackEntry* sp)
{
int32_t n = sp[0].i32;
int32_t s = sp[1].i32;
int64_t d = sp[2].i64;
if (!Wasm::memoryInit(instance, dataIndex, d, s, n))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(data_drop, int32_t dataIndex)
{
Wasm::dataDrop(instance, dataIndex);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(memory_copy, int64_t dst, int64_t src, int64_t count)
{
if (!Wasm::memoryCopy(instance, dst, src, count))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(memory_fill, int64_t dst, int32_t targetValue, int64_t count)
{
if (!Wasm::memoryFill(instance, dst, targetValue, count))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(elem_drop, int32_t dataIndex)
{
Wasm::elemDrop(instance, dataIndex);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_copy, IPIntStackEntry* sp, TableCopyMetadata* metadata)
{
int32_t n = sp[0].i32;
int32_t src = sp[1].i32;
int32_t dst = sp[2].i32;
if (!Wasm::tableCopy(instance, metadata->dstTableIndex, metadata->srcTableIndex, dst, src, n))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_size, int32_t tableIndex)
{
int32_t result = Wasm::tableSize(instance, tableIndex);
WASM_RETURN_TWO(std::bit_cast<void*>(static_cast<size_t>(result)), 0);
}
// Wasm-GC
WASM_IPINT_EXTERN_CPP_DECL(struct_new, uint32_t type, IPIntStackEntry* sp)
{
WebAssemblyGCStructure* structure = instance->gcObjectStructure(type);
JSValue result = Wasm::structNew(instance, structure, false, sp);
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadStructNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(struct_new_default, uint32_t type)
{
WebAssemblyGCStructure* structure = instance->gcObjectStructure(type);
JSValue result = Wasm::structNew(instance, structure, true, nullptr);
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadStructNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(struct_get, EncodedJSValue object, uint32_t fieldIndex, IPIntStackEntry* result)
{
UNUSED_PARAM(instance);
if (JSValue::decode(object).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullAccess);
Wasm::structGet(object, fieldIndex, result);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(struct_get_s, EncodedJSValue object, uint32_t fieldIndex, IPIntStackEntry* result)
{
UNUSED_PARAM(instance);
if (JSValue::decode(object).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullAccess);
Wasm::structGet(object, fieldIndex, result);
// sign extension
JSWebAssemblyStruct* structObject = jsCast<JSWebAssemblyStruct*>(JSValue::decode(object).getObject());
Wasm::StorageType type = structObject->fieldType(fieldIndex).type;
ASSERT(type.is<Wasm::PackedType>());
size_t elementSize = type.as<Wasm::PackedType>() == Wasm::PackedType::I8 ? sizeof(uint8_t) : sizeof(uint16_t);
uint8_t bitShift = (sizeof(uint32_t) - elementSize) * 8;
int32_t value = static_cast<int32_t>(result->i64);
value = value << bitShift;
result->i64 = static_cast<EncodedJSValue>(value >> bitShift);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(struct_set, EncodedJSValue object, uint32_t fieldIndex, IPIntStackEntry* sp)
{
UNUSED_PARAM(instance);
if (JSValue::decode(object).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullAccess);
Wasm::structSet(object, fieldIndex, sp);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_new, uint32_t type, uint32_t size, IPIntStackEntry* defaultValue)
{
WebAssemblyGCStructure* structure = instance->gcObjectStructure(type);
const Wasm::TypeDefinition& arraySignature = structure->typeDefinition();
Wasm::StorageType elementType = arraySignature.as<Wasm::ArrayType>()->elementType().type;
JSValue result;
if (elementType.unpacked().isV128())
result = Wasm::arrayNew(instance, structure, size, defaultValue->v128);
else
result = Wasm::arrayNew(instance, structure, size, defaultValue->i64);
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(array_new_default, uint32_t type, uint32_t size)
{
UNUSED_PARAM(instance);
WebAssemblyGCStructure* structure = instance->gcObjectStructure(type);
const Wasm::TypeDefinition& arraySignature = structure->typeDefinition();
Wasm::StorageType elementType = arraySignature.as<Wasm::ArrayType>()->elementType().type;
EncodedJSValue defaultValue = 0;
if (Wasm::isRefType(elementType)) {
defaultValue = JSValue::encode(jsNull());
} else if (elementType.unpacked().isV128()) {
JSValue result = Wasm::arrayNew(instance, structure, size, vectorAllZeros());
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
JSValue result = Wasm::arrayNew(instance, structure, size, defaultValue);
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(array_new_fixed, uint32_t type, uint32_t size, IPIntStackEntry* arguments)
{
WebAssemblyGCStructure* structure = instance->gcObjectStructure(type);
JSValue result = Wasm::arrayNewFixed(instance, structure, size, arguments);
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(array_new_data, IPInt::ArrayNewDataMetadata* metadata, uint32_t offset, uint32_t size)
{
EncodedJSValue result = Wasm::arrayNewData(instance, metadata->type, metadata->dataSegmentIndex, size, offset);
if (JSValue::decode(result).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNewInitData);
IPINT_RETURN(result);
}
WASM_IPINT_EXTERN_CPP_DECL(array_new_elem, IPInt::ArrayNewElemMetadata* metadata, uint32_t offset, uint32_t size)
{
EncodedJSValue result = Wasm::arrayNewElem(instance, metadata->type, metadata->elemSegmentIndex, size, offset);
if (JSValue::decode(result).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNewInitElem);
IPINT_RETURN(result);
}
WASM_IPINT_EXTERN_CPP_DECL(array_get, uint32_t type, IPIntStackEntry* sp)
{
// sp[1] = array / result
// sp[0] = index (i32)
EncodedJSValue array = sp[1].ref;
uint32_t index = sp[0].i32;
IPIntStackEntry* result = &sp[1];
if (JSValue::decode(array).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullAccess);
JSValue arrayValue = JSValue::decode(array);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
if (index >= arrayObject->size()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayGet);
Wasm::arrayGet(instance, type, array, index, result);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_get_s, uint32_t type, IPIntStackEntry* sp)
{
// sp[1] = array / result
// sp[0] = index (i32)
EncodedJSValue array = sp[1].ref;
uint32_t index = sp[0].i32;
IPIntStackEntry* result = &sp[1];
if (JSValue::decode(array).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullAccess);
JSValue arrayValue = JSValue::decode(array);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
if (index >= arrayObject->size()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayGet);
Wasm::arrayGet(instance, type, array, index, result);
// sign extension
Wasm::StorageType elementType = arrayObject->elementType().type;
ASSERT(elementType.is<Wasm::PackedType>());
size_t elementSize = elementType.as<Wasm::PackedType>() == Wasm::PackedType::I8 ? sizeof(uint8_t) : sizeof(uint16_t);
uint8_t bitShift = (sizeof(uint32_t) - elementSize) * 8;
int32_t value = static_cast<int32_t>(result->i64);
value = value << bitShift;
result->i64 = static_cast<EncodedJSValue>(value >> bitShift);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_set, uint32_t type, IPIntStackEntry* sp)
{
// sp[0] = value
// sp[1] = index
// sp[2] = array ref
if (JSValue::decode(sp[2].ref).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullAccess);
JSValue arrayValue = JSValue::decode(sp[2].ref);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
uint32_t index = static_cast<uint32_t>(sp[1].i32);
if (index >= arrayObject->size()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArraySet);
Wasm::arraySet(instance, type, sp[2].ref, index, &sp[0]);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_fill, IPIntStackEntry* sp)
{
// sp[0] = size
// sp[1] = value
// sp[2] = offset
// sp[3] = array
EncodedJSValue arrayref = sp[3].ref;
JSValue arrayValue = JSValue::decode(arrayref);
if (arrayValue.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullArrayFill);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
uint32_t offset = sp[2].i32;
IPIntStackEntry* value = &sp[1];
uint32_t size = sp[0].i32;
bool success;
if (arrayObject->elementType().type.unpacked().isV128())
success = Wasm::arrayFill(instance->vm(), arrayref, offset, value->v128, size);
else
success = Wasm::arrayFill(instance->vm(), arrayref, offset, value->i64, size);
if (!success) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayFill);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_copy, IPIntStackEntry* sp)
{
// sp[0] = size
// sp[1] = src_offset
// sp[2] = src
// sp[3] = dest_offset
// sp[4] = dest
EncodedJSValue dst = sp[4].ref;
uint32_t dstOffset = sp[3].i32;
EncodedJSValue src = sp[2].ref;
uint32_t srcOffset = sp[1].i32;
uint32_t size = sp[0].i32;
if (JSValue::decode(dst).isNull() || JSValue::decode(src).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullArrayCopy);
if (!Wasm::arrayCopy(instance, dst, dstOffset, src, srcOffset, size)) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayCopy);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_init_data, uint32_t dataIndex, IPIntStackEntry* sp)
{
// sp[0] = size
// sp[1] = src_offset
// sp[2] = dst_offset
// sp[3] = dst
EncodedJSValue dst = sp[3].ref;
uint32_t dstOffset = sp[2].i32;
uint32_t srcOffset = sp[1].i32;
uint32_t size = sp[0].i32;
if (JSValue::decode(dst).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullArrayInitData);
if (!Wasm::arrayInitData(instance, dst, dstOffset, dataIndex, srcOffset, size)) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayInitData);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_init_elem, uint32_t elemIndex, IPIntStackEntry* sp)
{
// sp[0] = size
// sp[1] = src_offset
// sp[2] = dst_offset
// sp[3] = dst
EncodedJSValue dst = sp[3].ref;
uint32_t dstOffset = sp[2].i32;
uint32_t srcOffset = sp[1].i32;
uint32_t size = sp[0].i32;
if (JSValue::decode(dst).isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullArrayInitElem);
if (!Wasm::arrayInitElem(instance, dst, dstOffset, elemIndex, srcOffset, size)) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayInitElem);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(any_convert_extern, EncodedJSValue value)
{
UNUSED_PARAM(instance);
IPINT_RETURN(Wasm::externInternalize(value));
}
WASM_IPINT_EXTERN_CPP_DECL(ref_test, int32_t heapType, bool allowNull, EncodedJSValue value)
{
if (Wasm::typeIndexIsType(static_cast<Wasm::TypeIndex>(heapType))) {
bool result = Wasm::refCast(value, allowNull, static_cast<Wasm::TypeIndex>(heapType), nullptr);
IPINT_RETURN(static_cast<uint64_t>(result));
}
auto& info = instance->module().moduleInformation();
bool result = Wasm::refCast(value, allowNull, info.typeSignatures[heapType]->index(), info.rtts[heapType].ptr());
IPINT_RETURN(static_cast<uint64_t>(result));
}
WASM_IPINT_EXTERN_CPP_DECL(ref_cast, int32_t heapType, bool allowNull, EncodedJSValue value)
{
if (Wasm::typeIndexIsType(static_cast<Wasm::TypeIndex>(heapType))) {
if (!Wasm::refCast(value, allowNull, static_cast<Wasm::TypeIndex>(heapType), nullptr)) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::CastFailure);
IPINT_RETURN(value);
}
auto& info = instance->module().moduleInformation();
if (!Wasm::refCast(value, allowNull, info.typeSignatures[heapType]->index(), info.rtts[heapType].ptr())) [[unlikely]] {
if (!allowNull && JSValue::decode(value).isNull())
IPINT_THROW(Wasm::ExceptionType::NullAccess);
IPINT_THROW(Wasm::ExceptionType::CastFailure);
}
IPINT_RETURN(value);
}
WASM_IPINT_EXTERN_CPP_DECL(prepare_function_body, CallFrame* callFrame)
{
Wasm::IPIntCallee* callee = IPINT_CALLEE(callFrame);
instance->ensureBaselineData(callee->functionIndex()).incrementTotalCount();
WASM_RETURN_TWO(callee, nullptr);
}
/**
* Given a function index, determine the pointer to its executable code.
* Return a pair of the wasm instance pointer received as the first argument and the code pointer.
* Additionally, store the following into the 'calleeAndWasmInstanceReturn':
*
* - calleeAndWasmInstanceReturn[0] - the callee to use, goes into the 'callee' slot of the CallFrame.
* - calleeAndWasmInstanceReturn[1] - the wasm instance to use, goes into the 'codeBlock' slot of the CallFrame.
*/
WASM_IPINT_EXTERN_CPP_DECL(prepare_call, CallFrame* callFrame, CallMetadata* call, Register* calleeAndWasmInstanceReturn)
{
auto* callee = IPINT_CALLEE(callFrame);
instance->ensureBaselineData(callee->functionIndex()).at(call->callProfileIndex).incrementCount();
Wasm::FunctionSpaceIndex functionIndex = call->functionIndex;
uint32_t importFunctionCount = instance->module().moduleInformation().importFunctionCount();
Register& calleeReturn = calleeAndWasmInstanceReturn[0];
Register& wasmInstanceReturn = calleeAndWasmInstanceReturn[1];
CodePtr<WasmEntryPtrTag> codePtr;
bool isJSCallee = false;
if (functionIndex < importFunctionCount) {
auto* functionInfo = instance->importFunctionInfo(functionIndex);
codePtr = functionInfo->importFunctionStub;
calleeReturn = functionInfo->boxedCallee.encodedBits();
if (functionInfo->isJS()) {
isJSCallee = true;
wasmInstanceReturn = reinterpret_cast<uintptr_t>(functionInfo);
} else
wasmInstanceReturn = functionInfo->targetInstance.get();
} else {
// Target is a wasm function within the same instance
codePtr = *instance->calleeGroup()->entrypointLoadLocationFromFunctionIndexSpace(functionIndex);
auto callee = instance->calleeGroup()->wasmCalleeFromFunctionIndexSpace(functionIndex);
calleeReturn = CalleeBits::encodeNativeCallee(callee.get());
wasmInstanceReturn = instance;
}
JSWebAssemblyInstance* targetInstance = isJSCallee ? nullptr : jsDynamicCast<JSWebAssemblyInstance*>(wasmInstanceReturn.unboxedCell());
IPINT_HANDLE_STEP_INTO_CALL(instance->vm(), CalleeBits(calleeReturn.encodedJSValue()), targetInstance);
RELEASE_ASSERT(WTF::isTaggedWith<WasmEntryPtrTag>(codePtr));
WASM_CALL_RETURN(instance, codePtr);
}
WASM_IPINT_EXTERN_CPP_DECL(prepare_call_indirect, CallFrame* callFrame, Wasm::FunctionSpaceIndex* functionIndex, CallIndirectMetadata* call)
{
auto* callee = IPINT_CALLEE(callFrame);
auto& callProfile = instance->ensureBaselineData(callee->functionIndex()).at(call->callProfileIndex);
callProfile.incrementCount();
unsigned tableIndex = call->tableIndex;
const Wasm::FuncRefTable::Function* function = nullptr;
if (!tableIndex) {
if (*functionIndex >= instance->cachedTable0Length()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsCallIndirect);
function = &instance->cachedTable0Buffer()[*functionIndex];
} else {
Wasm::FuncRefTable* table = instance->table(tableIndex)->asFuncrefTable();
if (*functionIndex >= table->length()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsCallIndirect);
function = &table->function(*functionIndex);
}
if (!function->m_function.rtt) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadSignature);
if (!function->m_function.rtt->isSubRTT(*call->rtt)) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadSignature);
auto boxedCallee = function->m_function.boxedCallee.encodedBits();
Register* calleeReturn = std::bit_cast<Register*>(functionIndex);
*calleeReturn = boxedCallee;
Register& functionInfoSlot = calleeReturn[1];
if (function->m_function.isJS())
functionInfoSlot = reinterpret_cast<uintptr_t>(jsCast<WebAssemblyFunctionBase*>(function->m_value.get())->callLinkInfo());
else {
auto* targetInstance = function->m_function.targetInstance.get();
functionInfoSlot = targetInstance;
if (instance != targetInstance)
callProfile.observeCrossInstanceCall();
else
callProfile.observeCallIndirect(boxedCallee);
}
IPINT_HANDLE_STEP_INTO_CALL(instance->vm(), function->m_function.boxedCallee, function->m_function.targetInstance.get());
auto callTarget = *function->m_function.entrypointLoadLocation;
WASM_CALL_RETURN(function->m_function.targetInstance.get(), callTarget);
}
WASM_IPINT_EXTERN_CPP_DECL(prepare_call_ref, CallFrame* callFrame, CallRefMetadata* call, IPIntStackEntry* sp)
{
auto* callee = IPINT_CALLEE(callFrame);
auto& callProfile = instance->ensureBaselineData(callee->functionIndex()).at(call->callProfileIndex);
callProfile.incrementCount();
JSValue targetReference = JSValue::decode(sp->ref);
if (targetReference.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::NullReference);
ASSERT(targetReference.isObject());
JSObject* referenceAsObject = jsCast<JSObject*>(targetReference);
ASSERT(referenceAsObject->inherits<WebAssemblyFunctionBase>());
auto* wasmFunction = jsCast<WebAssemblyFunctionBase*>(referenceAsObject);
auto& function = wasmFunction->importableFunction();
JSWebAssemblyInstance* calleeInstance = wasmFunction->instance();
auto boxedCallee = function.boxedCallee.encodedBits();
sp->ref = boxedCallee;
Register& functionInfoSlot = std::bit_cast<Register*>(sp)[1];
if (function.isJS())
functionInfoSlot = reinterpret_cast<uintptr_t>(wasmFunction->callLinkInfo());
else {
auto* targetInstance = function.targetInstance.get();
functionInfoSlot = targetInstance;
if (instance != targetInstance)
callProfile.observeCrossInstanceCall();
else
callProfile.observeCallIndirect(boxedCallee);
}
IPINT_HANDLE_STEP_INTO_CALL(instance->vm(), function.boxedCallee, calleeInstance);
auto callTarget = *function.entrypointLoadLocation;
WASM_CALL_RETURN(calleeInstance, callTarget);
}
WASM_IPINT_EXTERN_CPP_DECL(set_global_ref, uint32_t globalIndex, JSValue value)
{
instance->setGlobal(globalIndex, value);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(set_global_64, unsigned index, uint64_t value)
{
instance->setGlobal(index, value);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(get_global_64, unsigned index)
{
#if CPU(ARM64) || CPU(X86_64)
WASM_RETURN_TWO(std::bit_cast<void*>(instance->loadI64Global(index)), 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(index);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(memory_atomic_wait32, uint64_t pointerWithOffset, uint32_t value, uint64_t timeout)
{
#if CPU(ARM64) || CPU(X86_64)
int32_t result = Wasm::memoryAtomicWait32(instance, pointerWithOffset, value, timeout);
WASM_RETURN_TWO(std::bit_cast<void*>(static_cast<intptr_t>(result)), nullptr);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(pointerWithOffset);
UNUSED_PARAM(value);
UNUSED_PARAM(timeout);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(memory_atomic_wait64, uint64_t pointerWithOffset, uint64_t value, uint64_t timeout)
{
#if CPU(ARM64) || CPU(X86_64)
int32_t result = Wasm::memoryAtomicWait64(instance, pointerWithOffset, value, timeout);
WASM_RETURN_TWO(std::bit_cast<void*>(static_cast<intptr_t>(result)), nullptr);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(pointerWithOffset);
UNUSED_PARAM(value);
UNUSED_PARAM(timeout);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(memory_atomic_notify, unsigned base, unsigned offset, int32_t count)
{
#if CPU(ARM64) || CPU(X86_64)
int32_t result = Wasm::memoryAtomicNotify(instance, base, offset, count);
WASM_RETURN_TWO(std::bit_cast<void*>(static_cast<intptr_t>(result)), nullptr);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(base);
UNUSED_PARAM(offset);
UNUSED_PARAM(count);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(ref_func, unsigned index)
{
IPINT_RETURN(Wasm::refFunc(instance, index));
}
extern "C" void SYSV_ABI wasm_log_crash(CallFrame*, JSWebAssemblyInstance* instance)
{
dataLogLn("Reached IPInt code that should never have been executed.");
dataLogLn("Module internal function count: ", instance->module().moduleInformation().internalFunctionCount());
RELEASE_ASSERT_NOT_REACHED();
}
extern "C" UGPRPair SYSV_ABI slow_path_wasm_throw_exception(CallFrame* callFrame, JSWebAssemblyInstance* instance, Wasm::ExceptionType exceptionType)
{
// FaultPC is the exact PC causing the fault. When using it as a returnPC, we should point one next instruction instead.
WasmOperationPrologueCallFrameTracer tracer(instance->vm(), callFrame, std::bit_cast<void*>(std::bit_cast<uintptr_t>(instance->faultPC()) + 1));
instance->setFaultPC(Wasm::ExceptionType::Termination, nullptr);
WASM_RETURN_TWO(Wasm::throwWasmToJSException(callFrame, exceptionType, instance), nullptr);
}
// Similar logic to 'slow_path_wasm_throw_exception', but the exception is already sitting
// in the VM. We don't throw, we only unwind and go to the handler.
extern "C" UCPURegister SYSV_ABI slow_path_wasm_unwind_exception(CallFrame* callFrame, JSWebAssemblyInstance* instance)
{
VM& vm = instance->vm();
// FaultPC is the exact PC causing the fault. When using it as a returnPC, we should point one next instruction instead.
WasmOperationPrologueCallFrameTracer tracer(instance->vm(), callFrame, std::bit_cast<void*>(std::bit_cast<uintptr_t>(instance->faultPC()) + 1));
instance->setFaultPC(Wasm::ExceptionType::Termination, nullptr);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
return reinterpret_cast<UCPURegister>(vm.targetMachinePCForThrow);
}
extern "C" UGPRPair SYSV_ABI slow_path_wasm_popcount(const void* pc, uint32_t x)
{
void* result = std::bit_cast<void*>(static_cast<size_t>(std::popcount(x)));
WASM_RETURN_TWO(pc, result);
}
extern "C" UGPRPair SYSV_ABI slow_path_wasm_popcountll(const void* pc, uint64_t x)
{
void* result = std::bit_cast<void*>(static_cast<size_t>(std::popcount(x)));
WASM_RETURN_TWO(pc, result);
}
WASM_IPINT_EXTERN_CPP_DECL(check_stack_and_vm_traps, void* candidateNewStackPointer, Wasm::IPIntCallee* callee)
{
VM& vm = instance->vm();
if (vm.traps().handleTrapsIfNeeded()) {
if (vm.hasPendingTerminationException())
IPINT_THROW(Wasm::ExceptionType::Termination);
ASSERT(!vm.exceptionForInspection());
}
// Redo stack check because we may really have gotten here due to an imminent StackOverflow.
if (vm.softStackLimit() <= candidateNewStackPointer) {
if (Options::enableWasmDebugger()) [[unlikely]] {
if (vm.isWasmStopWorldActive())
Wasm::DebugServer::singleton().setInterruptBreakpoint(instance, callee);
}
IPINT_RETURN(encodedJSValue()); // No stack overflow. Carry on.
}
IPINT_THROW(Wasm::ExceptionType::StackOverflow);
}
static UNUSED_FUNCTION void displayWasmDebugState(JSWebAssemblyInstance* instance, Wasm::IPIntCallee* callee, IPIntStackEntry* sp, IPIntLocal* pl)
{
dataLogLn("=== WASM Debug State ===");
uint32_t numLocals = callee->numLocals();
dataLogLn("WASM Locals (", numLocals, " entries):");
auto functionIndex = callee->functionIndex();
const auto& moduleInfo = instance->module().moduleInformation();
const Vector<Wasm::Type>& localTypes = moduleInfo.debugInfo->ensureFunctionDebugInfo(functionIndex).locals;
for (uint32_t i = 0; i < numLocals; ++i)
logWasmLocalValue(i, pl[i], localTypes[i]);
constexpr size_t STACK_ENTRY_SIZE = 16;
if (sp && pl && sp <= reinterpret_cast<IPIntStackEntry*>(pl)) {
size_t stackDepth = (reinterpret_cast<uint8_t*>(pl) - reinterpret_cast<uint8_t*>(sp)) / STACK_ENTRY_SIZE;
dataLogLn("WASM Stack (", stackDepth, " entries - showing all type interpretations):");
IPIntStackEntry* currentEntry = sp;
for (size_t i = 0; i < stackDepth; ++i) {
dataLogLn(" Stack[", i, "]: i32=", currentEntry->i32, ", i64=", currentEntry->i64, ", f32=", currentEntry->f32, ", f64=", currentEntry->f64, ", ref=", currentEntry->ref);
currentEntry++;
}
} else
dataLogLn("WASM Stack: Invalid stack pointers");
dataLogLn("=== End WASM Debug State ===");
}
WASM_IPINT_EXTERN_CPP_DECL(unreachable_breakpoint_handler, CallFrame* callFrame, Register* sp)
{
dataLogLnIf(Options::verboseWasmDebugger(), "[Code][unreachable] Start");
bool breakpointHandled = false;
if (Options::enableWasmDebugger()) [[unlikely]] {
Wasm::DebugServer& debugServer = Wasm::DebugServer::singleton();
if (debugServer.needToHandleBreakpoints()) {
uint8_t* pc = static_cast<uint8_t*>(sp[2].pointer());
uint8_t* mc = static_cast<uint8_t*>(sp[3].pointer());
IPIntLocal* pl = static_cast<IPIntLocal*>(sp[0].pointer());
Wasm::IPIntCallee* callee = static_cast<Wasm::IPIntCallee*>(sp[1].pointer());
IPIntStackEntry* stackPointer = reinterpret_cast<IPIntStackEntry*>(sp + 4);
if (Options::verboseWasmDebugger())
displayWasmDebugState(instance, callee, stackPointer, pl);
breakpointHandled = debugServer.stopCode(callFrame, instance, callee, pc, mc, pl, stackPointer);
}
}
dataLogLnIf(Options::verboseWasmDebugger(), "[Code][unreachable] Done with breakpointHandled=", breakpointHandled);
IPINT_RETURN(static_cast<EncodedJSValue>(static_cast<int32_t>(breakpointHandled)));
}
} } // namespace JSC::IPInt
#endif
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END