blob: d82c60fb14bd56c0b934c7d5aa6f7ffb45c26338 [file] [log] [blame]
/*
* Copyright (C) 2019-2022 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 "WasmSlowPaths.h"
#if ENABLE(WEBASSEMBLY)
#include "BytecodeStructs.h"
#include "FrameTracers.h"
#include "JITExceptions.h"
#include "JSWebAssemblyArray.h"
#include "JSWebAssemblyException.h"
#include "JSWebAssemblyInstance.h"
#include "LLIntData.h"
#include "WasmBBQPlan.h"
#include "WasmCallee.h"
#include "WasmCallingConvention.h"
#include "WasmFunctionCodeBlockGenerator.h"
#include "WasmInstance.h"
#include "WasmLLIntBuiltin.h"
#include "WasmLLIntGenerator.h"
#include "WasmModuleInformation.h"
#include "WasmOMGPlan.h"
#include "WasmOSREntryPlan.h"
#include "WasmOperationsInlines.h"
#include "WasmTypeDefinitionInlines.h"
#include "WasmWorklist.h"
#include "WebAssemblyFunction.h"
#include <bit>
namespace JSC { namespace LLInt {
#define WASM_RETURN_TWO(first, second) do { \
return encodeResult(first, second); \
} while (false)
#define WASM_END_IMPL() WASM_RETURN_TWO(pc, 0)
#define WASM_THROW(exceptionType) do { \
callFrame->setArgumentCountIncludingThis(static_cast<int>(exceptionType)); \
WASM_RETURN_TWO(LLInt::wasmExceptionInstructions(), 0); \
} while (false)
#define WASM_END() do { \
WASM_END_IMPL(); \
} while (false)
#define WASM_RETURN(value) do { \
callFrame->uncheckedR(instruction.m_dst) = static_cast<EncodedJSValue>(value); \
WASM_END_IMPL(); \
} while (false)
#define WASM_CALL_RETURN(targetInstance, callTarget, callTargetTag) do { \
WASM_RETURN_TWO((retagCodePtr<callTargetTag, JSEntrySlowPathPtrTag>(callTarget)), targetInstance); \
} while (false)
#define CALLEE() \
static_cast<Wasm::LLIntCallee*>(callFrame->callee().asWasmCallee())
#define READ(virtualRegister) \
(virtualRegister.isConstant() \
? JSValue::decode(CALLEE()->getConstant(virtualRegister)) \
: callFrame->r(virtualRegister))
#if ENABLE(WEBASSEMBLY_B3JIT)
enum class RequiredWasmJIT { Any, OMG };
extern "C" void wasm_log_crash(CallFrame*, Wasm::Instance* instance)
{
dataLogLn("Reached LLInt code that should never have been executed.");
dataLogLn("Module internal function count: ", instance->module().moduleInformation().internalFunctionCount());
RELEASE_ASSERT_NOT_REACHED();
}
inline bool shouldJIT(Wasm::LLIntCallee* callee, RequiredWasmJIT requiredJIT = RequiredWasmJIT::Any)
{
if (requiredJIT == RequiredWasmJIT::OMG) {
if (!Options::useOMGJIT() || !Wasm::OMGPlan::ensureGlobalOMGAllowlist().containsWasmFunction(callee->functionIndex()))
return false;
} else {
if (Options::wasmLLIntTiersUpToBBQ()
&& (!Options::useBBQJIT() || !Wasm::BBQPlan::ensureGlobalBBQAllowlist().containsWasmFunction(callee->functionIndex())))
return false;
if (!Options::wasmLLIntTiersUpToBBQ()
&& (!Options::useOMGJIT() || !Wasm::OMGPlan::ensureGlobalOMGAllowlist().containsWasmFunction(callee->functionIndex())))
return false;
}
if (!Options::wasmFunctionIndexRangeToCompile().isInRange(callee->functionIndex()))
return false;
return true;
}
inline bool jitCompileAndSetHeuristics(Wasm::LLIntCallee* callee, Wasm::Instance* instance)
{
ASSERT(!instance->module().moduleInformation().usesSIMD(callee->functionIndex()));
Wasm::LLIntTierUpCounter& tierUpCounter = callee->tierUpCounter();
if (!tierUpCounter.checkIfOptimizationThresholdReached()) {
dataLogLnIf(Options::verboseOSR(), " JIT threshold should be lifted.");
return false;
}
if (callee->replacement(instance->memory()->mode())) {
dataLogLnIf(Options::verboseOSR(), " Code was already compiled.");
tierUpCounter.optimizeSoon();
return true;
}
bool compile = false;
{
Locker locker { tierUpCounter.m_lock };
switch (tierUpCounter.m_compilationStatus) {
case Wasm::LLIntTierUpCounter::CompilationStatus::NotCompiled:
compile = true;
tierUpCounter.m_compilationStatus = Wasm::LLIntTierUpCounter::CompilationStatus::Compiling;
break;
case Wasm::LLIntTierUpCounter::CompilationStatus::Compiling:
tierUpCounter.optimizeAfterWarmUp();
break;
case Wasm::LLIntTierUpCounter::CompilationStatus::Compiled:
break;
}
}
if (compile) {
uint32_t functionIndex = callee->functionIndex();
RefPtr<Wasm::Plan> plan;
if (Options::wasmLLIntTiersUpToBBQ() && Wasm::BBQPlan::ensureGlobalBBQAllowlist().containsWasmFunction(functionIndex))
plan = adoptRef(*new Wasm::BBQPlan(instance->vm(), const_cast<Wasm::ModuleInformation&>(instance->module().moduleInformation()), functionIndex, callee->hasExceptionHandlers(), instance->calleeGroup(), Wasm::Plan::dontFinalize()));
else // No need to check OMG allow list: if we didn't want to compile this function, shouldJIT should have returned false.
plan = adoptRef(*new Wasm::OMGPlan(instance->vm(), Ref<Wasm::Module>(instance->module()), functionIndex, callee->hasExceptionHandlers(), instance->memory()->mode(), Wasm::Plan::dontFinalize()));
Wasm::ensureWorklist().enqueue(*plan);
if (UNLIKELY(!Options::useConcurrentJIT()))
plan->waitForCompletion();
else
tierUpCounter.optimizeAfterWarmUp();
}
return !!callee->replacement(instance->memory()->mode());
}
inline bool jitCompileSIMDFunction(Wasm::LLIntCallee* callee, Wasm::Instance* instance)
{
Wasm::LLIntTierUpCounter& tierUpCounter = callee->tierUpCounter();
if (callee->replacement(instance->memory()->mode())) {
dataLogLnIf(Options::verboseOSR(), " SIMD code was already compiled.");
return true;
}
bool compile = false;
while (!compile) {
Locker locker { tierUpCounter.m_lock };
switch (tierUpCounter.m_compilationStatus) {
case Wasm::LLIntTierUpCounter::CompilationStatus::NotCompiled:
compile = true;
tierUpCounter.m_compilationStatus = Wasm::LLIntTierUpCounter::CompilationStatus::Compiling;
break;
case Wasm::LLIntTierUpCounter::CompilationStatus::Compiling:
// This spinlock is bad, but this is only temporary.
continue;
case Wasm::LLIntTierUpCounter::CompilationStatus::Compiled:
RELEASE_ASSERT(!!callee->replacement(instance->memory()->mode()));
return true;
}
}
uint32_t functionIndex = callee->functionIndex();
ASSERT(instance->module().moduleInformation().usesSIMD(functionIndex));
RefPtr<Wasm::Plan> plan;
if (Options::wasmLLIntTiersUpToBBQ())
plan = adoptRef(*new Wasm::BBQPlan(instance->vm(), const_cast<Wasm::ModuleInformation&>(instance->module().moduleInformation()), functionIndex, callee->hasExceptionHandlers(), instance->calleeGroup(), Wasm::Plan::dontFinalize()));
else
plan = adoptRef(*new Wasm::OMGPlan(instance->vm(), Ref<Wasm::Module>(instance->module()), functionIndex, callee->hasExceptionHandlers(), instance->memory()->mode(), Wasm::Plan::dontFinalize()));
Wasm::ensureWorklist().enqueue(*plan);
plan->waitForCompletion();
Locker locker { tierUpCounter.m_lock };
RELEASE_ASSERT(tierUpCounter.m_compilationStatus == Wasm::LLIntTierUpCounter::CompilationStatus::Compiled);
RELEASE_ASSERT(!!callee->replacement(instance->memory()->mode()));
return true;
}
WASM_SLOW_PATH_DECL(prologue_osr)
{
UNUSED_PARAM(pc);
Wasm::LLIntCallee* callee = CALLEE();
if (!shouldJIT(callee)) {
callee->tierUpCounter().deferIndefinitely();
WASM_RETURN_TWO(nullptr, nullptr);
}
if (!Options::useWasmLLIntPrologueOSR())
WASM_RETURN_TWO(nullptr, nullptr);
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered prologue_osr with tierUpCounter = ", callee->tierUpCounter());
if (!jitCompileAndSetHeuristics(callee, instance))
WASM_RETURN_TWO(nullptr, nullptr);
WASM_RETURN_TWO(callee->replacement(instance->memory()->mode())->entrypoint().taggedPtr(), nullptr);
}
WASM_SLOW_PATH_DECL(loop_osr)
{
Wasm::LLIntCallee* callee = CALLEE();
Wasm::LLIntTierUpCounter& tierUpCounter = callee->tierUpCounter();
if (!Options::useWebAssemblyOSR() || !Options::useWasmLLIntLoopOSR() || !shouldJIT(callee, RequiredWasmJIT::OMG)) {
slow_path_wasm_prologue_osr(callFrame, pc, instance);
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 = callee->bytecodeOffset(pc);
const auto& osrEntryData = tierUpCounter.osrEntryDataForLoop(loopOSREntryBytecodeOffset);
if (Options::wasmLLIntTiersUpToBBQ() && Options::useSinglePassBBQJIT()) {
if (!jitCompileAndSetHeuristics(callee, instance))
WASM_RETURN_TWO(nullptr, nullptr);
Wasm::BBQCallee* bbqCallee;
{
Locker locker { instance->calleeGroup()->m_lock };
bbqCallee = instance->calleeGroup()->bbqCallee(locker, callee->functionIndex());
}
RELEASE_ASSERT(bbqCallee);
size_t osrEntryScratchBufferSize = bbqCallee->osrEntryScratchBufferSize();
RELEASE_ASSERT(osrEntryScratchBufferSize >= osrEntryData.values.size());
uint64_t* buffer = instance->vm().wasmContext.scratchBufferForSize(osrEntryScratchBufferSize);
if (!buffer)
WASM_RETURN_TWO(nullptr, nullptr);
uint32_t index = 0;
buffer[index ++] = osrEntryData.loopIndex; // First entry is the loop index.
for (VirtualRegister reg : osrEntryData.values)
buffer[index++] = READ(reg).encodedJSValue();
auto sharedLoopEntrypoint = bbqCallee->sharedLoopEntrypoint();
RELEASE_ASSERT(sharedLoopEntrypoint);
WASM_RETURN_TWO(buffer, sharedLoopEntrypoint->taggedPtr());
} else if (Options::wasmLLIntTiersUpToBBQ() && Wasm::BBQPlan::planGeneratesLoopOSREntrypoints(instance->module().moduleInformation())) {
if (!jitCompileAndSetHeuristics(callee, instance))
WASM_RETURN_TWO(nullptr, nullptr);
Wasm::BBQCallee* bbqCallee;
{
Locker locker { instance->calleeGroup()->m_lock };
bbqCallee = instance->calleeGroup()->bbqCallee(locker, callee->functionIndex());
}
RELEASE_ASSERT(bbqCallee);
size_t osrEntryScratchBufferSize = bbqCallee->osrEntryScratchBufferSize();
RELEASE_ASSERT(osrEntryScratchBufferSize >= osrEntryData.values.size());
uint64_t* buffer = instance->vm().wasmContext.scratchBufferForSize(osrEntryScratchBufferSize);
if (!buffer)
WASM_RETURN_TWO(nullptr, nullptr);
RELEASE_ASSERT(osrEntryData.loopIndex < bbqCallee->loopEntrypoints().size());
uint32_t index = 0;
for (VirtualRegister reg : osrEntryData.values)
buffer[index++] = READ(reg).encodedJSValue();
WASM_RETURN_TWO(buffer, bbqCallee->loopEntrypoints()[osrEntryData.loopIndex].taggedPtr());
} else {
const auto doOSREntry = [&](Wasm::OSREntryCallee* osrEntryCallee) {
if (osrEntryCallee->loopIndex() != osrEntryData.loopIndex)
WASM_RETURN_TWO(nullptr, nullptr);
size_t osrEntryScratchBufferSize = osrEntryCallee->osrEntryScratchBufferSize();
RELEASE_ASSERT(osrEntryScratchBufferSize == osrEntryData.values.size());
uint64_t* buffer = instance->vm().wasmContext.scratchBufferForSize(osrEntryScratchBufferSize);
if (!buffer)
WASM_RETURN_TWO(nullptr, nullptr);
uint32_t index = 0;
for (VirtualRegister reg : osrEntryData.values)
buffer[index++] = READ(reg).encodedJSValue();
WASM_RETURN_TWO(buffer, osrEntryCallee->entrypoint().taggedPtr());
};
if (auto* osrEntryCallee = callee->osrEntryCallee(instance->memory()->mode()))
return doOSREntry(osrEntryCallee);
bool compile = false;
{
Locker locker { tierUpCounter.m_lock };
switch (tierUpCounter.m_loopCompilationStatus) {
case Wasm::LLIntTierUpCounter::CompilationStatus::NotCompiled:
compile = true;
tierUpCounter.m_loopCompilationStatus = Wasm::LLIntTierUpCounter::CompilationStatus::Compiling;
break;
case Wasm::LLIntTierUpCounter::CompilationStatus::Compiling:
tierUpCounter.optimizeAfterWarmUp();
break;
case Wasm::LLIntTierUpCounter::CompilationStatus::Compiled:
break;
}
}
if (compile) {
Ref<Wasm::Plan> plan = adoptRef(*static_cast<Wasm::Plan*>(new Wasm::OSREntryPlan(instance->vm(), Ref<Wasm::Module>(instance->module()), Ref<Wasm::Callee>(*callee), callee->functionIndex(), callee->hasExceptionHandlers(), osrEntryData.loopIndex, instance->memory()->mode(), Wasm::Plan::dontFinalize())));
Wasm::ensureWorklist().enqueue(plan.copyRef());
if (UNLIKELY(!Options::useConcurrentJIT()))
plan->waitForCompletion();
else
tierUpCounter.optimizeAfterWarmUp();
}
if (auto* osrEntryCallee = callee->osrEntryCallee(instance->memory()->mode()))
return doOSREntry(osrEntryCallee);
WASM_RETURN_TWO(nullptr, nullptr);
}
}
WASM_SLOW_PATH_DECL(epilogue_osr)
{
Wasm::LLIntCallee* callee = CALLEE();
if (!shouldJIT(callee)) {
callee->tierUpCounter().deferIndefinitely();
WASM_END_IMPL();
}
if (!Options::useWasmLLIntEpilogueOSR())
WASM_END_IMPL();
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered epilogue_osr with tierUpCounter = ", callee->tierUpCounter());
jitCompileAndSetHeuristics(callee, instance);
WASM_END_IMPL();
}
WASM_SLOW_PATH_DECL(simd_go_straight_to_bbq_osr)
{
UNUSED_PARAM(pc);
Wasm::LLIntCallee* callee = CALLEE();
if (!Options::useWebAssemblySIMD())
RELEASE_ASSERT_NOT_REACHED();
RELEASE_ASSERT(shouldJIT(callee));
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered simd_go_straight_to_bbq_osr with tierUpCounter = ", callee->tierUpCounter());
RELEASE_ASSERT(jitCompileSIMDFunction(callee, instance));
WASM_RETURN_TWO(callee->replacement(instance->memory()->mode())->entrypoint().taggedPtr(), nullptr);
}
#endif
WASM_SLOW_PATH_DECL(trace)
{
UNUSED_PARAM(instance);
if (!Options::traceLLIntExecution())
WASM_END_IMPL();
WasmOpcodeID opcodeID = pc->opcodeID();
dataLogF("<%p> %p / %p: executing bc#%zu, %s, pc = %p\n",
&Thread::current(),
CALLEE(),
callFrame,
static_cast<intptr_t>(CALLEE()->bytecodeOffset(pc)),
pc->name(),
pc);
if (opcodeID == wasm_enter) {
dataLogF("Frame will eventually return to %p\n", callFrame->returnPCForInspection());
*bitwise_cast<volatile char*>(callFrame->returnPCForInspection());
}
if (opcodeID == wasm_ret) {
dataLogF("Will be returning to %p\n", callFrame->returnPCForInspection());
dataLogF("The new cfr will be %p\n", callFrame->callerFrame());
}
WASM_END_IMPL();
}
WASM_SLOW_PATH_DECL(out_of_line_jump_target)
{
UNUSED_PARAM(instance);
pc = CALLEE()->outOfLineJumpTarget(pc);
WASM_END_IMPL();
}
WASM_SLOW_PATH_DECL(ref_func)
{
auto instruction = pc->as<WasmRefFunc>();
WASM_RETURN(Wasm::refFunc(instance, instruction.m_functionIndex));
}
WASM_IPINT_EXTERN_CPP_DECL(ref_func, unsigned index)
{
#if CPU(ARM64) || CPU(X86_64)
WASM_RETURN_TWO(bitwise_cast<void*>(Wasm::refFunc(instance, index)), 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(index);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_SLOW_PATH_DECL(array_new)
{
auto instruction = pc->as<WasmArrayNew>();
uint32_t size = READ(instruction.m_size).unboxedUInt32();
Wasm::ArrayGetKind kind = static_cast<Wasm::ArrayGetKind>(instruction.m_arrayNewKind);
const Wasm::TypeDefinition& arraySignature = instance->module().moduleInformation().typeSignatures[instruction.m_typeIndex]->expand();
ASSERT(arraySignature.is<Wasm::ArrayType>());
Wasm::StorageType elementType = arraySignature.as<Wasm::ArrayType>()->elementType().type;
EncodedJSValue value = 0;
switch (kind) {
case Wasm::ArrayGetKind::New: {
value = READ(instruction.m_value).encodedJSValue();
break;
}
case Wasm::ArrayGetKind::NewDefault: {
if (Wasm::isRefType(elementType))
value = JSValue::encode(jsNull());
break;
}
case Wasm::ArrayGetKind::NewFixed: {
// In this case, m_value must refer to a possibly-empty array of arguments,
// so m_value being constant would be a bug.
ASSERT(!instruction.m_value.isConstant());
WASM_RETURN(Wasm::arrayNewFixed(instance, instruction.m_typeIndex, size, reinterpret_cast<uint64_t*>(&callFrame->r(instruction.m_value))));
}
}
WASM_RETURN(Wasm::arrayNew(instance, instruction.m_typeIndex, size, value));
}
WASM_SLOW_PATH_DECL(array_get)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
auto instruction = pc->as<WasmArrayGet>();
EncodedJSValue arrayref = READ(instruction.m_arrayref).encodedJSValue();
if (JSValue::decode(arrayref).isNull())
WASM_THROW(Wasm::ExceptionType::NullArrayGet);
uint32_t index = READ(instruction.m_index).unboxedUInt32();
JSValue arrayValue = JSValue::decode(arrayref);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
if (index >= arrayObject->size())
WASM_THROW(Wasm::ExceptionType::OutOfBoundsArrayGet);
Wasm::ExtGCOpType arrayGetKind = static_cast<Wasm::ExtGCOpType>(instruction.m_arrayGetKind);
if (arrayGetKind == Wasm::ExtGCOpType::ArrayGetS) {
EncodedJSValue value = Wasm::arrayGet(instance, instruction.m_typeIndex, arrayref, index);
Wasm::StorageType type = arrayObject->elementType().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 result = static_cast<int32_t>(value);
result = result << bitShift;
WASM_RETURN(static_cast<EncodedJSValue>(result >> bitShift));
} else
WASM_RETURN(Wasm::arrayGet(instance, instruction.m_typeIndex, arrayref, index));
}
WASM_SLOW_PATH_DECL(array_set)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
auto instruction = pc->as<WasmArraySet>();
EncodedJSValue arrayref = READ(instruction.m_arrayref).encodedJSValue();
if (JSValue::decode(arrayref).isNull())
WASM_THROW(Wasm::ExceptionType::NullArraySet);
uint32_t index = READ(instruction.m_index).unboxedUInt32();
EncodedJSValue value = READ(instruction.m_value).encodedJSValue();
JSValue arrayValue = JSValue::decode(arrayref);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
if (index >= arrayObject->size())
WASM_THROW(Wasm::ExceptionType::OutOfBoundsArraySet);
Wasm::arraySet(instance, instruction.m_typeIndex, arrayref, index, value);
WASM_END_IMPL();
}
WASM_SLOW_PATH_DECL(struct_new)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
auto instruction = pc->as<WasmStructNew>();
ASSERT(instruction.m_typeIndex < instance->module().moduleInformation().typeCount());
ASSERT(!instruction.m_firstValue.isConstant());
WASM_RETURN(Wasm::structNew(instance, instruction.m_typeIndex, instruction.m_useDefault, instruction.m_useDefault ? nullptr : reinterpret_cast<uint64_t*>(&callFrame->r(instruction.m_firstValue))));
}
WASM_SLOW_PATH_DECL(struct_get)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmStructGet>();
auto structReference = READ(instruction.m_structReference).encodedJSValue();
if (JSValue::decode(structReference).isNull())
WASM_THROW(Wasm::ExceptionType::NullStructGet);
WASM_RETURN(Wasm::structGet(structReference, instruction.m_fieldIndex));
}
WASM_SLOW_PATH_DECL(struct_set)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
auto instruction = pc->as<WasmStructSet>();
auto structReference = READ(instruction.m_structReference).encodedJSValue();
if (JSValue::decode(structReference).isNull())
WASM_THROW(Wasm::ExceptionType::NullStructSet);
auto value = READ(instruction.m_value).encodedJSValue();
Wasm::structSet(instance, structReference, instruction.m_fieldIndex, value);
WASM_END();
}
WASM_SLOW_PATH_DECL(table_get)
{
auto instruction = pc->as<WasmTableGet>();
int32_t index = READ(instruction.m_index).unboxedInt32();
EncodedJSValue result = Wasm::tableGet(instance, instruction.m_tableIndex, index);
if (!result)
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
WASM_RETURN(result);
}
WASM_IPINT_EXTERN_CPP_DECL(table_get, unsigned tableIndex, unsigned index)
{
#if CPU(ARM64) || CPU(X86_64)
EncodedJSValue result = Wasm::tableGet(instance, tableIndex, index);
if (!result)
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<uint64_t>(Wasm::ExceptionType::OutOfBoundsTableAccess)));
WASM_RETURN_TWO(bitwise_cast<void*>(result), 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(tableIndex);
UNUSED_PARAM(index);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARMv8 and X86_64 (for now)");
#endif
}
WASM_SLOW_PATH_DECL(table_set)
{
auto instruction = pc->as<WasmTableSet>();
uint32_t index = READ(instruction.m_index).unboxedUInt32();
EncodedJSValue value = READ(instruction.m_value).encodedJSValue();
if (!Wasm::tableSet(instance, instruction.m_tableIndex, index, value))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
WASM_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_set, unsigned tableIndex, unsigned index, EncodedJSValue value)
{
#if CPU(ARM64) || CPU(X86_64)
if (!Wasm::tableSet(instance, tableIndex, index, value))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<uint64_t>(Wasm::ExceptionType::OutOfBoundsTableAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(tableIndex);
UNUSED_PARAM(index);
UNUSED_PARAM(value);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_SLOW_PATH_DECL(table_init)
{
auto instruction = pc->as<WasmTableInit>();
uint32_t dstOffset = READ(instruction.m_dstOffset).unboxedUInt32();
uint32_t srcOffset = READ(instruction.m_srcOffset).unboxedUInt32();
uint32_t length = READ(instruction.m_length).unboxedUInt32();
if (!Wasm::tableInit(instance, instruction.m_elementIndex, instruction.m_tableIndex, dstOffset, srcOffset, length))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
WASM_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_init, uint32_t* metadata, uint32_t dest, uint64_t srcAndLength)
{
#if CPU(ARM64) || CPU(X86_64)
uint32_t dstOffset = dest;
uint32_t srcOffset = srcAndLength >> 32;
uint32_t length = srcAndLength & 0xffffffff;
if (!Wasm::tableInit(instance, metadata[0], metadata[1], dstOffset, srcOffset, length))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<uint64_t>(Wasm::ExceptionType::OutOfBoundsTableAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(metadata);
UNUSED_PARAM(dest);
UNUSED_PARAM(srcAndLength);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_SLOW_PATH_DECL(table_fill)
{
auto instruction = pc->as<WasmTableFill>();
uint32_t offset = READ(instruction.m_offset).unboxedUInt32();
EncodedJSValue fill = READ(instruction.m_fill).encodedJSValue();
uint32_t size = READ(instruction.m_size).unboxedUInt32();
if (!Wasm::tableFill(instance, instruction.m_tableIndex, offset, fill, size))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
WASM_END();
}
WASM_IPINT_EXTERN_CPP_DECL(table_fill, uint32_t tableIndex, EncodedJSValue fill, int64_t offsetAndSize)
{
#if CPU(ARM64) || CPU(X86_64)
uint32_t offset = offsetAndSize >> 32;
uint32_t size = offsetAndSize & 0xffffffff;
if (!Wasm::tableFill(instance, tableIndex, offset, fill, size))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<uint64_t>(Wasm::ExceptionType::OutOfBoundsTableAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(tableIndex);
UNUSED_PARAM(fill);
UNUSED_PARAM(offsetAndSize);
RELEASE_ASSERT_NOT_REACHED("IPInt only supports ARM64 and X86_64 (for now)");
#endif
}
WASM_SLOW_PATH_DECL(table_grow)
{
auto instruction = pc->as<WasmTableGrow>();
EncodedJSValue fill = READ(instruction.m_fill).encodedJSValue();
uint32_t size = READ(instruction.m_size).unboxedUInt32();
WASM_RETURN(Wasm::tableGrow(instance, instruction.m_tableIndex, fill, size));
}
WASM_IPINT_EXTERN_CPP_DECL(table_grow, int32_t tableIndex, EncodedJSValue fill, uint32_t size)
{
#if CPU(ARM64) || CPU(X86_64)
WASM_RETURN_TWO(bitwise_cast<void*>(Wasm::tableGrow(instance, tableIndex, fill, size)), 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(tableIndex);
UNUSED_PARAM(fill);
UNUSED_PARAM(size);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL_1P(current_memory)
{
#if CPU(ARM64) || CPU(X86_64)
size_t size = instance->memory()->handle().size() >> 16;
WASM_RETURN_TWO(bitwise_cast<void*>(size), 0);
#else
UNUSED_PARAM(instance);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_SLOW_PATH_DECL(grow_memory)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
auto instruction = pc->as<WasmGrowMemory>();
int32_t delta = READ(instruction.m_delta).unboxedInt32();
WASM_RETURN(Wasm::growMemory(instance, delta));
}
WASM_IPINT_EXTERN_CPP_DECL(memory_grow, int32_t delta)
{
WASM_RETURN_TWO(reinterpret_cast<void*>(Wasm::growMemory(instance, delta)), 0);
}
WASM_IPINT_EXTERN_CPP_DECL(memory_init, int32_t dataIndex, int32_t dst, int64_t srcAndLength)
{
#if CPU(ARM64) || CPU(X86_64)
if (!Wasm::memoryInit(instance, dataIndex, dst, srcAndLength >> 32, srcAndLength & 0xffffffff))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<int64_t>(Wasm::ExceptionType::OutOfBoundsMemoryAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(dataIndex);
UNUSED_PARAM(dst);
UNUSED_PARAM(srcAndLength);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(data_drop, int32_t dataIndex)
{
Wasm::dataDrop(instance, dataIndex);
WASM_RETURN_TWO(0, 0);
}
WASM_IPINT_EXTERN_CPP_DECL(memory_copy, int32_t dst, int32_t src, int32_t count)
{
#if CPU(ARM64) || CPU(X86_64)
if (!Wasm::memoryCopy(instance, dst, src, count))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<int64_t>(Wasm::ExceptionType::OutOfBoundsMemoryAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(dst);
UNUSED_PARAM(src);
UNUSED_PARAM(count);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(memory_fill, int32_t dst, int32_t targetValue, int32_t count)
{
#if CPU(ARM64) || CPU(X86_64)
if (!Wasm::memoryFill(instance, dst, targetValue, count))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<int64_t>(Wasm::ExceptionType::OutOfBoundsMemoryAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(dst);
UNUSED_PARAM(targetValue);
UNUSED_PARAM(count);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(elem_drop, int32_t dataIndex)
{
Wasm::elemDrop(instance, dataIndex);
WASM_RETURN_TWO(0, 0);
}
WASM_IPINT_EXTERN_CPP_DECL(table_copy, int32_t* metadata, int32_t dst, int64_t srcAndCount)
{
#if CPU(ARM64) || CPU(X86_64)
if (!Wasm::tableCopy(instance, metadata[0], metadata[1], dst, srcAndCount >> 32, srcAndCount & 0xffffffff))
WASM_RETURN_TWO(bitwise_cast<void*>(1L), bitwise_cast<void*>(static_cast<int64_t>(Wasm::ExceptionType::OutOfBoundsTableAccess)));
WASM_RETURN_TWO(0, 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(metadata);
UNUSED_PARAM(dst);
UNUSED_PARAM(srcAndCount);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
WASM_IPINT_EXTERN_CPP_DECL(table_size, int32_t tableIndex)
{
#if CPU(ARM64) || CPU(X86_64)
int32_t result = Wasm::tableSize(instance, tableIndex);
WASM_RETURN_TWO(bitwise_cast<void*>(static_cast<EncodedJSValue>(result)), 0);
#else
UNUSED_PARAM(instance);
UNUSED_PARAM(tableIndex);
RELEASE_ASSERT_NOT_REACHED("IPInt only supported on ARM64 and X86_64 (for now)");
#endif
}
inline UGPRPair doWasmCall(Wasm::Instance* instance, unsigned functionIndex)
{
uint32_t importFunctionCount = instance->module().moduleInformation().importFunctionCount();
CodePtr<WasmEntryPtrTag> codePtr;
if (functionIndex < importFunctionCount) {
Wasm::Instance::ImportFunctionInfo* functionInfo = instance->importFunctionInfo(functionIndex);
codePtr = functionInfo->importFunctionStub;
} else {
// Target is a wasm function within the same instance
codePtr = *instance->calleeGroup()->entrypointLoadLocationFromFunctionIndexSpace(functionIndex);
}
WASM_CALL_RETURN(instance, codePtr.taggedPtr(), WasmEntryPtrTag);
}
WASM_IPINT_EXTERN_CPP_DECL(call, unsigned functionIndex)
{
return doWasmCall(instance, functionIndex);
}
WASM_SLOW_PATH_DECL(call)
{
UNUSED_PARAM(callFrame);
auto instruction = pc->as<WasmCall>();
return doWasmCall(instance, instruction.m_functionIndex);
}
inline UGPRPair doWasmCallIndirect(CallFrame* callFrame, Wasm::Instance* instance, unsigned functionIndex, unsigned tableIndex, unsigned typeIndex)
{
Wasm::FuncRefTable* table = instance->table(tableIndex)->asFuncrefTable();
if (functionIndex >= table->length())
WASM_THROW(Wasm::ExceptionType::OutOfBoundsCallIndirect);
const Wasm::FuncRefTable::Function& function = table->function(functionIndex);
if (function.m_function.typeIndex == Wasm::TypeDefinition::invalidIndex)
WASM_THROW(Wasm::ExceptionType::NullTableEntry);
const auto& callSignature = CALLEE()->signature(typeIndex);
if (callSignature.index() != function.m_function.typeIndex)
WASM_THROW(Wasm::ExceptionType::BadSignature);
WASM_CALL_RETURN(function.m_instance, function.m_function.entrypointLoadLocation->taggedPtr(), WasmEntryPtrTag);
}
WASM_IPINT_EXTERN_CPP_DECL(call_indirect, CallFrame* callFrame, unsigned functionIndex, unsigned* metadataEntry)
{
unsigned tableIndex = metadataEntry[0];
unsigned typeIndex = metadataEntry[1];
Wasm::FuncRefTable* table = instance->table(tableIndex)->asFuncrefTable();
if (functionIndex >= table->length())
WASM_THROW(Wasm::ExceptionType::OutOfBoundsCallIndirect);
const Wasm::FuncRefTable::Function& function = table->function(functionIndex);
if (function.m_function.typeIndex == Wasm::TypeDefinition::invalidIndex)
WASM_THROW(Wasm::ExceptionType::NullTableEntry);
const auto& callSignature = static_cast<Wasm::IPIntCallee*>(callFrame->callee().asWasmCallee())->signature(typeIndex);
if (callSignature.index() != function.m_function.typeIndex)
WASM_THROW(Wasm::ExceptionType::BadSignature);
WASM_CALL_RETURN(function.m_instance, function.m_function.entrypointLoadLocation->taggedPtr(), WasmEntryPtrTag);
}
WASM_SLOW_PATH_DECL(call_indirect)
{
auto instruction = pc->as<WasmCallIndirect>();
unsigned functionIndex = READ(instruction.m_functionIndex).unboxedInt32();
return doWasmCallIndirect(callFrame, instance, functionIndex, instruction.m_tableIndex, instruction.m_typeIndex);
}
inline UGPRPair doWasmCallRef(CallFrame* callFrame, Wasm::Instance* callerInstance, JSValue targetReference, unsigned typeIndex)
{
UNUSED_PARAM(callFrame);
UNUSED_PARAM(callerInstance);
if (targetReference.isNull())
WASM_THROW(Wasm::ExceptionType::NullReference);
ASSERT(targetReference.isObject());
JSObject* referenceAsObject = jsCast<JSObject*>(targetReference);
ASSERT(referenceAsObject->inherits<WebAssemblyFunctionBase>());
auto* wasmFunction = jsCast<WebAssemblyFunctionBase*>(referenceAsObject);
Wasm::WasmToWasmImportableFunction function = wasmFunction->importableFunction();
Wasm::Instance* calleeInstance = &wasmFunction->instance()->instance();
ASSERT(function.typeIndex == CALLEE()->signature(typeIndex).index());
UNUSED_PARAM(typeIndex);
WASM_CALL_RETURN(calleeInstance, function.entrypointLoadLocation->taggedPtr(), WasmEntryPtrTag);
}
WASM_SLOW_PATH_DECL(call_ref)
{
auto instruction = pc->as<WasmCallRef>();
JSValue reference = JSValue::decode(READ(instruction.m_functionReference).encodedJSValue());
return doWasmCallRef(callFrame, instance, reference, instruction.m_typeIndex);
}
WASM_SLOW_PATH_DECL(tail_call)
{
UNUSED_PARAM(callFrame);
auto instruction = pc->as<WasmTailCall>();
return doWasmCall(instance, instruction.m_functionIndex);
}
WASM_SLOW_PATH_DECL(tail_call_indirect)
{
auto instruction = pc->as<WasmTailCallIndirect>();
unsigned functionIndex = READ(instruction.m_functionIndex).unboxedInt32();
return doWasmCallIndirect(callFrame, instance, functionIndex, instruction.m_tableIndex, instruction.m_signatureIndex);
}
static size_t jsrSize()
{
static size_t jsrSize = 0;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
jsrSize = Wasm::wasmCallingConvention().jsrArgs.size();
});
return jsrSize;
}
WASM_SLOW_PATH_DECL(call_builtin)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
auto instruction = pc->as<WasmCallBuiltin>();
Register* stackBottom = callFrame->registers() - instruction.m_stackOffset;
Register* stackStart = stackBottom + CallFrame::headerSizeInRegisters + /* indirect call target */ 1;
Register* gprStart = stackStart + instruction.m_numberOfStackArgs;
Register* fprStart = gprStart + jsrSize();
UNUSED_PARAM(fprStart);
Wasm::LLIntBuiltin builtin = static_cast<Wasm::LLIntBuiltin>(instruction.m_builtinIndex);
unsigned gprIndex = 0;
unsigned fprIndex = 0;
unsigned stackIndex = 0;
auto takeGPR = [&]() -> Register& {
if (gprIndex != jsrSize())
return gprStart[gprIndex++];
return stackStart[stackIndex++];
};
UNUSED_PARAM(fprIndex);
switch (builtin) {
case Wasm::LLIntBuiltin::CurrentMemory: {
size_t size = instance->memory()->handle().size() >> 16;
gprStart[0] = static_cast<EncodedJSValue>(size);
WASM_END();
}
case Wasm::LLIntBuiltin::MemoryFill: {
uint32_t dstAddress = takeGPR().unboxedUInt32();
uint32_t targetValue = takeGPR().unboxedUInt32();
uint32_t count = takeGPR().unboxedUInt32();
if (!Wasm::memoryFill(instance, dstAddress, targetValue, count))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
WASM_END();
}
case Wasm::LLIntBuiltin::MemoryCopy: {
uint32_t dstAddress = takeGPR().unboxedUInt32();
uint32_t srcAddress = takeGPR().unboxedUInt32();
uint32_t count = takeGPR().unboxedUInt32();
if (!Wasm::memoryCopy(instance, dstAddress, srcAddress, count))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
WASM_END();
}
case Wasm::LLIntBuiltin::MemoryInit: {
uint32_t dstAddress = takeGPR().unboxedUInt32();
uint32_t srcAddress = takeGPR().unboxedUInt32();
uint32_t length = takeGPR().unboxedUInt32();
uint32_t dataSegmentIndex = takeGPR().unboxedUInt32();
if (!Wasm::memoryInit(instance, dataSegmentIndex, dstAddress, srcAddress, length))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
WASM_END();
}
case Wasm::LLIntBuiltin::TableSize: {
uint32_t tableIndex = takeGPR().unboxedUInt32();
int32_t result = Wasm::tableSize(instance, tableIndex);
gprStart[0] = static_cast<EncodedJSValue>(result);
WASM_END();
}
case Wasm::LLIntBuiltin::TableCopy: {
int32_t dstOffset = takeGPR().unboxedInt32();
int32_t srcOffset = takeGPR().unboxedInt32();
int32_t length = takeGPR().unboxedInt32();
uint32_t dstTableIndex = takeGPR().unboxedUInt32();
uint32_t srcTableIndex = takeGPR().unboxedUInt32();
if (!Wasm::tableCopy(instance, dstTableIndex, srcTableIndex, dstOffset, srcOffset, length))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess);
WASM_END();
}
case Wasm::LLIntBuiltin::DataDrop: {
uint32_t dataSegmentIndex = takeGPR().unboxedUInt32();
Wasm::dataDrop(instance, dataSegmentIndex);
WASM_END();
}
case Wasm::LLIntBuiltin::ElemDrop: {
uint32_t elementIndex = takeGPR().unboxedUInt32();
Wasm::elemDrop(instance, elementIndex);
WASM_END();
}
case Wasm::LLIntBuiltin::RefTest:
case Wasm::LLIntBuiltin::RefCast: {
auto reference = takeGPR().encodedJSValue();
bool allowNull = static_cast<bool>(takeGPR().unboxedInt32());
int32_t heapType = takeGPR().unboxedInt32();
Wasm::TypeIndex typeIndex;
if (Wasm::typeIndexIsType(static_cast<Wasm::TypeIndex>(heapType)))
typeIndex = static_cast<Wasm::TypeIndex>(heapType);
else
typeIndex = instance->module().moduleInformation().typeSignatures[heapType]->index();
if (builtin == Wasm::LLIntBuiltin::RefTest)
gprStart[0] = static_cast<uint32_t>(Wasm::refCast(reference, allowNull, typeIndex));
else {
if (!Wasm::refCast(reference, allowNull, typeIndex))
WASM_THROW(Wasm::ExceptionType::CastFailure);
gprStart[0] = reference;
}
WASM_END();
}
case Wasm::LLIntBuiltin::ArrayNewData: {
uint32_t typeIndex = takeGPR().unboxedUInt32();
uint32_t dataSegmentIndex = takeGPR().unboxedUInt32();
uint32_t arraySize = takeGPR().unboxedUInt32();
uint32_t offset = takeGPR().unboxedUInt32();
EncodedJSValue result = Wasm::arrayNewData(instance, typeIndex, dataSegmentIndex, arraySize, offset);
// arrayNewData returns false iff the segment access is out of bounds
if (JSValue::decode(result).isNull())
WASM_THROW(Wasm::ExceptionType::OutOfBoundsDataSegmentAccess);
gprStart[0] = static_cast<EncodedJSValue>(result);
WASM_END();
}
case Wasm::LLIntBuiltin::ArrayNewElem: {
uint32_t typeIndex = takeGPR().unboxedUInt32();
uint32_t elemSegmentIndex = takeGPR().unboxedUInt32();
uint32_t arraySize = takeGPR().unboxedUInt32();
uint32_t offset = takeGPR().unboxedUInt32();
EncodedJSValue result = Wasm::arrayNewElem(instance, typeIndex, elemSegmentIndex, arraySize, offset);
// arrayNewElem returns null iff the segment access is out of bounds
if (JSValue::decode(result).isNull())
WASM_THROW(Wasm::ExceptionType::OutOfBoundsElementSegmentAccess);
gprStart[0] = static_cast<EncodedJSValue>(result);
WASM_END();
}
case Wasm::LLIntBuiltin::ExternInternalize: {
auto reference = takeGPR().encodedJSValue();
gprStart[0] = Wasm::externInternalize(reference);
WASM_END();
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
WASM_END();
}
WASM_IPINT_EXTERN_CPP_DECL(set_global_ref, uint32_t globalIndex, JSValue value)
{
instance->setGlobal(globalIndex, value);
WASM_RETURN_TWO(0, 0);
}
WASM_SLOW_PATH_DECL(set_global_ref)
{
auto instruction = pc->as<WasmSetGlobalRef>();
instance->setGlobal(instruction.m_globalIndex, READ(instruction.m_value).jsValue());
WASM_END_IMPL();
}
WASM_IPINT_EXTERN_CPP_DECL(get_global_64, unsigned index)
{
#if CPU(ARM64) || CPU(X86_64)
WASM_RETURN_TWO(bitwise_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(set_global_64, unsigned index, uint64_t value)
{
instance->setGlobal(index, value);
WASM_RETURN_TWO(0, 0);
}
WASM_SLOW_PATH_DECL(set_global_ref_portable_binding)
{
auto instruction = pc->as<WasmSetGlobalRefPortableBinding>();
instance->setGlobal(instruction.m_globalIndex, READ(instruction.m_value).jsValue());
WASM_END_IMPL();
}
WASM_SLOW_PATH_DECL(memory_atomic_wait32)
{
auto instruction = pc->as<WasmMemoryAtomicWait32>();
unsigned base = READ(instruction.m_pointer).unboxedInt32();
unsigned offset = instruction.m_offset;
uint32_t value = READ(instruction.m_value).unboxedInt32();
int64_t timeout = READ(instruction.m_timeout).unboxedInt64();
int32_t result = Wasm::memoryAtomicWait32(instance, base, offset, value, timeout);
if (result < 0)
WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
WASM_RETURN(result);
}
WASM_SLOW_PATH_DECL(memory_atomic_wait64)
{
auto instruction = pc->as<WasmMemoryAtomicWait64>();
unsigned base = READ(instruction.m_pointer).unboxedInt32();
unsigned offset = instruction.m_offset;
uint64_t value = READ(instruction.m_value).unboxedInt64();
int64_t timeout = READ(instruction.m_timeout).unboxedInt64();
int32_t result = Wasm::memoryAtomicWait64(instance, base, offset, value, timeout);
if (result < 0)
WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
WASM_RETURN(result);
}
WASM_SLOW_PATH_DECL(memory_atomic_notify)
{
auto instruction = pc->as<WasmMemoryAtomicNotify>();
unsigned base = READ(instruction.m_pointer).unboxedInt32();
unsigned offset = instruction.m_offset;
int32_t count = READ(instruction.m_count).unboxedInt32();
int32_t result = Wasm::memoryAtomicNotify(instance, base, offset, count);
if (result < 0)
WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
WASM_RETURN(result);
}
WASM_SLOW_PATH_DECL(throw)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto instruction = pc->as<WasmThrow>();
const Wasm::Tag& tag = instance->tag(instruction.m_exceptionIndex);
FixedVector<uint64_t> values(tag.parameterCount());
for (unsigned i = 0; i < tag.parameterCount(); ++i)
values[i] = READ((instruction.m_firstValue - i)).encodedJSValue();
JSWebAssemblyException* exception = JSWebAssemblyException::create(vm, globalObject->webAssemblyExceptionStructure(), tag, WTFMove(values));
throwException(globalObject, throwScope, exception);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
WASM_RETURN_TWO(vm.targetMachinePCForThrow, nullptr);
}
WASM_IPINT_EXTERN_CPP_DECL(throw, CallFrame* callFrame, uint32_t exceptionIndex)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
const Wasm::Tag& tag = instance->tag(exceptionIndex);
FixedVector<uint64_t> values(tag.parameterCount());
JSWebAssemblyException* exception = JSWebAssemblyException::create(vm, globalObject->webAssemblyExceptionStructure(), tag, WTFMove(values));
throwException(globalObject, throwScope, exception);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
WASM_RETURN_TWO(vm.targetMachinePCForThrow, nullptr);
}
WASM_SLOW_PATH_DECL(rethrow)
{
SlowPathFrameTracer tracer(instance->vm(), callFrame);
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto instruction = pc->as<WasmRethrow>();
JSValue exception = READ(instruction.m_exception).jsValue();
throwException(globalObject, throwScope, exception);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
WASM_RETURN_TWO(vm.targetMachinePCForThrow, nullptr);
}
WASM_SLOW_PATH_DECL(retrieve_and_clear_exception)
{
UNUSED_PARAM(callFrame);
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT(!!throwScope.exception());
Exception* exception = throwScope.exception();
JSValue thrownValue = exception->value();
void* payload = nullptr;
const auto& handleCatchAll = [&](const auto& instruction) {
callFrame->uncheckedR(instruction.m_exception) = thrownValue;
};
const auto& handleCatch = [&](const auto& instruction) {
JSWebAssemblyException* wasmException = jsDynamicCast<JSWebAssemblyException*>(thrownValue);
RELEASE_ASSERT(!!wasmException);
payload = bitwise_cast<void*>(wasmException->payload().data());
callFrame->uncheckedR(instruction.m_exception) = thrownValue;
};
if (pc->is<WasmCatch>())
handleCatch(pc->as<WasmCatch>());
else if (pc->is<WasmCatchAll>())
handleCatchAll(pc->as<WasmCatchAll>());
else
RELEASE_ASSERT_NOT_REACHED();
// 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(pc, payload);
}
#if USE(JSVALUE32_64)
WASM_SLOW_PATH_DECL(f32_ceil)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF32Ceil>();
float operand = READ(instruction.m_operand).unboxedFloat();
WASM_RETURN(JSValue::encode(wasmUnboxedFloat(std::ceil(operand))));
}
WASM_SLOW_PATH_DECL(f32_floor)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF32Floor>();
float operand = READ(instruction.m_operand).unboxedFloat();
WASM_RETURN(JSValue::encode(wasmUnboxedFloat(std::floor(operand))));
}
WASM_SLOW_PATH_DECL(f32_trunc)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF32Trunc>();
float operand = READ(instruction.m_operand).unboxedFloat();
WASM_RETURN(JSValue::encode(wasmUnboxedFloat(std::trunc(operand))));
}
WASM_SLOW_PATH_DECL(f32_nearest)
{
static_assert(std::numeric_limits<float>::round_style == std::round_to_nearest);
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF32Nearest>();
float operand = READ(instruction.m_operand).unboxedFloat();
WASM_RETURN(JSValue::encode(wasmUnboxedFloat(std::nearbyint(operand))));
}
WASM_SLOW_PATH_DECL(f64_ceil)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF64Ceil>();
double operand = READ(instruction.m_operand).unboxedDouble();
WASM_RETURN(JSValue::encode(jsDoubleNumber(std::ceil(operand))));
}
WASM_SLOW_PATH_DECL(f64_floor)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF64Floor>();
double operand = READ(instruction.m_operand).unboxedDouble();
WASM_RETURN(JSValue::encode(jsDoubleNumber(std::floor(operand))));
}
WASM_SLOW_PATH_DECL(f64_trunc)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF64Trunc>();
double operand = READ(instruction.m_operand).unboxedDouble();
WASM_RETURN(JSValue::encode(jsDoubleNumber(std::trunc(operand))));
}
WASM_SLOW_PATH_DECL(f64_nearest)
{
static_assert(std::numeric_limits<float>::round_style == std::round_to_nearest);
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF64Nearest>();
double operand = READ(instruction.m_operand).unboxedDouble();
WASM_RETURN(JSValue::encode(jsDoubleNumber(std::nearbyint(operand))));
}
WASM_SLOW_PATH_DECL(f32_convert_u_i64)
{
static_assert(std::numeric_limits<float>::round_style == std::round_to_nearest);
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF32ConvertUI64>();
uint64_t operand = READ(instruction.m_operand).unboxedInt64();
WASM_RETURN(JSValue::encode(wasmUnboxedFloat(static_cast<float>(operand))));
}
WASM_SLOW_PATH_DECL(f32_convert_s_i64)
{
static_assert(std::numeric_limits<float>::round_style == std::round_to_nearest);
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF32ConvertSI64>();
int64_t operand = READ(instruction.m_operand).unboxedInt64();
WASM_RETURN(JSValue::encode(wasmUnboxedFloat(static_cast<float>(operand))));
}
WASM_SLOW_PATH_DECL(f64_convert_u_i64)
{
static_assert(std::numeric_limits<float>::round_style == std::round_to_nearest);
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF64ConvertUI64>();
uint64_t operand = READ(instruction.m_operand).unboxedInt64();
WASM_RETURN(JSValue::encode(jsDoubleNumber(static_cast<double>(operand))));
}
WASM_SLOW_PATH_DECL(f64_convert_s_i64)
{
static_assert(std::numeric_limits<float>::round_style == std::round_to_nearest);
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmF64ConvertSI64>();
int64_t operand = READ(instruction.m_operand).unboxedInt64();
WASM_RETURN(JSValue::encode(jsDoubleNumber(static_cast<double>(operand))));
}
WASM_SLOW_PATH_DECL(i64_trunc_u_f32)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncUF32>();
float operand = READ(instruction.m_operand).unboxedFloat();
if (std::isnan(operand) || operand <= -1.0f || operand >= -2.0f * static_cast<float>(INT64_MIN))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTrunc);
WASM_RETURN(static_cast<uint64_t>(operand));
}
WASM_SLOW_PATH_DECL(i64_trunc_s_f32)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncSF32>();
float operand = READ(instruction.m_operand).unboxedFloat();
if (std::isnan(operand) || operand < static_cast<float>(INT64_MIN) || operand >= -static_cast<float>(INT64_MIN))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTrunc);
WASM_RETURN(static_cast<int64_t>(operand));
}
WASM_SLOW_PATH_DECL(i64_trunc_u_f64)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncUF64>();
double operand = READ(instruction.m_operand).unboxedDouble();
if (std::isnan(operand) || operand <= -1.0 || operand >= -2.0 * static_cast<double>(INT64_MIN))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTrunc);
WASM_RETURN(static_cast<uint64_t>(operand));
}
WASM_SLOW_PATH_DECL(i64_trunc_s_f64)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncSF64>();
double operand = READ(instruction.m_operand).unboxedDouble();
if (std::isnan(operand) || operand < static_cast<double>(INT64_MIN) || operand >= -static_cast<double>(INT64_MIN))
WASM_THROW(Wasm::ExceptionType::OutOfBoundsTrunc);
WASM_RETURN(static_cast<int64_t>(operand));
}
WASM_SLOW_PATH_DECL(i64_trunc_sat_f32_u)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncSatF32U>();
float operand = READ(instruction.m_operand).unboxedFloat();
uint64_t result;
if (std::isnan(operand) || operand <= -1.0f)
result = 0;
else if (operand >= -2.0f * static_cast<float>(INT64_MIN))
result = UINT64_MAX;
else
result = static_cast<uint64_t>(operand);
WASM_RETURN(result);
}
WASM_SLOW_PATH_DECL(i64_trunc_sat_f32_s)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncSatF32S>();
float operand = READ(instruction.m_operand).unboxedFloat();
int64_t result;
if (std::isnan(operand))
result = 0;
else if (operand < static_cast<float>(INT64_MIN))
result = INT64_MIN;
else if (operand >= -static_cast<float>(INT64_MIN))
result = INT64_MAX;
else
result = static_cast<int64_t>(operand);
WASM_RETURN(result);
}
WASM_SLOW_PATH_DECL(i64_trunc_sat_f64_u)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncSatF64U>();
double operand = READ(instruction.m_operand).unboxedDouble();
uint64_t result;
if (std::isnan(operand) || operand <= -1.0)
result = 0;
else if (operand >= -2.0 * static_cast<double>(INT64_MIN))
result = UINT64_MAX;
else
result = static_cast<uint64_t>(operand);
WASM_RETURN(result);
}
WASM_SLOW_PATH_DECL(i64_trunc_sat_f64_s)
{
UNUSED_PARAM(instance);
auto instruction = pc->as<WasmI64TruncSatF64S>();
double operand = READ(instruction.m_operand).unboxedDouble();
int64_t result;
if (std::isnan(operand))
result = 0;
else if (operand < static_cast<double>(INT64_MIN))
result = INT64_MIN;
else if (operand >= -static_cast<double>(INT64_MIN))
result = INT64_MAX;
else
result = static_cast<int64_t>(operand);
WASM_RETURN(result);
}
#endif
extern "C" UGPRPair slow_path_wasm_throw_exception(CallFrame* callFrame, const WasmInstruction* pc, Wasm::Instance* instance, Wasm::ExceptionType exceptionType)
{
UNUSED_PARAM(pc);
SlowPathFrameTracer tracer(instance->vm(), callFrame);
WASM_RETURN_TWO(Wasm::throwWasmToJSException(callFrame, exceptionType, instance), nullptr);
}
extern "C" UGPRPair slow_path_wasm_popcount(const WasmInstruction* pc, uint32_t x)
{
void* result = bitwise_cast<void*>(static_cast<size_t>(std::popcount(x)));
WASM_RETURN_TWO(pc, result);
}
extern "C" UGPRPair slow_path_wasm_popcountll(const WasmInstruction* pc, uint64_t x)
{
void* result = bitwise_cast<void*>(static_cast<size_t>(std::popcount(x)));
WASM_RETURN_TWO(pc, result);
}
} } // namespace JSC::LLInt
#endif // ENABLE(WEBASSEMBLY)