[llvm] [RISCV] Fix musttail with indirect arguments by forwarding incoming pointers (PR #185094)

Xavier Roche via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 19 06:17:27 PDT 2026


https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/185094

>From 0166f898e46c6ece930361691af37ed80c4d26bf Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 6 Mar 2026 20:41:00 +0100
Subject: [PATCH 1/7] [RISCV] Fix musttail with indirect arguments by
 forwarding incoming pointers

When a musttail call has arguments passed indirectly
(CCValAssign::Indirect), the current code creates a new stack
temporary and copies the data there. The tail call then deallocates
the caller's stack frame, leaving the pointer dangling.

Fix this by forwarding the original incoming indirect pointer
instead of re-spilling. Since musttail guarantees matching
prototypes, incoming and outgoing indirect args have a 1:1
correspondence, and the incoming pointer (from the caller's caller's
frame) remains valid after the tail call.

This also subsumes the non-musttail indirect args fix: non-musttail
calls with indirect args are rejected from tail call optimization
(the pointer would dangle), while musttail calls forward the
incoming pointer.

Fixes #185089.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 118 +++++++++++-------
 .../Target/RISCV/RISCVMachineFunctionInfo.h   |  13 ++
 .../CodeGen/RISCV/musttail-indirect-args.ll   |  66 ++++++++++
 llvm/test/CodeGen/RISCV/tail-calls.ll         |  18 ++-
 4 files changed, 167 insertions(+), 48 deletions(-)
 create mode 100644 llvm/test/CodeGen/RISCV/musttail-indirect-args.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 17d7db95886ab..68cff3332b873 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -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
@@ -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;
+    }
+  }
+
   // Do not tail call opt if either caller or callee uses struct return
   // semantics.
   auto IsCallerStructRet = Caller.hasStructRetAttr();
@@ -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;
+        }
+        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);
     }
diff --git a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
index e23f162a317ef..65b7226025da5 100644
--- a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
@@ -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;
 
@@ -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;
diff --git a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
new file mode 100644
index 0000000000000..447cacf13e2b8
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
@@ -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
+}
diff --git a/llvm/test/CodeGen/RISCV/tail-calls.ll b/llvm/test/CodeGen/RISCV/tail-calls.ll
index 33feba3c6fba1..79855aa03adcf 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls.ll
@@ -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)
@@ -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

>From d94c5a02c11d56c4f2469bd05ae44083d98f743d Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Sat, 7 Mar 2026 08:29:22 +0100
Subject: [PATCH 2/7] [RISCV] Address review: use DenseMap for indirect args,
 add tests

- Use DenseMap<unsigned, SDValue> keyed by OrigArgIndex instead of
  SmallVector for incoming indirect arg pointers. This avoids a fragile
  O(n) counting loop in LowerCall and directly maps argument indices.
- Add test cases for: two indirect args (DenseMap multi-key), mixed
  direct+indirect, and i128 split+trailing direct arg.
- Add test for non-musttail forwarding of indirect arg (shows it
  correctly falls back to call).

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 13 +---
 .../Target/RISCV/RISCVMachineFunctionInfo.h   | 19 ++---
 .../CodeGen/RISCV/musttail-indirect-args.ll   | 77 +++++++++++++++++++
 3 files changed, 91 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 68cff3332b873..e49628550bc5f 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24454,8 +24454,6 @@ 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
@@ -24464,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);
       unsigned ArgPartOffset = Ins[InsIdx].PartOffset;
       assert(VA.getValVT().isVector() || ArgPartOffset == 0);
       while (i + 1 != e && Ins[InsIdx + 1].OrigArgIndex == ArgIndex) {
@@ -24784,15 +24784,10 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
       // 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;
-        }
-        ArgValue = RVFI->getIncomingIndirectArg(IndirectIdx);
+        unsigned ArgIndex = Outs[OutIdx].OrigArgIndex;
+        ArgValue = RVFI->getIncomingIndirectArg(ArgIndex);
         // 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;
diff --git a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
index 65b7226025da5..9db41bc04094e 100644
--- a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
@@ -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"
@@ -73,8 +74,9 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
   /// Incoming ByVal arguments
   SmallVector<SDValue, 8> IncomingByValArgs;
 
-  /// Incoming indirect argument pointers (for musttail forwarding)
-  SmallVector<SDValue, 4> IncomingIndirectArgs;
+  /// 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;
@@ -160,14 +162,13 @@ 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);
+  void setIncomingIndirectArg(unsigned ArgIndex, SDValue Val) {
+    IncomingIndirectArgs[ArgIndex] = Val;
   }
-  SDValue getIncomingIndirectArg(unsigned Idx) {
-    return IncomingIndirectArgs[Idx];
-  }
-  unsigned getIncomingIndirectArgsSize() const {
-    return IncomingIndirectArgs.size();
+  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 };
diff --git a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
index 447cacf13e2b8..97816c4c41e9d 100644
--- a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
+++ b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
@@ -50,6 +50,67 @@ define void @caller_no_musttail_indirect() nounwind {
   ret void
 }
 
+; Verify that non-musttail tail call forwarding an indirect arg from the
+; caller's own parameters also does NOT tail call (the arg lives on the
+; caller's frame, which would be deallocated).
+define i32 @caller_no_musttail_forward_indirect(fp128 %a) nounwind {
+; RV32-LABEL: caller_no_musttail_forward_indirect:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -32
+; RV32-NEXT:    sw ra, 28(sp) # 4-byte Folded Spill
+; RV32-NEXT:    lw a1, 0(a0)
+; RV32-NEXT:    lw a2, 4(a0)
+; RV32-NEXT:    lw a3, 8(a0)
+; RV32-NEXT:    lw a4, 12(a0)
+; RV32-NEXT:    mv a0, sp
+; RV32-NEXT:    sw a1, 0(sp)
+; RV32-NEXT:    sw a2, 4(sp)
+; RV32-NEXT:    sw a3, 8(sp)
+; RV32-NEXT:    sw a4, 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_forward_indirect:
+; RV64:       # %bb.0:
+; RV64-NEXT:    tail callee_musttail_indirect
+  %call = tail call i32 @callee_musttail_indirect(fp128 %a)
+  ret i32 %call
+}
+
+; Test musttail with two indirect fp128 args on RV32. Both pointers must be
+; forwarded. Exercises the DenseMap with two distinct OrigArgIndex values.
+declare i32 @callee_musttail_two_indirect(fp128 %a, fp128 %b)
+
+define i32 @caller_musttail_two_indirect(fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_two_indirect:
+; RV32:       # %bb.0:
+; RV32-NEXT:    tail callee_musttail_two_indirect
+;
+; RV64-LABEL: caller_musttail_two_indirect:
+; RV64:       # %bb.0:
+; RV64-NEXT:    tail callee_musttail_two_indirect
+  %call = musttail call i32 @callee_musttail_two_indirect(fp128 %a, fp128 %b)
+  ret i32 %call
+}
+
+; Test musttail with mixed direct (i32 in register) + indirect (fp128) args.
+; Confirms OrigArgIndex lookup works when not all args are indirect.
+declare i32 @callee_musttail_mixed(i32 %x, fp128 %a)
+
+define i32 @caller_musttail_mixed(i32 %x, fp128 %a) nounwind {
+; RV32-LABEL: caller_musttail_mixed:
+; RV32:       # %bb.0:
+; RV32-NEXT:    tail callee_musttail_mixed
+;
+; RV64-LABEL: caller_musttail_mixed:
+; RV64:       # %bb.0:
+; RV64-NEXT:    tail callee_musttail_mixed
+  %call = musttail call i32 @callee_musttail_mixed(i32 %x, fp128 %a)
+  ret i32 %call
+}
+
 ; Test musttail with i128 on RV32 (indirect, split into 4 x i32 parts).
 declare i64 @callee_musttail_i128(i128 %a)
 
@@ -64,3 +125,19 @@ define i64 @caller_musttail_i128(i128 %a) nounwind {
   %call = musttail call i64 @callee_musttail_i128(i128 %a)
   ret i64 %call
 }
+
+; Test musttail with i128 (indirect+split on RV32) plus a trailing i32 direct arg.
+; Exercises the split-skip logic followed by a normal register arg.
+declare i64 @callee_musttail_i128_and_i32(i128 %a, i32 %x)
+
+define i64 @caller_musttail_i128_and_i32(i128 %a, i32 %x) nounwind {
+; RV32-LABEL: caller_musttail_i128_and_i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    tail callee_musttail_i128_and_i32
+;
+; RV64-LABEL: caller_musttail_i128_and_i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    tail callee_musttail_i128_and_i32
+  %call = musttail call i64 @callee_musttail_i128_and_i32(i128 %a, i32 %x)
+  ret i64 %call
+}

>From ecb7952f4ef1f2eb475cc6d4f67055b08bc2c3a0 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Sat, 7 Mar 2026 13:02:31 +0100
Subject: [PATCH 3/7] [RISCV] Fix musttail indirect arg forwarding when
 arguments are reordered

Outs[].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 (e.g.,
`musttail call @f(fp128 %b, fp128 %a)`), these indices diverge, causing
the wrong pointers to be forwarded.

Fix by resolving the caller's formal parameter index via the IR: walk
CLI.CB->args() to find the Argument at the matching call position and
use its getArgNo() as the DenseMap lookup key.

Add comprehensive tests for swapped, rotated, duplicated, and
stack-spilled indirect args.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  23 +-
 .../CodeGen/RISCV/musttail-indirect-args.ll   | 209 ++++++++++++++++++
 2 files changed, 229 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index e49628550bc5f..0dafa47f8cc00 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24784,11 +24784,28 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
       // 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 ArgIndex = Outs[OutIdx].OrigArgIndex;
-        ArgValue = RVFI->getIncomingIndirectArg(ArgIndex);
+        // 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 == ArgIndex) {
+        while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == CallArgIdx) {
           ++i;
           ++OutIdx;
         }
diff --git a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
index 97816c4c41e9d..c6d1743a27d26 100644
--- a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
+++ b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
@@ -141,3 +141,212 @@ define i64 @caller_musttail_i128_and_i32(i128 %a, i32 %x) nounwind {
   %call = musttail call i64 @callee_musttail_i128_and_i32(i128 %a, i32 %x)
   ret i64 %call
 }
+
+; Test musttail with two indirect args SWAPPED. The pointers must be exchanged
+; before the tail call. This exercises the OrigArgIndex -> Argument::getArgNo()
+; resolution in LowerCall.
+define i32 @caller_musttail_two_indirect_swapped(fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_two_indirect_swapped:
+; RV32:       # %bb.0:
+; RV32-NEXT:    mv a2, a0
+; RV32-NEXT:    mv a0, a1
+; RV32-NEXT:    mv a1, a2
+; RV32-NEXT:    tail callee_musttail_two_indirect
+;
+; RV64-LABEL: caller_musttail_two_indirect_swapped:
+; RV64:       # %bb.0:
+; RV64-NEXT:    mv a4, a1
+; RV64-NEXT:    mv a5, a0
+; RV64-NEXT:    mv a0, a2
+; RV64-NEXT:    mv a1, a3
+; RV64-NEXT:    mv a2, a5
+; RV64-NEXT:    mv a3, a4
+; RV64-NEXT:    tail callee_musttail_two_indirect
+  %call = musttail call i32 @callee_musttail_two_indirect(fp128 %b, fp128 %a)
+  ret i32 %call
+}
+
+; Test musttail with three indirect args rotated: call @f(%c, %a, %b).
+; All three pointers need to be shuffled.
+declare i32 @callee_musttail_three_indirect(fp128 %a, fp128 %b, fp128 %c)
+
+define i32 @caller_musttail_three_indirect_rotated(fp128 %a, fp128 %b, fp128 %c) nounwind {
+; RV32-LABEL: caller_musttail_three_indirect_rotated:
+; RV32:       # %bb.0:
+; RV32-NEXT:    mv a3, a1
+; RV32-NEXT:    mv a1, a0
+; RV32-NEXT:    mv a0, a2
+; RV32-NEXT:    mv a2, a3
+; RV32-NEXT:    tail callee_musttail_three_indirect
+;
+; RV64-LABEL: caller_musttail_three_indirect_rotated:
+; RV64:       # %bb.0:
+; RV64-NEXT:    mv a6, a3
+; RV64-NEXT:    mv a7, a2
+; RV64-NEXT:    mv a3, a1
+; RV64-NEXT:    mv a2, a0
+; RV64-NEXT:    mv a0, a4
+; RV64-NEXT:    mv a1, a5
+; RV64-NEXT:    mv a4, a7
+; RV64-NEXT:    mv a5, a6
+; RV64-NEXT:    tail callee_musttail_three_indirect
+  %call = musttail call i32 @callee_musttail_three_indirect(fp128 %c, fp128 %a, fp128 %b)
+  ret i32 %call
+}
+
+; Test musttail with mixed direct + indirect args where the indirect args
+; are swapped but the direct arg stays in place.
+declare i32 @callee_musttail_mixed_two_indirect(i32 %x, fp128 %a, fp128 %b)
+
+define i32 @caller_musttail_mixed_swap_indirect(i32 %x, fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_mixed_swap_indirect:
+; RV32:       # %bb.0:
+; RV32-NEXT:    mv a3, a1
+; RV32-NEXT:    mv a1, a2
+; RV32-NEXT:    mv a2, a3
+; RV32-NEXT:    tail callee_musttail_mixed_two_indirect
+;
+; RV64-LABEL: caller_musttail_mixed_swap_indirect:
+; RV64:       # %bb.0:
+; RV64-NEXT:    mv a5, a2
+; RV64-NEXT:    mv a6, a1
+; RV64-NEXT:    mv a1, a3
+; RV64-NEXT:    mv a2, a4
+; RV64-NEXT:    mv a3, a6
+; RV64-NEXT:    mv a4, a5
+; RV64-NEXT:    tail callee_musttail_mixed_two_indirect
+  %call = musttail call i32 @callee_musttail_mixed_two_indirect(i32 %x, fp128 %b, fp128 %a)
+  ret i32 %call
+}
+
+; Test musttail with swapped i128 on RV32 (split indirect args).
+declare i64 @callee_musttail_two_i128(i128 %a, i128 %b)
+
+define i64 @caller_musttail_two_i128_swapped(i128 %a, i128 %b) nounwind {
+; RV32-LABEL: caller_musttail_two_i128_swapped:
+; RV32:       # %bb.0:
+; RV32-NEXT:    mv a2, a0
+; RV32-NEXT:    mv a0, a1
+; RV32-NEXT:    mv a1, a2
+; RV32-NEXT:    tail callee_musttail_two_i128
+;
+; RV64-LABEL: caller_musttail_two_i128_swapped:
+; RV64:       # %bb.0:
+; RV64-NEXT:    mv a4, a1
+; RV64-NEXT:    mv a5, a0
+; RV64-NEXT:    mv a0, a2
+; RV64-NEXT:    mv a1, a3
+; RV64-NEXT:    mv a2, a5
+; RV64-NEXT:    mv a3, a4
+; RV64-NEXT:    tail callee_musttail_two_i128
+  %call = musttail call i64 @callee_musttail_two_i128(i128 %b, i128 %a)
+  ret i64 %call
+}
+
+; Test musttail passing the same indirect arg to both positions.
+define i32 @caller_musttail_two_indirect_dup(fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_two_indirect_dup:
+; RV32:       # %bb.0:
+; RV32-NEXT:    mv a1, a0
+; RV32-NEXT:    tail callee_musttail_two_indirect
+;
+; RV64-LABEL: caller_musttail_two_indirect_dup:
+; RV64:       # %bb.0:
+; RV64-NEXT:    mv a2, a0
+; RV64-NEXT:    mv a3, a1
+; RV64-NEXT:    tail callee_musttail_two_indirect
+  %call = musttail call i32 @callee_musttail_two_indirect(fp128 %a, fp128 %a)
+  ret i32 %call
+}
+
+; Test musttail with enough indirect args to spill to the stack (9 fp128 on
+; RV32 uses a0-a7 for the first 8 pointers, 9th goes on the stack).
+declare void @callee_musttail_nine_indirect(fp128, fp128, fp128, fp128, fp128, fp128, fp128, fp128, fp128)
+
+define void @caller_musttail_nine_indirect(fp128 %a, fp128 %b, fp128 %c, fp128 %d, fp128 %e, fp128 %f, fp128 %g, fp128 %h, fp128 %i) nounwind {
+; RV32-LABEL: caller_musttail_nine_indirect:
+; RV32:       # %bb.0:
+; RV32-NEXT:    lw t0, 0(sp)
+; RV32-NEXT:    sw t0, 0(sp)
+; RV32-NEXT:    tail callee_musttail_nine_indirect
+;
+; RV64-LABEL: caller_musttail_nine_indirect:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi sp, sp, -32
+; RV64-NEXT:    sd s0, 24(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s1, 16(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s2, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    ld t0, 104(sp)
+; RV64-NEXT:    ld t1, 96(sp)
+; RV64-NEXT:    ld t2, 88(sp)
+; RV64-NEXT:    ld t3, 80(sp)
+; RV64-NEXT:    ld t4, 72(sp)
+; RV64-NEXT:    ld t5, 64(sp)
+; RV64-NEXT:    ld t6, 32(sp)
+; RV64-NEXT:    ld s0, 40(sp)
+; RV64-NEXT:    ld s1, 48(sp)
+; RV64-NEXT:    ld s2, 56(sp)
+; RV64-NEXT:    sd t6, 32(sp)
+; RV64-NEXT:    sd s0, 40(sp)
+; RV64-NEXT:    sd s1, 48(sp)
+; RV64-NEXT:    sd s2, 56(sp)
+; RV64-NEXT:    sd t5, 64(sp)
+; RV64-NEXT:    sd t4, 72(sp)
+; RV64-NEXT:    sd t3, 80(sp)
+; RV64-NEXT:    sd t2, 88(sp)
+; RV64-NEXT:    sd t1, 96(sp)
+; RV64-NEXT:    sd t0, 104(sp)
+; RV64-NEXT:    ld s0, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s2, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 32
+; RV64-NEXT:    tail callee_musttail_nine_indirect
+  musttail call void @callee_musttail_nine_indirect(fp128 %a, fp128 %b, fp128 %c, fp128 %d, fp128 %e, fp128 %f, fp128 %g, fp128 %h, fp128 %i)
+  ret void
+}
+
+; Test musttail swapping the first (register) and last (stack-spilled) args.
+define void @caller_musttail_nine_indirect_swap_first_last(fp128 %a, fp128 %b, fp128 %c, fp128 %d, fp128 %e, fp128 %f, fp128 %g, fp128 %h, fp128 %i) nounwind {
+; RV32-LABEL: caller_musttail_nine_indirect_swap_first_last:
+; RV32:       # %bb.0:
+; RV32-NEXT:    lw t0, 0(sp)
+; RV32-NEXT:    sw a0, 0(sp)
+; RV32-NEXT:    mv a0, t0
+; RV32-NEXT:    tail callee_musttail_nine_indirect
+;
+; RV64-LABEL: caller_musttail_nine_indirect_swap_first_last:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi sp, sp, -32
+; RV64-NEXT:    sd s0, 24(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s1, 16(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s2, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    ld t0, 96(sp)
+; RV64-NEXT:    ld t1, 104(sp)
+; RV64-NEXT:    ld t2, 88(sp)
+; RV64-NEXT:    ld t3, 80(sp)
+; RV64-NEXT:    ld t4, 72(sp)
+; RV64-NEXT:    ld t5, 64(sp)
+; RV64-NEXT:    ld t6, 32(sp)
+; RV64-NEXT:    ld s0, 40(sp)
+; RV64-NEXT:    ld s1, 48(sp)
+; RV64-NEXT:    ld s2, 56(sp)
+; RV64-NEXT:    sd t6, 32(sp)
+; RV64-NEXT:    sd s0, 40(sp)
+; RV64-NEXT:    sd s1, 48(sp)
+; RV64-NEXT:    sd s2, 56(sp)
+; RV64-NEXT:    sd t5, 64(sp)
+; RV64-NEXT:    sd t4, 72(sp)
+; RV64-NEXT:    sd t3, 80(sp)
+; RV64-NEXT:    sd t2, 88(sp)
+; RV64-NEXT:    sd a0, 96(sp)
+; RV64-NEXT:    sd a1, 104(sp)
+; RV64-NEXT:    mv a0, t0
+; RV64-NEXT:    mv a1, t1
+; RV64-NEXT:    ld s0, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s2, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 32
+; RV64-NEXT:    tail callee_musttail_nine_indirect
+  musttail call void @callee_musttail_nine_indirect(fp128 %i, fp128 %b, fp128 %c, fp128 %d, fp128 %e, fp128 %f, fp128 %g, fp128 %h, fp128 %a)
+  ret void
+}

>From 568648b54aeb5d9308a2b51d289b9980af9b2b2f Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Mon, 9 Mar 2026 13:23:04 +0100
Subject: [PATCH 4/7] [RISCV] Handle computed values in musttail indirect arg
 forwarding

When a musttail call passes a computed value (not a forwarded formal
parameter) as an indirect argument, we must store the value into the
incoming indirect pointer rather than blindly forwarding it. The
incoming pointer points to the caller's caller's frame, which remains
valid after the tail call and is writable per the RISC-V ABI.

Previously, computed values were silently dropped: the incoming pointer
was forwarded without modification, passing the original (stale) data.

The fix distinguishes two cases via dyn_cast<Argument> on the call arg:
- Forwarded formal parameter: zero-copy pointer forwarding (unchanged)
- Computed value: store into the incoming pointer, then forward it

Add tests for computed fp128, computed i128 (split indirect on RV32),
mixed computed+forwarded, and both-computed scenarios.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  60 ++-
 .../CodeGen/RISCV/musttail-indirect-args.ll   | 359 ++++++++++++++++++
 2 files changed, 405 insertions(+), 14 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 0dafa47f8cc00..fb0babc6b9dba 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24780,31 +24780,63 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
     // Promote the value if needed.
     // For now, only handle fully promoted and indirect arguments.
     if (VA.getLocInfo() == CCValAssign::Indirect) {
-      // 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.
+      // For musttail calls, reuse incoming indirect pointers instead of
+      // creating new stack temporaries. The incoming pointers point 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
+
+        // Resolve which formal parameter is being passed at this call
+        // position. Outs[].OrigArgIndex is the callee's argument position,
+        // but the incoming indirect arg map is keyed by the caller's formal
+        // parameter index. These differ when musttail reorders arguments.
+        const Argument *FormalArg = nullptr;
         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();
+            FormalArg = dyn_cast<Argument>(CallArg);
             break;
           }
           ++Idx;
         }
-        ArgValue = RVFI->getIncomingIndirectArg(FormalIdx);
-        // Skip any split parts of this argument (they are covered by the
-        // forwarded pointer).
+
+        if (FormalArg) {
+          // The call arg is a forwarded formal parameter. Forward its
+          // incoming indirect pointer directly (zero-copy).
+          ArgValue = RVFI->getIncomingIndirectArg(FormalArg->getArgNo());
+        } else {
+          // The call arg is a computed value. Store it into the incoming
+          // indirect pointer for the same-position formal parameter
+          // (musttail guarantees matching prototypes, so types match).
+          // The pointer survives the tail call since it points to the
+          // caller's caller's frame.
+          SDValue IncomingPtr =
+              RVFI->getIncomingIndirectArg(CallArgIdx);
+          MemOpChains.push_back(
+              DAG.getStore(Chain, DL, ArgValue, IncomingPtr,
+                           MachinePointerInfo()));
+          // Store any split parts at their respective offsets.
+          unsigned ArgPartOffset = Outs[OutIdx].PartOffset;
+          while (i + 1 != e &&
+                 Outs[OutIdx + 1].OrigArgIndex == CallArgIdx) {
+            SDValue PartValue = OutVals[OutIdx + 1];
+            unsigned PartOffset =
+                Outs[OutIdx + 1].PartOffset - ArgPartOffset;
+            SDValue Addr = DAG.getNode(ISD::ADD, DL, PtrVT, IncomingPtr,
+                                       DAG.getIntPtrConstant(PartOffset, DL));
+            MemOpChains.push_back(
+                DAG.getStore(Chain, DL, PartValue, Addr,
+                             MachinePointerInfo()));
+            ++i;
+            ++OutIdx;
+          }
+          ArgValue = IncomingPtr;
+        }
+
+        // Skip any remaining split parts (for forwarded args, they are
+        // covered by the forwarded pointer).
         while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == CallArgIdx) {
           ++i;
           ++OutIdx;
diff --git a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
index c6d1743a27d26..ec7eaad2b8924 100644
--- a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
+++ b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
@@ -350,3 +350,362 @@ define void @caller_musttail_nine_indirect_swap_first_last(fp128 %a, fp128 %b, f
   musttail call void @callee_musttail_nine_indirect(fp128 %i, fp128 %b, fp128 %c, fp128 %d, fp128 %e, fp128 %f, fp128 %g, fp128 %h, fp128 %a)
   ret void
 }
+
+; Test musttail where the indirect arg is a computed value, not a forwarded
+; formal parameter. The computed value must be stored into the incoming
+; indirect pointer before tail calling.
+define i32 @caller_musttail_computed(fp128 %a) nounwind {
+; RV32-LABEL: caller_musttail_computed:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -64
+; RV32-NEXT:    sw ra, 60(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s0, 56(sp) # 4-byte Folded Spill
+; RV32-NEXT:    mv s0, a0
+; RV32-NEXT:    lw a3, 0(a0)
+; RV32-NEXT:    lw a4, 4(a0)
+; RV32-NEXT:    lw a5, 8(a0)
+; RV32-NEXT:    lw a6, 12(a0)
+; RV32-NEXT:    sw a3, 8(sp)
+; RV32-NEXT:    sw a4, 12(sp)
+; RV32-NEXT:    sw a5, 16(sp)
+; RV32-NEXT:    sw a6, 20(sp)
+; RV32-NEXT:    addi a0, sp, 40
+; RV32-NEXT:    addi a1, sp, 24
+; RV32-NEXT:    addi a2, sp, 8
+; RV32-NEXT:    sw a3, 24(sp)
+; RV32-NEXT:    sw a4, 28(sp)
+; RV32-NEXT:    sw a5, 32(sp)
+; RV32-NEXT:    sw a6, 36(sp)
+; RV32-NEXT:    call __addtf3
+; RV32-NEXT:    lw a0, 40(sp)
+; RV32-NEXT:    lw a1, 44(sp)
+; RV32-NEXT:    lw a2, 48(sp)
+; RV32-NEXT:    lw a3, 52(sp)
+; RV32-NEXT:    sw a0, 0(s0)
+; RV32-NEXT:    sw a1, 4(s0)
+; RV32-NEXT:    sw a2, 8(s0)
+; RV32-NEXT:    sw a3, 12(s0)
+; RV32-NEXT:    mv a0, s0
+; RV32-NEXT:    lw ra, 60(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s0, 56(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 64
+; RV32-NEXT:    tail callee_musttail_indirect
+;
+; RV64-LABEL: caller_musttail_computed:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi sp, sp, -16
+; RV64-NEXT:    sd ra, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    mv a2, a0
+; RV64-NEXT:    mv a3, a1
+; RV64-NEXT:    call __addtf3
+; RV64-NEXT:    ld ra, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 16
+; RV64-NEXT:    tail callee_musttail_indirect
+  %sum = fadd fp128 %a, %a
+  %r = musttail call i32 @callee_musttail_indirect(fp128 %sum)
+  ret i32 %r
+}
+
+; Test musttail with a computed i128 on RV32 (split indirect). The add result
+; must be stored back into the incoming pointer.
+define i64 @caller_musttail_computed_i128(i128 %a) nounwind {
+; RV32-LABEL: caller_musttail_computed_i128:
+; RV32:       # %bb.0:
+; RV32-NEXT:    lw a1, 0(a0)
+; RV32-NEXT:    lw a2, 4(a0)
+; RV32-NEXT:    lw a3, 8(a0)
+; RV32-NEXT:    lw a4, 12(a0)
+; RV32-NEXT:    addi a1, a1, 1
+; RV32-NEXT:    seqz a5, a1
+; RV32-NEXT:    add a2, a2, a5
+; RV32-NEXT:    or a5, a1, a2
+; RV32-NEXT:    seqz a5, a5
+; RV32-NEXT:    add a5, a3, a5
+; RV32-NEXT:    sltu a3, a5, a3
+; RV32-NEXT:    add a3, a4, a3
+; RV32-NEXT:    sw a1, 0(a0)
+; RV32-NEXT:    sw a2, 4(a0)
+; RV32-NEXT:    sw a5, 8(a0)
+; RV32-NEXT:    sw a3, 12(a0)
+; RV32-NEXT:    tail callee_musttail_i128
+;
+; RV64-LABEL: caller_musttail_computed_i128:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi a0, a0, 1
+; RV64-NEXT:    seqz a2, a0
+; RV64-NEXT:    add a1, a1, a2
+; RV64-NEXT:    tail callee_musttail_i128
+  %sum = add i128 %a, 1
+  %r = musttail call i64 @callee_musttail_i128(i128 %sum)
+  ret i64 %r
+}
+
+; Test musttail with one computed and one forwarded indirect arg.
+; Position 0 gets the fadd result (stored into %a's incoming pointer),
+; position 1 gets %b's incoming pointer forwarded directly.
+define i32 @caller_musttail_computed_and_forwarded(fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_computed_and_forwarded:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -64
+; RV32-NEXT:    sw ra, 60(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s0, 56(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s1, 52(sp) # 4-byte Folded Spill
+; RV32-NEXT:    mv s0, a1
+; RV32-NEXT:    mv s1, a0
+; RV32-NEXT:    lw a3, 0(a0)
+; RV32-NEXT:    lw a4, 4(a0)
+; RV32-NEXT:    lw a5, 8(a0)
+; RV32-NEXT:    lw a6, 12(a0)
+; RV32-NEXT:    lw a0, 0(a1)
+; RV32-NEXT:    lw a1, 4(a1)
+; RV32-NEXT:    lw a2, 8(s0)
+; RV32-NEXT:    lw a7, 12(s0)
+; RV32-NEXT:    sw a0, 0(sp)
+; RV32-NEXT:    sw a1, 4(sp)
+; RV32-NEXT:    sw a2, 8(sp)
+; RV32-NEXT:    sw a7, 12(sp)
+; RV32-NEXT:    addi a0, sp, 32
+; RV32-NEXT:    addi a1, sp, 16
+; RV32-NEXT:    mv a2, sp
+; RV32-NEXT:    sw a3, 16(sp)
+; RV32-NEXT:    sw a4, 20(sp)
+; RV32-NEXT:    sw a5, 24(sp)
+; RV32-NEXT:    sw a6, 28(sp)
+; RV32-NEXT:    call __addtf3
+; RV32-NEXT:    lw a0, 32(sp)
+; RV32-NEXT:    lw a1, 36(sp)
+; RV32-NEXT:    lw a2, 40(sp)
+; RV32-NEXT:    lw a3, 44(sp)
+; RV32-NEXT:    sw a0, 0(s1)
+; RV32-NEXT:    sw a1, 4(s1)
+; RV32-NEXT:    sw a2, 8(s1)
+; RV32-NEXT:    sw a3, 12(s1)
+; RV32-NEXT:    mv a0, s1
+; RV32-NEXT:    mv a1, s0
+; RV32-NEXT:    lw ra, 60(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s0, 56(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s1, 52(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 64
+; RV32-NEXT:    tail callee_musttail_two_indirect
+;
+; RV64-LABEL: caller_musttail_computed_and_forwarded:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi sp, sp, -32
+; RV64-NEXT:    sd ra, 24(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s0, 16(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s1, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    mv s0, a3
+; RV64-NEXT:    mv s1, a2
+; RV64-NEXT:    call __addtf3
+; RV64-NEXT:    mv a2, s1
+; RV64-NEXT:    mv a3, s0
+; RV64-NEXT:    ld ra, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s0, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 32
+; RV64-NEXT:    tail callee_musttail_two_indirect
+  %sum = fadd fp128 %a, %b
+  %r = musttail call i32 @callee_musttail_two_indirect(fp128 %sum, fp128 %b)
+  ret i32 %r
+}
+
+; Test musttail with one forwarded and one computed indirect arg (reversed).
+; Position 0 forwards %a, position 1 gets the computed value.
+define i32 @caller_musttail_forwarded_and_computed(fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_forwarded_and_computed:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -64
+; RV32-NEXT:    sw ra, 60(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s0, 56(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s1, 52(sp) # 4-byte Folded Spill
+; RV32-NEXT:    mv s0, a1
+; RV32-NEXT:    mv s1, a0
+; RV32-NEXT:    lw a3, 0(a0)
+; RV32-NEXT:    lw a4, 4(a0)
+; RV32-NEXT:    lw a5, 8(a0)
+; RV32-NEXT:    lw a6, 12(a0)
+; RV32-NEXT:    lw a0, 0(a1)
+; RV32-NEXT:    lw a1, 4(a1)
+; RV32-NEXT:    lw a2, 8(s0)
+; RV32-NEXT:    lw a7, 12(s0)
+; RV32-NEXT:    sw a0, 0(sp)
+; RV32-NEXT:    sw a1, 4(sp)
+; RV32-NEXT:    sw a2, 8(sp)
+; RV32-NEXT:    sw a7, 12(sp)
+; RV32-NEXT:    addi a0, sp, 32
+; RV32-NEXT:    addi a1, sp, 16
+; RV32-NEXT:    mv a2, sp
+; RV32-NEXT:    sw a3, 16(sp)
+; RV32-NEXT:    sw a4, 20(sp)
+; RV32-NEXT:    sw a5, 24(sp)
+; RV32-NEXT:    sw a6, 28(sp)
+; RV32-NEXT:    call __addtf3
+; RV32-NEXT:    lw a0, 32(sp)
+; RV32-NEXT:    lw a1, 36(sp)
+; RV32-NEXT:    lw a2, 40(sp)
+; RV32-NEXT:    lw a3, 44(sp)
+; RV32-NEXT:    sw a0, 0(s0)
+; RV32-NEXT:    sw a1, 4(s0)
+; RV32-NEXT:    sw a2, 8(s0)
+; RV32-NEXT:    sw a3, 12(s0)
+; RV32-NEXT:    mv a0, s1
+; RV32-NEXT:    mv a1, s0
+; RV32-NEXT:    lw ra, 60(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s0, 56(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s1, 52(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 64
+; RV32-NEXT:    tail callee_musttail_two_indirect
+;
+; RV64-LABEL: caller_musttail_forwarded_and_computed:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi sp, sp, -32
+; RV64-NEXT:    sd ra, 24(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s0, 16(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s1, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    mv s0, a1
+; RV64-NEXT:    mv s1, a0
+; RV64-NEXT:    call __addtf3
+; RV64-NEXT:    mv a2, a0
+; RV64-NEXT:    mv a3, a1
+; RV64-NEXT:    mv a0, s1
+; RV64-NEXT:    mv a1, s0
+; RV64-NEXT:    ld ra, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s0, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 32
+; RV64-NEXT:    tail callee_musttail_two_indirect
+  %sum = fadd fp128 %a, %b
+  %r = musttail call i32 @callee_musttail_two_indirect(fp128 %a, fp128 %sum)
+  ret i32 %r
+}
+
+; Test musttail with both args computed. Neither can be zero-copy forwarded.
+define i32 @caller_musttail_both_computed(fp128 %a, fp128 %b) nounwind {
+; RV32-LABEL: caller_musttail_both_computed:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -160
+; RV32-NEXT:    sw ra, 156(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s0, 152(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s1, 148(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s2, 144(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s3, 140(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s4, 136(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s5, 132(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s6, 128(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s7, 124(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s8, 120(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s9, 116(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s10, 112(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s11, 108(sp) # 4-byte Folded Spill
+; RV32-NEXT:    mv s0, a1
+; RV32-NEXT:    mv s1, a0
+; RV32-NEXT:    lw s2, 0(a0)
+; RV32-NEXT:    lw s3, 4(a0)
+; RV32-NEXT:    lw s4, 8(a0)
+; RV32-NEXT:    lw s5, 12(a0)
+; RV32-NEXT:    lw s6, 0(a1)
+; RV32-NEXT:    lw s7, 4(a1)
+; RV32-NEXT:    lw s8, 8(a1)
+; RV32-NEXT:    lw s9, 12(a1)
+; RV32-NEXT:    sw s6, 56(sp)
+; RV32-NEXT:    sw s7, 60(sp)
+; RV32-NEXT:    sw s8, 64(sp)
+; RV32-NEXT:    sw s9, 68(sp)
+; RV32-NEXT:    addi a0, sp, 88
+; RV32-NEXT:    addi a1, sp, 72
+; RV32-NEXT:    addi a2, sp, 56
+; RV32-NEXT:    sw s2, 72(sp)
+; RV32-NEXT:    sw s3, 76(sp)
+; RV32-NEXT:    sw s4, 80(sp)
+; RV32-NEXT:    sw s5, 84(sp)
+; RV32-NEXT:    call __addtf3
+; RV32-NEXT:    lw s10, 88(sp)
+; RV32-NEXT:    lw s11, 92(sp)
+; RV32-NEXT:    lw a0, 96(sp)
+; RV32-NEXT:    sw a0, 4(sp) # 4-byte Folded Spill
+; RV32-NEXT:    lw a0, 100(sp)
+; RV32-NEXT:    sw a0, 0(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s6, 8(sp)
+; RV32-NEXT:    sw s7, 12(sp)
+; RV32-NEXT:    sw s8, 16(sp)
+; RV32-NEXT:    sw s9, 20(sp)
+; RV32-NEXT:    addi a0, sp, 40
+; RV32-NEXT:    addi a1, sp, 24
+; RV32-NEXT:    addi a2, sp, 8
+; RV32-NEXT:    sw s2, 24(sp)
+; RV32-NEXT:    sw s3, 28(sp)
+; RV32-NEXT:    sw s4, 32(sp)
+; RV32-NEXT:    sw s5, 36(sp)
+; RV32-NEXT:    call __subtf3
+; RV32-NEXT:    lw a0, 40(sp)
+; RV32-NEXT:    lw a1, 44(sp)
+; RV32-NEXT:    lw a2, 48(sp)
+; RV32-NEXT:    lw a3, 52(sp)
+; RV32-NEXT:    sw a0, 0(s0)
+; RV32-NEXT:    sw a1, 4(s0)
+; RV32-NEXT:    sw a2, 8(s0)
+; RV32-NEXT:    sw a3, 12(s0)
+; RV32-NEXT:    sw s10, 0(s1)
+; RV32-NEXT:    sw s11, 4(s1)
+; RV32-NEXT:    lw a0, 4(sp) # 4-byte Folded Reload
+; RV32-NEXT:    sw a0, 8(s1)
+; RV32-NEXT:    lw a0, 0(sp) # 4-byte Folded Reload
+; RV32-NEXT:    sw a0, 12(s1)
+; RV32-NEXT:    mv a0, s1
+; RV32-NEXT:    mv a1, s0
+; RV32-NEXT:    lw ra, 156(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s0, 152(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s1, 148(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s2, 144(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s3, 140(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s4, 136(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s5, 132(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s6, 128(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s7, 124(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s8, 120(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s9, 116(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s10, 112(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s11, 108(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 160
+; RV32-NEXT:    tail callee_musttail_two_indirect
+;
+; RV64-LABEL: caller_musttail_both_computed:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi sp, sp, -64
+; RV64-NEXT:    sd ra, 56(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s0, 48(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s1, 40(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s2, 32(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s3, 24(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s4, 16(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s5, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    mv s0, a3
+; RV64-NEXT:    mv s1, a2
+; RV64-NEXT:    mv s2, a1
+; RV64-NEXT:    mv s3, a0
+; RV64-NEXT:    call __addtf3
+; RV64-NEXT:    mv s4, a0
+; RV64-NEXT:    mv s5, a1
+; RV64-NEXT:    mv a0, s3
+; RV64-NEXT:    mv a1, s2
+; RV64-NEXT:    mv a2, s1
+; RV64-NEXT:    mv a3, s0
+; RV64-NEXT:    call __subtf3
+; RV64-NEXT:    mv a2, a0
+; RV64-NEXT:    mv a3, a1
+; RV64-NEXT:    mv a0, s4
+; RV64-NEXT:    mv a1, s5
+; RV64-NEXT:    ld ra, 56(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s0, 48(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 40(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s2, 32(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s3, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s4, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s5, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 64
+; RV64-NEXT:    tail callee_musttail_two_indirect
+  %sum = fadd fp128 %a, %b
+  %diff = fsub fp128 %a, %b
+  %r = musttail call i32 @callee_musttail_two_indirect(fp128 %sum, fp128 %diff)
+  ret i32 %r
+}

>From 484f135f9dfdbd97360bde0a7d29a1b46f4ab762 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Mon, 9 Mar 2026 13:30:39 +0100
Subject: [PATCH 5/7] [RISCV][NFC] Apply clang-format to musttail indirect arg
 forwarding

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index fb0babc6b9dba..545c2a397d707 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24812,23 +24812,18 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
           // (musttail guarantees matching prototypes, so types match).
           // The pointer survives the tail call since it points to the
           // caller's caller's frame.
-          SDValue IncomingPtr =
-              RVFI->getIncomingIndirectArg(CallArgIdx);
-          MemOpChains.push_back(
-              DAG.getStore(Chain, DL, ArgValue, IncomingPtr,
-                           MachinePointerInfo()));
+          SDValue IncomingPtr = RVFI->getIncomingIndirectArg(CallArgIdx);
+          MemOpChains.push_back(DAG.getStore(Chain, DL, ArgValue, IncomingPtr,
+                                             MachinePointerInfo()));
           // Store any split parts at their respective offsets.
           unsigned ArgPartOffset = Outs[OutIdx].PartOffset;
-          while (i + 1 != e &&
-                 Outs[OutIdx + 1].OrigArgIndex == CallArgIdx) {
+          while (i + 1 != e && Outs[OutIdx + 1].OrigArgIndex == CallArgIdx) {
             SDValue PartValue = OutVals[OutIdx + 1];
-            unsigned PartOffset =
-                Outs[OutIdx + 1].PartOffset - ArgPartOffset;
+            unsigned PartOffset = Outs[OutIdx + 1].PartOffset - ArgPartOffset;
             SDValue Addr = DAG.getNode(ISD::ADD, DL, PtrVT, IncomingPtr,
                                        DAG.getIntPtrConstant(PartOffset, DL));
             MemOpChains.push_back(
-                DAG.getStore(Chain, DL, PartValue, Addr,
-                             MachinePointerInfo()));
+                DAG.getStore(Chain, DL, PartValue, Addr, MachinePointerInfo()));
             ++i;
             ++OutIdx;
           }

>From bf80cae59800eefbd54cc594f581605440f4bc2a Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Sun, 19 Apr 2026 14:40:21 +0200
Subject: [PATCH 6/7] [RISCV] Revert byval/sret tail call support (RISCV-only
 part of 501417baa60f)

Apply the RISCV parts of the upstream revert 501417baa60f (#191508).
The reverted code stored SDValues in MachineFunctionInfo across basic
blocks, which is unsound because the SelectionDAG is cleared between
BBs.

This removes ArgumentStackSize, IncomingByValArgs, the byval matching
loop in isEligibleForTailCallOptimization, parametersInCSRMatch, and
the byval forwarding in LowerCall. The indirect arg rejection and sret
check revert to the conservative pre-byval behavior.

The IncomingIndirectArgs map (for musttail forwarding) is kept; its
SDValue lifetime issue will be fixed in the next commit.

The LoongArch part of the revert is not included because the branch
base has diverged too far.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 105 ++--
 .../Target/RISCV/RISCVMachineFunctionInfo.h   |  14 -
 llvm/test/CodeGen/RISCV/musttail.ll           | 555 ------------------
 llvm/test/CodeGen/RISCV/tail-calls.ll         |  62 +-
 4 files changed, 75 insertions(+), 661 deletions(-)
 delete mode 100644 llvm/test/CodeGen/RISCV/musttail.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 545c2a397d707..62845129b4ac3 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24350,7 +24350,6 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
     SelectionDAG &DAG, SmallVectorImpl<SDValue> &InVals) const {
 
   MachineFunction &MF = DAG.getMachineFunction();
-  RISCVMachineFunctionInfo *RVFI = MF.getInfo<RISCVMachineFunctionInfo>();
 
   switch (CallConv) {
   default:
@@ -24463,7 +24462,8 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
                                    MachinePointerInfo()));
       unsigned ArgIndex = Ins[InsIdx].OrigArgIndex;
       // Save the incoming indirect pointer for musttail forwarding.
-      RVFI->setIncomingIndirectArg(ArgIndex, ArgValue);
+      MF.getInfo<RISCVMachineFunctionInfo>()->setIncomingIndirectArg(
+          ArgIndex, ArgValue);
       unsigned ArgPartOffset = Ins[InsIdx].PartOffset;
       assert(VA.getValVT().isVector() || ArgPartOffset == 0);
       while (i + 1 != e && Ins[InsIdx + 1].OrigArgIndex == ArgIndex) {
@@ -24481,8 +24481,6 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
       continue;
     }
     InVals.push_back(ArgValue);
-    if (Ins[InsIdx].Flags.isByVal())
-      RVFI->addIncomingByValArgs(ArgValue);
   }
 
   if (any_of(ArgLocs,
@@ -24495,6 +24493,7 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
     const TargetRegisterClass *RC = &RISCV::GPRRegClass;
     MachineFrameInfo &MFI = MF.getFrameInfo();
     MachineRegisterInfo &RegInfo = MF.getRegInfo();
+    RISCVMachineFunctionInfo *RVFI = MF.getInfo<RISCVMachineFunctionInfo>();
 
     // Size of the vararg save area. For now, the varargs save area is either
     // zero or large enough to hold a0-a7.
@@ -24542,11 +24541,10 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
     RVFI->setVarArgsSaveSize(VarArgsSaveSize);
   }
 
-  RVFI->setArgumentStackSize(CCInfo.getStackSize());
-
   // All stores are grouped in one node to allow the matching between
   // the size of Ins and InVals. This only happens for vararg functions.
   if (!OutChains.empty()) {
+    assert(IsVarArg && "Only variadic functions should have OutChains");
     OutChains.push_back(Chain);
     Chain = DAG.getNode(ISD::TokenFactor, DL, MVT::Other, OutChains);
   }
@@ -24565,7 +24563,6 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
   auto &Outs = CLI.Outs;
   auto &Caller = MF.getFunction();
   auto CallerCC = Caller.getCallingConv();
-  auto *RVFI = MF.getInfo<RISCVMachineFunctionInfo>();
 
   // Exception-handling functions need a special set of instructions to
   // indicate a return to the hardware. Tail-calling another function would
@@ -24575,41 +24572,31 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
   if (Caller.hasFnAttribute("interrupt"))
     return false;
 
-  // If the stack arguments for this call do not fit into our own save area then
-  // the call cannot be made tail.
-  if (CCInfo.getStackSize() > RVFI->getArgumentStackSize())
+  // musttail calls must always be tail-called. The remaining checks are
+  // for optional tail call optimization of regular (non-musttail) calls.
+  if (CLI.CB && CLI.CB->isMustTailCall())
+    return true;
+
+  // Do not tail call opt if the stack is used to pass parameters.
+  if (CCInfo.getStackSize() != 0)
     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;
-    }
-  }
+  // Do not tail call opt if any parameters need to be passed indirectly.
+  // Since long doubles (fp128) and i128 are larger than 2*XLEN, they are
+  // passed indirectly. The caller allocates stack space for the value and
+  // passes a pointer. On a tail call the caller's frame is deallocated
+  // before the callee executes, leaving the pointer dangling.
+  for (auto &VA : ArgLocs)
+    if (VA.getLocInfo() == CCValAssign::Indirect)
+      return false;
 
   // Do not tail call opt if either caller or callee uses struct return
   // semantics.
   auto IsCallerStructRet = Caller.hasStructRetAttr();
   auto IsCalleeStructRet = Outs.empty() ? false : Outs[0].Flags.isSRet();
-  if (IsCallerStructRet != IsCalleeStructRet)
+  if (IsCallerStructRet || IsCalleeStructRet)
     return false;
 
-  // Do not tail call opt if caller's and callee's byval arguments do not match.
-  for (unsigned i = 0, j = 0, e = Outs.size(); i != e; ++i) {
-    if (!Outs[i].Flags.isByVal())
-      continue;
-    if (j++ >= RVFI->getIncomingByValArgsSize())
-      return false;
-    if (RVFI->getIncomingByValArgs(i).getValueType() != Outs[i].ArgVT)
-      return false;
-  }
-
   // The callee has to preserve all registers the caller needs to preserve.
   const RISCVRegisterInfo *TRI = Subtarget.getRegisterInfo();
   const uint32_t *CallerPreserved = TRI->getCallPreservedMask(MF, CallerCC);
@@ -24619,12 +24606,12 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
       return false;
   }
 
-  // If the callee takes no arguments then go on to check the results of the
-  // call.
-  const MachineRegisterInfo &MRI = MF.getRegInfo();
-  const SmallVectorImpl<SDValue> &OutVals = CLI.OutVals;
-  if (!parametersInCSRMatch(MRI, CallerPreserved, ArgLocs, OutVals))
-    return false;
+  // Byval parameters hand the function a pointer directly into the stack area
+  // we want to reuse during a tail call. Working around this *is* possible
+  // but less efficient and uglier in LowerCall.
+  for (auto &Arg : Outs)
+    if (Arg.Flags.isByVal())
+      return false;
 
   return true;
 }
@@ -24653,7 +24640,6 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
   const CallBase *CB = CLI.CB;
 
   MachineFunction &MF = DAG.getMachineFunction();
-  RISCVMachineFunctionInfo *RVFI = MF.getInfo<RISCVMachineFunctionInfo>();
   MachineFunction::CallSiteInfo CSInfo;
 
   // Set type id for call site info.
@@ -24687,7 +24673,7 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
 
   // Create local copies for byval args
   SmallVector<SDValue, 8> ByValArgs;
-  for (unsigned i = 0, j = 0, e = Outs.size(); i != e; ++i) {
+  for (unsigned i = 0, e = Outs.size(); i != e; ++i) {
     ISD::ArgFlagsTy Flags = Outs[i].Flags;
     if (!Flags.isByVal())
       continue;
@@ -24696,27 +24682,16 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
     unsigned Size = Flags.getByValSize();
     Align Alignment = Flags.getNonZeroByValAlign();
 
+    int FI =
+        MF.getFrameInfo().CreateStackObject(Size, Alignment, /*isSS=*/false);
+    SDValue FIPtr = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
     SDValue SizeNode = DAG.getConstant(Size, DL, XLenVT);
-    SDValue Dst;
 
-    if (IsTailCall) {
-      SDValue CallerArg = RVFI->getIncomingByValArgs(j++);
-      if (isa<GlobalAddressSDNode>(Arg) || isa<ExternalSymbolSDNode>(Arg) ||
-          isa<FrameIndexSDNode>(Arg))
-        Dst = CallerArg;
-    } else {
-      int FI =
-          MF.getFrameInfo().CreateStackObject(Size, Alignment, /*isSS=*/false);
-      Dst = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
-    }
-    if (Dst) {
-      Chain =
-          DAG.getMemcpy(Chain, DL, Dst, Arg, SizeNode, Alignment,
-                        /*IsVolatile=*/false,
-                        /*AlwaysInline=*/false, /*CI=*/nullptr, std::nullopt,
-                        MachinePointerInfo(), MachinePointerInfo());
-      ByValArgs.push_back(Dst);
-    }
+    Chain = DAG.getMemcpy(Chain, DL, FIPtr, Arg, SizeNode, Alignment,
+                          /*IsVolatile=*/false,
+                          /*AlwaysInline=*/false, /*CI*/ nullptr, IsTailCall,
+                          MachinePointerInfo(), MachinePointerInfo());
+    ByValArgs.push_back(FIPtr);
   }
 
   if (!IsTailCall)
@@ -24784,6 +24759,8 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
       // creating new stack temporaries. The incoming pointers point to the
       // caller's caller's frame, which remains valid after a tail call.
       if (IsTailCall && CLI.CB && CLI.CB->isMustTailCall()) {
+        RISCVMachineFunctionInfo *RVFI =
+            MF.getInfo<RISCVMachineFunctionInfo>();
         unsigned CallArgIdx = Outs[OutIdx].OrigArgIndex;
 
         // Resolve which formal parameter is being passed at this call
@@ -24888,12 +24865,8 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
     }
 
     // Use local copy if it is a byval arg.
-    if (Flags.isByVal()) {
-      if (!IsTailCall || (isa<GlobalAddressSDNode>(ArgValue) ||
-                          isa<ExternalSymbolSDNode>(ArgValue) ||
-                          isa<FrameIndexSDNode>(ArgValue)))
-        ArgValue = ByValArgs[j++];
-    }
+    if (Flags.isByVal())
+      ArgValue = ByValArgs[j++];
 
     if (VA.isRegLoc()) {
       // Queue up the argument copies and emit them at the end.
diff --git a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
index 9db41bc04094e..f0e37e3940258 100644
--- a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
@@ -67,13 +67,6 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
   /// Size of stack frame to save callee saved registers
   unsigned CalleeSavedStackSize = 0;
 
-  /// amount of bytes on stack consumed by the arguments being passed on the
-  /// stack
-  unsigned ArgumentStackSize = 0;
-
-  /// 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;
@@ -155,13 +148,6 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
   unsigned getCalleeSavedStackSize() const { return CalleeSavedStackSize; }
   void setCalleeSavedStackSize(unsigned Size) { CalleeSavedStackSize = Size; }
 
-  unsigned getArgumentStackSize() const { return ArgumentStackSize; }
-  void setArgumentStackSize(unsigned size) { ArgumentStackSize = size; }
-
-  void addIncomingByValArgs(SDValue Val) { IncomingByValArgs.push_back(Val); }
-  SDValue getIncomingByValArgs(unsigned Idx) { return IncomingByValArgs[Idx]; }
-  unsigned getIncomingByValArgsSize() const { return IncomingByValArgs.size(); }
-
   void setIncomingIndirectArg(unsigned ArgIndex, SDValue Val) {
     IncomingIndirectArgs[ArgIndex] = Val;
   }
diff --git a/llvm/test/CodeGen/RISCV/musttail.ll b/llvm/test/CodeGen/RISCV/musttail.ll
deleted file mode 100644
index 4c27822525511..0000000000000
--- a/llvm/test/CodeGen/RISCV/musttail.ll
+++ /dev/null
@@ -1,555 +0,0 @@
-; 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
-
-declare i32 @many_args_callee(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 %8, i32 %9)
-
-define i32 @many_args_tail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 %8, i32 %9) {
-; RV32-LABEL: many_args_tail:
-; RV32:       # %bb.0:
-; RV32-NEXT:    li a0, 8
-; RV32-NEXT:    li t0, 9
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    li a5, 5
-; RV32-NEXT:    li a6, 6
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    li a7, 7
-; RV32-NEXT:    sw t0, 4(sp)
-; RV32-NEXT:    li a0, 0
-; RV32-NEXT:    tail many_args_callee
-;
-; RV64-LABEL: many_args_tail:
-; RV64:       # %bb.0:
-; RV64-NEXT:    li a0, 8
-; RV64-NEXT:    li t0, 9
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    li a5, 5
-; RV64-NEXT:    li a6, 6
-; RV64-NEXT:    sd a0, 0(sp)
-; RV64-NEXT:    li a7, 7
-; RV64-NEXT:    sd t0, 8(sp)
-; RV64-NEXT:    li a0, 0
-; RV64-NEXT:    tail many_args_callee
-  %ret = tail call i32 @many_args_callee(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9)
-  ret i32 %ret
-}
-
-define i32 @many_args_musttail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 %8, i32 %9) {
-; RV32-LABEL: many_args_musttail:
-; RV32:       # %bb.0:
-; RV32-NEXT:    li a0, 8
-; RV32-NEXT:    li t0, 9
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    li a5, 5
-; RV32-NEXT:    li a6, 6
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    li a7, 7
-; RV32-NEXT:    sw t0, 4(sp)
-; RV32-NEXT:    li a0, 0
-; RV32-NEXT:    tail many_args_callee
-;
-; RV64-LABEL: many_args_musttail:
-; RV64:       # %bb.0:
-; RV64-NEXT:    li a0, 8
-; RV64-NEXT:    li t0, 9
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    li a5, 5
-; RV64-NEXT:    li a6, 6
-; RV64-NEXT:    sd a0, 0(sp)
-; RV64-NEXT:    li a7, 7
-; RV64-NEXT:    sd t0, 8(sp)
-; RV64-NEXT:    li a0, 0
-; RV64-NEXT:    tail many_args_callee
-  %ret = musttail call i32 @many_args_callee(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9)
-  ret i32 %ret
-}
-
-; This function has more arguments than it's tail-callee. This isn't valid for
-; the musttail attribute, but can still be tail-called as a non-guaranteed
-; optimisation, because the outgoing arguments to @many_args_callee fit in the
-; stack space allocated by the caller of @more_args_tail.
-define i32 @more_args_tail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 %8, i32 %9) {
-; RV32-LABEL: more_args_tail:
-; RV32:       # %bb.0:
-; RV32-NEXT:    li a0, 8
-; RV32-NEXT:    li t0, 9
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    li a5, 5
-; RV32-NEXT:    li a6, 6
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    li a7, 7
-; RV32-NEXT:    sw t0, 4(sp)
-; RV32-NEXT:    li a0, 0
-; RV32-NEXT:    tail many_args_callee
-;
-; RV64-LABEL: more_args_tail:
-; RV64:       # %bb.0:
-; RV64-NEXT:    li a0, 8
-; RV64-NEXT:    li t0, 9
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    li a5, 5
-; RV64-NEXT:    li a6, 6
-; RV64-NEXT:    sd a0, 0(sp)
-; RV64-NEXT:    li a7, 7
-; RV64-NEXT:    sd t0, 8(sp)
-; RV64-NEXT:    li a0, 0
-; RV64-NEXT:    tail many_args_callee
-  %ret = tail call i32 @many_args_callee(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9)
-  ret i32 %ret
-}
-
-; Again, this isn't valid for musttail, but can be tail-called in practice
-; because the stack size is the same.
-define i32 @different_args_tail_32bit(i64 %0, i64 %1, i64 %2, i64 %3, i64 %4) nounwind {
-; RV32-LABEL: different_args_tail_32bit:
-; RV32:       # %bb.0:
-; RV32-NEXT:    li a0, 8
-; RV32-NEXT:    li t0, 9
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    li a5, 5
-; RV32-NEXT:    li a6, 6
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    li a7, 7
-; RV32-NEXT:    sw t0, 4(sp)
-; RV32-NEXT:    li a0, 0
-; RV32-NEXT:    tail many_args_callee
-;
-; RV64-LABEL: different_args_tail_32bit:
-; RV64:       # %bb.0:
-; RV64-NEXT:    addi sp, sp, -32
-; RV64-NEXT:    sd ra, 24(sp) # 8-byte Folded Spill
-; RV64-NEXT:    li a0, 9
-; RV64-NEXT:    li t0, 8
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    li a5, 5
-; RV64-NEXT:    li a6, 6
-; RV64-NEXT:    li a7, 7
-; RV64-NEXT:    sd t0, 0(sp)
-; RV64-NEXT:    sd a0, 8(sp)
-; RV64-NEXT:    li a0, 0
-; RV64-NEXT:    call many_args_callee
-; RV64-NEXT:    ld ra, 24(sp) # 8-byte Folded Reload
-; RV64-NEXT:    addi sp, sp, 32
-; RV64-NEXT:    ret
-  %ret = tail call i32 @many_args_callee(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9)
-  ret i32 %ret
-}
-
-define i32 @different_args_tail_64bit(i128 %0, i128 %1, i128 %2, i128 %3, i128 %4) nounwind {
-; RV32-LABEL: different_args_tail_64bit:
-; RV32:       # %bb.0:
-; RV32-NEXT:    addi sp, sp, -16
-; RV32-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT:    li a0, 9
-; RV32-NEXT:    li t0, 8
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    li a5, 5
-; RV32-NEXT:    li a6, 6
-; RV32-NEXT:    li a7, 7
-; RV32-NEXT:    sw t0, 0(sp)
-; RV32-NEXT:    sw a0, 4(sp)
-; RV32-NEXT:    li a0, 0
-; RV32-NEXT:    call many_args_callee
-; RV32-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT:    addi sp, sp, 16
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: different_args_tail_64bit:
-; RV64:       # %bb.0:
-; RV64-NEXT:    li a0, 8
-; RV64-NEXT:    li t0, 9
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    li a5, 5
-; RV64-NEXT:    li a6, 6
-; RV64-NEXT:    sd a0, 0(sp)
-; RV64-NEXT:    li a7, 7
-; RV64-NEXT:    sd t0, 8(sp)
-; RV64-NEXT:    li a0, 0
-; RV64-NEXT:    tail many_args_callee
-  %ret = tail call i32 @many_args_callee(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9)
-  ret i32 %ret
-}
-
-; Here, the caller requires less stack space for it's arguments than the
-; callee, so it would not ba valid to do a tail-call.
-define i32 @fewer_args_tail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4) nounwind {
-; RV32-LABEL: fewer_args_tail:
-; RV32:       # %bb.0:
-; RV32-NEXT:    addi sp, sp, -16
-; RV32-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT:    li a0, 9
-; RV32-NEXT:    li t0, 8
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    li a5, 5
-; RV32-NEXT:    li a6, 6
-; RV32-NEXT:    li a7, 7
-; RV32-NEXT:    sw t0, 0(sp)
-; RV32-NEXT:    sw a0, 4(sp)
-; RV32-NEXT:    li a0, 0
-; RV32-NEXT:    call many_args_callee
-; RV32-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT:    addi sp, sp, 16
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: fewer_args_tail:
-; RV64:       # %bb.0:
-; RV64-NEXT:    addi sp, sp, -32
-; RV64-NEXT:    sd ra, 24(sp) # 8-byte Folded Spill
-; RV64-NEXT:    li a0, 9
-; RV64-NEXT:    li t0, 8
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    li a5, 5
-; RV64-NEXT:    li a6, 6
-; RV64-NEXT:    li a7, 7
-; RV64-NEXT:    sd t0, 0(sp)
-; RV64-NEXT:    sd a0, 8(sp)
-; RV64-NEXT:    li a0, 0
-; RV64-NEXT:    call many_args_callee
-; RV64-NEXT:    ld ra, 24(sp) # 8-byte Folded Reload
-; RV64-NEXT:    addi sp, sp, 32
-; RV64-NEXT:    ret
-  %ret = tail call i32 @many_args_callee(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9)
-  ret i32 %ret
-}
-
-declare void @foo(i32, i32, i32, i32, i32, i32, i32, i32, i32)
-
-define void @bar(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 %8) nounwind {
-; RV32-LABEL: bar:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    addi sp, sp, -48
-; RV32-NEXT:    sw ra, 44(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s0, 40(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s1, 36(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s2, 32(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s3, 28(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s4, 24(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s5, 20(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s6, 16(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s7, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT:    mv s0, a7
-; RV32-NEXT:    mv s1, a6
-; RV32-NEXT:    mv s2, a5
-; RV32-NEXT:    mv s3, a4
-; RV32-NEXT:    mv s4, a3
-; RV32-NEXT:    mv s5, a2
-; RV32-NEXT:    mv s6, a1
-; RV32-NEXT:    mv s7, a0
-; RV32-NEXT:    li a0, 1
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    mv a0, s7
-; RV32-NEXT:    call foo
-; RV32-NEXT:    li a0, 2
-; RV32-NEXT:    sw a0, 48(sp)
-; RV32-NEXT:    mv a0, s7
-; RV32-NEXT:    mv a1, s6
-; RV32-NEXT:    mv a2, s5
-; RV32-NEXT:    mv a3, s4
-; RV32-NEXT:    mv a4, s3
-; RV32-NEXT:    mv a5, s2
-; RV32-NEXT:    mv a6, s1
-; RV32-NEXT:    mv a7, s0
-; RV32-NEXT:    lw ra, 44(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s0, 40(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s1, 36(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s2, 32(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s3, 28(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s4, 24(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s5, 20(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s6, 16(sp) # 4-byte Folded Reload
-; RV32-NEXT:    lw s7, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT:    addi sp, sp, 48
-; RV32-NEXT:    tail foo
-;
-; RV64-LABEL: bar:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    addi sp, sp, -80
-; RV64-NEXT:    sd ra, 72(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s0, 64(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s1, 56(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s2, 48(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s3, 40(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s4, 32(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s5, 24(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s6, 16(sp) # 8-byte Folded Spill
-; RV64-NEXT:    sd s7, 8(sp) # 8-byte Folded Spill
-; RV64-NEXT:    mv s0, a7
-; RV64-NEXT:    mv s1, a6
-; RV64-NEXT:    mv s2, a5
-; RV64-NEXT:    mv s3, a4
-; RV64-NEXT:    mv s4, a3
-; RV64-NEXT:    mv s5, a2
-; RV64-NEXT:    mv s6, a1
-; RV64-NEXT:    mv s7, a0
-; RV64-NEXT:    li a0, 1
-; RV64-NEXT:    sd a0, 0(sp)
-; RV64-NEXT:    mv a0, s7
-; RV64-NEXT:    call foo
-; RV64-NEXT:    li a0, 2
-; RV64-NEXT:    sd a0, 80(sp)
-; RV64-NEXT:    mv a0, s7
-; RV64-NEXT:    mv a1, s6
-; RV64-NEXT:    mv a2, s5
-; RV64-NEXT:    mv a3, s4
-; RV64-NEXT:    mv a4, s3
-; RV64-NEXT:    mv a5, s2
-; RV64-NEXT:    mv a6, s1
-; RV64-NEXT:    mv a7, s0
-; RV64-NEXT:    ld ra, 72(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s0, 64(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s1, 56(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s2, 48(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s3, 40(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s4, 32(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s5, 24(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s6, 16(sp) # 8-byte Folded Reload
-; RV64-NEXT:    ld s7, 8(sp) # 8-byte Folded Reload
-; RV64-NEXT:    addi sp, sp, 80
-; RV64-NEXT:    tail foo
-entry:
-  call void @foo(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 1)
-  musttail call void @foo(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, i32 2)
-  ret void
-}
-
-declare void @sret_callee(ptr sret({ double, double }) align 8)
-
-; Functions which return by sret can be tail-called because the incoming sret
-; pointer gets passed through to the callee.
-define void @sret_caller_tail(ptr sret({ double, double }) align 8 %result) {
-; RV32-LABEL: sret_caller_tail:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    tail sret_callee
-;
-; RV64-LABEL: sret_caller_tail:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    tail sret_callee
-entry:
-  tail call void @sret_callee(ptr sret({ double, double }) align 8 %result)
-  ret void
-}
-
-define void @sret_caller_musttail(ptr sret({ double, double }) align 8 %result) {
-; RV32-LABEL: sret_caller_musttail:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    tail sret_callee
-;
-; RV64-LABEL: sret_caller_musttail:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    tail sret_callee
-entry:
-  musttail call void @sret_callee(ptr sret({ double, double }) align 8 %result)
-  ret void
-}
-
-%twenty_bytes = type { [5 x i32] }
-declare void @large_callee(%twenty_bytes* byval(%twenty_bytes) align 4)
-
-; Functions with byval parameters can be tail-called, because the value is
-; actually passed in registers in the same way for the caller and callee.
-define void @large_caller(%twenty_bytes* byval(%twenty_bytes) align 4 %a) {
-; RV32-LABEL: large_caller:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    tail large_callee
-;
-; RV64-LABEL: large_caller:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    tail large_callee
-entry:
-  musttail call void @large_callee(%twenty_bytes* byval(%twenty_bytes) align 4 %a)
-  ret void
-}
-
-; As above, but with some inline asm to test that the arguments in r4 is
-; re-loaded before the call.
-define void @large_caller_check_regs(%twenty_bytes* byval(%twenty_bytes) align 4 %a) nounwind {
-; RV32-LABEL: large_caller_check_regs:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    #APP
-; RV32-NEXT:    #NO_APP
-; RV32-NEXT:    tail large_callee
-;
-; RV64-LABEL: large_caller_check_regs:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    #APP
-; RV64-NEXT:    #NO_APP
-; RV64-NEXT:    tail large_callee
-entry:
-  tail call void asm sideeffect "", "~{r4}"()
-  musttail call void @large_callee(%twenty_bytes* byval(%twenty_bytes) align 4 %a)
-  ret void
-}
-
-; The IR for this one looks dodgy, because it has an alloca passed to a
-; musttail function, but it is passed as a byval argument, so will be copied
-; into the stack space allocated by @large_caller_new_value's caller, so is
-; valid.
-define void @large_caller_new_value(%twenty_bytes* byval(%twenty_bytes) align 4 %a) nounwind {
-; RV32-LABEL: large_caller_new_value:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    addi sp, sp, -32
-; RV32-NEXT:    li a1, 1
-; RV32-NEXT:    li a2, 2
-; RV32-NEXT:    li a3, 3
-; RV32-NEXT:    li a4, 4
-; RV32-NEXT:    sw zero, 12(sp)
-; RV32-NEXT:    sw a1, 16(sp)
-; RV32-NEXT:    sw a2, 20(sp)
-; RV32-NEXT:    sw a3, 24(sp)
-; RV32-NEXT:    sw a4, 28(sp)
-; RV32-NEXT:    sw a4, 16(a0)
-; RV32-NEXT:    sw zero, 0(a0)
-; RV32-NEXT:    sw a1, 4(a0)
-; RV32-NEXT:    sw a2, 8(a0)
-; RV32-NEXT:    sw a3, 12(a0)
-; RV32-NEXT:    addi sp, sp, 32
-; RV32-NEXT:    tail large_callee
-;
-; RV64-LABEL: large_caller_new_value:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    addi sp, sp, -32
-; RV64-NEXT:    li a1, 1
-; RV64-NEXT:    li a2, 2
-; RV64-NEXT:    li a3, 3
-; RV64-NEXT:    li a4, 4
-; RV64-NEXT:    sw zero, 12(sp)
-; RV64-NEXT:    sw a1, 16(sp)
-; RV64-NEXT:    sw a2, 20(sp)
-; RV64-NEXT:    sw a3, 24(sp)
-; RV64-NEXT:    sw a4, 28(sp)
-; RV64-NEXT:    sw a4, 16(a0)
-; RV64-NEXT:    sw zero, 0(a0)
-; RV64-NEXT:    sw a1, 4(a0)
-; RV64-NEXT:    sw a2, 8(a0)
-; RV64-NEXT:    sw a3, 12(a0)
-; RV64-NEXT:    addi sp, sp, 32
-; RV64-NEXT:    tail large_callee
-entry:
-  %y = alloca %twenty_bytes, align 4
-  store i32 0, ptr %y, align 4
-  %0 = getelementptr inbounds i8, ptr %y, i32 4
-  store i32 1, ptr %0, align 4
-  %1 = getelementptr inbounds i8, ptr %y, i32 8
-  store i32 2, ptr %1, align 4
-  %2 = getelementptr inbounds i8, ptr %y, i32 12
-  store i32 3, ptr %2, align 4
-  %3 = getelementptr inbounds i8, ptr %y, i32 16
-  store i32 4, ptr %3, align 4
-  musttail call void @large_callee(%twenty_bytes* byval(%twenty_bytes) align 4 %y)
-  ret void
-}
-
-declare void @two_byvals_callee(%twenty_bytes* byval(%twenty_bytes) align 4, %twenty_bytes* byval(%twenty_bytes) align 4)
-define void @swap_byvals(%twenty_bytes* byval(%twenty_bytes) align 4 %a, %twenty_bytes* byval(%twenty_bytes) align 4 %b) {
-; RV32-LABEL: swap_byvals:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    mv a2, a0
-; RV32-NEXT:    mv a0, a1
-; RV32-NEXT:    mv a1, a2
-; RV32-NEXT:    tail two_byvals_callee
-;
-; RV64-LABEL: swap_byvals:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    mv a2, a0
-; RV64-NEXT:    mv a0, a1
-; RV64-NEXT:    mv a1, a2
-; RV64-NEXT:    tail two_byvals_callee
-entry:
-  musttail call void @two_byvals_callee(%twenty_bytes* byval(%twenty_bytes) align 4 %b, %twenty_bytes* byval(%twenty_bytes) align 4 %a)
-  ret void
-}
-
-; A forwarded byval arg, but in a different argument register, so it needs to
-; be moved between registers first. This can't be musttail because of the
-; different signatures, but is still tail-called as an optimisation.
-declare void @shift_byval_callee(%twenty_bytes* byval(%twenty_bytes) align 4)
-define void @shift_byval(i32 %a, %twenty_bytes* byval(%twenty_bytes) align 4 %b) {
-; RV32-LABEL: shift_byval:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    mv a0, a1
-; RV32-NEXT:    tail shift_byval_callee
-;
-; RV64-LABEL: shift_byval:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    mv a0, a1
-; RV64-NEXT:    tail shift_byval_callee
-entry:
-  tail call void @shift_byval_callee(%twenty_bytes* byval(%twenty_bytes) align 4 %b)
-  ret void
-}
-
-; A global object passed to a byval argument, so it must be copied, but doesn't
-; need a stack temporary.
- at large_global = external global %twenty_bytes
-define void @large_caller_from_global(%twenty_bytes* byval(%twenty_bytes) align 4 %a) {
-; RV32-LABEL: large_caller_from_global:
-; RV32:       # %bb.0: # %entry
-; RV32-NEXT:    lui a1, %hi(large_global)
-; RV32-NEXT:    addi a1, a1, %lo(large_global)
-; RV32-NEXT:    lw a2, 16(a1)
-; RV32-NEXT:    sw a2, 16(a0)
-; RV32-NEXT:    lw a2, 12(a1)
-; RV32-NEXT:    sw a2, 12(a0)
-; RV32-NEXT:    lw a2, 8(a1)
-; RV32-NEXT:    sw a2, 8(a0)
-; RV32-NEXT:    lw a2, 4(a1)
-; RV32-NEXT:    sw a2, 4(a0)
-; RV32-NEXT:    lw a1, 0(a1)
-; RV32-NEXT:    sw a1, 0(a0)
-; RV32-NEXT:    tail large_callee
-;
-; RV64-LABEL: large_caller_from_global:
-; RV64:       # %bb.0: # %entry
-; RV64-NEXT:    lui a1, %hi(large_global)
-; RV64-NEXT:    addi a1, a1, %lo(large_global)
-; RV64-NEXT:    lw a2, 16(a1)
-; RV64-NEXT:    sw a2, 16(a0)
-; RV64-NEXT:    lw a2, 12(a1)
-; RV64-NEXT:    sw a2, 12(a0)
-; RV64-NEXT:    lw a2, 8(a1)
-; RV64-NEXT:    sw a2, 8(a0)
-; RV64-NEXT:    lw a2, 4(a1)
-; RV64-NEXT:    sw a2, 4(a0)
-; RV64-NEXT:    lw a1, 0(a1)
-; RV64-NEXT:    sw a1, 0(a0)
-; RV64-NEXT:    tail large_callee
-entry:
-  musttail call void @large_callee(%twenty_bytes* byval(%twenty_bytes) align 4 @large_global)
-  ret void
-}
diff --git a/llvm/test/CodeGen/RISCV/tail-calls.ll b/llvm/test/CodeGen/RISCV/tail-calls.ll
index 79855aa03adcf..6756fea8a1f85 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls.ll
@@ -204,39 +204,49 @@ declare i32 @callee_args(i32 %a, i32 %b, i32 %c, i32 %dd, i32 %e, i32 %ff, i32 %
 define i32 @caller_args(i32 %a, i32 %b, i32 %c, i32 %dd, i32 %e, i32 %ff, i32 %g, i32 %h, i32 %i, i32 %j, i32 %k, i32 %l, i32 %m, i32 %n) nounwind {
 ; CHECK-LABEL: caller_args:
 ; CHECK:       # %bb.0: # %entry
-; CHECK-NEXT:    lw t0, 20(sp)
-; CHECK-NEXT:    lw t1, 16(sp)
-; CHECK-NEXT:    lw t2, 0(sp)
-; CHECK-NEXT:    lw t3, 4(sp)
-; CHECK-NEXT:    lw t4, 8(sp)
-; CHECK-NEXT:    lw t5, 12(sp)
-; CHECK-NEXT:    sw t2, 0(sp)
-; CHECK-NEXT:    sw t3, 4(sp)
-; CHECK-NEXT:    sw t4, 8(sp)
-; CHECK-NEXT:    sw t5, 12(sp)
-; CHECK-NEXT:    sw t1, 16(sp)
-; CHECK-NEXT:    sw t0, 20(sp)
-; CHECK-NEXT:    tail callee_args
+; CHECK-NEXT:    addi sp, sp, -32
+; CHECK-NEXT:    sw ra, 28(sp) # 4-byte Folded Spill
+; CHECK-NEXT:    lw t0, 32(sp)
+; CHECK-NEXT:    lw t1, 36(sp)
+; CHECK-NEXT:    lw t2, 40(sp)
+; CHECK-NEXT:    lw t3, 44(sp)
+; CHECK-NEXT:    lw t4, 48(sp)
+; CHECK-NEXT:    lw t5, 52(sp)
+; CHECK-NEXT:    sw t4, 16(sp)
+; CHECK-NEXT:    sw t5, 20(sp)
+; CHECK-NEXT:    sw t0, 0(sp)
+; CHECK-NEXT:    sw t1, 4(sp)
+; CHECK-NEXT:    sw t2, 8(sp)
+; CHECK-NEXT:    sw t3, 12(sp)
+; CHECK-NEXT:    call callee_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_args:
 ; CHECK-LARGE-ZICFILP:       # %bb.0: # %entry
 ; CHECK-LARGE-ZICFILP-NEXT:    lpad 0
-; CHECK-LARGE-ZICFILP-NEXT:    lw t0, 20(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    lw t1, 16(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    lw t2, 0(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    lw t3, 12(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    lw t4, 8(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    lw t5, 4(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    sw t2, 0(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    addi sp, sp, -32
+; CHECK-LARGE-ZICFILP-NEXT:    sw ra, 28(sp) # 4-byte Folded Spill
+; CHECK-LARGE-ZICFILP-NEXT:    lw t0, 32(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    lw t1, 36(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    lw t3, 40(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    lw t4, 44(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    lw t2, 48(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    lw t5, 52(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    sw t2, 16(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    sw t5, 20(sp)
 ; CHECK-LARGE-ZICFILP-NEXT:  .Lpcrel_hi8:
 ; CHECK-LARGE-ZICFILP-NEXT:    auipc t2, %pcrel_hi(.LCPI6_0)
 ; CHECK-LARGE-ZICFILP-NEXT:    lw t2, %pcrel_lo(.Lpcrel_hi8)(t2)
-; CHECK-LARGE-ZICFILP-NEXT:    sw t5, 4(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    sw t4, 8(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    sw t3, 12(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    sw t1, 16(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    sw t0, 20(sp)
-; CHECK-LARGE-ZICFILP-NEXT:    jr t2
+; CHECK-LARGE-ZICFILP-NEXT:    sw t0, 0(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    sw t1, 4(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    sw t3, 8(sp)
+; CHECK-LARGE-ZICFILP-NEXT:    sw t4, 12(sp)
+; 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:
   %r = tail call i32 @callee_args(i32 %a, i32 %b, i32 %c, i32 %dd, i32 %e, i32 %ff, i32 %g, i32 %h, i32 %i, i32 %j, i32 %k, i32 %l, i32 %m, i32 %n)
   ret i32 %r

>From 7067a9ef2f6ffde121c958f83e79023aa66bbe88 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Sun, 19 Apr 2026 15:17:13 +0200
Subject: [PATCH 7/7] [RISCV] Fix SDValue lifetime for musttail indirect arg
 forwarding

The SDValue stored during LowerFormalArguments points into the entry
block's DAG, which is cleared at the end of CodeGenAndEmitDAG(). If the
musttail call is in a non-entry basic block, the stored SDValue is
stale.

Fix by saving the indirect pointer in a virtual register (CopyToReg
in LowerFormalArguments, CopyFromReg in LowerCall). The vreg survives
across basic blocks. The DenseMap in RVFI now stores Register instead
of SDValue, and the header does not include SelectionDAGNodes.h.

The vreg save is guarded behind a HasMusttail check so functions
without musttail calls see no codegen change.

Also adds musttail early return in isEligibleForTailCallOptimization
(musttail must always be tail-called), cross-BB test cases, and
converts musttail-call.ll from a crash test to a positive test.

Fixes #185089.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  78 ++++--
 .../Target/RISCV/RISCVMachineFunctionInfo.h   |  15 +-
 llvm/test/CodeGen/RISCV/musttail-call.ll      |  26 +-
 .../CodeGen/RISCV/musttail-indirect-args.ll   | 263 +++++++++++++-----
 4 files changed, 275 insertions(+), 107 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 62845129b4ac3..e037fc8fa22df 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24425,6 +24425,19 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
   EVT PtrVT = getPointerTy(DAG.getDataLayout());
   MVT XLenVT = Subtarget.getXLenVT();
   unsigned XLenInBytes = Subtarget.getXLen() / 8;
+
+  // Check if this function has any musttail calls. If so, incoming indirect
+  // arg pointers must be saved in virtual registers so they survive across
+  // basic blocks (the SelectionDAG is cleared between BBs). Only do this
+  // when needed to avoid adding register pressure to non-musttail functions.
+  bool HasMusttail = llvm::any_of(Func, [](const BasicBlock &BB) {
+    return llvm::any_of(BB, [](const Instruction &I) {
+      if (const auto *CI = dyn_cast<CallInst>(&I))
+        return CI->isMustTailCall();
+      return false;
+    });
+  });
+
   // Used with vargs to accumulate store chains.
   std::vector<SDValue> OutChains;
 
@@ -24461,9 +24474,16 @@ 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.
-      MF.getInfo<RISCVMachineFunctionInfo>()->setIncomingIndirectArg(
-          ArgIndex, ArgValue);
+      // Save the incoming indirect pointer in a virtual register for musttail
+      // forwarding. A vreg is used because the SelectionDAG is cleared between
+      // basic blocks, and musttail calls may appear in non-entry blocks.
+      if (HasMusttail) {
+        Register VReg =
+            MF.getRegInfo().createVirtualRegister(&RISCV::GPRRegClass);
+        Chain = DAG.getCopyToReg(Chain, DL, VReg, ArgValue);
+        MF.getInfo<RISCVMachineFunctionInfo>()->setIncomingIndirectArg(
+            ArgIndex, VReg);
+      }
       unsigned ArgPartOffset = Ins[InsIdx].PartOffset;
       assert(VA.getValVT().isVector() || ArgPartOffset == 0);
       while (i + 1 != e && Ins[InsIdx + 1].OrigArgIndex == ArgIndex) {
@@ -24764,32 +24784,54 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
         unsigned CallArgIdx = Outs[OutIdx].OrigArgIndex;
 
         // Resolve which formal parameter is being passed at this call
-        // position. Outs[].OrigArgIndex is the callee's argument position,
-        // but the incoming indirect arg map is keyed by the caller's formal
-        // parameter index. These differ when musttail reorders arguments.
+        // position. Outs[].OrigArgIndex indexes the filtered arg list
+        // (empty types removed), but IncomingIndirectArgs is keyed by
+        // Argument::getArgNo() (unfiltered position). We need to:
+        // 1. Find the call operand at filtered position CallArgIdx
+        // 2. Check if it's a forwarded formal param (dyn_cast<Argument>)
+        // 3. Resolve the unfiltered formal param index for the map lookup
         const Argument *FormalArg = nullptr;
-        unsigned Idx = 0;
+        unsigned FilteredIdx = 0;
         for (const auto &CallArg : CLI.CB->args()) {
           if (CallArg->getType()->isEmptyTy())
             continue;
-          if (Idx == CallArgIdx) {
+          if (FilteredIdx == CallArgIdx) {
             FormalArg = dyn_cast<Argument>(CallArg);
             break;
           }
-          ++Idx;
+          ++FilteredIdx;
+        }
+
+        // For forwarded args, getArgNo() gives the unfiltered index directly.
+        // For computed args, walk the caller's formals to resolve it.
+        unsigned FormalArgIdx = CallArgIdx;
+        if (FormalArg) {
+          FormalArgIdx = FormalArg->getArgNo();
+        } else {
+          const Function *CallerFn = CLI.CB->getFunction();
+          FilteredIdx = 0;
+          for (const auto &Arg : CallerFn->args()) {
+            if (Arg.getType()->isEmptyTy())
+              continue;
+            if (FilteredIdx == CallArgIdx) {
+              FormalArgIdx = Arg.getArgNo();
+              break;
+            }
+            ++FilteredIdx;
+          }
         }
 
         if (FormalArg) {
-          // The call arg is a forwarded formal parameter. Forward its
-          // incoming indirect pointer directly (zero-copy).
-          ArgValue = RVFI->getIncomingIndirectArg(FormalArg->getArgNo());
+          // Forwarded formal parameter: reuse its incoming indirect pointer.
+          Register VReg = RVFI->getIncomingIndirectArg(FormalArgIdx);
+          ArgValue = DAG.getCopyFromReg(Chain, DL, VReg, PtrVT);
         } else {
-          // The call arg is a computed value. Store it into the incoming
-          // indirect pointer for the same-position formal parameter
-          // (musttail guarantees matching prototypes, so types match).
-          // The pointer survives the tail call since it points to the
-          // caller's caller's frame.
-          SDValue IncomingPtr = RVFI->getIncomingIndirectArg(CallArgIdx);
+          // Computed value: store into the incoming indirect pointer for the
+          // same-position formal parameter (musttail guarantees matching
+          // prototypes, so types match). The pointer survives the tail call
+          // since it points to the caller's caller's frame.
+          Register VReg = RVFI->getIncomingIndirectArg(FormalArgIdx);
+          SDValue IncomingPtr = DAG.getCopyFromReg(Chain, DL, VReg, PtrVT);
           MemOpChains.push_back(DAG.getStore(Chain, DL, ArgValue, IncomingPtr,
                                              MachinePointerInfo()));
           // Store any split parts at their respective offsets.
diff --git a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
index f0e37e3940258..3e369899ccbe2 100644
--- a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h
@@ -67,9 +67,12 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
   /// Size of stack frame to save callee saved registers
   unsigned CalleeSavedStackSize = 0;
 
-  /// Incoming indirect argument pointers, keyed by OrigArgIndex.
-  /// Used for musttail forwarding of indirect args.
-  DenseMap<unsigned, SDValue> IncomingIndirectArgs;
+  /// Incoming indirect argument pointers saved as virtual registers, keyed by
+  /// formal parameter index. Used for musttail forwarding of indirect args.
+  /// Virtual registers (not SDValues) are used because the SelectionDAG is
+  /// cleared between basic blocks, and musttail calls may be in non-entry
+  /// blocks.
+  DenseMap<unsigned, Register> IncomingIndirectArgs;
 
   /// Is there any vector argument or return?
   bool IsVectorCall = false;
@@ -148,10 +151,10 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
   unsigned getCalleeSavedStackSize() const { return CalleeSavedStackSize; }
   void setCalleeSavedStackSize(unsigned Size) { CalleeSavedStackSize = Size; }
 
-  void setIncomingIndirectArg(unsigned ArgIndex, SDValue Val) {
-    IncomingIndirectArgs[ArgIndex] = Val;
+  void setIncomingIndirectArg(unsigned ArgIndex, Register Reg) {
+    IncomingIndirectArgs[ArgIndex] = Reg;
   }
-  SDValue getIncomingIndirectArg(unsigned ArgIndex) const {
+  Register getIncomingIndirectArg(unsigned ArgIndex) const {
     auto It = IncomingIndirectArgs.find(ArgIndex);
     assert(It != IncomingIndirectArgs.end() && "No incoming indirect arg");
     return It->second;
diff --git a/llvm/test/CodeGen/RISCV/musttail-call.ll b/llvm/test/CodeGen/RISCV/musttail-call.ll
index a3ac3560378db..ee58a1943c655 100644
--- a/llvm/test/CodeGen/RISCV/musttail-call.ll
+++ b/llvm/test/CodeGen/RISCV/musttail-call.ll
@@ -1,21 +1,17 @@
-; Check that we error out if tail is not possible but call is marked as mustail.
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; Check that musttail with sret generates a tail call (not an error).
 
-; RUN: not --crash llc -mtriple riscv32-unknown-linux-gnu -o - %s \
-; RUN: 2>&1 | FileCheck %s
-; RUN: not --crash llc -mtriple riscv32-unknown-elf -o - %s \
-; RUN: 2>&1 | FileCheck %s
-; RUN: not --crash llc -mtriple riscv64-unknown-linux-gnu -o - %s \
-; RUN: 2>&1 | FileCheck %s
-; RUN: not --crash llc -mtriple riscv64-unknown-elf -o - %s \
-; RUN: 2>&1 | FileCheck %s
+; RUN: llc -mtriple riscv32-unknown-linux-gnu -o - %s | FileCheck %s
+; RUN: llc -mtriple riscv64-unknown-linux-gnu -o - %s | FileCheck %s
 
-declare void @callee_musttail()
+%struct.A = type { i32 }
 
-define void @caller_musttail() #0 {
-; CHECK: LLVM ERROR: failed to perform tail call elimination on a call site marked musttail
+declare void @callee_musttail(ptr sret(%struct.A) %a)
+define void @caller_musttail(ptr sret(%struct.A) %a) {
+; CHECK-LABEL: caller_musttail:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    tail callee_musttail
 entry:
-  musttail call void @callee_musttail()
+  musttail call void @callee_musttail(ptr sret(%struct.A) %a)
   ret void
 }
-
-attributes #0 = { "interrupt"="machine" }
diff --git a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
index ec7eaad2b8924..596ae4ad45d35 100644
--- a/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
+++ b/llvm/test/CodeGen/RISCV/musttail-indirect-args.ll
@@ -361,21 +361,21 @@ define i32 @caller_musttail_computed(fp128 %a) nounwind {
 ; RV32-NEXT:    sw ra, 60(sp) # 4-byte Folded Spill
 ; RV32-NEXT:    sw s0, 56(sp) # 4-byte Folded Spill
 ; RV32-NEXT:    mv s0, a0
-; RV32-NEXT:    lw a3, 0(a0)
-; RV32-NEXT:    lw a4, 4(a0)
-; RV32-NEXT:    lw a5, 8(a0)
-; RV32-NEXT:    lw a6, 12(a0)
-; RV32-NEXT:    sw a3, 8(sp)
-; RV32-NEXT:    sw a4, 12(sp)
-; RV32-NEXT:    sw a5, 16(sp)
-; RV32-NEXT:    sw a6, 20(sp)
+; RV32-NEXT:    lw a0, 0(a0)
+; RV32-NEXT:    lw a3, 4(s0)
+; RV32-NEXT:    lw a4, 8(s0)
+; RV32-NEXT:    lw a5, 12(s0)
+; RV32-NEXT:    sw a0, 8(sp)
+; RV32-NEXT:    sw a0, 24(sp)
+; RV32-NEXT:    sw a3, 12(sp)
+; RV32-NEXT:    sw a4, 16(sp)
+; RV32-NEXT:    sw a5, 20(sp)
 ; RV32-NEXT:    addi a0, sp, 40
 ; RV32-NEXT:    addi a1, sp, 24
 ; RV32-NEXT:    addi a2, sp, 8
-; RV32-NEXT:    sw a3, 24(sp)
-; RV32-NEXT:    sw a4, 28(sp)
-; RV32-NEXT:    sw a5, 32(sp)
-; RV32-NEXT:    sw a6, 36(sp)
+; RV32-NEXT:    sw a3, 28(sp)
+; RV32-NEXT:    sw a4, 32(sp)
+; RV32-NEXT:    sw a5, 36(sp)
 ; RV32-NEXT:    call __addtf3
 ; RV32-NEXT:    lw a0, 40(sp)
 ; RV32-NEXT:    lw a1, 44(sp)
@@ -452,25 +452,25 @@ define i32 @caller_musttail_computed_and_forwarded(fp128 %a, fp128 %b) nounwind
 ; RV32-NEXT:    sw s1, 52(sp) # 4-byte Folded Spill
 ; RV32-NEXT:    mv s0, a1
 ; RV32-NEXT:    mv s1, a0
-; RV32-NEXT:    lw a3, 0(a0)
-; RV32-NEXT:    lw a4, 4(a0)
-; RV32-NEXT:    lw a5, 8(a0)
-; RV32-NEXT:    lw a6, 12(a0)
-; RV32-NEXT:    lw a0, 0(a1)
-; RV32-NEXT:    lw a1, 4(a1)
-; RV32-NEXT:    lw a2, 8(s0)
-; RV32-NEXT:    lw a7, 12(s0)
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    sw a1, 4(sp)
-; RV32-NEXT:    sw a2, 8(sp)
-; RV32-NEXT:    sw a7, 12(sp)
+; RV32-NEXT:    lw a3, 0(a1)
+; RV32-NEXT:    lw a4, 4(a1)
+; RV32-NEXT:    lw a5, 8(a1)
+; RV32-NEXT:    lw a6, 12(a1)
+; RV32-NEXT:    lw a0, 0(a0)
+; RV32-NEXT:    lw a1, 4(s1)
+; RV32-NEXT:    lw a2, 8(s1)
+; RV32-NEXT:    lw a7, 12(s1)
+; RV32-NEXT:    sw a0, 16(sp)
+; RV32-NEXT:    sw a1, 20(sp)
+; RV32-NEXT:    sw a2, 24(sp)
+; RV32-NEXT:    sw a7, 28(sp)
 ; RV32-NEXT:    addi a0, sp, 32
 ; RV32-NEXT:    addi a1, sp, 16
 ; RV32-NEXT:    mv a2, sp
-; RV32-NEXT:    sw a3, 16(sp)
-; RV32-NEXT:    sw a4, 20(sp)
-; RV32-NEXT:    sw a5, 24(sp)
-; RV32-NEXT:    sw a6, 28(sp)
+; RV32-NEXT:    sw a3, 0(sp)
+; RV32-NEXT:    sw a4, 4(sp)
+; RV32-NEXT:    sw a5, 8(sp)
+; RV32-NEXT:    sw a6, 12(sp)
 ; RV32-NEXT:    call __addtf3
 ; RV32-NEXT:    lw a0, 32(sp)
 ; RV32-NEXT:    lw a1, 36(sp)
@@ -520,25 +520,25 @@ define i32 @caller_musttail_forwarded_and_computed(fp128 %a, fp128 %b) nounwind
 ; RV32-NEXT:    sw s1, 52(sp) # 4-byte Folded Spill
 ; RV32-NEXT:    mv s0, a1
 ; RV32-NEXT:    mv s1, a0
-; RV32-NEXT:    lw a3, 0(a0)
-; RV32-NEXT:    lw a4, 4(a0)
-; RV32-NEXT:    lw a5, 8(a0)
-; RV32-NEXT:    lw a6, 12(a0)
-; RV32-NEXT:    lw a0, 0(a1)
-; RV32-NEXT:    lw a1, 4(a1)
-; RV32-NEXT:    lw a2, 8(s0)
-; RV32-NEXT:    lw a7, 12(s0)
-; RV32-NEXT:    sw a0, 0(sp)
-; RV32-NEXT:    sw a1, 4(sp)
-; RV32-NEXT:    sw a2, 8(sp)
-; RV32-NEXT:    sw a7, 12(sp)
+; RV32-NEXT:    lw a3, 0(a1)
+; RV32-NEXT:    lw a4, 4(a1)
+; RV32-NEXT:    lw a5, 8(a1)
+; RV32-NEXT:    lw a6, 12(a1)
+; RV32-NEXT:    lw a0, 0(a0)
+; RV32-NEXT:    lw a1, 4(s1)
+; RV32-NEXT:    lw a2, 8(s1)
+; RV32-NEXT:    lw a7, 12(s1)
+; RV32-NEXT:    sw a0, 16(sp)
+; RV32-NEXT:    sw a1, 20(sp)
+; RV32-NEXT:    sw a2, 24(sp)
+; RV32-NEXT:    sw a7, 28(sp)
 ; RV32-NEXT:    addi a0, sp, 32
 ; RV32-NEXT:    addi a1, sp, 16
 ; RV32-NEXT:    mv a2, sp
-; RV32-NEXT:    sw a3, 16(sp)
-; RV32-NEXT:    sw a4, 20(sp)
-; RV32-NEXT:    sw a5, 24(sp)
-; RV32-NEXT:    sw a6, 28(sp)
+; RV32-NEXT:    sw a3, 0(sp)
+; RV32-NEXT:    sw a4, 4(sp)
+; RV32-NEXT:    sw a5, 8(sp)
+; RV32-NEXT:    sw a6, 12(sp)
 ; RV32-NEXT:    call __addtf3
 ; RV32-NEXT:    lw a0, 32(sp)
 ; RV32-NEXT:    lw a1, 36(sp)
@@ -599,25 +599,25 @@ define i32 @caller_musttail_both_computed(fp128 %a, fp128 %b) nounwind {
 ; RV32-NEXT:    sw s11, 108(sp) # 4-byte Folded Spill
 ; RV32-NEXT:    mv s0, a1
 ; RV32-NEXT:    mv s1, a0
-; RV32-NEXT:    lw s2, 0(a0)
-; RV32-NEXT:    lw s3, 4(a0)
-; RV32-NEXT:    lw s4, 8(a0)
-; RV32-NEXT:    lw s5, 12(a0)
-; RV32-NEXT:    lw s6, 0(a1)
-; RV32-NEXT:    lw s7, 4(a1)
-; RV32-NEXT:    lw s8, 8(a1)
-; RV32-NEXT:    lw s9, 12(a1)
-; RV32-NEXT:    sw s6, 56(sp)
-; RV32-NEXT:    sw s7, 60(sp)
-; RV32-NEXT:    sw s8, 64(sp)
-; RV32-NEXT:    sw s9, 68(sp)
+; RV32-NEXT:    lw s2, 0(a1)
+; RV32-NEXT:    lw s3, 4(a1)
+; RV32-NEXT:    lw s4, 8(a1)
+; RV32-NEXT:    lw s5, 12(a1)
+; RV32-NEXT:    lw s6, 0(a0)
+; RV32-NEXT:    lw s7, 4(a0)
+; RV32-NEXT:    lw s8, 8(a0)
+; RV32-NEXT:    lw s9, 12(a0)
+; RV32-NEXT:    sw s6, 72(sp)
+; RV32-NEXT:    sw s7, 76(sp)
+; RV32-NEXT:    sw s8, 80(sp)
+; RV32-NEXT:    sw s9, 84(sp)
 ; RV32-NEXT:    addi a0, sp, 88
 ; RV32-NEXT:    addi a1, sp, 72
 ; RV32-NEXT:    addi a2, sp, 56
-; RV32-NEXT:    sw s2, 72(sp)
-; RV32-NEXT:    sw s3, 76(sp)
-; RV32-NEXT:    sw s4, 80(sp)
-; RV32-NEXT:    sw s5, 84(sp)
+; RV32-NEXT:    sw s2, 56(sp)
+; RV32-NEXT:    sw s3, 60(sp)
+; RV32-NEXT:    sw s4, 64(sp)
+; RV32-NEXT:    sw s5, 68(sp)
 ; RV32-NEXT:    call __addtf3
 ; RV32-NEXT:    lw s10, 88(sp)
 ; RV32-NEXT:    lw s11, 92(sp)
@@ -625,17 +625,17 @@ define i32 @caller_musttail_both_computed(fp128 %a, fp128 %b) nounwind {
 ; RV32-NEXT:    sw a0, 4(sp) # 4-byte Folded Spill
 ; RV32-NEXT:    lw a0, 100(sp)
 ; RV32-NEXT:    sw a0, 0(sp) # 4-byte Folded Spill
-; RV32-NEXT:    sw s6, 8(sp)
-; RV32-NEXT:    sw s7, 12(sp)
-; RV32-NEXT:    sw s8, 16(sp)
-; RV32-NEXT:    sw s9, 20(sp)
+; RV32-NEXT:    sw s6, 24(sp)
+; RV32-NEXT:    sw s7, 28(sp)
+; RV32-NEXT:    sw s8, 32(sp)
+; RV32-NEXT:    sw s9, 36(sp)
 ; RV32-NEXT:    addi a0, sp, 40
 ; RV32-NEXT:    addi a1, sp, 24
 ; RV32-NEXT:    addi a2, sp, 8
-; RV32-NEXT:    sw s2, 24(sp)
-; RV32-NEXT:    sw s3, 28(sp)
-; RV32-NEXT:    sw s4, 32(sp)
-; RV32-NEXT:    sw s5, 36(sp)
+; RV32-NEXT:    sw s2, 8(sp)
+; RV32-NEXT:    sw s3, 12(sp)
+; RV32-NEXT:    sw s4, 16(sp)
+; RV32-NEXT:    sw s5, 20(sp)
 ; RV32-NEXT:    call __subtf3
 ; RV32-NEXT:    lw a0, 40(sp)
 ; RV32-NEXT:    lw a1, 44(sp)
@@ -709,3 +709,130 @@ define i32 @caller_musttail_both_computed(fp128 %a, fp128 %b) nounwind {
   %r = musttail call i32 @callee_musttail_two_indirect(fp128 %sum, fp128 %diff)
   ret i32 %r
 }
+
+; Test musttail in a non-entry basic block. The indirect pointer must survive
+; across basic blocks (the SelectionDAG is cleared between BBs, so the pointer
+; must be preserved in a virtual register, not as a raw SDValue).
+declare i32 @callee_musttail_cross_bb(fp128 %a, i1 %c)
+
+define i32 @caller_musttail_cross_bb(fp128 %a, i1 %cond) nounwind {
+; RV32-LABEL: caller_musttail_cross_bb:
+; RV32:       # %bb.0: # %entry
+; RV32-NEXT:    andi a2, a1, 1
+; RV32-NEXT:    beqz a2, .LBB19_2
+; RV32-NEXT:  # %bb.1: # %then
+; RV32-NEXT:    tail callee_musttail_cross_bb
+; RV32-NEXT:  .LBB19_2: # %else
+; RV32-NEXT:    li a0, 0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: caller_musttail_cross_bb:
+; RV64:       # %bb.0: # %entry
+; RV64-NEXT:    andi a3, a2, 1
+; RV64-NEXT:    beqz a3, .LBB19_2
+; RV64-NEXT:  # %bb.1: # %then
+; RV64-NEXT:    tail callee_musttail_cross_bb
+; RV64-NEXT:  .LBB19_2: # %else
+; RV64-NEXT:    li a0, 0
+; RV64-NEXT:    ret
+entry:
+  br i1 %cond, label %then, label %else
+then:
+  %r = musttail call i32 @callee_musttail_cross_bb(fp128 %a, i1 %cond)
+  ret i32 %r
+else:
+  ret i32 0
+}
+
+; Test musttail with control flow and a computed indirect arg in a non-entry BB.
+declare i32 @callee_musttail_cross_bb_computed(fp128 %a, i1 %c)
+
+define i32 @caller_musttail_cross_bb_computed(fp128 %a, i1 %cond) nounwind {
+; RV32-LABEL: caller_musttail_cross_bb_computed:
+; RV32:       # %bb.0: # %entry
+; RV32-NEXT:    addi sp, sp, -64
+; RV32-NEXT:    sw ra, 60(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s0, 56(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s1, 52(sp) # 4-byte Folded Spill
+; RV32-NEXT:    sw s2, 48(sp) # 4-byte Folded Spill
+; RV32-NEXT:    mv s0, a0
+; RV32-NEXT:    lw a0, 0(a0)
+; RV32-NEXT:    lw a3, 4(s0)
+; RV32-NEXT:    lw a4, 8(s0)
+; RV32-NEXT:    lw a5, 12(s0)
+; RV32-NEXT:    mv s1, a1
+; RV32-NEXT:    andi s2, a1, 1
+; RV32-NEXT:    sw a0, 0(sp)
+; RV32-NEXT:    sw a0, 16(sp)
+; RV32-NEXT:    sw a3, 4(sp)
+; RV32-NEXT:    sw a4, 8(sp)
+; RV32-NEXT:    sw a5, 12(sp)
+; RV32-NEXT:    addi a0, sp, 32
+; RV32-NEXT:    addi a1, sp, 16
+; RV32-NEXT:    mv a2, sp
+; RV32-NEXT:    sw a3, 20(sp)
+; RV32-NEXT:    sw a4, 24(sp)
+; RV32-NEXT:    sw a5, 28(sp)
+; RV32-NEXT:    call __addtf3
+; RV32-NEXT:    beqz s2, .LBB20_2
+; RV32-NEXT:  # %bb.1: # %then
+; RV32-NEXT:    lw a0, 32(sp)
+; RV32-NEXT:    lw a1, 36(sp)
+; RV32-NEXT:    lw a2, 40(sp)
+; RV32-NEXT:    lw a3, 44(sp)
+; RV32-NEXT:    sw a0, 0(s0)
+; RV32-NEXT:    sw a1, 4(s0)
+; RV32-NEXT:    sw a2, 8(s0)
+; RV32-NEXT:    sw a3, 12(s0)
+; RV32-NEXT:    mv a0, s0
+; RV32-NEXT:    mv a1, s1
+; RV32-NEXT:    lw ra, 60(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s0, 56(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s1, 52(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s2, 48(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 64
+; RV32-NEXT:    tail callee_musttail_cross_bb_computed
+; RV32-NEXT:  .LBB20_2: # %else
+; RV32-NEXT:    li a0, 0
+; RV32-NEXT:    lw ra, 60(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s0, 56(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s1, 52(sp) # 4-byte Folded Reload
+; RV32-NEXT:    lw s2, 48(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 64
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: caller_musttail_cross_bb_computed:
+; RV64:       # %bb.0: # %entry
+; RV64-NEXT:    addi sp, sp, -32
+; RV64-NEXT:    sd ra, 24(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s0, 16(sp) # 8-byte Folded Spill
+; RV64-NEXT:    sd s1, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT:    mv s0, a2
+; RV64-NEXT:    andi s1, a2, 1
+; RV64-NEXT:    mv a2, a0
+; RV64-NEXT:    mv a3, a1
+; RV64-NEXT:    call __addtf3
+; RV64-NEXT:    beqz s1, .LBB20_2
+; RV64-NEXT:  # %bb.1: # %then
+; RV64-NEXT:    mv a2, s0
+; RV64-NEXT:    ld ra, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s0, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 32
+; RV64-NEXT:    tail callee_musttail_cross_bb_computed
+; RV64-NEXT:  .LBB20_2: # %else
+; RV64-NEXT:    li a0, 0
+; RV64-NEXT:    ld ra, 24(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s0, 16(sp) # 8-byte Folded Reload
+; RV64-NEXT:    ld s1, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT:    addi sp, sp, 32
+; RV64-NEXT:    ret
+entry:
+  %sum = fadd fp128 %a, %a
+  br i1 %cond, label %then, label %else
+then:
+  %r = musttail call i32 @callee_musttail_cross_bb_computed(fp128 %sum, i1 %cond)
+  ret i32 %r
+else:
+  ret i32 0
+}



More information about the llvm-commits mailing list