Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,58 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
// Flags: --wasm-inlining-call-indirect
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptCallIndirect() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
let add = builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
let mul = builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let table = builder.addTable(kWasmFuncRef, 2);
builder.addActiveElementSegment(table.index, wasmI32Const(0), [
[kExprRefFunc, add.index],
[kExprRefFunc, mul.index],
], kWasmFuncRef);
let mainSig =
makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallIndirect, funcRefT, table.index,
]).exportFunc();
let wasm = builder.instantiate().exports;
add = 0;
mul = 1;
assertEquals(42, wasm.main(12, 30, add));
%WasmTierUpFunction(wasm.main);
// Tier up.
assertEquals(42, wasm.main(12, 30, add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Deopt.
assertEquals(-360, wasm.main(12, -30, mul));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
assertEquals(42, wasm.main(12, 30, add));
// Re-optimize.
%WasmTierUpFunction(wasm.main);
assertEquals(360, wasm.main(12, 30, mul));
assertEquals(42, wasm.main(12, 30, add));
})();

View File

@ -0,0 +1,55 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptAfterDebuggingRequest() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
let enterDebuggingIndex = builder.addImport("m", 'enterDebugging', kSig_v_v);
let enterDebugging = () => %WasmEnterDebugging();
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
// if (local[0]) %WasmEnterDebugging();
kExprLocalGet, 0,
kExprIf, kWasmVoid,
kExprCallFunction, enterDebuggingIndex,
kExprEnd,
// call_ref(local[0], local[1], local[2])
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate({m: {enterDebugging}}).exports;
assertEquals(10, wasm.main(0, 10, wasm.add));
%WasmTierUpFunction(wasm.main);
// Not yet in debugging mode.
assertFalse(%IsWasmDebugFunction(wasm.main));
// Trigger debugging mode while the optimized "main" function is on the stack,
// followed by a deoptimization.
assertEquals(56, wasm.main(7, 8, wasm.mul));
// False because we deopted and false because we entered debugging.
assertFalse(%IsTurboFanFunction(wasm.main));
// We need to call it a second time, so that the function gets replaced with
// the "for debugging" version. (Same behavior without deopts enabled.)
assertEquals(0, wasm.main(0, 8, wasm.mul));
assertTrue(%IsWasmDebugFunction(wasm.main));
%WasmLeaveDebugging();
})();

View File

@ -0,0 +1,63 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff
// Flags: --wasm-tiering-budget=1000 --wasm-dynamic-tiering
// Flags: --no-predictable
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptTieringBudget() {
// This can be non-zero in certain variants (e.g. `code_serializer`).
let initialDeoptCount = %WasmDeoptsExecutedCount();
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(initialDeoptCount, %WasmDeoptsExecutedCount());
print("Running until tier up");
while (!%IsTurboFanFunction(wasm.main)) {
assertEquals(42, wasm.main(12, 30, wasm.add));
}
assertEquals(initialDeoptCount, %WasmDeoptsExecutedCount());
print("Tierup reached");
// Cause deopt.
assertEquals(360, wasm.main(12, 30, wasm.mul));
assertEquals(initialDeoptCount + 1, %WasmDeoptsExecutedCount());
print("Deopt reached");
// Run again until tierup.
while (!%IsTurboFanFunction(wasm.main)) {
assertEquals(42, wasm.main(12, 30, wasm.add));
assertEquals(360, wasm.main(12, 30, wasm.mul));
}
print("Tiered up again");
// If we are very unlucky, there can still be compilations with old feedback
// queued in the background that re-trigger new deopts with the wasm.mul
// target. Therefore we can't assert that the deopt count is still 1.
assertTrue(initialDeoptCount + %WasmDeoptsExecutedCount() < 20);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(42, wasm.main(12, 30, wasm.add));
assertEquals(360, wasm.main(12, 30, wasm.mul));
})();

View File

@ -0,0 +1,116 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Test progressing through the different "states" of feedback:
// - monomorphic
// - polymorphic (2-4 targets)
// - megamorphic
(function TestDeoptCallRef() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
builder.addFunction("sub", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub])
.exportFunc();
builder.addFunction("first", funcRefT)
.addBody([kExprLocalGet, 0])
.exportFunc();
builder.addFunction("second", funcRefT)
.addBody([kExprLocalGet, 1])
.exportFunc();
builder.addFunction("equals", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Eq,])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addLocals(kWasmI32, 1)
.addBody([
// Set the local to some value.
kExprI32Const, 1,
kExprLocalSet, 3,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
// Use the local after the deopt point.
kExprLocalGet, 3,
kExprI32Mul,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(42, wasm.main(12, 30, wasm.add));
%WasmTierUpFunction(wasm.main);
print("tierup");
assertEquals(42, wasm.main(12, 30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
print("non-deopt call succeeded, now causing deopt");
assertEquals(360, wasm.main(12, 30, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
print("deopt happened");
assertEquals(42, wasm.main(12, 30, wasm.add));
print("collect more feedback in liftoff");
assertEquals(-18, wasm.main(12, 30, wasm.sub));
print("re-opt");
%WasmTierUpFunction(wasm.main);
assertEquals(360, wasm.main(12, 30, wasm.mul));
assertEquals(42, wasm.main(12, 30, wasm.add));
assertEquals(-18, wasm.main(12, 30, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(12, wasm.main(12, 30, wasm.first));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
print("deopt happened");
print("re-opt with maximum polymorphism");
%WasmTierUpFunction(wasm.main);
assertEquals(360, wasm.main(12, 30, wasm.mul));
assertEquals(42, wasm.main(12, 30, wasm.add));
assertEquals(-18, wasm.main(12, 30, wasm.sub));
assertEquals(12, wasm.main(12, 30, wasm.first));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(30, wasm.main(12, 30, wasm.second));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
print("deopt happened");
print("reopt again");
%WasmTierUpFunction(wasm.main);
// All previous call targets still succeed.
assertEquals(360, wasm.main(12, 30, wasm.mul));
assertEquals(42, wasm.main(12, 30, wasm.add));
assertEquals(-18, wasm.main(12, 30, wasm.sub));
assertEquals(12, wasm.main(12, 30, wasm.first));
assertEquals(30, wasm.main(12, 30, wasm.second));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Previously unseen call targets do not cause a deopt any more.
assertEquals(1, wasm.main(42, 42, wasm.equals));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,67 @@
// 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: --wasm-deopt --allow-natives-syntax --liftoff
// Flags: --wasm-inlining --wasm-inlining-ignore-call-counts --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// This test case tests th scenario where the optimized function "main" doesn't
// have any parameter stack slots while the inner-most inlined function
// "innerInlinee" has multiple ones.
(function TestDeoptManyParameterStackSlotsInInlinedFunctionButNotInOuter() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let sigInner = makeSig([wasmRefType(funcRefT), kWasmI32, kWasmI32, kWasmI32,
kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32,
kWasmI32, kWasmI32], [kWasmI32]);
let innerInlinee = builder.addFunction("innerInlinee", sigInner)
.addBody([
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprLocalGet, 0,
kExprCallRef, funcRefT,
]);
let sigCallRef =
makeSig([wasmRefType(funcRefT)], [kWasmI32]);
let outerInlinee = builder.addFunction("outerInlinee", sigCallRef)
.addBody([
kExprLocalGet, 0, kExprI32Const, 30, kExprI32Const, 12, kExprI32Const, 0,
kExprI32Const, 0, kExprI32Const, 0, kExprI32Const, 0, kExprI32Const, 0,
kExprI32Const, 0, kExprI32Const, 0, kExprI32Const, 0, kExprI32Const, 0,
kExprI32Const, 0,
kExprCallFunction, innerInlinee.index,
]);
builder.addFunction("main", sigCallRef)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, outerInlinee.index,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(42, wasm.main(wasm.add));
%WasmTierUpFunction(wasm.main);
// tierup.
assertEquals(42, wasm.main(wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// deopt.
assertEquals(360, wasm.main(wasm.mul));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,92 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff
// Flags: --wasm-inlining-ignore-call-counts --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptInlinedStacktrace() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("div", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32DivS])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
let inlinee = builder.addFunction("inlinee", mainSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
builder.addFunction("main", mainSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallFunction, inlinee.index,
]).exportFunc();
let wasm = builder.instantiate().exports;
// Collect feedback.
assertEquals(42, wasm.main(12, 30, wasm.add));
// Trigger tierup.
%WasmTierUpFunction(wasm.main);
assertEquals(42, wasm.main(12, 30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Trigger deopt which then calls a target that throws, i.e. the stack trace
// contains frames created by the deoptimizer.
try {
wasm.main(10, 0, wasm.div);
assertUnreachable();
} catch (error) {
verifyException(error);
}
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
// Rerun unoptimized which should produce the same result.
try {
wasm.main(10, 0, wasm.div);
assertUnreachable();
} catch (error) {
verifyException(error);
}
%WasmTierUpFunction(wasm.main);
// Rerun optimized which should also produce the same result and not deopt.
try {
wasm.main(10, 0, wasm.div);
assertUnreachable();
} catch (error) {
verifyException(error);
}
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
function verifyException(error) {
assertMatches(/RuntimeError: divide by zero/, error + "");
let stack = error.stack.split("\n");
let expected = [
/RuntimeError: divide by zero/,
/at div .*wasm-function\[1\]:0x59/,
/at inlinee .*wasm-function\[2\]:0x63/,
/at main .*wasm-function\[3\]:0x6e/,
];
for (let i = 0; i < expected.length; ++i) {
assertMatches(expected[i], stack[i]);
}
}
})();

View File

@ -0,0 +1,104 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff
// Flags: --wasm-inlining-ignore-call-counts --no-jit-fuzzing
// Flags: --wasm-inlining-call-indirect
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptInlined() {
let inlineeIndex = 2;
let sig = 1;
// These different tests define the call "kind" for the inlining of the
// function called "inlinee", not the speculative inlining for add / mul.
let tests = [
{name: "callDirect", ops: [kExprCallFunction, inlineeIndex]},
{name: "callRef", ops: [kExprRefFunc, inlineeIndex, kExprCallRef, sig]},
{name: "callIndirect",
ops: [kExprI32Const, 0, kExprCallIndirect, sig, kTableZero]},
{name: "returnCall", ops: [kExprReturnCall, inlineeIndex]},
{name: "returnCallRef",
ops: [kExprRefFunc, inlineeIndex, kExprReturnCallRef, sig]},
{name: "returnCallIndirect",
ops: [kExprI32Const, 0, kExprReturnCallIndirect, sig, kTableZero]},
];
for (let test of tests) {
print(`test ${test.name}`);
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig = builder.addType(makeSig([kWasmI32, kWasmI32,
wasmRefType(funcRefT)], [kWasmI32]));
assertEquals(sig, mainSig);
let inlinee = builder.addFunction("inlinee", mainSig)
.addBody([
kExprLocalGet, 1,
kExprI32Const, 1,
kExprI32Add,
kExprLocalTee, 1,
kExprLocalGet, 0,
kExprI32Const, 1,
kExprI32Add,
kExprLocalTee, 0,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
assertEquals(inlinee.index, inlineeIndex);
builder.addFunction(test.name, mainSig)
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kExprI32Const, 1,
kExprI32Add,
kExprLocalGet, 1,
kExprI32Const, 1,
kExprI32Add,
kExprLocalGet, 2,
...test.ops,
]).exportFunc();
let table = builder.addTable(kWasmFuncRef, 1);
builder.addActiveElementSegment(table.index, wasmI32Const(0),
[[kExprRefFunc, inlinee.index]], kWasmFuncRef);
let wasm = builder.instantiate().exports;
let fct = wasm[test.name];
assertEquals(46, fct(12, 30, wasm.add));
// Tier up.
%WasmTierUpFunction(fct);
assertEquals(46, fct(12, 30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(fct));
}
// Cause deopt.
assertEquals(14 * 32, fct(12, 30, wasm.mul));
// Deopt happened.
if (%IsWasmTieringPredictable()) assertFalse(%IsTurboFanFunction(fct));
assertEquals(46, fct(12, 30, wasm.add));
// Trigger re-opt.
%WasmTierUpFunction(fct);
// Both call targets are used in the re-optimized function, so they don't
// trigger new deopts.
assertEquals(46, fct(12, 30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(fct));
}
assertEquals(14 * 32, fct(12, 30, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(fct));
}
}
})();

View File

@ -0,0 +1,124 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
// Test for different types of stack, local and literal values.
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Similar to deopt-value-types.js but focused on int64 which requires int64
// lowering.
(function TestDeoptTypesInt64Values() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_v_v);
builder.addFunction("nop1", funcRefT).addBody([]).exportFunc();
builder.addFunction("nop2", funcRefT).addBody([]).exportFunc();
let mainSig = makeSig([kWasmI64, wasmRefType(funcRefT)], [kWasmI64]);
let localConstant = (0x1n << 32n) + 0x2n;
let valueStackConstant = (0x12345n << 32n) + 0x54321n;
let addedValue = 123n;
let subtractedValue = 321n;
let argumentValue = 0x1234567887654321n;
builder.addFunction("main", mainSig)
.addLocals(kWasmI64, 3)
.addBody([
// Add a constant value as a local.
...wasmI64Const(localConstant),
kExprLocalSet, 2,
// Add a non-constant value as a local.
kExprLocalGet, 0,
...wasmI64Const(addedValue),
kExprI64Add,
kExprLocalSet, 3,
// Push a constant value to the value stack.
...wasmI64Const(valueStackConstant),
// Push a non-constant value to the value stack.
kExprLocalGet, 0,
...wasmI64Const(subtractedValue),
kExprI64Sub,
// Perform the call_ref.
kExprLocalGet, 1,
kExprCallRef, funcRefT,
// Use the locals in the now potentially deoptimized execution.
kExprLocalGet, 0, // the argument
kExprLocalGet, 2, // the constant local
kExprLocalGet, 3, // the non-constant local
kExprLocalGet, 4, // an uninitialized local (0)
kExprI64Add,
kExprI64Add,
kExprI64Add,
kExprI64Add,
kExprI64Add,
]).exportFunc();
let wasm = builder.instantiate().exports;
let expected = localConstant + valueStackConstant + addedValue
- subtractedValue + argumentValue * 3n;
assertEquals(expected, wasm.main(argumentValue, wasm.nop1));
%WasmTierUpFunction(wasm.main);
assertEquals(expected, wasm.main(argumentValue, wasm.nop1));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Deopt happened, the result should still be the same.
assertEquals(expected, wasm.main(argumentValue, wasm.nop2));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
})();
(function TestInlinedI64Params() {
let builder = new WasmModuleBuilder();
let funcRefT = builder.addType(makeSig([kWasmI64, kWasmI64], [kWasmI64]));
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI64Add]).exportFunc();
builder.addFunction("sub", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI64Sub]).exportFunc();
let callerSig =
makeSig([kWasmI64, kWasmI64, wasmRefType(funcRefT)], [kWasmI64]);
let inlinee = builder.addFunction("inlinee", callerSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
builder.addFunction("main", callerSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallFunction, inlinee.index,
]).exportFunc();
let wasm = builder.instantiate({}).exports;
assertEquals(43n, wasm.main(1n, 42n, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(43n, wasm.main(1n, 42n, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(-41n, wasm.main(1n, 42n, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
assertEquals(43n, wasm.main(1n, 42n, wasm.add));
assertEquals(-41n, wasm.main(1n, 42n, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,44 @@
// 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: --allow-natives-syntax --wasm-deopt --liftoff --no-jit-fuzzing
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(
makeSig([wasmRefType(kWasmI31Ref)], [kWasmI32]));
builder.addFunction("getS", calleeSig).addBody([
kExprLocalGet, 0, kGCPrefix, kExprI31GetS,
]).exportFunc();
builder.addFunction("getU", calleeSig).addBody([
kExprLocalGet, 0, kGCPrefix, kExprI31GetU,
]).exportFunc();
builder.addFunction("main", makeSig([wasmRefType(calleeSig)], [kWasmI32]))
.addBody([
...wasmI32Const(1689912565),
kGCPrefix, kExprRefI31,
kExprLocalGet, 0,
kExprCallRef, calleeSig,
]).exportFunc();
const instance = builder.instantiate({});
assertEquals(-457571083, instance.exports.main(instance.exports.getS));
%WasmTierUpFunction(instance.exports.main);
// Test value produced by DeoptimizerLiteral constant.
assertEquals(1689912565, instance.exports.main(instance.exports.getU));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instance.exports.main));
}
// Test value produced by Liftoff.
assertEquals(1689912565, instance.exports.main(instance.exports.getU));
%WasmTierUpFunction(instance.exports.main);
// Test value produced by Turboshaft.
assertEquals(1689912565, instance.exports.main(instance.exports.getU));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance.exports.main));
}

View File

@ -0,0 +1,113 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff
// Flags: --wasm-inlining-ignore-call-counts --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestManyParamsManyLocals() {
// x86-64 has 16 XMM registers, so we need a few values to run out of
// registers and end up with stack slots.
let paramCount = 21;
let builder = new WasmModuleBuilder();
let calleeParams = new Array(paramCount).fill(kWasmS128);
let funcRefT = builder.addType(makeSig(calleeParams, [kWasmS128]));
builder.addFunction("add", funcRefT)
.addBody(generateCalleeBody(kExprI32x4Add)).exportFunc();
builder.addFunction("sub", funcRefT)
.addBody(generateCalleeBody(kExprI32x4Sub)).exportFunc();
builder.addFunction("max", funcRefT)
.addBody(generateCalleeBody(kExprI32x4MaxU)).exportFunc();
let deoptingFct = builder.addFunction(
"deopting", makeSig([...calleeParams, wasmRefType(funcRefT)], [kWasmI32]))
.addBody([
// Push the arguments for the second call from the caller.
...pushArgs(paramCount + 1),
// Create new local arguments for the first call.
...createArgs(paramCount),
kExprLocalGet, paramCount,
kExprCallRef, funcRefT,
kExprLocalSet, 2,
kExprCallRef, funcRefT,
// Add both call_ref results and sum up the lanes.
kExprLocalGet, 2,
...SimdInstr(kExprI32x4Add),
kExprLocalTee, 2,
kSimdPrefix, kExprI32x4ExtractLane, 0,
kExprLocalGet, 2,
kSimdPrefix, kExprI32x4ExtractLane, 1,
kExprLocalGet, 2,
kSimdPrefix, kExprI32x4ExtractLane, 2,
kExprLocalGet, 2,
kSimdPrefix, kExprI32x4ExtractLane, 3,
kExprI32Add,
kExprI32Add,
kExprI32Add,
]).exportFunc();
let mainParams = [kWasmI32, wasmRefType(funcRefT)];
builder.addFunction("main", makeSig(mainParams, [kWasmI32]))
.addLocals(kWasmS128, 1)
.addBody([
...createArgs(paramCount),
kExprLocalGet, 1,
kExprCallFunction, deoptingFct.index,
]).exportFunc();
// [0, 1, ..., paramCount - 1]
let values = [...Array(paramCount).keys()];
let expectedSum = values.reduce((a, b) => a + b) * 4 * 2;
let expectedDiff = values.reduce((a, b) => a - b) * 4 * 2;
let expectedMax = (paramCount - 1) * 4 * 2;
assertEquals(expectedSum, -expectedDiff);
let wasm = builder.instantiate().exports;
assertEquals(expectedSum, wasm.main(0, wasm.add));
%WasmTierUpFunction(wasm.deopting);
assertEquals(expectedSum, wasm.main(0, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.deopting));
}
assertEquals(expectedDiff, wasm.main(0, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.deopting));
}
// Repeat the test but this time with an additional layer of inlining.
%WasmTierUpFunction(wasm.main);
assertEquals(expectedSum, wasm.main(0, wasm.add));
assertEquals(expectedDiff, wasm.main(0, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(expectedMax, wasm.main(0, wasm.max));
function generateCalleeBody(binop) {
let result = [kExprLocalGet, 0];
for (let i = 1; i < paramCount; ++i) {
result.push(kExprLocalGet, i, ...SimdInstr(binop));
}
return result;
}
function createArgs(paramCount) {
let result = [];
for (let i = 0; i < paramCount; ++i) {
result.push(kExprI32Const, i, kSimdPrefix, kExprI32x4Splat);
}
return result;
}
function pushArgs(paramCount) {
let result = [];
for (let i = 0; i < paramCount; ++i) {
result.push(kExprLocalGet, i);
}
return result;
}
})();

View File

@ -0,0 +1,144 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --expose-gc
// Flags: --wasm-inlining-ignore-call-counts --wasm-inlining-factor=30
// Flags: --wasm-inlining-budget=100000 --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Test deopt with many params with different types, some tagged, some untagged.
(function TestManyParamsTagged() {
let create = (function() {
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, false)]);
let array = builder.addArray(kWasmI32, true);
builder.addFunction("struct", makeSig([kWasmI32], [wasmRefType(struct)]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, struct,
]).exportFunc();
builder.addFunction("array", makeSig([kWasmI32], [wasmRefType(array)]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewFixed, array, 1,
]).exportFunc();
return builder.instantiate().exports;
})();
let paramCount = 30;
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, false)]);
let array = builder.addArray(kWasmI32, true);
let types = [
{type: kWasmI32, toI32: [], fromI32: (v) => v},
{type: wasmRefType(struct), toI32: [kGCPrefix, kExprStructGet, struct, 0],
fromI32: (v) => create.struct(v)},
{type: kWasmF64, toI32: [kExprI32SConvertF64], fromI32: (v) => v},
{type: wasmRefType(array),
toI32: [kExprI32Const, 0, kGCPrefix, kExprArrayGet, array],
fromI32: (v) => create.array(v)},
{type: kWasmF32, toI32: [kExprI32SConvertF32], fromI32: (v) => v},
{type: kWasmI31Ref, toI32: [kGCPrefix, kExprI31GetS], fromI32: (v) => v},
{type: kWasmI64, toI32: [kExprI32ConvertI64], fromI32: (v) => BigInt(v)},
];
let calleeParams = new Array(paramCount).fill()
.map((_, i) => types[i % types.length].type);
let funcRefT = builder.addType(makeSig(calleeParams, [kWasmI32]));
let gcImport = builder.addImport("i", "gc", makeSig([], []));
builder.addFunction("add", funcRefT)
.addBody(generateCalleeBody(kExprI32Add)).exportFunc();
builder.addFunction("sub", funcRefT)
.addBody(generateCalleeBody(kExprI32Sub)).exportFunc();
builder.addFunction("add2", funcRefT)
.addBody(generateCalleeBody(kExprI32Add)).exportFunc();
builder.addFunction("addGC", funcRefT)
.addBody([
kExprCallFunction, gcImport,
...generateCalleeBody(kExprI32Add)
]).exportFunc();
let mainParams = [...calleeParams, wasmRefType(funcRefT)];
let main = builder.addFunction("main", makeSig(mainParams, [kWasmI32]))
.addBody([
...pushArgs(paramCount + 1),
kExprCallRef, funcRefT,
// Repeat it to make sure that the parameter slots are still valid.
...pushArgs(paramCount + 1),
kExprCallRef, funcRefT,
// Add both results
kExprI32Add,
]).exportFunc();
// The outer params just contain an extra dummy param in the beginning, so
// they don't align with the parameters of the inner function.
let outerParams = [kWasmI32, ...mainParams];
builder.addFunction("outerDirect", makeSig(outerParams, [kWasmI32]))
.addBody([
...pushArgs(paramCount + 2),
kExprCallFunction, main.index,
kExprReturn,
]).exportFunc();
// [0, 1, ..., paramCount - 1]
let values = [...Array(paramCount).keys()];
let valuesTyped = values.map((_, i) => types[i % types.length].fromI32(i));
let expectedSum = 2 * values.reduce((a, b) => a + b);
let expectedDiff = 2 * values.reduce((a, b) => a - b);
assertEquals(expectedSum, -expectedDiff);
let wasm = builder.instantiate({i: {gc}}).exports;
assertEquals(expectedSum, wasm.main(...valuesTyped, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(expectedSum, wasm.main(...valuesTyped, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(expectedDiff, wasm.main(...valuesTyped, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
// Repeat the test but this time with an additional layer of inlining.
assertEquals(expectedSum, wasm.outerDirect(42, ...valuesTyped, wasm.add));
%WasmTierUpFunction(wasm.outerDirect);
assertEquals(expectedSum, wasm.outerDirect(42, ...valuesTyped, wasm.add));
assertEquals(expectedDiff, wasm.outerDirect(42, ...valuesTyped, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.outerDirect));
}
assertEquals(expectedSum, wasm.outerDirect(42, ...valuesTyped, wasm.add2));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.outerDirect));
}
%WasmTierUpFunction(wasm.outerDirect);
assertEquals(expectedSum, wasm.outerDirect(42, ...valuesTyped, wasm.add2));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.outerDirect));
}
assertEquals(expectedSum, wasm.outerDirect(42, ...valuesTyped, wasm.addGC));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.outerDirect));
}
function generateCalleeBody(binop) {
let result = [kExprLocalGet, 0, ...types[0].toI32];
for (let i = 1; i < paramCount; ++i) {
result.push(kExprLocalGet, i, ...types[i % types.length].toI32, binop);
}
return result;
}
function pushArgs(paramCount) {
let result = [];
for (let i = 0; i < paramCount; ++i) {
result.push(kExprLocalGet, i);
}
return result;
}
})();

View File

@ -0,0 +1,63 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff
// Flags: --wasm-inlining-ignore-call-counts --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestManyParams() {
let paramCount = 12;
let builder = new WasmModuleBuilder();
let calleeParams = new Array(paramCount).fill(kWasmI32);
let funcRefT = builder.addType(makeSig(calleeParams, [kWasmI32]));
builder.addFunction("add", funcRefT)
.addBody(generateCalleeBody(kExprI32Add)).exportFunc();
builder.addFunction("sub", funcRefT)
.addBody(generateCalleeBody(kExprI32Sub)).exportFunc();
let mainParams = [...calleeParams, wasmRefType(funcRefT)];
builder.addFunction("main", makeSig(mainParams, [kWasmI32]))
.addBody([
...pushArgs(paramCount + 1),
kExprCallRef, funcRefT,
]).exportFunc();
// [0, 1, ..., paramCount - 1]
let values = [...Array(paramCount).keys()];
let expectedSum = values.reduce((a, b) => a + b);
let expectedDiff = values.reduce((a, b) => a - b);
assertEquals(expectedSum, -expectedDiff);
let wasm = builder.instantiate().exports;
assertEquals(expectedSum, wasm.main(...values, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(expectedSum, wasm.main(...values, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(expectedDiff, wasm.main(...values, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
function generateCalleeBody(binop) {
let result = [kExprLocalGet, 0];
for (let i = 1; i < paramCount; ++i) {
result.push(kExprLocalGet, i, binop);
}
return result;
}
function pushArgs(paramCount) {
let result = [];
for (let i = 0; i < paramCount; ++i) {
result.push(kExprLocalGet, i);
}
return result;
}
})();

View File

@ -0,0 +1,129 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --expose-gc
// Flags: --wasm-inlining-ignore-call-counts --wasm-inlining-factor=30
// Flags: --wasm-inlining-budget=100000 --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Test deopt with many results with different types.
(function TestManyResults() {
let returnCount = 25;
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, false)]);
let array = builder.addArray(kWasmI32, true);
let gcImport = builder.addImport("i", "gc", makeSig([], []));
let createStruct = builder.addFunction("struct",
makeSig([kWasmI32], [wasmRefType(struct)]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, struct,
]).exportFunc();
let createArray = builder.addFunction("array",
makeSig([kWasmI32], [wasmRefType(array)]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewFixed, array, 1,
]).exportFunc();
let types = [
{type: kWasmI32, toI32: [], fromI32: []},
{type: wasmRefType(struct), toI32: [kGCPrefix, kExprStructGet, struct, 0],
fromI32: [kExprCallFunction, createStruct.index]},
{type: kWasmF64, toI32: [kExprI32SConvertF64],
fromI32: [kExprF64SConvertI32]},
{type: wasmRefType(array),
toI32: [kExprI32Const, 0, kGCPrefix, kExprArrayGet, array],
fromI32: [kExprCallFunction, createArray.index]},
{type: kWasmF32, toI32: [kExprI32SConvertF32],
fromI32: [kExprF32SConvertI32]},
{type: kWasmI31Ref, toI32: [kGCPrefix, kExprI31GetS],
fromI32: [kGCPrefix, kExprRefI31]},
{type: kWasmI64, toI32: [kExprI32ConvertI64],
fromI32: [kExprI64SConvertI32]},
];
let calleeReturns = new Array(returnCount).fill()
.map((_, i) => types[i % types.length].type);
let funcRefT = builder.addType(makeSig([], calleeReturns));
builder.addFunction("one", funcRefT)
.addBody(generateCalleeBody(1)).exportFunc();
builder.addFunction("two", funcRefT)
.addBody(generateCalleeBody(2)).exportFunc();
builder.addFunction("threeGC", funcRefT)
.addBody([
kExprCallFunction, gcImport,
...generateCalleeBody(3)
]).exportFunc();
builder.addFunction("four", funcRefT)
.addBody(generateCalleeBody(4)).exportFunc();
let main = builder.addFunction("passThrough",
makeSig([wasmRefType(funcRefT)], calleeReturns))
.addBody([
kExprLocalGet, 0,
kExprCallRef, funcRefT,
]).exportFunc();
builder.addFunction("passThroughExport",
makeSig([wasmRefType(funcRefT)], [kWasmI32]))
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, main.index,
...combine(),
]).exportFunc();
let expectedOne = returnCount * (returnCount + 1) / 2;
let expectedTwo = expectedOne + returnCount;
let expectedThree = expectedTwo + returnCount;
let expectedFour = expectedThree + returnCount;
let wasm = builder.instantiate({i: {gc}}).exports;
assertEquals(expectedOne, wasm.passThroughExport(wasm.one));
%WasmTierUpFunction(wasm.passThrough);
assertEquals(expectedTwo, wasm.passThroughExport(wasm.two));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.passThrough));
}
%WasmTierUpFunction(wasm.passThrough);
assertEquals(expectedThree, wasm.passThroughExport(wasm.threeGC));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.passThrough));
}
// This time tier up the outer function.
%WasmTierUpFunction(wasm.passThroughExport);
assertEquals(expectedFour, wasm.passThroughExport(wasm.four));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.passThrough));
}
function generateCalleeBody(value) {
let result = [];
for (let i = 0; i < returnCount; ++i) {
result.push(...wasmI32Const(value++), ...types[i % types.length].fromI32);
}
return result;
}
function combine() {
let result = [];
for (let i = 0; i < returnCount; ++i) {
result.push(
...types[(returnCount - i - 1) % types.length].toI32,
kExprLocalGet, 1,
kExprI32Add,
kExprLocalSet, 1
);
}
result.push(kExprLocalGet, 1);
return result;
}
})();

View File

@ -0,0 +1,57 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptMemoryStart() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addMemory(1, 1);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
// Store some value in the memory.
kExprI32Const, 24, // index
...wasmI32Const(0x12345678), // value
kExprI32StoreMem, 0, 0,
// Perform the call_ref with potential deopt.
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
// Load back the value and check that it's the same.
// Note that the cached memory start should have been invalidated by the
// call_ref, so this should reload the memory start.
kExprI32Const, 24,
kExprI32LoadMem, 0, 0,
...wasmI32Const(0x12345678),
kExprI32Ne,
kExprIf, kWasmVoid,
kExprUnreachable,
kExprEnd,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(42, wasm.main(12, 30, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(42, wasm.main(12, 30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(360, wasm.main(12, 30, wasm.mul));
})();

View File

@ -0,0 +1,57 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptMetrics() {
// This can be non-zero in certain variants (e.g. `code_serializer`).
const initialDeoptCount = %WasmDeoptsExecutedCount();
let builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
const initialDeoptCountMain = %WasmDeoptsExecutedForFunction(wasm.main);
assertEquals(initialDeoptCount, %WasmDeoptsExecutedCount());
assertEquals(42, wasm.main(12, 30, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(initialDeoptCount, %WasmDeoptsExecutedCount());
assertEquals(360, wasm.main(12, 30, wasm.mul));
assertEquals(initialDeoptCount + 1, %WasmDeoptsExecutedCount());
%WasmTierUpFunction(wasm.main);
assertEquals(42, wasm.main(12, 30, wasm.add));
assertEquals(360, wasm.main(12, 30, wasm.mul));
assertEquals(initialDeoptCount + 1, %WasmDeoptsExecutedCount());
assertEquals(0, %WasmDeoptsExecutedForFunction(wasm.add));
assertEquals(0, %WasmDeoptsExecutedForFunction(wasm.mul));
assertEquals(initialDeoptCountMain + 1,
%WasmDeoptsExecutedForFunction(wasm.main));
// A new instance shares the same counters for deopts.
let wasm2 = builder.instantiate().exports;
assertEquals(0, %WasmDeoptsExecutedForFunction(wasm2.add));
assertEquals(0, %WasmDeoptsExecutedForFunction(wasm2.mul));
assertEquals(initialDeoptCountMain + 1,
%WasmDeoptsExecutedForFunction(wasm2.main));
})();

View File

@ -0,0 +1,60 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Minimal test case for a wasm deopt.
// The following flags can help:
// --trace-deopt-verbose (information about the deopt input and output frames)
// --print-wasm-code (prints some information about the generated deopt data)
// --nodebug-code (keeps the generated code small)
// --code-comments (helpful comments in generated code if enabled in gn args)
(function TestDeoptMinimalCallRef() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig =
makeSig([kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
kExprI32Const, 12,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(42, wasm.main(30, wasm.add));
%WasmTierUpFunction(wasm.main);
// tierup.
assertEquals(42, wasm.main(30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Non-deopt call succeeded, now causing deopt.
assertEquals(360, wasm.main(30, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
// Deopt happened, executions are now in liftoff.
assertEquals(42, wasm.main(30, wasm.add));
// Re-opt.
%WasmTierUpFunction(wasm.main);
// There is feedback for both targets, they do not trigger new deopts.
assertEquals(360, wasm.main(30, wasm.mul));
assertEquals(42, wasm.main(30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,86 @@
// 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: --allow-natives-syntax --wasm-deopt --liftoff
// Flags: --wasm-inlining-call-indirect --wasm-inlining-ignore-call-counts
// See also inlining-multi-instance.js, from which this is adapted.
// This additionally checks that we don't run into a deopt loop.
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
function setup() {
const kInitialTableSize = 1;
let sharedTable = new WebAssembly.Table({
element: "anyfunc",
initial: kInitialTableSize,
});
let builder = new WasmModuleBuilder();
let type_i_v = builder.addType(kSig_i_v);
builder.addImportedTable("env", "table", kInitialTableSize);
// We use this global's value to differentiate which instance we are executing.
let importedGlobal = builder.addImportedGlobal("env", "g", kWasmI32);
builder
.addFunction("callee", kSig_i_v)
.addBody([kExprGlobalGet, importedGlobal, ...wasmI32Const(5), kExprI32Add])
.exportFunc();
builder
.addFunction("call_indirect", type_i_v)
.addBody([
...wasmI32Const(/* index of funcref in table */ 0),
kExprCallIndirect, type_i_v, kTableZero,
])
.exportFunc();
let wasmModule = builder.toModule();
// Both instances share the same table, but have different global values to
// distinguish them.
let instance1 = new WebAssembly.Instance(wasmModule, {
env: {
g: 1000,
table: sharedTable,
},
});
let instance2 = new WebAssembly.Instance(wasmModule, {
env: {
g: 2000,
table: sharedTable,
},
});
// And the table contains a reference to the function of the first instance.
sharedTable.set(0, instance1.exports.callee);
return {instance1, instance2};
}
let {instance1, instance2} = setup();
const func1 = instance1.exports.call_indirect;
const func2 = instance2.exports.call_indirect;
// Same-instance call can be inlined.
assertEquals(1005, func1());
%WasmTierUpFunction(func1);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(func1));
// Since we share code between instances, this is optimized as well.
assertTrue(%IsTurboFanFunction(func2));
}
// Cross-instance call triggers a deopt (and returns the correct result).
assertEquals(1005, func2());
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(func1));
assertFalse(%IsTurboFanFunction(func2));
}
// Re-optimizing after deopting due to non-inlineable target, we may not deopt
// again, otherwise we would have a deopt loop.
%WasmTierUpFunction(func2);
assertEquals(1005, func1());
assertEquals(1005, func2());
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(func1));
assertTrue(%IsTurboFanFunction(func2));
}

View File

@ -0,0 +1,106 @@
// 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: --wasm-deopt --allow-natives-syntax --no-jit-fuzzing --liftoff
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(makeSig([], [kWasmI32]));
let mainSig = builder.addType(makeSig([wasmRefType(calleeSig)], [kWasmI32]));
let global = builder.addGlobal(kWasmI32, true).exportAs("global");
builder.addFunction("a", calleeSig)
.exportFunc()
.addBody([
kExprGlobalGet, global.index,
kExprI32Const, 1,
kExprI32Add,
]);
builder.addFunction("b", calleeSig)
.exportFunc()
.addBody([
kExprGlobalGet, global.index,
kExprI32Const, 2,
kExprI32Add,
]);
builder.addFunction("main", mainSig).exportFunc()
.addBody([
kExprGlobalGet, global.index,
kExprI32Const, 10,
kExprI32Mul,
kExprLocalGet, 0,
kExprCallRef, calleeSig,
kExprI32Add,
]);
const instanceA = builder.instantiate({});
const instanceB = builder.instantiate({});
instanceA.exports.global.value = 10;
instanceB.exports.global.value = 20;
// Expected output:
// main: global * 10 + callee
// callee:
// - a: global + 1
// - b: global + 2
// Therefore we expect a three digit number:
// - first digit: 1 for instanceA.main, 2 for instanceB.main
// - second digit: 1 for instanceA.(a|b), 2 for instanceB.(a|b)
// - third digit: 1 for a, 2 for b
assertEquals(111, instanceA.exports.main(instanceA.exports.a));
%WasmTierUpFunction(instanceA.exports.main);
assertEquals(111, instanceA.exports.main(instanceA.exports.a));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instanceA.exports.main));
}
// This triggers a deopt as the callee is not function a and the optimized code
// is shared with instanceA.
assertEquals(222, instanceB.exports.main(instanceB.exports.b));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instanceB.exports.main));
}
// This re-optimizes the function with the feedback from instanceB.
// To prevent deopt loops, it must also keep the feedback from instanceA.
%WasmTierUpFunction(instanceB.exports.main);
assertEquals(222, instanceB.exports.main(instanceB.exports.b));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instanceA.exports.main));
}
// Calling a or b does not trigger a deopt.
assertEquals(111, instanceA.exports.main(instanceA.exports.a));
assertEquals(112, instanceA.exports.main(instanceA.exports.b));
assertEquals(221, instanceB.exports.main(instanceB.exports.a));
assertEquals(222, instanceB.exports.main(instanceB.exports.b));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instanceA.exports.main));
assertTrue(%IsTurboFanFunction(instanceB.exports.main));
}
// Note that so far no cross-intance calls have been performed.
// Doing so will trigger a deoptimization as cross-instance calls are not
// inlineable.
assertEquals(122, instanceA.exports.main(instanceB.exports.b));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instanceA.exports.main));
assertFalse(%IsTurboFanFunction(instanceB.exports.main));
}
// Tiering up again will not emit a deopt any more as the feedback contains a
// non-inlineable target.
%WasmTierUpFunction(instanceA.exports.main);
assertEquals(121, instanceA.exports.main(instanceB.exports.a));
assertEquals(122, instanceA.exports.main(instanceB.exports.b));
assertEquals(211, instanceB.exports.main(instanceA.exports.a));
assertEquals(212, instanceB.exports.main(instanceA.exports.b));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instanceA.exports.main));
assertTrue(%IsTurboFanFunction(instanceB.exports.main));
}

View File

@ -0,0 +1,113 @@
// 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: --wasm-deopt --allow-natives-syntax --no-jit-fuzzing --liftoff
// Flags: --wasm-inlining-ignore-call-counts --wasm-inlining-factor=15
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
(function TestMultipleModulesUninlineableTargets() {
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(makeSig([], [kWasmI32]));
let mainSig = builder.addType(makeSig([wasmRefType(calleeSig)], [kWasmI32]));
let callee0 = builder.addFunction("callee_0", calleeSig)
.exportFunc()
.addBody([kExprI32Const, 42]);
let inlinee = builder.addFunction("inlinee", mainSig).addBody([
// Trigger the deopt.
kExprLocalGet, 0,
kExprCallRef, calleeSig,
]);
builder.addFunction("main", mainSig).exportFunc()
.addBody([
// Call the inlinee.
kExprLocalGet, 0,
kExprCallFunction, inlinee.index,
// Just performing a call which triggers an update of the feedback vector.
kExprRefFunc, callee0.index,
kExprCallRef, calleeSig,
kExprI32Add,
]);
const instance = builder.instantiate({});
assertEquals(84, instance.exports.main(instance.exports.callee_0));
%WasmTierUpFunction(instance.exports.main);
assertEquals(84, instance.exports.main(instance.exports.callee_0));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance.exports.main));
}
const instance2 = builder.instantiate({});
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
assertEquals(84, instance2.exports.main(instance.exports.callee_0));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instance2.exports.main));
}
// Run it one more time, so that the call count to inlinee is > 0 for
// instance2 as otherwise the feedback doesn't get updated.
assertEquals(84, instance2.exports.main(instance.exports.callee_0));
%WasmTierUpFunction(instance2.exports.main);
assertEquals(84, instance2.exports.main(instance.exports.callee_0));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
})();
(function TestMultipleModulesUninlineableTargetsRecursiveFrames() {
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(makeSig([], [kWasmI32]));
let mainSig =
builder.addType(makeSig([kWasmI32, wasmRefType(calleeSig)], [kWasmI32]));
let callee0 = builder.addFunction("callee_0", calleeSig)
.exportFunc()
.addBody([kExprI32Const, 42]);
let mainIndex = callee0.index + 1;
let main = builder.addFunction("main", mainSig).exportFunc()
.addBody([
// Call itself recursively.
kExprLocalGet, 0,
kExprIf, kWasmVoid,
kExprLocalGet, 0,
kExprI32Const, 1,
kExprI32Sub,
kExprLocalGet, 1,
kExprCallFunction, mainIndex,
kExprDrop,
kExprEnd,
// Perform the deopting call_ref. (Only the inner-most frame will trigger
// it and deopt the outer frames for all inlined frames.)
kExprLocalGet, 1,
kExprCallRef, calleeSig,
]);
assertEquals(mainIndex, main.index);
const instance = builder.instantiate({});
assertEquals(42, instance.exports.main(7, instance.exports.callee_0));
%WasmTierUpFunction(instance.exports.main);
assertEquals(42, instance.exports.main(7, instance.exports.callee_0));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance.exports.main));
}
const instance2 = builder.instantiate({});
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
assertEquals(42, instance2.exports.main(7, instance.exports.callee_0));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instance2.exports.main));
}
%WasmTierUpFunction(instance2.exports.main);
assertEquals(42, instance2.exports.main(7, instance.exports.callee_0));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
})();

View File

@ -0,0 +1,94 @@
// 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: --wasm-deopt --allow-natives-syntax --no-jit-fuzzing --liftoff
// Flags: --wasm-inlining-call-indirect
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
(function TestCallRef() {
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(makeSig([], [kWasmI32]));
let mainSig = builder.addType(makeSig([wasmRefType(calleeSig)], [kWasmI32]));
builder.addFunction("callee_0", calleeSig)
.exportFunc()
.addBody([kExprI32Const, 42]);
builder.addFunction("main", mainSig).exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallRef, calleeSig,
]);
const instance = builder.instantiate({});
instance.exports.main(instance.exports.callee_0);
%WasmTierUpFunction(instance.exports.main);
instance.exports.main(instance.exports.callee_0);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance.exports.main));
}
const instance2 = builder.instantiate({});
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
instance2.exports.main(instance.exports.callee_0);
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instance2.exports.main));
}
%WasmTierUpFunction(instance2.exports.main);
instance2.exports.main(instance.exports.callee_0);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
})();
(function TestCallIndirect() {
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(makeSig([], [kWasmI32]));
let mainSig = builder.addType(makeSig([kWasmI32], [kWasmI32]));
let callee1 = builder.addFunction("callee1", calleeSig)
.exportFunc()
.addBody([kExprI32Const, 42]);
let callee2 = builder.addFunction("callee2", calleeSig)
.exportFunc()
.addBody([kExprI32Const, 10]);
let table = builder.addTable(kWasmFuncRef, 2);
builder.addActiveElementSegment(table.index, wasmI32Const(0), [
[kExprRefFunc, callee1.index],
[kExprRefFunc, callee2.index],
], kWasmFuncRef);
builder.addFunction("main", mainSig).exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallIndirect, calleeSig, table.index,
]);
const instance = builder.instantiate({});
instance.exports.main(0);
%WasmTierUpFunction(instance.exports.main);
instance.exports.main(0);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance.exports.main));
}
const instance2 = builder.instantiate({});
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
instance2.exports.main(1);
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(instance2.exports.main));
}
%WasmTierUpFunction(instance2.exports.main);
instance2.exports.main(1);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(instance2.exports.main));
}
})();

View File

@ -0,0 +1,61 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Simple test containing more than one deopt point.
(function TestMultipleDeoptPoints() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
let mainSig = makeSig(
[kWasmI32, kWasmI32, wasmRefType(funcRefT), kWasmI32,
wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
kExprLocalGet, 3,
kExprLocalGet, 4,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals((1 + 2) * 3, wasm.main(1, 2, wasm.add, 3, wasm.mul));
%WasmTierUpFunction(wasm.main);
assertEquals((1 + 2) * 3, wasm.main(1, 2, wasm.add, 3, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// New target on 2nd call_ref.
assertEquals((1 + 2) + 3, wasm.main(1, 2, wasm.add, 3, wasm.add));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
// New target on 1st call_ref.
assertEquals((1 * 2) * 3, wasm.main(1, 2, wasm.mul, 3, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
// New combination but no new targets.
assertEquals((1 * 2) + 3, wasm.main(1, 2, wasm.mul, 3, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,169 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptSignalingNanFloat32Literal() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(makeSig([kWasmF32], [kWasmI32]));
builder.addFunction("justOne", funcRefT)
.addBody([kExprI32Const, 1])
.exportFunc();
builder.addFunction("reinterpretF32", funcRefT)
.addBody([kExprLocalGet, 0, kExprI32ReinterpretF32])
.exportFunc();
let mainSig =
makeSig([wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
...wasmF32ConstSignalingNaN(),
kExprLocalGet, 0,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(1, wasm.main(wasm.justOne));
%WasmTierUpFunction(wasm.main);
assertEquals(1, wasm.main(wasm.justOne));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(0x7fa7a1b9, wasm.main(wasm.reinterpretF32));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
assertEquals(0x7fa7a1b9, wasm.main(wasm.reinterpretF32));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();
(function TestDeoptSignalingNanFloat32Value() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(makeSig([kWasmF32], [kWasmI32]));
let mem = builder.addMemory(1, 1);
builder.addActiveDataSegment(
mem.index, [kExprI32Const, 0], [0xb9, 0xa1, 0xa7, 0x7f]);
builder.addFunction("justOne", funcRefT)
.addBody([kExprI32Const, 1])
.exportFunc();
builder.addFunction("reinterpretF32", funcRefT)
.addBody([kExprLocalGet, 0, kExprI32ReinterpretF32])
.exportFunc();
let mainSig =
makeSig([wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
kExprI32Const, 0,
kExprF32LoadMem, 0, 0,
kExprLocalGet, 0,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(1, wasm.main(wasm.justOne));
%WasmTierUpFunction(wasm.main);
assertEquals(1, wasm.main(wasm.justOne));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(0x7fa7a1b9, wasm.main(wasm.reinterpretF32));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
assertEquals(0x7fa7a1b9, wasm.main(wasm.reinterpretF32));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();
(function TestDeoptSignalingNanFloat64Literal() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(makeSig([kWasmF64], [kWasmI64]));
builder.addFunction("justOne", funcRefT)
.addBody([kExprI64Const, 1])
.exportFunc();
builder.addFunction("reinterpretF64", funcRefT)
.addBody([kExprLocalGet, 0, kExprI64ReinterpretF64])
.exportFunc();
let mainSig =
makeSig([wasmRefType(funcRefT)], [kWasmI64]);
builder.addFunction("main", mainSig)
.addBody([
...wasmF64ConstSignalingNaN(),
kExprLocalGet, 0,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(1n, wasm.main(wasm.justOne));
%WasmTierUpFunction(wasm.main);
assertEquals(1n, wasm.main(wasm.justOne));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(0x7ff4000000000000n, wasm.main(wasm.reinterpretF64));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
assertEquals(0x7ff4000000000000n, wasm.main(wasm.reinterpretF64));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();
(function TestDeoptSignalingNanFloat64Value() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(makeSig([kWasmF64], [kWasmI64]));
let mem = builder.addMemory(1, 1);
builder.addActiveDataSegment(
mem.index, [kExprI32Const, 0],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x7f]);
builder.addFunction("justOne", funcRefT)
.addBody([kExprI64Const, 1])
.exportFunc();
builder.addFunction("reinterpretF32", funcRefT)
.addBody([kExprLocalGet, 0, kExprI64ReinterpretF64])
.exportFunc();
let mainSig =
makeSig([wasmRefType(funcRefT)], [kWasmI64]);
builder.addFunction("main", mainSig)
.addBody([
kExprI32Const, 0,
kExprF64LoadMem, 0, 0,
kExprLocalGet, 0,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(1n, wasm.main(wasm.justOne));
%WasmTierUpFunction(wasm.main);
assertEquals(1n, wasm.main(wasm.justOne));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertEquals(0x7ff4000000000000n, wasm.main(wasm.reinterpretF32));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
%WasmTierUpFunction(wasm.main);
assertEquals(0x7ff4000000000000n, wasm.main(wasm.reinterpretF32));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,114 @@
// 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: --wasm-deopt --allow-natives-syntax --liftoff
// Flags: --wasm-inlining --no-jit-fuzzing
// Flags: --wasm-inlining-call-indirect
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
(function TestDeoptWithNonInlineableTargetCallRef() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
let importMul = builder.addImport('m', 'mul', funcRefT);
builder.addExport('mul', importMul);
let mul = (a, b) => a * b;
builder.addFunction('add', funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
let mainSig =
makeSig([kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction('main', mainSig)
.addBody([
kExprI32Const, 12,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate({m: {mul}}).exports;
assertEquals(42, wasm.main(30, wasm.add));
%WasmTierUpFunction(wasm.main);
// Tier-up.
assertEquals(42, wasm.main(30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Non-deopt call succeeded, now causing deopt with imported function.
assertEquals(360, wasm.main(30, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
// Deopt happened, executions are now in Liftoff.
assertEquals(42, wasm.main(30, wasm.add));
// Re-opt.
%WasmTierUpFunction(wasm.main);
// There should be feedback for both targets (one of them being
// non-inlineable), they should not trigger new deopts.
assertEquals(360, wasm.main(30, wasm.mul));
assertEquals(42, wasm.main(30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();
(function TestDeoptWithNonInlineableTargetCallIndirect() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
let importMul = builder.addImport('m', 'mul', funcRefT);
builder.addExport('mul', importMul);
let mul = (a, b) => a * b;
let add = builder.addFunction('add', funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
let table = builder.addTable(kWasmFuncRef, 2);
builder.addActiveElementSegment(table.index, wasmI32Const(0), [
[kExprRefFunc, add.index],
[kExprRefFunc, importMul],
], kWasmFuncRef);
let mainSig =
makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]);
builder.addFunction('main', mainSig)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallIndirect, funcRefT, table.index,
]).exportFunc();
let wasm = builder.instantiate({m: {mul}}).exports;
let addTableIndex = 0;
let mulTableIndex = 1;
assertEquals(42, wasm.main(12, 30, addTableIndex));
%WasmTierUpFunction(wasm.main);
// Tier-up.
assertEquals(42, wasm.main(12, 30, addTableIndex));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Non-deopt call succeeded, now causing deopt with imported function.
assertEquals(360, wasm.main(12, 30, mulTableIndex));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
// Deopt happened, executions are now in Liftoff.
assertEquals(42, wasm.main(12, 30, addTableIndex));
// Re-opt.
%WasmTierUpFunction(wasm.main);
// There should be feedback for both targets (one of them being
// non-inlineable), they should not trigger new deopts.
assertEquals(360, wasm.main(12, 30, mulTableIndex));
assertEquals(42, wasm.main(12, 30, addTableIndex));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,58 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestNonInlinedTarget() {
let builder = new WasmModuleBuilder();
let funcRefT = builder.addType(makeSig([kWasmI32, kWasmI32], [kWasmI32]));
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add]).exportFunc();
builder.addFunction("sub", funcRefT)
.addBody([
// Some useless instructions to make this non-inlined due to size.
...wasmS128Const(100, 200), kExprDrop,
...wasmS128Const(100, 200), kExprDrop,
...wasmS128Const(100, 200), kExprDrop,
...wasmS128Const(100, 200), kExprDrop,
kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub]).exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul]).exportFunc();
let mainParams = [kWasmI32, kWasmI32, wasmRefType(funcRefT)];
builder.addFunction("main", makeSig(mainParams, [kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
]).exportFunc();
let wasm = builder.instantiate().exports;
// Add call feedback for add().
assertEquals(30, wasm.main(10, 20, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(30, wasm.main(10, 20, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
// Call with new target causing a deopt.
assertEquals(-10, wasm.main(10, 20, wasm.sub));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
// Re-opt. Due to the size of sub(), the target will not be inlined.
// This causes sub() to take the slow call_ref implementation and no deopt
// point is created.
%WasmTierUpFunction(wasm.main);
// Calling with a new call target therefore doesn't trigger a deopt.
assertEquals(200, wasm.main(10, 20, wasm.mul));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,98 @@
// 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.
// This test case reproduces a DCHECK failure where outstanding_baseline_units_
// in the CompilationStateImpl was decremented even though it was already 0.
//
// These are the steps required to reproduce which are tested here:
// 1) Instantiate a wasm module, collect feedback and optimize a wasm function
// inside it which includes a speculatively inlined call with a DeoptimizeIf
// for the bailout.
// 2) Serialize the wasm module.
// 3) Deserialize the wasm module, create a new instance for it without using
// the NativeModuleCache. This will mark the optimized function from (1) as
// RequiredBaselineTier = kTurbofan.
// 4) Trigger the deoptimization on this deserialized Turbofan function. The
// execution now goes back to Liftoff.
// 5) Continue running the Liftoff function until it triggers dynamic tier-up.
// (It has to be a background compilation job!)
// 6) When the tier-up finishes, it sees that the compiled tier (Turbofan) is
// the RequiredBaselineTier of that function while the reached tier is below
// the RequiredBaselineTier (the liftoff code created during deoptimization).
// It therefore decreases the outstanding_baseline_units_ for this instance.
// (Note that this counter is meant to be relevant only during the
// instantiation of a module (happening in step 3 above), afterwards it
// should never be modified again.)
// The code simply did not assume that we'd ever "drop below" the required
// baseline tier and then repeat a compilation with the required baseline
// tier, however with deopts this assumption doesn't hold true any more.
// This leads to a DCHECK failure then as the outstanding_baseline_units_
// should never be negative.
// Speculative inlining via deopts required:
// Flags: --wasm-inlining --wasm-deopt
// Dynamic tier-up required:
// Flags: --liftoff --wasm-dynamic-tiering
// The native module cache prevents hitting the bug:
// Flags: --no-wasm-native-module-cache
// Also reduce tiering budget to speed-up the busy wait on dynamic tier up:
// Flags: --wasm-tiering-budget=100 --expose-gc --allow-natives-syntax
// This test busy-waits for tier-up to be complete, hence it does not work in
// predictable more where we only have a single thread.
// Flags: --no-predictable
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptSerialized() {
let wire_bytes = (function createWireBytes() {
let builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
builder.addFunction("sub", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub])
.exportFunc();
let mainSig =
makeSig([kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addBody([
kExprI32Const, 12,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallRef, funcRefT,
]).exportFunc();
return builder.toBuffer();
})();
let module = new WebAssembly.Module(wire_bytes);
{ // Tier-up main function.
let wasm = new WebAssembly.Instance(module).exports;
assertEquals(42, wasm.main(30, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(42, wasm.main(30, wasm.add));
}
{ // Replace module with deserialized variant.
let buff = %SerializeWasmModule(module);
module = null;
gc(); gc();
module = %DeserializeWasmModule(buff, wire_bytes);
}
{
let wasm = new WebAssembly.Instance(module).exports;
assertEquals(360, wasm.main(30, wasm.mul));
// Trigger dynamic (background) tier-up.
while (!%IsTurboFanFunction(wasm.main)) {
assertEquals(360, wasm.main(30, wasm.mul));
}
// Trigger deopt again.
assertEquals(-18, wasm.main(30, wasm.sub));
}
})();

View File

@ -0,0 +1,52 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// The signal handler itself is completely unrelated to deopts. Still, when
// performing a deopt, the g_thread_in_wasm_code needs to be unset when calling
// into the deoptimizer.
(function TestDeoptSignalHandler() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("zero", funcRefT)
.addBody([kExprI32Const, 0])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
kExprLocalTee, 3,
kExprI32Eqz,
kExprIf, kWasmVoid,
kExprRefNull, kArrayRefCode,
kGCPrefix, kExprArrayLen, // traps,
kExprUnreachable,
kExprEnd,
kExprLocalGet, 3,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(42, wasm.main(12, 30, wasm.add));
%WasmTierUpFunction(wasm.main);
assertEquals(42, wasm.main(12, 30, wasm.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
assertThrows(() => wasm.main(12, 30, wasm.zero));
})();

View File

@ -0,0 +1,57 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
// Flags: --wasm-inlining-ignore-call-counts --wasm-inlining-factor=15
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// This test case covers a bug where a return call would result in not adding
// the parent FrameState to the deopt node. It is correct that the frame with
// the return call should be skipped (due to the nature of tail calls), however,
// any inlined parent frames of the return call (in this case the main function)
// need to be included.
//
// So, in this particular case at the deopt there should be a FrameState for:
// - inlinee1 (just prior to the callRef)
// - main (just behind the call) as a parent of inlinee1's FrameState
// But no FrameState for inlinee2.
(function TestDeoptInlinedReturnCall() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_v_v);
builder.addFunction("c1", funcRefT).addBody([]).exportFunc();
builder.addFunction("c2", funcRefT).addBody([]).exportFunc();
let inlineeSig = makeSig([wasmRefType(funcRefT)], []);
let inlinee1 = builder.addFunction("inlinee1", inlineeSig).addBody([
kExprLocalGet, 0,
kExprCallRef, funcRefT, // the deopt point.
]);
let inlinee2 = builder.addFunction("inlinee2", inlineeSig).addBody([
kExprLocalGet, 0,
kExprReturnCall, inlinee1.index,
]);
let mainSig =
makeSig([wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addLocals(kWasmI32, 1)
.addBody([
...wasmI32Const(1234567),
kExprLocalSet, 1,
kExprLocalGet, 0,
kExprCallFunction, inlinee2.index,
kExprLocalGet, 1,
]).exportFunc();
let wasm = builder.instantiate().exports;
assertEquals(1234567, wasm.main(wasm.c1));
%WasmTierUpFunction(wasm.main);
assertEquals(1234567, wasm.main(wasm.c2));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.main));
}
})();

View File

@ -0,0 +1,207 @@
// 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: --allow-natives-syntax --wasm-deopt --liftoff --wasm-inlining
// Flags: --no-jit-fuzzing
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let createUniqueModule = (function() {
// By using a different amount of locals on each module, they are going to be
// unique, so different test cases don't interfer with each other.
let localsCount = 0;
return function createModule() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_i_ii);
builder.addFunction("add", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("mul", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Mul])
.exportFunc();
builder.addFunction("sub", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub])
.exportFunc();
builder.addFunction("first", funcRefT)
.addBody([kExprLocalGet, 0])
.exportFunc();
builder.addFunction("second", funcRefT)
.addBody([kExprLocalGet, 1])
.exportFunc();
builder.addFunction("equals", funcRefT)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Eq,])
.exportFunc();
let mainSig =
makeSig([kWasmI32, kWasmI32, wasmRefType(funcRefT)], [kWasmI32]);
builder.addFunction("main", mainSig)
.addLocals(kWasmI32, ++localsCount)
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
kExprLocalTee, 3,
// If the result is 0, repeat the call, so that we have a second deopt
// point.
kExprI32Eqz,
kExprIf, kWasmVoid,
kExprI32Const, 0,
kExprI32Const, 0,
kExprLocalGet, 2,
kExprCallRef, funcRefT,
kExprDrop,
kExprEnd,
kExprLocalGet, 3,
]).exportFunc();
return builder.toModule();
}
})();
// Test a scenario where each instance only has polymorphic call feedback but
// the instsances combined sum up to more feedback slots than the maximum
// polymorphism (4).
(function TestCollectiveFeedbackMoreThanMaxPolymorphism() {
let module = createUniqueModule();
let wasm1 = new WebAssembly.Instance(module).exports;
// Collect polymorphic feedback for wasm1.
assertEquals(42, wasm1.main(30, 12, wasm1.add));
assertEquals(360, wasm1.main(30, 12, wasm1.mul));
assertEquals(18, wasm1.main(30, 12, wasm1.sub));
%WasmTierUpFunction(wasm1.main);
// Trigger deopt and collect polymorphic feedback for wasm2.
let wasm2 = new WebAssembly.Instance(module).exports;
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm2.main));
}
assertEquals(30, wasm2.main(30, 12, wasm2.first));
if (%IsWasmTieringPredictable()) {
// Deopt due to inlined target mismatch.
assertFalse(%IsTurboFanFunction(wasm2.main));
}
// Returning zero here means that the second call_ref in the main function now
// has feedback for "second".
assertEquals(0, wasm2.main(30, 0, wasm2.second));
// We have 5 different call targets, so the call is megamorphic. (The
// per-instance feedback for both instances is polymorphic.)
%WasmTierUpFunction(wasm2.main);
for (let wasm of [wasm1, wasm2]) {
assertEquals(42, wasm.main(30, 12, wasm.add));
assertEquals(360, wasm.main(30, 12, wasm.mul));
assertEquals(18, wasm.main(30, 12, wasm.sub));
assertEquals(30, wasm.main(30, 12, wasm.first));
assertEquals(12, wasm.main(30, 12, wasm.second));
// New targets don't trigger a de-opt either (as long as they don't return
// zero which would cause them executing the second call_ref).
assertEquals(1, wasm.main(30, 30, wasm.equals));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
}
let wasm3 = new WebAssembly.Instance(module).exports;
assertEquals(42, wasm3.main(30, 12, wasm3.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm3.main));
}
// Trigger deoptimization on second call_ref which so far has only seen the
// "second" function.
assertEquals(0, wasm3.main(12, 12, wasm3.sub));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm3.main));
}
// Now that we are back in liftoff, collect feedback for equals on the first
// call_ref as well.
assertEquals(0, wasm3.main(30, 12, wasm3.equals));
%WasmTierUpFunction(wasm3.main);
// The first call is still megamorphic, so we can call all functions without
// triggering a deopt (as long as the result isn't zero).
for (let wasm of [wasm1, wasm2, wasm3]) {
assertEquals(42, wasm.main(30, 12, wasm.add));
assertEquals(360, wasm.main(30, 12, wasm.mul));
assertEquals(18, wasm.main(30, 12, wasm.sub));
assertEquals(30, wasm.main(30, 12, wasm.first));
assertEquals(12, wasm.main(30, 12, wasm.second));
assertEquals(1, wasm.main(30, 30, wasm.equals));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
}
})();
// Test a scenario where the processed feedback is megamorphic based on the
// first module (using the megamorphic symbol in the feedback vector) and we get
// inlineable (in this case monomorphic but would be the same for polymorphic)
// feedback from the second module triggering a tier-up.
// The new tier-up should not overwrite / replace the megamorphic feedback.
(function TestMegamorphicFeedbackStaysMegamorphic() {
let module = createUniqueModule();
let wasm1 = new WebAssembly.Instance(module).exports;
// Collect megamorphic feedback for the first call_ref on wasm1.
assertEquals(42, wasm1.main(30, 12, wasm1.add));
assertEquals(360, wasm1.main(30, 12, wasm1.mul));
assertEquals(18, wasm1.main(30, 12, wasm1.sub));
assertEquals(30, wasm1.main(30, 12, wasm1.first));
assertEquals(0, wasm1.main(30, 0, wasm1.second));
%WasmTierUpFunction(wasm1.main);
// As the feedback is megamorphic, new call targets don't trigger a deopt.
assertEquals(1, wasm1.main(30, 30, wasm1.equals));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm1.main));
}
// Triggering a deopt with the first instance. (Note that for the second
// call_ref we have speculative inlining for wasm.second, so we can trigger a
// deopt on that call_ref.)
assertEquals(0, wasm1.main(0, 0, wasm1.add)); // MARKER_FOR_BELOW
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm1.main));
}
// Create a second instance. Initialize its feedback with a single target for
// both call_refs (sub).
let wasm2 = new WebAssembly.Instance(module).exports;
assertEquals(0, wasm2.main(30, 30, wasm2.sub));
// Tiering up with the feedback from the second instance, the merged feedback
// has to be megamorphic for the first call_ref.
%WasmTierUpFunction(wasm2.main);
for (let wasm of [wasm1, wasm2]) {
assertEquals(42, wasm.main(30, 12, wasm.add));
assertEquals(360, wasm.main(30, 12, wasm.mul));
assertEquals(18, wasm.main(30, 12, wasm.sub));
assertEquals(30, wasm.main(30, 12, wasm.first));
assertEquals(12, wasm.main(30, 12, wasm.second));
assertEquals(1, wasm.main(30, 30, wasm.equals));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.main));
}
}
// Just highlighting a shortcoming of the current implementation, the feedback
// for the second call_ref with wasm.add as a call target that triggered the
// deopt above can still trigger a deopt as we did not tier up with that
// feedback, yet, so the collected processed feedback doesn't know about it.
assertEquals(0, wasm2.main(0, 0, wasm2.add));
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm1.main));
assertFalse(%IsTurboFanFunction(wasm2.main));
}
// Now, if we trigger another tierup on wasm1, the feedback for the add call
// target for the second call_ref will be used. Note that this feedback was
// collected at the line with the "MARKER_FOR_BELOW" comment already, the
// deopt just above only collects feedback on the executing instance wasm2.
%WasmTierUpFunction(wasm1.main);
// No more deopt for add on the second call_ref.
assertEquals(0, wasm1.main(0, 0, wasm1.add));
assertEquals(0, wasm2.main(0, 0, wasm2.add));
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm1.main));
assertTrue(%IsTurboFanFunction(wasm2.main));
}
})();

View File

@ -0,0 +1,85 @@
// 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: --wasm-staging --wasm-deopt --liftoff
// Flags: --wasm-inlining-factor=15 --allow-natives-syntax --no-jit-fuzzing
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
// This test case covers a bug where the deoptimizer didn't correctly count the
// amount of parameter stack slots if there aren't any tagged stack slots
// present and there is at least one S128 parameter passed via stack slots.
// This test case reproduces this exact situation on x64, so all comments assume
// wasm calling conventions on x64.
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(makeSig([kWasmI32], [kWasmI32]));
let callee0 = builder.addFunction("callee0", calleeSig).addBody([
kExprLocalGet, 0,
]);
let callee1 = builder.addFunction("callee1", calleeSig).addBody([
kExprLocalGet, 0,
]);
let $global0 = builder.addGlobal(kWasmI32, true, false, wasmI32Const(0));
let $table0 = builder.addTable(kWasmFuncRef, 2, 2);
let $segment0 = builder.addActiveElementSegment($table0.index, wasmI32Const(0),
[[kExprRefFunc, callee0.index], [kExprRefFunc, callee1.index]], kWasmFuncRef);
let fctToOptimize = builder.addFunction("fctToOptimize", makeSig(
[ // Parameters
// 6 S128 values passed in registers xmm1 - xmm6 (see wasm-linkage.h).
kWasmS128, kWasmS128, kWasmS128, kWasmS128, kWasmS128, kWasmS128,
// 1 S128 value passed on the stack needing 2! stack slots.
kWasmS128,
// 6 i32 values passed in general purpose registers.
kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32,
// 1 i32 value passed on the stack needing 1 stack slot.
kWasmI32
],
[kWasmI32]))
.addBody([
// Perform a call to either callee0 or callee1 and pass the i32 value from
// the stack to the callee. Both callees will simply return that value.
kExprLocalGet, 13,
kExprGlobalGet, $global0.index,
kExprTableGet, $table0.index,
kGCPrefix, kExprRefCast, calleeSig,
kExprCallRef, calleeSig,
]).exportFunc();
builder.addFunction("main", kSig_i_i).addBody([
kExprLocalGet, 0, // $var0
kExprGlobalSet, $global0.index,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 0,
kSimdPrefix, kExprI8x16Splat,
kExprI32Const, 2,
...wasmI32Const(3),
...wasmI32Const(4),
kExprI32Const, 5,
...wasmI32Const(6),
kExprI32Const, 7,
// Pass the call index as the last parameter which will be passed via a
// stack slot.
kExprLocalGet, 0,
kExprCallFunction, fctToOptimize.index,
]).exportFunc();
const instance = builder.instantiate({});
assertEquals(0, instance.exports.main(0));
%WasmTierUpFunction(instance.exports.fctToOptimize);
assertEquals(1, instance.exports.main(1));

View File

@ -0,0 +1,52 @@
// 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: --wasm-staging --allow-natives-syntax --wasm-deopt
// Flags: --liftoff
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
(function TestDeoptWithUntaggedStackSlotsOnly() {
const builder = new WasmModuleBuilder();
let calleeSig = builder.addType(kSig_v_v);
let callee0 = builder.addFunction("c0", kSig_v_v).addBody([]);
let callee1 = builder.addFunction("c1", kSig_v_v).addBody([]);
let $table0 = builder.addTable(wasmRefNullType(calleeSig), 2, 2);
builder.addActiveElementSegment($table0.index, wasmI32Const(0),
[[kExprRefFunc, callee0.index], [kExprRefFunc, callee1.index]],
wasmRefNullType(calleeSig));
// The function uses the first argument as the call target in the table and
// then adds up all the parameters. The call_ref with the callee is only used
// to trigger the deopt.
let mainSig = builder.addType(makeSig([
kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32],
[kWasmI32]));
builder.addFunction("main", mainSig).addBody([
kExprLocalGet, 0,
kExprTableGet, $table0.index,
kExprCallRef, calleeSig,
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprLocalGet, 3,
kExprLocalGet, 4,
kExprLocalGet, 5,
kExprLocalGet, 6,
kExprI32Add,
kExprI32Add,
kExprI32Add,
kExprI32Add,
kExprI32Add,
kExprI32Add,
]).exportFunc();
const instance = builder.instantiate({});
const wasm = instance.exports;
assertEquals(0+1+2+3+4+5+6, wasm.main(0, 1, 2, 3, 4, 5, 6));
%WasmTierUpFunction(wasm.main);
assertEquals(1+1+2+3+4+5+6, wasm.main(1, 1, 2, 3, 4, 5, 6));
})();

View File

@ -0,0 +1,244 @@
// 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: --wasm-deopt --allow-natives-syntax
// Flags: --wasm-inlining --liftoff --no-jit-fuzzing
// Test for different types of stack, local and literal values.
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestDeoptTypesLiteralsInLocals() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_v_v);
builder.addFunction("nop1", funcRefT).addBody([]).exportFunc();
builder.addFunction("nop2", funcRefT).addBody([]).exportFunc();
let mainSig = makeSig([wasmRefType(funcRefT)], [kWasmF64]);
builder.addFunction("literals", mainSig)
.addLocals(kWasmI32, 1)
.addLocals(kWasmI64, 1)
.addLocals(kWasmF32, 1)
.addLocals(kWasmF64, 1)
.addLocals(kWasmI31Ref, 2)
.addLocals(kWasmS128, 1)
.addBody([
// Initialize the locals with non-default literal values.
...wasmI32Const(32),
kExprLocalSet, 1,
...wasmI64Const(64),
kExprLocalSet, 2,
...wasmF32Const(3.2),
kExprLocalSet, 3,
...wasmF64Const(6.4),
kExprLocalSet, 4,
...wasmI32Const(31),
kGCPrefix, kExprRefI31,
kExprLocalSet, 5,
kExprRefNull, kI31RefCode,
kExprLocalSet, 6,
...wasmS128Const(127, 129),
kExprLocalSet, 7,
// Perform the call_ref.
kExprLocalGet, 0,
kExprCallRef, funcRefT,
// Use the locals in the now potentially deoptimized execution.
kExprLocalGet, 1,
kExprF64SConvertI32,
kExprLocalGet, 2,
kExprF64SConvertI64,
kExprLocalGet, 3,
kExprF64ConvertF32,
kExprLocalGet, 4,
kExprLocalGet, 5,
kGCPrefix, kExprI31GetS,
kExprF64SConvertI32,
kExprLocalGet, 6,
kExprRefIsNull,
kExprF64SConvertI32,
kExprLocalGet, 7,
kSimdPrefix, kExprF64x2ExtractLane, 0,
kExprLocalGet, 7,
kSimdPrefix, kExprF64x2ExtractLane, 1,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
]).exportFunc();
let wasm = builder.instantiate().exports;
let expected = 32 + 64 + 3.2 + 6.4 + 31 + 1 + 127 + 129;
let delta = 1e-4;
assertEqualsDelta(expected, wasm.literals(wasm.nop1), delta);
%WasmTierUpFunction(wasm.literals);
assertEqualsDelta(expected, wasm.literals(wasm.nop1), delta);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.literals));
}
// Deopt happened, the result should still be the same.
assertEqualsDelta(expected, wasm.literals(wasm.nop2), delta);
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.literals));
}
})();
(function TestDeoptTypesLiteralsOnValueStack() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_v_v);
builder.addFunction("nop1", funcRefT).addBody([]).exportFunc();
builder.addFunction("nop2", funcRefT).addBody([]).exportFunc();
let mainSig = makeSig([wasmRefType(funcRefT)], [kWasmF64]);
builder.addFunction("literals", mainSig)
.addLocals(kWasmI32, 1)
.addLocals(kWasmI64, 1)
.addLocals(kWasmF32, 1)
.addLocals(kWasmF64, 1)
.addLocals(kWasmI31Ref, 2)
.addLocals(kWasmS128, 1)
.addBody([
...wasmI32Const(32),
...wasmI64Const(64),
...wasmF32Const(3.2),
...wasmF64Const(6.4),
...wasmI32Const(31),
kGCPrefix, kExprRefI31,
kExprRefNull, kI31RefCode,
...wasmS128Const(127, 129),
// Perform the call_ref.
kExprLocalGet, 0,
kExprCallRef, funcRefT,
// Use the values.
kExprLocalSet, 7,
kExprLocalSet, 6,
kExprLocalSet, 5,
kExprLocalSet, 4,
kExprLocalSet, 3,
kExprLocalSet, 2,
kExprF64SConvertI32,
kExprLocalGet, 2,
kExprF64SConvertI64,
kExprLocalGet, 3,
kExprF64ConvertF32,
kExprLocalGet, 4,
kExprLocalGet, 5,
kGCPrefix, kExprI31GetS,
kExprF64SConvertI32,
kExprLocalGet, 6,
kExprRefIsNull,
kExprF64SConvertI32,
kExprLocalGet, 7,
kSimdPrefix, kExprF64x2ExtractLane, 0,
kExprLocalGet, 7,
kSimdPrefix, kExprF64x2ExtractLane, 1,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
]).exportFunc();
let wasm = builder.instantiate().exports;
let expected = 32 + 64 + 3.2 + 6.4 + 31 + 1 + 127 + 129;
let delta = 1e-4;
assertEqualsDelta(expected, wasm.literals(wasm.nop1), delta);
%WasmTierUpFunction(wasm.literals);
assertEqualsDelta(expected, wasm.literals(wasm.nop1), delta);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.literals));
}
// Deopt happened, the result should still be the same.
assertEqualsDelta(expected, wasm.literals(wasm.nop2), delta);
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.literals));
}
})();
(function TestDeoptTypesNonLiterals() {
var builder = new WasmModuleBuilder();
let funcRefT = builder.addType(kSig_v_v);
builder.addFunction("nop1", funcRefT).addBody([]).exportFunc();
builder.addFunction("nop2", funcRefT).addBody([]).exportFunc();
let mainSig = makeSig([kWasmI32, wasmRefType(funcRefT)], [kWasmF64]);
builder.addFunction("locals", mainSig)
.addLocals(kWasmI32, 1)
.addLocals(kWasmI64, 1)
.addLocals(kWasmF32, 1)
.addLocals(kWasmF64, 1)
.addLocals(kWasmI31Ref, 1)
.addLocals(kWasmS128, 1)
.addBody([
// Initialize the locals with non-default non-literal values.
kExprLocalGet, 0,
kExprLocalTee, 2,
kExprI64SConvertI32,
kExprLocalTee, 3,
kExprF32SConvertI64,
kExprLocalTee, 4,
kExprF64ConvertF32,
kExprLocalSet, 5,
kExprLocalGet, 0,
kGCPrefix, kExprRefI31,
kExprLocalSet, 6,
kExprLocalGet, 0,
kExprF64SConvertI32,
kSimdPrefix, kExprF64x2Splat,
kExprLocalSet, 7,
// Perform the call_ref.
kExprLocalGet, 1,
kExprCallRef, funcRefT,
// Use the locals in the now potentially deoptimized stack.
kExprLocalGet, 2,
kExprF64SConvertI32,
kExprLocalGet, 3,
kExprF64SConvertI64,
kExprLocalGet, 4,
kExprF64ConvertF32,
kExprLocalGet, 5,
kExprLocalGet, 6,
kGCPrefix, kExprI31GetS,
kExprF64SConvertI32,
kExprLocalGet, 7,
kSimdPrefix, kExprF64x2ExtractLane, 0,
kExprLocalGet, 7,
kSimdPrefix, kExprF64x2ExtractLane, 1,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
kExprF64Add,
]).exportFunc();
let wasm = builder.instantiate().exports;
let delta = 1e-4;
assertEqualsDelta(42 * 7, wasm.locals(42, wasm.nop1), delta);
%WasmTierUpFunction(wasm.locals);
assertEqualsDelta(42 * 7, wasm.locals(42, wasm.nop1), delta);
if (%IsWasmTieringPredictable()) {
assertTrue(%IsTurboFanFunction(wasm.locals));
}
// Deopt happened, the result should still be the same.
assertEqualsDelta(42 * 7, wasm.locals(42, wasm.nop2), delta);
if (%IsWasmTieringPredictable()) {
assertFalse(%IsTurboFanFunction(wasm.locals));
}
})();