Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0166f89
[RISCV] Fix musttail with indirect arguments by forwarding incoming p…
xroche Mar 6, 2026
d94c5a0
[RISCV] Address review: use DenseMap for indirect args, add tests
xroche Mar 7, 2026
ecb7952
[RISCV] Fix musttail indirect arg forwarding when arguments are reord…
xroche Mar 7, 2026
568648b
[RISCV] Handle computed values in musttail indirect arg forwarding
xroche Mar 9, 2026
484f135
[RISCV][NFC] Apply clang-format to musttail indirect arg forwarding
xroche Mar 9, 2026
0caa871
Merge remote-tracking branch 'origin/main' into riscv-musttail-indire…
xroche Apr 19, 2026
76df154
[RISCV] Fix SDValue lifetime for musttail indirect arg forwarding
xroche Apr 19, 2026
782cd6b
[RISCV] Reject byval in musttail to avoid silent miscompile
xroche Apr 21, 2026
3c3433f
[RISCV] Address musttail review: thread CopyFromReg chain, use getUnk…
xroche Apr 21, 2026
b2a04bb
[RISCV] Add musttail tests for stack-spilled, sret, and mixed indirec…
xroche Apr 21, 2026
960401f
[RISCV] Sequence musttail computed-arg stores after CopyFromReg
xroche Apr 28, 2026
d9c9be7
[RISCV][NFC] Regenerate CHECK lines for musttail computed-args test
xroche Apr 28, 2026
f888fe4
[RISCV] Handle scalable vectors in musttail computed-arg parts loop
xroche May 6, 2026
718f9cf
[RISCV][NFC] Clarify why musttail computed-arg stores chain off CopyF…
xroche May 6, 2026
63260ea
[RISCV][NFC] Keep isEligibleForTailCallOptimization on one line in te…
xroche May 6, 2026
b88aa6f
[RISCV] Add musttail tests for scalable vector indirect args
xroche May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 76 additions & 42 deletions llvm/lib/Target/RISCV/RISCVISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24454,6 +24454,8 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
ArgValue = unpackFromMemLoc(DAG, Chain, VA, DL, *this);

if (VA.getLocInfo() == CCValAssign::Indirect) {
// Save the incoming indirect pointer for musttail forwarding.
RVFI->addIncomingIndirectArg(ArgValue);
// If the original argument was split and passed by reference (e.g. i128
// on RV32), we need to load all parts of it here (using the same
// address). Vectors may be partly split to registers and partly to the
Expand Down Expand Up @@ -24578,6 +24580,19 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
if (CCInfo.getStackSize() > RVFI->getArgumentStackSize())
return false;

// Do not tail call optimize if any argument needs to be passed indirectly.
// The caller allocates stack space and passes a pointer to the callee. On a
// tail call the caller's stack frame is deallocated before the callee
// executes, invalidating the pointer (use-after-free).
// musttail is excluded: callers forward incoming indirect pointers that
// point to the caller's caller's frame, which remains valid.
if (!CLI.CB || !CLI.CB->isMustTailCall()) {
for (const auto &VA : ArgLocs) {
if (VA.getLocInfo() == CCValAssign::Indirect)
return false;
}
}

Comment thread
xroche marked this conversation as resolved.
Outdated
// Do not tail call opt if either caller or callee uses struct return
// semantics.
auto IsCallerStructRet = Caller.hasStructRetAttr();
Expand Down Expand Up @@ -24765,51 +24780,70 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
// Promote the value if needed.
// For now, only handle fully promoted and indirect arguments.
if (VA.getLocInfo() == CCValAssign::Indirect) {
// Store the argument in a stack slot and pass its address.
Align StackAlign =
std::max(getPrefTypeAlign(Outs[OutIdx].ArgVT, DAG),
getPrefTypeAlign(ArgValue.getValueType(), DAG));
TypeSize StoredSize = ArgValue.getValueType().getStoreSize();
// If the original argument was split (e.g. i128), we need
// to store the required parts of it here (and pass just one address).
// Vectors may be partly split to registers and partly to the stack, in
// which case the base address is partly offset and subsequent stores are
// relative to that.
unsigned ArgIndex = Outs[OutIdx].OrigArgIndex;
unsigned ArgPartOffset = Outs[OutIdx].PartOffset;
assert(VA.getValVT().isVector() || ArgPartOffset == 0);
// Calculate the total size to store. We don't have access to what we're
// actually storing other than performing the loop and collecting the
// info.
SmallVector<std::pair<SDValue, SDValue>> Parts;
while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == ArgIndex) {
SDValue PartValue = OutVals[OutIdx + 1];
unsigned PartOffset = Outs[OutIdx + 1].PartOffset - ArgPartOffset;
SDValue Offset = DAG.getIntPtrConstant(PartOffset, DL);
EVT PartVT = PartValue.getValueType();
if (PartVT.isScalableVector())
Offset = DAG.getNode(ISD::VSCALE, DL, XLenVT, Offset);
StoredSize += PartVT.getStoreSize();
StackAlign = std::max(StackAlign, getPrefTypeAlign(PartVT, DAG));
Parts.push_back(std::make_pair(PartValue, Offset));
++i;
++OutIdx;
}
SDValue SpillSlot = DAG.CreateStackTemporary(StoredSize, StackAlign);
int FI = cast<FrameIndexSDNode>(SpillSlot)->getIndex();
MemOpChains.push_back(
DAG.getStore(Chain, DL, ArgValue, SpillSlot,
MachinePointerInfo::getFixedStack(MF, FI)));
for (const auto &Part : Parts) {
SDValue PartValue = Part.first;
SDValue PartOffset = Part.second;
SDValue Address =
DAG.getNode(ISD::ADD, DL, PtrVT, SpillSlot, PartOffset);
// For musttail calls, forward the incoming indirect pointer instead
// of creating a new stack temporary. The incoming pointer points to
// the caller's caller's frame, which remains valid after a tail call.
if (IsTailCall && CLI.CB && CLI.CB->isMustTailCall()) {
unsigned IndirectIdx = 0;
for (unsigned k = 0; k < OutIdx; ++k) {
if (ArgLocs[k].getLocInfo() == CCValAssign::Indirect)
++IndirectIdx;
}
Comment thread
xroche marked this conversation as resolved.
Outdated
ArgValue = RVFI->getIncomingIndirectArg(IndirectIdx);
// Skip any split parts of this argument (they are covered by the
// forwarded pointer).
unsigned ArgIndex = Outs[OutIdx].OrigArgIndex;
while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == ArgIndex) {
++i;
++OutIdx;
}
} else {
// Store the argument in a stack slot and pass its address.
Align StackAlign =
std::max(getPrefTypeAlign(Outs[OutIdx].ArgVT, DAG),
getPrefTypeAlign(ArgValue.getValueType(), DAG));
TypeSize StoredSize = ArgValue.getValueType().getStoreSize();
// If the original argument was split (e.g. i128), we need
// to store the required parts of it here (and pass just one address).
// Vectors may be partly split to registers and partly to the stack, in
// which case the base address is partly offset and subsequent stores
// are relative to that.
unsigned ArgIndex = Outs[OutIdx].OrigArgIndex;
unsigned ArgPartOffset = Outs[OutIdx].PartOffset;
assert(VA.getValVT().isVector() || ArgPartOffset == 0);
// Calculate the total size to store. We don't have access to what
// we're actually storing other than performing the loop and collecting
// the info.
SmallVector<std::pair<SDValue, SDValue>> Parts;
while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == ArgIndex) {
SDValue PartValue = OutVals[OutIdx + 1];
unsigned PartOffset = Outs[OutIdx + 1].PartOffset - ArgPartOffset;
SDValue Offset = DAG.getIntPtrConstant(PartOffset, DL);
EVT PartVT = PartValue.getValueType();
if (PartVT.isScalableVector())
Offset = DAG.getNode(ISD::VSCALE, DL, XLenVT, Offset);
StoredSize += PartVT.getStoreSize();
StackAlign = std::max(StackAlign, getPrefTypeAlign(PartVT, DAG));
Parts.push_back(std::make_pair(PartValue, Offset));
++i;
++OutIdx;
}
SDValue SpillSlot = DAG.CreateStackTemporary(StoredSize, StackAlign);
int FI = cast<FrameIndexSDNode>(SpillSlot)->getIndex();
MemOpChains.push_back(
DAG.getStore(Chain, DL, PartValue, Address,
DAG.getStore(Chain, DL, ArgValue, SpillSlot,
MachinePointerInfo::getFixedStack(MF, FI)));
for (const auto &Part : Parts) {
SDValue PartValue = Part.first;
SDValue PartOffset = Part.second;
SDValue Address =
DAG.getNode(ISD::ADD, DL, PtrVT, SpillSlot, PartOffset);
MemOpChains.push_back(
DAG.getStore(Chain, DL, PartValue, Address,
MachinePointerInfo::getFixedStack(MF, FI)));
}
ArgValue = SpillSlot;
}
ArgValue = SpillSlot;
} else {
ArgValue = convertValVTToLocVT(DAG, ArgValue, VA, DL, Subtarget);
}
Expand Down
13 changes: 13 additions & 0 deletions llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
/// Incoming ByVal arguments
SmallVector<SDValue, 8> IncomingByValArgs;

/// Incoming indirect argument pointers (for musttail forwarding)
SmallVector<SDValue, 4> IncomingIndirectArgs;

/// Is there any vector argument or return?
bool IsVectorCall = false;

Expand Down Expand Up @@ -157,6 +160,16 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
SDValue getIncomingByValArgs(unsigned Idx) { return IncomingByValArgs[Idx]; }
unsigned getIncomingByValArgsSize() const { return IncomingByValArgs.size(); }

void addIncomingIndirectArg(SDValue Val) {
IncomingIndirectArgs.push_back(Val);
}
SDValue getIncomingIndirectArg(unsigned Idx) {
return IncomingIndirectArgs[Idx];
}
unsigned getIncomingIndirectArgsSize() const {
return IncomingIndirectArgs.size();
}

enum class PushPopKind { None = 0, StdExtZcmp, VendorXqccmp };

PushPopKind getPushPopKind(const MachineFunction &MF) const;
Expand Down
66 changes: 66 additions & 0 deletions llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
Comment thread
xroche marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: llc -mtriple=riscv32 %s -o - | FileCheck %s --check-prefix=RV32
; RUN: llc -mtriple=riscv64 %s -o - | FileCheck %s --check-prefix=RV64

; Test that musttail with indirect args (fp128 on RV32) forwards the incoming
; pointer instead of creating a new stack temporary. Without this fix, the
; pointer would dangle after the tail call deallocates the caller's frame.

declare i32 @callee_musttail_indirect(fp128 %a)

; fp128 is indirect on RV32 (too large for registers), direct on RV64.
; On RV32, musttail must forward the incoming indirect pointer (a0) directly.
define i32 @caller_musttail_indirect(fp128 %a) nounwind {
; RV32-LABEL: caller_musttail_indirect:
; RV32: # %bb.0:
; RV32-NEXT: tail callee_musttail_indirect
;
; RV64-LABEL: caller_musttail_indirect:
; RV64: # %bb.0:
; RV64-NEXT: tail callee_musttail_indirect
%call = musttail call i32 @callee_musttail_indirect(fp128 %a)
ret i32 %call
}

; Verify that non-musttail tail call with indirect args does NOT tail call
; (this is the PR #184972 fix - indirect args are unsafe for regular tail calls).
define void @caller_no_musttail_indirect() nounwind {
; RV32-LABEL: caller_no_musttail_indirect:
; RV32: # %bb.0:
; RV32-NEXT: addi sp, sp, -32
; RV32-NEXT: sw ra, 28(sp) # 4-byte Folded Spill
; RV32-NEXT: lui a1, 262128
; RV32-NEXT: mv a0, sp
; RV32-NEXT: sw zero, 0(sp)
; RV32-NEXT: sw zero, 4(sp)
; RV32-NEXT: sw zero, 8(sp)
; RV32-NEXT: sw a1, 12(sp)
; RV32-NEXT: call callee_musttail_indirect
; RV32-NEXT: lw ra, 28(sp) # 4-byte Folded Reload
; RV32-NEXT: addi sp, sp, 32
; RV32-NEXT: ret
;
; RV64-LABEL: caller_no_musttail_indirect:
; RV64: # %bb.0:
; RV64-NEXT: lui a1, 16383
; RV64-NEXT: slli a1, a1, 36
; RV64-NEXT: li a0, 0
; RV64-NEXT: tail callee_musttail_indirect
%call = tail call i32 @callee_musttail_indirect(fp128 0xL00000000000000003FFF000000000000)
ret void
}

; Test musttail with i128 on RV32 (indirect, split into 4 x i32 parts).
declare i64 @callee_musttail_i128(i128 %a)

define i64 @caller_musttail_i128(i128 %a) nounwind {
; RV32-LABEL: caller_musttail_i128:
; RV32: # %bb.0:
; RV32-NEXT: tail callee_musttail_i128
;
; RV64-LABEL: caller_musttail_i128:
; RV64: # %bb.0:
; RV64-NEXT: tail callee_musttail_i128
%call = musttail call i64 @callee_musttail_i128(i128 %a)
ret i64 %call
}
18 changes: 12 additions & 6 deletions llvm/test/CodeGen/RISCV/tail-calls.ll
Original file line number Diff line number Diff line change
Expand Up @@ -247,20 +247,24 @@ declare i32 @callee_indirect_args(fp128 %a)
define void @caller_indirect_args() nounwind {
; CHECK-LABEL: caller_indirect_args:
; CHECK: # %bb.0: # %entry
; CHECK-NEXT: addi sp, sp, -16
; CHECK-NEXT: addi sp, sp, -32
; CHECK-NEXT: sw ra, 28(sp) # 4-byte Folded Spill
; CHECK-NEXT: lui a1, 262128
; CHECK-NEXT: mv a0, sp
; CHECK-NEXT: sw zero, 0(sp)
; CHECK-NEXT: sw zero, 4(sp)
; CHECK-NEXT: sw zero, 8(sp)
; CHECK-NEXT: sw a1, 12(sp)
; CHECK-NEXT: addi sp, sp, 16
; CHECK-NEXT: tail callee_indirect_args
; CHECK-NEXT: call callee_indirect_args
; CHECK-NEXT: lw ra, 28(sp) # 4-byte Folded Reload
; CHECK-NEXT: addi sp, sp, 32
; CHECK-NEXT: ret
;
; CHECK-LARGE-ZICFILP-LABEL: caller_indirect_args:
; CHECK-LARGE-ZICFILP: # %bb.0: # %entry
; CHECK-LARGE-ZICFILP-NEXT: lpad 0
; CHECK-LARGE-ZICFILP-NEXT: addi sp, sp, -16
; CHECK-LARGE-ZICFILP-NEXT: addi sp, sp, -32
; CHECK-LARGE-ZICFILP-NEXT: sw ra, 28(sp) # 4-byte Folded Spill
; CHECK-LARGE-ZICFILP-NEXT: lui a1, 262128
; CHECK-LARGE-ZICFILP-NEXT: .Lpcrel_hi9:
; CHECK-LARGE-ZICFILP-NEXT: auipc a0, %pcrel_hi(.LCPI7_0)
Expand All @@ -270,8 +274,10 @@ define void @caller_indirect_args() nounwind {
; CHECK-LARGE-ZICFILP-NEXT: sw zero, 4(sp)
; CHECK-LARGE-ZICFILP-NEXT: sw zero, 8(sp)
; CHECK-LARGE-ZICFILP-NEXT: sw a1, 12(sp)
; CHECK-LARGE-ZICFILP-NEXT: addi sp, sp, 16
; CHECK-LARGE-ZICFILP-NEXT: jr t2
; CHECK-LARGE-ZICFILP-NEXT: jalr t2
; CHECK-LARGE-ZICFILP-NEXT: lw ra, 28(sp) # 4-byte Folded Reload
; CHECK-LARGE-ZICFILP-NEXT: addi sp, sp, 32
; CHECK-LARGE-ZICFILP-NEXT: ret
entry:
%call = tail call i32 @callee_indirect_args(fp128 0xL00000000000000003FFF000000000000)
ret void
Expand Down