[llvm] [RegAllocFast] Order partial defs before other defs (PR #222882)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 11 01:28:28 PDT 2026
https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/222882
```
%0:gr64_with_sub_8bit = COPY $rax
%0.sub_32bit, %1:gr32 = MULX32rr $ecx, implicit $edx
```
The partial def of %0 is live-through: it reads the rest of %0, so it
must avoid the registers the instruction reads ($ecx, $edx) and is meant
to be allocated before an ordinary def like %1, which may share a
register with a use. The comparator in findAndSortDefOperandIndexes,
from the 2020 rewrite c8757ff3aa7d ("RegAllocFast: Rewrite and
improve"), tests `getSubReg() == 0`, the inverse of the allocation
sites, so %1 is allocated first and takes $eax; %0 then misses its hint
$rax, lands in $rsi, and the copy from $rax stays.
Share one isLiveThroughDef predicate between the ordering and the
allocation. %0 now takes $rax, the copy is deleted, and %1 takes $ecx.
Aided by Opus 5
>From 616e6ee5bdbdd338ee89838ecfb53982564dc3a8 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Fri, 11 Sep 2026 00:22:45 -0700
Subject: [PATCH] [RegAllocFast] Order partial defs before other defs
```
%0:gr64_with_sub_8bit = COPY $rax
%0.sub_32bit, %1:gr32 = MULX32rr $ecx, implicit $edx
```
The partial def of %0 is live-through: it reads the rest of %0, so it
must avoid the registers the instruction reads ($ecx, $edx) and is meant
to be allocated before an ordinary def like %1, which may share a
register with a use. The comparator in findAndSortDefOperandIndexes,
from the 2020 rewrite c8757ff3aa7d ("RegAllocFast: Rewrite and
improve"), tests `getSubReg() == 0`, the inverse of the allocation
sites, so %1 is allocated first and takes $eax; %0 then misses its hint
$rax, lands in $rsi, and the copy from $rax stays.
Share one isLiveThroughDef predicate between the ordering and the
allocation. %0 now takes $rax, the copy is deleted, and %1 takes $ecx.
Aided by Opus 5
---
llvm/lib/CodeGen/RegAllocFast.cpp | 41 ++++++++-----------
.../X86/virtreg-physreg-def-regallocfast.mir | 20 +++++++++
2 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index 524ed5e069941..fccbb463ed9ab 100644
--- a/llvm/lib/CodeGen/RegAllocFast.cpp
+++ b/llvm/lib/CodeGen/RegAllocFast.cpp
@@ -1416,6 +1416,16 @@ void RegAllocFastImpl::addRegClassDefCounts(
}
}
+/// Early clobber, partial def, or tied to a use that carries a value: the
+/// register is occupied while the uses are read.
+static bool isLiveThroughDef(const MachineInstr &MI, const MachineOperand &MO) {
+ assert(MO.isDef() && "expected def operand");
+ if (MO.isEarlyClobber() || MO.readsReg())
+ return true;
+ return MO.isTied() &&
+ !MI.getOperand(MI.findTiedOperandIdx(MI.getOperandNo(&MO))).isUndef();
+}
+
/// Compute \ref DefOperandIndexes so it contains the indices of "def" operands
/// that are to be allocated. Those are ordered in a way that small classes,
/// early clobbers and livethroughs are allocated first.
@@ -1475,10 +1485,8 @@ void RegAllocFastImpl::findAndSortDefOperandIndexes(const MachineInstr &MI) {
return false;
// Allocate early clobbers and livethrough operands first.
- bool Livethrough0 = MO0.isEarlyClobber() || MO0.isTied() ||
- (MO0.getSubReg() == 0 && !MO0.isUndef());
- bool Livethrough1 = MO1.isEarlyClobber() || MO1.isTied() ||
- (MO1.getSubReg() == 0 && !MO1.isUndef());
+ bool Livethrough0 = isLiveThroughDef(MI, MO0);
+ bool Livethrough1 = isLiveThroughDef(MI, MO1);
if (Livethrough0 > Livethrough1)
return true;
if (Livethrough0 < Livethrough1)
@@ -1489,17 +1497,6 @@ void RegAllocFastImpl::findAndSortDefOperandIndexes(const MachineInstr &MI) {
});
}
-// Returns true if this def (MO) ties to a use that actually carries a value
-// (not undef).
-static bool isTiedToNotUndef(const MachineInstr &MI, const MachineOperand &MO) {
- assert(MO.isDef() && "expected a def operand");
- if (!MO.isTied())
- return false;
- unsigned TiedIdx = MI.findTiedOperandIdx(MI.getOperandNo(&MO));
- const MachineOperand &TiedMO = MI.getOperand(TiedIdx);
- return !TiedMO.isUndef();
-}
-
void RegAllocFastImpl::allocateInstruction(MachineInstr &MI) {
// Backwards, a def frees a register and a use occupies it. The phases:
// * pre-assigned physreg defs
@@ -1541,12 +1538,9 @@ void RegAllocFastImpl::allocateInstruction(MachineInstr &MI) {
if (MO.isDef()) {
HasDef = true;
HasVRegDef = true;
- if (MO.isEarlyClobber()) {
+ if (MO.isEarlyClobber())
HasEarlyClobber = true;
- NeedToAssignLiveThroughs = true;
- }
- if (isTiedToNotUndef(MI, MO) ||
- (MO.getSubReg() != 0 && !MO.isUndef()))
+ if (isLiveThroughDef(MI, MO))
NeedToAssignLiveThroughs = true;
}
} else if (Reg.isPhysical()) {
@@ -1590,8 +1584,7 @@ void RegAllocFastImpl::allocateInstruction(MachineInstr &MI) {
MachineOperand &MO = MI.getOperand(OpIdx);
LLVM_DEBUG(dbgs() << "Allocating " << MO << '\n');
Register Reg = MO.getReg();
- if (MO.isEarlyClobber() || isTiedToNotUndef(MI, MO) ||
- (MO.getSubReg() && !MO.isUndef())) {
+ if (isLiveThroughDef(MI, MO)) {
ReArrangedImplicitOps = defineLiveThroughVirtReg(MI, OpIdx, Reg);
} else {
ReArrangedImplicitOps = defineVirtReg(MI, OpIdx, Reg);
@@ -1635,8 +1628,8 @@ void RegAllocFastImpl::allocateInstruction(MachineInstr &MI) {
assert((!MO.isTied() || !isClobberedByRegMasks(MO.getReg())) &&
"tied def assigned to clobbered register");
- // Do not free tied operands and early clobbers.
- if (isTiedToNotUndef(MI, MO) || MO.isEarlyClobber())
+ // Do not free live-through defs.
+ if (isLiveThroughDef(MI, MO))
continue;
if (!Reg)
continue;
diff --git a/llvm/test/CodeGen/X86/virtreg-physreg-def-regallocfast.mir b/llvm/test/CodeGen/X86/virtreg-physreg-def-regallocfast.mir
index 5222b3d9fdb85..473616a1b8870 100644
--- a/llvm/test/CodeGen/X86/virtreg-physreg-def-regallocfast.mir
+++ b/llvm/test/CodeGen/X86/virtreg-physreg-def-regallocfast.mir
@@ -18,3 +18,23 @@ body: |
KILL killed %0
RET 0
...
+---
+# Live-through defs are assigned before the other defs. The partial def of %0
+# goes first and takes its hint $rax; %1 then takes $ecx.
+# Assigning %1 first would put it in $eax and leave %0 a copy out of $rax.
+name: partial_def_before_other_defs
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $rax, $edx, $ecx
+ ; CHECK-LABEL: name: partial_def_before_other_defs
+ ; CHECK-NOT: COPY
+ ; CHECK: renamable $eax, renamable $ecx = MULX32rr killed $ecx, implicit killed $edx
+ %0:gr64_with_sub_8bit = COPY $rax
+ %0.sub_32bit, %1:gr32 = MULX32rr $ecx, implicit $edx
+ JMP_1 %bb.1
+ bb.1:
+ $rax = COPY %0
+ $ecx = COPY %1
+ RET64 implicit $rax, implicit $ecx
+...
More information about the llvm-commits
mailing list