Skip to content

Commit

Permalink
[js-api][test] Refine DefaultValue for new reference types
Browse files Browse the repository at this point in the history
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 WebAssembly#501
  • Loading branch information
takikawa committed Feb 14, 2024
1 parent 6b9b1e2 commit 663c8d5
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 9 deletions.
20 changes: 20 additions & 0 deletions document/core/appendix/embedding.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <syntax-val>` :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}
12 changes: 6 additions & 6 deletions document/js-api/index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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| &lt; |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|.
Expand All @@ -781,6 +783,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address
1. Let (<var ignore>limits</var>, |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|).
Expand Down Expand Up @@ -814,6 +817,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address
1. Let (<var ignore>limits</var>, |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=].
Expand Down Expand Up @@ -890,13 +894,8 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each

<div algorithm>
The algorithm <dfn>DefaultValue</dfn>(|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|).
</div>

<div algorithm>
Expand All @@ -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|.
Expand Down
43 changes: 43 additions & 0 deletions test/js-api/gc/default-value.tentative.any.js
Original file line number Diff line number Diff line change
@@ -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)");
4 changes: 2 additions & 2 deletions test/js-api/gc/i31.tentative.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ setup(() => {
builder
.addFunction("makeI31", makeSig_r_x(i31Ref, kWasmI32))
.addBody([kExprLocalGet, 0,
...GCInstr(kExprI31New)])
...GCInstr(kExprRefI31)])
.exportFunc();

builder
Expand All @@ -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);

Expand Down
6 changes: 5 additions & 1 deletion test/js-api/wasm-module-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 663c8d5

Please sign in to comment.