blob: 52e47dd9ab0fd5ecf35e44e691fec5f50bf3ca32 [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 "WasmCallee.h"
#include "WasmCallingConvention.h"
#include "WasmFunctionCodeBlockGenerator.h"
#include "WasmIPIntGenerator.h"
#include "WasmLLIntBuiltin.h"
#include "WasmLLIntGenerator.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) \
static_cast<Wasm::IPIntCallee*>(callFrame->callee().asNativeCallee())
// 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 { Call, 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());
auto getReplacement = [&] () -> RefPtr<Wasm::JITCallee> {
Locker locker { calleeGroup.m_lock };
if (osrFor == OSRFor::Call)
return calleeGroup.replacement(locker, callee->index());
return calleeGroup.tryGetBBQCalleeForLoopOSR(locker, instance->vm(), callee->functionIndex());
};
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;
}
}
if (compile) {
Wasm::FunctionCodeIndex functionIndex = callee->functionIndex();
if (Wasm::BBQPlan::ensureGlobalBBQAllowlist().containsWasmFunction(functionIndex)) {
auto plan = Wasm::BBQPlan::create(instance->vm(), const_cast<Wasm::ModuleInformation&>(instance->module().moduleInformation()), functionIndex, callee->hasExceptionHandlers(), Ref(*instance->calleeGroup()), Wasm::Plan::dontFinalize());
Wasm::ensureWorklist().enqueue(plan.get());
if (!Options::useConcurrentJIT() || !Options::useWasmIPInt()) [[unlikely]]
plan->waitForCompletion();
else
tierUpCounter.optimizeAfterWarmUp();
}
}
return getReplacement();
}
static inline Expected<RefPtr<Wasm::JITCallee>, Wasm::Plan::Error> jitCompileSIMDFunction(Wasm::IPIntCallee* callee, JSWebAssemblyInstance* instance)
{
Wasm::IPIntTierUpCounter& tierUpCounter = callee->tierUpCounter();
MemoryMode memoryMode = instance->memory()->mode();
Wasm::CalleeGroup& calleeGroup = *instance->calleeGroup();
{
Locker locker { calleeGroup.m_lock };
if (RefPtr replacement = calleeGroup.replacement(locker, callee->index())) {
dataLogLnIf(Options::verboseOSR(), "\tSIMD code was already compiled.");
return replacement;
}
}
bool compile = false;
while (!compile) {
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:
Thread::yield();
continue;
case Wasm::IPIntTierUpCounter::CompilationStatus::Compiled: {
// We can't hold a tierUpCounter lock while holding the calleeGroup lock since calleeGroup could reset our counter while releasing BBQ code.
// Besides we're outside the critical section.
locker.unlockEarly();
{
Locker locker { calleeGroup.m_lock };
RefPtr replacement = calleeGroup.replacement(locker, callee->index());
RELEASE_ASSERT(replacement);
return replacement;
}
}
}
}
Wasm::FunctionCodeIndex functionIndex = callee->functionIndex();
ASSERT(instance->module().moduleInformation().usesSIMD(functionIndex));
auto plan = Wasm::BBQPlan::create(instance->vm(), const_cast<Wasm::ModuleInformation&>(instance->module().moduleInformation()), functionIndex, callee->hasExceptionHandlers(), Ref(*instance->calleeGroup()), Wasm::Plan::dontFinalize());
Wasm::ensureWorklist().enqueue(plan.get());
plan->waitForCompletion();
if (plan->failed())
return makeUnexpected(plan->error());
{
Locker locker { tierUpCounter.m_lock };
RELEASE_ASSERT(tierUpCounter.compilationStatus(memoryMode) == Wasm::IPIntTierUpCounter::CompilationStatus::Compiled);
}
Locker locker { calleeGroup.m_lock };
RefPtr replacement = calleeGroup.replacement(locker, callee->index());
RELEASE_ASSERT(replacement);
return replacement;
}
WASM_IPINT_EXTERN_CPP_DECL(simd_go_straight_to_bbq, CallFrame* cfr)
{
auto* callee = IPINT_CALLEE(cfr);
if (!Options::useWasmSIMD())
RELEASE_ASSERT_NOT_REACHED();
RELEASE_ASSERT(shouldJIT(callee));
dataLogLnIf(Options::verboseOSR(), *callee, ": Entered simd_go_straight_to_bbq_osr with tierUpCounter = ", callee->tierUpCounter());
auto result = jitCompileSIMDFunction(callee, instance);
if (result.has_value()) [[likely]]
WASM_RETURN_TWO(result.value()->entrypoint().taggedPtr(), nullptr);
switch (result.error()) {
case Wasm::Plan::Error::OutOfMemory:
IPINT_THROW(Wasm::ExceptionType::OutOfMemory);
default:
break;
}
RELEASE_ASSERT_NOT_REACHED();
}
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::Call))
WASM_RETURN_TWO(replacement->entrypoint().taggedPtr(), nullptr);
WASM_RETURN_TWO(nullptr, nullptr);
}
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 = static_cast<Wasm::BBQCallee*>(compiledCallee.get());
ASSERT(bbqCallee->compilationMode() == Wasm::CompilationMode::BBQMode);
size_t osrEntryScratchBufferSize = bbqCallee->osrEntryScratchBufferSize();
RELEASE_ASSERT(osrEntryScratchBufferSize >= callee->numLocals() + osrEntryData.numberOfStackValues + osrEntryData.tryDepth);
uint64_t* buffer = instance->vm().wasmContext.scratchBufferForSize(osrEntryScratchBufferSize);
if (!buffer)
WASM_RETURN_TWO(nullptr, nullptr);
uint32_t index = 0;
buffer[index++] = osrEntryData.loopIndex;
for (uint32_t i = 0; i < callee->numLocals(); ++i)
buffer[index++] = pl[i].i64;
// If there's no rethrow slots just 0 fill the buffer.
ASSERT(osrEntryData.tryDepth <= callee->rethrowSlots() || !callee->rethrowSlots());
for (uint32_t i = 0; i < osrEntryData.tryDepth; ++i)
buffer[index++] = callee->rethrowSlots() ? pl[callee->localSizeToAlloc() + i].i64 : 0;
for (uint32_t i = 0; i < osrEntryData.numberOfStackValues; ++i) {
pl -= 1;
buffer[index++] = pl->i64;
}
auto sharedLoopEntrypoint = bbqCallee->sharedLoopEntrypoint();
RELEASE_ASSERT(sharedLoopEntrypoint);
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::Call);
WASM_RETURN_TWO(nullptr, nullptr);
}
#endif
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());
ASSERT(wasmException->payload().size() == wasmException->tag().parameterCount());
uint64_t size = wasmException->payload().size();
for (unsigned i = 0; i < size; ++i)
stackPointer[size - 1 - i].i64 = wasmException->payload()[i];
}
// 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().parameterCount());
uint64_t size = wasmException->payload().size();
stackPointer[0].ref = JSValue::encode(exception->value());
// We only have a stack pointer if we're doing a catch_ref not a catch_all_ref
for (unsigned i = 0; i < size; ++i)
stackPointer[size - i].i64 = wasmException->payload()[i];
// 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());
for (unsigned i = 0; i < tag->parameterBufferSize(); ++i)
values[tag->parameterBufferSize() - 1 - i] = arguments[i].i64;
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);
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);
JSValue thrownValue = exception;
if (&exception->tag() == &Wasm::Tag::jsExceptionTag())
thrownValue = JSValue::decode(exception->payload().at(0));
throwException(globalObject, throwScope, thrownValue);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
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);
JSValue thrownValue = exception;
if (&exception->tag() == &Wasm::Tag::jsExceptionTag())
thrownValue = JSValue::decode(exception->payload().at(0));
throwException(globalObject, throwScope, thrownValue);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
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_1P(current_memory)
{
size_t size = instance->memory()->memory().handle().size() >> 16;
WASM_RETURN_TWO(std::bit_cast<void*>(size), 0);
}
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, IPIntStackEntry* sp)
{
int32_t n = sp[0].i32;
int32_t s = sp[1].i32;
int32_t d = sp[2].i32;
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, int32_t dst, int32_t src, int32_t count)
{
if (!Wasm::memoryCopy(instance, dst, src, count))
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(memory_fill, int32_t dst, int32_t targetValue, int32_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, Wasm::TypeIndex typeIndex, IPIntStackEntry* sp)
{
const Wasm::StructType& structTypeDefinition = *instance->module().moduleInformation().typeSignatures[typeIndex]->expand().as<Wasm::StructType>();
Vector<uint64_t, 8> arguments(structTypeDefinition.fieldCount());
for (unsigned i = 0; i < structTypeDefinition.fieldCount(); ++i)
arguments[i] = sp[i].i64;
EncodedJSValue result = Wasm::structNew(instance, typeIndex, false, arguments.mutableSpan().data());
if (JSValue::decode(result).isNull())
IPINT_THROW(Wasm::ExceptionType::BadStructNew);
IPINT_RETURN(result);
}
WASM_IPINT_EXTERN_CPP_DECL(struct_new_default, Wasm::TypeIndex typeIndex)
{
EncodedJSValue result = Wasm::structNew(instance, typeIndex, true, nullptr);
if (JSValue::decode(result).isNull())
IPINT_THROW(Wasm::ExceptionType::BadStructNew);
IPINT_RETURN(result);
}
WASM_IPINT_EXTERN_CPP_DECL(struct_get, EncodedJSValue object, uint32_t fieldIndex)
{
UNUSED_PARAM(instance);
if (JSValue::decode(object).isNull())
IPINT_THROW(Wasm::ExceptionType::NullStructGet);
IPINT_RETURN(Wasm::structGet(object, fieldIndex));
}
WASM_IPINT_EXTERN_CPP_DECL(struct_get_s, EncodedJSValue object, uint32_t fieldIndex)
{
UNUSED_PARAM(instance);
if (JSValue::decode(object).isNull())
IPINT_THROW(Wasm::ExceptionType::NullStructGet);
EncodedJSValue value = Wasm::structGet(object, fieldIndex);
// 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 result = static_cast<int32_t>(value);
result = result << bitShift;
IPINT_RETURN(static_cast<EncodedJSValue>(result >> bitShift));
}
WASM_IPINT_EXTERN_CPP_DECL(struct_set, EncodedJSValue object, uint32_t fieldIndex, IPIntStackEntry* sp)
{
UNUSED_PARAM(instance);
if (JSValue::decode(object).isNull())
IPINT_THROW(Wasm::ExceptionType::NullStructSet);
Wasm::structSet(object, fieldIndex, sp->i64);
IPINT_END();
}
WASM_IPINT_EXTERN_CPP_DECL(array_new, Wasm::TypeIndex type, EncodedJSValue defaultValue, uint32_t size)
{
UNUSED_PARAM(instance);
JSValue result = Wasm::arrayNew(instance, type, size, defaultValue);
if (result.isNull())
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(array_new_default, Wasm::TypeIndex type, uint32_t size)
{
UNUSED_PARAM(instance);
const Wasm::TypeDefinition& arraySignature = instance->module().moduleInformation().typeSignatures[type]->expand();
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, type, size, vectorAllZeros());
if (result.isNull()) [[unlikely]]
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
JSValue result = Wasm::arrayNew(instance, type, size, defaultValue);
if (result.isNull())
IPINT_THROW(Wasm::ExceptionType::BadArrayNew);
IPINT_RETURN(JSValue::encode(result));
}
WASM_IPINT_EXTERN_CPP_DECL(array_new_fixed, Wasm::TypeIndex type, uint32_t size, IPIntStackEntry* sp)
{
Vector<uint64_t> arguments(size);
for (unsigned i = 0; i < size; ++i)
arguments[i] = sp[i].i64;
JSValue result = Wasm::arrayNewFixed(instance, type, size, arguments.mutableSpan().data());
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, static_cast<uint32_t>(metadata->typeIndex), 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, static_cast<uint32_t>(metadata->typeIndex), 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, Wasm::TypeIndex typeIndex, EncodedJSValue array, uint32_t index)
{
UNUSED_PARAM(instance);
if (JSValue::decode(array).isNull())
IPINT_THROW(Wasm::ExceptionType::NullArrayGet);
JSValue arrayValue = JSValue::decode(array);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
if (index >= arrayObject->size())
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayGet);
IPINT_RETURN(Wasm::arrayGet(instance, typeIndex, array, index));
}
WASM_IPINT_EXTERN_CPP_DECL(array_get_s, Wasm::TypeIndex typeIndex, EncodedJSValue array, uint32_t index)
{
UNUSED_PARAM(instance);
if (JSValue::decode(array).isNull())
IPINT_THROW(Wasm::ExceptionType::NullArrayGet);
JSValue arrayValue = JSValue::decode(array);
ASSERT(arrayValue.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayValue.getObject());
if (index >= arrayObject->size())
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArrayGet);
EncodedJSValue value = Wasm::arrayGet(instance, typeIndex, array, index);
// sign extension
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;
IPINT_RETURN(static_cast<EncodedJSValue>(result >> bitShift));
}
WASM_IPINT_EXTERN_CPP_DECL(array_set, Wasm::TypeIndex typeIndex, IPIntStackEntry* sp)
{
// sp[0] = value
// sp[1] = index
// sp[2] = array ref
if (JSValue::decode(sp[2].ref).isNull())
IPINT_THROW(Wasm::ExceptionType::NullArraySet);
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())
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsArraySet);
Wasm::arraySet(instance, typeIndex, sp[2].ref, index, sp[0].i64);
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;
if (JSValue::decode(arrayref).isNull())
IPINT_THROW(Wasm::ExceptionType::NullArrayFill);
uint32_t offset = sp[2].i32;
EncodedJSValue value = sp[1].ref;
uint32_t size = sp[0].i32;
if (!Wasm::arrayFill(instance->vm(), arrayref, offset, value, size))
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())
IPINT_THROW(Wasm::ExceptionType::NullArrayCopy);
if (!Wasm::arrayCopy(instance, dst, dstOffset, src, srcOffset, size))
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())
IPINT_THROW(Wasm::ExceptionType::NullArrayInitData);
if (!Wasm::arrayInitData(instance, dst, dstOffset, dataIndex, srcOffset, size))
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())
IPINT_THROW(Wasm::ExceptionType::NullArrayInitElem);
if (!Wasm::arrayInitElem(instance, dst, dstOffset, elemIndex, srcOffset, size))
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)
{
UNUSED_PARAM(instance);
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();
bool result = Wasm::refCast(value, allowNull, typeIndex);
IPINT_RETURN(static_cast<uint64_t>(result));
}
WASM_IPINT_EXTERN_CPP_DECL(ref_cast, int32_t heapType, bool allowNull, EncodedJSValue value)
{
UNUSED_PARAM(instance);
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 (!Wasm::refCast(value, allowNull, typeIndex))
IPINT_THROW(Wasm::ExceptionType::CastFailure);
IPINT_RETURN(value);
}
/**
* 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.
*/
static inline UGPRPair resolveWasmCall(JSWebAssemblyInstance* instance, Wasm::FunctionSpaceIndex functionIndex, Register* calleeAndWasmInstanceReturn)
{
uint32_t importFunctionCount = instance->module().moduleInformation().importFunctionCount();
Register& calleeReturn = calleeAndWasmInstanceReturn[0];
Register& wasmInstanceReturn = calleeAndWasmInstanceReturn[1];
CodePtr<WasmEntryPtrTag> codePtr;
if (functionIndex < importFunctionCount) {
auto* functionInfo = instance->importFunctionInfo(functionIndex);
codePtr = functionInfo->importFunctionStub;
calleeReturn = *std::bit_cast<uintptr_t*>(functionInfo->boxedWasmCalleeLoadLocation);
if (!functionInfo->targetInstance)
// The imported function is a JS function
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;
}
RELEASE_ASSERT(WTF::isTaggedWith<WasmEntryPtrTag>(codePtr));
WASM_CALL_RETURN(instance, codePtr);
}
WASM_IPINT_EXTERN_CPP_DECL(prepare_call, unsigned functionIndex, Register* callee)
{
return resolveWasmCall(instance, Wasm::FunctionSpaceIndex(functionIndex), callee);
}
WASM_IPINT_EXTERN_CPP_DECL(prepare_call_indirect, CallFrame* callFrame, Wasm::FunctionSpaceIndex* functionIndex, CallIndirectMetadata* call)
{
unsigned tableIndex = call->tableIndex;
unsigned typeIndex = call->typeIndex;
Wasm::FuncRefTable* table = instance->table(tableIndex)->asFuncrefTable();
if (*functionIndex >= table->length())
IPINT_THROW(Wasm::ExceptionType::OutOfBoundsCallIndirect);
const Wasm::FuncRefTable::Function& function = table->function(*functionIndex);
if (function.m_function.typeIndex == Wasm::TypeDefinition::invalidIndex)
IPINT_THROW(Wasm::ExceptionType::NullTableEntry);
const auto& callSignature = static_cast<Wasm::IPIntCallee*>(callFrame->callee().asNativeCallee())->signature(typeIndex);
if (!Wasm::isSubtypeIndex(function.m_function.typeIndex, callSignature.index()))
IPINT_THROW(Wasm::ExceptionType::BadSignature);
Register* calleeReturn = std::bit_cast<Register*>(functionIndex);
if (function.m_function.boxedWasmCalleeLoadLocation)
*calleeReturn = function.m_function.boxedWasmCalleeLoadLocation->encodedBits();
else {
auto callee = function.m_instance->calleeGroup()->wasmCalleeFromFunctionIndexSpace(*functionIndex);
*calleeReturn = CalleeBits::encodeNativeCallee(callee.get());
}
Register& functionInfoSlot = calleeReturn[1];
if (!function.m_function.targetInstance)
functionInfoSlot = reinterpret_cast<uintptr_t>(function.m_callLinkInfo);
else
functionInfoSlot = function.m_instance;
auto callTarget = *function.m_function.entrypointLoadLocation;
WASM_CALL_RETURN(function.m_instance, callTarget);
}
WASM_IPINT_EXTERN_CPP_DECL(prepare_call_ref, CallFrame* callFrame, Wasm::TypeIndex typeIndex, IPIntStackEntry* sp)
{
UNUSED_PARAM(callFrame);
UNUSED_PARAM(instance);
JSValue targetReference = JSValue::decode(sp->ref);
if (targetReference.isNull())
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();
ASSERT(function.boxedWasmCalleeLoadLocation);
if (function.boxedWasmCalleeLoadLocation)
sp->ref = function.boxedWasmCalleeLoadLocation->encodedBits();
else
sp->ref = CalleeBits::nullCallee().encodedBits();
Register& functionInfoSlot = std::bit_cast<Register*>(sp)[1];
if (!function.targetInstance)
functionInfoSlot = reinterpret_cast<uintptr_t>(wasmFunction->callLinkInfo());
else
functionInfoSlot = function.targetInstance.get();
ASSERT(Wasm::isSubtypeIndex(function.typeIndex, static_cast<Wasm::IPIntCallee*>(callFrame->callee().asNativeCallee())->signature(typeIndex).index()));
UNUSED_PARAM(typeIndex);
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));
}
} } // namespace JSC::IPInt
#endif
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END