[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
Sun Mar 1 23:57:40 PST 2026


https://github.com/jerryzj updated https://github.com/llvm/llvm-project/pull/177515

>From 96b07cf27d7daeea062f62fcec34d9b00a03f540 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 |  54 +++++++++
 llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp     |  55 +++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   |  27 +++++
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  19 ++-
 .../RISCV/RISCVIndirectBranchTracking.cpp     |  23 ----
 llvm/lib/Target/RISCV/RISCVInstrInfo.td       |  28 +++++
 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        | 112 ++++++++++++++++++
 llvm/test/CodeGen/RISCV/lpad.ll               |   6 +
 10 files changed, 383 insertions(+), 24 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..b70b11fbb2c7c 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,48 @@ void RISCVMCCodeEmitter::expandFunctionCall(const MCInst &MI,
   support::endian::write(CB, Binary, llvm::endianness::little);
 }
 
+// Expand to AUIPC+JALR+LPAD (direct) or JALR+LPAD (indirect).
+// Unlike expandFunctionCall, R_RISCV_RELAX is not added because linker
+// relaxation would break LPAD alignment.
+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");
+
+    TmpInst =
+        MCInstBuilder(RISCV::AUIPC).addReg(RISCV::X1).addExpr(Func.getExpr());
+    Binary = getBinaryCodeForInstr(TmpInst, Fixups, STI);
+    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 +482,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 9740123d3859e..07ab70b02daef 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);
@@ -273,6 +275,55 @@ bool RISCVAsmPrinter::EmitToStreamer(MCStreamer &S, const MCInst &Inst,
 // instructions) auto-generated.
 #include "RISCVGenMCPseudoLowering.inc"
 
+// Emit [.p2align 2] [.option push] [.option exact] call/jalr [.option pop]
+// lpad. Alignment and .option exact are needed when Zca is enabled to prevent
+// LPAD misalignment from c.jal/cm.jalt relaxation.
+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);
+
+  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);
+}
+
 // If the target supports Zihintntl and the instruction has a nontemporal
 // MachineMemOperand, emit an NTLH hint instruction before it.
 void RISCVAsmPrinter::emitNTLHint(const MachineInstr *MI) {
@@ -350,6 +401,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 38e39250f0b12..36efecb6b46e4 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:
     unsigned Locality = Node->getConstantOperandVal(3);
     if (Locality > 2)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index a8542be937a87..315b14ec675d4 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24876,6 +24876,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 =
@@ -24888,7 +24895,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 ba5a468916125..7ec161404b845 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,26 @@ 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..0ae1f09b2e16f
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/lpad-setjmp.ll
@@ -0,0 +1,112 @@
+; 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 < %s -o %t.o
+; RUN: llvm-readobj --relocations %t.o | 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