[llvm] [RegAllocFast] Give an undef tied use the register of its tied def (PR #222249)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 10:53:48 PDT 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/222249

>From d624e45675b47ffa0014ad119eeb8da876561fc0 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 8 Sep 2026 21:46:25 -0700
Subject: [PATCH 1/2] [RegAllocFast] Give an undef tied use the register of its
 tied def

X86TargetLowering::emitSetJmpShadowStackFix zeroes a register by
building an XOR whose two uses are undef reads of its own def (per
MachineOperand.h "... reading the same dont-care value"). Def processing
frees a def whose tied use is undef before the uses are allocated, so
allocVirtRegUndef() no longer finds the virtual register and takes the
head of the allocation order instead. -verify-machineinstrs would fail:

```
renamable $rcx = XOR64rr undef renamable $rax(tied-def 0), undef renamable $rax, implicit-def dead $eflags
*** Bad machine code: Tied physical registers must match. ***
```

Take the register from the tie, and rewrite the instruction's other
reads of the value with it so that they keep agreeing on one register.

Aided by Opus 5
---
 llvm/lib/CodeGen/RegAllocFast.cpp             | 22 +++++++++++++++++++
 .../X86/regallocfast-undef-tied-use.mir       | 21 ++++++++++++++++++
 2 files changed, 43 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir

diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index 3db118c62fb09..a084faf3cc108 100644
--- a/llvm/lib/CodeGen/RegAllocFast.cpp
+++ b/llvm/lib/CodeGen/RegAllocFast.cpp
@@ -1031,6 +1031,28 @@ void RegAllocFastImpl::allocVirtRegUndef(MachineOperand &MO) {
   if (!shouldAllocateRegister(VirtReg))
     return;
 
+  // The def is freed before the uses are allocated, so a tie is the only
+  // record left of its register. Rewrite every read of VirtReg so they agree.
+  MachineInstr &MI = *MO.getParent();
+  for (const MachineOperand &Tied : MI.all_uses()) {
+    if (!Tied.isTied() || Tied.getReg() != VirtReg)
+      continue;
+    unsigned TiedIdx = MI.findTiedOperandIdx(MI.getOperandNo(&Tied));
+    MCRegister DefReg = MI.getOperand(TiedIdx).getReg().asMCReg();
+    for (MachineOperand &O : MI.all_uses()) {
+      if (O.getReg() != VirtReg)
+        continue;
+      // A tie needs the register itself, not the subregister it names.
+      MCRegister Reg = DefReg;
+      if (unsigned SubIdx = O.getSubReg(); SubIdx && !O.isTied())
+        Reg = TRI->getSubReg(DefReg, SubIdx);
+      O.setSubReg(0);
+      O.setReg(Reg);
+      O.setIsRenamable(!MRI->isReserved(Reg));
+    }
+    return;
+  }
+
   LiveRegMap::iterator LRI = findLiveVirtReg(VirtReg);
   MCRegister PhysReg;
   bool IsRenamable = true;
diff --git a/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir b/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir
new file mode 100644
index 0000000000000..b9cef97eca525
--- /dev/null
+++ b/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir
@@ -0,0 +1,21 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=x86_64-linux -run-pass=regallocfast -verify-machineinstrs %s -o - | FileCheck %s
+
+## X86 builds this zeroing XOR for __builtin_setjmp with a shadow stack. The
+## copy hint puts the def in $rcx, which the undef reads have to follow.
+
+--- |
+  define i64 @undef_tied_use() { ret i64 0 }
+...
+---
+name: undef_tied_use
+tracksRegLiveness: true
+body: |
+  bb.0:
+    ; CHECK-LABEL: name: undef_tied_use
+    ; CHECK: renamable $rcx = XOR64rr undef renamable $rcx, undef renamable $rcx, implicit-def dead $eflags
+    ; CHECK-NEXT: RET64 implicit killed $rcx
+    %0:gr64 = XOR64rr undef %0, undef %0, implicit-def dead $eflags
+    $rcx = COPY %0
+    RET64 implicit $rcx
+...

>From 31aee631692bd72be70a6abe25e18de069f95987 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Wed, 9 Sep 2026 10:53:35 -0700
Subject: [PATCH 2/2] Test %1:gr64 = XOR64rr undef %0:gr64, undef %0:gr64,
 implicit-def dead $eflags

---
 llvm/lib/CodeGen/RegAllocFast.cpp             | 20 +++++++++----------
 .../X86/regallocfast-undef-tied-use.mir       | 17 +++++++++++++---
 2 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index a084faf3cc108..524ed5e069941 100644
--- a/llvm/lib/CodeGen/RegAllocFast.cpp
+++ b/llvm/lib/CodeGen/RegAllocFast.cpp
@@ -1031,24 +1031,24 @@ void RegAllocFastImpl::allocVirtRegUndef(MachineOperand &MO) {
   if (!shouldAllocateRegister(VirtReg))
     return;
 
-  // The def is freed before the uses are allocated, so a tie is the only
-  // record left of its register. Rewrite every read of VirtReg so they agree.
+  // If there are multiple undef uses, give them the same register. The def is
+  // already freed, so take the register from the tie, not the lookup below.
   MachineInstr &MI = *MO.getParent();
   for (const MachineOperand &Tied : MI.all_uses()) {
     if (!Tied.isTied() || Tied.getReg() != VirtReg)
       continue;
-    unsigned TiedIdx = MI.findTiedOperandIdx(MI.getOperandNo(&Tied));
-    MCRegister DefReg = MI.getOperand(TiedIdx).getReg().asMCReg();
+    MCRegister DefReg =
+        MI.getOperand(MI.findTiedOperandIdx(MI.getOperandNo(&Tied)))
+            .getReg()
+            .asMCReg();
     for (MachineOperand &O : MI.all_uses()) {
       if (O.getReg() != VirtReg)
         continue;
-      // A tie needs the register itself, not the subregister it names.
-      MCRegister Reg = DefReg;
-      if (unsigned SubIdx = O.getSubReg(); SubIdx && !O.isTied())
-        Reg = TRI->getSubReg(DefReg, SubIdx);
+      // The def is already narrowed, so a tie takes its register whole.
+      unsigned SubIdx = O.isTied() ? 0 : O.getSubReg();
+      O.setReg(SubIdx ? TRI->getSubReg(DefReg, SubIdx) : DefReg);
       O.setSubReg(0);
-      O.setReg(Reg);
-      O.setIsRenamable(!MRI->isReserved(Reg));
+      O.setIsRenamable(!MRI->isReserved(O.getReg()));
     }
     return;
   }
diff --git a/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir b/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir
index b9cef97eca525..d04ca0e88bc0a 100644
--- a/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir
+++ b/llvm/test/CodeGen/X86/regallocfast-undef-tied-use.mir
@@ -4,9 +4,6 @@
 ## X86 builds this zeroing XOR for __builtin_setjmp with a shadow stack. The
 ## copy hint puts the def in $rcx, which the undef reads have to follow.
 
---- |
-  define i64 @undef_tied_use() { ret i64 0 }
-...
 ---
 name: undef_tied_use
 tracksRegLiveness: true
@@ -19,3 +16,17 @@ body: |
     $rcx = COPY %0
     RET64 implicit $rcx
 ...
+---
+## The tied def is a distinct virtual register, as it is before
+## TwoAddressInstructionPass rewrites the tie.
+name: undef_tied_use_distinct_def
+tracksRegLiveness: true
+body: |
+  bb.0:
+    ; CHECK-LABEL: name: undef_tied_use_distinct_def
+    ; CHECK: renamable $rcx = XOR64rr undef renamable $rcx, undef renamable $rcx, implicit-def dead $eflags
+    ; CHECK-NEXT: RET64 implicit killed $rcx
+    %1:gr64 = XOR64rr undef %0:gr64, undef %0:gr64, implicit-def dead $eflags
+    $rcx = COPY %1
+    RET64 implicit $rcx
+...



More information about the llvm-commits mailing list