[llvm] [MCSched] Allow tuning LoadLatency/MispredictPenalty (PR #203139)
Sam Elliott via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 10 18:50:29 PDT 2026
https://github.com/lenary updated https://github.com/llvm/llvm-project/pull/203139
>From 5aa4b59c011ba9ad9b2fdcdd86e53bbe9b40feaa Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Wed, 10 Jun 2026 18:04:11 -0700
Subject: [PATCH 1/2] [MCSched] Allow tuning LoadLatency/MispredictPenalty
This means an existing scheduling model can be tweaked at runtime to
support an unknown processor rather than adding an entirely new model.
---
llvm/include/llvm/MC/MCSchedule.h | 8 ++
llvm/lib/CodeGen/EarlyIfConversion.cpp | 4 +-
llvm/lib/CodeGen/SelectOptimize.cpp | 2 +-
llvm/lib/CodeGen/TargetInstrInfo.cpp | 2 +-
llvm/lib/MC/MCSchedule.cpp | 28 +++++++
.../AArch64/AArch64ConditionalCompares.cpp | 2 +-
llvm/lib/Target/ARM/ARMSubtarget.cpp | 2 +-
llvm/lib/Target/RISCV/RISCVSubtarget.cpp | 2 +-
llvm/lib/Target/X86/X86CmovConversion.cpp | 2 +-
.../CodeGen/RISCV/sched-model-load-latency.ll | 43 +++++++++++
.../RISCV/sched-model-mispredict-penalty.ll | 74 +++++++++++++++++++
11 files changed, 161 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/CodeGen/RISCV/sched-model-load-latency.ll
create mode 100644 llvm/test/CodeGen/RISCV/sched-model-mispredict-penalty.ll
diff --git a/llvm/include/llvm/MC/MCSchedule.h b/llvm/include/llvm/MC/MCSchedule.h
index 8c21dbd0ae385..f0736c35bd0cd 100644
--- a/llvm/include/llvm/MC/MCSchedule.h
+++ b/llvm/include/llvm/MC/MCSchedule.h
@@ -426,6 +426,14 @@ struct MCSchedModel {
LLVM_ABI static unsigned getBypassDelayCycles(const MCSubtargetInfo &STI,
const MCSchedClassDesc &SCDesc);
+ /// Return the mispredict penalty in cycles, respecting any command-line
+ /// override via -sched-mispredict-penalty.
+ LLVM_ABI unsigned getMispredictPenalty() const;
+
+ /// Return the load latency in cycles, respecting any command-line override
+ /// via -sched-load-latency.
+ LLVM_ABI unsigned getLoadLatency() const;
+
/// Return the buffer size of the resource. If a positive scale factor
/// is provided and the original buffer size is > 1, the size is scaled
/// accordingly.
diff --git a/llvm/lib/CodeGen/EarlyIfConversion.cpp b/llvm/lib/CodeGen/EarlyIfConversion.cpp
index f178923070656..8cee019f8e7ea 100644
--- a/llvm/lib/CodeGen/EarlyIfConversion.cpp
+++ b/llvm/lib/CodeGen/EarlyIfConversion.cpp
@@ -1099,8 +1099,8 @@ bool EarlyIfConverter::shouldConvertIf() {
if (EnableDataDependentBranchAnalysis)
DataDependent = isConditionDataDependent();
- unsigned CritLimit = DataDependent ? SchedModel.MispredictPenalty
- : SchedModel.MispredictPenalty / 2;
+ unsigned CritLimit = DataDependent ? SchedModel.getMispredictPenalty()
+ : SchedModel.getMispredictPenalty() / 2;
MachineBasicBlock &MBB = *IfConv.Head;
MachineOptimizationRemarkEmitter MORE(*MBB.getParent(), nullptr);
diff --git a/llvm/lib/CodeGen/SelectOptimize.cpp b/llvm/lib/CodeGen/SelectOptimize.cpp
index 399adf4467d8a..2637e647876a1 100644
--- a/llvm/lib/CodeGen/SelectOptimize.cpp
+++ b/llvm/lib/CodeGen/SelectOptimize.cpp
@@ -1414,7 +1414,7 @@ SelectOptimizeImpl::computeInstCost(const Instruction *I) {
ScaledNumber<uint64_t>
SelectOptimizeImpl::getMispredictionCost(const SelectLike SI,
const Scaled64 CondCost) {
- uint64_t MispredictPenalty = TSchedModel.getMCSchedModel()->MispredictPenalty;
+ uint64_t MispredictPenalty = TSchedModel.getMCSchedModel()->getMispredictPenalty();
// Account for the default misprediction rate when using a branch
// (conservatively set to 25% by default).
diff --git a/llvm/lib/CodeGen/TargetInstrInfo.cpp b/llvm/lib/CodeGen/TargetInstrInfo.cpp
index f3666b05464b7..cba1b8949f6f9 100644
--- a/llvm/lib/CodeGen/TargetInstrInfo.cpp
+++ b/llvm/lib/CodeGen/TargetInstrInfo.cpp
@@ -1818,7 +1818,7 @@ unsigned TargetInstrInfo::defaultDefLatency(const MCSchedModel &SchedModel,
if (DefMI.isTransient())
return 0;
if (DefMI.mayLoad())
- return SchedModel.LoadLatency;
+ return SchedModel.getLoadLatency();
if (isHighLatencyDef(DefMI.getOpcode()))
return SchedModel.HighLatency;
return 1;
diff --git a/llvm/lib/MC/MCSchedule.cpp b/llvm/lib/MC/MCSchedule.cpp
index e2124c1c76355..0aa7f8e6ff4dc 100644
--- a/llvm/lib/MC/MCSchedule.cpp
+++ b/llvm/lib/MC/MCSchedule.cpp
@@ -27,6 +27,18 @@ cl::OptionCategory llvm::MCScheduleOptions("Machine scheduling model options");
static constexpr float DefaultReservationStationScaleFactor = 1.0f;
+static cl::opt<int> SchedMispredictPenalty(
+ "sched-mispredict-penalty", cl::Hidden, cl::init(-1),
+ cl::cat(MCScheduleOptions),
+ cl::desc("Override the mispredict penalty (in cycles) in the scheduler "
+ "model. A non-negative value overrides the target default."));
+
+static cl::opt<int> SchedLoadLatency(
+ "sched-load-latency", cl::Hidden, cl::init(-1),
+ cl::cat(MCScheduleOptions),
+ cl::desc("Override the load latency (in cycles) in the scheduler model. "
+ "A non-negative value overrides the target default."));
+
static cl::opt<float> ReservationStationScaleFactor(
"sched-model-reservation-station-scale-factor", cl::Hidden,
cl::init(DefaultReservationStationScaleFactor), cl::cat(MCScheduleOptions),
@@ -229,6 +241,22 @@ unsigned MCSchedModel::getBypassDelayCycles(const MCSubtargetInfo &STI,
return 0;
}
+/// Return the mispredict penalty in cycles, respecting any command-line
+/// override via -sched-mispredict-penalty.
+unsigned MCSchedModel::getMispredictPenalty() const {
+ if (SchedMispredictPenalty >= 0)
+ return static_cast<unsigned>(SchedMispredictPenalty);
+ return MispredictPenalty;
+}
+
+/// Return the load latency in cycles, respecting any command-line override
+/// via -sched-load-latency.
+unsigned MCSchedModel::getLoadLatency() const {
+ if (SchedLoadLatency >= 0)
+ return static_cast<unsigned>(SchedLoadLatency);
+ return LoadLatency;
+}
+
/// Return the buffer size of the resource. If a positive scale factor
/// is provided and the original buffer size is > 1, the size is scaled
/// accordingly.
diff --git a/llvm/lib/Target/AArch64/AArch64ConditionalCompares.cpp b/llvm/lib/Target/AArch64/AArch64ConditionalCompares.cpp
index 4754f5c1bb5b4..e05f80520db61 100644
--- a/llvm/lib/Target/AArch64/AArch64ConditionalCompares.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ConditionalCompares.cpp
@@ -902,7 +902,7 @@ bool AArch64ConditionalComparesImpl::shouldConvert() {
// the cost of a misprediction.
//
// Set a limit on the delay we will accept.
- unsigned DelayLimit = SchedModel.MispredictPenalty * 3 / 4;
+ unsigned DelayLimit = SchedModel.getMispredictPenalty() * 3 / 4;
// Instruction depths can be computed for all trace instructions above CmpBB.
unsigned HeadDepth =
diff --git a/llvm/lib/Target/ARM/ARMSubtarget.cpp b/llvm/lib/Target/ARM/ARMSubtarget.cpp
index 0f324a6a31757..945e455987a18 100644
--- a/llvm/lib/Target/ARM/ARMSubtarget.cpp
+++ b/llvm/lib/Target/ARM/ARMSubtarget.cpp
@@ -402,7 +402,7 @@ bool ARMSubtarget::isGVInGOT(const GlobalValue *GV) const {
}
unsigned ARMSubtarget::getMispredictionPenalty() const {
- return SchedModel.MispredictPenalty;
+ return SchedModel.getMispredictPenalty();
}
bool ARMSubtarget::enableMachineScheduler() const {
diff --git a/llvm/lib/Target/RISCV/RISCVSubtarget.cpp b/llvm/lib/Target/RISCV/RISCVSubtarget.cpp
index 9f68c8d354d6b..457e8e3ea347d 100644
--- a/llvm/lib/Target/RISCV/RISCVSubtarget.cpp
+++ b/llvm/lib/Target/RISCV/RISCVSubtarget.cpp
@@ -193,7 +193,7 @@ unsigned RISCVSubtarget::getMaxBuildIntsCost() const {
// building integers (addi, slli, etc.) can be done in one cycle, so here we
// set the default cost to (LoadLatency + 1) if no threshold is provided.
return RISCVMaxBuildIntsCost == 0
- ? getSchedModel().LoadLatency + 1
+ ? getSchedModel().getLoadLatency() + 1
: std::max<unsigned>(2, RISCVMaxBuildIntsCost);
}
diff --git a/llvm/lib/Target/X86/X86CmovConversion.cpp b/llvm/lib/Target/X86/X86CmovConversion.cpp
index d2f0a9f72f6e3..25d23b5478599 100644
--- a/llvm/lib/Target/X86/X86CmovConversion.cpp
+++ b/llvm/lib/Target/X86/X86CmovConversion.cpp
@@ -546,7 +546,7 @@ bool X86CmovConversionImpl::checkForProfitableCmovCandidates(
// To be conservative, the gain of such CMOV transformation should cover at
// at least 25% of branch-misprediction-penalty.
//===--------------------------------------------------------------------===//
- unsigned MispredictPenalty = TSchedModel.getMCSchedModel()->MispredictPenalty;
+ unsigned MispredictPenalty = TSchedModel.getMCSchedModel()->getMispredictPenalty();
CmovGroups TempGroups;
std::swap(TempGroups, CmovInstGroups);
for (auto &Group : TempGroups) {
diff --git a/llvm/test/CodeGen/RISCV/sched-model-load-latency.ll b/llvm/test/CodeGen/RISCV/sched-model-load-latency.ll
new file mode 100644
index 0000000000000..cea02360b2b6d
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/sched-model-load-latency.ll
@@ -0,0 +1,43 @@
+; Test that -sched-load-latency overrides the scheduler model's LoadLatency
+; field, and that the override affects code generation decisions that depend on
+; LoadLatency (specifically RISC-V's getMaxBuildIntsCost, which uses
+; LoadLatency to decide how many instructions can be used to materialize a
+; large integer constant inline vs. using the constant pool).
+;
+; rocket-rv64 has LoadLatency=3, so getMaxBuildIntsCost() returns 4 by
+; default. The constant 0x800000007bbbbbbb requires exactly 4 MatInt
+; instructions, so it is materialized inline under the default model.
+;
+; With -sched-load-latency=2, getLoadLatency() returns 2, making
+; getMaxBuildIntsCost() return 3. Since 4 > 3, the constant is too expensive
+; to build inline and falls back to the constant pool.
+;
+; RUN: llc -mtriple=riscv64 -mcpu=rocket-rv64 -verify-machineinstrs < %s \
+; RUN: | FileCheck %s --check-prefix=DEFAULT
+; RUN: llc -mtriple=riscv64 -mcpu=rocket-rv64 -sched-load-latency=2 \
+; RUN: -verify-machineinstrs < %s \
+; RUN: | FileCheck %s --check-prefix=LOWLATENCY
+
+; With default LoadLatency=3 (maxCost=4), the 4-instruction constant is built
+; inline using the MatInt sequence.
+; DEFAULT-LABEL: large_int:
+; DEFAULT: # %bb.0:
+; DEFAULT-NEXT: lui a0, 506812
+; DEFAULT-NEXT: addi a0, a0, -1093
+; DEFAULT-NEXT: slli a1, a0, 63
+; DEFAULT-NEXT: add a0, a0, a1
+; DEFAULT-NEXT: ret
+
+; With -sched-load-latency=2 (maxCost=3), the 4-instruction sequence exceeds
+; the cost threshold, so the constant is loaded from the constant pool.
+; LOWLATENCY-LABEL: large_int:
+; LOWLATENCY: # %bb.0:
+; LOWLATENCY-NEXT: lui a0, %hi(.LCPI0_0)
+; LOWLATENCY-NEXT: ld a0, %lo(.LCPI0_0)(a0)
+; LOWLATENCY-NEXT: ret
+
+define i64 @large_int() {
+ ; 0x800000007bbbbbbb = -9223372034778874949
+ ; Requires exactly 4 MatInt instructions: lui, addi, slli, add
+ ret i64 -9223372034778874949
+}
diff --git a/llvm/test/CodeGen/RISCV/sched-model-mispredict-penalty.ll b/llvm/test/CodeGen/RISCV/sched-model-mispredict-penalty.ll
new file mode 100644
index 0000000000000..9a57888134faf
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/sched-model-mispredict-penalty.ll
@@ -0,0 +1,74 @@
+; Test that -sched-mispredict-penalty overrides the scheduler model's
+; MispredictPenalty field, and that the override affects code generation
+; decisions that depend on MispredictPenalty (specifically SelectOptimize,
+; which weighs the cost of branch mispredictions against the benefit of
+; out-of-order execution when deciding whether to convert select instructions
+; to conditional branches).
+;
+; sifive-p550 is an out-of-order core with SelectOptimize enabled. For a
+; select in an inner loop, SelectOptimize calculates:
+;
+; BranchCost = PredictedPathCost + MispredictCost
+; MispredictCost = max(MispredictPenalty, CondCost) * MispredictRate / 100
+;
+; When MispredictCost is very large (high penalty), BranchCost >> SelectCost
+; and the select is kept as-is. When MispredictCost is small (low penalty),
+; BranchCost < SelectCost and the select is converted to a conditional branch.
+;
+; The -select-opti-loop-cycle-gain-threshold=1 flag lowers the minimum
+; absolute gain requirement so that the gain between the two cases is visible.
+; It does not affect which direction MispredictPenalty pushes the decision.
+;
+; RUN: opt -passes='require<profile-summary>,function(select-optimize)' \
+; RUN: -mtriple=riscv64 -mcpu=sifive-p550 \
+; RUN: -sched-mispredict-penalty=10000 \
+; RUN: -select-opti-loop-cycle-gain-threshold=1 \
+; RUN: -S < %s \
+; RUN: | FileCheck %s --check-prefix=HIGHPENALTY
+; RUN: opt -passes='require<profile-summary>,function(select-optimize)' \
+; RUN: -mtriple=riscv64 -mcpu=sifive-p550 \
+; RUN: -sched-mispredict-penalty=0 \
+; RUN: -select-opti-loop-cycle-gain-threshold=1 \
+; RUN: -S < %s \
+; RUN: | FileCheck %s --check-prefix=LOWPENALTY
+
+; With a very high mispredict penalty (10000 cycles), BranchCost is dominated
+; by misprediction cost. SelectOptimize keeps the select instruction because
+; the misprediction risk makes branches unprofitable.
+; HIGHPENALTY-LABEL: @sum_filtered(
+; HIGHPENALTY: %cond = icmp slt i64 %v, 0
+; HIGHPENALTY-NEXT: %sel = select i1 %cond, i64 %v, i64 0
+
+; With a zero mispredict penalty, branch mispredictions carry no cost.
+; BranchCost (= PredictedPathCost only) is less than SelectCost (which
+; must speculatively compute both paths). The select is converted to a
+; conditional branch + phi.
+; LOWPENALTY-LABEL: @sum_filtered(
+; LOWPENALTY: %cond = icmp slt i64 %v, 0
+; LOWPENALTY-NEXT: %cond.frozen = freeze i1 %cond
+; LOWPENALTY-NEXT: br i1 %cond.frozen, label %select.end, label %select.false
+
+define i64 @sum_filtered(ptr %p, i64 %n) {
+entry:
+ %entry.cmp = icmp sgt i64 %n, 0
+ br i1 %entry.cmp, label %loop.ph, label %exit
+
+loop.ph:
+ br label %loop
+
+loop:
+ %i = phi i64 [ 0, %loop.ph ], [ %i.next, %loop ]
+ %acc = phi i64 [ 0, %loop.ph ], [ %acc.next, %loop ]
+ %ptr = getelementptr i64, ptr %p, i64 %i
+ %v = load i64, ptr %ptr
+ %cond = icmp slt i64 %v, 0
+ %sel = select i1 %cond, i64 %v, i64 0
+ %acc.next = add i64 %acc, %sel
+ %i.next = add i64 %i, 1
+ %cmp = icmp ne i64 %i.next, %n
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ 0, %entry ], [ %acc.next, %loop ]
+ ret i64 %result
+}
>From c149506a388b4ffe6e7c14be7fefef31542e191a Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Wed, 10 Jun 2026 18:49:39 -0700
Subject: [PATCH 2/2] clang-format
---
llvm/lib/CodeGen/SelectOptimize.cpp | 3 ++-
llvm/lib/MC/MCSchedule.cpp | 3 +--
llvm/lib/Target/X86/X86CmovConversion.cpp | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectOptimize.cpp b/llvm/lib/CodeGen/SelectOptimize.cpp
index 2637e647876a1..01d69faaab918 100644
--- a/llvm/lib/CodeGen/SelectOptimize.cpp
+++ b/llvm/lib/CodeGen/SelectOptimize.cpp
@@ -1414,7 +1414,8 @@ SelectOptimizeImpl::computeInstCost(const Instruction *I) {
ScaledNumber<uint64_t>
SelectOptimizeImpl::getMispredictionCost(const SelectLike SI,
const Scaled64 CondCost) {
- uint64_t MispredictPenalty = TSchedModel.getMCSchedModel()->getMispredictPenalty();
+ uint64_t MispredictPenalty =
+ TSchedModel.getMCSchedModel()->getMispredictPenalty();
// Account for the default misprediction rate when using a branch
// (conservatively set to 25% by default).
diff --git a/llvm/lib/MC/MCSchedule.cpp b/llvm/lib/MC/MCSchedule.cpp
index 0aa7f8e6ff4dc..8808343b29c2d 100644
--- a/llvm/lib/MC/MCSchedule.cpp
+++ b/llvm/lib/MC/MCSchedule.cpp
@@ -34,8 +34,7 @@ static cl::opt<int> SchedMispredictPenalty(
"model. A non-negative value overrides the target default."));
static cl::opt<int> SchedLoadLatency(
- "sched-load-latency", cl::Hidden, cl::init(-1),
- cl::cat(MCScheduleOptions),
+ "sched-load-latency", cl::Hidden, cl::init(-1), cl::cat(MCScheduleOptions),
cl::desc("Override the load latency (in cycles) in the scheduler model. "
"A non-negative value overrides the target default."));
diff --git a/llvm/lib/Target/X86/X86CmovConversion.cpp b/llvm/lib/Target/X86/X86CmovConversion.cpp
index 25d23b5478599..a14d8d897bcf4 100644
--- a/llvm/lib/Target/X86/X86CmovConversion.cpp
+++ b/llvm/lib/Target/X86/X86CmovConversion.cpp
@@ -546,7 +546,8 @@ bool X86CmovConversionImpl::checkForProfitableCmovCandidates(
// To be conservative, the gain of such CMOV transformation should cover at
// at least 25% of branch-misprediction-penalty.
//===--------------------------------------------------------------------===//
- unsigned MispredictPenalty = TSchedModel.getMCSchedModel()->getMispredictPenalty();
+ unsigned MispredictPenalty =
+ TSchedModel.getMCSchedModel()->getMispredictPenalty();
CmovGroups TempGroups;
std::swap(TempGroups, CmovInstGroups);
for (auto &Group : TempGroups) {
More information about the llvm-commits
mailing list