[llvm] [MCSched] Allow tuning LoadLatency/MispredictPenalty (PR #203139)

Sam Elliott via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 10 18:05:39 PDT 2026


https://github.com/lenary created https://github.com/llvm/llvm-project/pull/203139

This means an existing scheduling model can be tweaked at runtime to support an unknown processor rather than adding an entirely new model.

>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] [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
+}



More information about the llvm-commits mailing list