nodejs/deps/v8/test/mjsunit/wasm/deopt/deopt-too-much-feedback.js
Michaël Zasso 918fe04351
deps: update V8 to 13.6.233.8
PR-URL: https://github.com/nodejs/node/pull/58070
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
2025-05-02 15:06:53 +02:00

208 lines
8.4 KiB
JavaScript

// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --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));
}
})();