Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
130 changes: 88 additions & 42 deletions llvm/lib/Target/RISCV/RISCVISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24462,6 +24462,8 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
InVals.push_back(DAG.getLoad(VA.getValVT(), DL, Chain, ArgValue,
MachinePointerInfo()));
unsigned ArgIndex = Ins[InsIdx].OrigArgIndex;
// Save the incoming indirect pointer for musttail forwarding.
RVFI->setIncomingIndirectArg(ArgIndex, ArgValue);
Comment thread
xroche marked this conversation as resolved.
Outdated
unsigned ArgPartOffset = Ins[InsIdx].PartOffset;
assert(VA.getValVT().isVector() || ArgPartOffset == 0);
while (i + 1 != e && Ins[InsIdx + 1].OrigArgIndex == ArgIndex) {
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,82 @@ 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()) {
// Outs[OutIdx].OrigArgIndex is the position in the call's argument
// list (callee perspective), but the incoming indirect arg map is
// keyed by the caller's formal parameter index. When musttail
// reorders arguments, these differ. Resolve via the IR: find which
// formal parameter is being passed at this call position.
unsigned CallArgIdx = Outs[OutIdx].OrigArgIndex;
unsigned FormalIdx = CallArgIdx; // default if lookup fails
unsigned Idx = 0;
for (const auto &CallArg : CLI.CB->args()) {
if (CallArg->getType()->isEmptyTy())
continue;
if (Idx == CallArgIdx) {
if (const auto *FormalArg = dyn_cast<Argument>(CallArg))
FormalIdx = FormalArg->getArgNo();
break;
}
++Idx;
}
ArgValue = RVFI->getIncomingIndirectArg(FormalIdx);
// Skip any split parts of this argument (they are covered by the
// forwarded pointer).
while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == CallArgIdx) {
++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
14 changes: 14 additions & 0 deletions llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define LLVM_LIB_TARGET_RISCV_RISCVMACHINEFUNCTIONINFO_H

#include "RISCVSubtarget.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/CodeGen/MIRYamlMapping.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
Expand Down Expand Up @@ -73,6 +74,10 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
/// Incoming ByVal arguments
SmallVector<SDValue, 8> IncomingByValArgs;

/// Incoming indirect argument pointers, keyed by OrigArgIndex.
/// Used for musttail forwarding of indirect args.
DenseMap<unsigned, SDValue> IncomingIndirectArgs;

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

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

void setIncomingIndirectArg(unsigned ArgIndex, SDValue Val) {
IncomingIndirectArgs[ArgIndex] = Val;
}
SDValue getIncomingIndirectArg(unsigned ArgIndex) const {
auto It = IncomingIndirectArgs.find(ArgIndex);
assert(It != IncomingIndirectArgs.end() && "No incoming indirect arg");
return It->second;
}

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

PushPopKind getPushPopKind(const MachineFunction &MF) const;
Expand Down
Loading