mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
Support SharedArrayBuffer in WASM Memory
Signed-off-by: HyukWoo Park <hyukwoo.park@jbnu.ac.kr>
This commit is contained in:
parent
a11e806c20
commit
1e11e1d668
18 changed files with 202 additions and 244 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -529,6 +529,7 @@ namespace Escargot {
|
|||
F(memory) \
|
||||
F(module) \
|
||||
F(table) \
|
||||
F(shared) \
|
||||
F(v128) \
|
||||
F(validate)
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -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] doesn’t 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
limits.any.js
|
||||
memory/grow.any.js
|
||||
module/customSections.any.js
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
Loading…
Add table
Add a link
Reference in a new issue