v8/test/mjsunit/wasm/stack-switching.js
Thibaud Michaud 921135c705 [wasm] Handle arguments in stack-switching export wrapper
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}
2022-02-17 16:00:05 +00:00

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));
})();