From 663c8d532329e280916da789a28583bf93b2c71f Mon Sep 17 00:00:00 2001 From: Asumu Takikawa Date: Wed, 14 Feb 2024 11:59:12 -0800 Subject: [PATCH] [js-api][test] Refine DefaultValue for new reference types DefaultValue will return undefined for externref as before, but for other nullable reference types will return null. For non-nullable reference types, operations like grow and set should error if the value to set is missing. Also adds WPT tests for the error cases. This requires some updates to wasm-module-builder.js as well. Fixes issue #501 --- document/core/appendix/embedding.rst | 20 +++++++++ document/js-api/index.bs | 12 +++--- test/js-api/gc/default-value.tentative.any.js | 43 +++++++++++++++++++ test/js-api/gc/i31.tentative.any.js | 4 +- test/js-api/wasm-module-builder.js | 6 ++- 5 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 test/js-api/gc/default-value.tentative.any.js diff --git a/document/core/appendix/embedding.rst b/document/core/appendix/embedding.rst index c0a19791d..e7d60a1a4 100644 --- a/document/core/appendix/embedding.rst +++ b/document/core/appendix/embedding.rst @@ -693,3 +693,23 @@ Matching \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \TRUE && (\iff \vdashexterntypematch \X{et}_1 \matchesexterntype \X{et}_2) \\ \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \FALSE && (\otherwise) \\ \end{array} + + +.. index:: value type +.. _embed-default-value: + +Value types +~~~~~~~~~~~ + +:math:`\F{default\_value}(\type) : \val` +............................................... + +1. If :math:`\default_{type}` is not defined, then return :math:`\ERROR`. + +1. Else, return the :ref:`value ` :math:`\default_{type}`. + +.. math:: + \begin{array}{lclll} + \F{default\_value}(t) &=& v && (\iff \default_t = v) \\ + \F{default\_value}(t) &=& \ERROR && (\iff \default_t = \epsilon) \\ + \end{array} diff --git a/document/js-api/index.bs b/document/js-api/index.bs index d7657d9fe..9df91d001 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -116,6 +116,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: global_write; url: appendix/embedding.html#embed-global-write text: ref_type; url: appendix/embedding.html#embed-ref-type text: match_valtype; url: appendix/embedding.html#embed-match-valtype + text: default_value; url: appendix/embedding.html#embed-default-value text: error; url: appendix/embedding.html#embed-error text: store; url: exec/runtime.html#syntax-store text: table type; url: syntax/types.html#syntax-tabletype @@ -764,6 +765,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Assert: |ref| is not [=error=]. 1. Otherwise, 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). 1. Let |type| be the [=table type=] {[=table type|min=] |initial|, [=table type|max=] |maximum|} |elementType|. @@ -781,6 +783,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. If |ref| is [=error=], throw a {{TypeError}} exception. 1. Otherwise, 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). 1. Let |result| be [=table_grow=](|store|, |tableaddr|, |delta|, |ref|). @@ -814,6 +817,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. If |ref| is [=error=], throw a {{TypeError}} exception. 1. Otherwise, 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. @@ -890,13 +894,8 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each
The algorithm DefaultValue(|valuetype|) performs the following steps: - 1. If |valuetype| equals [=i32=], return [=i32.const=] 0. - 1. If |valuetype| equals [=i64=], return [=i64.const=] 0. - 1. If |valuetype| equals [=f32=], return [=f32.const=] 0. - 1. If |valuetype| equals [=f64=], return [=f64.const=] 0. - 1. If |valuetype| equals [=funcref=], return [=ref.null=] [=funcref=]. 1. If |valuetype| equals [=externref=], return [=ToWebAssemblyValue=](undefined, |valuetype|). - 1. Assert: This step is not reached. + 1. Return [=default_value=](|valuetype|).
@@ -907,6 +906,7 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. Throw a {{TypeError}} exception. 1. If |v| is missing, 1. Let |value| be [=DefaultValue=](|valuetype|). + 1. Assert: |value| is not [=error=]. 1. Otherwise, 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valuetype|). 1. If |mutable| is true, let |globaltype| be [=var=] |valuetype|; otherwise, let |globaltype| be [=const=] |valuetype|. diff --git a/test/js-api/gc/default-value.tentative.any.js b/test/js-api/gc/default-value.tentative.any.js new file mode 100644 index 000000000..d828b4d87 --- /dev/null +++ b/test/js-api/gc/default-value.tentative.any.js @@ -0,0 +1,43 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + builder.addTable(wasmRefType(kWasmAnyRef), 10, 20, [...wasmI32Const(42), ...GCInstr(kExprRefI31)]) + .exportAs("tableAnyNonNullable"); + builder.addTable(wasmRefNullType(kWasmAnyRef), 10, 20) + .exportAs("tableAnyNullable"); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + exports.tableAnyNullable.grow(5); + for (let i = 0; i < 5; i++) + assert_equals(exports.tableAnyNullable.get(10 + i), null); +}, "grow (nullable anyref)"); + +test(() => { + assert_throws_js(TypeError, () => { exports.tableAnyNonNullable.grow(5); }); + exports.tableAnyNonNullable.grow(5, "foo"); + for (let i = 0; i < 5; i++) + assert_equals(exports.tableAnyNonNullable.get(10 + i), "foo"); +}, "grow (non-nullable anyref)"); + +test(() => { + for (let i = 0; i < exports.tableAnyNullable.length; i++) { + exports.tableAnyNullable.set(i); + assert_equals(exports.tableAnyNullable.get(i), null); + } +}, "set (nullable anyref)"); + +test(() => { + for (let i = 0; i < exports.tableAnyNonNullable.length; i++) { + assert_throws_js(TypeError, () => { exports.tableAnyNonNullable.set(i); }); + } +}, "set (non-nullable anyref)"); diff --git a/test/js-api/gc/i31.tentative.any.js b/test/js-api/gc/i31.tentative.any.js index 17fd82440..d8cfe424e 100644 --- a/test/js-api/gc/i31.tentative.any.js +++ b/test/js-api/gc/i31.tentative.any.js @@ -11,7 +11,7 @@ setup(() => { builder .addFunction("makeI31", makeSig_r_x(i31Ref, kWasmI32)) .addBody([kExprLocalGet, 0, - ...GCInstr(kExprI31New)]) + ...GCInstr(kExprRefI31)]) .exportFunc(); builder @@ -33,7 +33,7 @@ setup(() => { .exportFunc(); builder - .addGlobal(i31NullableRef, true, [...wasmI32Const(0), ...GCInstr(kExprI31New)]) + .addGlobal(i31NullableRef, true, [...wasmI32Const(0), ...GCInstr(kExprRefI31)]) builder .addExportOfKind("i31Global", kExternalGlobal, 0); diff --git a/test/js-api/wasm-module-builder.js b/test/js-api/wasm-module-builder.js index 8c6519239..104c730c7 100644 --- a/test/js-api/wasm-module-builder.js +++ b/test/js-api/wasm-module-builder.js @@ -456,7 +456,7 @@ let kExprBrOnCast = 0x18; let kExprBrOnCastFail = 0x19; let kExprExternInternalize = 0x1a; let kExprExternExternalize = 0x1b; -let kExprI31New = 0x1c; +let kExprRefI31 = 0x1c; let kExprI31GetS = 0x1d; let kExprI31GetU = 0x1e; @@ -1199,6 +1199,10 @@ class WasmModuleBuilder { binary.emit_section(kTableSectionCode, section => { section.emit_u32v(wasm.tables.length); for (let table of wasm.tables) { + if (table.has_init) { + section.emit_u8(0x40); // "has initializer" + section.emit_u8(0x00); // Reserved byte. + } section.emit_type(table.type); section.emit_u8(table.has_max); section.emit_u32v(table.initial_size);