blob: 1426464c6cc02a4231199d5366ca0f82bd737f9a [file] [log] [blame]
/*
* Copyright (C) 2023 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.
*/
#pragma once
#include "WasmOperations.h"
#if ENABLE(WEBASSEMBLY)
#include "JITExceptions.h"
#include "JSWebAssemblyArrayInlines.h"
#include "JSWebAssemblyHelpers.h"
#include "JSWebAssemblyInstance.h"
#include "JSWebAssemblyStruct.h"
#include "TypedArrayController.h"
#include "WaiterListManager.h"
#include "WasmIPIntGenerator.h"
#include "WasmModuleInformation.h"
#include "WasmTypeDefinition.h"
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
namespace JSC {
namespace Wasm {
inline EncodedJSValue refFunc(JSWebAssemblyInstance* instance, uint32_t index)
{
JSValue value = instance->getFunctionWrapper(index);
ASSERT(value.isCallable());
return JSValue::encode(value);
}
template <typename T>
JSWebAssemblyArray* tryFillArray(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, uint32_t size, T value)
{
VM& vm = instance->vm();
auto* array = JSWebAssemblyArray::tryCreate(vm, structure, size);
if (array) [[likely]]
array->fill(vm, 0, static_cast<T>(value), size);
return array;
}
inline JSValue arrayNew(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, uint32_t size, EncodedJSValue encValue)
{
const Wasm::TypeDefinition& arraySignature = structure->typeDefinition();
ASSERT(arraySignature.is<ArrayType>());
Wasm::FieldType fieldType = arraySignature.as<ArrayType>()->elementType();
size_t elementSize = fieldType.type.elementSize();
JSWebAssemblyArray* array = nullptr;
switch (elementSize) {
case sizeof(uint8_t): {
array = tryFillArray<uint8_t>(instance, structure, size, static_cast<uint8_t>(encValue));
break;
}
case sizeof(uint16_t): {
array = tryFillArray<uint16_t>(instance, structure, size, static_cast<uint16_t>(encValue));
break;
}
case sizeof(uint32_t): {
array = tryFillArray<uint32_t>(instance, structure, size, static_cast<uint32_t>(encValue));
break;
}
case sizeof(uint64_t): {
array = tryFillArray<uint64_t>(instance, structure, size, encValue);
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
if (!array) [[unlikely]]
return jsNull();
return array;
}
inline JSValue arrayNew(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, uint32_t size, v128_t value)
{
VM& vm = instance->vm();
const Wasm::TypeDefinition& arraySignature = structure->typeDefinition();
ASSERT(arraySignature.is<ArrayType>());
Wasm::FieldType fieldType = arraySignature.as<ArrayType>()->elementType();
ASSERT_UNUSED(fieldType, fieldType.type.unpacked().isV128());
auto* array = JSWebAssemblyArray::tryCreate(vm, structure, size);
if (!array) [[unlikely]]
return jsNull();
array->fill(vm, 0, value, size);
return array;
}
template <typename T>
JSWebAssemblyArray* tryCopyElementsInReverse(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, uint32_t size, IPInt::IPIntStackEntry* arguments)
{
VM& vm = instance->vm();
auto* array = JSWebAssemblyArray::tryCreate(vm, structure, size);
if (!array) [[unlikely]]
return array;
if (!size)
return array;
ASSERT(arguments);
auto span = array->span<T>();
for (int srcIndex = size - 1; srcIndex >= 0; srcIndex--) {
unsigned dstIndex = size - srcIndex - 1;
if constexpr (std::is_same_v<T, v128_t>)
span[dstIndex] = arguments[srcIndex].v128;
else
span[dstIndex] = static_cast<T>(arguments[srcIndex].i64);
}
if (array->elementsAreRefTypes())
vm.writeBarrier(array);
return array;
}
// Expects arguments in reverse order
inline JSValue arrayNewFixed(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, uint32_t size, IPInt::IPIntStackEntry* arguments)
{
// Get the array element type and determine the element size
const Wasm::TypeDefinition& arraySignature = structure->typeDefinition();
ASSERT(arraySignature.is<ArrayType>());
Wasm::FieldType fieldType = arraySignature.as<ArrayType>()->elementType();
size_t elementSize = fieldType.type.elementSize();
// Copy the elements into the result array in reverse order
JSWebAssemblyArray* array = nullptr;
switch (elementSize) {
case sizeof(uint8_t): {
array = tryCopyElementsInReverse<uint8_t>(instance, structure, size, arguments);
break;
}
case sizeof(uint16_t): {
array = tryCopyElementsInReverse<uint16_t>(instance, structure, size, arguments);
break;
}
case sizeof(uint32_t): {
array = tryCopyElementsInReverse<uint32_t>(instance, structure, size, arguments);
break;
}
case sizeof(uint64_t): {
array = tryCopyElementsInReverse<uint64_t>(instance, structure, size, arguments);
break;
}
case sizeof(v128_t): {
array = tryCopyElementsInReverse<v128_t>(instance, structure, size, arguments);
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
if (!array) [[unlikely]]
return jsNull();
return array;
}
template<typename T>
EncodedJSValue createArrayFromDataSegment(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, size_t arraySize, unsigned dataSegmentIndex, unsigned offset)
{
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
auto* array = JSWebAssemblyArray::tryCreate(vm, structure, arraySize);
if (!array) [[unlikely]]
return JSValue::encode(jsNull());
ASSERT(!array->elementsAreRefTypes());
auto span = array->span<T>();
// Copy the data from the segment into the temp `values` vector
if (!instance->copyDataSegment(array, dataSegmentIndex, offset, span.size_bytes(), reinterpret_cast<uint8_t*>(span.data()))) {
// If copyDataSegment() returns false, the segment access is out of bounds.
// In that case, the caller is responsible for throwing an exception.
return JSValue::encode(jsNull());
}
return JSValue::encode(array);
}
inline EncodedJSValue arrayNewData(JSWebAssemblyInstance* instance, uint32_t typeIndex, uint32_t dataSegmentIndex, uint32_t arraySize, uint32_t offset)
{
// Check that the type index is within bounds
ASSERT(typeIndex < instance->module().moduleInformation().typeCount());
WebAssemblyGCStructure* structure = instance->gcObjectStructure(typeIndex);
const Wasm::TypeDefinition& arraySignature = structure->typeDefinition();
ASSERT(arraySignature.is<ArrayType>());
// Get the array element type
Wasm::FieldType fieldType = arraySignature.as<ArrayType>()->elementType();
// Determine the array length in bytes from the element type and desired array size
size_t elementSize = fieldType.type.elementSize();
// Check for overflow when determining array length in bytes
if (productOverflows<uint32_t>(elementSize, arraySize)) [[unlikely]]
return JSValue::encode(jsNull());
uint32_t arrayLengthInBytes = arraySize * elementSize;
// Check for offset + arrayLengthInBytes overflow
if (sumOverflows<uint32_t>(offset, arrayLengthInBytes)) [[unlikely]]
return JSValue::encode(jsNull());
// Finally, allocate the array from the `values` vector
if (fieldType.type.is<PackedType>()) {
switch (fieldType.type.as<PackedType>()) {
case PackedType::I8: {
return createArrayFromDataSegment<uint8_t>(instance, structure, arraySize, dataSegmentIndex, offset);
}
case PackedType::I16: {
return createArrayFromDataSegment<uint16_t>(instance, structure, arraySize, dataSegmentIndex, offset);
}
default:
break;
}
} else {
switch (fieldType.type.as<Type>().kind) {
case Wasm::TypeKind::I32:
case Wasm::TypeKind::F32: {
return createArrayFromDataSegment<uint32_t>(instance, structure, arraySize, dataSegmentIndex, offset);
}
case Wasm::TypeKind::I64:
case Wasm::TypeKind::F64: {
return createArrayFromDataSegment<uint64_t>(instance, structure, arraySize, dataSegmentIndex, offset);
}
case Wasm::TypeKind::V128: {
return createArrayFromDataSegment<v128_t>(instance, structure, arraySize, dataSegmentIndex, offset);
}
default:
break;
}
}
RELEASE_ASSERT_NOT_REACHED();
}
inline EncodedJSValue arrayNewElem(JSWebAssemblyInstance* instance, uint32_t typeIndex, uint32_t elemSegmentIndex, uint32_t arraySize, uint32_t offset)
{
// Check that the type index is within bounds
ASSERT(typeIndex < instance->module().moduleInformation().typeCount());
WebAssemblyGCStructure* structure = instance->gcObjectStructure(typeIndex);
ASSERT(structure->typeDefinition().is<ArrayType>());
// Ensure that adding the offset to the desired array length doesn't overflow int32 or
// overflow the length of the element segment
auto element = instance->elementAt(elemSegmentIndex);
size_t segmentLength = element ? element->length() : 0U;
auto calculatedArrayEnd = CheckedUint32 { offset } + arraySize;
if (calculatedArrayEnd.hasOverflowed() || calculatedArrayEnd > segmentLength) [[unlikely]]
return JSValue::encode(jsNull());
VM& vm = instance->vm();
StorageType arrayType = structure->typeDefinition().as<ArrayType>()->elementType().type;
ASSERT_UNUSED(arrayType, !arraySize || isSubtype(StorageType(element->elementType), arrayType));
auto* array = JSWebAssemblyArray::tryCreate(vm, structure, arraySize);
if (!array) [[unlikely]]
return JSValue::encode(jsNull());
#if ASSERT_ENABLED
array->setIsUnpopulated(true);
WTF::storeLoadFence();
#endif
instance->copyElementSegment(array, instance->module().moduleInformation().elements[elemSegmentIndex], offset, arraySize, array->span<uint64_t>().data());
#if ASSERT_ENABLED
WTF::storeStoreFence();
array->setIsUnpopulated(false);
#endif
ASSERT(!arraySize || Wasm::isRefType(element->elementType));
vm.writeBarrier(array);
return JSValue::encode(array);
}
inline void arrayGet(JSWebAssemblyInstance* instance, uint32_t typeIndex, EncodedJSValue arrayValue, uint32_t index, IPInt::IPIntStackEntry* result)
{
ASSERT_UNUSED(instance, typeIndex < instance->module().moduleInformation().typeCount());
const Wasm::TypeDefinition& arraySignature = instance->module().moduleInformation().typeSignatures[typeIndex]->expand();
ASSERT_UNUSED(arraySignature, arraySignature.is<ArrayType>());
JSValue arrayRef = JSValue::decode(arrayValue);
ASSERT(arrayRef.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayRef.getObject());
if (arrayObject->elementType().type.unpacked().isV128())
result->v128 = arrayObject->getVector(index);
else
result->i64 = arrayObject->get(index);
}
inline void arraySet(JSWebAssemblyInstance* instance, uint32_t typeIndex, EncodedJSValue arrayValue, uint32_t index, IPInt::IPIntStackEntry* value)
{
ASSERT_UNUSED(instance, typeIndex < instance->module().moduleInformation().typeCount());
const Wasm::TypeDefinition& arraySignature = instance->module().moduleInformation().typeSignatures[typeIndex]->expand();
ASSERT(arraySignature.is<ArrayType>());
JSValue arrayRef = JSValue::decode(arrayValue);
ASSERT(arrayRef.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayRef.getObject());
Wasm::FieldType elementType = arraySignature.as<ArrayType>()->elementType();
if (elementType.type.unpacked().isV128())
arrayObject->set(instance->vm(), index, value->v128);
else
arrayObject->set(instance->vm(), index, value->i64);
}
inline bool doArrayFill(VM& vm, EncodedJSValue arrayValue, uint32_t offset, Variant<uint64_t, v128_t> value, uint32_t size)
{
JSValue arrayRef = JSValue::decode(arrayValue);
ASSERT(arrayRef.isObject());
JSWebAssemblyArray* arrayObject = jsCast<JSWebAssemblyArray*>(arrayRef.getObject());
CheckedUint32 lastElementIndexChecked = offset;
lastElementIndexChecked += size;
if (lastElementIndexChecked.hasOverflowed())
return false;
if (lastElementIndexChecked > arrayObject->size())
return false;
if (std::holds_alternative<uint64_t>(value))
arrayObject->fill(vm, offset, std::get<uint64_t>(value), size);
else {
ASSERT(std::holds_alternative<v128_t>(value));
arrayObject->fill(vm, offset, std::get<v128_t>(value), size);
}
return true;
}
inline bool arrayFill(VM& vm, EncodedJSValue arrayValue, uint32_t offset, uint64_t value, uint32_t size)
{
return doArrayFill(vm, arrayValue, offset, value, size);
}
inline bool arrayFill(VM& vm, EncodedJSValue arrayValue, uint32_t offset, v128_t value, uint32_t size)
{
return doArrayFill(vm, arrayValue, offset, value, size);
}
// FIXME:
// To be consistent it would make sense to include dst and src type indices, but they
// are not necessary for operation and hits a limitation of BBQ JIT calls.
inline bool arrayCopy(JSWebAssemblyInstance* instance, EncodedJSValue dst, uint32_t dstOffset, EncodedJSValue src, uint32_t srcOffset, uint32_t size)
{
JSValue dstRef = JSValue::decode(dst);
JSValue srcRef = JSValue::decode(src);
ASSERT(dstRef.isObject());
ASSERT(srcRef.isObject());
JSWebAssemblyArray* dstObject = jsCast<JSWebAssemblyArray*>(dstRef.getObject());
JSWebAssemblyArray* srcObject = jsCast<JSWebAssemblyArray*>(srcRef.getObject());
CheckedUint32 lastDstElementIndexChecked = dstOffset;
lastDstElementIndexChecked += size;
if (lastDstElementIndexChecked.hasOverflowed())
return false;
if (lastDstElementIndexChecked > dstObject->size())
return false;
CheckedUint32 lastSrcElementIndexChecked = srcOffset;
lastSrcElementIndexChecked += size;
if (lastSrcElementIndexChecked.hasOverflowed())
return false;
if (lastSrcElementIndexChecked > srcObject->size())
return false;
srcObject->copy(instance->vm(), *dstObject, dstOffset, srcOffset, size);
return true;
}
inline bool arrayInitElem(JSWebAssemblyInstance* instance, EncodedJSValue dst, uint32_t dstOffset, uint32_t srcElementIndex, uint32_t srcOffset, uint32_t size)
{
JSValue dstRef = JSValue::decode(dst);
ASSERT(dstRef.isObject());
JSWebAssemblyArray* dstObject = jsCast<JSWebAssemblyArray*>(dstRef.getObject());
CheckedUint32 lastDstElementIndexChecked = dstOffset;
lastDstElementIndexChecked += size;
if (lastDstElementIndexChecked.hasOverflowed())
return false;
if (lastDstElementIndexChecked > dstObject->size())
return false;
CheckedUint32 lastSrcElementIndexChecked = srcOffset;
lastSrcElementIndexChecked += size;
if (lastSrcElementIndexChecked.hasOverflowed())
return false;
const uint32_t lengthOfElementSegment = instance->elementAt(srcElementIndex) ? instance->elementAt(srcElementIndex)->length() : 0U;
if (lastSrcElementIndexChecked > lengthOfElementSegment)
return false;
auto* elementSegment = instance->elementAt(srcElementIndex);
if (elementSegment) {
instance->copyElementSegment(dstObject, *elementSegment, srcOffset, size, dstObject->refTypeSpan().data() + dstOffset);
instance->vm().writeBarrier(dstObject);
}
else
ASSERT(!lastSrcElementIndexChecked);
return true;
}
inline bool arrayInitData(JSWebAssemblyInstance* instance, EncodedJSValue dst, uint32_t dstOffset, uint32_t srcDataIndex, uint32_t srcOffset, uint32_t size)
{
JSValue dstRef = JSValue::decode(dst);
ASSERT(dstRef.isObject());
JSWebAssemblyArray* dstObject = jsCast<JSWebAssemblyArray*>(dstRef.getObject());
CheckedUint32 lastDstElementIndexChecked = dstOffset;
lastDstElementIndexChecked += size;
if (lastDstElementIndexChecked.hasOverflowed())
return false;
if (lastDstElementIndexChecked > dstObject->size())
return false;
size_t elementSize = dstObject->elementType().type.elementSize();
CheckedUint32 lastSrcByteChecked = size;
lastSrcByteChecked *= elementSize;
lastSrcByteChecked += srcOffset;
if (lastSrcByteChecked.hasOverflowed())
return false;
// Otherwise we need a writeBarrier.
ASSERT(!dstObject->elementsAreRefTypes());
return dstObject->visitSpan([&](auto span) ALWAYS_INLINE_LAMBDA -> bool {
return instance->copyDataSegment(dstObject, srcDataIndex, srcOffset, size * elementSize, reinterpret_cast<uint8_t*>(span.subspan(dstOffset).data()));
});
}
// structNew() expects the `arguments` array (when used) to be in reverse order
inline JSValue structNew(JSWebAssemblyInstance* instance, WebAssemblyGCStructure* structure, bool useDefault, IPInt::IPIntStackEntry* arguments)
{
JSGlobalObject* globalObject = instance->globalObject();
VM& vm = globalObject->vm();
ASSERT(structure->typeDefinition().is<StructType>());
const StructType& structType = *structure->typeDefinition().as<StructType>();
JSWebAssemblyStruct* structValue = JSWebAssemblyStruct::create(vm, structure);
if (static_cast<Wasm::UseDefaultValue>(useDefault) == Wasm::UseDefaultValue::Yes) {
for (unsigned i = 0; i < structType.fieldCount(); ++i) {
if (structType.field(i).type.unpacked().isV128()) {
structValue->set(i, vectorAllZeros());
continue;
}
EncodedJSValue value = 0;
if (Wasm::isRefType(structType.field(i).type))
value = JSValue::encode(jsNull());
structValue->set(i, value);
}
} else {
ASSERT(arguments);
for (unsigned dstIndex = 0; dstIndex < structType.fieldCount(); ++dstIndex) {
// Arguments are in reverse order!
unsigned srcIndex = structType.fieldCount() - dstIndex - 1;
if (structType.field(dstIndex).type.unpacked().isV128())
structValue->set(dstIndex, arguments[srcIndex].v128);
else
structValue->set(dstIndex, arguments[srcIndex].i64);
}
}
return structValue;
}
inline void structGet(EncodedJSValue encodedStructReference, uint32_t fieldIndex, IPInt::IPIntStackEntry* result)
{
auto structReference = JSValue::decode(encodedStructReference);
ASSERT(structReference.isObject());
JSObject* structureAsObject = jsCast<JSObject*>(structReference);
ASSERT(structureAsObject->inherits<JSWebAssemblyStruct>());
JSWebAssemblyStruct* structPointer = jsCast<JSWebAssemblyStruct*>(structureAsObject);
Wasm::FieldType field = structPointer->fieldType(fieldIndex);
if (field.type.unpacked().isV128())
result->v128 = structPointer->getVector(fieldIndex);
else
result->i64 = structPointer->get(fieldIndex);
}
inline void structSet(EncodedJSValue encodedStructReference, uint32_t fieldIndex, IPInt::IPIntStackEntry* argument)
{
auto structReference = JSValue::decode(encodedStructReference);
ASSERT(structReference.isObject());
JSObject* structureAsObject = jsCast<JSObject*>(structReference);
ASSERT(structureAsObject->inherits<JSWebAssemblyStruct>());
JSWebAssemblyStruct* structPointer = jsCast<JSWebAssemblyStruct*>(structureAsObject);
Wasm::FieldType field = structPointer->fieldType(fieldIndex);
if (field.type.unpacked().isV128())
structPointer->set(fieldIndex, argument->v128);
else
structPointer->set(fieldIndex, argument->i64);
}
inline bool refCast(EncodedJSValue encodedReference, bool allowNull, TypeIndex typeIndex, const RTT* rtt)
{
return TypeInformation::isReferenceValueAssignable(JSValue::decode(encodedReference), allowNull, typeIndex, rtt);
}
inline EncodedJSValue externInternalize(EncodedJSValue reference)
{
return JSValue::encode(Wasm::internalizeExternref(JSValue::decode(reference)));
}
inline EncodedJSValue tableGet(JSWebAssemblyInstance* instance, unsigned tableIndex, int32_t signedIndex)
{
ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
if (signedIndex < 0)
return 0;
uint32_t index = signedIndex;
if (index >= instance->table(tableIndex)->length())
return 0;
return JSValue::encode(instance->table(tableIndex)->get(index));
}
inline bool tableSet(JSWebAssemblyInstance* instance, unsigned tableIndex, uint32_t index, EncodedJSValue encValue)
{
ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
if (index >= instance->table(tableIndex)->length())
return false;
JSValue value = JSValue::decode(encValue);
if (value.isNull())
instance->table(tableIndex)->clear(index);
else
instance->table(tableIndex)->set(index, value);
return true;
}
inline bool tableInit(JSWebAssemblyInstance* instance, unsigned elementIndex, unsigned tableIndex, uint32_t dstOffset, uint32_t srcOffset, uint32_t length)
{
ASSERT(elementIndex < instance->module().moduleInformation().elementCount());
ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
if (WTF::sumOverflows<uint32_t>(srcOffset, length))
return false;
if (WTF::sumOverflows<uint32_t>(dstOffset, length))
return false;
if (dstOffset + length > instance->table(tableIndex)->length())
return false;
const uint32_t lengthOfElementSegment = instance->elementAt(elementIndex) ? instance->elementAt(elementIndex)->length() : 0U;
if (srcOffset + length > lengthOfElementSegment)
return false;
if (!lengthOfElementSegment)
return true;
instance->tableInit(dstOffset, srcOffset, length, elementIndex, tableIndex);
return true;
}
inline bool tableFill(JSWebAssemblyInstance* instance, unsigned tableIndex, uint32_t offset, EncodedJSValue fill, uint32_t count)
{
ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
if (WTF::sumOverflows<uint32_t>(offset, count))
return false;
if (offset + count > instance->table(tableIndex)->length())
return false;
for (uint32_t index = 0; index < count; ++index)
tableSet(instance, tableIndex, offset + index, fill);
return true;
}
inline size_t tableGrow(JSWebAssemblyInstance* instance, unsigned tableIndex, EncodedJSValue fill, uint32_t delta)
{
ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
auto oldSize = instance->table(tableIndex)->length();
auto newSize = instance->table(tableIndex)->grow(delta, jsNull());
if (!newSize)
return -1;
for (unsigned i = oldSize; i < instance->table(tableIndex)->length(); ++i)
tableSet(instance, tableIndex, i, fill);
return oldSize;
}
inline bool tableCopy(JSWebAssemblyInstance* instance, unsigned dstTableIndex, unsigned srcTableIndex, int32_t dstOffset, int32_t srcOffset, int32_t length)
{
ASSERT(dstTableIndex < instance->module().moduleInformation().tableCount());
ASSERT(srcTableIndex < instance->module().moduleInformation().tableCount());
const Table* dstTable = instance->table(dstTableIndex);
const Table* srcTable = instance->table(srcTableIndex);
ASSERT(dstTable->type() == srcTable->type());
if ((srcOffset < 0) || (dstOffset < 0) || (length < 0))
return false;
CheckedUint32 lastDstElementIndexChecked = static_cast<uint32_t>(dstOffset);
lastDstElementIndexChecked += static_cast<uint32_t>(length);
if (lastDstElementIndexChecked.hasOverflowed())
return false;
if (lastDstElementIndexChecked > dstTable->length())
return false;
CheckedUint32 lastSrcElementIndexChecked = static_cast<uint32_t>(srcOffset);
lastSrcElementIndexChecked += static_cast<uint32_t>(length);
if (lastSrcElementIndexChecked.hasOverflowed())
return false;
if (lastSrcElementIndexChecked > srcTable->length())
return false;
instance->tableCopy(dstOffset, srcOffset, length, dstTableIndex, srcTableIndex);
return true;
}
inline int32_t tableSize(JSWebAssemblyInstance* instance, unsigned tableIndex)
{
return instance->table(tableIndex)->length();
}
inline int32_t growMemory(JSWebAssemblyInstance* instance, int32_t delta)
{
if (delta < 0)
return -1;
auto grown = instance->memory()->memory().grow(instance->vm(), PageCount(delta));
if (!grown) {
switch (grown.error()) {
case GrowFailReason::InvalidDelta:
case GrowFailReason::InvalidGrowSize:
case GrowFailReason::WouldExceedMaximum:
case GrowFailReason::OutOfMemory:
case GrowFailReason::GrowSharedUnavailable:
return -1;
}
RELEASE_ASSERT_NOT_REACHED();
}
return grown.value().pageCount();
}
inline bool memoryInit(JSWebAssemblyInstance* instance, unsigned dataSegmentIndex, uint64_t dstAddress, uint32_t srcAddress, uint32_t length)
{
ASSERT(dataSegmentIndex < instance->module().moduleInformation().dataSegmentsCount());
return instance->memoryInit(dstAddress, srcAddress, length, dataSegmentIndex);
}
inline bool memoryFill(JSWebAssemblyInstance* instance, uint32_t dstAddress, uint32_t targetValue, uint32_t count)
{
auto* base = std::bit_cast<uint8_t*>(instance->cachedMemory());
uint64_t size = instance->cachedMemorySize();
uint64_t lastDstAddress = static_cast<uint64_t>(dstAddress) + count;
if (lastDstAddress > size)
return false;
memset(base + dstAddress, targetValue, count);
return true;
}
inline bool memoryCopy(JSWebAssemblyInstance* instance, uint32_t dstAddress, uint32_t srcAddress, uint32_t count)
{
auto* base = std::bit_cast<uint8_t*>(instance->cachedMemory());
uint64_t size = instance->cachedMemorySize();
uint64_t lastDstAddress = static_cast<uint64_t>(dstAddress) + count;
uint64_t lastSrcAddress = static_cast<uint64_t>(srcAddress) + count;
if (lastDstAddress > size || lastSrcAddress > size)
return false;
if (!count)
return true;
// Source and destination areas might overlap, so using memmove.
memmove(base + dstAddress, base + srcAddress, count);
return true;
}
inline void dataDrop(JSWebAssemblyInstance* instance, unsigned dataSegmentIndex)
{
ASSERT(dataSegmentIndex < instance->module().moduleInformation().dataSegmentsCount());
instance->dataDrop(dataSegmentIndex);
}
inline void elemDrop(JSWebAssemblyInstance* instance, unsigned elementIndex)
{
ASSERT(elementIndex < instance->module().moduleInformation().elementCount());
instance->elemDrop(elementIndex);
}
template<typename ValueType>
static inline int32_t waitImpl(VM& vm, ValueType* pointer, ValueType expectedValue, int64_t timeoutInNanoseconds)
{
Seconds timeout = Seconds::infinity();
if (timeoutInNanoseconds >= 0)
timeout = Seconds::fromNanoseconds(timeoutInNanoseconds);
auto result = WaiterListManager::singleton().waitSync(vm, pointer, expectedValue, timeout);
switch (result) {
case WaiterListManager::WaitSyncResult::OK:
case WaiterListManager::WaitSyncResult::NotEqual:
case WaiterListManager::WaitSyncResult::TimedOut:
return static_cast<int32_t>(result);
case WaiterListManager::WaitSyncResult::Terminated:
vm.throwTerminationException();
return -1;
}
RELEASE_ASSERT_NOT_REACHED();
return -1;
}
inline int32_t memoryAtomicWait32(JSWebAssemblyInstance* instance, uint64_t offsetInMemory, int32_t value, int64_t timeoutInNanoseconds)
{
VM& vm = instance->vm();
if (offsetInMemory & (0x4 - 1))
return -1;
if (!instance->memory())
return -1;
if (offsetInMemory >= instance->memory()->memory().size())
return -1;
if (instance->memory()->sharingMode() != MemorySharingMode::Shared)
return -1;
if (!vm.m_typedArrayController->isAtomicsWaitAllowedOnCurrentThread())
return -1;
int32_t* pointer = std::bit_cast<int32_t*>(std::bit_cast<uint8_t*>(instance->memory()->basePointer()) + offsetInMemory);
return waitImpl<int32_t>(vm, pointer, value, timeoutInNanoseconds);
}
inline int32_t memoryAtomicWait32(JSWebAssemblyInstance* instance, unsigned base, unsigned offset, int32_t value, int64_t timeoutInNanoseconds)
{
return memoryAtomicWait32(instance, static_cast<uint64_t>(base) + offset, value, timeoutInNanoseconds);
}
inline int32_t memoryAtomicWait64(JSWebAssemblyInstance* instance, uint64_t offsetInMemory, int64_t value, int64_t timeoutInNanoseconds)
{
VM& vm = instance->vm();
if (offsetInMemory & (0x8 - 1))
return -1;
if (!instance->memory())
return -1;
if (offsetInMemory >= instance->memory()->memory().size())
return -1;
if (instance->memory()->sharingMode() != MemorySharingMode::Shared)
return -1;
if (!vm.m_typedArrayController->isAtomicsWaitAllowedOnCurrentThread())
return -1;
int64_t* pointer = std::bit_cast<int64_t*>(std::bit_cast<uint8_t*>(instance->memory()->basePointer()) + offsetInMemory);
return waitImpl<int64_t>(vm, pointer, value, timeoutInNanoseconds);
}
inline int32_t memoryAtomicWait64(JSWebAssemblyInstance* instance, unsigned base, unsigned offset, int64_t value, int64_t timeoutInNanoseconds)
{
return memoryAtomicWait64(instance, static_cast<uint64_t>(base) + offset, value, timeoutInNanoseconds);
}
inline int32_t memoryAtomicNotify(JSWebAssemblyInstance* instance, unsigned base, unsigned offset, int32_t countValue)
{
uint64_t offsetInMemory = static_cast<uint64_t>(base) + offset;
if (offsetInMemory & (0x4 - 1))
return -1;
if (!instance->memory())
return -1;
if (offsetInMemory >= instance->memory()->memory().size())
return -1;
if (instance->memory()->sharingMode() != MemorySharingMode::Shared)
return 0;
uint8_t* pointer = std::bit_cast<uint8_t*>(instance->memory()->basePointer()) + offsetInMemory;
unsigned count = UINT_MAX;
if (countValue >= 0)
count = static_cast<unsigned>(countValue);
return static_cast<int32_t>(WaiterListManager::singleton().notifyWaiter(pointer, count));
}
inline void* throwWasmToJSException(CallFrame* callFrame, Wasm::ExceptionType type, JSWebAssemblyInstance* instance)
{
JSGlobalObject* globalObject = instance->globalObject();
// Do not retrieve VM& from CallFrame since CallFrame's callee is not a JSCell.
VM& vm = globalObject->vm();
do {
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSObject* error;
if (type == ExceptionType::Termination) {
// Nothing to do because the exception should have already been thrown.
RELEASE_ASSERT(vm.hasPendingTerminationException());
break;
}
if (type == ExceptionType::StackOverflow)
error = createStackOverflowError(globalObject);
else if (isTypeErrorExceptionType(type))
error = createTypeError(globalObject, Wasm::errorMessageForExceptionType(type));
else
error = createJSWebAssemblyRuntimeError(globalObject, vm, type);
throwException(globalObject, throwScope, error);
} while (false);
genericUnwind(vm, callFrame);
ASSERT(!!vm.callFrameForCatch);
ASSERT(!!vm.targetMachinePCForThrow);
return vm.targetMachinePCForThrow;
}
} } // namespace JSC::Wasm
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
#endif // ENABLE(WEBASSEMBLY)