mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-16 23:39:44 +00: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: b03138cbff
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
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…
Add table
Add a link
Reference in a new issue