[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