[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