mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-22 17:24:48 -05:00
LibWeb: Support creation of shared memory in WebAssembly API
Add support for shared memory creation in WebAssembly memory API. This API is needed for WPT tests that use shared array buffers. Import related WPT tests.
This commit is contained in:
parent
6ec06a01a2
commit
b03138cbff
Notes:
github-actions[bot]
2024-12-08 21:11:34 +00:00
Author: https://github.com/zetslief Commit: https://github.com/LadybirdBrowser/ladybird/commit/b03138cbffd Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2828 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/alimpfard ✅
13 changed files with 516 additions and 16 deletions
|
@ -27,6 +27,7 @@
|
|||
#include <LibJS/Runtime/PrimitiveString.h>
|
||||
#include <LibJS/Runtime/RegExpObject.h>
|
||||
#include <LibJS/Runtime/Set.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/StringObject.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
@ -803,7 +804,7 @@ public:
|
|||
if (bytes_or_error.is_error())
|
||||
return WebIDL::DataCloneError::create(*realm, "out of memory"_string);
|
||||
auto bytes = bytes_or_error.release_value();
|
||||
JS::ArrayBuffer* buffer = TRY(JS::allocate_shared_array_buffer(m_vm, realm->intrinsics().array_buffer_constructor(), bytes.size()));
|
||||
JS::ArrayBuffer* buffer = TRY(JS::allocate_shared_array_buffer(m_vm, realm->intrinsics().shared_array_buffer_constructor(), bytes.size()));
|
||||
bytes.span().copy_to(buffer->buffer().span());
|
||||
value = buffer;
|
||||
break;
|
||||
|
@ -820,7 +821,7 @@ public:
|
|||
return WebIDL::DataCloneError::create(*realm, "out of memory"_string);
|
||||
size_t max_byte_length = deserialize_primitive_type<size_t>(m_serialized, m_position);
|
||||
auto bytes = bytes_or_error.release_value();
|
||||
JS::ArrayBuffer* buffer = TRY(JS::allocate_shared_array_buffer(m_vm, realm->intrinsics().array_buffer_constructor(), bytes.size()));
|
||||
JS::ArrayBuffer* buffer = TRY(JS::allocate_shared_array_buffer(m_vm, realm->intrinsics().shared_array_buffer_constructor(), bytes.size()));
|
||||
bytes.span().copy_to(buffer->buffer().span());
|
||||
buffer->set_max_byte_length(max_byte_length);
|
||||
value = buffer;
|
||||
|
|
|
@ -59,7 +59,7 @@ void Instance::initialize(JS::Realm& realm)
|
|||
[&](Wasm::MemoryAddress const& address) {
|
||||
Optional<GC::Ptr<Memory>> object = m_memory_instances.get(address);
|
||||
if (!object.has_value()) {
|
||||
object = realm.create<Memory>(realm, address);
|
||||
object = realm.create<Memory>(realm, address, Memory::Shared::No);
|
||||
m_memory_instances.set(address, *object);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWasm/Types.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
|
@ -21,6 +22,13 @@ WebIDL::ExceptionOr<GC::Ref<Memory>> Memory::construct_impl(JS::Realm& realm, Me
|
|||
{
|
||||
auto& vm = realm.vm();
|
||||
|
||||
// https://webassembly.github.io/threads/js-api/index.html#dom-memory-memory
|
||||
// 4. Let share be shared if descriptor["shared"] is true and unshared otherwise.
|
||||
// 5. If share is shared and maximum is empty, throw a TypeError exception.
|
||||
auto shared = descriptor.shared.value_or(false);
|
||||
if (shared && !descriptor.maximum.has_value())
|
||||
return vm.throw_completion<JS::TypeError>("Maximum has to be specified for shared memory."sv);
|
||||
|
||||
Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) };
|
||||
Wasm::MemoryType memory_type { move(limits) };
|
||||
|
||||
|
@ -29,7 +37,8 @@ WebIDL::ExceptionOr<GC::Ref<Memory>> Memory::construct_impl(JS::Realm& realm, Me
|
|||
if (!address.has_value())
|
||||
return vm.throw_completion<JS::TypeError>("Wasm Memory allocation failed"sv);
|
||||
|
||||
auto memory_object = realm.create<Memory>(realm, *address);
|
||||
auto memory_object = realm.create<Memory>(realm, *address, shared ? Shared::Yes : Shared::No);
|
||||
|
||||
cache.abstract_machine().store().get(*address)->successful_grow_hook = [memory_object] {
|
||||
MUST(memory_object->reset_the_memory_buffer());
|
||||
};
|
||||
|
@ -37,9 +46,10 @@ WebIDL::ExceptionOr<GC::Ref<Memory>> Memory::construct_impl(JS::Realm& realm, Me
|
|||
return memory_object;
|
||||
}
|
||||
|
||||
Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address)
|
||||
Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
|
||||
: Bindings::PlatformObject(realm)
|
||||
, m_address(address)
|
||||
, m_shared(shared)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -74,7 +84,9 @@ WebIDL::ExceptionOr<u32> Memory::grow(u32 delta)
|
|||
return previous_size;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#reset-the-memory-buffer
|
||||
// https://webassembly.github.io/spec/js-api/#refresh-the-memory-buffer
|
||||
// FIXME: `refresh-the-memory-buffer` is a global abstract operation.
|
||||
// Implement it as a static function to align with the spec.
|
||||
WebIDL::ExceptionOr<void> Memory::reset_the_memory_buffer()
|
||||
{
|
||||
if (!m_buffer)
|
||||
|
@ -83,10 +95,16 @@ WebIDL::ExceptionOr<void> Memory::reset_the_memory_buffer()
|
|||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
MUST(JS::detach_array_buffer(vm, *m_buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)));
|
||||
if (m_buffer->is_fixed_length()) {
|
||||
// https://webassembly.github.io/threads/js-api/index.html#refresh-the-memory-buffer
|
||||
// 1. If IsSharedArrayBuffer(buffer) is false,
|
||||
if (!m_buffer->is_shared_array_buffer()) {
|
||||
// 1. Perform ! DetachArrayBuffer(buffer, "WebAssembly.Memory").
|
||||
MUST(JS::detach_array_buffer(vm, *m_buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)));
|
||||
}
|
||||
}
|
||||
|
||||
auto buffer = TRY(create_a_memory_buffer(vm, realm, m_address));
|
||||
m_buffer = buffer;
|
||||
m_buffer = TRY(create_a_fixed_length_memory_buffer(vm, realm, m_address, m_shared));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -98,21 +116,41 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::buffer() const
|
|||
auto& realm = *vm.current_realm();
|
||||
|
||||
if (!m_buffer)
|
||||
m_buffer = TRY(create_a_memory_buffer(vm, realm, m_address));
|
||||
m_buffer = TRY(create_a_fixed_length_memory_buffer(vm, realm, m_address, m_shared));
|
||||
|
||||
return GC::Ref(*m_buffer);
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#create-a-memory-buffer
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::create_a_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address)
|
||||
// https://webassembly.github.io/spec/js-api/#create-a-fixed-length-memory-buffer
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::create_a_fixed_length_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
|
||||
{
|
||||
auto& context = Detail::get_cache(realm);
|
||||
auto* memory = context.abstract_machine().store().get(address);
|
||||
if (!memory)
|
||||
return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv);
|
||||
|
||||
auto array_buffer = JS::ArrayBuffer::create(realm, &memory->data());
|
||||
array_buffer->set_detach_key(JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string));
|
||||
JS::ArrayBuffer* array_buffer;
|
||||
// https://webassembly.github.io/threads/js-api/index.html#create-a-fixed-length-memory-buffer
|
||||
// 3. If share is shared,
|
||||
if (shared == Shared::Yes) {
|
||||
// 1. Let block be a Shared Data Block which is identified with the underlying memory of memaddr.
|
||||
auto bytes = memory->data();
|
||||
|
||||
// 2. Let buffer be a new SharedArrayBuffer with the internal slots [[ArrayBufferData]] and [[ArrayBufferByteLength]].
|
||||
array_buffer = TRY(JS::allocate_shared_array_buffer(vm, realm.intrinsics().shared_array_buffer_constructor(), bytes.size()));
|
||||
bytes.span().copy_to(array_buffer->buffer().span());
|
||||
// 3. FIXME: Set buffer.[[ArrayBufferData]] to block.
|
||||
// 4. FIXME: Set buffer.[[ArrayBufferByteLength]] to the length of block.
|
||||
|
||||
// 5. Perform ! SetIntegrityLevel(buffer, "frozen").
|
||||
MUST(array_buffer->set_integrity_level(JS::Object::IntegrityLevel::Frozen));
|
||||
}
|
||||
|
||||
// 4. Otherwise,
|
||||
else {
|
||||
array_buffer = JS::ArrayBuffer::create(realm, &memory->data());
|
||||
array_buffer->set_detach_key(JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string));
|
||||
}
|
||||
|
||||
return GC::Ref(*array_buffer);
|
||||
}
|
||||
|
|
|
@ -20,12 +20,18 @@ namespace Web::WebAssembly {
|
|||
struct MemoryDescriptor {
|
||||
u32 initial { 0 };
|
||||
Optional<u32> maximum;
|
||||
Optional<bool> shared;
|
||||
};
|
||||
|
||||
class Memory : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(Memory, Bindings::PlatformObject);
|
||||
GC_DECLARE_ALLOCATOR(Memory);
|
||||
|
||||
enum class Shared {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<GC::Ref<Memory>> construct_impl(JS::Realm&, MemoryDescriptor& descriptor);
|
||||
|
||||
|
@ -35,15 +41,16 @@ public:
|
|||
Wasm::MemoryAddress address() const { return m_address; }
|
||||
|
||||
private:
|
||||
Memory(JS::Realm&, Wasm::MemoryAddress);
|
||||
Memory(JS::Realm&, Wasm::MemoryAddress, Shared shared);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
WebIDL::ExceptionOr<void> reset_the_memory_buffer();
|
||||
static WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> create_a_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress);
|
||||
static WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> create_a_fixed_length_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared);
|
||||
|
||||
Wasm::MemoryAddress m_address;
|
||||
Shared m_shared { Shared::No };
|
||||
mutable GC::Ptr<JS::ArrayBuffer> m_buffer;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
dictionary MemoryDescriptor {
|
||||
required [EnforceRange] unsigned long initial;
|
||||
[EnforceRange] unsigned long maximum;
|
||||
// https://webassembly.github.io/threads/js-api/index.html#dictdef-memorydescriptor
|
||||
boolean shared = false;
|
||||
};
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#memories
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 3 tests
|
||||
|
||||
3 Pass
|
||||
Pass Shared memory without maximum
|
||||
Pass Order of evaluation for descriptor (with shared)
|
||||
Pass Shared memory
|
|
@ -0,0 +1,25 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 19 tests
|
||||
|
||||
18 Pass
|
||||
1 Fail
|
||||
Pass Missing arguments
|
||||
Pass Branding
|
||||
Pass Zero initial
|
||||
Pass Zero initial with valueOf
|
||||
Pass Non-zero initial
|
||||
Pass Zero initial with respected maximum
|
||||
Pass Zero initial with respected maximum grown twice
|
||||
Pass Zero initial growing too much
|
||||
Pass Out-of-range argument: undefined
|
||||
Pass Out-of-range argument: NaN
|
||||
Pass Out-of-range argument: Infinity
|
||||
Pass Out-of-range argument: -Infinity
|
||||
Pass Out-of-range argument: -1
|
||||
Pass Out-of-range argument: 4294967296
|
||||
Pass Out-of-range argument: 68719476736
|
||||
Pass Out-of-range argument: "0x100000000"
|
||||
Pass Out-of-range argument: object "[object Object]"
|
||||
Pass Stray argument
|
||||
Fail Growing shared memory does not detach old buffer
|
105
Tests/LibWeb/Text/input/wpt-import/wasm/jsapi/assertions.js
Normal file
105
Tests/LibWeb/Text/input/wpt-import/wasm/jsapi/assertions.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
function assert_function_name(fn, name, description) {
|
||||
const propdesc = Object.getOwnPropertyDescriptor(fn, "name");
|
||||
assert_equals(typeof propdesc, "object", `${description} should have name property`);
|
||||
assert_false(propdesc.writable, "writable", `${description} name should not be writable`);
|
||||
assert_false(propdesc.enumerable, "enumerable", `${description} name should not be enumerable`);
|
||||
assert_true(propdesc.configurable, "configurable", `${description} name should be configurable`);
|
||||
assert_equals(propdesc.value, name, `${description} name should be ${name}`);
|
||||
}
|
||||
globalThis.assert_function_name = assert_function_name;
|
||||
|
||||
function assert_function_length(fn, length, description) {
|
||||
const propdesc = Object.getOwnPropertyDescriptor(fn, "length");
|
||||
assert_equals(typeof propdesc, "object", `${description} should have length property`);
|
||||
assert_false(propdesc.writable, "writable", `${description} length should not be writable`);
|
||||
assert_false(propdesc.enumerable, "enumerable", `${description} length should not be enumerable`);
|
||||
assert_true(propdesc.configurable, "configurable", `${description} length should be configurable`);
|
||||
assert_equals(propdesc.value, length, `${description} length should be ${length}`);
|
||||
}
|
||||
globalThis.assert_function_length = assert_function_length;
|
||||
|
||||
function assert_exported_function(fn, { name, length }, description) {
|
||||
if (WebAssembly.Function === undefined) {
|
||||
assert_equals(Object.getPrototypeOf(fn), Function.prototype,
|
||||
`${description}: prototype`);
|
||||
} else {
|
||||
assert_equals(Object.getPrototypeOf(fn), WebAssembly.Function.prototype,
|
||||
`${description}: prototype`);
|
||||
}
|
||||
|
||||
assert_function_name(fn, name, description);
|
||||
assert_function_length(fn, length, description);
|
||||
}
|
||||
globalThis.assert_exported_function = assert_exported_function;
|
||||
|
||||
function assert_Instance(instance, expected_exports) {
|
||||
assert_equals(Object.getPrototypeOf(instance), WebAssembly.Instance.prototype,
|
||||
"prototype");
|
||||
assert_true(Object.isExtensible(instance), "extensible");
|
||||
|
||||
assert_equals(instance.exports, instance.exports, "exports should be idempotent");
|
||||
const exports = instance.exports;
|
||||
|
||||
assert_equals(Object.getPrototypeOf(exports), null, "exports prototype");
|
||||
assert_false(Object.isExtensible(exports), "extensible exports");
|
||||
assert_array_equals(Object.keys(exports), Object.keys(expected_exports), "matching export keys");
|
||||
for (const [key, expected] of Object.entries(expected_exports)) {
|
||||
const property = Object.getOwnPropertyDescriptor(exports, key);
|
||||
assert_equals(typeof property, "object", `${key} should be present`);
|
||||
assert_false(property.writable, `${key}: writable`);
|
||||
assert_true(property.enumerable, `${key}: enumerable`);
|
||||
assert_false(property.configurable, `${key}: configurable`);
|
||||
const actual = property.value;
|
||||
assert_true(Object.isExtensible(actual), `${key}: extensible`);
|
||||
|
||||
switch (expected.kind) {
|
||||
case "function":
|
||||
assert_exported_function(actual, expected, `value of ${key}`);
|
||||
break;
|
||||
case "global":
|
||||
assert_equals(Object.getPrototypeOf(actual), WebAssembly.Global.prototype,
|
||||
`value of ${key}: prototype`);
|
||||
assert_equals(actual.value, expected.value, `value of ${key}: value`);
|
||||
assert_equals(actual.valueOf(), expected.value, `value of ${key}: valueOf()`);
|
||||
break;
|
||||
case "memory":
|
||||
assert_equals(Object.getPrototypeOf(actual), WebAssembly.Memory.prototype,
|
||||
`value of ${key}: prototype`);
|
||||
assert_equals(Object.getPrototypeOf(actual.buffer), ArrayBuffer.prototype,
|
||||
`value of ${key}: prototype of buffer`);
|
||||
assert_equals(actual.buffer.byteLength, 0x10000 * expected.size, `value of ${key}: size of buffer`);
|
||||
const array = new Uint8Array(actual.buffer);
|
||||
assert_equals(array[0], 0, `value of ${key}: first element of buffer`);
|
||||
assert_equals(array[array.byteLength - 1], 0, `value of ${key}: last element of buffer`);
|
||||
break;
|
||||
case "table":
|
||||
assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype,
|
||||
`value of ${key}: prototype`);
|
||||
assert_equals(actual.length, expected.length, `value of ${key}: length of table`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
globalThis.assert_Instance = assert_Instance;
|
||||
|
||||
function assert_WebAssemblyInstantiatedSource(actual, expected_exports={}) {
|
||||
assert_equals(Object.getPrototypeOf(actual), Object.prototype,
|
||||
"Prototype");
|
||||
assert_true(Object.isExtensible(actual), "Extensibility");
|
||||
|
||||
const module = Object.getOwnPropertyDescriptor(actual, "module");
|
||||
assert_equals(typeof module, "object", "module: type of descriptor");
|
||||
assert_true(module.writable, "module: writable");
|
||||
assert_true(module.enumerable, "module: enumerable");
|
||||
assert_true(module.configurable, "module: configurable");
|
||||
assert_equals(Object.getPrototypeOf(module.value), WebAssembly.Module.prototype,
|
||||
"module: prototype");
|
||||
|
||||
const instance = Object.getOwnPropertyDescriptor(actual, "instance");
|
||||
assert_equals(typeof instance, "object", "instance: type of descriptor");
|
||||
assert_true(instance.writable, "instance: writable");
|
||||
assert_true(instance.enumerable, "instance: enumerable");
|
||||
assert_true(instance.configurable, "instance: configurable");
|
||||
assert_Instance(instance.value, expected_exports);
|
||||
}
|
||||
globalThis.assert_WebAssemblyInstantiatedSource = assert_WebAssemblyInstantiatedSource;
|
|
@ -0,0 +1,40 @@
|
|||
function assert_ArrayBuffer(actual, { size=0, shared=false, detached=false }, message) {
|
||||
// https://github.com/WebAssembly/spec/issues/840
|
||||
// See https://github.com/whatwg/html/issues/5380 for why not `self.SharedArrayBuffer`
|
||||
const isShared = !("isView" in actual.constructor);
|
||||
assert_equals(isShared, shared, `${message}: constructor`);
|
||||
const sharedString = shared ? "Shared" : "";
|
||||
assert_equals(actual.toString(), `[object ${sharedString}ArrayBuffer]`, `${message}: toString()`);
|
||||
assert_equals(Object.getPrototypeOf(actual).toString(), `[object ${sharedString}ArrayBuffer]`, `${message}: prototype toString()`);
|
||||
if (detached) {
|
||||
// https://github.com/tc39/ecma262/issues/678
|
||||
let byteLength;
|
||||
try {
|
||||
byteLength = actual.byteLength;
|
||||
} catch (e) {
|
||||
byteLength = 0;
|
||||
}
|
||||
assert_equals(byteLength, 0, `${message}: detached size`);
|
||||
} else {
|
||||
assert_equals(actual.byteLength, 0x10000 * size, `${message}: size`);
|
||||
if (size > 0) {
|
||||
const array = new Uint8Array(actual);
|
||||
assert_equals(array[0], 0, `${message}: first element`);
|
||||
assert_equals(array[array.byteLength - 1], 0, `${message}: last element`);
|
||||
}
|
||||
}
|
||||
assert_equals(Object.isFrozen(actual), shared, "buffer frozen");
|
||||
assert_equals(Object.isExtensible(actual), !shared, "buffer extensibility");
|
||||
}
|
||||
globalThis.assert_ArrayBuffer = assert_ArrayBuffer;
|
||||
|
||||
function assert_Memory(memory, { size=0, shared=false }) {
|
||||
assert_equals(Object.getPrototypeOf(memory), WebAssembly.Memory.prototype,
|
||||
"prototype");
|
||||
assert_true(Object.isExtensible(memory), "extensible");
|
||||
|
||||
// https://github.com/WebAssembly/spec/issues/840
|
||||
assert_equals(memory.buffer, memory.buffer, "buffer should be idempotent");
|
||||
assert_ArrayBuffer(memory.buffer, { size, shared });
|
||||
}
|
||||
globalThis.assert_Memory = assert_Memory;
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
|
||||
<script>
|
||||
self.GLOBAL = {
|
||||
isWindow: function() { return true; },
|
||||
isWorker: function() { return false; },
|
||||
isShadowRealm: function() { return false; },
|
||||
};
|
||||
</script>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../wasm/jsapi/assertions.js"></script>
|
||||
<script src="../../../wasm/jsapi/memory/assertions.js"></script>
|
||||
<div id=log></div>
|
||||
<script src="../../../wasm/jsapi/memory/constructor-shared.tentative.any.js"></script>
|
|
@ -0,0 +1,54 @@
|
|||
// META: global=window,dedicatedworker,jsshell,shadowrealm
|
||||
// META: script=/wasm/jsapi/assertions.js
|
||||
// META: script=/wasm/jsapi/memory/assertions.js
|
||||
|
||||
test(() => {
|
||||
assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": 10, "shared": true }));
|
||||
}, "Shared memory without maximum");
|
||||
|
||||
test(t => {
|
||||
const order = [];
|
||||
|
||||
new WebAssembly.Memory({
|
||||
get maximum() {
|
||||
order.push("maximum");
|
||||
return {
|
||||
valueOf() {
|
||||
order.push("maximum valueOf");
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
get initial() {
|
||||
order.push("initial");
|
||||
return {
|
||||
valueOf() {
|
||||
order.push("initial valueOf");
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
get shared() {
|
||||
order.push("shared");
|
||||
return {
|
||||
valueOf: t.unreached_func("should not call shared valueOf"),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert_array_equals(order, [
|
||||
"initial",
|
||||
"initial valueOf",
|
||||
"maximum",
|
||||
"maximum valueOf",
|
||||
"shared",
|
||||
]);
|
||||
}, "Order of evaluation for descriptor (with shared)");
|
||||
|
||||
test(() => {
|
||||
const argument = { "initial": 4, "maximum": 10, shared: true };
|
||||
const memory = new WebAssembly.Memory(argument);
|
||||
assert_Memory(memory, { "size": 4, "shared": true });
|
||||
}, "Shared memory");
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
|
||||
<script>
|
||||
self.GLOBAL = {
|
||||
isWindow: function() { return true; },
|
||||
isWorker: function() { return false; },
|
||||
isShadowRealm: function() { return false; },
|
||||
};
|
||||
</script>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../wasm/jsapi/memory/assertions.js"></script>
|
||||
<div id=log></div>
|
||||
<script src="../../../wasm/jsapi/memory/grow.any.js"></script>
|
189
Tests/LibWeb/Text/input/wpt-import/wasm/jsapi/memory/grow.any.js
Normal file
189
Tests/LibWeb/Text/input/wpt-import/wasm/jsapi/memory/grow.any.js
Normal file
|
@ -0,0 +1,189 @@
|
|||
// META: global=window,dedicatedworker,jsshell,shadowrealm
|
||||
// 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");
|
||||
|
||||
test(() => {
|
||||
const argument = { "initial": 1, "maximum": 2, "shared": true };
|
||||
const memory = new WebAssembly.Memory(argument);
|
||||
const oldMemory = memory.buffer;
|
||||
assert_ArrayBuffer(oldMemory, { "size": 1, "shared": true }, "Buffer before growing");
|
||||
|
||||
const result = memory.grow(1);
|
||||
assert_equals(result, 1);
|
||||
|
||||
const newMemory = memory.buffer;
|
||||
assert_not_equals(oldMemory, newMemory);
|
||||
assert_ArrayBuffer(oldMemory, { "size": 1, "shared": true }, "Old buffer after growing");
|
||||
assert_ArrayBuffer(newMemory, { "size": 2, "shared": true }, "New buffer after growing");
|
||||
|
||||
// The old and new buffers must have the same value for the
|
||||
// [[ArrayBufferData]] internal slot.
|
||||
const oldArray = new Uint8Array(oldMemory);
|
||||
const newArray = new Uint8Array(newMemory);
|
||||
assert_equals(oldArray[0], 0, "old first element");
|
||||
assert_equals(newArray[0], 0, "new first element");
|
||||
oldArray[0] = 1;
|
||||
assert_equals(oldArray[0], 1, "old first element");
|
||||
assert_equals(newArray[0], 1, "new first element");
|
||||
|
||||
}, "Growing shared memory does not detach old buffer");
|
Loading…
Reference in a new issue