node/deps/v8/test/mjsunit/wasm/stack-switching.js
Michaël Zasso f1e0b83e7b
deps: update V8 to 14.6.202.33
PR-URL: https://github.com/nodejs/node/pull/61898
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2026-04-24 18:01:28 +02:00

404 lines
14 KiB
JavaScript

// Copyright 2025 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-wasmfx --expose-gc
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let builder = new WasmModuleBuilder();
let sig_v_v = builder.addType(kSig_v_v);
let cont_index = builder.addCont(sig_v_v);
let gc_index = builder.addImport("m", "gc", kSig_v_v);
builder.addExport("gc", gc_index);
let jspi_suspending_index = builder.addImport("m", "jspi_suspending", sig_v_v);
builder.addExport("jspi_suspending", jspi_suspending_index);
let call_next_from_js_index =
builder.addImport("m", "call_next_from_js", kSig_v_v);
builder.addExport("call_next_from_js", call_next_from_js_index);
let get_next_sig = builder.addType(makeSig([], [wasmRefNullType(sig_v_v)]));
let get_next_index = builder.addImport("m", "get_next", get_next_sig);
let tag0_index = builder.addTag(kSig_v_v);
let tag1_index = builder.addTag(kSig_v_v);
// A list of wasm functions where each function is expected to pop and call
// the next one in the list, to easily create specific call stacks.
let call_stack = builder.addGlobal(kWasmAnyRef, true).exportAs("call_stack");
let rejecting_promise_index = builder.addImport(
"m", "rejecting_promise", sig_v_v);
builder.addExport("rejecting_promise", rejecting_promise_index);
let g_cont = builder.addGlobal(wasmRefNullType(cont_index), true, false);
builder.addFunction("cont_new_null", kSig_v_v)
.addBody([
kExprRefNull, kNullFuncRefCode,
kExprContNew, cont_index,
kExprUnreachable,
]).exportFunc();
builder.addFunction("resume_null", kSig_v_v)
.addBody([
kExprRefNull, kNullContRefCode,
kExprResume, cont_index,
kExprUnreachable,
]).exportFunc();
builder.addFunction("throw_error", kSig_v_v)
.addBody([kExprThrow, tag0_index]).exportFunc();
builder.addFunction("call_next_as_cont", kSig_v_v)
.addBody([
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 0,
]).exportFunc();
builder.addFunction("call_next", kSig_v_v)
.addBody([
kExprCallFunction, get_next_index,
kExprCallRef, sig_v_v,
]).exportFunc();
builder.addFunction("call_next_in_catch_all", kSig_v_v)
.addBody([
kExprTryTable, kWasmVoid, 1,
kCatchAllNoRef, 0,
kExprCallFunction, get_next_index,
kExprCallRef, sig_v_v,
kExprEnd,
]).exportFunc();
let nop_index = builder.addFunction("nop", kSig_v_v).addBody([]).exportFunc().index;
builder.addFunction("resume_next_handle_tag0", kSig_v_v)
.addBody([
kExprBlock, kWasmRef, cont_index,
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 1, kOnSuspend, tag0_index, 0,
kExprUnreachable,
kExprEnd,
kExprDrop,
]).exportFunc();
builder.addFunction("resume_next_handle_tag1", kSig_v_v)
.addBody([
kExprBlock, kWasmRef, cont_index,
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 1, kOnSuspend, tag1_index, 0,
kExprUnreachable,
kExprEnd,
kExprDrop,
kExprUnreachable,
]).exportFunc();
builder.addFunction("resume_next_twice", kSig_v_v)
.addBody([
kExprBlock, kWasmRef, cont_index,
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 1, kOnSuspend, tag0_index, 0,
kExprUnreachable,
kExprEnd,
kExprResume, cont_index, 0,
]).exportFunc();
builder.addFunction("resume_next_with_handler_and_catch_all", kSig_i_v)
.addBody([
kExprBlock, kWasmVoid,
kExprTryTable, kWasmVoid, 1,
kCatchAllNoRef, 0,
kExprBlock, kWasmRef, cont_index,
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 1, kOnSuspend, tag0_index, 0,
kExprI32Const, 10,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 11,
kExprReturn,
kExprEnd,
kExprUnreachable,
kExprEnd,
kExprI32Const, 12,
kExprReturn,
]).exportFunc();
builder.addFunction("throw_exn", kSig_v_v)
.addBody([
kExprThrow, tag0_index
]).exportFunc();
let suspend_tag0 = builder.addFunction("suspend_tag0", kSig_v_v)
.addBody([
kExprSuspend, tag0_index
]).exportFunc();
builder.addFunction("suspend_tag1", kSig_v_v)
.addBody([
kExprSuspend, tag1_index
]).exportFunc();
builder.addFunction("resume_bad_cont_1", kSig_v_v)
.addBody([
kExprRefFunc, nop_index,
kExprContNew, cont_index,
kExprGlobalSet, g_cont.index,
kExprGlobalGet, g_cont.index,
kExprResume, cont_index, 0,
// Reusing cont after return.
kExprGlobalGet, g_cont.index,
kExprResume, cont_index, 0
]).exportFunc();
builder.addFunction("resume_bad_cont_2", kSig_v_v)
.addBody([
kExprBlock, kWasmRef, cont_index,
kExprRefFunc, suspend_tag0.index,
kExprContNew, cont_index,
kExprGlobalSet, g_cont.index,
kExprGlobalGet, g_cont.index,
kExprResume, cont_index, 1, kOnSuspend, tag0_index, 0,
kExprReturn,
kExprEnd,
kExprDrop,
// Reusing cont after suspend.
kExprGlobalGet, g_cont.index,
kExprResume, cont_index, 0
]).exportFunc();
let helper = builder.addFunction("resume_bad_cont_3_helper", kSig_v_v)
.addBody([
kExprGlobalGet, g_cont.index,
// Reusing cont before return/suspend.
kExprResume, cont_index, 0,
]).exportFunc();
builder.addFunction("resume_bad_cont_3", kSig_v_v)
.addLocals(wasmRefNullType(cont_index), 1)
.addBody([
kExprRefFunc, helper.index,
kExprContNew, cont_index,
kExprGlobalSet, g_cont.index,
kExprGlobalGet, g_cont.index,
kExprResume, cont_index, 0
]).exportFunc();
builder.addFunction("resume_next_with_two_handlers", kSig_i_v)
.addBody([
kExprBlock, kWasmVoid,
kExprBlock, kWasmRef, cont_index,
kExprBlock, kWasmRef, cont_index,
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 2,
kOnSuspend, tag0_index, 0,
kOnSuspend, tag1_index, 1,
kExprUnreachable,
kExprEnd,
kExprDrop,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
kExprEnd,
kExprUnreachable,
]).exportFunc();
builder.addFunction("resume_next_with_two_handlers_same_tag", kSig_i_v)
.addBody([
kExprBlock, kWasmVoid,
kExprBlock, kWasmRef, cont_index,
kExprBlock, kWasmRef, cont_index,
kExprCallFunction, get_next_index,
kExprContNew, cont_index,
kExprResume, cont_index, 2,
kOnSuspend, tag0_index, 0,
kOnSuspend, tag0_index, 1,
kExprUnreachable,
kExprEnd,
kExprDrop,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
kExprEnd,
kExprUnreachable,
]).exportFunc();
let loop_sig = builder.addType(makeSig([wasmRefNullType(cont_index)], [wasmRefNullType(cont_index)]));
builder.addFunction("handler_is_loop", kSig_i_v)
.addLocals(kWasmI32, 1)
.addBody([
kExprRefFunc, suspend_tag0.index,
kExprContNew, cont_index,
kExprLoop, loop_sig,
// The suspension is handled by the loop, so this should be
// executed twice:
kExprLocalGet, 0,
kExprI32Const, 1,
kExprI32Add,
kExprLocalSet, 0,
kExprResume, cont_index, 1,
kOnSuspend, tag0_index, 0,
// The second time, the continuation returns normally.
kExprLocalGet, 0,
kExprReturn,
kExprEnd,
kExprUnreachable,
]).exportFunc();
let instance;
instance = builder.instantiate( {m: {
gc,
jspi_suspending: new WebAssembly.Suspending(() => {}),
call_next_from_js: () => instance.exports.call_stack.value.shift()(),
get_next: () => instance.exports.call_stack.value.shift(),
rejecting_promise: new WebAssembly.Suspending(() => Promise.reject())
}});
(function TestNullRefs() {
print(arguments.callee.name);
assertThrows(instance.exports.cont_new_null, WebAssembly.RuntimeError);
assertThrows(instance.exports.resume_null, WebAssembly.RuntimeError);
})();
(function TestInitialResumeAndReturn() {
print(arguments.callee.name);
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.gc
];
instance.exports.call_next();
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_as_cont,
instance.exports.call_next_as_cont,
instance.exports.gc,
];
instance.exports.call_next();
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_as_cont,
instance.exports.call_next_as_cont,
instance.exports.throw_error,
];
assertThrows(instance.exports.call_next, WebAssembly.Exception);
})();
(function TestWasmFXResumeAndJSPISuspend() {
print(arguments.callee.name);
// Suspend multiple WasmFX stacks with JSPI.
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.jspi_suspending
];
WebAssembly.promising(instance.exports.call_next)();
// Throw if the top WasmFX stack contains JS frames:
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_from_js,
instance.exports.jspi_suspending
];
assertThrowsAsync(
WebAssembly.promising(instance.exports.call_next_as_cont)(),
WebAssembly.SuspendError);
// Throw if an intermediate stack contains JS frames.
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_from_js,
instance.exports.call_next_as_cont,
instance.exports.jspi_suspending
];
assertThrowsAsync(
WebAssembly.promising(instance.exports.call_next_as_cont)(),
WebAssembly.SuspendError);
// Catch error in an intermediate stack.
instance.exports.call_stack.value = [
instance.exports.call_next_in_catch_all,
instance.exports.call_next_as_cont,
instance.exports.rejecting_promise,
];
WebAssembly.promising(instance.exports.call_next_as_cont)()
})();
(function TestSuspend() {
print(arguments.callee.name);
instance.exports.call_stack.value = [
instance.exports.suspend_tag0
];
instance.exports.resume_next_twice();
// A resume instruction within a try scope triggers interesting code paths:
// the resume builtin call must be able to handle either exceptions or
// effects.
// Test the three possible successors of the resume instruction: normal
// return, exception handler and effect handler.
instance.exports.call_stack.value = [
instance.exports.nop
];
assertEquals(10, instance.exports.resume_next_with_handler_and_catch_all());
instance.exports.call_stack.value = [
instance.exports.suspend_tag0
];
assertEquals(11, instance.exports.resume_next_with_handler_and_catch_all());
instance.exports.call_stack.value = [
instance.exports.throw_exn
];
assertEquals(12, instance.exports.resume_next_with_handler_and_catch_all());
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_as_cont,
instance.exports.suspend_tag0,
];
instance.exports.resume_next_twice();
instance.exports.call_stack.value = [
instance.exports.resume_next_handle_tag1,
instance.exports.suspend_tag0
];
instance.exports.resume_next_handle_tag0();
})();
(function TestSuspendError() {
print(arguments.callee.name);
// Throw if the top WasmFX stack contains JS frames:
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_from_js,
instance.exports.suspend_tag0,
];
assertThrows(instance.exports.resume_next_handle_tag0,
WebAssembly.SuspendError, /WasmFX: unhandled suspend/);
// Throw if an intermediate stack contains JS frames.
instance.exports.call_stack.value = [
instance.exports.call_next_as_cont,
instance.exports.call_next_from_js,
instance.exports.call_next_as_cont,
instance.exports.suspend_tag0,
];
assertThrows(instance.exports.resume_next_handle_tag0,
WebAssembly.SuspendError, /WasmFX: unhandled suspend/);
instance.exports.call_stack.value = [
instance.exports.suspend_tag1];
assertThrows(instance.exports.resume_next_handle_tag0,
WebAssembly.SuspendError, /WasmFX: unhandled suspend/);
})();
(function TestInvalidContinuation() {
print(arguments.callee.name);
assertThrows(instance.exports.resume_bad_cont_1, WebAssembly.RuntimeError);
assertThrows(instance.exports.resume_bad_cont_2, WebAssembly.RuntimeError);
assertThrows(instance.exports.resume_bad_cont_3, WebAssembly.RuntimeError);
})();
(function TestMultipleHandlers() {
print(arguments.callee.name);
instance.exports.call_stack.value = [instance.exports.suspend_tag0];
assertEquals(0, instance.exports.resume_next_with_two_handlers());
instance.exports.call_stack.value = [instance.exports.suspend_tag1];
assertEquals(1, instance.exports.resume_next_with_two_handlers());
// If the same tag is used multiple times, use the first occurrence.
instance.exports.call_stack.value = [instance.exports.suspend_tag0];
assertEquals(0, instance.exports.resume_next_with_two_handlers_same_tag());
})();
(function TestLoopHandler() {
print(arguments.callee.name);
assertEquals(2, instance.exports.handler_is_loop());
})();