// META: global=window,dedicatedworker,jsshell // META: script=/wasm/jsapi/wasm-module-builder.js // Test for invalid wrappers test(() => { assert_throws_js(TypeError, () => WebAssembly.promising({}), "Argument 0 must be a function"); assert_throws_js(TypeError, () => WebAssembly.promising(() => {}), "Argument 0 must be a WebAssembly exported function"); assert_throws_js(TypeError, () => WebAssembly.Suspending(() => {}), "WebAssembly.Suspending must be invoked with 'new'"); assert_throws_js(TypeError, () => new WebAssembly.Suspending({}), "Argument 0 must be a function"); function asmModule() { "use asm"; function x(v) { v = v | 0; } return x; } assert_throws_js(TypeError, () => WebAssembly.promising(asmModule()), "Argument 0 must be a WebAssembly exported function"); },"Valid use of API"); test(() => { let builder = new WasmModuleBuilder(); builder.addGlobal(kWasmI32, true).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(); assert_equals(42, instance.exports.g.value); },"Promising function always entered"); promise_test(async () => { let builder = new WasmModuleBuilder(); let import_index = builder.addImport('m', 'import', kSig_i_v); builder.addFunction("test", kSig_i_i) .addBody([ kExprCallFunction, import_index, // suspend ]).exportFunc(); let js_import = () => 42; let instance = builder.instantiate({ m: { import: js_import } }); let wrapped_export = WebAssembly.promising(instance.exports.test); let export_promise = wrapped_export(); assert_true(export_promise instanceof Promise); assert_equals(await export_promise, 42); }, "Always get a Promise"); promise_test(async () => { 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, // 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 export_promise = wrapped_export(); assert_true(export_promise instanceof Promise); assert_equals(await export_promise, 42); }, "Suspend once"); promise_test(async () => { let builder = new WasmModuleBuilder(); builder.addGlobal(kWasmI32, true).exportAs('g'); let import_index = builder.addImport('m', 'import', kSig_i_v); // void test() { // for (i = 0; i < 5; ++i) { // g = g + await import(); // } // } builder.addFunction("test", kSig_v_i) .addLocals({ i32_count: 1 }) .addBody([ kExprI32Const, 5, kExprLocalSet, 1, kExprLoop, kWasmStmt, kExprCallFunction, import_index, // suspend kExprGlobalGet, 0, kExprI32Add, kExprGlobalSet, 0, kExprLocalGet, 1, kExprI32Const, 1, kExprI32Sub, kExprLocalTee, 1, kExprBrIf, 0, kExprEnd, ]).exportFunc(); let i = 0; 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 export_promise = wrapped_export(); assert_equals(instance.exports.g.value, 0); assert_true(export_promise instanceof Promise); await export_promise; assert_equals(instance.exports.g.value, 15); }, "Suspend/resume in a loop"); promise_test(async () => { let builder = new WasmModuleBuilder(); let 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); assert_equals(await wrapped_export(), 42); // 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); assert_equals(await wrapped_export(), 42); // 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); assert_equals(await wrapped_export(), 42); },"Suspending with mismatched args and via Proxy"); function recordAbeforeB() { let AbeforeB = []; let setA = () => { AbeforeB.push("A") } let setB = () => { AbeforeB.push("B") } let isAbeforeB = () => AbeforeB[0] == "A" && AbeforeB[1] == "B"; let isBbeforeA = () => AbeforeB[0] == "B" && AbeforeB[1] == "A"; return { setA: setA, setB: setB, isAbeforeB: isAbeforeB, isBbeforeA: isBbeforeA, } } promise_test(async () => { let builder = new WasmModuleBuilder(); let AbeforeB = recordAbeforeB(); let import42_index = builder.addImport('m', 'import42', kSig_i_i); let importSetA_index = builder.addImport('m', 'setA', kSig_v_v); builder.addFunction("test", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprCallFunction, import42_index, // suspend? kExprCallFunction, importSetA_index ]).exportFunc(); let import42 = new WebAssembly.Suspending(() => Promise.resolve(42)); let instance = builder.instantiate({ m: { import42: import42, setA: AbeforeB.setA } }); let wrapped_export = WebAssembly.promising(instance.exports.test); let exported_promise = wrapped_export(); AbeforeB.setB(); assert_equals(await exported_promise, 42); assert_true(AbeforeB.isBbeforeA()); }, "Make sure we actually suspend"); promise_test(async () => { let builder = new WasmModuleBuilder(); let AbeforeB = recordAbeforeB(); let import42_index = builder.addImport('m', 'import42', kSig_i_i); let importSetA_index = builder.addImport('m', 'setA', kSig_v_v); builder.addFunction("test", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprCallFunction, import42_index, // suspend? kExprCallFunction, importSetA_index ]).exportFunc(); let import42 = new WebAssembly.Suspending(() => 42); let instance = builder.instantiate({ m: { import42: import42, setA: AbeforeB.setA } }); let wrapped_export = WebAssembly.promising(instance.exports.test); let exported_promise = wrapped_export(); AbeforeB.setB(); assert_equals(await exported_promise, 42); assert_true(AbeforeB.isBbeforeA()); }, "Do suspend even if the import's return value is not a Promise by wrapping it with Promise.resolve"); promise_test(async (t) => { let tag = new WebAssembly.Tag({ parameters: ['i32'] }); let builder = new WasmModuleBuilder(); let import_index = builder.addImport('m', 'import', kSig_i_i); let tag_index = builder.addImportedTag('m', 'tag', kSig_v_i); builder.addFunction("test", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprLocalGet, 0, kExprCallFunction, import_index, kExprCatch, tag_index, kExprEnd ]).exportFunc(); function js_import(unused) { 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 export_promise = wrapped_export(); assert_true(export_promise instanceof Promise); assert_equals(await export_promise, 42); }, "Catch rejected promise"); async function TestSandwich(suspend) { // Set up a 'sandwich' scenario. The call chain looks like: // top (wasm) -> outer (js) -> bottom (wasm) -> inner (js) // If 'suspend' is true, the inner JS function returns a Promise, which // suspends the bottom wasm function, which returns a Promise, which suspends // the top wasm function, which returns a Promise. The inner Promise // resolves first, which resumes the bottom continuation. Then the outer // promise resolves which resumes the top continuation. // If 'suspend' is false, the bottom JS function returns a regular value and // no computation is suspended. let builder = new WasmModuleBuilder(); let inner_index = builder.addImport('m', 'inner', kSig_i_i); let outer_index = builder.addImport('m', 'outer', kSig_i_i); builder.addFunction("top", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprCallFunction, outer_index ]).exportFunc(); builder.addFunction("bottom", kSig_i_i) .addBody([ kExprLocalGet, 0, kExprCallFunction, inner_index ]).exportFunc(); let inner = new WebAssembly.Suspending(() => suspend ? Promise.resolve(42) : 43); let export_inner; let outer = new WebAssembly.Suspending(() => export_inner()); let instance = builder.instantiate({ m: { inner, outer } }); export_inner = WebAssembly.promising(instance.exports.bottom); let export_top = WebAssembly.promising(instance.exports.top); let result = export_top(); assert_true(result instanceof Promise); if (suspend) assert_equals(await result, 42); else assert_equals(await result, 43); } promise_test(async () => { TestSandwich(true); }, "Test sandwich with suspension"); promise_test(async () => { TestSandwich(false); }, "Test sandwich with no suspension"); test(() => { // 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); assert_true(export_wrapper instanceof Function); },"Promising with no return"); promise_test(async () => { let builder1 = new WasmModuleBuilder(); let 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); assert_equals(await wrapped_export(), 3); },"Suspend two modules");