921135c705
Use the existing generic js-to-wasm wrapper to handle arguments in the stack-switching export wrapper, by combining them into a single helper function parameterized by a boolean. If the stack_switch parameter is false, the generated js-to-wasm wrapper is the same as before. If the stack_switch parameter is true, we allocate and switch to the new stack before starting to process the parameters. To load the parameters, we also keep a pointer to the old stack. After the call, we convert the return value according to the return type as usual, and then switch back to the parent stack (which may be different than the original stack, but has a compatible stack frame layout). If the stack suspends during the call, control-flow jumps right before we deconstruct and leave the frame, and returns the Promise as an externref in the return register. R=ahaas@chromium.org,jkummerow@chromium.org CC=fgm@chromium.org Bug: v8:12191 Change-Id: If3f8eaba8edebe6e98d4738f79f895fdb5322adc Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3460410 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@{#79148}
229 lines
9.2 KiB
JavaScript
229 lines
9.2 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));
|
|
})();
|