Bug 1825098: Implement the JS API for the GC proposal. r=rhunt

Differential Revision: https://phabricator.services.mozilla.com/D178980
This commit is contained in:
Ben Visness 2023-06-12 15:39:49 +00:00
Родитель 41881e657a
Коммит ca4c9d5773
15 изменённых файлов: 320 добавлений и 372 удалений

Просмотреть файл

@ -485,6 +485,7 @@ MSG_DEF(JSMSG_WASM_WRONG_NUMBER_OF_VALUES, 2, JSEXN_TYPEERR, "wrong number of va
MSG_DEF(JSMSG_WASM_NONSHARED_WAIT, 0, JSEXN_WASMRUNTIMEERROR, "atomic wait on non-shared memory")
MSG_DEF(JSMSG_WASM_SUPPLY_ONLY_ONE, 2, JSEXN_TYPEERR, "exactly one of {0} and {1} must be supplied")
MSG_DEF(JSMSG_WASM_MISSING_REQUIRED, 1, JSEXN_TYPEERR, "Missing required argument {0}")
MSG_DEF(JSMSG_WASM_MODIFIED_GC_OBJECT, 0, JSEXN_TYPEERR, "can't modify WebAssembly GC objects")
// Proxy
MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")

Просмотреть файл

@ -74,6 +74,7 @@
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h"
#include "js/Conversions.h"
#include "js/Date.h"
#include "js/experimental/CodeCoverage.h" // js::GetCodeCoverageSummary
#include "js/experimental/CompileScript.h" // JS::ParseGlobalScript, JS::PrepareForInstantiate
@ -127,6 +128,7 @@
#include "vm/StringType.h"
#include "wasm/AsmJS.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmGcObject.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIntrinsic.h"
#include "wasm/WasmIonCompile.h"
@ -1997,6 +1999,58 @@ static bool WasmIntrinsicI8VecMul(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
#ifdef ENABLE_WASM_GC
static bool WasmGcReadField(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
if (!args.requireAtLeast(cx, "wasmGcReadField", 2)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<WasmGcObject>()) {
ReportUsageErrorASCII(cx, callee,
"First argument must be a WebAssembly GC object");
return false;
}
uint32_t fieldIndex;
if (!JS::ToUint32(cx, args[1], &fieldIndex)) {
ReportUsageErrorASCII(cx, callee, "Second argument must be an integer");
return false;
}
Rooted<WasmGcObject*> gcObject(cx, &args[0].toObject().as<WasmGcObject>());
Rooted<Value> gcValue(cx);
if (!WasmGcObject::loadValue(cx, gcObject, jsid::Int(int32_t(fieldIndex)),
&gcValue)) {
return false;
}
args.rval().set(gcValue);
return true;
}
static bool WasmGcArrayLength(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
if (!args.requireAtLeast(cx, "wasmGcArrayLength", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<WasmArrayObject>()) {
ReportUsageErrorASCII(cx, callee,
"First argument must be a WebAssembly GC array");
return false;
}
WasmArrayObject& arr = args[0].toObject().as<WasmArrayObject>();
args.rval().setInt32(int32_t(arr.numElements_));
return true;
}
#endif // ENABLE_WASM_GC
static bool LargeArrayBufferSupported(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(ArrayBufferObject::MaxByteLength >
@ -9183,6 +9237,16 @@ JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE)
"wasmIntrinsicI8VecMul()",
" Returns a module that implements an i8 vector pairwise multiplication intrinsic."),
#ifdef ENABLE_WASM_GC
JS_FN_HELP("wasmGcReadField", WasmGcReadField, 2, 0,
"wasmGcReadField(obj, index)",
" Gets a field of a WebAssembly GC struct or array."),
JS_FN_HELP("wasmGcArrayLength", WasmGcArrayLength, 1, 0,
"wasmGcArrayLength(arr)",
" Gets the length of a WebAssembly GC array."),
#endif // ENABLE_WASM_GC
JS_FN_HELP("largeArrayBufferSupported", LargeArrayBufferSupported, 0, 0,
"largeArrayBufferSupported()",
" Returns true if array buffers larger than 2GB can be allocated."),

Просмотреть файл

@ -1,6 +1,9 @@
// |jit-test| test-also=--wasm-compiler=optimizing; test-also=--wasm-function-references --wasm-gc; skip-if: !wasmDebuggingEnabled() || !wasmGcEnabled()
// |jit-test| test-also=--wasm-compiler=optimizing; test-also=--wasm-function-references --wasm-gc; skip-if: !wasmDebuggingEnabled() || !wasmGcEnabled(); skip-if: true
// An extension of wasm-10.js, testing that wasm GC objects are inspectable in locals.
// As of bug 1825098, this test is disabled. (skip-if: true)
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1836320
load(libdir + "wasm.js");
function monitorLocalValues(wast, lib, expected) {

Просмотреть файл

@ -1,7 +1,6 @@
// |jit-test| skip-if: !wasmGcEnabled()
// We can read the object fields from JS
// We can read the object fields from JS via a builtin
{
let ins = wasmEvalText(`(module
(type $p (struct (field f64) (field (mut i32))))
@ -10,13 +9,12 @@
(struct.new $p (f64.const 1.5) (i32.const 33))))`).exports;
let p = ins.mkp();
assertEq(p[0], 1.5);
assertEq(p[1], 33);
assertEq(p[2], undefined);
assertEq(wasmGcReadField(p, 0), 1.5);
assertEq(wasmGcReadField(p, 1), 33);
assertErrorMessage(() => wasmGcReadField(p, 2), WebAssembly.RuntimeError, /index out of bounds/);
}
// Writing an immutable field from JS throws.
// Fields can't be modified from JS.
{
let ins = wasmEvalText(`(module
(type $p (struct (field f64)))
@ -25,13 +23,10 @@
(struct.new $p (f64.const 1.5))))`).exports;
let p = ins.mkp();
assertErrorMessage(() => p[0] = 5.7,
Error,
/setting immutable field/);
assertErrorMessage(() => p[0] = 5.7, TypeError, /can't modify/);
}
// MVA v1 restriction: structs have no prototype
{
let ins = wasmEvalText(`(module
(type $q (struct (field (mut f64))))
@ -50,7 +45,6 @@
}
// MVA v1 restriction: all fields are immutable
{
let ins = wasmEvalText(`(module
(type $q (struct (field (mut f64))))
@ -63,24 +57,18 @@
(struct.new $p (ref.null $q) (ref.null eq))))`).exports;
let q = ins.mkq();
assertEq(typeof q, "object");
assertEq(q[0], 1.5);
assertEq(wasmGcReadField(q, 0), 1.5);
let p = ins.mkp();
assertEq(typeof p, "object");
assertEq(p[0], null);
assertEq(wasmGcReadField(p, 0), null);
assertErrorMessage(() => { p[0] = q },
Error,
/setting immutable field/);
assertErrorMessage(() => { p[1] = q },
Error,
/setting immutable field/);
assertErrorMessage(() => { p[0] = q }, TypeError, /can't modify/);
assertErrorMessage(() => { p[1] = q }, TypeError, /can't modify/);
}
// MVA v1 restriction: structs that expose i64 fields make those fields
// immutable from JS, and the structs are not constructible from JS.
{
let ins = wasmEvalText(`(module
(type $p (struct (field (mut i64))))
@ -89,16 +77,13 @@
let p = ins.mkp();
assertEq(typeof p, "object");
assertEq(p[0], 0x1234567887654321n)
assertEq(wasmGcReadField(p, 0), 0x1234567887654321n)
assertErrorMessage(() => { p[0] = 0 },
Error,
/setting immutable field/);
assertErrorMessage(() => { p[0] = 0 }, TypeError, /can't modify/);
}
// A consequence of the current mapping of i64 as two i32 fields is that we run
// a risk of struct.narrow not recognizing the difference. So check this.
{
let ins = wasmEvalText(
`(module

Просмотреть файл

@ -71,11 +71,11 @@ for (let [valtype, def, nondef] of GENERAL_TESTS) {
function checkArray(array, length, init, setval) {
// Check length
assertEq(len(array), length);
assertEq(array.length, length);
assertEq(wasmGcArrayLength(array), length);
// Check init value
for (let i = 0; i < length; i++) {
assertEq(array[i], init);
assertEq(wasmGcReadField(array, i), init);
assertEq(get(array, i), init);
}
@ -85,7 +85,7 @@ for (let [valtype, def, nondef] of GENERAL_TESTS) {
// Check there is no overwrite
for (let j = i + 1; j < length; j++) {
assertEq(array[j], init);
assertEq(wasmGcReadField(array, j), init);
assertEq(get(array, j), init);
}
}
@ -161,10 +161,10 @@ for (let [fieldtype, max] of [
assertEq(getU(a, 0), max);
// JS-API defaults to sign extension
assertEq(a[0], getS(a, 0));
assertEq(wasmGcReadField(a, 0), getS(a, 0));
// Check array.new truncates init value
assertEq(create(1, max + 1)[0], 0);
assertEq(wasmGcReadField(create(1, max + 1), 0), 0);
// Check array.set truncates
let b = create(1, 0);
@ -401,11 +401,11 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let a = newFixed();
assertEq(a.length, 4);
assertEq(a[0], 66);
assertEq(a[1], 77);
assertEq(a[2], 88);
assertEq(a[3], 99);
assertEq(wasmGcArrayLength(a), 4);
assertEq(wasmGcReadField(a, 0), 66);
assertEq(wasmGcReadField(a, 1), 77);
assertEq(wasmGcReadField(a, 2), 88);
assertEq(wasmGcReadField(a, 3), 99);
}
// run: resulting zero-element array is as expected
@ -417,7 +417,7 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let a = newFixed();
assertEq(a.length, 0);
assertEq(wasmGcArrayLength(a), 0);
}
// run: resulting 30-element array is as expected
@ -459,9 +459,9 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let a = newFixed();
assertEq(a.length, 30);
assertEq(wasmGcArrayLength(a), 30);
for (i = 0; i < 30; i++) {
assertEq(a[i], i + 1);
assertEq(wasmGcReadField(a, i), i + 1);
}
}
@ -576,7 +576,7 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let arr = newData();
assertEq(arr.length, 0);
assertEq(wasmGcArrayLength(arr), 0);
}
// run: range to copy would require OOB read on data segment
@ -608,11 +608,11 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let arr = newData();
assertEq(arr.length, 4);
assertEq(arr[0], 48+1);
assertEq(arr[1], 48+3);
assertEq(arr[2], 48+3);
assertEq(arr[3], 48+7);
assertEq(wasmGcArrayLength(arr), 4);
assertEq(wasmGcReadField(arr, 0), 48+1);
assertEq(wasmGcReadField(arr, 1), 48+3);
assertEq(wasmGcReadField(arr, 2), 48+3);
assertEq(wasmGcReadField(arr, 3), 48+7);
}
//////////////////////////////////////////////////////////////////////////////
@ -770,7 +770,7 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let arr = newElem();
assertEq(arr.length, 0);
assertEq(wasmGcArrayLength(arr), 0);
}
// run: range to copy would require OOB read on elem segment
@ -809,11 +809,11 @@ assertErrorMessage(() => wasmEvalText(`(module
)
)`).exports;
let arr = newElem();
assertEq(arr.length, 4);
assertEq(arr[0], f1);
assertEq(arr[1], f2);
assertEq(arr[2], f3);
assertEq(arr[3], f4);
assertEq(wasmGcArrayLength(arr), 4);
assertEq(wasmGcReadField(arr, 0), f1);
assertEq(wasmGcReadField(arr, 1), f2);
assertEq(wasmGcReadField(arr, 2), f3);
assertEq(wasmGcReadField(arr, 3), f4);
}
//////////////////////////////////////////////////////////////////////////////
@ -964,10 +964,16 @@ for (let [elemTy, valueTy, src, exp1, exp2] of ARRAY_COPY_TESTS) {
assertEq(exp2.length, 6);
function eqArrays(a1, a2) {
assertEq(a1.length, 6);
assertEq(a2.length, 6);
function len(arr) {
return Array.isArray(arr) ? arr.length : wasmGcArrayLength(arr);
}
function get(arr, i) {
return Array.isArray(arr) ? arr[i] : wasmGcReadField(arr, i);
}
assertEq(len(a1), 6);
assertEq(len(a2), 6);
for (i = 0; i < 6; i++) {
if (a1[i] !== a2[i])
if (get(a1, i) !== get(a2, i))
return false;
}
return true;

Просмотреть файл

@ -35,7 +35,7 @@ for (let v of WasmExternrefValues)
(func (export "make") (param $v externref) (result eqref)
(struct.new $S (local.get $v))))`);
let x = ins.exports.make(v);
assertEq(x[0], v);
assertEq(wasmGcReadField(x, 0), v);
}
// Try to make sure externrefs are properly traced
@ -51,24 +51,24 @@ for (let v of WasmExternrefValues)
let ins = wasmEvalText(txt);
let x = ins.exports.make({x:0}, {x:1}, {x:2}, {x:3}, {x:4}, {x:5}, {x:6}, {x:7}, {x:8}, {x:9})
gc('shrinking');
assertEq(typeof x[0], "object");
assertEq(x[0].x, 0);
assertEq(typeof x[1], "object");
assertEq(x[1].x, 1);
assertEq(typeof x[2], "object");
assertEq(x[2].x, 2);
assertEq(typeof x[3], "object");
assertEq(x[3].x, 3);
assertEq(typeof x[4], "object");
assertEq(x[4].x, 4);
assertEq(typeof x[5], "object");
assertEq(x[5].x, 5);
assertEq(typeof x[6], "object");
assertEq(x[6].x, 6);
assertEq(typeof x[7], "object");
assertEq(x[7].x, 7);
assertEq(typeof x[8], "object");
assertEq(x[8].x, 8);
assertEq(typeof x[9], "object");
assertEq(x[9].x, 9);
assertEq(typeof wasmGcReadField(x, 0), "object");
assertEq(wasmGcReadField(x, 0).x, 0);
assertEq(typeof wasmGcReadField(x, 1), "object");
assertEq(wasmGcReadField(x, 1).x, 1);
assertEq(typeof wasmGcReadField(x, 2), "object");
assertEq(wasmGcReadField(x, 2).x, 2);
assertEq(typeof wasmGcReadField(x, 3), "object");
assertEq(wasmGcReadField(x, 3).x, 3);
assertEq(typeof wasmGcReadField(x, 4), "object");
assertEq(wasmGcReadField(x, 4).x, 4);
assertEq(typeof wasmGcReadField(x, 5), "object");
assertEq(wasmGcReadField(x, 5).x, 5);
assertEq(typeof wasmGcReadField(x, 6), "object");
assertEq(wasmGcReadField(x, 6).x, 6);
assertEq(typeof wasmGcReadField(x, 7), "object");
assertEq(wasmGcReadField(x, 7).x, 7);
assertEq(typeof wasmGcReadField(x, 8), "object");
assertEq(wasmGcReadField(x, 8).x, 8);
assertEq(typeof wasmGcReadField(x, 9), "object");
assertEq(wasmGcReadField(x, 9).x, 9);
}

Просмотреть файл

@ -33,14 +33,14 @@ const { structNew, structNewDefault, structLarge } = wasmEvalText(`(module
let result;
result = structNew();
assertEq(result[0], 2);
assertEq(result[1], new Float32Array([3.140000104904175])[0]);
assertEq(wasmGcReadField(result, 0), 2);
assertEq(wasmGcReadField(result, 1), new Float32Array([3.140000104904175])[0]);
result = structNewDefault();
assertEq(result[0], 0);
assertEq(result[1], 0);
assertEq(wasmGcReadField(result, 0), 0);
assertEq(wasmGcReadField(result, 1), 0);
result = structLarge();
assertEq(result[2], 3n);
assertEq(result[19], 19);
assertEq(wasmGcReadField(result, 2), 3n);
assertEq(wasmGcReadField(result, 19), 19);
// array.new, array.new_default, and array.new_fixed
@ -62,14 +62,14 @@ const { arrayNew, arrayNewDefault, arrayNewFixed } = wasmEvalText(`(module
)`).exports;
result = arrayNew();
assertEq(result.length, 3);
assertEq(result[0], 3.14);
assertEq(result[2], 3.14);
assertEq(wasmGcArrayLength(result), 3);
assertEq(wasmGcReadField(result, 0), 3.14);
assertEq(wasmGcReadField(result, 2), 3.14);
result = arrayNewDefault();
assertEq(result.length, 2);
assertEq(result[1], 0);
assertEq(wasmGcArrayLength(result), 2);
assertEq(wasmGcReadField(result, 1), 0);
result = arrayNewFixed();
assertEq(result.length, 2);
assertEq(result[0][0], 10);
assertEq(result[0][1], 16);
assertEq(result[1], null);
assertEq(wasmGcArrayLength(result), 2);
assertEq(wasmGcReadField(wasmGcReadField(result, 0), 0), 10);
assertEq(wasmGcReadField(wasmGcReadField(result, 0), 1), 16);
assertEq(wasmGcReadField(result, 1), null);

Просмотреть файл

@ -14,13 +14,9 @@ let { newStruct } = wasmEvalText(`
test('(ref null 0)', [newStruct()], WasmNonAnyrefValues, '(type (struct))');
test('nullref', [null], WasmNonAnyrefValues);
function test(type, validValues, invalidValues, typeSection) {
function test(type, validValues, invalidValues, typeSection = "") {
const CheckError = /can only pass|bad type/;
if (!typeSection) {
typeSection = "";
}
// 1. Exported function params
let {a} = wasmEvalText(`(module
${typeSection}
@ -104,4 +100,26 @@ function test(type, validValues, invalidValues, typeSection) {
TypeError,
CheckError);
}
// 5. Verify that GC objects are opaque
for (const val of validValues) {
if (!val) continue;
assertEq(Reflect.getPrototypeOf(val), null);
assertEq(Reflect.setPrototypeOf(val, null), true);
assertEq(Reflect.setPrototypeOf(val, {}), false);
assertEq(Reflect.isExtensible(val), false);
assertEq(Reflect.preventExtensions(val), false);
assertEq(Reflect.getOwnPropertyDescriptor(val, "anything"), undefined);
assertEq(Reflect.defineProperty(val, "anything", { value: 42 }), false);
assertEq(Reflect.has(val, "anything"), false);
assertEq(Reflect.get(val, "anything"), undefined);
assertErrorMessage(() => { Reflect.set(val, "anything", 3); }, TypeError, /can't modify/);
assertErrorMessage(() => { Reflect.deleteProperty(val, "anything"); }, TypeError, /can't modify/);
assertEq(Reflect.ownKeys(val).length, 0, `gc objects should not have keys, but this one had: ${Reflect.ownKeys(val)}`);
for (const i in val) {
throw new Error(`GC objects should have no enumerable properties, but had ${i}`);
}
assertEq(val[Symbol.iterator], undefined, "GC objects should not be iterable");
}
}

Просмотреть файл

@ -210,11 +210,11 @@ assertEq(wasmEvalText(
let a = makeA();
let b = makeB();
assertEq(b[0], 0);
assertEq(b[1], 0);
assertEq(wasmGcReadField(b, 0), 0);
assertEq(wasmGcReadField(b, 1), 0);
let c = makeC();
assertEq(c[0], null);
assertEq(wasmGcReadField(c, 0), null);
}
// struct.new_default: valid if all struct fields are defaultable

Просмотреть файл

@ -202,29 +202,23 @@ assertEq(ins.x1(12), 36)
assertEq(ins.x2(8), Math.PI)
var point = ins.mk_point();
assertEq(0 in point, true);
assertEq(1 in point, true);
assertEq(2 in point, false);
assertEq(point[0], 37);
assertEq(point[1], 42);
assertEq(wasmGcReadField(point, 0), 37);
assertEq(wasmGcReadField(point, 1), 42);
var int_node = ins.mk_int_node(78, point);
assertEq(int_node[0], 78);
assertEq(int_node[1], point);
assertEq(wasmGcReadField(int_node, 0), 78);
assertEq(wasmGcReadField(int_node, 1), point);
var bigger = ins.mk_bigger();
for ( let i=0; i < 52; i++ )
assertEq(bigger[i], i);
assertEq(bigger[-1], undefined);
assertEq(bigger[52], undefined);
assertEq(wasmGcReadField(bigger, i), i);
var withfloats = ins.mk_withfloats(1/3, Math.PI, bigger, 5/6, 0x1337);
assertEq(withfloats[0], Math.fround(1/3));
assertEq(withfloats[1], Math.PI);
assertEq(withfloats[2], bigger);
assertEq(withfloats[3], Math.fround(5/6));
assertEq(withfloats[4], 0x1337);
assertEq(wasmGcReadField(withfloats, 0), Math.fround(1/3));
assertEq(wasmGcReadField(withfloats, 1), Math.PI);
assertEq(wasmGcReadField(withfloats, 2), bigger);
assertEq(wasmGcReadField(withfloats, 3), Math.fround(5/6));
assertEq(wasmGcReadField(withfloats, 4), 0x1337);
// A simple stress test
@ -244,8 +238,8 @@ var stressIns = new WebAssembly.Instance(new WebAssembly.Module(stress)).exports
var stressLevel = conf.x64 && !conf.tsan && !conf.asan && !conf.valgrind ? 100000 : 1000;
var the_list = stressIns.iota1(stressLevel);
for (let i=1; i <= stressLevel; i++) {
assertEq(the_list[0], i);
the_list = the_list[1];
assertEq(wasmGcReadField(the_list, 0), i);
the_list = wasmGcReadField(the_list, 1);
}
assertEq(the_list, null);
@ -289,19 +283,19 @@ assertEq(the_list, null);
let v = ins.mk();
assertEq(typeof v, "object");
assertEq(v[0], 0x7aaaaaaa);
assertEq(v[1], 0x4201020337n);
assertEq(wasmGcReadField(v, 0), 0x7aaaaaaa);
assertEq(wasmGcReadField(v, 1), 0x4201020337n);
assertEq(ins.low(v), 0x01020337);
assertEq(ins.high(v), 0x42);
assertEq(v[2], 0x6bbbbbbb);
assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
ins.set(v);
assertEq(v[0], 0x7aaaaaaa);
assertEq(v[1], 0x3333333376544567n);
assertEq(v[2], 0x6bbbbbbb);
assertEq(wasmGcReadField(v, 0), 0x7aaaaaaa);
assertEq(wasmGcReadField(v, 1), 0x3333333376544567n);
assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
ins.set2(v);
assertEq(v[1], 0x3141592653589793n);
assertEq(wasmGcReadField(v, 1), 0x3141592653589793n);
assertEq(ins.low(v), 0x53589793);
assertEq(ins.high(v), 0x31415926)
}
@ -351,28 +345,28 @@ assertEq(the_list, null);
let ins = wasmEvalText(txt).exports;
let v = ins.make();
assertEq(v[0], 0x7aaaaaaa);
assertEq(v[1], 0x4201020337n);
assertEq(v[2], 0x6bbbbbbb);
assertEq(wasmGcReadField(v, 0), 0x7aaaaaaa);
assertEq(wasmGcReadField(v, 1), 0x4201020337n);
assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
ins.update0(0x45367101);
assertEq(v[0], 0x45367101);
assertEq(wasmGcReadField(v, 0), 0x45367101);
assertEq(ins.get0(), 0x45367101);
assertEq(v[1], 0x4201020337n);
assertEq(v[2], 0x6bbbbbbb);
assertEq(wasmGcReadField(v, 1), 0x4201020337n);
assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
ins.update2(0x62345123);
assertEq(v[0], 0x45367101);
assertEq(v[1], 0x4201020337n);
assertEq(wasmGcReadField(v, 0), 0x45367101);
assertEq(wasmGcReadField(v, 1), 0x4201020337n);
assertEq(ins.get2(), 0x62345123);
assertEq(v[2], 0x62345123);
assertEq(wasmGcReadField(v, 2), 0x62345123);
ins.update1(0x77777777, 0x22222222);
assertEq(v[0], 0x45367101);
assertEq(wasmGcReadField(v, 0), 0x45367101);
assertEq(ins.get1_low(), 0x22222222);
assertEq(ins.get1_high(), 0x77777777);
assertEq(v[1], 0x7777777722222222n);
assertEq(v[2], 0x62345123);
assertEq(wasmGcReadField(v, 1), 0x7777777722222222n);
assertEq(wasmGcReadField(v, 2), 0x62345123);
}
@ -600,12 +594,8 @@ WebAssembly.CompileError, /signature index references non-signature/);
(func (export "make") (result eqref)
(struct.new $s (i32.const 37) (i64.const 42))))`).exports;
let v = ins.make();
assertErrorMessage(() => v[0] = 12,
Error,
/setting immutable field/);
assertErrorMessage(() => v[1] = 12,
Error,
/setting immutable field/);
assertErrorMessage(() => v[0] = 12, TypeError, /can't modify/);
assertErrorMessage(() => v[1] = 12, TypeError, /can't modify/);
}
// Function should not reference struct type: binary test

Просмотреть файл

@ -61,7 +61,7 @@
// writes are too large, but those will only be visible if we're running
// on ASan or Valgrind. In any case, add a fake data dependency below, so
// that the construction of the object can't (so easily) be optimised away.
assertEq(obj8[0] + BigInt(obj8[31]), 0x12ACn); // == 0x1234 + 0x78
assertEq(wasmGcReadField(obj8, 0) + BigInt(wasmGcReadField(obj8, 31)), 0x12ACn); // == 0x1234 + 0x78
}
// And exactly the same, except for 16 bit fields.
@ -97,7 +97,7 @@
)`;
let exports = wasmEvalText(txt).exports;
let obj16 = exports.build16(0x4321n, 0x7865);
assertEq(obj16[0] + BigInt(obj16[23]), 0xBB86n); // == 0x4321 + 0x7865
assertEq(wasmGcReadField(obj16, 0) + BigInt(wasmGcReadField(obj16, 23)), 0xBB86n); // == 0x4321 + 0x7865
}
// Test that 8-bit field writes do not overwrite adjacent fields.
@ -122,14 +122,14 @@
let exports = wasmEvalText(txt).exports;
let theObject = exports.create();
exports.writeField8x8_3(theObject, 0x77);
assertEq(theObject[0], 0x55);
assertEq(theObject[1], 0x55);
assertEq(theObject[2], 0x55);
assertEq(theObject[3], 0x77);
assertEq(theObject[4], 0x55);
assertEq(theObject[5], 0x55);
assertEq(theObject[6], 0x55);
assertEq(theObject[7], 0x55);
assertEq(wasmGcReadField(theObject, 0), 0x55);
assertEq(wasmGcReadField(theObject, 1), 0x55);
assertEq(wasmGcReadField(theObject, 2), 0x55);
assertEq(wasmGcReadField(theObject, 3), 0x77);
assertEq(wasmGcReadField(theObject, 4), 0x55);
assertEq(wasmGcReadField(theObject, 5), 0x55);
assertEq(wasmGcReadField(theObject, 6), 0x55);
assertEq(wasmGcReadField(theObject, 7), 0x55);
}
// Test that 16-bit field writes do not overwrite adjacent fields.
@ -154,14 +154,14 @@
let exports = wasmEvalText(txt).exports;
let theObject = exports.create();
exports.writeField16x8_3(theObject, 0x7766);
assertEq(theObject[0], 0x5555);
assertEq(theObject[1], 0x5555);
assertEq(theObject[2], 0x5555);
assertEq(theObject[3], 0x7766);
assertEq(theObject[4], 0x5555);
assertEq(theObject[5], 0x5555);
assertEq(theObject[6], 0x5555);
assertEq(theObject[7], 0x5555);
assertEq(wasmGcReadField(theObject, 0), 0x5555);
assertEq(wasmGcReadField(theObject, 1), 0x5555);
assertEq(wasmGcReadField(theObject, 2), 0x5555);
assertEq(wasmGcReadField(theObject, 3), 0x7766);
assertEq(wasmGcReadField(theObject, 4), 0x5555);
assertEq(wasmGcReadField(theObject, 5), 0x5555);
assertEq(wasmGcReadField(theObject, 6), 0x5555);
assertEq(wasmGcReadField(theObject, 7), 0x5555);
}
// Test that 8-bit field reads sign/zero extend correctly.

Просмотреть файл

@ -1939,13 +1939,10 @@ bool js::SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto,
}
/*
* Disallow mutating the [[Prototype]] on Typed Objects, per the spec.
* Disallow mutating the [[Prototype]] on WebAssembly GC objects.
*/
if (obj->is<WasmGcObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANT_SET_PROTO_OF,
"incompatible WebAssembly object");
return false;
return result.fail(JSMSG_CANT_SET_PROTO);
}
/* ES6 9.1.2 step 5 forbids changing [[Prototype]] if not [[Extensible]]. */
@ -1998,6 +1995,10 @@ bool js::PreventExtensions(JSContext* cx, HandleObject obj,
return js::Proxy::preventExtensions(cx, obj, result);
}
if (obj->is<WasmGcObject>()) {
return result.failCantPreventExtensions();
}
if (!obj->nonProxyIsExtensible()) {
// If the following assertion fails, there's somewhere else a missing
// call to shrinkCapacityToInitializedLength() which needs to be found

Просмотреть файл

@ -160,62 +160,6 @@ using namespace wasm;
//=========================================================================
// WasmGcObject
bool WasmGcObject::lookupProperty(JSContext* cx, Handle<WasmGcObject*> object,
jsid id, PropOffset* offset,
FieldType* type) {
switch (kind()) {
case wasm::TypeDefKind::Struct: {
const auto& structType = typeDef().structType();
uint32_t index;
if (!IdIsIndex(id, &index)) {
return false;
}
if (index >= structType.fields_.length()) {
return false;
}
const StructField& field = structType.fields_[index];
offset->set(field.offset);
*type = field.type;
return true;
}
case wasm::TypeDefKind::Array: {
const auto& arrayType = typeDef().arrayType();
// Special case for property 'length' that loads the length field at the
// beginning of the data buffer
if (id.isString() &&
id.toString() == cx->runtime()->commonNames->length) {
STATIC_ASSERT_WASMARRAYELEMENTS_NUMELEMENTS_IS_U32;
*type = FieldType::I32;
offset->set(UINT32_MAX);
return true;
}
// Normal case of indexed properties for loading array elements
uint32_t index;
if (!IdIsIndex(id, &index)) {
return false;
}
uint32_t numElements = object->as<WasmArrayObject>().numElements_;
if (index >= numElements) {
return false;
}
uint64_t scaledIndex =
uint64_t(index) * uint64_t(arrayType.elementType_.size());
if (scaledIndex >= uint64_t(UINT32_MAX)) {
// It's unrepresentable as an WasmGcObject::PropOffset. Give up.
return false;
}
offset->set(uint32_t(scaledIndex));
*type = arrayType.elementType_;
return true;
}
default:
MOZ_ASSERT_UNREACHABLE();
return false;
}
}
const ObjectOps WasmGcObject::objectOps_ = {
WasmGcObject::obj_lookupProperty, // lookupProperty
WasmGcObject::obj_defineProperty, // defineProperty
@ -232,122 +176,52 @@ const ObjectOps WasmGcObject::objectOps_ = {
bool WasmGcObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
HandleId id, MutableHandleObject objp,
PropertyResult* propp) {
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
if (typedObj->hasProperty(cx, typedObj, id)) {
propp->setWasmGcProperty();
objp.set(obj);
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
objp.set(nullptr);
propp->setNotFound();
return true;
}
return LookupProperty(cx, proto, id, objp, propp);
objp.set(nullptr);
propp->setNotFound();
return true;
}
bool WasmGcObject::obj_defineProperty(JSContext* cx, HandleObject obj,
HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_OBJECT_NOT_EXTENSIBLE, "WasmGcObject");
return false;
result.failReadOnly();
return true;
}
bool WasmGcObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id,
bool* foundp) {
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
if (typedObj->hasProperty(cx, typedObj, id)) {
*foundp = true;
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
*foundp = false;
return true;
}
return HasProperty(cx, proto, id, foundp);
*foundp = false;
return true;
}
bool WasmGcObject::obj_getProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
WasmGcObject::PropOffset offset;
FieldType type;
if (typedObj->lookupProperty(cx, typedObj, id, &offset, &type)) {
return typedObj->loadValue(cx, offset, type, vp);
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
vp.setUndefined();
return true;
}
return GetProperty(cx, proto, receiver, id, vp);
vp.setUndefined();
return true;
}
bool WasmGcObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id,
HandleValue v, HandleValue receiver,
ObjectOpResult& result) {
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
if (typedObj->hasProperty(cx, typedObj, id)) {
if (!receiver.isObject() || obj != &receiver.toObject()) {
return SetPropertyByDefining(cx, id, v, receiver, result);
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPEDOBJECT_SETTING_IMMUTABLE);
return false;
}
return SetPropertyOnProto(cx, obj, id, v, receiver, result);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MODIFIED_GC_OBJECT);
return false;
}
bool WasmGcObject::obj_getOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
WasmGcObject::PropOffset offset;
FieldType type;
if (typedObj->lookupProperty(cx, typedObj, id, &offset, &type)) {
RootedValue value(cx);
if (!typedObj->loadValue(cx, offset, type, &value)) {
return false;
}
desc.set(mozilla::Some(PropertyDescriptor::Data(
value,
{JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable})));
return true;
}
desc.reset();
return true;
}
bool WasmGcObject::obj_deleteProperty(JSContext* cx, HandleObject obj,
HandleId id, ObjectOpResult& result) {
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
if (typedObj->hasProperty(cx, typedObj, id)) {
return Throw(cx, id, JSMSG_CANT_DELETE);
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
return result.succeed();
}
return DeleteProperty(cx, proto, id, result);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MODIFIED_GC_OBJECT);
return false;
}
/* static */
@ -375,9 +249,61 @@ WasmGcObject* WasmGcObject::create(JSContext* cx,
return obj;
}
bool WasmGcObject::loadValue(JSContext* cx,
const WasmGcObject::PropOffset& offset,
FieldType type, MutableHandleValue vp) {
bool WasmGcObject::lookUpProperty(JSContext* cx, Handle<WasmGcObject*> obj,
jsid id, WasmGcObject::PropOffset* offset,
FieldType* type) {
switch (obj->kind()) {
case wasm::TypeDefKind::Struct: {
const auto& structType = obj->typeDef().structType();
uint32_t index;
if (!IdIsIndex(id, &index)) {
return false;
}
if (index >= structType.fields_.length()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS);
return false;
}
const StructField& field = structType.fields_[index];
offset->set(field.offset);
*type = field.type;
return true;
}
case wasm::TypeDefKind::Array: {
const auto& arrayType = obj->typeDef().arrayType();
uint32_t index;
if (!IdIsIndex(id, &index)) {
return false;
}
uint32_t numElements = obj->as<WasmArrayObject>().numElements_;
if (index >= numElements) {
return false;
}
uint64_t scaledIndex =
uint64_t(index) * uint64_t(arrayType.elementType_.size());
if (scaledIndex >= uint64_t(UINT32_MAX)) {
// It's unrepresentable as an WasmGcObject::PropOffset. Give up.
return false;
}
offset->set(uint32_t(scaledIndex));
*type = arrayType.elementType_;
return true;
}
default:
MOZ_ASSERT_UNREACHABLE();
return false;
}
}
bool WasmGcObject::loadValue(JSContext* cx, Handle<WasmGcObject*> obj, jsid id,
MutableHandleValue vp) {
WasmGcObject::PropOffset offset;
FieldType type;
if (!lookUpProperty(cx, obj, id, &offset, &type)) {
return false;
}
// Temporary hack, (ref T) is not exposable to JS yet but some tests would
// like to access it so we erase (ref T) with eqref when loading. This is
// safe as (ref T) <: eqref and we're not in the writing case where we
@ -392,10 +318,10 @@ bool WasmGcObject::loadValue(JSContext* cx,
return false;
}
if (is<WasmStructObject>()) {
if (obj->is<WasmStructObject>()) {
// `offset` is the field offset, without regard to the in/out-line split.
// That is handled by the call to `fieldOffsetToAddress`.
WasmStructObject& structObj = as<WasmStructObject>();
const WasmStructObject& structObj = obj->as<WasmStructObject>();
// Ensure no out-of-range access possible
MOZ_RELEASE_ASSERT(structObj.kind() == TypeDefKind::Struct);
MOZ_RELEASE_ASSERT(offset.get() + type.size() <=
@ -404,17 +330,8 @@ bool WasmGcObject::loadValue(JSContext* cx,
type, vp);
}
MOZ_ASSERT(is<WasmArrayObject>());
WasmArrayObject& arrayObj = as<WasmArrayObject>();
if (offset.get() == UINT32_MAX) {
// This denotes "length"
uint32_t numElements = arrayObj.numElements_;
// We can't use `ToJSValue(.., ValType::I32, ..)` here since it will
// treat the integer as signed, which it isn't. `vp.set(..)` will
// coerce correctly to a JS::Value, though.
vp.set(NumberValue(numElements));
return true;
}
MOZ_ASSERT(obj->is<WasmArrayObject>());
const WasmArrayObject& arrayObj = obj->as<WasmArrayObject>();
return ToJSValue(cx, arrayObj.data_ + offset.get(), type, vp);
}
@ -426,38 +343,6 @@ bool WasmGcObject::isRuntimeSubtypeOf(
bool WasmGcObject::obj_newEnumerate(JSContext* cx, HandleObject obj,
MutableHandleIdVector properties,
bool enumerableOnly) {
MOZ_ASSERT(obj->is<WasmGcObject>());
Rooted<WasmGcObject*> typedObj(cx, &obj->as<WasmGcObject>());
size_t indexCount = 0;
size_t otherCount = 0;
switch (typedObj->kind()) {
case wasm::TypeDefKind::Struct: {
indexCount = typedObj->typeDef().structType().fields_.length();
break;
}
case wasm::TypeDefKind::Array: {
indexCount = typedObj->as<WasmArrayObject>().numElements_;
otherCount = 1;
break;
}
default:
MOZ_ASSERT_UNREACHABLE();
}
if (!properties.reserve(indexCount + otherCount)) {
return false;
}
RootedId id(cx);
for (size_t index = 0; index < indexCount; index++) {
id = PropertyKey::Int(int32_t(index));
properties.infallibleAppend(id);
}
if (typedObj->kind() == wasm::TypeDefKind::Array) {
properties.infallibleAppend(NameToId(cx->runtime()->commonNames->length));
}
return true;
}

Просмотреть файл

@ -12,9 +12,9 @@
#include "gc/Allocator.h"
#include "gc/Pretenuring.h"
#include "vm/ArrayBufferObject.h"
#include "vm/JSObject.h"
#include "wasm/WasmInstanceData.h"
#include "wasm/WasmMemory.h"
#include "wasm/WasmTypeDef.h"
#include "wasm/WasmValType.h"
@ -86,20 +86,14 @@ class WasmGcObject : public JSObject {
void set(uint32_t u32) { u32_ = u32; }
};
[[nodiscard]] bool lookupProperty(JSContext* cx,
js::Handle<WasmGcObject*> object, jsid id,
PropOffset* offset, wasm::FieldType* type);
[[nodiscard]] bool hasProperty(JSContext* cx,
js::Handle<WasmGcObject*> object, jsid id) {
WasmGcObject::PropOffset offset;
wasm::FieldType type;
return lookupProperty(cx, object, id, &offset, &type);
}
bool loadValue(JSContext* cx, const WasmGcObject::PropOffset& offset,
wasm::FieldType type, MutableHandleValue vp);
[[nodiscard]] static bool lookUpProperty(JSContext* cx,
Handle<WasmGcObject*> obj, jsid id,
PropOffset* offset, FieldType* type);
public:
[[nodiscard]] static bool loadValue(JSContext* cx, Handle<WasmGcObject*> obj,
jsid id, MutableHandleValue vp);
const wasm::SuperTypeVector& superTypeVector() const {
return *superTypeVector_;
}
@ -289,7 +283,7 @@ class WasmStructObject : public WasmGcObject {
// Given the offset of a field, return its actual address. `fieldType` is
// for assertional purposes only.
inline uint8_t* fieldOffsetToAddress(FieldType fieldType,
uint32_t fieldOffset);
uint32_t fieldOffset) const;
// JIT accessors
static constexpr size_t offsetOfOutlineData() {
@ -351,8 +345,8 @@ inline void WasmStructObject::fieldOffsetToAreaAndOffset(FieldType fieldType,
((fieldOffset + fieldType.size() - 1) < WasmStructObject_MaxInlineBytes));
}
inline uint8_t* WasmStructObject::fieldOffsetToAddress(FieldType fieldType,
uint32_t fieldOffset) {
inline uint8_t* WasmStructObject::fieldOffsetToAddress(
FieldType fieldType, uint32_t fieldOffset) const {
bool areaIsOutline;
uint32_t areaOffset;
fieldOffsetToAreaAndOffset(fieldType, fieldOffset, &areaIsOutline,

Просмотреть файл

@ -1780,9 +1780,10 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports,
}
// Find the shape using the class and recursion group
const ObjectFlags objectFlags = {ObjectFlag::NotExtensible};
typeDefData->shape =
WasmGCShape::getShape(cx, clasp, cx->realm(), TaggedProto(),
&typeDef.recGroup(), ObjectFlags());
&typeDef.recGroup(), objectFlags);
if (!typeDefData->shape) {
return false;
}