Skip to content

Commit

Permalink
Merge pull request WebAssembly#437 from takikawa/gc-js-api-i31-test
Browse files Browse the repository at this point in the history
[test] Add JS API tests for I31 refs
  • Loading branch information
rossberg authored Oct 10, 2023
2 parents 6becba4 + e157117 commit 10de3ad
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 48 deletions.
98 changes: 98 additions & 0 deletions test/js-api/gc/i31.tentative.any.js
Original file line number Diff line number Diff line change
@@ -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");
6 changes: 3 additions & 3 deletions test/js-api/instanceTestFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ const instanceTestFactory = [

builder.addGlobal(kWasmI32, true)
.exportAs("")
.init = 7;
.init = wasmI32Const(7);

const buffer = builder.toBuffer();

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

Expand Down
6 changes: 3 additions & 3 deletions test/js-api/module/exports.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
95 changes: 53 additions & 42 deletions test/js-api/wasm-module-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
});
}
Expand Down Expand Up @@ -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);
}
});
}
Expand Down

0 comments on commit 10de3ad

Please sign in to comment.