Support SharedArrayBuffer in WASM Memory

Signed-off-by: HyukWoo Park <hyukwoo.park@jbnu.ac.kr>
This commit is contained in:
HyukWoo Park 2024-07-15 14:48:39 +09:00 committed by Patrick Kim
commit 1e11e1d668
18 changed files with 202 additions and 244 deletions

View file

@ -139,6 +139,10 @@ ENDIF()
IF (ESCARGOT_WASM)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_WASM)
IF (NOT DEFINED ESCARGOT_THREADING)
# threading should be enabled for WASM (WASM threading feature)
SET (ESCARGOT_THREADING ON)
ENDIF()
ENDIF()
IF (ESCARGOT_THREADING)

View file

@ -187,6 +187,7 @@ IF (ESCARGOT_WASM)
SET (WALRUS_MODE ${ESCARGOT_MODE})
SET (WALRUS_OUTPUT "shared_lib")
SET (WALRUS_WASI OFF) # WASI should be OFF
SET (WALRUS_EXTENDED_FEATURES ON) # enable extended features
IF (${ESCARGOT_MODE} STREQUAL "release")
SET (WALRUS_CXXFLAGS ${WALRUS_CXXFLAGS} ${ESCARGOT_CXXFLAGS_RELEASE})

View file

@ -129,9 +129,9 @@ public:
return m_platform->onMallocArrayBufferObjectDataBuffer(sizeInByte);
}
virtual void onFreeArrayBufferObjectDataBuffer(void* buffer, size_t sizeInByte) override
virtual void onFreeArrayBufferObjectDataBuffer(void* buffer, size_t sizeInByte, void* deleterData) override
{
m_platform->onFreeArrayBufferObjectDataBuffer(buffer, sizeInByte);
m_platform->onFreeArrayBufferObjectDataBuffer(buffer, sizeInByte, deleterData);
}
virtual void* onReallocArrayBufferObjectDataBuffer(void* oldBuffer, size_t oldSizeInByte, size_t newSizeInByte) override

View file

@ -2222,7 +2222,7 @@ public:
{
return calloc(sizeInByte, 1);
}
virtual void onFreeArrayBufferObjectDataBuffer(void* buffer, size_t sizeInByte)
virtual void onFreeArrayBufferObjectDataBuffer(void* buffer, size_t sizeInByte, void* deleterData)
{
return free(buffer);
}

View file

@ -61,6 +61,19 @@ ArrayBufferObject* ArrayBufferObject::allocateArrayBuffer(ExecutionState& state,
return obj;
}
ArrayBufferObject* ArrayBufferObject::allocateExternalArrayBuffer(ExecutionState& state, void* dataBlock, size_t byteLength)
{
if (UNLIKELY(byteLength >= ArrayBuffer::maxArrayBufferSize)) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().ArrayBuffer.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferSize);
}
// creating a fixed length memory buffer from memaddr.
// NOTE) deleter do nothing, dataBlock will be freed in external module
BackingStore* backingStore = BackingStore::createNonSharedBackingStore(dataBlock, byteLength, [](void* data, size_t length, void* deleterData) {}, nullptr);
return new ArrayBufferObject(state, backingStore);
}
ArrayBufferObject* ArrayBufferObject::cloneArrayBuffer(ExecutionState& state, ArrayBuffer* srcBuffer, size_t srcByteOffset, uint64_t srcLength, Object* constructor)
{
// https://www.ecma-international.org/ecma-262/10.0/#sec-clonearraybuffer
@ -84,6 +97,15 @@ ArrayBufferObject::ArrayBufferObject(ExecutionState& state, Object* proto)
{
}
ArrayBufferObject::ArrayBufferObject(ExecutionState& state, BackingStore* backingStore)
: ArrayBufferObject(state)
{
// BackingStore should be valid and non-shared
ASSERT(!!backingStore && !backingStore->isShared());
updateBackingStore(backingStore);
}
void ArrayBufferObject::allocateBuffer(ExecutionState& state, size_t byteLength)
{
detachArrayBuffer();

View file

@ -31,9 +31,11 @@ class ArrayBufferObject : public ArrayBuffer {
public:
explicit ArrayBufferObject(ExecutionState& state);
explicit ArrayBufferObject(ExecutionState& state, Object* proto);
explicit ArrayBufferObject(ExecutionState& state, BackingStore* backingStore);
static ArrayBufferObject* allocateArrayBuffer(ExecutionState& state, Object* constructor, uint64_t byteLength,
Optional<uint64_t> maxByteLength = Optional<uint64_t>(), bool resizeable = true);
static ArrayBufferObject* allocateExternalArrayBuffer(ExecutionState& state, void* dataBlock, size_t byteLength);
static ArrayBufferObject* cloneArrayBuffer(ExecutionState& state, ArrayBuffer* srcBuffer, size_t srcByteOffset, uint64_t srcLength, Object* constructor);
void allocateBuffer(ExecutionState& state, size_t bytelength);

View file

@ -27,7 +27,7 @@ namespace Escargot {
static void backingStorePlatformDeleter(void* data, size_t length, void* deleterData)
{
if (!!data) {
Global::platform()->onFreeArrayBufferObjectDataBuffer(data, length);
Global::platform()->onFreeArrayBufferObjectDataBuffer(data, length, deleterData);
}
}
@ -46,15 +46,15 @@ BackingStore* BackingStore::createDefaultResizableNonSharedBackingStore(size_t b
byteLength, backingStorePlatformDeleter, maxByteLength, true);
}
BackingStore* BackingStore::createNonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback callback, void* callbackData)
BackingStore* BackingStore::createNonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback deleter, void* callbackData)
{
return new NonSharedBackingStore(data, byteLength, callback, callbackData, false);
return new NonSharedBackingStore(data, byteLength, deleter, callbackData, false);
}
NonSharedBackingStore::NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback callback, void* callbackData, bool isAllocatedByPlatform)
NonSharedBackingStore::NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback deleter, void* callbackData, bool isAllocatedByPlatform)
: m_data(data)
, m_byteLength(byteLength)
, m_deleter(callback)
, m_deleter(deleter)
, m_deleterData(callbackData)
, m_isAllocatedByPlatform(isAllocatedByPlatform)
, m_isResizable(false)
@ -64,10 +64,10 @@ NonSharedBackingStore::NonSharedBackingStore(void* data, size_t byteLength, Back
self->m_deleter(self->m_data, self->m_byteLength, self->m_deleterData); }, nullptr, nullptr, nullptr);
}
NonSharedBackingStore::NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback callback, size_t maxByteLength, bool isAllocatedByPlatform)
NonSharedBackingStore::NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback deleter, size_t maxByteLength, bool isAllocatedByPlatform)
: m_data(data)
, m_byteLength(byteLength)
, m_deleter(callback)
, m_deleter(deleter)
, m_maxByteLength(maxByteLength)
, m_isAllocatedByPlatform(isAllocatedByPlatform)
, m_isResizable(true)
@ -132,7 +132,8 @@ BackingStore* BackingStore::createDefaultSharedBackingStore(size_t byteLength)
{
SharedDataBlockInfo* sharedInfo = new SharedDataBlockInfo(
Global::platform()->onMallocArrayBufferObjectDataBuffer(byteLength),
byteLength);
byteLength,
backingStorePlatformDeleter);
return new SharedBackingStore(sharedInfo);
}
@ -140,13 +141,13 @@ BackingStore* BackingStore::createDefaultGrowableSharedBackingStore(size_t byteL
{
SharedDataBlockInfo* sharedInfo = new GrowableSharedDataBlockInfo(
Global::platform()->onMallocArrayBufferObjectDataBuffer(maxByteLength),
byteLength, maxByteLength);
byteLength, maxByteLength,
backingStorePlatformDeleter);
return new SharedBackingStore(sharedInfo);
}
BackingStore* BackingStore::createSharedBackingStore(SharedDataBlockInfo* sharedInfo)
{
ASSERT(sharedInfo->hasValidReference());
return new SharedBackingStore(sharedInfo);
}
@ -157,9 +158,9 @@ void SharedDataBlockInfo::deref()
auto oldValue = m_refCount.fetch_sub(1);
if (oldValue == 1) {
if (isGrowable()) {
Global::platform()->onFreeArrayBufferObjectDataBuffer(m_data, maxByteLength());
m_deleter(m_data, maxByteLength(), nullptr);
} else {
Global::platform()->onFreeArrayBufferObjectDataBuffer(m_data, m_byteLength);
m_deleter(m_data, m_byteLength, nullptr);
}
m_data = nullptr;

View file

@ -107,7 +107,7 @@ class BackingStore : public gc, public BufferAddressObserverManager<BackingStore
public:
static BackingStore* createDefaultNonSharedBackingStore(size_t byteLength);
static BackingStore* createDefaultResizableNonSharedBackingStore(size_t byteLength, size_t maxByteLength);
static BackingStore* createNonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback callback, void* callbackData);
static BackingStore* createNonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback deleter, void* callbackData);
#if defined(ENABLE_THREADING)
static BackingStore* createDefaultSharedBackingStore(size_t byteLength);
@ -124,6 +124,12 @@ public:
virtual void* deleterData() const = 0;
virtual bool isResizable() const = 0;
virtual size_t byteLengthRMW(size_t newByteLength) // special function used only for WASMMemoryObject
{
ASSERT_NOT_REACHED();
return 0;
}
virtual SharedDataBlockInfo* sharedDataBlockInfo() const
{
ASSERT_NOT_REACHED();
@ -192,8 +198,8 @@ public:
void* operator new[](size_t size) = delete;
private:
NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback callback, void* callbackData, bool isAllocatedByPlatform);
NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback callback, size_t maxByteLength, bool isAllocatedByPlatform);
NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback deleter, void* callbackData, bool isAllocatedByPlatform);
NonSharedBackingStore(void* data, size_t byteLength, BackingStoreDeleterCallback deleter, size_t maxByteLength, bool isAllocatedByPlatform);
void* m_data;
size_t m_byteLength;
@ -209,11 +215,13 @@ private:
#if defined(ENABLE_THREADING)
class SharedDataBlockInfo {
public:
SharedDataBlockInfo(void* data, size_t byteLength)
SharedDataBlockInfo(void* data, size_t byteLength, BackingStoreDeleterCallback deleter)
: m_data(data)
, m_byteLength(byteLength)
, m_refCount(0)
, m_deleter(deleter)
{
ASSERT(!!deleter);
}
virtual ~SharedDataBlockInfo() {}
@ -247,6 +255,12 @@ public:
return m_byteLength.load();
}
size_t byteLengthRMW(size_t newByteLength)
{
ASSERT(hasValidReference());
return m_byteLength.exchange(newByteLength);
}
void ref()
{
m_refCount++;
@ -264,12 +278,13 @@ protected:
// defined as atomic value to not to use a lock
std::atomic<size_t> m_byteLength;
std::atomic<size_t> m_refCount;
BackingStoreDeleterCallback m_deleter;
};
class GrowableSharedDataBlockInfo : public SharedDataBlockInfo {
public:
GrowableSharedDataBlockInfo(void* data, size_t byteLength, size_t maxByteLength)
: SharedDataBlockInfo(data, byteLength)
GrowableSharedDataBlockInfo(void* data, size_t byteLength, size_t maxByteLength, BackingStoreDeleterCallback deleter)
: SharedDataBlockInfo(data, byteLength, deleter)
, m_maxByteLength(maxByteLength)
{
}
@ -339,6 +354,11 @@ public:
return m_sharedDataBlockInfo->isGrowable();
}
virtual size_t byteLengthRMW(size_t newByteLength) override
{
return m_sharedDataBlockInfo->byteLengthRMW(newByteLength);
}
virtual void resize(size_t newByteLength) override;
void* operator new(size_t size);

View file

@ -32,7 +32,7 @@ public:
virtual ~Platform() {}
// ArrayBuffer
virtual void* onMallocArrayBufferObjectDataBuffer(size_t sizeInByte) = 0;
virtual void onFreeArrayBufferObjectDataBuffer(void* buffer, size_t sizeInByte) = 0;
virtual void onFreeArrayBufferObjectDataBuffer(void* buffer, size_t sizeInByte, void* deleterData) = 0;
virtual void* onReallocArrayBufferObjectDataBuffer(void* oldBuffer, size_t oldSizeInByte, size_t newSizeInByte) = 0;
// Promise

View file

@ -106,6 +106,20 @@ SharedArrayBufferObject* SharedArrayBufferObject::allocateSharedArrayBuffer(Exec
return new SharedArrayBufferObject(state, proto, byteLength);
}
SharedArrayBufferObject* SharedArrayBufferObject::allocateExternalSharedArrayBuffer(ExecutionState& state, void* dataBlock, size_t byteLength)
{
if (UNLIKELY(byteLength >= ArrayBuffer::maxArrayBufferSize)) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().SharedArrayBuffer.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferSize);
}
// creating a fixed length memory buffer from memaddr.
// NOTE) deleter do nothing, dataBlock will be freed in external module
SharedDataBlockInfo* sharedInfo = new SharedDataBlockInfo(dataBlock, byteLength,
[](void* data, size_t length, void* deleterData) {});
return new SharedArrayBufferObject(state, state.context()->globalObject()->sharedArrayBufferPrototype(), sharedInfo);
}
void* SharedArrayBufferObject::operator new(size_t size)
{
static MAY_THREAD_LOCAL bool typeInited = false;
@ -278,6 +292,12 @@ void SharedArrayBufferObject::setValueInBuffer(ExecutionState& state, size_t byt
}
#endif
}
size_t SharedArrayBufferObject::byteLengthRMW(size_t newByteLength)
{
ASSERT(m_backingStore.hasValue());
return m_backingStore->byteLengthRMW(newByteLength);
}
} // namespace Escargot
#endif

View file

@ -34,6 +34,7 @@ public:
SharedArrayBufferObject(ExecutionState& state, Object* proto, SharedDataBlockInfo* sharedInfo);
static SharedArrayBufferObject* allocateSharedArrayBuffer(ExecutionState& state, Object* constructor, uint64_t byteLength, Optional<uint64_t> maxByteLength);
static SharedArrayBufferObject* allocateExternalSharedArrayBuffer(ExecutionState& state, void* dataBlock, size_t byteLength);
virtual bool isSharedArrayBufferObject() const override
{
@ -47,6 +48,8 @@ public:
void* operator new(size_t size);
void* operator new[](size_t size) = delete;
size_t byteLengthRMW(size_t newByteLength);
};
} // namespace Escargot

View file

@ -529,6 +529,7 @@ namespace Escargot {
F(memory) \
F(module) \
F(table) \
F(shared) \
F(v128) \
F(validate)
#else

View file

@ -28,6 +28,7 @@
#include "runtime/ExtendedNativeFunctionObject.h"
#include "runtime/ArrayObject.h"
#include "runtime/ArrayBufferObject.h"
#include "runtime/SharedArrayBufferObject.h"
#include "runtime/BackingStore.h"
#include "runtime/PromiseObject.h"
#include "runtime/Job.h"
@ -382,6 +383,36 @@ static Value builtinWASMInstanceExportsGetter(ExecutionState& state, Value thisV
}
// WebAssembly.Memory
static ArrayBuffer* createFixedLengthMemoryBuffer(ExecutionState& state, wasm_memory_t* memaddr, bool isShared)
{
ASSERT(!!memaddr);
// Note) wasm_memory_data with zero size returns null pointer
// predefined temporal address is allocated for this case
void* dataBlock = wasm_memory_size(memaddr) == 0 ? WASMEmptyBlockAddress : wasm_memory_data(memaddr);
ArrayBuffer* buffer = nullptr;
if (isShared) {
ASSERT(wasm_memory_is_shared(memaddr));
// Let block be a Shared Data Block which is identified with the underlying memory of memaddr.
// Let buffer be a new SharedArrayBuffer with the internal slots [[ArrayBufferData]] and [[ArrayBufferByteLength]].
buffer = SharedArrayBufferObject::allocateExternalSharedArrayBuffer(state, dataBlock, wasm_memory_data_size(memaddr));
// Perform ! SetIntegrityLevel(buffer, "frozen").
if (UNLIKELY(!Object::setIntegrityLevel(state, buffer, false))) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::GlobalObject_IllegalFirstArgument);
}
} else {
// Let block be a Data Block which is identified with the underlying memory of memaddr.
// Let buffer be a new ArrayBuffer with the internal slots [[ArrayBufferData]], [[ArrayBufferByteLength]], and [[ArrayBufferDetachKey]].
buffer = ArrayBufferObject::allocateExternalArrayBuffer(state, dataBlock, wasm_memory_data_size(memaddr));
}
// Return buffer.
return buffer;
}
static Value builtinWASMMemoryConstructor(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
const StaticStrings* strings = &state.context()->staticStrings();
@ -391,6 +422,7 @@ static Value builtinWASMMemoryConstructor(ExecutionState& state, Value thisValue
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, strings->WebAssembly.string(), false, strings->Memory.string(), ErrorObject::Messages::Not_Invoked_With_New);
}
bool isShared = false;
Value desc = argv[0];
// check and get 'initial' property from the first argument
@ -406,12 +438,17 @@ static Value builtinWASMMemoryConstructor(ExecutionState& state, Value thisValue
uint32_t maximum = wasm_limits_max_default;
{
auto maxResult = wasmGetValueFromObjectProperty(state, desc, strings->maximum, strings->valueOf);
auto sharedResult = wasmGetValueFromObjectProperty(state, desc, strings->shared, AtomicString());
isShared = sharedResult.first ? sharedResult.second.toBoolean() : false;
if (maxResult.first) {
Value maxValue = maxResult.second;
if (!maxValue.isUInt32()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, strings->WebAssembly.string(), false, strings->Memory.string(), ErrorObject::Messages::GlobalObject_IllegalFirstArgument);
}
maximum = maxValue.asUInt32();
} else if (isShared) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, strings->WebAssembly.string(), false, strings->Memory.string(), ErrorObject::Messages::GlobalObject_IllegalFirstArgument);
}
}
@ -424,20 +461,20 @@ static Value builtinWASMMemoryConstructor(ExecutionState& state, Value thisValue
own wasm_memorytype_t* memtype = wasm_memorytype_new(&limits);
// Let (store, memaddr) be mem_alloc(store, memtype). If allocation fails, throw a RangeError exception.
own wasm_memory_t* memaddr = wasm_memory_new(ThreadLocal::wasmStore(), memtype);
own wasm_memory_t* memaddr = nullptr;
if (isShared) {
memaddr = wasm_shared_memory_new(ThreadLocal::wasmStore(), memtype);
} else {
memaddr = wasm_memory_new(ThreadLocal::wasmStore(), memtype);
}
wasm_memorytype_delete(memtype);
wasm_ref_t* memref = wasm_memory_as_ref(memaddr);
// Let map be the surrounding agent's associated Memory object cache.
// Assert: map[memaddr] doesnt exist.
ASSERT(!state.context()->wasmCache()->findMemory(wasm_memory_as_ref(memaddr)));
// Create a memory buffer from memaddr
ArrayBufferObject* buffer = new ArrayBufferObject(state);
// Note) wasm_memory_data with zero size returns null pointer
// predefined temporal address is allocated for this case
void* dataBlock = initial == 0 ? WASMEmptyBlockAddress : wasm_memory_data(memaddr);
BackingStore* backingStore = BackingStore::createNonSharedBackingStore(dataBlock, wasm_memory_data_size(memaddr), [](void* data, size_t length, void* deleterData) {}, nullptr);
buffer->attachBuffer(backingStore);
// Let buffer be the result of creating a fixed length memory buffer from memaddr.
ArrayBuffer* buffer = createFixedLengthMemoryBuffer(state, memaddr, isShared);
// Let proto be ? GetPrototypeFromConstructor(newTarget, "%WebAssemblyMemoryPrototype%").
Object* proto = Object::getPrototypeFromConstructor(state, newTarget.value(), [](ExecutionState& state, Context* constructorRealm) -> Object* {
@ -449,7 +486,7 @@ static Value builtinWASMMemoryConstructor(ExecutionState& state, Value thisValue
WASMMemoryObject* memoryObj = new WASMMemoryObject(state, proto, memaddr, buffer);
// Set map[memaddr] to memory.
state.context()->wasmCache()->appendMemory(memref, memoryObj);
state.context()->wasmCache()->appendMemory(wasm_memory_as_ref(memaddr), memoryObj);
return memoryObj;
}
@ -470,8 +507,7 @@ static Value builtinWASMMemoryGrow(ExecutionState& state, Value thisValue, size_
wasm_memory_pages_t delta = deltaValue.asUInt32();
// Let memaddr be this.[[Memory]].
WASMMemoryObject* memoryObj = thisValue.asObject()->asWASMMemoryObject();
wasm_memory_t* memaddr = memoryObj->memory();
wasm_memory_t* memaddr = thisValue.asObject()->asWASMMemoryObject()->memory();
// Let ret be the mem_size(store, memaddr).
// wasm_memory_pages_t maps to uint32_t
@ -484,25 +520,27 @@ static Value builtinWASMMemoryGrow(ExecutionState& state, Value thisValue, size_
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, strings->WebAssemblyDotMemory.string(), false, strings->grow.string(), ErrorObject::Messages::GlobalObject_RangeError);
}
// Refresh the memory buffer of memaddr.
// Let map be the surrounding agent's associated Memory object cache.
// Assert: map[memaddr] exists.
ASSERT(state.context()->wasmCache()->findMemory(wasm_memory_as_ref(memaddr)));
// Perform ! DetachArrayBuffer(memory.[[BufferObject]], "WebAssembly.Memory").
memoryObj->buffer()->detachArrayBuffer();
// Let memory be map[memaddr].
WASMMemoryObject* memoryObj = state.context()->wasmCache()->findMemory(wasm_memory_as_ref(memaddr));
// Let buffer be memory.[[BufferObject]].
ArrayBuffer* buffer = memoryObj->buffer();
// Let buffer be a the result of creating a memory buffer from memaddr.
ArrayBufferObject* buffer = new ArrayBufferObject(state);
// Note) wasm_memory_data with zero size returns null pointer
// predefined temporal address is allocated for this case
size_t dataSize = wasm_memory_data_size(memaddr);
void* dataBlock = dataSize == 0 ? WASMEmptyBlockAddress : wasm_memory_data(memaddr);
// If IsSharedArrayBuffer(buffer) is false,
// Perform ! DetachArrayBuffer(buffer, "WebAssembly.Memory").
bool isShared = buffer->isSharedArrayBufferObject();
if (!isShared) {
buffer->asArrayBufferObject()->detachArrayBuffer();
}
BackingStore* backingStore = BackingStore::createNonSharedBackingStore(dataBlock, dataSize, [](void* data, size_t length, void* deleterData) {}, nullptr);
buffer->attachBuffer(backingStore);
// Set memory.[[BufferObject]] to buffer.
memoryObj->setBuffer(buffer);
// Let newBuffer be the result of creating a fixed length memory buffer from memaddr.
ArrayBuffer* newBuffer = createFixedLengthMemoryBuffer(state, memaddr, isShared);
// Set memory.[[BufferObject]] to newBuffer.
memoryObj->setBuffer(newBuffer);
// Return ret.
return Value(ret);
@ -515,22 +553,27 @@ static Value builtinWASMMemoryBufferGetter(ExecutionState& state, Value thisValu
}
WASMMemoryObject* memoryObj = thisValue.asObject()->asWASMMemoryObject();
if (UNLIKELY(wasm_memory_data_size(memoryObj->memory()) != memoryObj->buffer()->byteLength())) {
// FIXME data block of memory has been changed by previous memory.grow operation, but not yet reflected
// So, we update the buffer here to reflect modifications on data block and its size.
// TODO Actually, this change should be applied immediately after memory.grow operation
memoryObj->buffer()->detachArrayBuffer();
wasm_memory_t* memaddr = memoryObj->memory();
ArrayBufferObject* buffer = new ArrayBufferObject(state);
size_t dataSize = wasm_memory_data_size(memoryObj->memory());
void* dataBlock = dataSize == 0 ? WASMEmptyBlockAddress : wasm_memory_data(memoryObj->memory());
if (memoryObj->buffer()->isSharedArrayBufferObject()) {
// Let map be the surrounding agent's associated Memory object cache.
// Assert: map[memaddr] exists.
ASSERT(state.context()->wasmCache()->findMemory(wasm_memory_as_ref(memaddr)));
BackingStore* backingStore = BackingStore::createNonSharedBackingStore(dataBlock, dataSize, [](void* data, size_t length, void* deleterData) {}, nullptr);
buffer->attachBuffer(backingStore);
// Let newMemory be map[memaddr].
WASMMemoryObject* newMemory = state.context()->wasmCache()->findMemory(wasm_memory_as_ref(memaddr));
// Let newBufferObject be newMemory.[[BufferObject]].
ArrayBuffer* newBufferObject = newMemory->buffer();
memoryObj->setBuffer(buffer);
// Set this.[[BufferObject]] to newBufferObject.
memoryObj->setBuffer(newBufferObject);
// Return newBufferObject.
ASSERT(wasm_memory_data_size(memoryObj->memory()) == memoryObj->buffer()->byteLength());
return newBufferObject;
}
// Return this.[[BufferObject]].
ASSERT(wasm_memory_data_size(memoryObj->memory()) == memoryObj->buffer()->byteLength());
return memoryObj->buffer();
}

View file

@ -24,6 +24,7 @@
#include "runtime/Context.h"
#include "runtime/Object.h"
#include "runtime/ArrayBufferObject.h"
#include "runtime/SharedArrayBufferObject.h"
#include "runtime/BackingStore.h"
#include "wasm/WASMObject.h"
#include "wasm/WASMValueConverter.h"
@ -123,12 +124,12 @@ void* WASMInstanceObject::operator new(size_t size)
return GC_MALLOC_EXPLICITLY_TYPED(size, descr);
}
WASMMemoryObject::WASMMemoryObject(ExecutionState& state, wasm_memory_t* memory, ArrayBufferObject* buffer)
WASMMemoryObject::WASMMemoryObject(ExecutionState& state, wasm_memory_t* memory, ArrayBuffer* buffer)
: WASMMemoryObject(state, state.context()->globalObject()->wasmMemoryPrototype(), memory, buffer)
{
}
WASMMemoryObject::WASMMemoryObject(ExecutionState& state, Object* proto, wasm_memory_t* memory, ArrayBufferObject* buffer)
WASMMemoryObject::WASMMemoryObject(ExecutionState& state, Object* proto, wasm_memory_t* memory, ArrayBuffer* buffer)
: DerivedObject(state, proto)
, m_memory(memory)
, m_buffer(buffer)
@ -137,8 +138,10 @@ WASMMemoryObject::WASMMemoryObject(ExecutionState& state, Object* proto, wasm_me
addFinalizer([](PointerValue* obj, void* data) {
WASMMemoryObject* self = (WASMMemoryObject*)obj;
if (!wasm_memory_is_shared(self->memory())) {
self->buffer()->asArrayBufferObject()->detachArrayBuffer();
}
wasm_memory_delete(self->memory());
self->buffer()->detachArrayBuffer();
},
nullptr);
}
@ -169,19 +172,24 @@ WASMMemoryObject* WASMMemoryObject::createMemoryObject(ExecutionState& state, wa
return memory;
}
// Let memory be a new Memory.
// Initialize memory from memory.
ArrayBufferObject* buffer = new ArrayBufferObject(state);
// Note) wasm_memory_data with zero size returns null pointer
// predefined temporal address is allocated for this case
void* dataBlock = wasm_memory_size(memaddr) == 0 ? WASMEmptyBlockAddress : wasm_memory_data(memaddr);
// Init BackingStore with empty deleter
BackingStore* backingStore = BackingStore::createNonSharedBackingStore(dataBlock, wasm_memory_data_size(memaddr), [](void* data, size_t length, void* deleterData) {}, nullptr);
buffer->attachBuffer(backingStore);
ArrayBuffer* buffer = nullptr;
if (wasm_memory_is_shared(memaddr)) {
buffer = SharedArrayBufferObject::allocateExternalSharedArrayBuffer(state, dataBlock, wasm_memory_data_size(memaddr));
} else {
buffer = ArrayBufferObject::allocateExternalArrayBuffer(state, dataBlock, wasm_memory_data_size(memaddr));
}
// Set memory.[[Memory]] to memory.
// Let status be the result of calling SetIntegrityLevel(buffer, "frozen").
// If status is false, a TypeError is thrown.
if (!Object::setIntegrityLevel(state, buffer, false)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::GlobalObject_IllegalFirstArgument);
}
// Set memory.[[Memory]] to memaddr.
// Set memory.[[BufferObject]] to buffer.
memory = new WASMMemoryObject(state, memaddr, buffer);
@ -192,13 +200,13 @@ WASMMemoryObject* WASMMemoryObject::createMemoryObject(ExecutionState& state, wa
return memory;
}
ArrayBufferObject* WASMMemoryObject::buffer() const
ArrayBuffer* WASMMemoryObject::buffer() const
{
ASSERT(!!m_buffer && !m_buffer->isDetachedBuffer());
return m_buffer;
}
void WASMMemoryObject::setBuffer(ArrayBufferObject* buffer)
void WASMMemoryObject::setBuffer(ArrayBuffer* buffer)
{
ASSERT(!!buffer && !buffer->isDetachedBuffer());
m_buffer = buffer;

View file

@ -99,8 +99,8 @@ private:
class WASMMemoryObject : public DerivedObject {
public:
explicit WASMMemoryObject(ExecutionState& state, wasm_memory_t* memory, ArrayBufferObject* buffer);
explicit WASMMemoryObject(ExecutionState& state, Object* proto, wasm_memory_t* memory, ArrayBufferObject* buffer);
explicit WASMMemoryObject(ExecutionState& state, wasm_memory_t* memory, ArrayBuffer* buffer);
explicit WASMMemoryObject(ExecutionState& state, Object* proto, wasm_memory_t* memory, ArrayBuffer* buffer);
virtual bool isWASMMemoryObject() const override
{
@ -118,12 +118,12 @@ public:
return m_memory;
}
ArrayBufferObject* buffer() const;
void setBuffer(ArrayBufferObject* buffer);
ArrayBuffer* buffer() const;
void setBuffer(ArrayBuffer* buffer);
private:
wasm_memory_t* m_memory;
ArrayBufferObject* m_buffer;
ArrayBuffer* m_buffer;
};
class WASMTableObject : public DerivedObject {

View file

@ -814,9 +814,6 @@ def run_wasm_js(engine, arch, extra_arg):
WASM_TEST_MJS = join(WASM_TEST_ROOT, 'mjsunit.js')
WASM_TEST_HARNESS = join(WASM_TEST_ROOT, 'testharness.js')
copy(join(PROJECT_SOURCE_DIR, 'tools', 'test', 'wasm-js', 'grow-part.any.js'), join(WASM_TEST_DIR, 'memory', 'grow-part.any.js'))
EXCLUDE_LIST_FILE = join(PROJECT_SOURCE_DIR, 'tools', 'test', 'wasm-js', 'exclude_list.txt')
exclude_list = []
with open(EXCLUDE_LIST_FILE) as f:

View file

@ -1,3 +1,2 @@
limits.any.js
memory/grow.any.js
module/customSections.any.js

View file

@ -1,163 +0,0 @@
// META: global=window,dedicatedworker,jsshell
// META: script=/wasm/jsapi/memory/assertions.js
test(() => {
const argument = { "initial": 0 };
const memory = new WebAssembly.Memory(argument);
assert_throws_js(TypeError, () => memory.grow());
}, "Missing arguments");
test(t => {
const thisValues = [
undefined,
null,
true,
"",
Symbol(),
1,
{},
WebAssembly.Memory,
WebAssembly.Memory.prototype,
];
const argument = {
valueOf: t.unreached_func("Should not touch the argument (valueOf)"),
toString: t.unreached_func("Should not touch the argument (toString)"),
};
const fn = WebAssembly.Memory.prototype.grow;
for (const thisValue of thisValues) {
assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`);
}
}, "Branding");
test(() => {
const argument = { "initial": 0 };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing");
const result = memory.grow(2);
assert_equals(result, 0);
const newMemory = memory.buffer;
assert_not_equals(oldMemory, newMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing");
assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing");
}, "Zero initial");
test(() => {
const argument = { "initial": { valueOf() { return 0 } } };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing");
const result = memory.grow({ valueOf() { return 2 } });
assert_equals(result, 0);
const newMemory = memory.buffer;
assert_not_equals(oldMemory, newMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing");
assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing");
}, "Zero initial with valueOf");
test(() => {
const argument = { "initial": 3 };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 3 }, "Buffer before growing");
const result = memory.grow(2);
assert_equals(result, 3);
const newMemory = memory.buffer;
assert_not_equals(oldMemory, newMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing");
assert_ArrayBuffer(newMemory, { "size": 5 }, "New buffer after growing");
}, "Non-zero initial");
test(() => {
const argument = { "initial": 0, "maximum": 2 };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing");
const result = memory.grow(2);
assert_equals(result, 0);
const newMemory = memory.buffer;
assert_not_equals(oldMemory, newMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing");
assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing");
}, "Zero initial with respected maximum");
test(() => {
const argument = { "initial": 0, "maximum": 2 };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing");
const result = memory.grow(1);
assert_equals(result, 0);
const newMemory = memory.buffer;
assert_not_equals(oldMemory, newMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing once");
assert_ArrayBuffer(newMemory, { "size": 1 }, "New buffer after growing once");
const result2 = memory.grow(1);
assert_equals(result2, 1);
const newestMemory = memory.buffer;
assert_not_equals(newMemory, newestMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "New buffer after growing twice");
assert_ArrayBuffer(newMemory, { "detached": true }, "New buffer after growing twice");
assert_ArrayBuffer(newestMemory, { "size": 2 }, "Newest buffer after growing twice");
}, "Zero initial with respected maximum grown twice");
test(() => {
const argument = { "initial": 1, "maximum": 2 };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 1 }, "Buffer before growing");
assert_throws_js(RangeError, () => memory.grow(2));
assert_equals(memory.buffer, oldMemory);
assert_ArrayBuffer(memory.buffer, { "size": 1 }, "Buffer before trying to grow");
}, "Zero initial growing too much");
const outOfRangeValues = [
undefined,
NaN,
Infinity,
-Infinity,
-1,
0x100000000,
0x1000000000,
"0x100000000",
{ valueOf() { return 0x100000000; } },
];
for (const value of outOfRangeValues) {
test(() => {
const argument = { "initial": 0 };
const memory = new WebAssembly.Memory(argument);
assert_throws_js(TypeError, () => memory.grow(value));
}, `Out-of-range argument: ${format_value(value)}`);
}
test(() => {
const argument = { "initial": 0 };
const memory = new WebAssembly.Memory(argument);
const oldMemory = memory.buffer;
assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing");
const result = memory.grow(2, {});
assert_equals(result, 0);
const newMemory = memory.buffer;
assert_not_equals(oldMemory, newMemory);
assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing");
assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing");
}, "Stray argument");