mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
PR-URL: https://github.com/nodejs/node/pull/60111 Reviewed-By: Richard Lau <richard.lau@ibm.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
311 lines
9.1 KiB
JavaScript
311 lines
9.1 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: --turbolev-inline-js-wasm-wrappers --turboshaft-wasm-in-js-inlining
|
|
// Flags: --turbolev --no-maglev
|
|
// Flags: --trace-turbo-inlining --trace-deopt
|
|
// Flags: --allow-natives-syntax
|
|
|
|
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
|
|
d8.file.execute('test/mjsunit/mjsunit.js');
|
|
|
|
(function testJsWasmWrapperInliningWithI32Args() {
|
|
print("testJsWasmWrapperInliningWithI32Args");
|
|
var builder = new WasmModuleBuilder();
|
|
|
|
builder.addFunction("squareI32", kSig_i_i)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprI32Mul])
|
|
.exportAs("squareI32");
|
|
|
|
let instance = builder.instantiate({});
|
|
|
|
let square = instance.exports.squareI32;
|
|
function callSquare(n) {
|
|
return square(n);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callSquare);
|
|
assertEquals(1, callSquare(1));
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
assertEquals(9, callSquare(3));
|
|
|
|
// Convertible arguments do not trigger a deopt, but are converted to 0.
|
|
print("Test inputs of different convertible type");
|
|
assertEquals(0, callSquare({ x: 4 }));
|
|
assertEquals(0, callSquare("test"));
|
|
assertEquals(0, callSquare(undefined));
|
|
|
|
// Non convertible arguments throws an exception.
|
|
print("Test input of unconvertible type");
|
|
assertThrows(
|
|
() => callSquare(2n), TypeError);
|
|
|
|
// Eager-deopt expected if we change the callee.
|
|
print("Test different callee");
|
|
square = (a) => a + a;
|
|
assertEquals(6, callSquare(3));
|
|
|
|
// Optimizing again does not trigger another deoptimization.
|
|
print("Test optimizing again does not trigger another deoptimization");
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
assertEquals(6, callSquare(3));
|
|
assertTrue(%ActiveTierIsTurbofan(callSquare));
|
|
})();
|
|
|
|
(function testJsWasmWrapperInliningWithDifferentModules() {
|
|
print("testJsWasmWrapperInliningWithDifferentModules");
|
|
var builder1 = new WasmModuleBuilder();
|
|
builder1.addFunction("squareI32", kSig_i_i)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprI32Mul])
|
|
.exportAs("squareI32");
|
|
let instance1 = builder1.instantiate({});
|
|
|
|
let square = instance1.exports.squareI32;
|
|
function callSquare(n) {
|
|
return square(n);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callSquare);
|
|
assertEquals(25, callSquare(5));
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
assertEquals(9, callSquare(3));
|
|
|
|
var builder2 = new WasmModuleBuilder();
|
|
builder2.addFunction("doubleI32", kSig_i_i)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprI32Add])
|
|
.exportAs("squareI32"); // Export with same function name.
|
|
let instance2 = builder2.instantiate({});
|
|
|
|
// Eager-deopt expected if we change the callee.
|
|
print("Test callee from different instance");
|
|
square = instance2.exports.squareI32;
|
|
assertEquals(6, callSquare(3));
|
|
|
|
// Optimizing again does not trigger another deoptimization.
|
|
print("Test optimizing again does not trigger another deoptimization");
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
assertEquals(6, callSquare(3));
|
|
assertTrue(%ActiveTierIsTurbofan(callSquare));
|
|
})();
|
|
|
|
(function testJsWasmWrapperInliningOfSameExportedImportedFunction() {
|
|
print("testJsWasmWrapperInliningOfSameExportedImportedFunction");
|
|
var builder1 = new WasmModuleBuilder();
|
|
builder1.addFunction("squareI32", kSig_i_i)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprI32Mul])
|
|
.exportAs("squareI32");
|
|
builder1.addFunction("doubleI32", kSig_i_i)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprI32Add])
|
|
.exportAs("doubleI32");
|
|
let instance1 = builder1.instantiate({});
|
|
|
|
let square = instance1.exports.squareI32;
|
|
function callSquare(n) {
|
|
return square(n);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callSquare);
|
|
assertEquals(25, callSquare(5));
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
assertEquals(9, callSquare(3));
|
|
|
|
var builder2 = new WasmModuleBuilder();
|
|
builder2.addImport("o", "fn", kSig_i_i);
|
|
builder2.addExport("fn", 0);
|
|
let instance2 = builder2.instantiate({
|
|
o: { fn: instance1.exports.squareI32 }
|
|
});
|
|
// No deopt expected because it is the same function.
|
|
print("Test replacing callee with same function imported and exported by " +
|
|
"different modules");
|
|
square = instance2.exports.fn;
|
|
assertEquals(9, callSquare(3));
|
|
assertTrue(%ActiveTierIsTurbofan(callSquare));
|
|
|
|
var builder3 = new WasmModuleBuilder();
|
|
builder3.addImport("o", "fn", kSig_i_i);
|
|
builder3.addExport("fn", 0);
|
|
let instance3 = builder2.instantiate({
|
|
o: { fn: instance1.exports.doubleI32 }
|
|
});
|
|
// Deopt expected because it is a different function.
|
|
print("Test replacing callee with different function from same module");
|
|
square = instance3.exports.fn;
|
|
assertEquals(6, callSquare(3));
|
|
assertFalse(%ActiveTierIsTurbofan(callSquare));
|
|
// Optimizing again does not trigger another deoptimization.
|
|
print("Test optimizing again does not trigger another deoptimization");
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
assertEquals(6, callSquare(3));
|
|
assertTrue(%ActiveTierIsTurbofan(callSquare));
|
|
})();
|
|
|
|
(function testJsWasmWrapperInliningWithF32Args() {
|
|
print("testJsWasmWrapperInliningWithF32Args");
|
|
var builder = new WasmModuleBuilder();
|
|
|
|
builder.addFunction("squareF32", kSig_f_f)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprF32Mul])
|
|
.exportAs("squareF32");
|
|
|
|
let instance = builder.instantiate({});
|
|
|
|
function callSquare(n) {
|
|
return instance.exports.squareF32(n);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callSquare);
|
|
callSquare(1.1);
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
callSquare(3.3);
|
|
|
|
// Convertible arguments do not trigger a deopt, but are converted to NaN.
|
|
print("Test inputs of different convertible type");
|
|
assertEquals(NaN, callSquare({ x: 4 }));
|
|
assertEquals(NaN, callSquare("test"));
|
|
assertEquals(NaN, callSquare(undefined));
|
|
|
|
// Non convertible arguments throw an exception.
|
|
print("Test input of unconvertible type");
|
|
assertThrows(
|
|
() => callSquare(2n), TypeError);
|
|
})();
|
|
|
|
(function testJsWasmWrapperInliningWithF64Args() {
|
|
print("testJsWasmWrapperInliningWithF64Args");
|
|
var builder = new WasmModuleBuilder();
|
|
|
|
builder.addFunction("squareF64", kSig_d_d)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 0,
|
|
kExprF64Mul])
|
|
.exportAs("squareF64");
|
|
|
|
let instance = builder.instantiate({});
|
|
|
|
function callSquare(n) {
|
|
return instance.exports.squareF64(n);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callSquare);
|
|
callSquare(1.1);
|
|
%OptimizeFunctionOnNextCall(callSquare);
|
|
callSquare(3.3);
|
|
|
|
// Convertible arguments do not trigger a deopt, but are converted to NaN.
|
|
print("Test inputs of different convertible type");
|
|
assertEquals(NaN, callSquare({ x: 4 }));
|
|
assertEquals(NaN, callSquare("test"));
|
|
assertEquals(NaN, callSquare(undefined));
|
|
|
|
// Non convertible arguments throw an exception.
|
|
print("Test input of unconvertible type");
|
|
assertThrows(
|
|
() => callSquare(2n), TypeError);
|
|
})();
|
|
|
|
(function testJsWasmWrapperInliningWithExternRefArgs() {
|
|
print("testJsWasmWrapperInliningWithExternRefArgs");
|
|
function fn() {
|
|
return 42;
|
|
}
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
var kSig_r_r = makeSig([kWasmExternRef], [kWasmExternRef]);
|
|
|
|
builder.addFunction("refTest", kSig_r_r)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
])
|
|
.exportAs("refTest");
|
|
|
|
let instance = builder.instantiate({});
|
|
|
|
function callTest(f) {
|
|
return instance.exports.refTest(f);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callTest);
|
|
assertEquals(fn, callTest(fn));
|
|
print("Test input of externref type");
|
|
%OptimizeFunctionOnNextCall(callTest);
|
|
assertEquals(fn, callTest(fn));
|
|
})();
|
|
|
|
(function testNoJsWasmWrapperInliningWithRefArgs() {
|
|
print("testNoJsWasmWrapperInliningWithRefArgs");
|
|
function fn() {
|
|
return 42;
|
|
}
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
var kSig_a_a = makeSig([kWasmAnyFunc], [kWasmAnyFunc]);
|
|
|
|
builder.addFunction("refTest", kSig_a_a)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
])
|
|
.exportAs("refTest");
|
|
|
|
let instance = builder.instantiate({});
|
|
const refTest = instance.exports.refTest;
|
|
|
|
function callTest(f) {
|
|
// Funcref arguments do not get a JS-to-Wasm wrapper, so no inlining.
|
|
return refTest(f);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callTest);
|
|
assertEquals(refTest, callTest(refTest));
|
|
%OptimizeFunctionOnNextCall(callTest);
|
|
assertEquals(refTest, callTest(refTest));
|
|
})();
|
|
|
|
(function testJsWasmWrapperInliningRetrievesWasmInstance() {
|
|
print("testJsWasmWrapperInliningRetrievesWasmInstance");
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
builder.addGlobal(kWasmI32, true, false);
|
|
|
|
builder.addFunction("globalTest", kSig_i_i)
|
|
.addBody([
|
|
kExprLocalGet, 0,
|
|
kExprGlobalSet, 0,
|
|
kExprGlobalGet, 0,
|
|
])
|
|
.exportAs("globalTest");
|
|
|
|
let instance = builder.instantiate({});
|
|
const globalTest = instance.exports.globalTest;
|
|
|
|
function callTest(i32) {
|
|
// Verify that the Wasm instance is retrieved correctly.
|
|
return globalTest(i32);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(callTest);
|
|
assertEquals(321, callTest(321));
|
|
%OptimizeFunctionOnNextCall(callTest);
|
|
assertEquals(123, callTest(123));
|
|
})();
|