node/deps/v8/test/inspector/debugger/wasm-jspi-async-stack.js
Michaël Zasso 9d7cd9b864
deps: update V8 to 12.8.374.13
PR-URL: https://github.com/nodejs/node/pull/54077
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
2024-08-16 16:03:01 +02:00

278 lines
7.7 KiB
JavaScript

// Copyright 2024 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-jspi --wasm-stack-switching-stack-size=100 --async-stack-traces
utils.load('test/inspector/wasm-inspector-test.js');
let {session, contextGroup, Protocol} = InspectorTest.start(
'Test async stack traces with wasm jspi');
// test.js
function instantiateWasm(bytes) {
const buffer = new ArrayBuffer(bytes.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; ++i) {
view[i] = bytes[i] | 0;
}
const module = new WebAssembly.Module(buffer);
async function js_func(f) {
return await f();
};
const wasmjs_func = new WebAssembly.Function(
{parameters:['externref', 'externref'], results:['i32']},
js_func,
{suspending: 'first'});
const instance = new WebAssembly.Instance(
module, {env: {wrapped: wasmjs_func}});
const wasmWrapperFunc = new WebAssembly.Function(
{parameters: ['externref'], results:['externref']},
instance.exports.threeTimes,
{promising: 'first'});
async function wrapperFunc(f) {
// JS function that calls wasm should show up on the call stack.
firstTime = true;
const p = wasmWrapperFunc(f);
console.log('Suspending wrapperFunc');
return await p;
}
return wrapperFunc;
}
function doPause() {
console.log(`Error location: ${new Error().stack}\n`);
debugger;
}
async function testStackSwitching()
{
async function alsoSimple() {
doPause();
return 2;
}
async function thread1() {
return await wrapperFunc(testSimple);
}
async function thread2() {
return await wrapperFunc(alsoSimple);
}
async function thread3() {
await Promise.resolve();
return await testSimple() + await testSimple() + await testSimple();
}
const result = await Promise.all([thread1(), thread2(), thread3()]);
return result.toString();
}
async function testSimple() {
doPause();
return 1;
}
async function testSetTimeout() {
const result = await new Promise(r => setTimeout(() => r(1), 0));
doPause();
return result;
}
async function testDoubleNested() {
async function innerPause() {
// Pause before and after await as callstacks will be different
doPause();
await Promise.resolve();
doPause();
// Throw so we don't do this nine times
throw 'early return';
}
try {
await wrapperFunc(innerPause);
} catch (e) {}
return 1;
}
async function testSyncThrow() {
throw 'fail';
}
async function testAsyncThrow() {
await Promise.resolve();
throw 'fail';
}
async function testSyncThrowAfterResume() {
if (firstTime) {
firstTime = false;
return await Promise.resolve(1);
} else {
throw 'fail';
}
}
async function testCatch(f) {
try {
await wrapperFunc(f);
} catch (e) {
console.log('caught: ' + e);
}
}
async function testDontCatch(f) {
let resolveFunc = null;
const done = new Promise(res => {resolveFunc = res});
wrapperFunc(f).finally(()=>resolveFunc());
await done;
}
// Generate Wasm module
const kSig_i_rr = makeSig([kWasmExternRef, kWasmExternRef], [kWasmI32]);
const builder = new WasmModuleBuilder();
// All functions take a suspender, the js async function to call, and
// return int.
// Add two functions that will be suspended by calling an async
// JS function
const wrapped_js = builder.addImport('env', 'wrapped', kSig_i_rr);
const wrapped_wasm = builder.addFunction(
'wrappedWasm', kSig_i_rr, ['suspender', 'js_func'])
.addBody([
// Load a parameter and call the import.
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, wrapped_js,
]);
const main = builder.addFunction('threeTimes', kSig_i_rr)
.addBody([
// Call function 'wrappedWasm' three times.
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, 1,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, wrapped_wasm.index,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, 1,
kExprI32Add,
kExprI32Add,
])
.exportFunc();
const module_bytes = builder.toArray();
// Create debuggee script
const helpers = [instantiateWasm, doPause, testStackSwitching];
const testPauseFunctions = [testSimple, testSetTimeout, testDoubleNested];
const testThrowFunctions = [
testSyncThrow, testAsyncThrow, testSyncThrowAfterResume];
const testCatchFunctions = [testCatch, testDontCatch];
const file = [
...helpers,
...testPauseFunctions,
...testThrowFunctions,
...testCatchFunctions].join('\n\n') + `
const wrapperFunc = instantiateWasm(${JSON.stringify(module_bytes)});
let firstTime = false;
Error.stackTraceLimit = 30;
`;
const startLine = 14; // Should match first line of first function
contextGroup.addScript(file, startLine, 0, 'test.js');
// Initialize debugger
let predictedUncaught = null;
let actuallyUncaught = null;
session.setupScriptMap();
Protocol.Runtime.enable();
Protocol.Debugger.enable();
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 6});
Protocol.Debugger.setPauseOnExceptions({state: 'all'});
Protocol.Debugger.onPaused(message => {
predictedUncaught = message.params.data?.uncaught;
InspectorTest.log('Debugger paused on ' + (
((predictedUncaught && 'uncaught exception') ?? 'debugger statement')
|| 'caught exception'));
session.logCallFrames(message.params.callFrames);
session.logAsyncStackTrace(message.params.asyncStackTrace);
InspectorTest.log('');
Protocol.Debugger.resume();
});
Protocol.Console.enable();
Protocol.Console.onMessageAdded(event => {
InspectorTest.log('console: ' + event.params.message.text);
});
Protocol.Runtime.onExceptionRevoked(event => {
actuallyUncaught = false;
});
Protocol.Runtime.onExceptionThrown(event => {
actuallyUncaught = true;
});
Protocol.Runtime.enable();
// Run tests
InspectorTest.runAsyncTestSuite([
async function testAsyncStackTracesOnPauseAndError() {
for (const testFunc of testPauseFunctions) {
InspectorTest.log(
`Testing async callstacks in JSPI with test function ${testFunc.name}`);
const {result} = await Protocol.Runtime.evaluate({
expression: `wrapperFunc(${testFunc.name})//# sourceURL=test_framework.js`,
awaitPromise: true});
InspectorTest.log(`Returned result ${JSON.stringify(result)}`);
InspectorTest.log('');
}
},
async function testAsyncStackTracesWithSwitching() {
InspectorTest.log(
`Testing async callstacks in JSPI with stack switching`);
const {result} = await Protocol.Runtime.evaluate({
expression: `testStackSwitching()//# sourceURL=test_framework.js`,
awaitPromise: true});
InspectorTest.log(`Returned result ${JSON.stringify(result)}`);
InspectorTest.log('');
},
async function testCatchPrediction() {
for (const testCatchFunc of testCatchFunctions) {
for (const testThrowFunc of testThrowFunctions) {
InspectorTest.log(
`Testing catch prediction through JSPI throwing from ${testThrowFunc.name} to ${testCatchFunc.name}`);
actuallyUncaught = false;
predictedUncaught = null;
const {result} = await Protocol.Runtime.evaluate({
expression: `${testCatchFunc.name}(${testThrowFunc.name})//# sourceURL=test_framework.js`,
awaitPromise: true});
InspectorTest.log(`Returned result ${JSON.stringify(result)}`);
if (actuallyUncaught) {
InspectorTest.log('Exception was not caught');
}
if (actuallyUncaught !== predictedUncaught) {
InspectorTest.log(
`PREDICTION MISMATCH: predicted uncaught=${predictedUncaught} actual uncaught=${actuallyUncaught}`);
}
InspectorTest.log('');
}
}
}
]);