diff --git a/document/js-api/index.bs b/document/js-api/index.bs index bb12a12621..9800ae5fa1 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -89,6 +89,8 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT text: CreateArrayFromList; url: sec-createarrayfromlist text: GetMethod; url: sec-getmethod text: IterableToList; url: sec-iterabletolist + text: ToBigInt64; url: #sec-tobigint64 + text: BigInt; url: #sec-ecmascript-language-types-bigint-type type: abstract-op text: CreateMethodProperty; url: sec-createmethodproperty urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: dfn @@ -172,6 +174,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: current frame; url: exec/conventions.html#exec-notation-textual text: module; for: frame; url: exec/runtime.html#syntax-frame text: memaddrs; for: moduleinst; url: exec/runtime.html#syntax-moduleinst + text: signed_64; url: exec/numerics.html#aux-signed text: sequence; url: syntax/conventions.html#grammar-notation urlPrefix: https://heycam.github.io/webidl/; spec: WebIDL type: dfn @@ -348,8 +351,11 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje 1. Let |externfunc| be the [=external value=] [=external value|func=] |funcaddr|. 1. [=list/Append=] |externfunc| to |imports|. 1. If |externtype| is of the form [=global=] mut |valtype|, - 1. If [=Type=](|v|) is Number, - 1. If |valtype| is [=i64=], throw a {{LinkError}} exception. + 1. If [=Type=](|v|) is Number or BigInt, + 1. If |valtype| is [=i64=] and [=Type=](|v|) is Number, + 1. Throw a {{LinkError}} exception. + 1. If |valtype| is not [=i64=] and [=Type=](|v|) is BigInt, + 1. Throw a {{LinkError}} exception. 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valtype|). 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (|store|, |globaladdr|) be [=global_alloc=](|store|, [=const=] |valtype|, |value|). @@ -871,7 +877,6 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. If |v| is undefined, 1. Let |value| be [=DefaultValue=](|valuetype|). 1. Otherwise, - 1. If |valuetype| is [=i64=], throw a {{TypeError}} exception. 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valuetype|). 1. If |mutable| is true, let |globaltype| be [=var=] |valuetype|; otherwise, let |globaltype| be [=const=] |valuetype|. 1. Let |store| be the current agent's [=associated store=]. @@ -884,8 +889,6 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each The algorithm GetGlobalValue({{Global}} |global|) performs the following steps: 1. Let |store| be the current agent's [=associated store=]. 1. Let |globaladdr| be |global|.\[[Global]]. - 1. Let |globaltype| be [=global_type=](|store|, |globaladdr|). - 1. If |globaltype| is of the form mut [=i64=], throw a {{TypeError}}. 1. Let |value| be [=global_read=](|store|, |globaladdr|). 1. Return [=ToJSValue=](|value|). @@ -899,7 +902,6 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. Let |globaladdr| be **this**.\[[Global]]. 1. Let |mut| |valuetype| be [=global_type=](|store|, |globaladdr|). 1. If |mut| is [=const=], throw a {{TypeError}}. - 1. If |valuetype| is [=i64=], throw a {{TypeError}}. 1. Let |value| be [=ToWebAssemblyValue=](**the given value**, |valuetype|). 1. Let |store| be [=global_write=](|store|, |globaladdr|, |value|). 1. If |store| is [=error=], throw a {{RangeError}} exception. @@ -958,11 +960,7 @@ This slot holds a [=function address=] relative to the [=surrounding agent=]'s [ 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let |functype| be [=func_type=](|store|, |funcaddr|). - 1. Let [|parameters|] → [|results|] be |functype|. - 1. If |parameters| or |results| contains an [=i64=], throw a {{TypeError}}. - - Note: the above error is thrown each time the \[[Call]] method is invoked. - + 1. Let [|parameters|] → [results] be |functype|. 1. Let |args| be an empty [=list=] of WebAssembly values. 1. Let |i| be 0. 1. [=list/iterate|For each=] |t| of |parameters|, @@ -990,8 +988,7 @@ Note: Exported Functions do not have a \[[Construct]] method and thus it is not
To run a host function from the JavaScript object |func|, type |functype|, and [=list=] of [=WebAssembly values=] |arguments|, perform the following steps: - 1. Let [|parameters|] → [|results|] be |functype|. - 1. If either |parameters| or |results| contains [=i64=], throw a {{TypeError}}. + 1. Let [parameters] → [|results|] be |functype|. 1. Let |jsArguments| be an empty [=list=]. 1. [=list/iterate|For each=] |arg| of |arguments|, 1. [=list/Append=] ! [=ToJSValue=](|arg|) to |jsArguments|. @@ -1028,7 +1025,9 @@ Note: Exported Functions do not have a \[[Construct]] method and thus it is not
The algorithm ToJSValue(|w|) coerces a [=WebAssembly value=] to a JavaScript value by performing the following steps: -1. Assert: |w| is not of the form [=i64.const=] i64. +1. If |w| is of the form [=i64.const=] |i64|, + 1. Let |v| be [=signed_64=](|i64|). + 1. Return a [=BigInt=] representing the mathematical value |v|. 1. If |w| is of the form [=i32.const=] |i32|, return [=the Number value=] for [=signed_32=](|i32|). 1. If |w| is of the form [=f32.const=] |f32|, return [=the Number value=] for |f32|. 1. If |w| is of the form [=f64.const=] |f64|, return [=the Number value=] for |f64|. @@ -1042,7 +1041,9 @@ Note: Number values which are equal to NaN may have various observable NaN paylo The algorithm ToWebAssemblyValue(|v|, |type|) coerce a JavaScript value to a [=WebAssembly value=] performs the following steps: -1. Assert: |type| is not [=i64=]. +1. If |type| is [=i64=], + 1. Let |i64| be ? [=ToBigInt64=](|v|). + 1. Return [=i64.const=] |i64|. 1. If |type| is [=i32=], 1. Let |i32| be ? [=ToInt32=](|v|). 1. Return [=i32.const=] |i32|. diff --git a/test/js-api/bad-imports.js b/test/js-api/bad-imports.js index afd41936d3..a1da7f6860 100644 --- a/test/js-api/bad-imports.js +++ b/test/js-api/bad-imports.js @@ -11,6 +11,16 @@ * (if any) yields the correct error. */ function test_bad_imports(t) { + function value_type(type) { + switch (type) { + case "i32": return kWasmI32; + case "i64": return kWasmI64; + case "f32": return kWasmF32; + case "f64": return kWasmF64; + default: throw new TypeError(`Unexpected type ${type}`); + } + } + for (const value of [null, true, "", Symbol(), 1, 0.1, NaN]) { t(`Non-object imports argument: ${format_value(value)}`, TypeError, @@ -45,17 +55,6 @@ function test_bad_imports(t) { value); } - t(`Importing an i64 global`, - WebAssembly.LinkError, - builder => { - builder.addImportedGlobal("module", "global", kWasmI64); - }, - { - "module": { - "global": 0, - }, - }); - for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN, {}]) { t(`Importing a function with an incorrectly-typed value: ${format_value(value)}`, WebAssembly.LinkError, @@ -82,17 +81,48 @@ function test_bad_imports(t) { [new WebAssembly.Global({value: "f32"}), "WebAssembly.Global object (wrong value type)"], ]; - for (const [value, name = format_value(value)] of nonGlobals) { - t(`Importing a global with an incorrectly-typed value: ${name}`, + for (const type of ["i32", "i64", "f32", "f64"]) { + const extendedNonGlobals = nonGlobals.concat([ + type === "i64" ? [0, "Number"] : [0n, "BigInt"] + ]); + for (const [value, name = format_value(value)] of extendedNonGlobals) { + t(`Importing an ${type} global with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type)); + }, + { + "module": { + "global": value, + }, + }); + } + } + + for (const type of ["i32", "i64", "f32", "f64"]) { + const value = type === "i64" ? 0n : 0; + t(`Importing an ${type} mutable global with a primitive value`, WebAssembly.LinkError, builder => { - builder.addImportedGlobal("module", "global", kWasmI32); + builder.addImportedGlobal("module", "global", value_type(type), true); }, { "module": { "global": value, }, }); + + const global = new WebAssembly.Global({ "value": type }, value); + t(`Importing an ${type} mutable global with an immutable Global object`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type), true); + }, + { + "module": { + "global": global, + }, + }); } const nonMemories = [ diff --git a/test/js-api/global/constructor.any.js b/test/js-api/global/constructor.any.js index b008ef3016..f536f5d7b5 100644 --- a/test/js-api/global/constructor.any.js +++ b/test/js-api/global/constructor.any.js @@ -90,8 +90,7 @@ test(() => { test(() => { const argument = { "value": "i64" }; const global = new WebAssembly.Global(argument); - assert_throws_js(TypeError, () => global.value); - assert_throws_js(TypeError, () => global.valueOf()); + assert_Global(global, 0n); }, "i64 with default"); for (const type of ["i32", "f32", "f64"]) { @@ -108,8 +107,10 @@ for (const type of ["i32", "f32", "f64"]) { [false, 0], [2, 2], ["3", 3], - [{ toString() { return "5" } }, 5, "object with toString"], - [{ valueOf() { return "8" } }, 8, "object with valueOf"], + [{ toString() { return "5" } }, 5, "object with toString returning string"], + [{ valueOf() { return "8" } }, 8, "object with valueOf returning string"], + [{ toString() { return 6 } }, 6, "object with toString returning number"], + [{ valueOf() { return 9 } }, 9, "object with valueOf returning number"], ]; for (const [value, expected, name = format_value(value)] of valueArguments) { test(() => { @@ -118,6 +119,44 @@ for (const type of ["i32", "f32", "f64"]) { assert_Global(global, expected); }, `Explicit value ${name} for type ${type}`); } + + test(() => { + const argument = { "value": type }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument, 0n)); + }, `BigInt value for type ${type}`); +} + +const valueArguments = [ + [undefined, 0n], + [true, 1n], + [false, 0n], + ["3", 3n], + [123n, 123n], + [{ toString() { return "5" } }, 5n, "object with toString returning string"], + [{ valueOf() { return "8" } }, 8n, "object with valueOf returning string"], + [{ toString() { return 6n } }, 6n, "object with toString returning bigint"], + [{ valueOf() { return 9n } }, 9n, "object with valueOf returning bigint"], +]; +for (const [value, expected, name = format_value(value)] of valueArguments) { + test(() => { + const argument = { "value": "i64" }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type i64`); +} + +const invalidBigints = [ + null, + 666, + { toString() { return 5 } }, + { valueOf() { return 8 } }, + Symbol(), +]; +for (const invalidBigint of invalidBigints) { + test(() => { + var argument = { "value": "i64" }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument, invalidBigint)); + }, `Pass non-bigint as i64 Global value: ${format_value(invalidBigint)}`); } test(() => { diff --git a/test/js-api/global/value-get-set.any.js b/test/js-api/global/value-get-set.any.js index 43d8474a25..f95b7ca9e3 100644 --- a/test/js-api/global/value-get-set.any.js +++ b/test/js-api/global/value-get-set.any.js @@ -28,7 +28,8 @@ test(() => { } }, "Branding"); -for (const type of ["i32", "f32", "f64"]) { +for (const type of ["i32", "i64", "f32", "f64"]) { + const [initial, value, invalid] = type === "i64" ? [0n, 1n, 2] : [0, 1, 2n]; const immutableOptions = [ [{}, "missing"], [{ "mutable": undefined }, "undefined"], @@ -41,20 +42,20 @@ for (const type of ["i32", "f32", "f64"]) { test(() => { opts.value = type; const global = new WebAssembly.Global(opts); - assert_equals(global.value, 0, "initial value"); - assert_equals(global.valueOf(), 0, "initial valueOf"); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); - assert_throws_js(TypeError, () => global.value = 1); + assert_throws_js(TypeError, () => global.value = value); - assert_equals(global.value, 0, "post-set value"); - assert_equals(global.valueOf(), 0, "post-set valueOf"); + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); }, `Immutable ${type} (${name})`); test(t => { opts.value = type; const global = new WebAssembly.Global(opts); - assert_equals(global.value, 0, "initial value"); - assert_equals(global.valueOf(), 0, "initial valueOf"); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); const value = { valueOf: t.unreached_func("should not call valueOf"), @@ -62,8 +63,8 @@ for (const type of ["i32", "f32", "f64"]) { }; assert_throws_js(TypeError, () => global.value = value); - assert_equals(global.value, 0, "post-set value"); - assert_equals(global.valueOf(), 0, "post-set valueOf"); + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); }, `Immutable ${type} with ToNumber side-effects (${name})`); } @@ -77,13 +78,15 @@ for (const type of ["i32", "f32", "f64"]) { test(() => { opts.value = type; const global = new WebAssembly.Global(opts); - assert_equals(global.value, 0, "initial value"); - assert_equals(global.valueOf(), 0, "initial valueOf"); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + global.value = value; - global.value = 1; + assert_throws_js(TypeError, () => global.value = invalid); - assert_equals(global.value, 1, "post-set value"); - assert_equals(global.valueOf(), 1, "post-set valueOf"); + assert_equals(global.value, value, "post-set value"); + assert_equals(global.valueOf(), value, "post-set valueOf"); }, `Mutable ${type} (${name})`); } } @@ -91,20 +94,33 @@ for (const type of ["i32", "f32", "f64"]) { test(() => { const argument = { "value": "i64", "mutable": true }; const global = new WebAssembly.Global(argument); - assert_throws_js(TypeError, () => global.value); - assert_throws_js(TypeError, () => global.value = 0); - assert_throws_js(TypeError, () => global.valueOf()); -}, "i64 with default"); -test(t => { - const argument = { "value": "i64", "mutable": true }; - const global = new WebAssembly.Global(argument); - const value = { - valueOf: t.unreached_func("should not call valueOf"), - toString: t.unreached_func("should not call toString"), - }; - assert_throws_js(TypeError, () => global.value = value); -}, "i64 with ToNumber side-effects"); + assert_equals(global.value, 0n, "initial value using ToJSValue"); + + const valid = [ + [123n, 123n], + [2n ** 63n, - (2n ** 63n)], + [true, 1n], + [false, 0n], + ["456", 456n], + ]; + for (const [input, output] of valid) { + global.value = input; + assert_equals(global.valueOf(), output, "post-set valueOf"); + } + + const invalid = [ + undefined, + null, + 0, + 1, + 4.2, + Symbol(), + ]; + for (const input of invalid) { + assert_throws_js(TypeError, () => global.value = input); + } +}, "i64 mutability"); test(() => { const argument = { "value": "i32", "mutable": true }; diff --git a/test/js-api/instanceTestFactory.js b/test/js-api/instanceTestFactory.js index c81672f208..86f593f46f 100644 --- a/test/js-api/instanceTestFactory.js +++ b/test/js-api/instanceTestFactory.js @@ -210,6 +210,415 @@ const instanceTestFactory = [ } ], + [ + "i64 exports and imports", + function() { + const value = 102n; + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI64); + builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const index2 = builder.addImportedGlobal("module", "global2", kWasmI64); + builder.addExportOfKind("global", kExternalGlobal, index2); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + "global2": 2n ** 63n, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 0 }, + "global": { "kind": "global", "value": -(2n ** 63n) }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => assert_equals(instance.exports.fn(), value) + }; + } + ], + + [ + "import with i32-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_i_v); + const fn2 = builder + .addFunction("fn2", kSig_v_v) + .addBody([ + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function() { + called = true; + return 6n; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_throws_js(TypeError, () => instance.exports.fn2()); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with function that takes and returns i32", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_i_i); + const fn2 = builder + .addFunction("fn2", kSig_i_v) + .addBody([ + kExprI32Const, 0x66, + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function(n) { + called = true; + assert_equals(n, -26); + return { valueOf() { return 6; } }; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_equals(instance.exports.fn2(), 6); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with i64-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_l_v); + const fn2 = builder + .addFunction("fn2", kSig_v_v) + .addBody([ + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function() { + called = true; + return 6; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_throws_js(TypeError, () => instance.exports.fn2()); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with function that takes and returns i64", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_l_l); + const fn2 = builder + .addFunction("fn2", kSig_l_v) + .addBody([ + kExprI64Const, 0x66, + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function(n) { + called = true; + assert_equals(n, -26n); + return { valueOf() { return 6n; } }; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_equals(instance.exports.fn2(), 6n); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with i32-taking function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_v_i) + .addBody([ + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 1 }, + }, + verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6n)) + }; + } + ], + + [ + "import with i64-taking function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_v_l) + .addBody([ + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 1 }, + }, + verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6)) + }; + } + ], + + [ + "export i64-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprI64Const, 0x66, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }, + verify: instance => assert_equals(instance.exports.fn(), -26n) + }; + } + ], + + [ + "i32 mutable WebAssembly.Global import", + function() { + const initial = 102; + const value = new WebAssembly.Global({ "value": "i32", "mutable": true }, initial); + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI32, true); + const fn = builder + .addFunction("fn", kSig_i_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => { + assert_equals(instance.exports.fn(), initial); + const after = 201; + value.value = after; + assert_equals(instance.exports.fn(), after); + } + }; + } + ], + + [ + "i64 mutable WebAssembly.Global import", + function() { + const initial = 102n; + const value = new WebAssembly.Global({ "value": "i64", "mutable": true }, initial); + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI64, true); + const fn = builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => { + assert_equals(instance.exports.fn(), initial); + const after = 201n; + value.value = after; + assert_equals(instance.exports.fn(), after); + } + }; + } + ], + + [ + "Multiple i64 arguments", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_l_ll) + .addBody([ + kExprLocalGet, 1, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 2 }, + }; + + return { + buffer, + args: [], + exports, + verify: instance => { + const fn = instance.exports.fn; + assert_equals(fn(1n, 0n), 0n); + assert_equals(fn(1n, 123n), 123n); + assert_equals(fn(1n, -123n), -123n); + assert_equals(fn(1n, "5"), 5n); + assert_throws_js(TypeError, () => fn(1n, 5)); + } + }; + } + ], + [ "stray argument", function() {