[llvm] [Hexagon] Disable restore stubs when ShadowCallStack is active (PR #206302)
Brian Cain via llvm-commits
llvm-commits at lists.llvm.org
Sun Jun 28 10:57:33 PDT 2026
https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/206302
>From 420bad42bfbdcbe7dad81220763b58c8dd3791ce Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sat, 27 Jun 2026 21:27:55 -0700
Subject: [PATCH 1/2] [Hexagon] Disable restore stubs when ShadowCallStack is
active
The returning restore stubs (e.g. __restore_r16_through_r17_and_deallocframe)
perform deallocframe+jumpr r31 internally, returning via the on-stack return
address. This is incompatible with ShadowCallStack, which must restore r31
from the shadow stack before returning.
Fix by having useRestoreFunction() return false when the ShadowCallStack
attribute is present, forcing inline callee-saved restores so the SCS epilogue
is properly emitted.
---
.../Target/Hexagon/HexagonFrameLowering.cpp | 3 +
.../test/CodeGen/Hexagon/shadow-call-stack.ll | 69 +++++++++++++++++++
2 files changed, 72 insertions(+)
diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
index 9a02bfda4b4a9..41e422a8ac362 100644
--- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
@@ -3000,6 +3000,9 @@ bool HexagonFrameLowering::useRestoreFunction(const MachineFunction &MF,
const CSIVect &CSI) const {
if (shouldInlineCSR(MF, CSI))
return false;
+ // The returning restore stubs do jumpr r31, this breaks ShadowCallStack:
+ if (MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+ return false;
// The restore functions do a bit more than just restoring registers.
// The non-returning versions will go back directly to the caller's
// caller, others will clean up the stack frame in preparation for
diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
index ae6b254f5ed8e..b4e03f17e00a3 100644
--- a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -71,6 +71,43 @@
; CHECK-NOT: r19 = add(r19,#-4)
; CHECK-LABEL: nonleaf_cfi:
+;; Minsize + multiple callee-saved registers: the restore stub
+;; (__restore_r16_through_r17_and_deallocframe) must NOT be used when SCS is
+;; active because it performs deallocframe+jumpr without the SCS epilogue.
+; CHECK-LABEL: minsize_multicall:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK-NOT: __restore_r16_through_r17_and_deallocframe
+; CHECK: {
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: }
+; CHECK: jumpr r31
+
+;; Minsize + tail call + multiple callee-saved registers: the tailcall restore
+;; stub (__restore_r16_through_r17_and_deallocframe_before_tailcall) must NOT be
+;; used when SCS is active. The SCS epilogue and tail jump are fused together.
+; CHECK-LABEL: minsize_tailcall:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK-NOT: __restore_r16_through_r17_and_deallocframe_before_tailcall
+; CHECK: {
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: jump bar
+; CHECK: }
+
+;; Multiple return paths - each exit block gets its own SCS epilogue.
+; CHECK-LABEL: multi_return:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK: r31 = memw(r19+#-4)
+; CHECK: r19 = add(r19,#-4)
+; CHECK: jumpr r31
+; CHECK: r31 = memw(r19+#-4)
+; CHECK: r19 = add(r19,#-4)
+; CHECK: jumpr r31
+
;; Without r19 reserved, SCS should report an error.
; ERR: Must reserve r19 to use shadow call stack on Hexagon
@@ -97,7 +134,9 @@
; MUSL: }
; MUSL: jumpr r31
+declare i32 @foo(i32)
declare void @bar()
+declare void @baz(i32)
define void @leaf() shadowcallstack nounwind {
ret void
@@ -143,3 +182,33 @@ define void @vararg_musl(i32 %a, ...) shadowcallstack nounwind {
call void @bar()
ret void
}
+
+define i32 @minsize_multicall(i32 %x) shadowcallstack nounwind minsize
+ "disable-tail-calls"="true" {
+ %call = call i32 @foo(i32 %x)
+ %call1 = call i32 @foo(i32 %x)
+ %sum = add i32 %call, %call1
+ ret i32 %sum
+}
+
+define void @minsize_tailcall(i32 %x) shadowcallstack nounwind minsize {
+ call void @baz(i32 %x)
+ call void @baz(i32 %x)
+ tail call void @bar()
+ ret void
+}
+
+define i32 @multi_return(i32 %x) shadowcallstack nounwind optnone noinline {
+entry:
+ %call = call i32 @foo(i32 %x)
+ %cmp = icmp sgt i32 %call, 0
+ br i1 %cmp, label %pos, label %neg
+
+pos:
+ %r1 = call i32 @foo(i32 %call)
+ ret i32 %r1
+
+neg:
+ %r2 = call i32 @foo(i32 0)
+ ret i32 %r2
+}
>From 7ff44fa721469bbaa019b20b97312fd81864bb8c Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sun, 28 Jun 2026 10:55:41 -0700
Subject: [PATCH 2/2] fixup! [Hexagon] Disable restore stubs when
ShadowCallStack is active
Tighten CHECK-NOT placement: move __restore_ checks to after the SCS
epilogue packet and before the exit instruction, so the negative check
covers the return/tail-jump rather than only the range before the
opening {. Use the __restore_ prefix to cover both stub variants.
---
llvm/test/CodeGen/Hexagon/shadow-call-stack.ll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
index b4e03f17e00a3..ddcca760cd474 100644
--- a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -77,11 +77,11 @@
; CHECK-LABEL: minsize_multicall:
; CHECK: r19 = add(r19,#4)
; CHECK: memw(r19+#-4) = r31
-; CHECK-NOT: __restore_r16_through_r17_and_deallocframe
; CHECK: {
; CHECK-DAG: r19 = add(r19,#-4)
; CHECK-DAG: r31 = memw(r19+#-4)
; CHECK: }
+; CHECK-NOT: __restore_
; CHECK: jumpr r31
;; Minsize + tail call + multiple callee-saved registers: the tailcall restore
@@ -90,12 +90,12 @@
; CHECK-LABEL: minsize_tailcall:
; CHECK: r19 = add(r19,#4)
; CHECK: memw(r19+#-4) = r31
-; CHECK-NOT: __restore_r16_through_r17_and_deallocframe_before_tailcall
; CHECK: {
; CHECK-DAG: r19 = add(r19,#-4)
; CHECK-DAG: r31 = memw(r19+#-4)
; CHECK-DAG: jump bar
; CHECK: }
+; CHECK-NOT: __restore_
;; Multiple return paths - each exit block gets its own SCS epilogue.
; CHECK-LABEL: multi_return:
More information about the llvm-commits
mailing list