[llvm] [RISCV] Ignore debug instructions in epilogue block checks (PR #219401)

Yongqiang Tian via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 03:27:04 PDT 2026


https://github.com/yqtian-se updated https://github.com/llvm/llvm-project/pull/219401

>From 6541b3aa24dcd90b7ab5453ac5e3e310dac6fd2e Mon Sep 17 00:00:00 2001
From: Yongqiang Tian <yqtian668 at gmail.com>
Date: Fri, 28 Aug 2026 05:55:51 +0000
Subject: [PATCH 1/3] [RISCV] Add a test for debug-sensitive shrink wrapping

Add IR coverage documenting how a debug instruction in the return block prevents save/restore shrink wrapping.

Assisted-by: OpenAI Codex 5.6 Sol, co-reviewed by Claude and Yongqiang Tian.
---
 .../RISCV/shrinkwrap-save-restore-debug.ll    | 75 +++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll

diff --git a/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll b/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
new file mode 100644
index 0000000000000..637998c75e291
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
@@ -0,0 +1,75 @@
+; RUN: llc -mtriple=riscv32 -mattr=+save-restore -verify-machineinstrs < %s | FileCheck %s
+;
+; Document the current shrink-wrapping behavior with and without a debug
+; instruction in the common return block.
+
+declare ptr @llvm.stacksave.p0()
+declare void @llvm.stackrestore.p0(ptr)
+declare void @notdead(ptr)
+
+define void @without_debug(i32 %n) nounwind {
+; CHECK-LABEL: without_debug:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    li a1, 32
+; CHECK-NEXT:    bltu a1, a0, .LBB0_2
+; CHECK-NEXT:  # %bb.1: # %if.then
+; CHECK-NEXT:    call t0, __riscv_save_{{[0-9]+}}
+; CHECK:         tail __riscv_restore_{{[0-9]+}}
+; CHECK:       .LBB0_2: # %if.end
+; CHECK-NEXT:    ret
+entry:
+  %cmp = icmp ult i32 %n, 33
+  br i1 %cmp, label %if.then, label %if.end
+
+if.then:
+  %size = zext i32 %n to i64
+  %stack = call ptr @llvm.stacksave.p0()
+  %buffer = alloca i8, i64 %size, align 16
+  call void @notdead(ptr %buffer)
+  call void @llvm.stackrestore.p0(ptr %stack)
+  br label %if.end
+
+if.end:
+  ret void
+}
+
+define void @with_debug(i32 %n) nounwind !dbg !4 {
+; CHECK-LABEL: with_debug:
+; CHECK:       # %bb.0: # %entry
+; CHECK:         call t0, __riscv_save_{{[0-9]+}}
+; CHECK:         li a1, 32
+; CHECK:         bltu a1, a0, .LBB1_2
+; CHECK:       # %bb.1: # %if.then
+; CHECK-NOT:     __riscv_save
+; CHECK:       .LBB1_2: # %if.end
+; CHECK:         tail __riscv_restore_{{[0-9]+}}
+entry:
+  %cmp = icmp ult i32 %n, 33
+  br i1 %cmp, label %if.then, label %if.end
+
+if.then:
+  %size = zext i32 %n to i64
+  %stack = call ptr @llvm.stacksave.p0()
+  %buffer = alloca i8, i64 %size, align 16
+  call void @notdead(ptr %buffer)
+  call void @llvm.stackrestore.p0(ptr %stack)
+  br label %if.end
+
+if.end:
+    #dbg_value(i32 0, !8, !DIExpression(), !9)
+  ret void, !dbg !10
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, emissionKind: FullDebug)
+!1 = !DIFile(filename: "x.c", directory: "/")
+!2 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "with_debug", scope: !1, file: !1, line: 1, type: !5, unit: !0, retainedNodes: !6)
+!5 = !DISubroutineType(types: !11)
+!6 = !{!8}
+!8 = !DILocalVariable(name: "ghost", scope: !4, file: !1, line: 2, type: !2)
+!9 = !DILocation(line: 0, scope: !4)
+!10 = !DILocation(line: 3, scope: !4)
+!11 = !{null, !2}

>From d103db2c30ccb6287b94a53f6e0bfd6028f4b4d8 Mon Sep 17 00:00:00 2001
From: Yongqiang Tian <yqtian668 at gmail.com>
Date: Fri, 28 Aug 2026 05:56:39 +0000
Subject: [PATCH 2/3] [RISCV] Ignore debug instructions in epilogue block
 checks

Ignore debug instructions when checking whether a return successor contains only the return. Keep iterating over the raw instruction range so bundles, pseudo probes, and other non-debug instructions retain their existing behavior.

Assisted-by: OpenAI Codex 5.6 Sol, co-reviewed by Claude and Yongqiang Tian.
---
 llvm/lib/Target/RISCV/RISCVFrameLowering.cpp          | 11 ++++++++---
 .../CodeGen/RISCV/shrinkwrap-save-restore-debug.ll    | 11 ++++++-----
 2 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
index 7f2f70a1cf185..b793c108bb492 100644
--- a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
@@ -15,6 +15,7 @@
 #include "MCTargetDesc/RISCVMCTargetDesc.h"
 #include "RISCVMachineFunctionInfo.h"
 #include "RISCVSubtarget.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/CodeGen/CFIInstBuilder.h"
 #include "llvm/CodeGen/LivePhysRegs.h"
@@ -2713,9 +2714,13 @@ bool RISCVFrameLowering::canUseAsEpilogue(const MachineBasicBlock &MBB) const {
   if (!SuccMBB)
     return true;
 
-  // The successor can only contain a return, since we would effectively be
-  // replacing the successor with our own tail return at the end of our block.
-  return SuccMBB->isReturnBlock() && SuccMBB->size() == 1;
+  // The successor can only contain a return and debug instructions, since we
+  // would effectively replace it with our own tail return at the end of this
+  // block. The debug instructions would not execute on the tail-return path.
+  return SuccMBB->isReturnBlock() &&
+         llvm::count_if(SuccMBB->instrs(), [](const MachineInstr &MI) {
+           return !MI.isDebugInstr();
+         }) == 1;
 }
 
 bool RISCVFrameLowering::isSupportedStackID(TargetStackID::Value ID) const {
diff --git a/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll b/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
index 637998c75e291..2a1b7e55d9c4b 100644
--- a/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
+++ b/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
@@ -1,7 +1,7 @@
 ; RUN: llc -mtriple=riscv32 -mattr=+save-restore -verify-machineinstrs < %s | FileCheck %s
 ;
-; Document the current shrink-wrapping behavior with and without a debug
-; instruction in the common return block.
+; Debug instructions in the common return block should not affect save/restore
+; shrink wrapping.
 
 declare ptr @llvm.stacksave.p0()
 declare void @llvm.stackrestore.p0(ptr)
@@ -36,13 +36,14 @@ if.end:
 define void @with_debug(i32 %n) nounwind !dbg !4 {
 ; CHECK-LABEL: with_debug:
 ; CHECK:       # %bb.0: # %entry
-; CHECK:         call t0, __riscv_save_{{[0-9]+}}
+; CHECK-NOT:     __riscv_save
 ; CHECK:         li a1, 32
 ; CHECK:         bltu a1, a0, .LBB1_2
 ; CHECK:       # %bb.1: # %if.then
-; CHECK-NOT:     __riscv_save
-; CHECK:       .LBB1_2: # %if.end
+; CHECK:         call t0, __riscv_save_{{[0-9]+}}
 ; CHECK:         tail __riscv_restore_{{[0-9]+}}
+; CHECK:       .LBB1_2: # %if.end
+; CHECK:         ret
 entry:
   %cmp = icmp ult i32 %n, 33
   br i1 %cmp, label %if.then, label %if.end

>From 892151c3b4324ac05d912f231071adcc958c1631 Mon Sep 17 00:00:00 2001
From: Yongqiang Tian <yqtian668 at gmail.com>
Date: Fri, 28 Aug 2026 10:26:06 +0000
Subject: [PATCH 3/3] fixup! [RISCV] Ignore debug instructions in epilogue
 block checks

Remove the redundant STLExtras include, simplify the debug file metadata,
and regenerate the test checks with update_llc_test_checks.py.

Assisted-by: OpenAI Codex 5.6 Sol, co-reviewed by Claude and Yongqiang Tian.
---
 llvm/lib/Target/RISCV/RISCVFrameLowering.cpp  |  1 -
 .../RISCV/shrinkwrap-save-restore-debug.ll    | 60 +++++++++++++++----
 2 files changed, 48 insertions(+), 13 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
index b793c108bb492..d2815db0b8b34 100644
--- a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
@@ -15,7 +15,6 @@
 #include "MCTargetDesc/RISCVMCTargetDesc.h"
 #include "RISCVMachineFunctionInfo.h"
 #include "RISCVSubtarget.h"
-#include "llvm/ADT/STLExtras.h"
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/CodeGen/CFIInstBuilder.h"
 #include "llvm/CodeGen/LivePhysRegs.h"
diff --git a/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll b/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
index 2a1b7e55d9c4b..787dbcb500883 100644
--- a/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
+++ b/llvm/test/CodeGen/RISCV/shrinkwrap-save-restore-debug.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
 ; RUN: llc -mtriple=riscv32 -mattr=+save-restore -verify-machineinstrs < %s | FileCheck %s
 ;
 ; Debug instructions in the common return block should not affect save/restore
@@ -13,9 +14,24 @@ define void @without_debug(i32 %n) nounwind {
 ; CHECK-NEXT:    li a1, 32
 ; CHECK-NEXT:    bltu a1, a0, .LBB0_2
 ; CHECK-NEXT:  # %bb.1: # %if.then
-; CHECK-NEXT:    call t0, __riscv_save_{{[0-9]+}}
-; CHECK:         tail __riscv_restore_{{[0-9]+}}
-; CHECK:       .LBB0_2: # %if.end
+; CHECK-NEXT:    call t0, __riscv_save_2
+; CHECK-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NEXT:    .cfi_offset ra, -4
+; CHECK-NEXT:    .cfi_offset s0, -8
+; CHECK-NEXT:    .cfi_offset s1, -12
+; CHECK-NEXT:    addi s0, sp, 16
+; CHECK-NEXT:    .cfi_def_cfa s0, 0
+; CHECK-NEXT:    mv s1, sp
+; CHECK-NEXT:    addi a0, a0, 15
+; CHECK-NEXT:    andi a0, a0, -16
+; CHECK-NEXT:    sub a0, sp, a0
+; CHECK-NEXT:    mv sp, a0
+; CHECK-NEXT:    call notdead
+; CHECK-NEXT:    mv sp, s1
+; CHECK-NEXT:    addi sp, s0, -16
+; CHECK-NEXT:    .cfi_def_cfa sp, 16
+; CHECK-NEXT:    tail __riscv_restore_2
+; CHECK-NEXT:  .LBB0_2: # %if.end
 ; CHECK-NEXT:    ret
 entry:
   %cmp = icmp ult i32 %n, 33
@@ -36,14 +52,34 @@ if.end:
 define void @with_debug(i32 %n) nounwind !dbg !4 {
 ; CHECK-LABEL: with_debug:
 ; CHECK:       # %bb.0: # %entry
-; CHECK-NOT:     __riscv_save
-; CHECK:         li a1, 32
-; CHECK:         bltu a1, a0, .LBB1_2
-; CHECK:       # %bb.1: # %if.then
-; CHECK:         call t0, __riscv_save_{{[0-9]+}}
-; CHECK:         tail __riscv_restore_{{[0-9]+}}
-; CHECK:       .LBB1_2: # %if.end
-; CHECK:         ret
+; CHECK-NEXT:    .file 1 "x.c"
+; CHECK-NEXT:    .loc 1 0 0 prologue_end # x.c:0:0
+; CHECK-NEXT:    li a1, 32
+; CHECK-NEXT:    bltu a1, a0, .LBB1_2
+; CHECK-NEXT:  # %bb.1: # %if.then
+; CHECK-NEXT:    call t0, __riscv_save_2
+; CHECK-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NEXT:    .cfi_offset ra, -4
+; CHECK-NEXT:    .cfi_offset s0, -8
+; CHECK-NEXT:    .cfi_offset s1, -12
+; CHECK-NEXT:    addi s0, sp, 16
+; CHECK-NEXT:    .cfi_def_cfa s0, 0
+; CHECK-NEXT:    mv s1, sp
+; CHECK-NEXT:    addi a0, a0, 15
+; CHECK-NEXT:    andi a0, a0, -16
+; CHECK-NEXT:    sub a0, sp, a0
+; CHECK-NEXT:    mv sp, a0
+; CHECK-NEXT:    call notdead
+; CHECK-NEXT:    mv sp, s1
+; CHECK-NEXT:    addi sp, s0, -16
+; CHECK-NEXT:    .cfi_def_cfa sp, 16
+; CHECK-NEXT:    tail __riscv_restore_2
+; CHECK-NEXT:  .LBB1_2: # %if.end
+; CHECK-NEXT:  .Ltmp0:
+; CHECK-NEXT:    #DEBUG_VALUE: with_debug:ghost <- 0
+; CHECK-NEXT:    .loc 1 3 0 # x.c:3
+; CHECK-NEXT:    ret
+; CHECK-NEXT:  .Ltmp1:
 entry:
   %cmp = icmp ult i32 %n, 33
   br i1 %cmp, label %if.then, label %if.end
@@ -64,7 +100,7 @@ if.end:
 !llvm.dbg.cu = !{!0}
 !llvm.module.flags = !{!3}
 !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, emissionKind: FullDebug)
-!1 = !DIFile(filename: "x.c", directory: "/")
+!1 = !DIFile(filename: "x.c", directory: "")
 !2 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
 !3 = !{i32 2, !"Debug Info Version", i32 3}
 !4 = distinct !DISubprogram(name: "with_debug", scope: !1, file: !1, line: 1, type: !5, unit: !0, retainedNodes: !6)



More information about the llvm-commits mailing list