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:
Konstantin Konstantin 2024-12-07 21:17:20 +01:00 committed by Ali Mohammad Pur
parent 6ec06a01a2
commit b03138cbff
Notes: github-actions[bot] 2024-12-08 21:11:34 +00:00
13 changed files with 516 additions and 16 deletions

View file

@ -27,6 +27,7 @@
#include <LibJS/Runtime/PrimitiveString.h> #include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/RegExpObject.h> #include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/Set.h> #include <LibJS/Runtime/Set.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/StringObject.h> #include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
@ -803,7 +804,7 @@ public:
if (bytes_or_error.is_error()) if (bytes_or_error.is_error())
return WebIDL::DataCloneError::create(*realm, "out of memory"_string); return WebIDL::DataCloneError::create(*realm, "out of memory"_string);
auto bytes = bytes_or_error.release_value(); 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()); bytes.span().copy_to(buffer->buffer().span());
value = buffer; value = buffer;
break; break;
@ -820,7 +821,7 @@ public:
return WebIDL::DataCloneError::create(*realm, "out of memory"_string); return WebIDL::DataCloneError::create(*realm, "out of memory"_string);
size_t max_byte_length = deserialize_primitive_type<size_t>(m_serialized, m_position); size_t max_byte_length = deserialize_primitive_type<size_t>(m_serialized, m_position);
auto bytes = bytes_or_error.release_value(); 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()); bytes.span().copy_to(buffer->buffer().span());
buffer->set_max_byte_length(max_byte_length); buffer->set_max_byte_length(max_byte_length);
value = buffer; value = buffer;

View file

@ -59,7 +59,7 @@ void Instance::initialize(JS::Realm& realm)
[&](Wasm::MemoryAddress const& address) { [&](Wasm::MemoryAddress const& address) {
Optional<GC::Ptr<Memory>> object = m_memory_instances.get(address); Optional<GC::Ptr<Memory>> object = m_memory_instances.get(address);
if (!object.has_value()) { 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); m_memory_instances.set(address, *object);
} }

View file

@ -6,6 +6,7 @@
*/ */
#include <LibJS/Runtime/Realm.h> #include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
#include <LibWasm/Types.h> #include <LibWasm/Types.h>
#include <LibWeb/Bindings/Intrinsics.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(); 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::Limits limits { descriptor.initial, move(descriptor.maximum) };
Wasm::MemoryType memory_type { move(limits) }; 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()) if (!address.has_value())
return vm.throw_completion<JS::TypeError>("Wasm Memory allocation failed"sv); 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] { cache.abstract_machine().store().get(*address)->successful_grow_hook = [memory_object] {
MUST(memory_object->reset_the_memory_buffer()); 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; return memory_object;
} }
Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address) Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
: Bindings::PlatformObject(realm) : Bindings::PlatformObject(realm)
, m_address(address) , m_address(address)
, m_shared(shared)
{ {
} }
@ -74,7 +84,9 @@ WebIDL::ExceptionOr<u32> Memory::grow(u32 delta)
return previous_size; 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() WebIDL::ExceptionOr<void> Memory::reset_the_memory_buffer()
{ {
if (!m_buffer) if (!m_buffer)
@ -83,10 +95,16 @@ WebIDL::ExceptionOr<void> Memory::reset_the_memory_buffer()
auto& vm = this->vm(); auto& vm = this->vm();
auto& realm = *vm.current_realm(); 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 = TRY(create_a_fixed_length_memory_buffer(vm, realm, m_address, m_shared));
m_buffer = buffer;
return {}; return {};
} }
@ -98,21 +116,41 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::buffer() const
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
if (!m_buffer) 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); return GC::Ref(*m_buffer);
} }
// https://webassembly.github.io/spec/js-api/#create-a-memory-buffer // https://webassembly.github.io/spec/js-api/#create-a-fixed-length-memory-buffer
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::create_a_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address) 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& context = Detail::get_cache(realm);
auto* memory = context.abstract_machine().store().get(address); auto* memory = context.abstract_machine().store().get(address);
if (!memory) if (!memory)
return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv);
auto array_buffer = JS::ArrayBuffer::create(realm, &memory->data()); JS::ArrayBuffer* array_buffer;
array_buffer->set_detach_key(JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)); // 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); return GC::Ref(*array_buffer);
} }

View file

@ -20,12 +20,18 @@ namespace Web::WebAssembly {
struct MemoryDescriptor { struct MemoryDescriptor {
u32 initial { 0 }; u32 initial { 0 };
Optional<u32> maximum; Optional<u32> maximum;
Optional<bool> shared;
}; };
class Memory : public Bindings::PlatformObject { class Memory : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Memory, Bindings::PlatformObject); WEB_PLATFORM_OBJECT(Memory, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Memory); GC_DECLARE_ALLOCATOR(Memory);
enum class Shared {
No,
Yes,
};
public: public:
static WebIDL::ExceptionOr<GC::Ref<Memory>> construct_impl(JS::Realm&, MemoryDescriptor& descriptor); static WebIDL::ExceptionOr<GC::Ref<Memory>> construct_impl(JS::Realm&, MemoryDescriptor& descriptor);
@ -35,15 +41,16 @@ public:
Wasm::MemoryAddress address() const { return m_address; } Wasm::MemoryAddress address() const { return m_address; }
private: private:
Memory(JS::Realm&, Wasm::MemoryAddress); Memory(JS::Realm&, Wasm::MemoryAddress, Shared shared);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
WebIDL::ExceptionOr<void> reset_the_memory_buffer(); 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; Wasm::MemoryAddress m_address;
Shared m_shared { Shared::No };
mutable GC::Ptr<JS::ArrayBuffer> m_buffer; mutable GC::Ptr<JS::ArrayBuffer> m_buffer;
}; };

View file

@ -1,6 +1,8 @@
dictionary MemoryDescriptor { dictionary MemoryDescriptor {
required [EnforceRange] unsigned long initial; required [EnforceRange] unsigned long initial;
[EnforceRange] unsigned long maximum; [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 // https://webassembly.github.io/spec/js-api/#memories

View file

@ -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

View file

@ -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

View 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;

View file

@ -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;

View file

@ -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>

View file

@ -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");

View file

@ -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>

View 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");