[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 00:29:09 PDT 2026
https://github.com/yqtian-se created https://github.com/llvm/llvm-project/pull/219401
A debug instruction in a return-only block can make
`RISCVFrameLowering::canUseAsEpilogue` reject an otherwise valid shrink-wrap
point. This moves the save and restore libcalls onto the fast path.
Ignore debug instructions in the successor cardinality check while continuing
to count bundle interiors, pseudo probes, and other non-debug instructions.
The first commit records the existing behavior. The second applies the fix and
updates the test expectations to require localized save and restore calls.
Fixes #219378
Testing:
- Verified that the regression test fails on an affected compiler.
- Verified the targeted test after the fix.
- RISC-V CodeGen: 2654/2654 passed.
Assisted-by: OpenAI Codex 5.6 Sol, co-reviewed by Claude and Yongqiang Tian.
>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/2] [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/2] [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
More information about the llvm-commits
mailing list