// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flags: --experimental-wasm-gc d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); (function TestTables() { print(arguments.callee.name); var exporting_instance = (function() { var builder = new WasmModuleBuilder(); var binary_type = builder.addType(kSig_i_ii); builder.addFunction('addition', kSig_i_ii) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add]) .exportFunc(); builder.addFunction('succ', kSig_i_i) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add]) .exportFunc(); builder.addTable(wasmRefNullType(binary_type), 1, 100).exportAs('table'); return builder.instantiate({}); })(); // Wrong type for imported table. assertThrows(() => { var builder = new WasmModuleBuilder(); var unary_type = builder.addType(kSig_i_i); builder.addImportedTable( 'imports', 'table', 1, 100, wasmRefNullType(unary_type)); builder.instantiate({imports: {table: exporting_instance.exports.table}}) }, WebAssembly.LinkError, /imported table does not match the expected type/); // Type for imported table must match exactly. assertThrows(() => { var builder = new WasmModuleBuilder(); builder.addImportedTable('imports', 'table', 1, 100, kWasmFuncRef); builder.instantiate({imports: {table: exporting_instance.exports.table}}) }, WebAssembly.LinkError, /imported table does not match the expected type/); var instance = (function() { var builder = new WasmModuleBuilder(); var unary_type = builder.addType(kSig_i_i); var binary_type = builder.addType(kSig_i_ii); builder.addImportedTable( 'imports', 'table', 1, 100, wasmRefNullType(binary_type)); var table = builder.addTable(wasmRefNullType(unary_type), 10).exportAs('table'); builder.addTable(kWasmFuncRef, 1).exportAs('generic_table'); builder .addFunction( 'table_test', makeSig([wasmRefType(unary_type)], [kWasmI32])) // Set table[0] to input function, then retrieve it and call it. .addBody([ kExprI32Const, 0, kExprLocalGet, 0, kExprTableSet, table.index, kExprI32Const, 42, kExprI32Const, 0, kExprTableGet, table.index, kExprCallRef, unary_type, ]) .exportFunc(); // Same, but with table[1] and call_indirect builder .addFunction( 'table_indirect_test', makeSig([wasmRefType(unary_type)], [kWasmI32])) .addBody([ kExprI32Const, 1, kExprLocalGet, 0, kExprTableSet, table.index, kExprI32Const, 42, kExprI32Const, 0, kExprCallIndirect, unary_type, table.index ]) .exportFunc(); // Instantiate with a table of the correct type. return builder.instantiate( {imports: {table: exporting_instance.exports.table}}); })(); // This module is valid. assertTrue(!!instance); // The correct function reference is preserved when setting it to and getting // it back from a table. assertEquals( 43, instance.exports.table_test(exporting_instance.exports.succ)); // Same for call indirect (the indirect call tables are also set correctly). assertEquals( 43, instance.exports.table_indirect_test(exporting_instance.exports.succ)); // Setting from JS API respects types. instance.exports.generic_table.set(0, exporting_instance.exports.succ); instance.exports.table.set(0, exporting_instance.exports.succ); assertThrows( () => instance.exports.table.set(0, exporting_instance.exports.addition), TypeError, /Argument 1 is invalid for table: assigned exported function has to be a subtype of the expected type/); })(); (function TestNonNullableTables() { print(arguments.callee.name); var builder = new WasmModuleBuilder(); var binary_type = builder.addType(kSig_i_ii); var addition = builder.addFunction('addition', binary_type).addBody([ kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add ]); var subtraction = builder.addFunction('subtraction', binary_type) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub]) .exportFunc(); var table = builder.addTable(wasmRefType(binary_type), 3, 3, [kExprRefFunc, addition.index]); builder.addFunction('init', kSig_v_v) .addBody([ kExprI32Const, 1, kExprRefFunc, subtraction.index, kExprTableSet, table.index ]) .exportFunc(); // (index, arg1, arg2) -> table[index](arg1, arg2) builder.addFunction('table_test', kSig_i_iii) .addBody([ kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 0, kExprCallIndirect, binary_type, table.index ]) .exportFunc(); var instance = builder.instantiate({}); assertTrue(!!instance); instance.exports.init(); assertEquals(44, instance.exports.table_test(0, 33, 11)); assertEquals(22, instance.exports.table_test(1, 33, 11)); })(); (function TestAnyRefTable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array_type = builder.addArray(kWasmI32); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); let table = builder.addTable(kWasmAnyRef, 4, 4); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI32Const(111), ...wasmI32Const(222), kGCPrefix, kExprArrayNewFixed, array_type, 2], [...wasmI32Const(-31), kGCPrefix, kExprI31New], [...wasmI32Const(10), kGCPrefix, kExprStructNew, struct_type], [kExprRefNull, kEqRefCode]], kWasmAnyRef); // return ...static_cast(table[0]) builder.addFunction("array_getter", kSig_ii_v) .addLocals(wasmRefNullType(array_type), 1) .addBody([ kExprI32Const, 0, kExprTableGet, 0, kGCPrefix, kExprRefAsArray, kGCPrefix, kExprRefCast, array_type, kExprLocalSet, 0, kExprLocalGet, 0, ...wasmI32Const(0), kGCPrefix, kExprArrayGet, array_type, kExprLocalGet, 0, ...wasmI32Const(1), kGCPrefix, kExprArrayGet, array_type]) .exportFunc(); // return static_cast(table[1]) builder.addFunction("i31_getter", kSig_i_v) .addBody([ kExprI32Const, 1, kExprTableGet, 0, kGCPrefix, kExprRefAsI31, kGCPrefix, kExprI31GetS]) .exportFunc(); // return static_cast(table[2]).field_0 builder.addFunction("struct_getter", kSig_i_v) .addBody([ kExprI32Const, 2, kExprTableGet, 0, kGCPrefix, kExprRefAsStruct, kGCPrefix, kExprRefCast, struct_type, kGCPrefix, kExprStructGet, struct_type, 0]) .exportFunc(); // return table[3] == null builder.addFunction("null_getter", kSig_i_v) .addBody([kExprI32Const, 3, kExprTableGet, 0, kExprRefIsNull]) .exportFunc(); let instance = builder.instantiate({}); assertTrue(!!instance); assertEquals([111, 222], instance.exports.array_getter()); assertEquals(-31, instance.exports.i31_getter()); assertEquals(10, instance.exports.struct_getter()); assertEquals(1, instance.exports.null_getter()); })(); (function TestAnyRefTableNotNull() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array_type = builder.addArray(kWasmI32); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); let table = builder.addTable(wasmRefType(kWasmAnyRef), 3, 6, [...wasmI32Const(111), ...wasmI32Const(222), kGCPrefix, kExprArrayNewFixed, array_type, 2]) .exportAs("table"); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI32Const(111), ...wasmI32Const(222), kGCPrefix, kExprArrayNewFixed, array_type, 2], [...wasmI32Const(-31), kGCPrefix, kExprI31New], [...wasmI32Const(10), kGCPrefix, kExprStructNew, struct_type]], wasmRefType(kWasmAnyRef)); // return ...cast(table.get(0)) builder.addFunction("array_getter", kSig_ii_v) .addLocals(wasmRefNullType(array_type), 1) .addBody([ kExprI32Const, 0, kExprTableGet, 0, kGCPrefix, kExprRefAsArray, kGCPrefix, kExprRefCast, array_type, kExprLocalSet, 0, kExprLocalGet, 0, ...wasmI32Const(0), kGCPrefix, kExprArrayGet, array_type, kExprLocalGet, 0, ...wasmI32Const(1), kGCPrefix, kExprArrayGet, array_type]) .exportFunc(); // return cast(table.get(1)) builder.addFunction("i31_getter", kSig_i_v) .addBody([ kExprI32Const, 1, kExprTableGet, 0, kGCPrefix, kExprRefAsI31, kGCPrefix, kExprI31GetS]) .exportFunc(); // return cast(table.get(param<0>))[0] builder.addFunction("struct_getter", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprTableGet, 0, kGCPrefix, kExprRefAsStruct, kGCPrefix, kExprRefCast, struct_type, kGCPrefix, kExprStructGet, struct_type, 0]) .exportFunc(); builder.addFunction("grow_table", kSig_v_v) .addBody([ ...wasmI32Const(20), kGCPrefix, kExprStructNew, struct_type, kExprI32Const, 1, kNumericPrefix, kExprTableGrow, 0, kExprDrop, ]) .exportFunc(); builder.addFunction("create_struct", makeSig([kWasmI32], [kWasmExternRef])) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructNew, struct_type, kGCPrefix, kExprExternExternalize, ]) .exportFunc(); let instance = builder.instantiate({}); let wasmTable = instance.exports.table; assertEquals([111, 222], instance.exports.array_getter()); assertEquals(-31, instance.exports.i31_getter()); assertEquals(10, instance.exports.struct_getter(2)); assertTraps(kTrapTableOutOfBounds, () => instance.exports.struct_getter(3)); instance.exports.grow_table(); assertEquals(20, instance.exports.struct_getter(3)); assertThrows(() => wasmTable.grow(1), TypeError, /Argument 1 must be specified for non-nullable element type/); wasmTable.grow(1, instance.exports.create_struct(33)); assertEquals(33, instance.exports.struct_getter(4)); // undefined is ok for (ref any), but not null. wasmTable.set(4, undefined); assertThrows(() => wasmTable.set(4, null), TypeError, /Argument 1 is invalid/); })(); (function TestStructRefTable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); let table = builder.addTable(wasmRefNullType(struct_type), 4, 4); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI32Const(10), kGCPrefix, kExprStructNew, struct_type], [...wasmI32Const(11), kGCPrefix, kExprStructNew, struct_type], [kExprRefNull, struct_type]], wasmRefNullType(struct_type)); builder.addFunction("struct_getter", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprTableGet, 0, kGCPrefix, kExprStructGet, struct_type, 0]) .exportFunc(); builder.addFunction("null_getter", kSig_i_i) .addBody([kExprLocalGet, 0, kExprTableGet, 0, kExprRefIsNull]) .exportFunc(); let instance = builder.instantiate({}); assertTrue(!!instance); assertEquals(10, instance.exports.struct_getter(0)); assertEquals(11, instance.exports.struct_getter(1)); assertEquals(1, instance.exports.null_getter(2)); })(); (function TestI31RefTable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let table = builder.addTable(kWasmI31Ref, 4, 4); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI32Const(10), kGCPrefix, kExprI31New], [...wasmI32Const(-42), kGCPrefix, kExprI31New], [kExprRefNull, kI31RefCode]], kWasmI31Ref); builder.addFunction("i31GetI32", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprTableGet, 0, kGCPrefix, kExprI31GetS]) .exportFunc(); builder.addFunction("i31GetNull", kSig_i_i) .addBody([kExprLocalGet, 0, kExprTableGet, 0, kExprRefIsNull]) .exportFunc(); let instance = builder.instantiate({}); assertTrue(!!instance); assertEquals(0, instance.exports.i31GetNull(0)); assertEquals(0, instance.exports.i31GetNull(1)); assertEquals(1, instance.exports.i31GetNull(2)); assertEquals(1, instance.exports.i31GetNull(3)); assertEquals(10, instance.exports.i31GetI32(0)); assertEquals(-42, instance.exports.i31GetI32(1)); assertTraps(kTrapNullDereference, () => instance.exports.i31GetI32(2)); assertTraps(kTrapNullDereference, () => instance.exports.i31GetI32(3)); })(); (function TestArrayRefTable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array_type = builder.addArray(kWasmI32); let table = builder.addTable(wasmRefNullType(array_type), 4, 4); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI32Const(10), ...wasmI32Const(11), ...wasmI32Const(12), kGCPrefix, kExprArrayNewFixed, array_type, 3], [kGCPrefix, kExprArrayNewFixed, array_type, 0], [kExprRefNull, array_type]], wasmRefNullType(array_type)); builder.addFunction("array_getter", kSig_i_ii) .addBody([ kExprLocalGet, 0, kExprTableGet, 0, kExprLocalGet, 1, kGCPrefix, kExprArrayGet, array_type]) .exportFunc(); builder.addFunction("null_getter", kSig_i_i) .addBody([kExprLocalGet, 0, kExprTableGet, 0, kExprRefIsNull]) .exportFunc(); let instance = builder.instantiate({}); assertTrue(!!instance); assertEquals(10, instance.exports.array_getter(0, 0)); assertEquals(11, instance.exports.array_getter(0, 1)); assertEquals(12, instance.exports.array_getter(0, 2)); assertEquals(0, instance.exports.null_getter(1)); assertTraps(kTrapArrayOutOfBounds, () => instance.exports.array_getter(1, 0)); assertEquals(1, instance.exports.null_getter(2)); })(); (function TestRefTableInvalidSegmentType() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let struct_type_a = builder.addStruct([makeField(kWasmI32, false)]); let struct_type_b = builder.addStruct([makeField(kWasmI64, false)]); let table = builder.addTable(wasmRefNullType(struct_type_a), 4, 4); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI32Const(10), kGCPrefix, kExprStructNew, struct_type_b]], wasmRefNullType(struct_type_b)); // Mismatches table type. assertThrows(() => builder.instantiate({}), WebAssembly.CompileError, /Element segment .* is not a subtype of referenced table/); })(); (function TestRefTableInvalidSegmentExpressionType() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let struct_type_a = builder.addStruct([makeField(kWasmI32, false)]); let struct_type_b = builder.addStruct([makeField(kWasmI64, false)]); let table = builder.addTable(wasmRefNullType(struct_type_a), 4, 4); builder.addActiveElementSegment( table, wasmI32Const(0), [[...wasmI64Const(10), kGCPrefix, kExprStructNew, struct_type_b]], wasmRefNullType(struct_type_a)); assertThrows(() => builder.instantiate({}), WebAssembly.CompileError, /expected \(ref null 0\), got \(ref 1\)/); })(); (function TestMultiModuleRefTableEquivalentTypes() { print(arguments.callee.name); let exporting_instance = (() => { let builder = new WasmModuleBuilder(); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); builder.addTable(wasmRefNullType(struct_type), 1, 100).exportAs('table'); return builder.instantiate({}); })(); // Mismatching struct definition. assertThrows(() => { let builder = new WasmModuleBuilder(); let struct_type = builder.addStruct([makeField(kWasmI64, false)]); builder.addImportedTable( 'imports', 'table', 1, 100, wasmRefNullType(struct_type)); builder.instantiate({imports: {table: exporting_instance.exports.table}}) }, WebAssembly.LinkError, /imported table does not match the expected type/); // Mismatching nullability. assertThrows(() => { let builder = new WasmModuleBuilder(); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); builder.addImportedTable( 'imports', 'table', 1, 100, wasmRefType(struct_type)); builder.instantiate({imports: {table: exporting_instance.exports.table}}) }, WebAssembly.LinkError, /imported table does not match the expected type/); // Equivalent struct type. let builder = new WasmModuleBuilder(); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); let struct_type_invalid = builder.addStruct([makeField(kWasmI64, false)]); let struct_type_sub = builder.addStruct( [makeField(kWasmI32, false), makeField(kWasmI32, false)], struct_type); builder.addImportedTable( 'imports', 'table', 1, 100, wasmRefNullType(struct_type)); builder.addFunction("invalid_struct", makeSig([], [kWasmExternRef])) .addBody([ kExprI64Const, 44, kGCPrefix, kExprStructNew, struct_type_invalid, kGCPrefix, kExprExternExternalize]) .exportFunc(); builder.addFunction("valid_struct", makeSig([], [kWasmExternRef])) .addBody([ kExprI32Const, 44, kGCPrefix, kExprStructNew, struct_type, kGCPrefix, kExprExternExternalize]) .exportFunc(); builder.addFunction("valid_struct_sub", makeSig([], [kWasmExternRef])) .addBody([ kExprI32Const, 55, kExprI32Const, 66, kGCPrefix, kExprStructNew, struct_type_sub, kGCPrefix, kExprExternExternalize]) .exportFunc(); let table = exporting_instance.exports.table; let instance = builder.instantiate({imports: {table}}); table.grow(5, undefined); assertThrows(() => table.set(1, instance.exports.invalid_struct()), TypeError); table.set(1, instance.exports.valid_struct()); table.set(2, instance.exports.valid_struct_sub()); table.set(3, null); assertThrows(() => table.set(1, undefined), TypeError); })(); (function TestMultiModuleRefTableSuperType() { print(arguments.callee.name); let exporting_instance = (() => { let builder = new WasmModuleBuilder(); let struct_type_base = builder.addStruct([makeField(kWasmI32, false)]); let struct_type = builder.addStruct([makeField(kWasmI32, false)], struct_type_base); builder.addTable(wasmRefNullType(struct_type), 1, 100).exportAs('table'); return builder.instantiate({}); })(); let builder = new WasmModuleBuilder(); let struct_type_base = builder.addStruct([makeField(kWasmI32, false)]); let struct_type = builder.addStruct([makeField(kWasmI32, false)], struct_type_base); builder.addImportedTable( 'imports', 'table', 1, 100, wasmRefNullType(struct_type)); builder.addFunction("struct_base", makeSig([], [kWasmExternRef])) .addBody([ kExprI32Const, 66, kGCPrefix, kExprStructNew, struct_type_base, kGCPrefix, kExprExternExternalize]) .exportFunc(); let table = exporting_instance.exports.table; let instance = builder.instantiate({imports: {table}}); assertThrows(() => table.set(0, instance.exports.struct_base()), TypeError); assertThrows(() => table.grow(1, instance.exports.struct_base()), TypeError); })(); (function TestRefTableNotNull() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let struct_type = builder.addStruct([makeField(kWasmI32, false)]); let table = builder.addTable(wasmRefType(struct_type), 2, 6, [...wasmI32Const(111), kGCPrefix, kExprStructNew, struct_type]) .exportAs("table"); builder.addFunction("create_struct", makeSig([kWasmI32], [kWasmExternRef])) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructNew, struct_type, kGCPrefix, kExprExternExternalize, ]) .exportFunc(); builder.addFunction("struct_getter", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprTableGet, 0, kGCPrefix, kExprStructGet, struct_type, 0]) .exportFunc(); let instance = builder.instantiate({}); let wasmTable = instance.exports.table; // Initial values. assertEquals(111, instance.exports.struct_getter(0)); assertEquals(111, instance.exports.struct_getter(1)); assertTraps(kTrapTableOutOfBounds, () => instance.exports.struct_getter(2)); assertThrows(() => wasmTable.grow(1), TypeError, /Argument 1 must be specified for non-nullable element type/); wasmTable.grow(1, instance.exports.create_struct(222)); assertEquals(222, instance.exports.struct_getter(2)); assertThrows(() => wasmTable.set(2, undefined), TypeError, /Argument 1 is invalid/); assertThrows(() => wasmTable.set(2, null), TypeError, /Argument 1 is invalid/); wasmTable.set(2, instance.exports.create_struct(333)); assertEquals(333, instance.exports.struct_getter(2)); })(); (function TestTypedTableCallIndirect() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let super_struct = builder.addStruct([makeField(kWasmI32, false)]); let sub_struct = builder.addStruct( [makeField(kWasmI32, false), makeField(kWasmI32, false)], super_struct); let super_sig = builder.addType( makeSig([kWasmI32], [wasmRefType(super_struct)])); let sub_sig = builder.addType( makeSig([kWasmI32], [wasmRefType(sub_struct)]), super_sig); let super_func = builder.addFunction("super_func", super_sig) .addBody([kExprLocalGet, 0, kGCPrefix, kExprStructNew, super_struct]); let sub_func = builder.addFunction("super_func", sub_sig) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add, kExprLocalGet, 0, kExprI32Const, 2, kExprI32Add, kGCPrefix, kExprStructNew, sub_struct]); let table = builder.addTable(wasmRefNullType(super_sig), 10, 10); builder.addActiveElementSegment( table.index, wasmI32Const(0), [[kExprRefFunc, super_func.index], [kExprRefFunc, sub_func.index]], wasmRefType(super_sig)); // Parameters: index, value. builder.addFunction("call_indirect_super", kSig_i_ii) .addBody([kExprLocalGet, 1, kExprLocalGet, 0, kExprCallIndirect, super_sig, table.index, kGCPrefix, kExprStructGet, super_struct, 0]) .exportFunc(); builder.addFunction("call_indirect_sub", kSig_i_ii) .addBody([kExprLocalGet, 1, kExprLocalGet, 0, kExprCallIndirect, sub_sig, table.index, kGCPrefix, kExprStructGet, sub_struct, 0]) .exportFunc(); let instance = builder.instantiate(); // No type check needed, null check needed. assertEquals(10, instance.exports.call_indirect_super(0, 10)); assertEquals(11, instance.exports.call_indirect_super(1, 10)); assertTraps(kTrapFuncSigMismatch, () => instance.exports.call_indirect_super(2, 10)); // Type check and null check needed. assertEquals(11, instance.exports.call_indirect_sub(1, 10)); assertTraps(kTrapFuncSigMismatch, () => instance.exports.call_indirect_sub(0, 10)); assertTraps(kTrapFuncSigMismatch, () => instance.exports.call_indirect_sub(2, 10)); })();