From e157117998bf12869d8d57427388fc398731255f Mon Sep 17 00:00:00 2001 From: Asumu Takikawa Date: Tue, 19 Sep 2023 11:39:55 -0700 Subject: [PATCH] [test] Add JS API tests for i31ref Also updates wasm-module-builder for necessary changes, such as support for global init expressions with GC operations. These changes are imported from the upstream V8 file. This module builder change also requires changes in a few other test files that used the old init API. --- test/js-api/gc/i31.tentative.any.js | 98 +++++++++++++++++++++++++++++ test/js-api/instanceTestFactory.js | 6 +- test/js-api/module/exports.any.js | 6 +- test/js-api/wasm-module-builder.js | 95 +++++++++++++++------------- 4 files changed, 157 insertions(+), 48 deletions(-) create mode 100644 test/js-api/gc/i31.tentative.any.js diff --git a/test/js-api/gc/i31.tentative.any.js b/test/js-api/gc/i31.tentative.any.js new file mode 100644 index 0000000000..17fd82440c --- /dev/null +++ b/test/js-api/gc/i31.tentative.any.js @@ -0,0 +1,98 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const i31Ref = wasmRefType(kWasmI31Ref); + const i31NullableRef = wasmRefNullType(kWasmI31Ref); + const anyRef = wasmRefType(kWasmAnyRef); + + builder + .addFunction("makeI31", makeSig_r_x(i31Ref, kWasmI32)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31New)]) + .exportFunc(); + + builder + .addFunction("castI31", makeSig_r_x(kWasmI32, anyRef)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprRefCast), kI31RefCode, + ...GCInstr(kExprI31GetU)]) + .exportFunc(); + + builder + .addFunction("getI31", makeSig_r_x(kWasmI32, i31Ref)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31GetS)]) + .exportFunc(); + + builder + .addFunction("argI31", makeSig_v_x(i31NullableRef)) + .addBody([]) + .exportFunc(); + + builder + .addGlobal(i31NullableRef, true, [...wasmI32Const(0), ...GCInstr(kExprI31New)]) + builder + .addExportOfKind("i31Global", kExternalGlobal, 0); + + builder + .addTable(i31NullableRef, 10) + builder + .addExportOfKind("i31Table", kExternalTable, 0); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + assert_equals(exports.makeI31(42), 42); + assert_equals(exports.makeI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.makeI31(2 ** 30), -(2 ** 30)); + assert_equals(exports.makeI31(-(2 ** 30)), -(2 ** 30)); + assert_equals(exports.makeI31(2 ** 31 - 1), -1); + assert_equals(exports.makeI31(2 ** 31), 0); +}, "i31ref conversion to Number"); + +test(() => { + assert_equals(exports.getI31(exports.makeI31(42)), 42); + assert_equals(exports.getI31(42), 42); + assert_equals(exports.getI31(2.0 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.getI31(-(2 ** 30)), -(2 ** 30)); +}, "Number conversion to i31ref"); + +test(() => { + exports.argI31(null); + assert_throws_js(TypeError, () => exports.argI31(2 ** 30)); + assert_throws_js(TypeError, () => exports.argI31(-(2 ** 30) - 1)); + assert_throws_js(TypeError, () => exports.argI31(2n)); + assert_throws_js(TypeError, () => exports.argI31(() => 3)); + assert_throws_js(TypeError, () => exports.argI31(exports.getI31)); +}, "Check i31ref argument type"); + +test(() => { + assert_equals(exports.castI31(42), 42); + assert_equals(exports.castI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 30); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(-(2 ** 30) - 1); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 32); }); +}, "Numbers in i31 range are i31ref, not hostref"); + +test(() => { + assert_equals(exports.i31Global.value, 0); + exports.i31Global.value = 42; + assert_throws_js(TypeError, () => exports.i31Global.value = 2 ** 30); + assert_throws_js(TypeError, () => exports.i31Global.value = -(2 ** 30) - 1); + assert_equals(exports.i31Global.value, 42); +}, "i31ref global"); + +test(() => { + assert_equals(exports.i31Table.get(0), null); + exports.i31Table.set(0, 42); + assert_throws_js(TypeError, () => exports.i31Table.set(0, 2 ** 30)); + assert_throws_js(TypeError, () => exports.i31Table.set(0, -(2 ** 30) - 1)); + assert_equals(exports.i31Table.get(0), 42); +}, "i31ref table"); diff --git a/test/js-api/instanceTestFactory.js b/test/js-api/instanceTestFactory.js index ac468947ec..7936810a52 100644 --- a/test/js-api/instanceTestFactory.js +++ b/test/js-api/instanceTestFactory.js @@ -237,7 +237,7 @@ const instanceTestFactory = [ builder.addGlobal(kWasmI32, true) .exportAs("") - .init = 7; + .init = wasmI32Const(7); const buffer = builder.toBuffer(); @@ -273,10 +273,10 @@ const instanceTestFactory = [ builder.addGlobal(kWasmI32, true) .exportAs("global") - .init = 7; + .init = wasmI32Const(7); builder.addGlobal(kWasmF64, true) .exportAs("global2") - .init = 1.2; + .init = wasmF64Const(1.2); builder.addMemory(4, 8, true); diff --git a/test/js-api/module/exports.any.js b/test/js-api/module/exports.any.js index 40a3935a4a..0d62725ae4 100644 --- a/test/js-api/module/exports.any.js +++ b/test/js-api/module/exports.any.js @@ -109,10 +109,10 @@ test(() => { builder.addGlobal(kWasmI32, true) .exportAs("global") - .init = 7; + .init = wasmI32Const(7); builder.addGlobal(kWasmF64, true) .exportAs("global2") - .init = 1.2; + .init = wasmF64Const(1.2); builder.addMemory(0, 256, true); @@ -167,7 +167,7 @@ test(() => { builder.addGlobal(kWasmI32, true) .exportAs("") - .init = 7; + .init = wasmI32Const(7); const buffer = builder.toBuffer() const module = new WebAssembly.Module(buffer); diff --git a/test/js-api/wasm-module-builder.js b/test/js-api/wasm-module-builder.js index 4349be2cc7..8c6519239b 100644 --- a/test/js-api/wasm-module-builder.js +++ b/test/js-api/wasm-module-builder.js @@ -650,6 +650,11 @@ class Binary { } } + emit_init_expr(expr) { + this.emit_bytes(expr); + this.emit_u8(kExprEnd); + } + emit_header() { this.emit_bytes([ kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3 @@ -740,11 +745,11 @@ class WasmFunctionBuilder { } class WasmGlobalBuilder { - constructor(module, type, mutable) { + constructor(module, type, mutable, init) { this.module = module; this.type = type; this.mutable = mutable; - this.init = 0; + this.init = init; } exportAs(name) { @@ -754,13 +759,24 @@ class WasmGlobalBuilder { } } +function checkExpr(expr) { + for (let b of expr) { + if (typeof b !== 'number' || (b & (~0xFF)) !== 0) { + throw new Error( + 'invalid body (entries must be 8 bit numbers): ' + expr); + } + } +} + class WasmTableBuilder { - constructor(module, type, initial_size, max_size) { + constructor(module, type, initial_size, max_size, init_expr) { this.module = module; this.type = type; this.initial_size = initial_size; this.has_max = max_size != undefined; this.max_size = max_size; + this.init_expr = init_expr; + this.has_init = init_expr !== undefined; } exportAs(name) { @@ -875,18 +891,44 @@ class WasmModuleBuilder { return this.types.length - 1; } - addGlobal(local_type, mutable) { - let glob = new WasmGlobalBuilder(this, local_type, mutable); + static defaultFor(type) { + switch (type) { + case kWasmI32: + return wasmI32Const(0); + case kWasmI64: + return wasmI64Const(0); + case kWasmF32: + return wasmF32Const(0.0); + case kWasmF64: + return wasmF64Const(0.0); + case kWasmS128: + return [kSimdPrefix, kExprS128Const, ...(new Array(16).fill(0))]; + default: + if ((typeof type) != 'number' && type.opcode != kWasmRefNull) { + throw new Error("Non-defaultable type"); + } + let heap_type = (typeof type) == 'number' ? type : type.heap_type; + return [kExprRefNull, ...wasmSignedLeb(heap_type, kMaxVarInt32Size)]; + } + } + + addGlobal(type, mutable, init) { + if (init === undefined) init = WasmModuleBuilder.defaultFor(type); + checkExpr(init); + let glob = new WasmGlobalBuilder(this, type, mutable, init); glob.index = this.globals.length + this.num_imported_globals; this.globals.push(glob); return glob; } - addTable(type, initial_size, max_size = undefined) { - if (type != kWasmExternRef && type != kWasmAnyFunc) { - throw new Error('Tables must be of type kWasmExternRef or kWasmAnyFunc'); + addTable(type, initial_size, max_size = undefined, init_expr = undefined) { + if (type == kWasmI32 || type == kWasmI64 || type == kWasmF32 || + type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) { + throw new Error('Tables must be of a reference type'); } - let table = new WasmTableBuilder(this, type, initial_size, max_size); + if (init_expr != undefined) checkExpr(init_expr); + let table = new WasmTableBuilder( + this, type, initial_size, max_size, init_expr); table.index = this.tables.length + this.num_imported_tables; this.tables.push(table); return table; @@ -1161,6 +1203,7 @@ class WasmModuleBuilder { section.emit_u8(table.has_max); section.emit_u32v(table.initial_size); if (table.has_max) section.emit_u32v(table.max_size); + if (table.has_init) section.emit_init_expr(table.init_expr); } }); } @@ -1191,39 +1234,7 @@ class WasmModuleBuilder { for (let global of wasm.globals) { section.emit_type(global.type); section.emit_u8(global.mutable); - if ((typeof global.init_index) == "undefined") { - // Emit a constant initializer. - switch (global.type) { - case kWasmI32: - section.emit_u8(kExprI32Const); - section.emit_u32v(global.init); - break; - case kWasmI64: - section.emit_u8(kExprI64Const); - section.emit_u64v(global.init); - break; - case kWasmF32: - section.emit_bytes(wasmF32Const(global.init)); - break; - case kWasmF64: - section.emit_bytes(wasmF64Const(global.init)); - break; - case kWasmAnyFunc: - case kWasmExternRef: - if (global.function_index !== undefined) { - section.emit_u8(kExprRefFunc); - section.emit_u32v(global.function_index); - } else { - section.emit_u8(kExprRefNull); - } - break; - } - } else { - // Emit a global-index initializer. - section.emit_u8(kExprGlobalGet); - section.emit_u32v(global.init_index); - } - section.emit_u8(kExprEnd); // end of init expression + section.emit_init_expr(global.init); } }); }