[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