| // Copyright 2024 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-stack-switching |
| // Flags: --allow-natives-syntax --experimental-wasm-type-reflection |
| // Flags: --expose-gc --wasm-stack-switching-stack-size=100 |
| |
| d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); |
| |
| (function TestInvalidWrappers() { |
| print(arguments.callee.name); |
| assertThrows(() => WebAssembly.promising({}), TypeError, |
| /Argument 0 must be a function/); |
| assertThrows(() => WebAssembly.promising(() => {}), TypeError, |
| /Argument 0 must be a WebAssembly exported function/); |
| assertThrows(() => WebAssembly.Suspending(() => {}), TypeError, |
| /WebAssembly.Suspending must be invoked with 'new'/); |
| assertThrows(() => new WebAssembly.Suspending({}), TypeError, |
| /Argument 0 must be a function/); |
| function asmModule() { |
| "use asm"; |
| function x(v) { |
| v = v | 0; |
| } |
| return x; |
| } |
| assertThrows(() => WebAssembly.promising(asmModule()), TypeError, |
| /Argument 0 must be a WebAssembly exported function/); |
| |
| let builder = new WasmModuleBuilder(); |
| builder.addFunction("forty2", kSig_i_v) |
| .addBody([ |
| kExprI32Const, 42]).exportFunc(); |
| let instance = builder.instantiate(); |
| |
| assertThrows(()=> new WebAssembly.Suspending(instance.exports.forty2), TypeError, |
| /Argument 0 must not be a WebAssembly function/); |
| |
| let funcref = new WebAssembly.Function( |
| {parameters: [], results: ['i32']}, |
| (() => 42)); |
| |
| assertThrows(() => new WebAssembly.Suspending(funcref ), TypeError, |
| /Argument 0 must not be a WebAssembly function/); |
| })(); |
| |
| (function TestStackSwitchNoSuspend() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| builder.addGlobal(kWasmI32, true, false).exportAs('g'); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprI32Const, 42, |
| kExprGlobalSet, 0, |
| kExprI32Const, 0]).exportFunc(); |
| let instance = builder.instantiate(); |
| let wrapper = WebAssembly.promising(instance.exports.test); |
| wrapper(); |
| assertEquals(42, instance.exports.g.value); |
| })(); |
| |
| (function TestStackSwitchSuspend() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| import_index = builder.addImport('m', 'import', kSig_i_v); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, // suspend |
| ]).exportFunc(); |
| let js_import = new WebAssembly.Suspending(() => Promise.resolve(42)); |
| let instance = builder.instantiate({m: {import: js_import}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(42, v)); |
| |
| // Also try with a JS function with a mismatching arity. |
| js_import = new WebAssembly.Suspending((unused) => Promise.resolve(42)); |
| instance = builder.instantiate({m: {import: js_import}}); |
| wrapped_export = WebAssembly.promising(instance.exports.test); |
| combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(42, v)); |
| |
| // Also try with a proxy. |
| js_import = new WebAssembly.Suspending(new Proxy(() => Promise.resolve(42), {})); |
| instance = builder.instantiate({m: {import: js_import}}); |
| wrapped_export = WebAssembly.promising(instance.exports.test); |
| combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(42, v)); |
| %CheckIsOnCentralStack(); |
| })(); |
| |
| // Check that we can suspend back out of a resumed computation. |
| (function TestStackSwitchSuspendLoop() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| builder.addGlobal(kWasmI32, true, false).exportAs('g'); |
| import_index = builder.addImport('m', 'import', kSig_i_v); |
| // Pseudo-code for the wasm function: |
| // for (i = 0; i < 5; ++i) { |
| // g = g + import(); |
| // } |
| builder.addFunction("test", kSig_i_v) |
| .addLocals(kWasmI32, 1) |
| .addBody([ |
| kExprI32Const, 5, |
| kExprLocalSet, 0, |
| kExprLoop, kWasmVoid, |
| kExprCallFunction, import_index, // suspend |
| kExprGlobalGet, 0, // resume |
| kExprI32Add, |
| kExprGlobalSet, 0, |
| kExprLocalGet, 0, |
| kExprI32Const, 1, |
| kExprI32Sub, |
| kExprLocalTee, 0, |
| kExprBrIf, 0, |
| kExprEnd, |
| kExprI32Const, 0, |
| ]).exportFunc(); |
| let i = 0; |
| // The n-th call to the import returns a promise that resolves to n. |
| function js_import() { |
| return Promise.resolve(++i); |
| }; |
| let wasm_js_import = new WebAssembly.Suspending(js_import); |
| let instance = builder.instantiate({m: {import: wasm_js_import}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let chained_promise = wrapped_export(); |
| assertEquals(0, instance.exports.g.value); |
| assertPromiseResult(chained_promise, _ => assertEquals(15, instance.exports.g.value)); |
| })(); |
| |
| // Call the GC in the import call. |
| (function TestStackSwitchGC() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| let gc_index = builder.addImport('m', 'gc', kSig_v_v); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, gc_index, |
| kExprI32Const, 0 |
| ]).exportFunc(); |
| let js_import = new WebAssembly.Suspending(gc); |
| let instance = builder.instantiate({'m': {'gc': js_import}}); |
| let wrapper = WebAssembly.promising(instance.exports.test); |
| wrapper(); |
| })(); |
| |
| // Call the GC during param conversion. |
| (function TestStackSwitchGC2() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| let import_index = builder.addImport('m', 'import', kSig_i_i); |
| builder.addFunction("test", kSig_i_i) |
| .addBody([ |
| kExprLocalGet, 0, |
| kExprCallFunction, import_index, |
| ]).exportFunc(); |
| let js_import = new WebAssembly.Suspending((v) => { return Promise.resolve(v) }); |
| let instance = builder.instantiate({'m': {'import': js_import}}); |
| let wrapper = WebAssembly.promising(instance.exports.test); |
| let arg = { valueOf: () => { gc(); return 24; } }; |
| assertPromiseResult(wrapper(arg), v => assertEquals(arg.valueOf(), v)); |
| })(); |
| |
| // Check that the suspender DOES suspend even if the import's |
| // return value is not a promise. |
| (function TestStackSwitchNoPromise() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| builder.addGlobal(kWasmI32, true, false).exportAs('g'); |
| import_index = builder.addImport('m', 'import', kSig_i_v); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, // suspend |
| kExprGlobalSet, 0, // resume |
| kExprGlobalGet, 0, |
| ]).exportFunc(); |
| function js_import() { |
| return 42 |
| }; |
| let wasm_js_import = new WebAssembly.Suspending(js_import); |
| let instance = builder.instantiate({m: {import: wasm_js_import}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let result = wrapped_export(); |
| assertEquals(0, instance.exports.g.value); |
| })(); |
| |
| (function TestStackSwitchSuspendArgs() { |
| print(arguments.callee.name); |
| function reduce(array) { |
| // a[0] + a[1] * 2 + a[2] * 3 + ... |
| return array.reduce((prev, cur, i) => prev + cur * (i + 1)); |
| } |
| let builder = new WasmModuleBuilder(); |
| // Number of param registers + 1 for both types. |
| let params = [kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, |
| kWasmF32, kWasmF32, kWasmF32, kWasmF32, kWasmF32, kWasmF32, kWasmF32]; |
| let sig = makeSig(params, [kWasmI32]); |
| import_index = builder.addImport('m', 'import', sig); |
| builder.addFunction("test", sig) |
| .addBody([ |
| kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, |
| kExprLocalGet, 4, kExprLocalGet, 5, kExprLocalGet, 6, kExprLocalGet, 7, |
| kExprLocalGet, 8, kExprLocalGet, 9, kExprLocalGet, 10, kExprLocalGet, 11, |
| kExprLocalGet, 12, |
| kExprCallFunction, import_index, // suspend |
| ]).exportFunc(); |
| function js_import(i1, i2, i3, i4, i5, i6, f1, f2, f3, f4, f5, f6, f7) { |
| return Promise.resolve(reduce(Array.from(arguments))); |
| }; |
| let wasm_js_import = new WebAssembly.Suspending(js_import); |
| |
| let instance = builder.instantiate({m: {import: wasm_js_import}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let args = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; |
| let combined_promise = |
| wrapped_export.apply(null, args); |
| assertPromiseResult(combined_promise, v => assertEquals(reduce(args), v)); |
| })(); |
| |
| (function TestStackSwitchReturnFloat() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| import_index = builder.addImport('m', 'import', kSig_f_v); |
| builder.addFunction("test", kSig_f_v) |
| .addBody([ |
| kExprCallFunction, import_index, // suspend |
| ]).exportFunc(); |
| function js_import() { |
| return Promise.resolve(0.5); |
| }; |
| let wasm_js_import = new WebAssembly.Suspending(js_import); |
| |
| let instance = builder.instantiate({m: {import: wasm_js_import}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(0.5, v)); |
| })(); |
| |
| // Throw an exception before suspending. The export wrapper should return a |
| // promise rejected with the exception. |
| (function TestStackSwitchException1() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| let tag = builder.addTag(kSig_v_v); |
| builder.addFunction("throw", kSig_i_v) |
| .addBody([kExprThrow, tag]).exportFunc(); |
| let instance = builder.instantiate(); |
| let wrapper = WebAssembly.promising(instance.exports.throw); |
| assertThrowsAsync(wrapper(), WebAssembly.Exception); |
| })(); |
| |
| // Throw an exception after the first resume event, which propagates to the |
| // promise wrapper. |
| (function TestStackSwitchException2() { |
| print(arguments.callee.name); |
| let tag = new WebAssembly.Tag({parameters: []}); |
| let builder = new WasmModuleBuilder(); |
| import_index = builder.addImport('m', 'import', kSig_i_v); |
| tag_index = builder.addImportedTag('m', 'tag', kSig_v_v); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, |
| kExprThrow, tag_index |
| ]).exportFunc(); |
| function js_import() { |
| return Promise.resolve(42); |
| }; |
| let wasm_js_import = new WebAssembly.Suspending(js_import); |
| |
| let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let combined_promise = wrapped_export(); |
| assertThrowsAsync(combined_promise, WebAssembly.Exception); |
| })(); |
| |
| (function TestStackSwitchPromiseReject() { |
| print(arguments.callee.name); |
| let tag = new WebAssembly.Tag({parameters: ['i32']}); |
| let builder = new WasmModuleBuilder(); |
| import_index = builder.addImport('m', 'import', kSig_i_v); |
| tag_index = builder.addImportedTag('m', 'tag', kSig_v_i); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprTry, kWasmI32, |
| kExprCallFunction, import_index, |
| kExprCatch, tag_index, |
| kExprEnd, |
| ]).exportFunc(); |
| function js_import() { |
| return Promise.reject(new WebAssembly.Exception(tag, [42])); |
| }; |
| let wasm_js_import = new WebAssembly.Suspending(js_import); |
| |
| let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| let combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(v, 42)); |
| })(); |
| |
| function TestNestedSuspenders(suspend) { |
| // Nest two suspenders. The call chain looks like: |
| // outer (wasm) -> outer (js) -> inner (wasm) -> inner (js) |
| // If 'suspend' is true, the inner JS function returns a Promise, which |
| // suspends the inner wasm function, which returns a Promise, which suspends |
| // the outer wasm function, which returns a Promise. The inner Promise |
| // resolves first, which resumes the inner continuation. Then the outer |
| // promise resolves which resumes the outer continuation. |
| // If 'suspend' is false, the inner and outer JS functions return a regular |
| // value and no computation is suspended. |
| let builder = new WasmModuleBuilder(); |
| inner_index = builder.addImport('m', 'inner', kSig_i_v); |
| outer_index = builder.addImport('m', 'outer', kSig_i_v); |
| builder.addFunction("outer", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, outer_index |
| ]).exportFunc(); |
| builder.addFunction("inner", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, inner_index |
| ]).exportFunc(); |
| |
| let inner = new WebAssembly.Suspending(() => suspend ? Promise.resolve(42) : 42); |
| |
| let export_inner; |
| let outer = new WebAssembly.Suspending(() => suspend ? export_inner() : 42); |
| |
| let instance = builder.instantiate({m: {inner, outer}}); |
| export_inner = WebAssembly.promising(instance.exports.inner); |
| let export_outer = WebAssembly.promising(instance.exports.outer); |
| assertPromiseResult(export_outer(), v => assertEquals(42, v)); |
| } |
| |
| (function TestNestedSuspendersSuspend() { |
| print(arguments.callee.name); |
| TestNestedSuspenders(true); |
| })(); |
| |
| (function TestNestedSuspendersNoSuspend() { |
| print(arguments.callee.name); |
| TestNestedSuspenders(false); |
| })(); |
| |
| (function Regress13231() { |
| print(arguments.callee.name); |
| // Check that a promising function with no return is allowed. |
| let builder = new WasmModuleBuilder(); |
| builder.addFunction("export", kSig_v_v).addBody([]).exportFunc(); |
| let instance = builder.instantiate(); |
| let export_wrapper = WebAssembly.promising(instance.exports.export); |
| let export_sig = export_wrapper.type(); |
| assertEquals([], export_sig.parameters); |
| assertEquals(['externref'], export_sig.results); |
| })(); |
| |
| (function TestStackOverflow() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, 0 |
| ]).exportFunc(); |
| let instance = builder.instantiate(); |
| let wrapper = WebAssembly.promising(instance.exports.test); |
| assertThrowsAsync(wrapper(), RangeError, /Maximum call stack size exceeded/); |
| })(); |
| |
| (function SuspendCallRef() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| let funcref_type = builder.addType(kSig_i_v); |
| let table = builder.addTable(wasmRefNullType(funcref_type), 1) |
| .exportAs('table'); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprI32Const, 0, kExprTableGet, table.index, |
| kExprCallRef, funcref_type, |
| ]).exportFunc(); |
| let instance = builder.instantiate(); |
| |
| let funcref = new WebAssembly.Function( |
| {parameters: [], results: ['i32']}, |
| new WebAssembly.Suspending(() => Promise.resolve(42))); |
| instance.exports.table.set(0, funcref); |
| |
| let exp = WebAssembly.promising(instance.exports.test); |
| assertPromiseResult(exp(), v => assertEquals(42, v)); |
| })(); |
| |
| (function SuspendCallIndirect() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| let functype = builder.addType(kSig_i_v); |
| let table = builder.addTable(kWasmFuncRef, 10, 10); |
| let callee = builder.addImport('m', 'f', kSig_i_v); |
| builder.addActiveElementSegment(table, wasmI32Const(0), [callee]); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprI32Const, 0, |
| kExprCallIndirect, functype, table.index, |
| ]).exportFunc(); |
| |
| let create_promise = new WebAssembly.Suspending(() => Promise.resolve(42)); |
| |
| let instance = builder.instantiate({m: {f: create_promise}}); |
| |
| let exp = WebAssembly.promising(instance.exports.test); |
| assertPromiseResult(exp(), v => assertEquals(42, v)); |
| })(); |
| |
| (function TestSwitchingToTheCentralStackForJS() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| import_index = builder.addImport('m', 'import', kSig_i_v); |
| builder.addFunction("test", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, |
| ]).exportFunc(); |
| let js_import = new WebAssembly.Suspending( |
| () => { |
| %CheckIsOnCentralStack(); |
| return 123; |
| }); |
| let instance = builder.instantiate({m: {import: js_import}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| assertPromiseResult(wrapped_export(), v => assertEquals(123, v)); |
| })(); |
| |
| // Test that the wasm-to-js stack params get scanned. |
| (function TestSwitchingToTheCentralStackManyParams() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| const num_params = 10; |
| const sig = makeSig(Array(num_params).fill(kWasmExternRef), [kWasmExternRef]); |
| const import_index = builder.addImport('m', 'import_', sig); |
| let body = []; |
| for (let i = 0; i < num_params; ++i) { |
| body.push(kExprLocalGet, i); |
| } |
| body.push(kExprCallFunction, import_index); |
| builder.addFunction("test", sig) |
| .addBody(body).exportFunc(); |
| function import_js(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { |
| gc(); |
| return [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]; |
| }; |
| import_js(); |
| let import_ = new WebAssembly.Suspending(import_js); |
| let instance = builder.instantiate({m: {import_}}); |
| let wrapper = WebAssembly.promising(instance.exports.test); |
| let args = Array(num_params).fill({}); |
| assertPromiseResult(wrapper(...args), results => { assertEquals(args, results); }); |
| })(); |
| |
| // Similar to TestNestedSuspenders, but trigger an infinite recursion inside the |
| // outer wasm function after the import call. This is likely to crash if the |
| // stack limit is not properly restored when we return from the central stack. |
| // In particular in the nested case, we should preserve and restore the limit of |
| // each intermediate secondary stack. |
| (function TestCentralStackReentrency() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| inner_index = builder.addImport('m', 'inner', kSig_i_v); |
| outer_index = builder.addImport('m', 'outer', kSig_i_v); |
| let stack_overflow = builder.addFunction('stack_overflow', kSig_v_v) |
| .addBody([kExprCallFunction, 2]); |
| builder.addFunction("outer", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, outer_index, |
| kExprCallFunction, stack_overflow.index, |
| ]).exportFunc(); |
| builder.addFunction("inner", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, inner_index |
| ]).exportFunc(); |
| |
| let inner = new WebAssembly.Suspending(() => Promise.resolve(42)); |
| |
| let export_inner; |
| let outer = new WebAssembly.Suspending(() => export_inner()); |
| |
| let instance = builder.instantiate({m: {inner, outer}}); |
| export_inner = WebAssembly.promising(instance.exports.inner); |
| let export_outer = WebAssembly.promising(instance.exports.outer); |
| assertThrowsAsync(export_outer(), RangeError, |
| /Maximum call stack size exceeded/); |
| })(); |
| |
| (function TestStackSwitchRegressStackLimit() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| suspend_index = builder.addImport('m', 'suspend', kSig_v_r); |
| let leaf_index = builder.addFunction("leaf", kSig_v_v) |
| .addBody([ |
| ]).index; |
| let stackcheck_index = builder.addFunction("stackcheck", kSig_v_v) |
| .addBody([ |
| kExprCallFunction, leaf_index, |
| ]).index; |
| builder.addFunction("test", kSig_v_r) |
| .addBody([ |
| kExprLocalGet, 0, |
| kExprCallFunction, suspend_index, |
| // This call should not throw a stack overflow. |
| kExprCallFunction, stackcheck_index, |
| ]).exportFunc(); |
| let suspend = new WebAssembly.Suspending(() => Promise.resolve()); |
| let instance = builder.instantiate({m: {suspend}}); |
| let wrapped_export = WebAssembly.promising(instance.exports.test); |
| assertPromiseResult(wrapped_export(), v => assertEquals(undefined, v)); |
| })(); |
| |
| (function TestSuspendTwoModules() { |
| print(arguments.callee.name); |
| let builder1 = new WasmModuleBuilder(); |
| import_index = builder1.addImport('m', 'import', kSig_i_v); |
| builder1.addFunction("f", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, // suspend |
| kExprI32Const, 1, |
| kExprI32Add, |
| ]).exportFunc(); |
| let js_import = new WebAssembly.Suspending(() => Promise.resolve(1)); |
| let instance1 = builder1.instantiate({m: {import: js_import}}); |
| let builder2 = new WasmModuleBuilder(); |
| import_index = builder2.addImport('m', 'import', kSig_i_v); |
| builder2.addFunction("main", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, |
| kExprI32Const, 1, |
| kExprI32Add, |
| ]).exportFunc(); |
| let instance2 = builder2.instantiate({m: {import: instance1.exports.f}}); |
| let wrapped_export = WebAssembly.promising(instance2.exports.main); |
| let combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(3, v)); |
| })(); |
| |
| (function ReImportedSuspendingImport() { |
| print(arguments.callee.name); |
| let builder1 = new WasmModuleBuilder(); |
| import_index = builder1.addImport('m', 'import', kSig_i_v); |
| builder1.addExport("f", import_index); |
| let js_import = new WebAssembly.Suspending(() => Promise.resolve(1)); |
| let instance1 = builder1.instantiate({m: {import: js_import}}); |
| let builder2 = new WasmModuleBuilder(); |
| import_index = builder2.addImport('m', 'import', kSig_i_v); |
| builder2.addFunction("main", kSig_i_v) |
| .addBody([ |
| kExprCallFunction, import_index, |
| kExprI32Const, 1, |
| kExprI32Add, |
| ]).exportFunc(); |
| let instance2 = builder2.instantiate({m: {import: instance1.exports.f}}); |
| let wrapped_export = WebAssembly.promising(instance2.exports.main); |
| let combined_promise = wrapped_export(); |
| assertPromiseResult(combined_promise, v => assertEquals(2, v)); |
| })(); |
| |
| // Send a termination request to a worker with a full stack pool, to check that |
| // retiring the stack does not invalidate the stack memory that the unwinder is |
| // currently using. |
| (function TestTerminationWithFullStackPool() { |
| print(arguments.callee.name); |
| const builder = new WasmModuleBuilder(); |
| let js_async = builder.addImport('m', 'js_async', kSig_v_v); |
| let fill_stack_pool = builder.addImport('m', 'fill_stack_pool', kSig_v_v); |
| builder.addFunction('wasm_async', kSig_v_v).addBody([ |
| kExprCallFunction, js_async, |
| ]).exportFunc(); |
| builder.addFunction('main', kSig_v_v).addBody([ |
| kExprCallFunction, fill_stack_pool, |
| kExprLoop, kWasmVoid, kExprBr, 0, kExprEnd |
| ]).exportFunc(); |
| const module = builder.toModule(); |
| |
| function workerCode() { |
| onmessage = async function({data:module}) { |
| let wasm_async; |
| let instance = new WebAssembly.Instance(module, {m: { |
| js_async: new WebAssembly.Suspending(() => { |
| return Promise.resolve(); |
| }), |
| fill_stack_pool: new WebAssembly.Suspending(async () => { |
| let promises = []; |
| // Suspend multiple concurrent calls to create multiple stacks. |
| for (let i = 0; i < 50; ++i) { |
| promises.push(wasm_async()); |
| } |
| // Await them now, which returns the finished stacks to the stack pool. |
| for (let i = 0; i < 50; ++i) { |
| await promises[i]; |
| } |
| // Terminate the worker. This unwinds the main stack and attempts to |
| // return it to the stack pool, which is already full. |
| postMessage('terminate'); |
| }) |
| }}); |
| wasm_async = WebAssembly.promising(instance.exports.wasm_async); |
| WebAssembly.promising(instance.exports.main)(); |
| }; |
| } |
| |
| const worker = new Worker(workerCode, {type: 'function'}); |
| worker.postMessage(module); |
| assertEquals('terminate', worker.getMessage()); |
| worker.terminateAndWait(); |
| })(); |
| |
| // A promising export splits the logical stack into multiple segments in memory. |
| // Check that the stack frame iterator still iterates the full logical stack |
| // where we expect it to, e.g. in error stack traces. |
| (function TestStackTrace() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| import_index = builder.addImport('m', 'check_stack', kSig_v_v); |
| builder.addFunction("test", kSig_v_v) |
| .addBody([ |
| kExprCallFunction, import_index |
| ]).exportFunc(); |
| stacks = []; |
| function check_stack() { |
| let e = new Error(); |
| stacks.push(e.stack); |
| } |
| let instance = builder.instantiate({m: {check_stack}}); |
| let wrapper_sync = instance.exports.test; |
| let wrapper_async = WebAssembly.promising(instance.exports.test); |
| // Perform the calls in a loop so that the source location of the call is |
| // the same, which makes it easy to compare their call stack below. |
| for (let exp of [wrapper_sync, wrapper_async]) { |
| exp(); |
| } |
| assertEquals(stacks[0], stacks[1]); |
| })(); |
| |
| (function RegressPCAuthFailure() { |
| print(arguments.callee.name); |
| var builder = new WasmModuleBuilder(); |
| builder.addImport("mod", "func", kSig_v_v); |
| builder.addFunction("main", kSig_v_v) |
| .addBody([kExprCallFunction, 0]) |
| .exportAs("main"); |
| var main = builder.instantiate({ |
| mod: { |
| func: ()=>{%DebugTrace();} |
| } |
| }).exports.main; |
| WebAssembly.promising(main)(); |
| })(); |