[llvm] [RISC-V] Do not emit cm.popret[z] with zicfiss (PR #196267)
Nemanja Ivanovic via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 15 00:48:37 PDT 2026
https://github.com/nemanjai updated https://github.com/llvm/llvm-project/pull/196267
>From d4683ec222c93625f44be5a8f00f549cd1897150 Mon Sep 17 00:00:00 2001
From: Nemanja Ivanovic <nemanja at synopsys.com>
Date: Thu, 7 May 2026 11:33:08 +0200
Subject: [PATCH] [RISC-V] Do not emit cm.popret[z] with zicfiss
When emitting shadow call stack protection instructions,
the push/pop optimization needs to be turned off because
an sspopchk before a cm.popret[z] is guaranteed to fail
in a non-leaf function. In addition, the sspopchk must
be emitted after a cm.pop so that the ra has the correct
value when the check is performed.
Fixes: https://github.com/llvm/llvm-project/issues/196261
---
llvm/lib/Target/RISCV/RISCVFrameLowering.cpp | 4 +
.../Target/RISCV/RISCVPushPopOptimizer.cpp | 9 +
llvm/test/CodeGen/RISCV/shadow-stack-zcmp.ll | 57 ++++++
llvm/test/CodeGen/RISCV/shadowcallstack.ll | 166 ++++++++++++++++++
4 files changed, 236 insertions(+)
create mode 100644 llvm/test/CodeGen/RISCV/shadow-stack-zcmp.ll
diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
index 7facdb1b4d548..64658aad72996 100644
--- a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
@@ -12,6 +12,7 @@
#include "RISCVFrameLowering.h"
#include "MCTargetDesc/RISCVBaseInfo.h"
+#include "MCTargetDesc/RISCVMCTargetDesc.h"
#include "RISCVMachineFunctionInfo.h"
#include "RISCVSubtarget.h"
#include "llvm/BinaryFormat/Dwarf.h"
@@ -195,6 +196,9 @@ static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB,
CSI, [&](CalleeSavedInfo &CSR) { return CSR.getReg() == RAReg; }))
return;
+ // The shadow call stack popchk needs to happen after cm.pop that loads ra.
+ if (MI->getOpcode() == RISCV::CM_POP || MI->getOpcode() == RISCV::QC_CM_POP)
+ ++MI;
const RISCVInstrInfo *TII = STI.getInstrInfo();
if (HasHWShadowStack) {
BuildMI(MBB, MI, DL, TII->get(RISCV::SSPOPCHK)).addReg(RAReg);
diff --git a/llvm/lib/Target/RISCV/RISCVPushPopOptimizer.cpp b/llvm/lib/Target/RISCV/RISCVPushPopOptimizer.cpp
index eae7e8697f0ad..656e0f82621d5 100644
--- a/llvm/lib/Target/RISCV/RISCVPushPopOptimizer.cpp
+++ b/llvm/lib/Target/RISCV/RISCVPushPopOptimizer.cpp
@@ -138,6 +138,15 @@ bool RISCVPushPopOpt::runOnMachineFunction(MachineFunction &Fn) {
if (!Subtarget->hasStdExtZcmp() && !Subtarget->hasVendorXqccmp())
return false;
+ // We don't want any popret[z] instructions when emitting code with shadow
+ // stack protection. Note that this pass would actually fail to insert any
+ // popret[z] instructions in this case since the cm.pop and ret will not be
+ // adjacent. But there's no point in running a pass that won't do anything.
+ if ((Fn.getFunction().hasFnAttribute("hw-shadow-stack") &&
+ Subtarget->hasStdExtZimop()) ||
+ Subtarget->hasStdExtZimop())
+ return false;
+
TII = Subtarget->getInstrInfo();
TRI = Subtarget->getRegisterInfo();
diff --git a/llvm/test/CodeGen/RISCV/shadow-stack-zcmp.ll b/llvm/test/CodeGen/RISCV/shadow-stack-zcmp.ll
new file mode 100644
index 0000000000000..77feb85036981
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/shadow-stack-zcmp.ll
@@ -0,0 +1,57 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s \
+; RUN: | FileCheck %s --check-prefix=RV32
+ at .str = private unnamed_addr constant [13 x i8] c"Val[%d]: %d\0A\00", align 1
+
+define i32 @printSomething(ptr %arr, i32 %len) #0 {
+; RV32-LABEL: printSomething:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: sspush ra
+; RV32-NEXT: cm.push {ra, s0-s3}, -32
+; RV32-NEXT: .cfi_def_cfa_offset 32
+; RV32-NEXT: .cfi_offset ra, -20
+; RV32-NEXT: .cfi_offset s0, -16
+; RV32-NEXT: .cfi_offset s1, -12
+; RV32-NEXT: .cfi_offset s2, -8
+; RV32-NEXT: .cfi_offset s3, -4
+; RV32-NEXT: mv s0, a1
+; RV32-NEXT: blez a1, .LBB0_3
+; RV32-NEXT: # %bb.1: # %for.body.preheader
+; RV32-NEXT: mv s1, a0
+; RV32-NEXT: li s2, 0
+; RV32-NEXT: lui s3, %hi(.L.str)
+; RV32-NEXT: addi s3, s3, %lo(.L.str)
+; RV32-NEXT: .LBB0_2: # %for.body
+; RV32-NEXT: # =>This Inner Loop Header: Depth=1
+; RV32-NEXT: lw a2, 0(s1)
+; RV32-NEXT: cm.mva01s s3, s2
+; RV32-NEXT: call printf
+; RV32-NEXT: addi s2, s2, 1
+; RV32-NEXT: addi s1, s1, 4
+; RV32-NEXT: bne s0, s2, .LBB0_2
+; RV32-NEXT: .LBB0_3: # %for.cond.cleanup
+; RV32-NEXT: mv a0, s0
+; RV32-NEXT: cm.pop {ra, s0-s3}, 32
+; RV32-NEXT: sspopchk ra
+; RV32-NEXT: ret
+entry:
+ %cmp5 = icmp sgt i32 %len, 0
+ br i1 %cmp5, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup: ; preds = %for.body, %entry
+ ret i32 %len
+
+for.body: ; preds = %entry, %for.body
+ %i.06 = phi i32 [ %inc, %for.body ], [ 0, %entry ]
+ %arrayidx = getelementptr inbounds nuw [4 x i8], ptr %arr, i32 %i.06
+ %0 = load i32, ptr %arrayidx, align 4
+ %call = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str, i32 noundef %i.06, i32 noundef %0)
+ %inc = add nuw nsw i32 %i.06, 1
+ %exitcond.not = icmp eq i32 %inc, %len
+ br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
+}
+
+; Function Attrs: nofree nounwind
+declare dso_local noundef i32 @printf(ptr noundef readonly captures(none), ...) local_unnamed_addr #0
+
+attributes #0 = { "hw-shadow-stack" "target-features"="+experimental-zicfiss,+zcmop,+zcmp" }
diff --git a/llvm/test/CodeGen/RISCV/shadowcallstack.ll b/llvm/test/CodeGen/RISCV/shadowcallstack.ll
index 03acd9491fed8..07e10cbf1e2a9 100644
--- a/llvm/test/CodeGen/RISCV/shadowcallstack.ll
+++ b/llvm/test/CodeGen/RISCV/shadowcallstack.ll
@@ -3,6 +3,8 @@
; RUN: | FileCheck %s --check-prefix=RV32
; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s \
; RUN: | FileCheck %s --check-prefix=RV64
+; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s \
+; RUN: -mattr=+zcmp | FileCheck %s --check-prefix=RV64-ZCMP
; RUN: llc -mtriple=riscv32 -mattr=+experimental-zicfiss < %s \
; RUN: -verify-machineinstrs | FileCheck %s --check-prefix=RV32-ZICFISS
; RUN: llc -mtriple=riscv64 -mattr=+experimental-zicfiss < %s \
@@ -17,6 +19,10 @@ define void @f1() shadowcallstack {
; RV64: # %bb.0:
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f1:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f1:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: ret
@@ -38,6 +44,10 @@ define void @f2() shadowcallstack {
; RV64: # %bb.0:
; RV64-NEXT: tail foo
;
+; RV64-ZCMP-LABEL: f2:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: tail foo
+;
; RV32-ZICFISS-LABEL: f2:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: tail foo
@@ -90,6 +100,21 @@ define i32 @f3() shadowcallstack {
; RV64-NEXT: .cfi_restore gp
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f3:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: addi gp, gp, 8
+; RV64-ZCMP-NEXT: sd ra, -8(gp)
+; RV64-ZCMP-NEXT: .cfi_escape 0x16, 0x03, 0x02, 0x73, 0x78 #
+; RV64-ZCMP-NEXT: cm.push {ra}, -16
+; RV64-ZCMP-NEXT: .cfi_def_cfa_offset 16
+; RV64-ZCMP-NEXT: .cfi_offset ra, -8
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: cm.pop {ra}, 16
+; RV64-ZCMP-NEXT: ld ra, -8(gp)
+; RV64-ZCMP-NEXT: addi gp, gp, -8
+; RV64-ZCMP-NEXT: .cfi_restore gp
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f3:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: addi gp, gp, 4
@@ -213,6 +238,33 @@ define i32 @f4() shadowcallstack {
; RV64-NEXT: .cfi_restore gp
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f4:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: addi gp, gp, 8
+; RV64-ZCMP-NEXT: sd ra, -8(gp)
+; RV64-ZCMP-NEXT: .cfi_escape 0x16, 0x03, 0x02, 0x73, 0x78 #
+; RV64-ZCMP-NEXT: cm.push {ra, s0-s2}, -32
+; RV64-ZCMP-NEXT: .cfi_def_cfa_offset 32
+; RV64-ZCMP-NEXT: .cfi_offset ra, -32
+; RV64-ZCMP-NEXT: .cfi_offset s0, -24
+; RV64-ZCMP-NEXT: .cfi_offset s1, -16
+; RV64-ZCMP-NEXT: .cfi_offset s2, -8
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s0, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s1, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s2, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: add s0, s0, s1
+; RV64-ZCMP-NEXT: add a0, a0, s2
+; RV64-ZCMP-NEXT: addw a0, a0, s0
+; RV64-ZCMP-NEXT: cm.pop {ra, s0-s2}, 32
+; RV64-ZCMP-NEXT: ld ra, -8(gp)
+; RV64-ZCMP-NEXT: addi gp, gp, -8
+; RV64-ZCMP-NEXT: .cfi_restore gp
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f4:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: addi gp, gp, 4
@@ -329,6 +381,17 @@ define i32 @f5() shadowcallstack nounwind {
; RV64-NEXT: addi gp, gp, -8
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f5:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: addi gp, gp, 8
+; RV64-ZCMP-NEXT: sd ra, -8(gp)
+; RV64-ZCMP-NEXT: cm.push {ra}, -16
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: cm.pop {ra}, 16
+; RV64-ZCMP-NEXT: ld ra, -8(gp)
+; RV64-ZCMP-NEXT: addi gp, gp, -8
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f5:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: addi gp, gp, 4
@@ -368,6 +431,10 @@ define void @f1_hw() "hw-shadow-stack" {
; RV64: # %bb.0:
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f1_hw:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f1_hw:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: ret
@@ -387,6 +454,10 @@ define void @f2_hw() "hw-shadow-stack" {
; RV64: # %bb.0:
; RV64-NEXT: tail foo
;
+; RV64-ZCMP-LABEL: f2_hw:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: tail foo
+;
; RV32-ZICFISS-LABEL: f2_hw:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: tail foo
@@ -425,6 +496,14 @@ define i32 @f3_hw() "hw-shadow-stack" {
; RV64-NEXT: .cfi_def_cfa_offset 0
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f3_hw:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: cm.push {ra}, -16
+; RV64-ZCMP-NEXT: .cfi_def_cfa_offset 16
+; RV64-ZCMP-NEXT: .cfi_offset ra, -8
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: cm.popret {ra}, 16
+;
; RV32-ZICFISS-LABEL: f3_hw:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: sspush ra
@@ -528,6 +607,26 @@ define i32 @f4_hw() "hw-shadow-stack" {
; RV64-NEXT: .cfi_def_cfa_offset 0
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f4_hw:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: cm.push {ra, s0-s2}, -32
+; RV64-ZCMP-NEXT: .cfi_def_cfa_offset 32
+; RV64-ZCMP-NEXT: .cfi_offset ra, -32
+; RV64-ZCMP-NEXT: .cfi_offset s0, -24
+; RV64-ZCMP-NEXT: .cfi_offset s1, -16
+; RV64-ZCMP-NEXT: .cfi_offset s2, -8
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s0, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s1, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s2, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: add s0, s0, s1
+; RV64-ZCMP-NEXT: add a0, a0, s2
+; RV64-ZCMP-NEXT: addw a0, a0, s0
+; RV64-ZCMP-NEXT: cm.popret {ra, s0-s2}, 32
+;
; RV32-ZICFISS-LABEL: f4_hw:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: sspush ra
@@ -628,6 +727,12 @@ define i32 @f5_hw() "hw-shadow-stack" nounwind {
; RV64-NEXT: addi sp, sp, 16
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f5_hw:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: cm.push {ra}, -16
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: cm.popret {ra}, 16
+;
; RV32-ZICFISS-LABEL: f5_hw:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: sspush ra
@@ -663,6 +768,10 @@ define void @f1_both() "hw-shadow-stack" shadowcallstack {
; RV64: # %bb.0:
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f1_both:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f1_both:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: ret
@@ -682,6 +791,10 @@ define void @f2_both() "hw-shadow-stack" shadowcallstack {
; RV64: # %bb.0:
; RV64-NEXT: tail foo
;
+; RV64-ZCMP-LABEL: f2_both:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: tail foo
+;
; RV32-ZICFISS-LABEL: f2_both:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: tail foo
@@ -732,6 +845,21 @@ define i32 @f3_both() "hw-shadow-stack" shadowcallstack {
; RV64-NEXT: .cfi_restore gp
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f3_both:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: addi gp, gp, 8
+; RV64-ZCMP-NEXT: sd ra, -8(gp)
+; RV64-ZCMP-NEXT: .cfi_escape 0x16, 0x03, 0x02, 0x73, 0x78 #
+; RV64-ZCMP-NEXT: cm.push {ra}, -16
+; RV64-ZCMP-NEXT: .cfi_def_cfa_offset 16
+; RV64-ZCMP-NEXT: .cfi_offset ra, -8
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: cm.pop {ra}, 16
+; RV64-ZCMP-NEXT: ld ra, -8(gp)
+; RV64-ZCMP-NEXT: addi gp, gp, -8
+; RV64-ZCMP-NEXT: .cfi_restore gp
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f3_both:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: sspush ra
@@ -847,6 +975,33 @@ define i32 @f4_both() "hw-shadow-stack" shadowcallstack {
; RV64-NEXT: .cfi_restore gp
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f4_both:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: addi gp, gp, 8
+; RV64-ZCMP-NEXT: sd ra, -8(gp)
+; RV64-ZCMP-NEXT: .cfi_escape 0x16, 0x03, 0x02, 0x73, 0x78 #
+; RV64-ZCMP-NEXT: cm.push {ra, s0-s2}, -32
+; RV64-ZCMP-NEXT: .cfi_def_cfa_offset 32
+; RV64-ZCMP-NEXT: .cfi_offset ra, -32
+; RV64-ZCMP-NEXT: .cfi_offset s0, -24
+; RV64-ZCMP-NEXT: .cfi_offset s1, -16
+; RV64-ZCMP-NEXT: .cfi_offset s2, -8
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s0, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s1, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: mv s2, a0
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: add s0, s0, s1
+; RV64-ZCMP-NEXT: add a0, a0, s2
+; RV64-ZCMP-NEXT: addw a0, a0, s0
+; RV64-ZCMP-NEXT: cm.pop {ra, s0-s2}, 32
+; RV64-ZCMP-NEXT: ld ra, -8(gp)
+; RV64-ZCMP-NEXT: addi gp, gp, -8
+; RV64-ZCMP-NEXT: .cfi_restore gp
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f4_both:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: sspush ra
@@ -955,6 +1110,17 @@ define i32 @f5_both() "hw-shadow-stack" shadowcallstack nounwind {
; RV64-NEXT: addi gp, gp, -8
; RV64-NEXT: ret
;
+; RV64-ZCMP-LABEL: f5_both:
+; RV64-ZCMP: # %bb.0:
+; RV64-ZCMP-NEXT: addi gp, gp, 8
+; RV64-ZCMP-NEXT: sd ra, -8(gp)
+; RV64-ZCMP-NEXT: cm.push {ra}, -16
+; RV64-ZCMP-NEXT: call bar
+; RV64-ZCMP-NEXT: cm.pop {ra}, 16
+; RV64-ZCMP-NEXT: ld ra, -8(gp)
+; RV64-ZCMP-NEXT: addi gp, gp, -8
+; RV64-ZCMP-NEXT: ret
+;
; RV32-ZICFISS-LABEL: f5_both:
; RV32-ZICFISS: # %bb.0:
; RV32-ZICFISS-NEXT: sspush ra
More information about the llvm-commits
mailing list