[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