================
@@ -1789,6 +1799,253 @@ void RegAllocFastImpl::handleBundle(MachineInstr &MI) {
}
}
+/// Decide which of \p MI's foldable ("rm"-style) register operands actually
+/// need to be converted to memory, and record their registers in \p ToFold.
+///
+/// A foldable operand is one where SelectionDAG chose 'r' (registers give
+/// better code than always spilling to memory) but recorded, via
+/// InlineAsm::Flag::RegMayBeFolded, that 'm' is an available fallback -- see
+/// TargetLowering::ComputeConstraintToUse. The greedy allocator can leave
+/// this decision until it actually runs out of registers: InlineSpiller
+/// folds on demand, informed by real, global register pressure. RegAllocFast
+/// has no such on-demand spilling machinery, and restructuring its
+/// single-pass, iterate-MI's-live-operand-list design to support it safely
+/// is a larger change (folding an operand replaces MI with a new
+/// instruction, which can't happen mid-iteration of the def/use loops in
+/// allocateInstruction()). Folding every foldable operand unconditionally
+/// would sidestep that, but is strictly pessimistic -- verified against
+/// this file's own asm-constraints-torture.ll, it regresses cases with no
+/// real pressure at all from "uses a register, like greedy" to "always
+/// spills," which is worse than doing nothing.
+///
+/// This is the middle ground: estimate, from MI's own operand list alone,
+/// whether its simultaneous register demands -- defs, uses, and clobbers,
+/// all alive only for this one instruction -- fit in the relevant register
+/// class, and only fold as many foldable operands as needed to make them
+/// fit. Non-foldable operands get first claim on the available registers,
+/// since they have no fallback.
+///
+/// Three simplifications, all biased toward folding too little rather than
+/// too much (i.e. toward RegAllocFast's pre-existing "hard error if a
+/// register genuinely isn't available" behavior, never toward silently
+/// wrong codegen):
+/// - It only accounts for pressure local to this one instruction, not
+/// registers already committed to values live across other instructions
+/// in the block. RegAllocFast has no on-demand spilling for those either
+/// way, so this is a pre-existing limitation, not a regression.
+/// - It buckets demand by exact TargetRegisterClass rather than unifying
+/// classes that alias the same physical registers (e.g. GR32 and GR64),
+/// so it can undercount pressure when an instruction mixes classes of
+/// different widths. RegAllocFast's normal out-of-registers error path
+/// remains a backstop for any such case this estimate gets wrong.
+/// - It doesn't model early-clobber's stricter requirement that a def be
+/// disjoint from every input, not just other defs -- it counts an
+/// early-clobber def as ordinary same-class demand, the same as it would
+/// a non-early-clobber one. This is really a specific, easy-to-hit
+/// instance of the previous point (the disjointness early-clobber
+/// requires isn't scoped to one register class either), called out
+/// separately because "=&rm" early-clobber outputs are a common shape
+/// for this constraint in practice. Same backstop applies.
+void RegAllocFastImpl::selectInlineAsmOperandsToFold(
----------------
isanbard wrote:
*From Claude*:
The short answer: Greedy already has the analysis to make this decision safely;
RegAllocFast has never had it, and building it would compromise what
RegAllocFast is for.
- Greedy runs on top of LiveIntervals — real, whole-function live-range data
computed before allocation starts. When it runs out of registers, InlineSpiller
decides what to spill using actual global pressure and spill weights, and for a
foldable operand it calls the same generic TargetInstrInfo::foldMemoryOperand()
this PR's RegAllocFast code also calls. That fold primitive is shared already —
nothing new was invented there.
- RegAllocFast has none of that machinery by design. It's a single reverse pass
over one block, assigning physical registers on the spot with no lookahead and
no live-range analysis. It doesn't "decide" who to spill ahead of time — it
reacts locally when it runs out. There's no existing hook in that loop for
"this specific operand doesn't need a register at all, fold it into the
instruction instead" — that's a structural rewrite decision, not a
register-assignment decision, and Greedy's version of that decision is only
cheap for Greedy because LiveIntervals already did the expensive work upstream.
- So what's duplicated between the two isn't the fold mechanism (shared) — it's
the decision heuristic for when to fold, and that has to be different by
necessity: Greedy's is exact/global, RegAllocFast's has to be a cheap local
pressure estimate over just the current instruction's operands, or it stops
being fast. That's what selectInlineAsmOperandsToFold() is.
Worth being honest that this estimate is genuinely harder to get right
precisely because it has so much less information than Greedy — we just found
and fixed a real bug in it this week (a tied "+rm" operand's pressure was being
double-counted, since a tied def/use pair is one physical register but the
local scan didn't know that, and could force an unnecessary spill even with
exactly enough registers available **[editor: that's included in this PR]**).
That's a good concrete illustration of the class of bug you get when you build
a local approximation of something Greedy gets for free from global analysis.
https://github.com/llvm/llvm-project/pull/214061
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits