7da19e25d2
Exceptions should propagate inside the logical stack, which can consist of multiple wasm stack segments. When the outermost frame of the current segment is reached, pick up the parent stack and continue the search from there, and update the state to reflect the implicit stack switch. Drive-by: cleanups. R=ahaas@chromium.org CC=fgm@chromium.org Bug: v8:12191, v8:12960 Change-Id: Ia5cb39a6ae197fb68e635f986952419dc43c7b98 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3695376 Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Andreas Haas <ahaas@chromium.org> Commit-Queue: Thibaud Michaud <thibaudm@chromium.org> Cr-Commit-Position: refs/heads/main@{#81208}
298 lines
12 KiB
JavaScript
298 lines
12 KiB
JavaScript
// Copyright 2021 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: --allow-natives-syntax --experimental-wasm-stack-switching
|
|
// Flags: --experimental-wasm-type-reflection --expose-gc
|
|
|
|
load("test/mjsunit/wasm/wasm-module-builder.js");
|
|
|
|
(function TestSuspender() {
|
|
print(arguments.callee.name);
|
|
let suspender = new WebAssembly.Suspender();
|
|
assertTrue(suspender.toString() == "[object WebAssembly.Suspender]");
|
|
assertThrows(() => WebAssembly.Suspender(), TypeError,
|
|
/WebAssembly.Suspender must be invoked with 'new'/);
|
|
})();
|
|
|
|
(function TestSuspenderTypes() {
|
|
print(arguments.callee.name);
|
|
let builder = new WasmModuleBuilder();
|
|
builder.addImport('m', 'import', kSig_v_i);
|
|
builder.addFunction("export", kSig_i_i)
|
|
.addBody([kExprLocalGet, 0]).exportFunc();
|
|
builder.addFunction("wrong1", kSig_ii_v)
|
|
.addBody([kExprI32Const, 0, kExprI32Const, 0]).exportFunc();
|
|
builder.addFunction("wrong2", kSig_v_v)
|
|
.addBody([]).exportFunc();
|
|
let suspender = new WebAssembly.Suspender();
|
|
function js_import(i) {
|
|
return Promise.resolve(42);
|
|
}
|
|
|
|
// Wrap the import, instantiate the module, and wrap the export.
|
|
let wasm_js_import = new WebAssembly.Function(
|
|
{parameters: ['i32'], results: ['externref']}, js_import);
|
|
let import_wrapper = suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
let instance = builder.instantiate({'m': {'import': import_wrapper}});
|
|
let export_wrapper =
|
|
suspender.returnPromiseOnSuspend(instance.exports.export);
|
|
|
|
// Check type errors.
|
|
wasm_js_import = new WebAssembly.Function(
|
|
{parameters: [], results: ['i32']}, js_import);
|
|
assertThrows(() => suspender.suspendOnReturnedPromise(wasm_js_import),
|
|
TypeError, /Expected a WebAssembly.Function with return type externref/);
|
|
assertThrows(() => suspender.returnPromiseOnSuspend(instance.exports.wrong1),
|
|
TypeError,
|
|
/Expected a WebAssembly.Function with exactly one return type/);
|
|
assertThrows(() => suspender.returnPromiseOnSuspend(instance.exports.wrong2),
|
|
TypeError,
|
|
/Expected a WebAssembly.Function with exactly one return type/);
|
|
// Signature mismatch (link error).
|
|
let wrong_import = new WebAssembly.Function(
|
|
{parameters: ['f32'], results: ['externref']}, () => {});
|
|
wrong_import = suspender.suspendOnReturnedPromise(wrong_import);
|
|
assertThrows(() => builder.instantiate({'m': {'import': wrong_import}}),
|
|
WebAssembly.LinkError,
|
|
/imported function does not match the expected type/);
|
|
|
|
// Check the wrapped export's signature.
|
|
let export_sig = WebAssembly.Function.type(export_wrapper);
|
|
assertEquals(['i32'], export_sig.parameters);
|
|
assertEquals(['externref'], export_sig.results);
|
|
|
|
// Check the wrapped import's signature.
|
|
let import_sig = WebAssembly.Function.type(import_wrapper);
|
|
assertEquals(['i32'], import_sig.parameters);
|
|
assertEquals(['externref'], import_sig.results);
|
|
})();
|
|
|
|
(function TestStackSwitchNoSuspend() {
|
|
print(arguments.callee.name);
|
|
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 suspender = new WebAssembly.Suspender();
|
|
let wrapper = suspender.returnPromiseOnSuspend(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 suspender = new WebAssembly.Suspender();
|
|
function js_import() {
|
|
return Promise.resolve(42);
|
|
};
|
|
let wasm_js_import = new WebAssembly.Function(
|
|
{parameters: [], results: ['externref']}, js_import);
|
|
let suspending_wasm_js_import =
|
|
suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
|
|
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
|
|
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
|
|
let combined_promise = wrapped_export();
|
|
combined_promise.then(v => assertEquals(42, v));
|
|
})();
|
|
|
|
// 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).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 suspender = new WebAssembly.Suspender();
|
|
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.Function(
|
|
{parameters: [], results: ['externref']}, js_import);
|
|
let suspending_wasm_js_import =
|
|
suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
|
|
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
|
|
let chained_promise = wrapped_export();
|
|
assertEquals(0, instance.exports.g.value);
|
|
chained_promise.then(_ => assertEquals(15, instance.exports.g.value));
|
|
})();
|
|
|
|
(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 instance = builder.instantiate({'m': {'gc': gc}});
|
|
let suspender = new WebAssembly.Suspender();
|
|
let wrapper = suspender.returnPromiseOnSuspend(instance.exports.test);
|
|
wrapper();
|
|
})();
|
|
|
|
// Check that the suspender does not suspend if the import's
|
|
// return value is not a promise.
|
|
(function TestStackSwitchNoPromise() {
|
|
print(arguments.callee.name);
|
|
let builder = new WasmModuleBuilder();
|
|
builder.addGlobal(kWasmI32, true).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();
|
|
let suspender = new WebAssembly.Suspender();
|
|
function js_import() {
|
|
return 42
|
|
};
|
|
let wasm_js_import = new WebAssembly.Function({parameters: [], results: ['externref']}, js_import);
|
|
let suspending_wasm_js_import = suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
|
|
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
|
|
let result = wrapped_export();
|
|
// TODO(thibaudm): Check the result's value once this is supported.
|
|
assertEquals(42, 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 sig = makeSig([kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32,
|
|
kWasmF32, kWasmF32, kWasmF32, kWasmF32, kWasmF32, kWasmF32, kWasmF32], [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();
|
|
let suspender = new WebAssembly.Suspender();
|
|
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.Function(
|
|
{parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'f32', 'f32',
|
|
'f32', 'f32', 'f32', 'f32', 'f32'], results: ['externref']}, js_import);
|
|
let suspending_wasm_js_import =
|
|
suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
|
|
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
|
|
let wrapped_export = suspender.returnPromiseOnSuspend(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);
|
|
combined_promise.then(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();
|
|
let suspender = new WebAssembly.Suspender();
|
|
function js_import() {
|
|
return Promise.resolve(0.5);
|
|
};
|
|
let wasm_js_import = new WebAssembly.Function(
|
|
{parameters: [], results: ['externref']}, js_import);
|
|
let suspending_wasm_js_import =
|
|
suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
|
|
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
|
|
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
|
|
let combined_promise = wrapped_export();
|
|
combined_promise.then(v => assertEquals(0.5, v));
|
|
})();
|
|
|
|
// Throw an exception after the initial prompt.
|
|
(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 suspender = new WebAssembly.Suspender();
|
|
let wrapper = suspender.returnPromiseOnSuspend(instance.exports.throw);
|
|
try {
|
|
wrapper();
|
|
assertUnreachable();
|
|
} catch (e) {
|
|
assertTrue(e instanceof 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();
|
|
let suspender = new WebAssembly.Suspender();
|
|
function js_import() {
|
|
return Promise.resolve(42);
|
|
};
|
|
let wasm_js_import = new WebAssembly.Function(
|
|
{parameters: [], results: ['externref']}, js_import);
|
|
let suspending_wasm_js_import =
|
|
suspender.suspendOnReturnedPromise(wasm_js_import);
|
|
|
|
let instance = builder.instantiate({m: {import: suspending_wasm_js_import, tag: tag}});
|
|
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
|
|
let combined_promise = wrapped_export();
|
|
assertThrowsAsync(combined_promise, WebAssembly.Exception);
|
|
})();
|