nodejs/deps/v8/test/message/wasm-in-js-inlining-turboshaft.js
Michaël Zasso 5edec0e39a
deps: update V8 to 13.0.245.25
PR-URL: https://github.com/nodejs/node/pull/55014
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
2025-01-31 12:45:51 +01:00

289 lines
11 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: --turboshaft-wasm-in-js-inlining
// Flags: --allow-natives-syntax
// Flags: --turbofan --no-always-turbofan --no-always-sparkplug
// Only tier-up the test functions to get a cleaner and stable trace.
// Flags: --trace-turbo-inlining --turbo-filter='js_*'
// Concurrent inlining leads to additional traces.
// Flags: --no-stress-concurrent-inlining
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
d8.file.execute("test/mjsunit/mjsunit.js");
const builder = new WasmModuleBuilder();
const array = builder.addArray(kWasmI32, true);
const globalI32 = builder.addGlobal(kWasmI32, true, false);
const globalEqRef = builder.addGlobal(kWasmEqRef, true, false);
let tests = [];
// A helper to avoid repetition (e.g., the Wasm function name) and keep all
// pieces of one test case in one place (e.g., JS arguments, Wasm signature).
function addTestcase(name, wasmSignature, wasmArguments, wasmBody, jsFunctionSrc) {
assertEquals(wasmSignature.params.length, wasmArguments.length);
const wasmFunction = builder.addFunction(name, wasmSignature).addBody(wasmBody).exportFunc();
if (!jsFunctionSrc) {
// Pass the arguments from the JS function to the Wasm function so that they
// are not constant after inlining (otherwise constant folding often removes
// the code from the inlinee).
// Also, do not use the spread syntax (...args) to call the Wasm export, but
// instead explicitly list the arguments. Otherwise the JS-to-Wasm wrapper
// will not be inlined.
// TODO(353475584): Is it worth lifting this restriction? Is this a
// restriction also for regular JS inlining?
const argumentList = Array.from(wasmArguments, (x, i) => 'arg' + i);
jsFunctionSrc = `
function js_${name}(${argumentList}) {
return wasmExports.${name}(${argumentList});
}`;
}
tests[name] = { wasmArguments, jsFunctionSrc };
// Return the wasmFunction, so that it can still be manually modified.
return wasmFunction;
}
// =============================================================================
// Testcases where both JS-to-Wasm wrapper inlining and Wasm body inlining
// succeed:
// =============================================================================
addTestcase('empty', kSig_v_v, [], []);
addTestcase('nop', kSig_v_v, [], [kExprNop]);
addTestcase('i32Const', kSig_i_v, [], [...wasmI32Const(42)]);
addTestcase('f32Const', kSig_f_v, [], [...wasmF32Const(42.0)]);
addTestcase('f64Const', kSig_d_v, [], [...wasmF64Const(42.0)]);
function addUnaryTestcase(op, wasmSignature, wasmArgument) {
addTestcase(op, wasmSignature, [wasmArgument], [
kExprLocalGet, 0,
eval('kExpr' + op)
]);
}
addUnaryTestcase('I32Eqz', kSig_i_i, 0);
addUnaryTestcase('F32Abs', kSig_f_f, 0);
addUnaryTestcase('F32Neg', kSig_f_f, 0);
addUnaryTestcase('F32Sqrt', kSig_f_f, 0);
addUnaryTestcase('F64Abs', kSig_d_d, 0);
addUnaryTestcase('F64Neg', kSig_d_d, 0);
addUnaryTestcase('F64Sqrt', kSig_d_d, 0);
addUnaryTestcase('F64SConvertI32', kSig_d_i, 0);
addUnaryTestcase('F64UConvertI32', kSig_d_i, 0);
addUnaryTestcase('F32SConvertI32', kSig_f_i, 0);
addUnaryTestcase('F32UConvertI32', kSig_f_i, 0);
addUnaryTestcase('F32ConvertF64', kSig_f_d, 0);
addUnaryTestcase('F64ConvertF32', kSig_d_f, 0);
addUnaryTestcase('F32ReinterpretI32', kSig_f_i, 0);
addUnaryTestcase('I32ReinterpretF32', kSig_i_f, 0);
addUnaryTestcase('I32Clz', kSig_i_i, 0);
addUnaryTestcase('I32SExtendI8', kSig_i_i, 0);
addUnaryTestcase('I32SExtendI16', kSig_i_i, 0);
addUnaryTestcase('RefIsNull', kSig_i_r, null);
// We cannot pass Wasm any refs across the JS-Wasm boundary, hence convert to
// any and back in one test.
// TODO(dlehmann): Update output once arm64 no-ptr-compression calls are fixed.
addTestcase('anyConvertExternConvertAny', kSig_r_r, [{}], [
kExprLocalGet, 0,
kGCPrefix, kExprAnyConvertExtern,
kGCPrefix, kExprExternConvertAny,
]);
function addBinaryTestcase(op, wasmSignature, wasmArgument0, wasmArgument1) {
addTestcase(op, wasmSignature, [wasmArgument0, wasmArgument1], [
kExprLocalGet, 0,
kExprLocalGet, 1,
eval('kExpr' + op)
]);
}
addBinaryTestcase('I32Add', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Sub', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Mul', kSig_i_ii, 3, 7);
addBinaryTestcase('I32And', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Ior', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Xor', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Shl', kSig_i_ii, 3, 7);
addBinaryTestcase('I32ShrS', kSig_i_ii, 3, 7);
addBinaryTestcase('I32ShrU', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Ror', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Rol', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Eq', kSig_i_ii, 3, 7);
addBinaryTestcase('I32Ne', kSig_i_ii, 3, 7);
addBinaryTestcase('I32LtS', kSig_i_ii, 3, 7);
addBinaryTestcase('I32LeS', kSig_i_ii, 3, 7);
addBinaryTestcase('I32LtU', kSig_i_ii, 3, 7);
addBinaryTestcase('I32LeU', kSig_i_ii, 3, 7);
addBinaryTestcase('I32GtS', kSig_i_ii, 3, 7);
addBinaryTestcase('I32GeS', kSig_i_ii, 3, 7);
addBinaryTestcase('I32GtU', kSig_i_ii, 3, 7);
addBinaryTestcase('I32GeU', kSig_i_ii, 3, 7);
addBinaryTestcase('F32CopySign', kSig_f_ff, 3, 7);
addBinaryTestcase('F32Add', kSig_f_ff, 3, 7);
addBinaryTestcase('F32Sub', kSig_f_ff, 3, 7);
addBinaryTestcase('F32Mul', kSig_f_ff, 3, 7);
addBinaryTestcase('F32Div', kSig_f_ff, 3, 7);
addBinaryTestcase('F32Eq', kSig_i_ff, 3, 7);
addBinaryTestcase('F32Ne', kSig_i_ff, 3, 7);
addBinaryTestcase('F32Lt', kSig_i_ff, 3, 7);
addBinaryTestcase('F32Le', kSig_i_ff, 3, 7);
addBinaryTestcase('F32Gt', kSig_i_ff, 3, 7);
addBinaryTestcase('F32Ge', kSig_i_ff, 3, 7);
addBinaryTestcase('F32Min', kSig_f_ff, 3, 7);
addBinaryTestcase('F32Max', kSig_f_ff, 3, 7);
addBinaryTestcase('F64Add', kSig_d_dd, 3, 7);
addBinaryTestcase('F64Sub', kSig_d_dd, 3, 7);
addBinaryTestcase('F64Mul', kSig_d_dd, 3, 7);
addBinaryTestcase('F64Div', kSig_d_dd, 3, 7);
addBinaryTestcase('F64Eq', kSig_i_dd, 3, 7);
addBinaryTestcase('F64Ne', kSig_i_dd, 3, 7);
addBinaryTestcase('F64Lt', kSig_i_dd, 3, 7);
addBinaryTestcase('F64Le', kSig_i_dd, 3, 7);
addBinaryTestcase('F64Gt', kSig_i_dd, 3, 7);
addBinaryTestcase('F64Ge', kSig_i_dd, 3, 7);
addBinaryTestcase('F64Min', kSig_d_dd, 3, 7);
addBinaryTestcase('F64Max', kSig_d_dd, 3, 7);
// This currently cannot be inlined when passing `eqref`s as an argument from JS
// because the JS-to-Wasm wrapper inlining bails out in that case.
// So go through through globals instead.
addTestcase('RefEq', kSig_i_v, [], [
kExprGlobalGet, globalEqRef.index,
kExprGlobalGet, globalEqRef.index,
kExprRefEq,
])
// Function arguments and locals.
addTestcase('passthroughI32', kSig_i_i, [13], [kExprLocalGet, 0]);
addTestcase('localTee', kSig_i_i, [42], [
kExprLocalGet, 0,
...wasmI32Const(7),
kExprLocalTee, 1,
kExprI32Add,
kExprLocalGet, 1,
kExprI32Add,
]).addLocals(kWasmI32, 1);
addTestcase('localSwap', kSig_i_i, [42], [
...wasmI32Const(3),
kExprLocalSet, 2, // Initialize local_2 = 3.
// Ring-swap: local 2 to parameter, local 1 to local 2, parameter to local 1,
// stack as temporary.
kExprLocalGet, 0,
kExprLocalGet, 2,
kExprLocalSet, 0,
kExprLocalGet, 1,
kExprLocalSet, 2,
kExprLocalSet, 1,
// Use subtraction since it's not commutative.
kExprLocalGet, 2, // value: 0
kExprLocalGet, 1, // value: parameter
kExprI32Sub,
kExprLocalGet, 0, // value: 3
kExprI32Sub, // = (0 - p) - 3 = -10
]).addLocals(kWasmI32, 2);
addTestcase('globalSetGet', kSig_i_i, [42], [
kExprLocalGet, 0,
kExprGlobalSet, globalI32.index,
kExprGlobalGet, globalI32.index,
kExprGlobalGet, globalI32.index,
kExprI32Add,
kExprGlobalGet, globalI32.index,
kExprI32Add,
]);
// =============================================================================
// Testcases that we (currently) do not inline (the JS-to-Wasm wrapper or body):
// =============================================================================
// TODO(dlehmann,353475584): We want to support the following `arrayLen`
// testcase soon to reach feature parity with the TurboFan Wasm-in-JS inlining,
// but it's not there yet. `createArray` requires allocation, which might get
// complicated with allocation folding, so that's not in the MVP yet.
addTestcase('createArray', makeSig([kWasmI32], [kWasmExternRef]), [42], [
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewDefault, array,
kGCPrefix, kExprExternConvertAny,
]);
addTestcase('arrayLen', makeSig([kWasmExternRef], [kWasmI32]), [null], [
kExprLocalGet, 0,
kGCPrefix, kExprAnyConvertExtern,
kGCPrefix, kExprRefCastNull, array,
kGCPrefix, kExprArrayLen,
], `
const array = wasmExports.createArray(42);
function js_arrayLen() {
wasmExports.arrayLen(array);
}`);
// TODO(dlehmann,353475584): Support i64 in Wasm signatures when inlining the
// JS-to-Wasm wrapper on 32-bit architectures.
// addTestcase('passthroughI64', kSig_l_l, [13n], [kExprLocalGet, 0]);
// TODO(dlehmann,353475584): We don't support i64 ops yet, since that requires
// `Int64LoweringReducer`, which isn't compatible with the JS pipeline yet.
// Also, there is no wrapper inlining for i64 on 32-bit architectures right
// now, so pass as i32 on the boundary.
addTestcase(`i64Add`, kSig_i_ii, [13, 23], [
kExprLocalGet, 0,
kExprI64SConvertI32,
kExprLocalGet, 1,
kExprI64SConvertI32,
kExprI64Add,
kExprI32ConvertI64,
]);
// TODO(dlehmann,353475584): This would require supporting multi-value in
// JS-to-Wasm wrapper inlining first. (Right now, we bailout already at this
// first step.)
addTestcase('multiValue', kSig_ii_v, [], [
...wasmI32Const(3),
...wasmI32Const(7),
]);
// TODO(dlehmann,353475584): Support control-flow in the inlinee (much later).
addTestcase('brNoInline', kSig_i_v, [], [
...wasmI32Const(42),
// Need to use a `br`anch here, as `return` is actually optimized.
kExprBr, 0,
]);
addTestcase('trapNoInline', kSig_v_v, [], [
kExprUnreachable,
], `function js_trapNoInline() {
try {
wasmExports.trapNoInline();
} catch (e) {
return e.toString();
}
}`);
// Compile and instantiate the Wasm module, define the caller JS functions,
// call with arguments defined above.
const wasmExports = builder.instantiate({}).exports;
for (const [ name, { wasmArguments, jsFunctionSrc } ] of Object.entries(tests)) {
print(`\nTest: ${name}`);
// Use `eval` to define the JS functions, such that they have a more useful
// name during debugging and in the `--trace-turbo` output. (Otherwise they
// would be all called `jsFunction-0`, `-1`, etc.).
// We also cannot use the spread operator but need to generate slightly
// different code in the JS callers, see above.
eval(jsFunctionSrc);
const jsFunction = eval('js_' + name);
%PrepareFunctionForOptimization(jsFunction);
let resultUnopt = jsFunction(...wasmArguments);
assertUnoptimized(jsFunction);
%OptimizeFunctionOnNextCall(jsFunction);
let resultOpt = jsFunction(...wasmArguments);
assertOptimized(jsFunction);
assertEquals(resultUnopt, resultOpt);
}