diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 5fe37d01..b0184d0e 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -795,6 +795,7 @@ The type() method steps are: enum TableKind { "externref", "anyfunc", + "funcref", // Note: More values may be added in future iterations, // e.g., typed function references, typed GC references }; @@ -848,7 +849,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address The algorithm ToTableKind(|t|) performs the following steps: -1. If |t| equals [=funcref=], return "{{TableKind/anyfunc}}". +1. If |t| equals [=funcref=], return "{{TableKind/funcref}}". 1. If |t| equals [=externref=], return "{{TableKind/externref}}". 1. Assert: This step is not reached. @@ -950,6 +951,7 @@ enum ValueType { "f32", "f64", "externref", + "funcref", "anyfunc", }; @@ -1002,6 +1004,7 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. If |s| equals "i64", return [=i64=]. 1. If |s| equals "f32", return [=f32=]. 1. If |s| equals "f64", return [=f64=]. + 1. If |s| equals "funcref", return [=funcref=]. 1. If |s| equals "anyfunc", return [=funcref=]. 1. If |s| equals "externref", return [=externref=]. 1. Assert: This step is not reached. @@ -1015,7 +1018,7 @@ The algorithm FromValueType(|s|) performs the following s 1. If |s| equals [=i64=], return "{{ValueType/i64}}". 1. If |s| equals [=f32=], return "{{ValueType/f32}}". 1. If |s| equals [=f64=], return "{{ValueType/f64}}". -1. If |s| equals [=funcref=], return "{{ValueType/anyfunc}}". +1. If |s| equals [=funcref=], return "{{ValueType/funcref}}". 1. If |s| equals [=externref=], return "{{ValueType/externref}}". 1. Assert: This step is not reached. diff --git a/proposals/js-types/Overview.md b/proposals/js-types/Overview.md index 28a040be..1c863b2c 100644 --- a/proposals/js-types/Overview.md +++ b/proposals/js-types/Overview.md @@ -32,7 +32,7 @@ All Wasm types can be defined by a simple grammar. This grammar could be mapped ```TypeScript type ValueType = "i32" | "i64" | "f32" | "f64" -type ElemType = "anyfunc" +type ElemType = "funcref" type GlobalType = {value: ValueType, mutable: boolean} type MemoryType = {limits: Limits} type TableType = {limits: Limits, element: ElemType} @@ -200,7 +200,7 @@ function print(...args) { for (let x of args) console.log(x + "\n") } -let table = new Table({element: "anyfunc", minimum: 10}); +let table = new Table({element: "funcref", minimum: 10}); let print_i32 = new WebAssembly.Function({parameters: ["i32"], results: []}, print); table.set(0, print_i32); diff --git a/test/js-api/global/constructor.any.js b/test/js-api/global/constructor.any.js index f536f5d7..4e123164 100644 --- a/test/js-api/global/constructor.any.js +++ b/test/js-api/global/constructor.any.js @@ -164,3 +164,15 @@ test(() => { const global = new WebAssembly.Global(argument, 0, {}); assert_Global(global, 0); }, "Stray argument"); + +test(() => { + const argument = { "value": "anyfunc" }; + const global = new WebAssembly.Global(argument); + assert_Global(global, null); +}, "funcref with default"); + +test(() => { + const argument = { "value": "funcref" }; + const global = new WebAssembly.Global(argument); + assert_Global(global, null); +}, "funcref with default"); diff --git a/test/js-api/global/type.any.js b/test/js-api/global/type.any.js new file mode 100644 index 00000000..68a51fba --- /dev/null +++ b/test/js-api/global/type.any.js @@ -0,0 +1,83 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const myglobal = new WebAssembly.Global(argument); + const globaltype = myglobal.type(); + + assert_equals(globaltype.value, argument.value); + assert_equals(globaltype.mutable, argument.mutable); +} + +test(() => { + assert_type({ "value": "i32", "mutable": true}); +}, "i32, mutable"); + +test(() => { + assert_type({ "value": "i32", "mutable": false}); +}, "i32, immutable"); + +test(() => { + assert_type({ "value": "i64", "mutable": true}); +}, "i64, mutable"); + +test(() => { + assert_type({ "value": "i64", "mutable": false}); +}, "i64, immutable"); + +test(() => { + assert_type({ "value": "f32", "mutable": true}); +}, "f32, mutable"); + +test(() => { + assert_type({ "value": "f32", "mutable": false}); +}, "f32, immutable"); + +test(() => { + assert_type({ "value": "f64", "mutable": true}); +}, "f64, mutable"); + +test(() => { + assert_type({ "value": "f64", "mutable": false}); +}, "f64, immutable"); + +test(() => { + assert_type({"value": "externref", "mutable": true}) +}, "externref, mutable") + +test(() => { + assert_type({"value": "externref", "mutable": false}) +}, "externref, immutable") + +test(() => { + const argument = {"value": "anyfunc", "mutable": true}; + const myglobal = new WebAssembly.Global(argument); + const globaltype = myglobal.type(); + + assert_equals(globaltype.value, "funcref"); + assert_equals(globaltype.mutable, argument.mutable); +}, "anyfunc, mutable") + +test(() => { + const argument = {"value": "anyfunc", "mutable": false}; + const myglobal = new WebAssembly.Global(argument); + const globaltype = myglobal.type(); + + assert_equals(globaltype.value, "funcref"); + assert_equals(globaltype.mutable, argument.mutable); +}, "anyfunc, immutable") + +test(() => { + assert_type({"value": "funcref", "mutable": true}) +}, "funcref, mutable") + +test(() => { + assert_type({"value": "funcref", "mutable": false}) +}, "funcref, immutable") + +test(() => { + const myglobal = new WebAssembly.Global({"value": "i32", "mutable": true}); + const propertyNames = Object.getOwnPropertyNames(myglobal.type()); + assert_equals(propertyNames[0], "mutable"); + assert_equals(propertyNames[1], "value"); +}, "key ordering"); diff --git a/test/js-api/module/exports.any.js b/test/js-api/module/exports.any.js index 60b2c04f..0a179319 100644 --- a/test/js-api/module/exports.any.js +++ b/test/js-api/module/exports.any.js @@ -21,6 +21,35 @@ function assert_ModuleExportDescriptor(export_, expected) { assert_true(kind.enumerable, "kind: enumerable"); assert_true(kind.configurable, "kind: configurable"); assert_equals(kind.value, expected.kind); + + if (expected.type) { + const type = Object.getOwnPropertyDescriptor(export_, 'type'); + assert_true(type.writable, 'type: writable'); + assert_true(type.enumerable, 'type: enumerable'); + assert_true(type.configurable, 'type: configurable'); + + if (expected.type.parameters !== undefined) { + assert_array_equals(type.value.parameters, expected.type.parameters); + } + if (expected.type.results !== undefined) { + assert_array_equals(type.value.results, expected.type.results); + } + if (expected.type.value !== undefined) { + assert_equals(type.value.value, expected.type.value); + } + if (expected.type.mutable !== undefined) { + assert_equals(type.value.mutable, expected.type.mutable); + } + if (expected.type.mimimum !== undefined) { + assert_equals(type.value.mimimum, expected.type.mimimum); + } + if (expected.type.maximum !== undefined) { + assert_equals(type.value.maximum, expected.type.maximum); + } + if (expected.type.element !== undefined) { + assert_equals(type.value.element, expected.type.element); + } + } } function assert_exports(exports, expected) { @@ -75,6 +104,12 @@ test(() => { } }, "Branding"); +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module); + assert_true(Array.isArray(exports)); +}, "Return type"); + test(() => { const module = new WebAssembly.Module(emptyModuleBinary); const exports = WebAssembly.Module.exports(module); @@ -83,7 +118,8 @@ test(() => { test(() => { const module = new WebAssembly.Module(emptyModuleBinary); - assert_not_equals(WebAssembly.Module.exports(module), WebAssembly.Module.exports(module)); + assert_not_equals( + WebAssembly.Module.exports(module), WebAssembly.Module.exports(module)); }, "Empty module: array caching"); test(() => { @@ -114,18 +150,112 @@ test(() => { const module = new WebAssembly.Module(buffer); const exports = WebAssembly.Module.exports(module); const expected = [ - { "kind": "function", "name": "fn" }, - { "kind": "function", "name": "fn2" }, - { "kind": "table", "name": "table" }, - { "kind": "global", "name": "global" }, - { "kind": "global", "name": "global2" }, - { "kind": "memory", "name": "memory" }, + { + 'kind': 'function', + 'name': 'fn', + 'type': {'parameters': [], 'results': []} + }, + { + 'kind': 'function', + 'name': 'fn2', + 'type': {'parameters': [], 'results': []} + }, + {'kind': 'table', 'name': 'table'}, + {'kind': 'global', 'name': 'global'}, + {'kind': 'global', 'name': 'global2'}, + {'kind': 'memory', 'name': 'memory'}, ]; assert_exports(exports, expected); }, "exports"); +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("", kSig_v_v) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + {'kind': 'function', 'name': '', 'type': {'parameters': [], 'results': []}}, + ]; + assert_exports(exports, expected); +}, "exports with empty name: function"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.setTableBounds(1); + builder.addExportOfKind("", kExternalTable, 0); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "table", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: table"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addGlobal(kWasmI32, true) + .exportAs("") + .init = 7; + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "global", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: global"); + test(() => { const module = new WebAssembly.Module(emptyModuleBinary); const exports = WebAssembly.Module.exports(module, {}); assert_exports(exports, []); }, "Stray argument"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_a_a) + .addBody([kExprLocalGet, 0]) + .exportFunc(); + + builder.addTable(kWasmAnyFunc, 10, 100); + builder.addExportOfKind("table", kExternalTable, 0); + + builder.addGlobal(kWasmAnyFunc, true) + .exportAs("global").function_index = 0; + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { + 'kind': 'function', + 'name': 'fn', + 'type': {'parameters': ['funcref'], 'results': ['funcref']} + }, + { + 'kind': 'table', + 'name': 'table', + 'type': {'minimum': 10, 'maximum': 100, 'element': 'funcref'} + }, + { + 'kind': 'global', + 'name': 'global', + 'type': {'value': 'funcref', 'mutable': true} + }, + ]; + assert_exports(exports, expected); +}, "exports with type funcref"); + diff --git a/test/js-api/module/imports.any.js b/test/js-api/module/imports.any.js index d6754c9e..0f0b2017 100644 --- a/test/js-api/module/imports.any.js +++ b/test/js-api/module/imports.any.js @@ -22,6 +22,34 @@ function assert_ModuleImportDescriptor(import_, expected) { assert_true(kind.enumerable, "kind: enumerable"); assert_true(kind.configurable, "kind: configurable"); assert_equals(kind.value, expected.kind); + + if (expected.type) { + const type = Object.getOwnPropertyDescriptor(import_, 'type'); + assert_true(type.writable, 'type: writable'); + assert_true(type.enumerable, 'type: enumerable'); + assert_true(type.configurable, 'type: configurable'); + if (expected.type.parameters) { + assert_array_equals(type.value.parameters, expected.type.parameters); + } + if (expected.type.results !== undefined) { + assert_array_equals(type.value.results, expected.type.results); + } + if (expected.type.value !== undefined) { + assert_equals(type.value.value, expected.type.value); + } + if (expected.type.mutable !== undefined) { + assert_equals(type.value.mutable, expected.type.mutable); + } + if (expected.type.mimimum !== undefined) { + assert_equals(type.value.mimimum, expected.type.mimimum); + } + if (expected.type.maximum !== undefined) { + assert_equals(type.value.maximum, expected.type.maximum); + } + if (expected.type.element !== undefined) { + assert_equals(type.value.element, expected.type.element); + } + } } function assert_imports(imports, expected) { @@ -110,16 +138,128 @@ test(() => { const module = new WebAssembly.Module(buffer); const imports = WebAssembly.Module.imports(module); const expected = [ - { "module": "module", "kind": "function", "name": "fn" }, - { "module": "module", "kind": "global", "name": "global" }, - { "module": "module", "kind": "memory", "name": "memory" }, - { "module": "module", "kind": "table", "name": "table" }, + { + 'module': 'module', + 'kind': 'function', + 'name': 'fn', + 'type': {'parameters': [], 'results': []} + }, + {'module': 'module', 'kind': 'global', 'name': 'global', 'value': 'i32'}, + {'module': 'module', 'kind': 'memory', 'name': 'memory'}, + {'module': 'module', 'kind': 'table', 'name': 'table'}, ]; assert_imports(imports, expected); }, "imports"); +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "fn", kSig_v_v); + builder.addImportedGlobal("", "global", kWasmI32); + builder.addImportedMemory("", "memory", 0, 128); + builder.addImportedTable("", "table", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { + 'module': '', + 'kind': 'function', + 'name': 'fn', + 'type': {'parameters': [], 'results': []} + }, + {'module': '', 'kind': 'global', 'name': 'global', 'value': 'i32'}, + {'module': '', 'kind': 'memory', 'name': 'memory'}, + {'module': '', 'kind': 'table', 'name': 'table'}, + ]; + assert_imports(imports, expected); +}, "imports with empty module name"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("a", "", kSig_v_v); + builder.addImportedGlobal("b", "", kWasmI32); + builder.addImportedMemory("c", "", 0, 128); + builder.addImportedTable("d", "", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { + 'module': 'a', + 'kind': 'function', + 'name': '', + 'type': {'parameters': [], 'results': []} + }, + {'module': 'b', 'kind': 'global', 'name': '', 'value': 'i32'}, + {'module': 'c', 'kind': 'memory', 'name': ''}, + {'module': 'd', 'kind': 'table', 'name': ''}, + ]; + assert_imports(imports, expected); +}, "imports with empty names"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "", kSig_v_v); + builder.addImportedGlobal("", "", kWasmI32); + builder.addImportedMemory("", "", 0, 128); + builder.addImportedTable("", "", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { + 'module': '', + 'kind': 'function', + 'name': '', + 'type': {'parameters': [], 'results': []} + }, + {'module': '', 'kind': 'global', 'name': '', 'value': 'i32'}, + {'module': '', 'kind': 'memory', 'name': ''}, + {'module': '', 'kind': 'table', 'name': ''}, + ]; + assert_imports(imports, expected); +}, "imports with empty module names and names"); + test(() => { const module = new WebAssembly.Module(emptyModuleBinary); const imports = WebAssembly.Module.imports(module, {}); assert_imports(imports, []); }, "Stray argument"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder.addImport("module", "fn", kSig_a_a); + builder.addImportedGlobal("module", "g", kWasmAnyFunc); + builder.addImportedTable("module", "t", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { + 'kind': 'function', + 'module': 'module', + 'name': 'fn', + 'type': {'parameters': ['funcref'], 'results': ['funcref']} + }, + { + 'module': 'module', + 'kind': 'global', + 'name': 'g', + 'type': {'value': 'funcref', 'mutable': false} + }, + { + 'module': 'module', + 'kind': 'table', + 'name': 't', + 'type': {'minimum': 0, 'maximum': 128, 'element': 'funcref'} + }, + ]; + assert_imports(imports, expected); +}, "imports with type funcref"); diff --git a/test/js-api/table/constructor.any.js b/test/js-api/table/constructor.any.js index e9e77a13..cfc73b2b 100644 --- a/test/js-api/table/constructor.any.js +++ b/test/js-api/table/constructor.any.js @@ -96,6 +96,12 @@ test(() => { assert_Table(table, { "length": 5 }); }, "Basic (non-zero)"); +test(() => { + const argument = { "element": "funcref", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 5 }); +}, "Basic with 'funcref'"); + test(() => { const argument = { "element": "anyfunc", "initial": 0 }; const table = new WebAssembly.Table(argument, {}); diff --git a/test/js-api/table/type.any.js b/test/js-api/table/type.any.js new file mode 100644 index 00000000..eedb80b5 --- /dev/null +++ b/test/js-api/table/type.any.js @@ -0,0 +1,42 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument, element) { + const mytable = new WebAssembly.Table(argument); + const tabletype = mytable.type() + assert_equals(tabletype.minimum, argument.minimum); + assert_equals(tabletype.maximum, argument.maximum); + assert_equals(tabletype.element, element); +} + +test(() => { + assert_type({ "minimum": 0, "element": "anyfunc"}, "funcref"); +}, "anyfunc, Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5, "element": "anyfunc" }, "funcref"); +}, "anyfunc, Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0, "element": "anyfunc" }, "funcref"); +}, "anyfunc, Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5, "element": "anyfunc" }, "funcref"); +}, "anyfunc, Non-zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "element": "funcref"}, "funcref"); +}, "funcref, Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5, "element": "funcref" }, "funcref"); +}, "funcref, Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0, "element": "funcref" }, "funcref"); +}, "funcref, Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5, "element": "funcref" }, "funcref"); +}, "funcref, Non-zero maximum");