370 lines
No EOL
12 KiB
JavaScript
370 lines
No EOL
12 KiB
JavaScript
// 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"); |