[llvm] [AArch64][GlobalISel] Don't let debug instructions change register classes (PR #228078)
Aochang Liu via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 06:42:26 PDT 2026
https://github.com/HelloWorldU created https://github.com/llvm/llvm-project/pull/228078
## Problem
`selectDebugInstr` constrains the vreg operands of debug instructions to the smallest class for their bank, before their defs are selected. For the reproducer in #227628 (`-O0 -g`), after selection:
```
%30:gpr64all = COPY %29
$sp = COPY %30
%17:gpr64 = COPY %30 ; gpr64 from DBG_VALUE %17
DBG_VALUE %17, ...
$x0 = COPY %17
```
With `-g0`, `%17` is `gpr64all` and the COPY is removed as redundant; here it stays, and three instructions in the final code change.
`select-hint.mir` (run with `-debugify-and-strip-all-safe`) already shows the effect: D129037, which added this constraint, changed `%copy:gpr32all` to `gpr32` there.
## Fix
Skip vregs with non-debug uses, leaving them to their defs and users. Debug-only vregs are still constrained as before: nothing else gives them a class, and using the widest class for them made no difference in local probes.
Constraining all operands to the widest class instead is not enough: a PHI keeps a class already assigned to its def, so it would get `gpr64all` with `-g` but `gpr64` without.
## Testing
- `select-dbg-value.mir`: new `test_dbg_value_copy` fails on main; new `test_dbg_value_phi` fails if operands are constrained to the widest class.
- `select-hint.mir` is back to its pre-D129037 checks.
- Reproducer: GlobalISel `.text` is now identical with `-g` and `-g0`. Local probes with `llvm.stacksave`, dynamic `alloca` and `atomicrmw` showed the same `-g`/`-g0` mismatch on main; all now match at `-O0` and `-O2`.
- `CodeGen/AArch64` and `DebugInfo/AArch64` pass.
Assisted-by: Claude Opus 5.5
>From 64bcaeb8beee8eec1f8efd315df0c9af1f95166b Mon Sep 17 00:00:00 2001
From: HelloWorldU <asd001liu at gmail.com>
Date: Thu, 1 Oct 2026 16:27:02 +0800
Subject: [PATCH] [AArch64][GlobalISel] Don't let debug instructions change
register classes
selectDebugInstr constrains its vreg operands to the smallest class for
their bank, before their defs are selected. With -g, a vreg copied from
a GPR64all vreg thus ends up in GPR64, the redundant COPY is no longer
removed, and the code differs from -g0.
Skip vregs with non-debug uses; only debug-only vregs still need a class
from here. Constraining to the widest class instead is not enough, since
a PHI keeps a class already assigned to its def.
Fixes #227628
Assisted-by: Claude Opus 5.5
---
.../GISel/AArch64InstructionSelector.cpp | 5 ++
.../AArch64/GlobalISel/select-dbg-value.mir | 64 +++++++++++++++++++
.../AArch64/GlobalISel/select-hint.mir | 4 +-
3 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
index 22c0e3a3a462d49..20a6256de43dd98 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
@@ -903,6 +903,11 @@ static bool selectDebugInstr(MachineInstr &I, MachineRegisterInfo &MRI,
const TargetRegisterClass *RC =
dyn_cast<const TargetRegisterClass *>(RegClassOrBank);
if (!RC) {
+ // Debug instructions are selected before the defs of their operands.
+ // Leave vregs with non-debug uses to their defs and users, so that
+ // debug info does not change the class they get.
+ if (!MRI.use_nodbg_empty(Reg))
+ continue;
const RegisterBank &RB = *cast<const RegisterBank *>(RegClassOrBank);
RC = getRegClassForTypeOnBank(Ty, RB);
if (!RC) {
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/select-dbg-value.mir b/llvm/test/CodeGen/AArch64/GlobalISel/select-dbg-value.mir
index b28ebbb90a79c6e..fe497c3ce0fb261 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/select-dbg-value.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/select-dbg-value.mir
@@ -15,6 +15,9 @@
ret void
}
+ define void @test_dbg_value_copy() { ret void }
+ define void @test_dbg_value_phi() { ret void }
+
declare void @llvm.dbg.value(metadata, i64, metadata, metadata)
!llvm.dbg.cu = !{!0}
@@ -70,3 +73,64 @@ body: |
%1:gpr(i64) = G_ZEXT %0:gpr(i32)
DBG_VALUE %1(i64), $noreg, !7, !DIExpression(), debug-location !9
...
+
+# The DBG_VALUE must not narrow the class of %1, otherwise the redundant COPY
+# from %0 is kept.
+---
+name: test_dbg_value_copy
+legalized: true
+regBankSelected: true
+body: |
+ bb.0:
+ liveins: $x0
+ ; CHECK-LABEL: name: test_dbg_value_copy
+ ; CHECK: liveins: $x0
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr64all = COPY $x0
+ ; CHECK-NEXT: DBG_VALUE [[COPY]], $noreg, !7, !DIExpression(), debug-location !9
+ ; CHECK-NEXT: $x0 = COPY [[COPY]]
+ %0:gpr(p0) = COPY $x0
+ %1:gpr(p0) = COPY %0(p0)
+ DBG_VALUE %1(p0), $noreg, !7, !DIExpression(), debug-location !9
+ $x0 = COPY %1(p0)
+...
+
+# A PHI keeps any class already assigned to its def, so the DBG_VALUE must not
+# assign one, not even the widest.
+---
+name: test_dbg_value_phi
+legalized: true
+regBankSelected: true
+tracksRegLiveness: true
+body: |
+ ; CHECK-LABEL: name: test_dbg_value_phi
+ ; CHECK: bb.0:
+ ; CHECK-NEXT: successors: %bb.2(0x40000000), %bb.1(0x40000000)
+ ; CHECK-NEXT: liveins: $w0, $x1, $x2
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr32 = COPY $w0
+ ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gpr64all = COPY $x1
+ ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gpr64all = COPY $x2
+ ; CHECK-NEXT: TBNZW [[COPY]], 0, %bb.2
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: bb.1:
+ ; CHECK-NEXT: successors: %bb.2(0x80000000)
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: bb.2:
+ ; CHECK-NEXT: [[PHI:%[0-9]+]]:gpr64 = PHI [[COPY1]], %bb.0, [[COPY2]], %bb.1
+ ; CHECK-NEXT: DBG_VALUE [[PHI]], $noreg, !7, !DIExpression(), debug-location !9
+ ; CHECK-NEXT: $x0 = COPY [[PHI]]
+ bb.0:
+ liveins: $w0, $x1, $x2
+ %0:gpr(i32) = COPY $w0
+ %1:gpr(i64) = COPY $x1
+ %2:gpr(i64) = COPY $x2
+ G_BRCOND %0, %bb.2
+
+ bb.1:
+
+ bb.2:
+ %3:gpr(i64) = G_PHI %1(i64), %bb.0, %2(i64), %bb.1
+ DBG_VALUE %3(i64), $noreg, !7, !DIExpression(), debug-location !9
+ $x0 = COPY %3(i64)
+...
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/select-hint.mir b/llvm/test/CodeGen/AArch64/GlobalISel/select-hint.mir
index d7efa669bf35ca2..3d8d5741905074e 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/select-hint.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/select-hint.mir
@@ -16,7 +16,7 @@ body: |
; CHECK-LABEL: name: assert_zext_gpr
; CHECK: liveins: $w0, $w1
; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: %copy:gpr32 = COPY $w0
+ ; CHECK-NEXT: %copy:gpr32all = COPY $w0
; CHECK-NEXT: $w1 = COPY %copy
; CHECK-NEXT: RET_ReallyLR implicit $w1
%copy:gpr(i32) = COPY $w0
@@ -104,7 +104,7 @@ body: |
; CHECK-LABEL: name: assert_sext_gpr
; CHECK: liveins: $w0, $w1
; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: %copy:gpr32 = COPY $w0
+ ; CHECK-NEXT: %copy:gpr32all = COPY $w0
; CHECK-NEXT: $w1 = COPY %copy
; CHECK-NEXT: RET_ReallyLR implicit $w1
%copy:gpr(i32) = COPY $w0
More information about the llvm-commits
mailing list