v8/test/mjsunit/wasm/stack-switching.js
Thibaud Michaud 3f695391b3 [wasm] Fix stack-switching import wrapper return
The JS import returns a tagged value, not a value of the machine
representation that corresponds to the signature's return type, since it
hasn't been converted yet.

R=ahaas@chromium.org

Bug: v8:12191
Change-Id: I0783af85eed9c5d25347200540e3e4eee48edfd4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3464036
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80213}
2022-04-27 10:19:01 +00:00

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