[llvm] [RISCV] Ensure LPAD alignment for calls to returns_twice functions (PR #177515)
Jerry Zhang Jian via llvm-commits
llvm-commits at lists.llvm.org
Thu Mar 19 23:14:00 PDT 2026
https://github.com/jerryzj updated https://github.com/llvm/llvm-project/pull/177515
>From 5171692215eb0bc89bd36fadb3b61e48c40074ec Mon Sep 17 00:00:00 2001
From: Jerry Zhang Jian <jerry.zhangjian at sifive.com>
Date: Thu, 22 Jan 2026 17:57:41 -0800
Subject: [PATCH] [RISCV] Ensure LPAD alignment for calls to returns_twice
functions
When Zicfilp is enabled, all LPAD instructions must be 4-byte aligned,
including those following a returns_twice call. Linker relaxation can
convert calls to c.jal (RV32C) or cm.jalt (Zcmt), causing LPAD misalignment.
This patch handles calls to returns_twice functions (e.g., setjmp) when
Zicfilp CFI is enabled:
1. In ISel (LowerCall), detect CLI.CB->hasFnAttr(Attribute::ReturnsTwice)
and select LPAD_CALL or LPAD_CALL_INDIRECT ISD opcodes. Custom ISel
in RISCVISelDAGToDAG adds the landing pad label as a pseudo operand.
2. RISCVAsmPrinter::emitLpadAlignedCall handles assembly output: when Zca
is enabled, emit .p2align 2 before the call. When linker relaxation is
also enabled, wrap with .option push/exact/pop to prevent relaxation.
The LPAD instruction is emitted immediately after the call.
3. RISCVMCCodeEmitter::expandFunctionCallLpad handles object output:
expands the pseudo to AUIPC+JALR+LPAD (direct) or JALR+LPAD (indirect),
without R_RISCV_RELAX relocation.
Note: Both AsmPrinter and MCCodeEmitter need separate expansion logic
because GNU assembler doesn't yet support call.lpad/jalr.lpad syntax.
4. RISCVIndirectBranchTracking no longer needs to scan for returns_twice
calls since LPAD is now emitted directly by the pseudo expansion.
The existing lpad.ll test is updated to add cf-protection-branch module
flag, as the test_returns_twice function was intended to test CFI-enabled
behavior. Without this flag, LPAD insertion after setjmp would not be
triggered.
Known issue: When Large Code Model and Zicfilp are both enabled, direct
calls to returns_twice functions use SW_GUARDED_CALL which takes priority
over LPAD_CALL. This combination is not yet handled and will be addressed
in a follow-up patch.
Signed-off-by: Jerry Zhang Jian <jerry.zhangjian at sifive.com>
---
.../RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp | 61 ++++++++++
llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp | 78 +++++++++++-
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 27 +++++
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 19 ++-
.../RISCV/RISCVIndirectBranchTracking.cpp | 23 ----
llvm/lib/Target/RISCV/RISCVInstrInfo.td | 33 ++++++
llvm/test/CodeGen/RISCV/lpad-setjmp-isel.ll | 59 ++++++++++
llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll | 24 ++++
llvm/test/CodeGen/RISCV/lpad-setjmp.ll | 111 ++++++++++++++++++
llvm/test/CodeGen/RISCV/lpad.ll | 6 +
10 files changed, 416 insertions(+), 25 deletions(-)
create mode 100644 llvm/test/CodeGen/RISCV/lpad-setjmp-isel.ll
create mode 100644 llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll
create mode 100644 llvm/test/CodeGen/RISCV/lpad-setjmp.ll
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index 4304a1e651ca5..17fb448e328d3 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -68,6 +68,10 @@ class RISCVMCCodeEmitter : public MCCodeEmitter {
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
+ void expandFunctionCallLpad(const MCInst &MI, SmallVectorImpl<char> &CB,
+ SmallVectorImpl<MCFixup> &Fixups,
+ const MCSubtargetInfo &STI) const;
+
void expandQCLongCondBrImm(const MCInst &MI, SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI, unsigned Size) const;
@@ -209,6 +213,55 @@ void RISCVMCCodeEmitter::expandFunctionCall(const MCInst &MI,
support::endian::write(CB, Binary, llvm::endianness::little);
}
+// Expand to AUIPC+JALR+LPAD (direct) or JALR+LPAD (indirect).
+// R_RISCV_RELAX is not emitted for the call because linker relaxation could
+// convert it to c.jal/cm.jalt, which would misalign the following LPAD.
+void RISCVMCCodeEmitter::expandFunctionCallLpad(
+ const MCInst &MI, SmallVectorImpl<char> &CB,
+ SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
+ bool IsIndirect = MI.getOpcode() == RISCV::PseudoCALLIndirectLpadAlign;
+ MCInst TmpInst;
+ uint32_t Binary;
+
+ if (!IsIndirect) {
+ MCOperand Func = MI.getOperand(0);
+ assert(Func.isExpr() && "Expected expression for call target");
+
+ // Use a STI without FeatureRelax so getImmOpValue does not mark the
+ // R_RISCV_CALL_PLT fixup as LinkerRelaxable (which would emit
+ // R_RISCV_RELAX).
+ MCSubtargetInfo NoRelaxSTI(STI);
+ if (STI.hasFeature(RISCV::FeatureRelax))
+ NoRelaxSTI.ToggleFeature(RISCV::FeatureRelax);
+
+ TmpInst =
+ MCInstBuilder(RISCV::AUIPC).addReg(RISCV::X1).addExpr(Func.getExpr());
+ Binary = getBinaryCodeForInstr(TmpInst, Fixups, NoRelaxSTI);
+ support::endian::write(CB, Binary, llvm::endianness::little);
+
+ TmpInst = MCInstBuilder(RISCV::JALR)
+ .addReg(RISCV::X1)
+ .addReg(RISCV::X1)
+ .addImm(0);
+ Binary = getBinaryCodeForInstr(TmpInst, Fixups, STI);
+ support::endian::write(CB, Binary, llvm::endianness::little);
+ } else {
+ TmpInst = MCInstBuilder(RISCV::JALR)
+ .addReg(RISCV::X1)
+ .addReg(MI.getOperand(0).getReg())
+ .addImm(0);
+ Binary = getBinaryCodeForInstr(TmpInst, Fixups, STI);
+ support::endian::write(CB, Binary, llvm::endianness::little);
+ }
+
+ // LPAD is encoded as AUIPC X0, label.
+ TmpInst = MCInstBuilder(RISCV::AUIPC)
+ .addReg(RISCV::X0)
+ .addImm(MI.getOperand(1).getImm());
+ Binary = getBinaryCodeForInstr(TmpInst, Fixups, STI);
+ support::endian::write(CB, Binary, llvm::endianness::little);
+}
+
void RISCVMCCodeEmitter::expandTLSDESCCall(const MCInst &MI,
SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups,
@@ -436,6 +489,14 @@ void RISCVMCCodeEmitter::encodeInstruction(const MCInst &MI,
expandFunctionCall(MI, CB, Fixups, STI);
MCNumEmitted += 2;
return;
+ case RISCV::PseudoCALLLpadAlign:
+ expandFunctionCallLpad(MI, CB, Fixups, STI);
+ MCNumEmitted += 3; // AUIPC + JALR + LPAD
+ return;
+ case RISCV::PseudoCALLIndirectLpadAlign:
+ expandFunctionCallLpad(MI, CB, Fixups, STI);
+ MCNumEmitted += 2; // JALR + LPAD
+ return;
case RISCV::PseudoAddTPRel:
expandAddTPRel(MI, CB, Fixups, STI);
MCNumEmitted += 1;
diff --git a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
index eb15227a72a83..4031c908febf2 100644
--- a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
+++ b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
@@ -121,6 +121,8 @@ class RISCVAsmPrinter : public AsmPrinter {
void emitNTLHint(const MachineInstr *MI);
+ void emitLpadAlignedCall(const MachineInstr &MI);
+
// XRay Support
void LowerPATCHABLE_FUNCTION_ENTER(const MachineInstr *MI);
void LowerPATCHABLE_FUNCTION_EXIT(const MachineInstr *MI);
@@ -129,7 +131,7 @@ class RISCVAsmPrinter : public AsmPrinter {
void lowerToMCInst(const MachineInstr *MI, MCInst &OutMI);
};
-}
+} // namespace
void RISCVAsmPrinter::LowerSTACKMAP(MCStreamer &OutStreamer, StackMaps &SM,
const MachineInstr &MI) {
@@ -273,6 +275,76 @@ bool RISCVAsmPrinter::EmitToStreamer(MCStreamer &S, const MCInst &Inst,
// instructions) auto-generated.
#include "RISCVGenMCPseudoLowering.inc"
+// Emit a call to a returns_twice function with LPAD.
+// When Zca is enabled, emit .p2align 2 before the call to ensure the
+// following LPAD is 4-byte aligned. For assembly output, wrap with
+// .option push/exact/pop to prevent relaxation. For object output,
+// emit the pseudo directly so MCCodeEmitter handles it without R_RISCV_RELAX.
+void RISCVAsmPrinter::emitLpadAlignedCall(const MachineInstr &MI) {
+ const MCSubtargetInfo &MCSTI = getSubtargetInfo();
+ const bool IsIndirect = MI.getOpcode() == RISCV::PseudoCALLIndirectLpadAlign,
+ HasZca = MCSTI.hasFeature(RISCV::FeatureStdExtZca),
+ HasRelax = MCSTI.hasFeature(RISCV::FeatureRelax);
+
+ if (HasZca)
+ OutStreamer->emitCodeAlignment(Align(4), &MCSTI);
+
+ if (OutStreamer->hasRawTextSupport()) {
+ // Assembly path: wrap call with .option push/exact/pop and emit LPAD
+ // separately so the output is human-readable.
+ auto *RTS =
+ static_cast<RISCVTargetStreamer *>(OutStreamer->getTargetStreamer());
+ if (HasZca && HasRelax) {
+ RTS->emitDirectiveOptionPush();
+ RTS->emitDirectiveOptionExact();
+ }
+
+ MCInst CallInst;
+ if (!IsIndirect) {
+ CallInst.setOpcode(RISCV::PseudoCALL);
+ MCOperand MCOp;
+ lowerOperand(MI.getOperand(0), MCOp);
+ CallInst.addOperand(MCOp);
+ } else {
+ CallInst.setOpcode(RISCV::JALR);
+ CallInst.addOperand(MCOperand::createReg(RISCV::X1));
+ CallInst.addOperand(MCOperand::createReg(MI.getOperand(0).getReg()));
+ CallInst.addOperand(MCOperand::createImm(0));
+ }
+
+ if (HasZca && HasRelax) {
+ MCSubtargetInfo NoRelaxSTI(MCSTI);
+ NoRelaxSTI.ToggleFeature(RISCV::FeatureRelax);
+ EmitToStreamer(*OutStreamer, CallInst, NoRelaxSTI);
+ RTS->emitDirectiveOptionPop();
+ } else {
+ EmitToStreamer(*OutStreamer, CallInst, MCSTI);
+ }
+
+ // LPAD is encoded as AUIPC X0, label.
+ MCInst LpadInst;
+ LpadInst.setOpcode(RISCV::AUIPC);
+ LpadInst.addOperand(MCOperand::createReg(RISCV::X0));
+ LpadInst.addOperand(MCOperand::createImm(MI.getOperand(1).getImm()));
+ EmitToStreamer(*OutStreamer, LpadInst, MCSTI);
+ } else {
+ // Object path: emit PseudoCALL(Indirect)LpadAlign directly.
+ // MCCodeEmitter::expandFunctionCallLpad expands to AUIPC+JALR+LPAD
+ // without emitting R_RISCV_RELAX on the call fixup.
+ MCInst TmpInst;
+ TmpInst.setOpcode(MI.getOpcode());
+ if (!IsIndirect) {
+ MCOperand MCOp;
+ lowerOperand(MI.getOperand(0), MCOp);
+ TmpInst.addOperand(MCOp);
+ } else {
+ TmpInst.addOperand(MCOperand::createReg(MI.getOperand(0).getReg()));
+ }
+ TmpInst.addOperand(MCOperand::createImm(MI.getOperand(1).getImm()));
+ EmitToStreamer(*OutStreamer, TmpInst, MCSTI);
+ }
+}
+
// If the instruction has a nontemporal MachineMemOperand, emit an NTL hint
// instruction before it. NTL hints are always safe to emit since they use
// HINT encodings that are guaranteed not to trap
@@ -351,6 +423,10 @@ void RISCVAsmPrinter::emitInstruction(const MachineInstr *MI) {
case TargetOpcode::PATCHABLE_TAIL_CALL:
LowerPATCHABLE_TAIL_CALL(MI);
return;
+ case RISCV::PseudoCALLLpadAlign:
+ case RISCV::PseudoCALLIndirectLpadAlign:
+ emitLpadAlignedCall(*MI);
+ return;
}
MCInst OutInst;
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 6387ab998d7b8..804316bbaa778 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -30,6 +30,8 @@ using namespace llvm;
#define DEBUG_TYPE "riscv-isel"
#define PASS_NAME "RISC-V DAG->DAG Pattern Instruction Selection"
+extern cl::opt<uint32_t> PreferredLandingPadLabel;
+
static cl::opt<bool> UsePseudoMovImm(
"riscv-use-rematerializable-movimm", cl::Hidden,
cl::desc("Use a rematerializable pseudoinstruction for 2 instruction "
@@ -3060,6 +3062,31 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
ReplaceNode(Node, Load);
return;
}
+ case RISCVISD::LPAD_CALL:
+ case RISCVISD::LPAD_CALL_INDIRECT: {
+ bool IsIndirect = Opcode == RISCVISD::LPAD_CALL_INDIRECT;
+ unsigned PseudoOpc = IsIndirect ? RISCV::PseudoCALLIndirectLpadAlign
+ : RISCV::PseudoCALLLpadAlign;
+
+ uint32_t LpadLabel = 0;
+ if (PreferredLandingPadLabel.getNumOccurrences() > 0) {
+ if (!isUInt<20>(PreferredLandingPadLabel))
+ report_fatal_error("riscv-landing-pad-label=<val>, <val> needs to fit "
+ "in unsigned 20-bits");
+ LpadLabel = PreferredLandingPadLabel;
+ }
+
+ SmallVector<SDValue, 4> Ops;
+ Ops.push_back(Node->getOperand(1));
+ Ops.push_back(CurDAG->getTargetConstant(LpadLabel, DL, XLenVT));
+ Ops.push_back(Node->getOperand(0));
+ if (Node->getGluedNode())
+ Ops.push_back(Node->getOperand(Node->getNumOperands() - 1));
+
+ ReplaceNode(Node,
+ CurDAG->getMachineNode(PseudoOpc, DL, Node->getVTList(), Ops));
+ return;
+ }
case ISD::PREFETCH:
// MIPS's prefetch instruction already encodes the hint within the
// instruction itself, so no extra NTL hint is needed.
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 83f1b8788a145..4110f8ea55fc1 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24888,6 +24888,13 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
((CLI.CB && !CLI.CB->isIndirectCall()) || CalleeIsLargeExternalSymbol))
NeedSWGuarded = true;
+ // Use special pseudo for returns_twice calls (e.g., setjmp) when Zicfilp
+ // CFI is enabled, to ensure LPAD is inserted after the call.
+ bool NeedLpadCall =
+ CLI.CB && CLI.CB->hasFnAttr(Attribute::ReturnsTwice) &&
+ Subtarget.hasStdExtZicfilp() &&
+ MF.getFunction().getParent()->getModuleFlag("cf-protection-branch");
+
if (IsTailCall) {
MF.getFrameInfo().setHasTailCall();
unsigned CallOpc =
@@ -24900,7 +24907,17 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
return Ret;
}
- unsigned CallOpc = NeedSWGuarded ? RISCVISD::SW_GUARDED_CALL : RISCVISD::CALL;
+ unsigned CallOpc;
+ // FIXME: Large Code Model + Zicfilp: SW_GUARDED_CALL takes priority over
+ // LPAD_CALL for returns_twice calls, breaking LPAD alignment.
+ if (NeedSWGuarded)
+ CallOpc = RISCVISD::SW_GUARDED_CALL;
+ else if (NeedLpadCall && CLI.CB->isIndirectCall())
+ CallOpc = RISCVISD::LPAD_CALL_INDIRECT;
+ else if (NeedLpadCall)
+ CallOpc = RISCVISD::LPAD_CALL;
+ else
+ CallOpc = RISCVISD::CALL;
Chain = DAG.getNode(CallOpc, DL, NodeTys, Ops);
if (CLI.CFIType)
Chain.getNode()->setCFIType(CLI.CFIType->getZExtValue());
diff --git a/llvm/lib/Target/RISCV/RISCVIndirectBranchTracking.cpp b/llvm/lib/Target/RISCV/RISCVIndirectBranchTracking.cpp
index 0fc139a30ae76..e845f81a0f44a 100644
--- a/llvm/lib/Target/RISCV/RISCVIndirectBranchTracking.cpp
+++ b/llvm/lib/Target/RISCV/RISCVIndirectBranchTracking.cpp
@@ -64,16 +64,6 @@ emitLpad(MachineBasicBlock &MBB, const RISCVInstrInfo *TII, uint32_t Label,
.addImm(Label);
}
-static bool isCallReturnTwice(const MachineOperand &MOp) {
- if (!MOp.isGlobal())
- return false;
- auto *CalleeFn = dyn_cast<Function>(MOp.getGlobal());
- if (!CalleeFn)
- return false;
- AttributeList Attrs = CalleeFn->getAttributes();
- return Attrs.hasFnAttr(Attribute::ReturnsTwice);
-}
-
bool RISCVIndirectBranchTracking::runOnMachineFunction(MachineFunction &MF) {
const auto &Subtarget = MF.getSubtarget<RISCVSubtarget>();
const RISCVInstrInfo *TII = Subtarget.getInstrInfo();
@@ -113,18 +103,5 @@ bool RISCVIndirectBranchTracking::runOnMachineFunction(MachineFunction &MF) {
}
}
- // Check for calls to functions with ReturnsTwice attribute and insert
- // LPAD after such calls
- for (MachineBasicBlock &MBB : MF) {
- for (MachineBasicBlock::iterator I = MBB.begin(); I != MBB.end(); ++I) {
- if (I->isCall() && I->getNumOperands() > 0 &&
- isCallReturnTwice(I->getOperand(0))) {
- auto NextI = std::next(I);
- emitLpad(MBB, TII, FixedLabel, NextI);
- Changed = true;
- }
- }
- }
-
return Changed;
}
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index a5c0e016aaa83..8f8744059480a 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
@@ -66,6 +66,14 @@ def riscv_sw_guarded_tail : RVSDNode<"SW_GUARDED_TAIL", SDT_RISCVCall,
[SDNPHasChain, SDNPOptInGlue, SDNPOutGlue,
SDNPVariadic]>;
+// LPAD aligned calls
+def riscv_lpad_call
+ : RVSDNode<"LPAD_CALL", SDT_RISCVCall,
+ [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic]>;
+def riscv_lpad_call_indirect
+ : RVSDNode<"LPAD_CALL_INDIRECT", SDT_RISCVCall,
+ [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic]>;
+
def riscv_ret_glue : RVSDNode<"RET_GLUE", SDTNone,
[SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>;
def riscv_sret_glue : RVSDNode<"SRET_GLUE", SDTNone,
@@ -1817,6 +1825,31 @@ def PseudoCALL : Pseudo<(outs), (ins call_symbol:$func), [],
def : Pat<(riscv_call tglobaladdr:$func), (PseudoCALL tglobaladdr:$func)>;
def : Pat<(riscv_call texternalsym:$func), (PseudoCALL texternalsym:$func)>;
+// PseudoCALLLpadAlign is used for calls to returns_twice functions (e.g.,
+// setjmp) when Zicfilp is enabled. RISCVAsmPrinter expands this to:
+// [.p2align 2] [.option exact] call <func> [.option noexact] lpad <label>
+// Size = 12: 8-byte call (auipc+jalr) + 4-byte lpad (auipc x0, label).
+// Note: The 2-byte alignment padding is not included in Size because it's
+// emitted separately via .p2align directive.
+let isCall = 1, Defs = [X1], Size = 12, hasSideEffects = 0, mayLoad = 0,
+ mayStore = 0 in
+def PseudoCALLLpadAlign : Pseudo<(outs),
+ (ins call_symbol:$func, uimm20_auipc:$lpad_label),
+ []>,
+ Sched<[WriteIALU, WriteJalr, ReadJalr]>;
+
+// PseudoCALLIndirectLpadAlign is the indirect call version.
+// RISCVAsmPrinter expands this to:
+// [.p2align 2] [.option exact] jalr ra, <rs1>, 0 [.option noexact] lpad <label>
+// Size = 8: 4-byte jalr + 4-byte lpad.
+let isCall = 1, Defs = [X1], Size = 8, hasSideEffects = 0, mayLoad = 0,
+ mayStore = 0 in
+def PseudoCALLIndirectLpadAlign : Pseudo<(outs),
+ (ins GPRJALRNonX7:$rs1,
+ uimm20_auipc:$lpad_label),
+ []>,
+ Sched<[WriteJalr, ReadJalr]>;
+
def : Pat<(riscv_sret_glue), (SRET)>;
def : Pat<(riscv_mret_glue), (MRET)>;
let Predicates = [HasStdExtSmrnmi] in
diff --git a/llvm/test/CodeGen/RISCV/lpad-setjmp-isel.ll b/llvm/test/CodeGen/RISCV/lpad-setjmp-isel.ll
new file mode 100644
index 0000000000000..8ee8e7c08256e
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/lpad-setjmp-isel.ll
@@ -0,0 +1,59 @@
+; Test that RISCVISelDAGToDAG correctly selects PseudoCALLLpadAlign and
+; PseudoCALLIndirectLpadAlign for RISCVISD::LPAD_CALL and
+; RISCVISD::LPAD_CALL_INDIRECT nodes (returns_twice calls with Zicfilp + CFI).
+
+; RUN: llc -mtriple=riscv32 -mattr=+experimental-zicfilp -stop-after=finalize-isel \
+; RUN: < %s | FileCheck %s --check-prefixes=ZICFILP
+; RUN: llc -mtriple=riscv64 -mattr=+experimental-zicfilp -stop-after=finalize-isel \
+; RUN: < %s | FileCheck %s --check-prefixes=ZICFILP
+; RUN: llc -mtriple=riscv64 -mattr=+experimental-zicfilp -riscv-landing-pad-label=1 \
+; RUN: -stop-after=finalize-isel < %s | FileCheck %s --check-prefixes=LABEL1
+; RUN: llc -mtriple=riscv64 -stop-after=finalize-isel \
+; RUN: < %s | FileCheck %s --check-prefixes=NOZICFILP
+
+declare i32 @setjmp(ptr) returns_twice
+
+; Direct returns_twice call: should select PseudoCALLLpadAlign.
+define i32 @test_direct() {
+ ; ZICFILP-LABEL: name: test_direct
+ ; ZICFILP: PseudoCALLLpadAlign target-flags(riscv-call) @setjmp, 0
+ ; LABEL1-LABEL: name: test_direct
+ ; LABEL1: PseudoCALLLpadAlign target-flags(riscv-call) @setjmp, 1
+ ; Without Zicfilp, a regular PseudoCALL is used instead.
+ ; NOZICFILP-LABEL: name: test_direct
+ ; NOZICFILP-NOT: PseudoCALLLpadAlign
+ ; NOZICFILP: PseudoCALL target-flags(riscv-call) @setjmp
+ %buf = alloca [1 x i32], align 4
+ %call = call i32 @setjmp(ptr %buf)
+ ret i32 %call
+}
+
+; Indirect returns_twice call: should select PseudoCALLIndirectLpadAlign.
+define i32 @test_indirect(ptr %fptr) {
+ ; ZICFILP-LABEL: name: test_indirect
+ ; ZICFILP: PseudoCALLIndirectLpadAlign %{{[0-9]+}}, 0
+ ; LABEL1-LABEL: name: test_indirect
+ ; LABEL1: PseudoCALLIndirectLpadAlign %{{[0-9]+}}, 1
+ ; Without Zicfilp, a regular PseudoCALLIndirect is used.
+ ; NOZICFILP-LABEL: name: test_indirect
+ ; NOZICFILP-NOT: PseudoCALLIndirectLpadAlign
+ ; NOZICFILP: PseudoCALLIndirect %{{[0-9]+}}
+ %call = call i32 %fptr() #0
+ ret i32 %call
+}
+
+; Non-returns_twice calls must NOT select PseudoCALLLpadAlign.
+declare i32 @regular_func()
+
+define i32 @test_regular_call() {
+ ; ZICFILP-LABEL: name: test_regular_call
+ ; ZICFILP-NOT: PseudoCALLLpadAlign
+ ; ZICFILP: PseudoCALL target-flags(riscv-call) @regular_func
+ %call = call i32 @regular_func()
+ ret i32 %call
+}
+
+attributes #0 = { returns_twice }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 8, !"cf-protection-branch", i32 1}
diff --git a/llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll b/llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll
new file mode 100644
index 0000000000000..7ae90ae37a608
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll
@@ -0,0 +1,24 @@
+; RUN: llc -mtriple riscv32 -mattr=+experimental-zicfilp,+relax,+c < %s | FileCheck %s
+; RUN: llc -mtriple riscv64 -mattr=+experimental-zicfilp,+relax,+c < %s | FileCheck %s
+
+; Test that when Zicfilp is enabled but the cf-protection-branch module flag
+; is NOT set, no LPAD is inserted AFTER the setjmp call and no .option push/exact
+; workaround is applied. The function entry LPAD is still generated by Zicfilp,
+; but the special returns_twice handling is not triggered.
+
+declare i32 @setjmp(ptr) returns_twice
+
+define i32 @test_no_cf_protection() {
+; CHECK-LABEL: test_no_cf_protection:
+; CHECK-NOT: .option exact
+; CHECK: call setjmp
+; Verify no lpad immediately after setjmp (next instruction is register reload).
+; Use regex to match both lw (RV32) and ld (RV64).
+; CHECK-NEXT: {{(lw|ld)}} ra,
+ %buf = alloca [1 x i32], align 4
+ %call = call i32 @setjmp(ptr %buf)
+ ret i32 %call
+}
+
+; Note: No !llvm.module.flags with cf-protection-branch - this is intentional.
+
diff --git a/llvm/test/CodeGen/RISCV/lpad-setjmp.ll b/llvm/test/CodeGen/RISCV/lpad-setjmp.ll
new file mode 100644
index 0000000000000..70d34194edd70
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/lpad-setjmp.ll
@@ -0,0 +1,111 @@
+; RUN: llc -mtriple riscv32 -mattr=+experimental-zicfilp,+relax,+c \
+; RUN: < %s | FileCheck %s --check-prefixes=CHECK
+; RUN: llc -mtriple riscv64 -mattr=+experimental-zicfilp,+relax,+c \
+; RUN: < %s | FileCheck %s --check-prefixes=CHECK
+; RUN: llc -mtriple riscv32 -mattr=+experimental-zicfilp,+relax,+zcmt \
+; RUN: < %s | FileCheck %s --check-prefixes=CHECK
+; RUN: llc -mtriple riscv32 -mattr=+experimental-zicfilp,-relax,+c \
+; RUN: < %s | FileCheck %s --check-prefixes=NORELAX
+; RUN: llc -mtriple riscv32 -mattr=+experimental-zicfilp,+relax,-c \
+; RUN: < %s | FileCheck %s --check-prefixes=NOZCA
+; RUN: llc -mtriple riscv64 -mattr=+experimental-zicfilp,+relax,+c \
+; RUN: -riscv-landing-pad-label=1 < %s | FileCheck %s --check-prefixes=LABEL1
+
+; Verify that object file output does not have R_RISCV_RELAX on the setjmp call.
+; RUN: llc -mtriple riscv64 -mattr=+experimental-zicfilp,+relax,+c \
+; RUN: -filetype=obj -o - < %s | llvm-readobj -r - | FileCheck %s --check-prefix=RELOC
+; setjmp call: R_RISCV_CALL_PLT followed by regular_func (no R_RISCV_RELAX in between)
+; regular_func call: R_RISCV_CALL_PLT followed by R_RISCV_RELAX
+; RELOC: R_RISCV_CALL_PLT setjmp
+; RELOC-NEXT: R_RISCV_CALL_PLT regular_func
+; RELOC-NEXT: R_RISCV_RELAX
+
+; Test that returns_twice calls (e.g., setjmp) are wrapped with .option push/exact/pop
+; when Zicfilp + relax are enabled, to prevent LPAD misalignment.
+
+declare i32 @setjmp(ptr) returns_twice
+declare i32 @regular_func(ptr)
+
+define i32 @test_setjmp() {
+; CHECK-LABEL: test_setjmp:
+; CHECK: .p2align 2
+; CHECK-NEXT: .option push
+; CHECK-NEXT: .option exact
+; CHECK-NEXT: call setjmp
+; CHECK-NEXT: .option pop
+; CHECK-NEXT: lpad 0
+;
+; Without relax, only .p2align 2 is needed.
+; NORELAX-LABEL: test_setjmp:
+; NORELAX-NOT: .option push
+; NORELAX-NOT: .option exact
+; NORELAX: .p2align 2
+; NORELAX-NEXT: call setjmp
+; NORELAX-NEXT: lpad 0
+;
+; Without Zca, no .p2align or .option push/exact needed (no compressed calls).
+; NOZCA-LABEL: test_setjmp:
+; NOZCA-NOT: .p2align 2
+; NOZCA-NOT: .option push
+; NOZCA-NOT: .option exact
+; NOZCA: call setjmp
+; NOZCA-NEXT: lpad 0
+;
+; With -riscv-landing-pad-label=1, lpad uses label 1.
+; LABEL1-LABEL: test_setjmp:
+; LABEL1: call setjmp
+; LABEL1: lpad 1
+ %buf = alloca [1 x i32], align 4
+ %call = call i32 @setjmp(ptr %buf)
+ ret i32 %call
+}
+
+; Regular calls should not be affected.
+define i32 @test_regular_call() {
+; CHECK-LABEL: test_regular_call:
+; CHECK-NOT: .option exact
+; CHECK: call regular_func
+ %buf = alloca [1 x i32], align 4
+ %call = call i32 @regular_func(ptr %buf)
+ ret i32 %call
+}
+
+; Indirect calls with returns_twice attribute also get the same treatment.
+define i32 @test_indirect_returns_twice(ptr %fptr) {
+; CHECK-LABEL: test_indirect_returns_twice:
+; CHECK: .p2align 2
+; CHECK-NEXT: .option push
+; CHECK-NEXT: .option exact
+; CHECK-NEXT: jalr
+; CHECK-NEXT: .option pop
+; CHECK-NEXT: lpad 0
+;
+; Without relax, only .p2align 2 is needed (no .option push/exact).
+; NORELAX-LABEL: test_indirect_returns_twice:
+; NORELAX-NOT: .option push
+; NORELAX-NOT: .option exact
+; NORELAX: .p2align 2
+; NORELAX-NEXT: jalr
+; NORELAX-NEXT: lpad 0
+;
+; Without Zca, no .p2align or .option push/exact needed.
+; NOZCA-LABEL: test_indirect_returns_twice:
+; NOZCA-NOT: .p2align 2
+; NOZCA-NOT: .option push
+; NOZCA-NOT: .option exact
+; NOZCA: jalr
+; NOZCA-NEXT: lpad 0
+;
+; With -riscv-landing-pad-label=1, lpad uses label 1.
+; LABEL1-LABEL: test_indirect_returns_twice:
+; LABEL1: jalr
+; LABEL1: lpad 1
+ %buf = alloca [1 x i32], align 4
+ %call = call i32 %fptr(ptr %buf) #0
+ ret i32 %call
+}
+
+attributes #0 = { returns_twice }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 8, !"cf-protection-branch", i32 1}
diff --git a/llvm/test/CodeGen/RISCV/lpad.ll b/llvm/test/CodeGen/RISCV/lpad.ll
index 28873ab6c49a4..6378f90980eca 100644
--- a/llvm/test/CodeGen/RISCV/lpad.ll
+++ b/llvm/test/CodeGen/RISCV/lpad.ll
@@ -292,6 +292,9 @@ define void @interrupt() "interrupt"="machine" {
declare i32 @setjmp(ptr) returns_twice
+; Note: This test doesn't have +c or +relax, so no .p2align 2 or .option exact
+; is emitted before the setjmp call. See lpad-setjmp.ll for tests with those
+; attributes enabled.
define i32 @test_returns_twice() {
; RV32-LABEL: test_returns_twice:
; RV32: # %bb.0:
@@ -360,3 +363,6 @@ define i32 @test_returns_twice() {
%call = call i32 @setjmp(ptr %buf)
ret i32 %call
}
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 8, !"cf-protection-branch", i32 1}
More information about the llvm-commits
mailing list