[llvm] [RISCV] Ensure LPAD alignment for calls to returns_twice functions (PR #177515)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 22 18:02:35 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-risc-v
Author: Jerry Zhang Jian (jerryzj)
<details>
<summary>Changes</summary>
When Zicfilp is enabled, the LPAD instruction following a call must be 4-byte aligned. 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.
2. RISCVAsmPrinter handles these pseudos: when Zca is enabled, emit .p2align 2 before the call to ensure the following LPAD is 4-byte aligned. When linker relaxation is also enabled, wrap it with .option exact to prevent the call from being relaxed to c.jal/cm.jalt. When Zca is not enabled, only emit the call instruction (no alignment workaround needed since no compressed calls exist).
3. RISCVIndirectBranchTracking inserts LPAD after these pseudo instructions.
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/177515.diff
7 Files Affected:
- (modified) llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp (+52)
- (modified) llvm/lib/Target/RISCV/RISCVISelLowering.cpp (+16-1)
- (modified) llvm/lib/Target/RISCV/RISCVIndirectBranchTracking.cpp (+5-14)
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.td (+30)
- (added) llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll (+24)
- (added) llvm/test/CodeGen/RISCV/lpad-setjmp.ll (+97)
- (modified) llvm/test/CodeGen/RISCV/lpad.ll (+3)
``````````diff
diff --git a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
index 3baa5d06b0bf1..5edd9ea2e4e98 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, bool IsIndirect);
+
// XRay Support
void LowerPATCHABLE_FUNCTION_ENTER(const MachineInstr *MI);
void LowerPATCHABLE_FUNCTION_EXIT(const MachineInstr *MI);
@@ -273,6 +275,50 @@ bool RISCVAsmPrinter::EmitToStreamer(MCStreamer &S, const MCInst &Inst,
// instructions) auto-generated.
#include "RISCVGenMCPseudoLowering.inc"
+// Emit a call to a returns_twice function with LPAD alignment handling.
+// When Zca is enabled, emit .p2align 2 (and .option exact if relaxation
+// is enabled) to prevent LPAD misalignment from c.jal/cm.jalt relaxation.
+void RISCVAsmPrinter::emitLpadAlignedCall(const MachineInstr &MI,
+ bool IsIndirect) {
+ const MCSubtargetInfo &MCSTI = getSubtargetInfo();
+ bool HasZca = MCSTI.hasFeature(RISCV::FeatureStdExtZca);
+ bool HasRelax = MCSTI.hasFeature(RISCV::FeatureRelax);
+
+ if (HasZca)
+ OutStreamer->emitCodeAlignment(Align(4), &MCSTI);
+
+ auto *RTS =
+ static_cast<RISCVTargetStreamer *>(OutStreamer->getTargetStreamer());
+ if (HasZca && HasRelax)
+ RTS->emitDirectiveOptionExact();
+
+ // Emit the call instruction.
+ MCInst CallInst;
+ if (!IsIndirect) {
+ // Direct call
+ CallInst.setOpcode(RISCV::PseudoCALL);
+ const MachineOperand &CallTarget = MI.getOperand(0);
+ MCOperand MCOp;
+ lowerOperand(CallTarget, MCOp);
+ CallInst.addOperand(MCOp);
+ } else {
+ // Indirect call: JALR X1, Rs, 0
+ 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->emitDirectiveOptionNoExact();
+ } else {
+ EmitToStreamer(*OutStreamer, CallInst, 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 +396,12 @@ void RISCVAsmPrinter::emitInstruction(const MachineInstr *MI) {
case TargetOpcode::PATCHABLE_TAIL_CALL:
LowerPATCHABLE_TAIL_CALL(MI);
return;
+ case RISCV::PseudoCALLLpadAlign:
+ emitLpadAlignedCall(*MI, /*IsIndirect=*/false);
+ return;
+ case RISCV::PseudoCALLIndirectLpadAlign:
+ emitLpadAlignedCall(*MI, /*IsIndirect=*/true);
+ return;
}
MCInst OutInst;
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 5427bcfe8eef8..a3b26977d140c 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24336,6 +24336,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 =
@@ -24348,7 +24355,15 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
return Ret;
}
- unsigned CallOpc = NeedSWGuarded ? RISCVISD::SW_GUARDED_CALL : RISCVISD::CALL;
+ unsigned CallOpc;
+ 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..c569e78eee6b7 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,12 +103,13 @@ bool RISCVIndirectBranchTracking::runOnMachineFunction(MachineFunction &MF) {
}
}
- // Check for calls to functions with ReturnsTwice attribute and insert
- // LPAD after such calls
+ // Insert LPAD after calls to functions with ReturnsTwice attribute.
+ // ISel marks these calls with PseudoCALLLpadAlign or PseudoCALLIndirectLpadAlign.
for (MachineBasicBlock &MBB : MF) {
for (MachineBasicBlock::iterator I = MBB.begin(); I != MBB.end(); ++I) {
- if (I->isCall() && I->getNumOperands() > 0 &&
- isCallReturnTwice(I->getOperand(0))) {
+ unsigned Opc = I->getOpcode();
+ if (Opc == RISCV::PseudoCALLLpadAlign ||
+ Opc == RISCV::PseudoCALLIndirectLpadAlign) {
auto NextI = std::next(I);
emitLpad(MBB, TII, FixedLabel, NextI);
Changed = true;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index c7be5b07c33cc..f33896cb450a4 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,
@@ -1814,6 +1822,28 @@ 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. It emits .p2align 2 + .option exact
+// before the call to ensure the following LPAD is 4-byte aligned.
+// Size = 10: worst-case 2-byte nop for alignment + 8-byte call (auipc+jalr).
+let isCall = 1, Defs = [X1], isCodeGenOnly = 1, Size = 10 in
+def PseudoCALLLpadAlign : Pseudo<(outs), (ins call_symbol:$func),
+ [(riscv_lpad_call tglobaladdr:$func)]>,
+ Sched<[WriteIALU, WriteJalr, ReadJalr]>;
+
+def : Pat<(riscv_lpad_call texternalsym:$func),
+ (PseudoCALLLpadAlign texternalsym:$func)>;
+
+// PseudoCALLIndirectLpadAlign is the indirect call version.
+// Size = 6: worst-case 2-byte nop for alignment + 4-byte jalr.
+// Note: Do not use PseudoInstExpansion here as it bypasses emitInstruction
+// where we handle .p2align and .option exact.
+let isCall = 1, Defs = [X1], isCodeGenOnly = 1, Size = 6 in
+def PseudoCALLIndirectLpadAlign : Pseudo<(outs), (ins GPRJALRNonX7:$rs1),
+ [(riscv_lpad_call_indirect
+ GPRJALRNonX7:$rs1)]>,
+ 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-no-cfp.ll b/llvm/test/CodeGen/RISCV/lpad-setjmp-no-cfp.ll
new file mode 100644
index 0000000000000..2dfe9b50adaa3
--- /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 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..71feaf5c7cd30
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/lpad-setjmp.ll
@@ -0,0 +1,97 @@
+; 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
+
+; 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
+; RUN: llvm-readobj --relocations %t.o | FileCheck %s --check-prefix=RELOC-REGULAR
+; setjmp call: R_RISCV_CALL_PLT without R_RISCV_RELAX
+; RELOC-SETJMP: R_RISCV_CALL_PLT setjmp
+; RELOC-SETJMP-NOT: R_RISCV_RELAX
+; RELOC-SETJMP: R_RISCV_CALL_PLT regular_func
+; regular_func call: R_RISCV_CALL_PLT with R_RISCV_RELAX
+; RELOC-REGULAR: R_RISCV_CALL_PLT regular_func
+; RELOC-REGULAR-NEXT: R_RISCV_RELAX
+
+; Test that returns_twice calls (e.g., setjmp) are wrapped with .option exact
+; 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 exact
+; CHECK-NEXT: call setjmp
+; CHECK-NEXT: .option noexact
+; CHECK-NEXT: lpad 0
+;
+; Without relax, only .p2align 2 is needed.
+; NORELAX-LABEL: test_setjmp:
+; NORELAX-NOT: .option exact
+; NORELAX: .p2align 2
+; NORELAX-NEXT: call setjmp
+; NORELAX-NEXT: lpad 0
+;
+; Without Zca, no .p2align or .option exact needed (no compressed calls).
+; NOZCA-LABEL: test_setjmp:
+; NOZCA-NOT: .p2align 2
+; NOZCA-NOT: .option exact
+; NOZCA: call setjmp
+; NOZCA-NEXT: lpad 0
+ %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 norelax
+; 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 exact
+; CHECK-NEXT: jalr
+; CHECK-NEXT: .option noexact
+; CHECK-NEXT: lpad 0
+;
+; Without relax, only .p2align 2 is needed (no .option exact).
+; NORELAX-LABEL: test_indirect_returns_twice:
+; NORELAX-NOT: .option exact
+; NORELAX: .p2align 2
+; NORELAX-NEXT: jalr
+; NORELAX-NEXT: lpad 0
+;
+; Without Zca, no .p2align or .option exact needed.
+; NOZCA-LABEL: test_indirect_returns_twice:
+; NOZCA-NOT: .p2align 2
+; NOZCA-NOT: .option exact
+; NOZCA: jalr
+; NOZCA-NEXT: lpad 0
+ %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..71ad708e425a0 100644
--- a/llvm/test/CodeGen/RISCV/lpad.ll
+++ b/llvm/test/CodeGen/RISCV/lpad.ll
@@ -360,3 +360,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}
``````````
</details>
https://github.com/llvm/llvm-project/pull/177515
More information about the llvm-commits
mailing list