[llvm] [RISCV] Preserve call-preserved reg mask for LPAD-aligned calls (PR #210868)

Jerry Zhang Jian via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 20 20:15:21 PDT 2026


https://github.com/jerryzj created https://github.com/llvm/llvm-project/pull/210868

RISCVISelDAGToDAG's lowering of RISCVISD::LPAD_CALL / LPAD_CALL_INDIRECT to PseudoCALLLpadAlign / PseudoCALLIndirectLpadAlign (introduced in #177515) only copied the callee, lpad label, chain, and glue operands, dropping the argument-register and register-mask operands in between.

Without the register-mask operand, the register allocator treats these calls as clobbering nothing but ra, so values live across the call are not spilled/reloaded even though the callee is free to clobber caller-saved registers. This caused a miscompile where a pointer held live across a call to getcontext() (a returns_twice function) was corrupted after the call returned, leading to a SIGSEGV in llvm-test-suite's siod test.

Fix the operand copy to include the argument-register and register-mask operands, matching the pseudo-instruction operands of a regular PseudoCALL/PseudoCALLIndirect.

>From c96e6499ab454f4944e3d60d61b509884cbf90f2 Mon Sep 17 00:00:00 2001
From: Jerry Zhang Jian <jerry.zhangjian at sifive.com>
Date: Tue, 21 Jul 2026 11:09:14 +0800
Subject: [PATCH] [RISCV] Preserve call-preserved reg mask for LPAD-aligned
 calls

RISCVISelDAGToDAG's lowering of RISCVISD::LPAD_CALL /
LPAD_CALL_INDIRECT to PseudoCALLLpadAlign / PseudoCALLIndirectLpadAlign
(introduced in #177515) only copied the callee, lpad label,
chain, and glue operands, dropping the argument-register and
register-mask operands in between.

Without the register-mask operand, the register allocator treats
these calls as clobbering nothing but ra, so values live across the
call are not spilled/reloaded even though the callee is free to
clobber caller-saved registers. This caused a miscompile where a
pointer held live across a call to getcontext() (a returns_twice
function) was corrupted after the call returned, leading to a SIGSEGV
in llvm-test-suite's siod test.

Fix the operand copy to include the argument-register and
register-mask operands, matching the pseudo-instruction operands of a
regular PseudoCALL/PseudoCALLIndirect.

Signed-off-by: Jerry Zhang Jian <jerry.zhangjian at sifive.com>
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   | 16 ++++++++--
 .../test/CodeGen/RISCV/lpad-setjmp-regmask.ll | 31 +++++++++++++++++++
 2 files changed, 44 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/CodeGen/RISCV/lpad-setjmp-regmask.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 8d9c2d519274c..36576a7279915 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -3236,12 +3236,22 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
       LpadLabel = PreferredLandingPadLabel;
     }
 
-    SmallVector<SDValue, 4> Ops;
+    // Preserve the argument-register and register-mask operands (operands
+    // 2..N-2, between Callee and the optional glue) so the pseudo call
+    // still reports its call-preserved mask to the register allocator.
+    SmallVector<SDValue, 8> Ops;
     Ops.push_back(Node->getOperand(1));
     Ops.push_back(CurDAG->getTargetConstant(LpadLabel, DL, XLenVT));
+
+    unsigned NumOps = Node->getNumOperands();
+    bool HasGlue = Node->getGluedNode() != nullptr;
+    unsigned LastRegOperand = HasGlue ? NumOps - 1 : NumOps;
+    for (unsigned I = 2; I != LastRegOperand; ++I)
+      Ops.push_back(Node->getOperand(I));
+
     Ops.push_back(Node->getOperand(0));
-    if (Node->getGluedNode())
-      Ops.push_back(Node->getOperand(Node->getNumOperands() - 1));
+    if (HasGlue)
+      Ops.push_back(Node->getOperand(NumOps - 1));
 
     ReplaceNode(Node,
                 CurDAG->getMachineNode(PseudoOpc, DL, Node->getVTList(), Ops));
diff --git a/llvm/test/CodeGen/RISCV/lpad-setjmp-regmask.ll b/llvm/test/CodeGen/RISCV/lpad-setjmp-regmask.ll
new file mode 100644
index 0000000000000..62ccf2d9ed6f5
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/lpad-setjmp-regmask.ll
@@ -0,0 +1,31 @@
+; RUN: llc -mtriple=riscv32 -stop-after=finalize-isel < %s | FileCheck %s
+; RUN: llc -mtriple=riscv64 -stop-after=finalize-isel < %s | FileCheck %s
+
+; PseudoCALLLpadAlign/PseudoCALLIndirectLpadAlign must carry the same
+; call-preserved register mask as PseudoCALL/PseudoCALLIndirect, otherwise
+; values live across a returns_twice call are not spilled/reloaded and get
+; corrupted when the callee clobbers them.
+
+declare i32 @setjmp(ptr) returns_twice
+declare void @use(ptr, ptr)
+
+define void @test_direct_call_preserved_mask(ptr %buf) {
+  ; CHECK-LABEL: name: test_direct_call_preserved_mask
+  ; CHECK: PseudoCALLLpadAlign target-flags(riscv-call) @setjmp, 0, csr_ilp32{{d?}}_lp64{{d?}}, implicit-def dead $x1, implicit $x10, implicit-def $x2, implicit-def $x10
+  %call = call i32 @setjmp(ptr %buf)
+  call void @use(ptr %buf, ptr %buf)
+  ret void
+}
+
+define void @test_indirect_call_preserved_mask(ptr %fptr, ptr %buf) {
+  ; CHECK-LABEL: name: test_indirect_call_preserved_mask
+  ; CHECK: PseudoCALLIndirectLpadAlign %{{[0-9]+}}, 0, csr_ilp32{{d?}}_lp64{{d?}}, implicit-def dead $x1, implicit $x10, implicit-def $x2, implicit-def $x10
+  %call = call i32 %fptr(ptr %buf) #0
+  call void @use(ptr %buf, ptr %buf)
+  ret void
+}
+
+attributes #0 = { returns_twice }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 8, !"cf-protection-branch", i32 1}



More information about the llvm-commits mailing list