PR-URL: https://github.com/nodejs/node/pull/58070 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
654 lines
25 KiB
JavaScript
654 lines
25 KiB
JavaScript
// 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() {
|
|
d8.test.enableJSPI();
|
|
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)();
|
|
})();
|