[llvm] [RISCV] Support tail calls for functions with sret parameters (PR #223999)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 23 18:21:53 PDT 2026
https://github.com/renndong updated https://github.com/llvm/llvm-project/pull/223999
>From 321b04323e310ec0f605851f1ade4bf4e948c6cc Mon Sep 17 00:00:00 2001
From: Mingliang Liu <liumingliang.dev at bytedance.com>
Date: Wed, 16 Sep 2026 19:50:01 +0800
Subject: [PATCH 1/6] [RISCV] Add tail call test
---
llvm/test/CodeGen/RISCV/tail-calls-sret.ll | 192 +++++++++++++++++++++
1 file changed, 192 insertions(+)
create mode 100644 llvm/test/CodeGen/RISCV/tail-calls-sret.ll
diff --git a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
new file mode 100644
index 0000000000000..f09c2fdf6d2ad
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
@@ -0,0 +1,192 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV32
+; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64
+
+%struct.Buffer = type { [7 x i64] }
+
+declare void @forward_sret(ptr sret(%struct.Buffer), i64)
+declare void @use_pointer(ptr)
+declare void @use_as_second_arg(i32, ptr)
+
+; Matching caller and callee sret semantics can reuse the incoming
+; return buffer.
+define void @forward_result(ptr noalias sret(%struct.Buffer) %result, i64 %tag) {
+; RV32-LABEL: forward_result:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: addi sp, sp, -16
+; RV32-NEXT: .cfi_def_cfa_offset 16
+; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
+; RV32-NEXT: .cfi_offset ra, -4
+; RV32-NEXT: call forward_sret
+; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
+; RV32-NEXT: .cfi_restore ra
+; RV32-NEXT: addi sp, sp, 16
+; RV32-NEXT: .cfi_def_cfa_offset 0
+; RV32-NEXT: ret
+;
+; RV64-LABEL: forward_result:
+; RV64: # %bb.0: # %entry
+; RV64-NEXT: addi sp, sp, -16
+; RV64-NEXT: .cfi_def_cfa_offset 16
+; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT: .cfi_offset ra, -8
+; RV64-NEXT: call forward_sret
+; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT: .cfi_restore ra
+; RV64-NEXT: addi sp, sp, 16
+; RV64-NEXT: .cfi_def_cfa_offset 0
+; RV64-NEXT: ret
+entry:
+ tail call void @forward_sret(ptr sret(%struct.Buffer) %result, i64 %tag)
+ ret void
+}
+
+; A caller's sret pointer does not need to be forwarded to a callee without
+; sret semantics.
+define void @caller_sret_unused(ptr noalias sret(%struct.Buffer) %result, ptr %other) {
+; RV32-LABEL: caller_sret_unused:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: addi sp, sp, -16
+; RV32-NEXT: .cfi_def_cfa_offset 16
+; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
+; RV32-NEXT: .cfi_offset ra, -4
+; RV32-NEXT: mv a0, a1
+; RV32-NEXT: call use_pointer
+; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
+; RV32-NEXT: .cfi_restore ra
+; RV32-NEXT: addi sp, sp, 16
+; RV32-NEXT: .cfi_def_cfa_offset 0
+; RV32-NEXT: ret
+;
+; RV64-LABEL: caller_sret_unused:
+; RV64: # %bb.0: # %entry
+; RV64-NEXT: addi sp, sp, -16
+; RV64-NEXT: .cfi_def_cfa_offset 16
+; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT: .cfi_offset ra, -8
+; RV64-NEXT: mv a0, a1
+; RV64-NEXT: call use_pointer
+; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT: .cfi_restore ra
+; RV64-NEXT: addi sp, sp, 16
+; RV64-NEXT: .cfi_def_cfa_offset 0
+; RV64-NEXT: ret
+entry:
+ tail call void @use_pointer(ptr %other)
+ ret void
+}
+
+; The caller's sret pointer can also be passed as an ordinary non-first
+; argument.
+define void @caller_sret_as_second_arg(ptr noalias sret(%struct.Buffer) %result, i32 %tag) {
+; RV32-LABEL: caller_sret_as_second_arg:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: addi sp, sp, -16
+; RV32-NEXT: .cfi_def_cfa_offset 16
+; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
+; RV32-NEXT: .cfi_offset ra, -4
+; RV32-NEXT: mv a2, a0
+; RV32-NEXT: mv a0, a1
+; RV32-NEXT: mv a1, a2
+; RV32-NEXT: call use_as_second_arg
+; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
+; RV32-NEXT: .cfi_restore ra
+; RV32-NEXT: addi sp, sp, 16
+; RV32-NEXT: .cfi_def_cfa_offset 0
+; RV32-NEXT: ret
+;
+; RV64-LABEL: caller_sret_as_second_arg:
+; RV64: # %bb.0: # %entry
+; RV64-NEXT: addi sp, sp, -16
+; RV64-NEXT: .cfi_def_cfa_offset 16
+; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT: .cfi_offset ra, -8
+; RV64-NEXT: mv a2, a0
+; RV64-NEXT: mv a0, a1
+; RV64-NEXT: mv a1, a2
+; RV64-NEXT: call use_as_second_arg
+; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT: .cfi_restore ra
+; RV64-NEXT: addi sp, sp, 16
+; RV64-NEXT: .cfi_def_cfa_offset 0
+; RV64-NEXT: ret
+entry:
+ tail call void @use_as_second_arg(i32 %tag, ptr %result)
+ ret void
+}
+
+; Do not tail call when caller and callee use different sret buffers.
+define void @sret_not_forwarded(ptr noalias sret(%struct.Buffer) %result, ptr %other, i64 %tag) {
+; RV32-LABEL: sret_not_forwarded:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: addi sp, sp, -16
+; RV32-NEXT: .cfi_def_cfa_offset 16
+; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
+; RV32-NEXT: .cfi_offset ra, -4
+; RV32-NEXT: mv a0, a1
+; RV32-NEXT: mv a1, a2
+; RV32-NEXT: mv a2, a3
+; RV32-NEXT: call forward_sret
+; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
+; RV32-NEXT: .cfi_restore ra
+; RV32-NEXT: addi sp, sp, 16
+; RV32-NEXT: .cfi_def_cfa_offset 0
+; RV32-NEXT: ret
+;
+; RV64-LABEL: sret_not_forwarded:
+; RV64: # %bb.0: # %entry
+; RV64-NEXT: addi sp, sp, -16
+; RV64-NEXT: .cfi_def_cfa_offset 16
+; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
+; RV64-NEXT: .cfi_offset ra, -8
+; RV64-NEXT: mv a0, a1
+; RV64-NEXT: mv a1, a2
+; RV64-NEXT: call forward_sret
+; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
+; RV64-NEXT: .cfi_restore ra
+; RV64-NEXT: addi sp, sp, 16
+; RV64-NEXT: .cfi_def_cfa_offset 0
+; RV64-NEXT: ret
+entry:
+ tail call void @forward_sret(ptr sret(%struct.Buffer) %other, i64 %tag)
+ ret void
+}
+
+; Do not tail call when the callee's sret pointer refers to the caller's local
+; stack frame.
+define void @local_sret_buffer(i64 %tag) {
+; RV32-LABEL: local_sret_buffer:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: addi sp, sp, -64
+; RV32-NEXT: .cfi_def_cfa_offset 64
+; RV32-NEXT: sw ra, 60(sp) # 4-byte Folded Spill
+; RV32-NEXT: .cfi_offset ra, -4
+; RV32-NEXT: mv a2, a1
+; RV32-NEXT: mv a1, a0
+; RV32-NEXT: mv a0, sp
+; RV32-NEXT: call forward_sret
+; RV32-NEXT: lw ra, 60(sp) # 4-byte Folded Reload
+; RV32-NEXT: .cfi_restore ra
+; RV32-NEXT: addi sp, sp, 64
+; RV32-NEXT: .cfi_def_cfa_offset 0
+; RV32-NEXT: ret
+;
+; RV64-LABEL: local_sret_buffer:
+; RV64: # %bb.0: # %entry
+; RV64-NEXT: addi sp, sp, -64
+; RV64-NEXT: .cfi_def_cfa_offset 64
+; RV64-NEXT: sd ra, 56(sp) # 8-byte Folded Spill
+; RV64-NEXT: .cfi_offset ra, -8
+; RV64-NEXT: mv a1, a0
+; RV64-NEXT: mv a0, sp
+; RV64-NEXT: call forward_sret
+; RV64-NEXT: ld ra, 56(sp) # 8-byte Folded Reload
+; RV64-NEXT: .cfi_restore ra
+; RV64-NEXT: addi sp, sp, 64
+; RV64-NEXT: .cfi_def_cfa_offset 0
+; RV64-NEXT: ret
+entry:
+ %local = alloca %struct.Buffer, align 8
+ tail call void @forward_sret(ptr sret(%struct.Buffer) %local, i64 %tag)
+ ret void
+}
>From 6550d780391a15dd722d610392dd8c147a109e55 Mon Sep 17 00:00:00 2001
From: Mingliang Liu <liumingliang.dev at bytedance.com>
Date: Wed, 16 Sep 2026 20:06:57 +0800
Subject: [PATCH 2/6] [RISCV] Support tail calls with sret parameters
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 21 ++++--
llvm/test/CodeGen/RISCV/tail-calls-sret.ll | 81 ++++++---------------
llvm/test/CodeGen/RISCV/tail-calls.ll | 37 ++--------
3 files changed, 43 insertions(+), 96 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index d05e1f85afb69..75c96b704b45e 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -27587,12 +27587,23 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
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();
+ // If the callee has an sret parameter, conservatively require it to receive
+ // the caller's sret pointer. If only the caller has an sret parameter, treat
+ // that pointer like an ordinary pointer when passing call arguments.
+ // TODO: Support other sret buffers that outlive the caller, such as globals.
+ auto IsCallerStructRet =
+ !Caller.arg_empty() && Caller.getArg(0)->hasStructRetAttr();
auto IsCalleeStructRet = Outs.empty() ? false : Outs[0].Flags.isSRet();
- if (IsCallerStructRet || IsCalleeStructRet)
- return false;
+ if (IsCalleeStructRet) {
+ // Do not allow the tail call if the caller has no sret parameter.
+ if (!IsCallerStructRet)
+ return false;
+ // RISC-V passes the sret pointer as the first argument in a0. Require the
+ // callee's sret argument to be the caller's incoming sret pointer.
+ if (!CLI.CB || CLI.CB->arg_empty() ||
+ CLI.CB->getArgOperand(0) != Caller.getArg(0))
+ return false;
+ }
// The callee has to preserve all registers the caller needs to preserve.
const RISCVRegisterInfo *TRI = Subtarget.getRegisterInfo();
diff --git a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
index f09c2fdf6d2ad..8256916f3c293 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
@@ -8,34 +8,31 @@ declare void @forward_sret(ptr sret(%struct.Buffer), i64)
declare void @use_pointer(ptr)
declare void @use_as_second_arg(i32, ptr)
+; The caller's sret pointer can be passed as an ordinary first argument, such
+; as the `this` pointer of a C++ constructor.
+define void @caller_sret_as_first_arg(ptr noalias sret(%struct.Buffer) %result) {
+; RV32-LABEL: caller_sret_as_first_arg:
+; RV32: # %bb.0: # %entry
+; RV32-NEXT: tail use_pointer
+;
+; RV64-LABEL: caller_sret_as_first_arg:
+; RV64: # %bb.0: # %entry
+; RV64-NEXT: tail use_pointer
+entry:
+ tail call void @use_pointer(ptr %result)
+ ret void
+}
+
; Matching caller and callee sret semantics can reuse the incoming
; return buffer.
define void @forward_result(ptr noalias sret(%struct.Buffer) %result, i64 %tag) {
; RV32-LABEL: forward_result:
; RV32: # %bb.0: # %entry
-; RV32-NEXT: addi sp, sp, -16
-; RV32-NEXT: .cfi_def_cfa_offset 16
-; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT: .cfi_offset ra, -4
-; RV32-NEXT: call forward_sret
-; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT: .cfi_restore ra
-; RV32-NEXT: addi sp, sp, 16
-; RV32-NEXT: .cfi_def_cfa_offset 0
-; RV32-NEXT: ret
+; RV32-NEXT: tail forward_sret
;
; RV64-LABEL: forward_result:
; RV64: # %bb.0: # %entry
-; RV64-NEXT: addi sp, sp, -16
-; RV64-NEXT: .cfi_def_cfa_offset 16
-; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
-; RV64-NEXT: .cfi_offset ra, -8
-; RV64-NEXT: call forward_sret
-; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
-; RV64-NEXT: .cfi_restore ra
-; RV64-NEXT: addi sp, sp, 16
-; RV64-NEXT: .cfi_def_cfa_offset 0
-; RV64-NEXT: ret
+; RV64-NEXT: tail forward_sret
entry:
tail call void @forward_sret(ptr sret(%struct.Buffer) %result, i64 %tag)
ret void
@@ -46,31 +43,13 @@ entry:
define void @caller_sret_unused(ptr noalias sret(%struct.Buffer) %result, ptr %other) {
; RV32-LABEL: caller_sret_unused:
; RV32: # %bb.0: # %entry
-; RV32-NEXT: addi sp, sp, -16
-; RV32-NEXT: .cfi_def_cfa_offset 16
-; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT: .cfi_offset ra, -4
; RV32-NEXT: mv a0, a1
-; RV32-NEXT: call use_pointer
-; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT: .cfi_restore ra
-; RV32-NEXT: addi sp, sp, 16
-; RV32-NEXT: .cfi_def_cfa_offset 0
-; RV32-NEXT: ret
+; RV32-NEXT: tail use_pointer
;
; RV64-LABEL: caller_sret_unused:
; RV64: # %bb.0: # %entry
-; RV64-NEXT: addi sp, sp, -16
-; RV64-NEXT: .cfi_def_cfa_offset 16
-; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
-; RV64-NEXT: .cfi_offset ra, -8
; RV64-NEXT: mv a0, a1
-; RV64-NEXT: call use_pointer
-; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
-; RV64-NEXT: .cfi_restore ra
-; RV64-NEXT: addi sp, sp, 16
-; RV64-NEXT: .cfi_def_cfa_offset 0
-; RV64-NEXT: ret
+; RV64-NEXT: tail use_pointer
entry:
tail call void @use_pointer(ptr %other)
ret void
@@ -81,35 +60,17 @@ entry:
define void @caller_sret_as_second_arg(ptr noalias sret(%struct.Buffer) %result, i32 %tag) {
; RV32-LABEL: caller_sret_as_second_arg:
; RV32: # %bb.0: # %entry
-; RV32-NEXT: addi sp, sp, -16
-; RV32-NEXT: .cfi_def_cfa_offset 16
-; RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT: .cfi_offset ra, -4
; RV32-NEXT: mv a2, a0
; RV32-NEXT: mv a0, a1
; RV32-NEXT: mv a1, a2
-; RV32-NEXT: call use_as_second_arg
-; RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT: .cfi_restore ra
-; RV32-NEXT: addi sp, sp, 16
-; RV32-NEXT: .cfi_def_cfa_offset 0
-; RV32-NEXT: ret
+; RV32-NEXT: tail use_as_second_arg
;
; RV64-LABEL: caller_sret_as_second_arg:
; RV64: # %bb.0: # %entry
-; RV64-NEXT: addi sp, sp, -16
-; RV64-NEXT: .cfi_def_cfa_offset 16
-; RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
-; RV64-NEXT: .cfi_offset ra, -8
; RV64-NEXT: mv a2, a0
; RV64-NEXT: mv a0, a1
; RV64-NEXT: mv a1, a2
-; RV64-NEXT: call use_as_second_arg
-; RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
-; RV64-NEXT: .cfi_restore ra
-; RV64-NEXT: addi sp, sp, 16
-; RV64-NEXT: .cfi_def_cfa_offset 0
-; RV64-NEXT: ret
+; RV64-NEXT: tail use_as_second_arg
entry:
tail call void @use_as_second_arg(i32 %tag, ptr %result)
ret void
diff --git a/llvm/test/CodeGen/RISCV/tail-calls.ll b/llvm/test/CodeGen/RISCV/tail-calls.ll
index e43acfa56f3fd..bd12a9498d22e 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls.ll
@@ -1110,63 +1110,38 @@ entry:
ret void
}
-; Do not tail call optimize if caller uses structret semantics.
+; A caller using structret semantics does not prevent tail call optimization.
declare void @callee_nostruct()
define void @caller_struct(ptr sret(%struct.A) %a) nounwind {
; CHECK-LABEL: caller_struct:
; CHECK: # %bb.0: # %entry
-; CHECK-NEXT: addi sp, sp, -16
-; CHECK-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
-; CHECK-NEXT: call callee_nostruct
-; CHECK-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
-; CHECK-NEXT: addi sp, sp, 16
-; CHECK-NEXT: ret
+; CHECK-NEXT: tail callee_nostruct
;
; CHECK-CF-RV32-LABEL: caller_struct:
; CHECK-CF-RV32: # %bb.0: # %entry
; CHECK-CF-RV32-NEXT: lpad 0
-; CHECK-CF-RV32-NEXT: addi sp, sp, -16
-; CHECK-CF-RV32-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
-; CHECK-CF-RV32-NEXT: call callee_nostruct
-; CHECK-CF-RV32-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
-; CHECK-CF-RV32-NEXT: addi sp, sp, 16
-; CHECK-CF-RV32-NEXT: ret
+; CHECK-CF-RV32-NEXT: tail callee_nostruct, t2
;
; CHECK-CF-RV64-LABEL: caller_struct:
; CHECK-CF-RV64: # %bb.0: # %entry
; CHECK-CF-RV64-NEXT: lpad 0
-; CHECK-CF-RV64-NEXT: addi sp, sp, -16
-; CHECK-CF-RV64-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
-; CHECK-CF-RV64-NEXT: call callee_nostruct
-; CHECK-CF-RV64-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
-; CHECK-CF-RV64-NEXT: addi sp, sp, 16
-; CHECK-CF-RV64-NEXT: ret
+; CHECK-CF-RV64-NEXT: tail callee_nostruct, t2
;
; CHECK-CF-RV32-LARGE-LABEL: caller_struct:
; CHECK-CF-RV32-LARGE: # %bb.0: # %entry
; CHECK-CF-RV32-LARGE-NEXT: lpad 0
-; CHECK-CF-RV32-LARGE-NEXT: addi sp, sp, -16
-; CHECK-CF-RV32-LARGE-NEXT: sw ra, 12(sp) # 4-byte Folded Spill
; CHECK-CF-RV32-LARGE-NEXT: .Lpcrel_hi15:
; CHECK-CF-RV32-LARGE-NEXT: auipc a0, %pcrel_hi(.LCPI12_0)
; CHECK-CF-RV32-LARGE-NEXT: lw t2, %pcrel_lo(.Lpcrel_hi15)(a0)
-; CHECK-CF-RV32-LARGE-NEXT: jalr t2
-; CHECK-CF-RV32-LARGE-NEXT: lw ra, 12(sp) # 4-byte Folded Reload
-; CHECK-CF-RV32-LARGE-NEXT: addi sp, sp, 16
-; CHECK-CF-RV32-LARGE-NEXT: ret
+; CHECK-CF-RV32-LARGE-NEXT: jr t2
;
; CHECK-CF-RV64-LARGE-LABEL: caller_struct:
; CHECK-CF-RV64-LARGE: # %bb.0: # %entry
; CHECK-CF-RV64-LARGE-NEXT: lpad 0
-; CHECK-CF-RV64-LARGE-NEXT: addi sp, sp, -16
-; CHECK-CF-RV64-LARGE-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
; CHECK-CF-RV64-LARGE-NEXT: .Lpcrel_hi15:
; CHECK-CF-RV64-LARGE-NEXT: auipc a0, %pcrel_hi(.LCPI12_0)
; CHECK-CF-RV64-LARGE-NEXT: ld t2, %pcrel_lo(.Lpcrel_hi15)(a0)
-; CHECK-CF-RV64-LARGE-NEXT: jalr t2
-; CHECK-CF-RV64-LARGE-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
-; CHECK-CF-RV64-LARGE-NEXT: addi sp, sp, 16
-; CHECK-CF-RV64-LARGE-NEXT: ret
+; CHECK-CF-RV64-LARGE-NEXT: jr t2
entry:
tail call void @callee_nostruct()
ret void
>From 07609d645fd6078f207efd307eaf27476a2eca98 Mon Sep 17 00:00:00 2001
From: Mingliang Liu <liumingliang.dev at bytedance.com>
Date: Wed, 16 Sep 2026 20:48:14 +0800
Subject: [PATCH 3/6] [RISCV] Remove duplicate test
---
llvm/test/CodeGen/RISCV/tail-calls-sret.ll | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
index 8256916f3c293..9d91f5e8f52f5 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
@@ -8,21 +8,6 @@ declare void @forward_sret(ptr sret(%struct.Buffer), i64)
declare void @use_pointer(ptr)
declare void @use_as_second_arg(i32, ptr)
-; The caller's sret pointer can be passed as an ordinary first argument, such
-; as the `this` pointer of a C++ constructor.
-define void @caller_sret_as_first_arg(ptr noalias sret(%struct.Buffer) %result) {
-; RV32-LABEL: caller_sret_as_first_arg:
-; RV32: # %bb.0: # %entry
-; RV32-NEXT: tail use_pointer
-;
-; RV64-LABEL: caller_sret_as_first_arg:
-; RV64: # %bb.0: # %entry
-; RV64-NEXT: tail use_pointer
-entry:
- tail call void @use_pointer(ptr %result)
- ret void
-}
-
; Matching caller and callee sret semantics can reuse the incoming
; return buffer.
define void @forward_result(ptr noalias sret(%struct.Buffer) %result, i64 %tag) {
>From c36278c6e435a170c55e119f5be7c468a37dff79 Mon Sep 17 00:00:00 2001
From: Mingliang Liu <liumingliang.dev at bytedance.com>
Date: Thu, 17 Sep 2026 13:08:55 +0800
Subject: [PATCH 4/6] fixup! don't use auto
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 75c96b704b45e..dc1301ca6eefd 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -27591,9 +27591,9 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
// the caller's sret pointer. If only the caller has an sret parameter, treat
// that pointer like an ordinary pointer when passing call arguments.
// TODO: Support other sret buffers that outlive the caller, such as globals.
- auto IsCallerStructRet =
+ bool IsCallerStructRet =
!Caller.arg_empty() && Caller.getArg(0)->hasStructRetAttr();
- auto IsCalleeStructRet = Outs.empty() ? false : Outs[0].Flags.isSRet();
+ bool IsCalleeStructRet = Outs.empty() ? false : Outs[0].Flags.isSRet();
if (IsCalleeStructRet) {
// Do not allow the tail call if the caller has no sret parameter.
if (!IsCallerStructRet)
>From 062c28ae1b21a5e5d084d4cfb373b9f1c4d10c01 Mon Sep 17 00:00:00 2001
From: Mingliang Liu <liumingliang.dev at bytedance.com>
Date: Tue, 22 Sep 2026 23:37:01 +0800
Subject: [PATCH 5/6] [RISCV] Allow sret tail calls for Microsoft C++ ABI
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 24 ++++++++++++++-------
llvm/test/CodeGen/RISCV/tail-calls-sret.ll | 22 +++++++++++++++++++
2 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index dc1301ca6eefd..f052ed9fb489b 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -27591,17 +27591,25 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
// the caller's sret pointer. If only the caller has an sret parameter, treat
// that pointer like an ordinary pointer when passing call arguments.
// TODO: Support other sret buffers that outlive the caller, such as globals.
- bool IsCallerStructRet =
- !Caller.arg_empty() && Caller.getArg(0)->hasStructRetAttr();
- bool IsCalleeStructRet = Outs.empty() ? false : Outs[0].Flags.isSRet();
+ bool IsCalleeStructRet = llvm::any_of(
+ Outs, [](const ISD::OutputArg &Out) { return Out.Flags.isSRet(); });
if (IsCalleeStructRet) {
// Do not allow the tail call if the caller has no sret parameter.
- if (!IsCallerStructRet)
+ if (!Caller.hasStructRetAttr() || !CLI.CB || CLI.CB->arg_empty())
return false;
- // RISC-V passes the sret pointer as the first argument in a0. Require the
- // callee's sret argument to be the caller's incoming sret pointer.
- if (!CLI.CB || CLI.CB->arg_empty() ||
- CLI.CB->getArgOperand(0) != Caller.getArg(0))
+
+ // RISC-V psABI passes the sret pointer as the first argument. But under
+ // the Microsoft C++ ABI on Windows, the sret pointer is allowed as the
+ // second pointer after `this` pointer.
+ if (Subtarget.getTargetTriple().isKnownWindowsMSVCEnvironment()) {
+ auto *CallerSRetArg = Caller.getArg(0)->hasStructRetAttr()
+ ? Caller.getArg(0)
+ : Caller.getArg(1);
+ for (unsigned Idx = 0; Idx < 2 && Idx < CLI.CB->arg_size(); Idx++)
+ if (CLI.CB->paramHasAttr(Idx, Attribute::StructRet) &&
+ CLI.CB->getArgOperand(Idx) != CallerSRetArg)
+ return false;
+ } else if (CLI.CB->getArgOperand(0) != Caller.getArg(0))
return false;
}
diff --git a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
index 9d91f5e8f52f5..818b67c5d894d 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
@@ -1,12 +1,14 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV32
; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64
+; RUN: llc -mtriple=riscv64-pc-windows-msvc -verify-machineinstrs < %s | FileCheck %s --check-prefix=WIN64
%struct.Buffer = type { [7 x i64] }
declare void @forward_sret(ptr sret(%struct.Buffer), i64)
declare void @use_pointer(ptr)
declare void @use_as_second_arg(i32, ptr)
+declare void @forward_windows_sret(ptr, ptr sret(%struct.Buffer))
; Matching caller and callee sret semantics can reuse the incoming
; return buffer.
@@ -136,3 +138,23 @@ entry:
tail call void @forward_sret(ptr sret(%struct.Buffer) %local, i64 %tag)
ret void
}
+
+define void @windows_sret_after_this(ptr %this, ptr noalias sret(%struct.Buffer) %result) {
+; WIN64-LABEL: windows_sret_after_this:
+; WIN64: # %bb.0: # %entry
+; WIN64-NEXT: tail forward_windows_sret
+entry:
+ tail call void @forward_windows_sret(ptr %this, ptr sret(%struct.Buffer) %result)
+ ret void
+}
+
+define void @windows_sret(ptr %this, ptr noalias sret(%struct.Buffer) %result, i64 %tag) {
+; WIN64-LABEL: windows_sret:
+; WIN64: # %bb.0: # %entry
+; WIN64-NEXT: mv a0, a1
+; WIN64-NEXT: mv a1, a2
+; WIN64-NEXT: tail forward_sret
+entry:
+ tail call void @forward_sret(ptr sret(%struct.Buffer) %result, i64 %tag)
+ ret void
+}
>From 410df60537ced42fde74e90a837c9f2ebdbb911f Mon Sep 17 00:00:00 2001
From: Mingliang Liu <liumingliang.dev at bytedance.com>
Date: Thu, 24 Sep 2026 01:15:04 +0800
Subject: [PATCH 6/6] [RISCV] Assert that the sret pointer is the first
argument
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 18 ++++++-----------
llvm/test/CodeGen/RISCV/tail-calls-sret.ll | 22 ---------------------
2 files changed, 6 insertions(+), 34 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index f052ed9fb489b..f717c74bf3cbb 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -27598,18 +27598,12 @@ bool RISCVTargetLowering::isEligibleForTailCallOptimization(
if (!Caller.hasStructRetAttr() || !CLI.CB || CLI.CB->arg_empty())
return false;
- // RISC-V psABI passes the sret pointer as the first argument. But under
- // the Microsoft C++ ABI on Windows, the sret pointer is allowed as the
- // second pointer after `this` pointer.
- if (Subtarget.getTargetTriple().isKnownWindowsMSVCEnvironment()) {
- auto *CallerSRetArg = Caller.getArg(0)->hasStructRetAttr()
- ? Caller.getArg(0)
- : Caller.getArg(1);
- for (unsigned Idx = 0; Idx < 2 && Idx < CLI.CB->arg_size(); Idx++)
- if (CLI.CB->paramHasAttr(Idx, Attribute::StructRet) &&
- CLI.CB->getArgOperand(Idx) != CallerSRetArg)
- return false;
- } else if (CLI.CB->getArgOperand(0) != Caller.getArg(0))
+ // RISC-V psABI passes the sret pointer as the first argument. The Microsoft
+ // C++ ABI may instead pass it as the second argument after `this`, but that
+ // ABI is rarely used on RISC-V and is not supported here.
+ assert(Caller.getArg(0)->hasStructRetAttr() && Outs[0].Flags.isSRet() &&
+ "sret pointer must be argument 0");
+ if (CLI.CB->getArgOperand(0) != Caller.getArg(0))
return false;
}
diff --git a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
index 818b67c5d894d..9d91f5e8f52f5 100644
--- a/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
+++ b/llvm/test/CodeGen/RISCV/tail-calls-sret.ll
@@ -1,14 +1,12 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV32
; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64
-; RUN: llc -mtriple=riscv64-pc-windows-msvc -verify-machineinstrs < %s | FileCheck %s --check-prefix=WIN64
%struct.Buffer = type { [7 x i64] }
declare void @forward_sret(ptr sret(%struct.Buffer), i64)
declare void @use_pointer(ptr)
declare void @use_as_second_arg(i32, ptr)
-declare void @forward_windows_sret(ptr, ptr sret(%struct.Buffer))
; Matching caller and callee sret semantics can reuse the incoming
; return buffer.
@@ -138,23 +136,3 @@ entry:
tail call void @forward_sret(ptr sret(%struct.Buffer) %local, i64 %tag)
ret void
}
-
-define void @windows_sret_after_this(ptr %this, ptr noalias sret(%struct.Buffer) %result) {
-; WIN64-LABEL: windows_sret_after_this:
-; WIN64: # %bb.0: # %entry
-; WIN64-NEXT: tail forward_windows_sret
-entry:
- tail call void @forward_windows_sret(ptr %this, ptr sret(%struct.Buffer) %result)
- ret void
-}
-
-define void @windows_sret(ptr %this, ptr noalias sret(%struct.Buffer) %result, i64 %tag) {
-; WIN64-LABEL: windows_sret:
-; WIN64: # %bb.0: # %entry
-; WIN64-NEXT: mv a0, a1
-; WIN64-NEXT: mv a1, a2
-; WIN64-NEXT: tail forward_sret
-entry:
- tail call void @forward_sret(ptr sret(%struct.Buffer) %result, i64 %tag)
- ret void
-}
More information about the llvm-commits
mailing list