Skip to content

[Sparc] Fix 32-bit return for values demoted to sret - #240041

Open
brad0 wants to merge 1 commit into
llvm:mainfrom
brad0:sparc-fp128-demoted-sret
Open

brad0 wants to merge 1 commit into
llvm:mainfrom
brad0:sparc-fp128-demoted-sret

Conversation

@brad0

@brad0 brad0 commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

On 32-bit SPARC, a return value that does not fit in the return
registers (e.g. fp128, or a struct larger than six i32s) is demoted
to a hidden sret argument by the generic call lowering. The caller
side keys off the sret argument flags and correctly emits the
struct-return sequence with an unimp after the call. The callee side,
however, only checked hasStructRetAttr() on the IR function, which
is false for a demoted return. As a result the callee neither copied
the sret pointer to %i0 nor returned past the unimp, so control
landed on the unimp in the caller and trapped.

Check the lowered sret argument / SRetReturnReg instead of the IR
attribute so demoted returns are handled the same way as an
explicit sret parameter.

IsEligibleForTailCallOptimization() had the same problem: a caller
whose return value was demoted to sret has no sret attribute, and the
check was skipped entirely when the callee has no arguments. In both
cases a struct-returning function could tail call a non-sret function,
which then returns to %o7+8 and lands on the unimp. Treat the caller
as struct-returning if it has an SRetReturnReg, and compare against
the callee even when it has no arguments.

Fixes #232963

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-sparc

Author: Brad Smith (brad0)

Changes

On 32-bit SPARC, a return value that does not fit in the return
registers (e.g. fp128, or a struct larger than six i32s) is demoted
to a hidden sret argument by the generic call lowering. The caller
side keys off the sret argument flags and correctly emits the
struct-return sequence with an unimp after the call. The callee side,
however, only checked hasStructRetAttr() on the IR function, which
is false for a demoted return. As a result the callee neither copied
the sret pointer to %i0 nor returned past the unimp, so control
landed on the unimp in the caller and trapped.

Check the lowered sret argument / SRetReturnReg instead of the IR
attribute so demoted returns are handled the same way as an
explicit sret parameter.

Fixes #232963


Patch is 24.29 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/240041.diff

7 Files Affected:

  • (modified) llvm/lib/Target/Sparc/SparcISelLowering.cpp (+11-8)
  • (modified) llvm/test/CodeGen/SPARC/fmuladd-soft-float.ll (+27-27)
  • (modified) llvm/test/CodeGen/SPARC/fp128-abi.ll (+3-3)
  • (modified) llvm/test/CodeGen/SPARC/fp128-strict.ll (+22-22)
  • (modified) llvm/test/CodeGen/SPARC/half.ll (+2-2)
  • (modified) llvm/test/CodeGen/SPARC/llvm.sincos.ll (+36-36)
  • (added) llvm/test/CodeGen/SPARC/sret-demoted.ll (+138)
diff --git a/llvm/lib/Target/Sparc/SparcISelLowering.cpp b/llvm/lib/Target/Sparc/SparcISelLowering.cpp
index 7821adaefba93b..47ef77c8838efd 100644
--- a/llvm/lib/Target/Sparc/SparcISelLowering.cpp
+++ b/llvm/lib/Target/Sparc/SparcISelLowering.cpp
@@ -317,12 +317,11 @@ SparcTargetLowering::LowerReturn_32(SDValue Chain, CallingConv::ID CallConv,
   }
 
   unsigned RetAddrOffset = 8; // Call Inst + Delay Slot
-  // If the function returns a struct, copy the SRetReturnReg to I0
-  if (MF.getFunction().hasStructRetAttr()) {
-    SparcMachineFunctionInfo *SFI = MF.getInfo<SparcMachineFunctionInfo>();
-    Register Reg = SFI->getSRetReturnReg();
-    if (!Reg)
-      llvm_unreachable("sret virtual register not created in the entry block");
+  // If the function returns a struct, copy the SRetReturnReg to I0. This
+  // covers both an explicit sret parameter and a return value that was
+  // demoted to sret because it does not fit in the return registers.
+  SparcMachineFunctionInfo *SFI = MF.getInfo<SparcMachineFunctionInfo>();
+  if (Register Reg = SFI->getSRetReturnReg()) {
     auto PtrVT = getPointerTy(DAG.getDataLayout());
     SDValue Val = DAG.getCopyFromReg(Chain, DL, Reg, PtrVT);
     Chain = DAG.getCopyToReg(Chain, DL, SP::I0, Val, Glue);
@@ -332,7 +331,9 @@ SparcTargetLowering::LowerReturn_32(SDValue Chain, CallingConv::ID CallConv,
     // A zero-sized return value, e.g. an empty struct or union, is returned
     // without an unimp instruction after the call, so there is nothing for the
     // return to skip over.
-    Type *RetType = MF.getFunction().getParamStructRetType(0);
+    const Function &F = MF.getFunction();
+    Type *RetType =
+        F.hasStructRetAttr() ? F.getParamStructRetType(0) : F.getReturnType();
     if (!RetType->isEmptyTy())
       RetAddrOffset = 12; // CallInst + Delay Slot + Unimp
   }
@@ -591,7 +592,9 @@ SDValue SparcTargetLowering::LowerFormalArguments_32(
     }
   }
 
-  if (MF.getFunction().hasStructRetAttr()) {
+  // Ins[0] is an sret argument both for an explicit sret parameter and for a
+  // return value that was demoted to sret.
+  if (!Ins.empty() && Ins[0].Flags.isSRet()) {
     // Copy the SRet Argument to SRetReturnReg.
     SparcMachineFunctionInfo *SFI = MF.getInfo<SparcMachineFunctionInfo>();
     Register Reg = SFI->getSRetReturnReg();
diff --git a/llvm/test/CodeGen/SPARC/fmuladd-soft-float.ll b/llvm/test/CodeGen/SPARC/fmuladd-soft-float.ll
index 54bd83f10586cd..f222afd5cfb1ea 100644
--- a/llvm/test/CodeGen/SPARC/fmuladd-soft-float.ll
+++ b/llvm/test/CodeGen/SPARC/fmuladd-soft-float.ll
@@ -250,7 +250,7 @@ define <4 x double> @fmuladd_contract_v4f64(<4 x double> %a, <4 x double> %b, <4
 ; SOFT-FLOAT-32-NEXT:    .cfi_def_cfa_register %fp
 ; SOFT-FLOAT-32-NEXT:    .cfi_window_save
 ; SOFT-FLOAT-32-NEXT:    .cfi_register %o7, %i7
-; SOFT-FLOAT-32-NEXT:    ld [%fp+64], %l6
+; SOFT-FLOAT-32-NEXT:    ld [%fp+64], %l0
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+156], %g2
 ; SOFT-FLOAT-32-NEXT:    st %g2, [%fp+-4] ! 4-byte Folded Spill
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+160], %g2
@@ -263,15 +263,15 @@ define <4 x double> @fmuladd_contract_v4f64(<4 x double> %a, <4 x double> %b, <4
 ; SOFT-FLOAT-32-NEXT:    st %g2, [%fp+-20] ! 4-byte Folded Spill
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+144], %g2
 ; SOFT-FLOAT-32-NEXT:    st %g2, [%fp+-24] ! 4-byte Folded Spill
-; SOFT-FLOAT-32-NEXT:    ld [%fp+132], %g2
+; SOFT-FLOAT-32-NEXT:    ld [%fp+132], %l7
+; SOFT-FLOAT-32-NEXT:    ld [%fp+136], %g2
 ; SOFT-FLOAT-32-NEXT:    st %g2, [%fp+-28] ! 4-byte Folded Spill
-; SOFT-FLOAT-32-NEXT:    ld [%fp+136], %l7
-; SOFT-FLOAT-32-NEXT:    ld [%fp+100], %l0
-; SOFT-FLOAT-32-NEXT:    ld [%fp+104], %l1
-; SOFT-FLOAT-32-NEXT:    ld [%fp+108], %l2
-; SOFT-FLOAT-32-NEXT:    ld [%fp+112], %l3
-; SOFT-FLOAT-32-NEXT:    ld [%fp+116], %l4
-; SOFT-FLOAT-32-NEXT:    ld [%fp+120], %l5
+; SOFT-FLOAT-32-NEXT:    ld [%fp+100], %l2
+; SOFT-FLOAT-32-NEXT:    ld [%fp+104], %l3
+; SOFT-FLOAT-32-NEXT:    ld [%fp+108], %l4
+; SOFT-FLOAT-32-NEXT:    ld [%fp+112], %l5
+; SOFT-FLOAT-32-NEXT:    ld [%fp+116], %l6
+; SOFT-FLOAT-32-NEXT:    ld [%fp+120], %l1
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+92], %o0
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+96], %o1
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+124], %o2
@@ -281,26 +281,26 @@ define <4 x double> @fmuladd_contract_v4f64(<4 x double> %a, <4 x double> %b, <4
 ; SOFT-FLOAT-32-NEXT:    st %o1, [%fp+-36] ! 4-byte Folded Spill
 ; SOFT-FLOAT-32-NEXT:    mov %i4, %o0
 ; SOFT-FLOAT-32-NEXT:    mov %i5, %o1
-; SOFT-FLOAT-32-NEXT:    mov %l4, %o2
+; SOFT-FLOAT-32-NEXT:    mov %l6, %o2
 ; SOFT-FLOAT-32-NEXT:    call __muldf3
-; SOFT-FLOAT-32-NEXT:    mov %l5, %o3
-; SOFT-FLOAT-32-NEXT:    mov %o0, %l4
-; SOFT-FLOAT-32-NEXT:    mov %o1, %l5
+; SOFT-FLOAT-32-NEXT:    mov %l1, %o3
+; SOFT-FLOAT-32-NEXT:    mov %o0, %l1
+; SOFT-FLOAT-32-NEXT:    mov %o1, %l6
 ; SOFT-FLOAT-32-NEXT:    mov %i2, %o0
 ; SOFT-FLOAT-32-NEXT:    mov %i3, %o1
-; SOFT-FLOAT-32-NEXT:    mov %l2, %o2
+; SOFT-FLOAT-32-NEXT:    mov %l4, %o2
 ; SOFT-FLOAT-32-NEXT:    call __muldf3
-; SOFT-FLOAT-32-NEXT:    mov %l3, %o3
+; SOFT-FLOAT-32-NEXT:    mov %l5, %o3
 ; SOFT-FLOAT-32-NEXT:    mov %o0, %i4
 ; SOFT-FLOAT-32-NEXT:    mov %o1, %i5
 ; SOFT-FLOAT-32-NEXT:    mov %i0, %o0
 ; SOFT-FLOAT-32-NEXT:    mov %i1, %o1
-; SOFT-FLOAT-32-NEXT:    mov %l0, %o2
+; SOFT-FLOAT-32-NEXT:    mov %l2, %o2
 ; SOFT-FLOAT-32-NEXT:    call __muldf3
-; SOFT-FLOAT-32-NEXT:    mov %l1, %o3
-; SOFT-FLOAT-32-NEXT:    ld [%fp+-28], %o2 ! 4-byte Folded Reload
+; SOFT-FLOAT-32-NEXT:    mov %l3, %o3
+; SOFT-FLOAT-32-NEXT:    mov %l7, %o2
 ; SOFT-FLOAT-32-NEXT:    call __adddf3
-; SOFT-FLOAT-32-NEXT:    mov %l7, %o3
+; SOFT-FLOAT-32-NEXT:    ld [%fp+-28], %o3
 ; SOFT-FLOAT-32-NEXT:    mov %o0, %i2
 ; SOFT-FLOAT-32-NEXT:    mov %o1, %i3
 ; SOFT-FLOAT-32-NEXT:    mov %i4, %o0
@@ -310,8 +310,8 @@ define <4 x double> @fmuladd_contract_v4f64(<4 x double> %a, <4 x double> %b, <4
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+-24], %o3
 ; SOFT-FLOAT-32-NEXT:    mov %o0, %i4
 ; SOFT-FLOAT-32-NEXT:    mov %o1, %i5
-; SOFT-FLOAT-32-NEXT:    mov %l4, %o0
-; SOFT-FLOAT-32-NEXT:    mov %l5, %o1
+; SOFT-FLOAT-32-NEXT:    mov %l1, %o0
+; SOFT-FLOAT-32-NEXT:    mov %l6, %o1
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+-12], %o2 ! 4-byte Folded Reload
 ; SOFT-FLOAT-32-NEXT:    call __adddf3
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+-16], %o3
@@ -324,12 +324,12 @@ define <4 x double> @fmuladd_contract_v4f64(<4 x double> %a, <4 x double> %b, <4
 ; SOFT-FLOAT-32-NEXT:    ld [%fp+-8], %o3
 ; SOFT-FLOAT-32-NEXT:    ! kill: def $o0 killed $o0 killed $o0_o1 def $o0_o1
 ; SOFT-FLOAT-32-NEXT:    ! kill: def $o1 killed $o1 killed $o0_o1 def $o0_o1
-; SOFT-FLOAT-32-NEXT:    std %o0, [%l6+24]
-; SOFT-FLOAT-32-NEXT:    std %i0, [%l6+16]
-; SOFT-FLOAT-32-NEXT:    std %i4, [%l6+8]
-; SOFT-FLOAT-32-NEXT:    std %i2, [%l6]
-; SOFT-FLOAT-32-NEXT:    ret
-; SOFT-FLOAT-32-NEXT:    restore
+; SOFT-FLOAT-32-NEXT:    std %o0, [%l0+24]
+; SOFT-FLOAT-32-NEXT:    std %i0, [%l0+16]
+; SOFT-FLOAT-32-NEXT:    std %i4, [%l0+8]
+; SOFT-FLOAT-32-NEXT:    std %i2, [%l0]
+; SOFT-FLOAT-32-NEXT:    jmp %i7+12
+; SOFT-FLOAT-32-NEXT:    restore %g0, %l0, %o0
 ;
 ; SOFT-FLOAT-64-LABEL: fmuladd_contract_v4f64:
 ; SOFT-FLOAT-64:         .cfi_startproc
diff --git a/llvm/test/CodeGen/SPARC/fp128-abi.ll b/llvm/test/CodeGen/SPARC/fp128-abi.ll
index c38892f4f6371f..eec75087e4827e 100644
--- a/llvm/test/CodeGen/SPARC/fp128-abi.ll
+++ b/llvm/test/CodeGen/SPARC/fp128-abi.ll
@@ -23,7 +23,7 @@ define fp128 @f128_direct(fp128 %num) nounwind {
 ; SPARC32-NEXT:    ldd [%fp+-16], %f2
 ; SPARC32-NEXT:    std %f0, [%i0+8]
 ; SPARC32-NEXT:    std %f2, [%i0]
-; SPARC32-NEXT:    ret
+; SPARC32-NEXT:    jmp %i7+12
 ; SPARC32-NEXT:    restore
 ;
 ; SPARC64-LABEL: f128_direct:
@@ -66,8 +66,8 @@ define fp128 @f128_direct_spill(i32 %o0, i32 %o1, i32 %o2, i32 %o3, i32 %o4, i32
 ; SPARC32-NEXT:    ldd [%fp+-16], %f2
 ; SPARC32-NEXT:    std %f0, [%l0+8]
 ; SPARC32-NEXT:    std %f2, [%l0]
-; SPARC32-NEXT:    ret
-; SPARC32-NEXT:    restore
+; SPARC32-NEXT:    jmp %i7+12
+; SPARC32-NEXT:    restore %g0, %l0, %o0
 ;
 ; SPARC64-LABEL: f128_direct_spill:
 ; SPARC64:       ! %bb.0:
diff --git a/llvm/test/CodeGen/SPARC/fp128-strict.ll b/llvm/test/CodeGen/SPARC/fp128-strict.ll
index b65a49675f4c31..a27f209e371516 100644
--- a/llvm/test/CodeGen/SPARC/fp128-strict.ll
+++ b/llvm/test/CodeGen/SPARC/fp128-strict.ll
@@ -31,7 +31,7 @@ define fp128 @fadd_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i0+8]
 ; SOFT32-NEXT:    std %f2, [%i0]
-; SOFT32-NEXT:    ret
+; SOFT32-NEXT:    jmp %i7+12
 ; SOFT32-NEXT:    restore
 ;
 ; SOFT64-LABEL: fadd_f128:
@@ -59,7 +59,7 @@ define fp128 @fadd_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; HARD32-NEXT:    ld [%sp+64], %o0
 ; HARD32-NEXT:    faddq %f4, %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    std %f0, [%o0]
 ;
 ; HARD-LABEL: fadd_f128:
@@ -93,7 +93,7 @@ define fp128 @fsub_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i0+8]
 ; SOFT32-NEXT:    std %f2, [%i0]
-; SOFT32-NEXT:    ret
+; SOFT32-NEXT:    jmp %i7+12
 ; SOFT32-NEXT:    restore
 ;
 ; SOFT64-LABEL: fsub_f128:
@@ -121,7 +121,7 @@ define fp128 @fsub_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; HARD32-NEXT:    ld [%sp+64], %o0
 ; HARD32-NEXT:    fsubq %f4, %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    std %f0, [%o0]
 ;
 ; HARD-LABEL: fsub_f128:
@@ -155,7 +155,7 @@ define fp128 @fmul_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i0+8]
 ; SOFT32-NEXT:    std %f2, [%i0]
-; SOFT32-NEXT:    ret
+; SOFT32-NEXT:    jmp %i7+12
 ; SOFT32-NEXT:    restore
 ;
 ; SOFT64-LABEL: fmul_f128:
@@ -183,7 +183,7 @@ define fp128 @fmul_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; HARD32-NEXT:    ld [%sp+64], %o0
 ; HARD32-NEXT:    fmulq %f4, %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    std %f0, [%o0]
 ;
 ; HARD-LABEL: fmul_f128:
@@ -217,7 +217,7 @@ define fp128 @fdiv_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i0+8]
 ; SOFT32-NEXT:    std %f2, [%i0]
-; SOFT32-NEXT:    ret
+; SOFT32-NEXT:    jmp %i7+12
 ; SOFT32-NEXT:    restore
 ;
 ; SOFT64-LABEL: fdiv_f128:
@@ -245,7 +245,7 @@ define fp128 @fdiv_f128(fp128 %a, fp128 %b) nounwind strictfp {
 ; HARD32-NEXT:    ld [%sp+64], %o0
 ; HARD32-NEXT:    fdivq %f4, %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    std %f0, [%o0]
 ;
 ; HARD-LABEL: fdiv_f128:
@@ -274,7 +274,7 @@ define fp128 @fsqrt_f128(fp128 %a) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i0+8]
 ; SOFT32-NEXT:    std %f2, [%i0]
-; SOFT32-NEXT:    ret
+; SOFT32-NEXT:    jmp %i7+12
 ; SOFT32-NEXT:    restore
 ;
 ; SOFT64-LABEL: fsqrt_f128:
@@ -297,7 +297,7 @@ define fp128 @fsqrt_f128(fp128 %a) nounwind strictfp {
 ; HARD32-NEXT:    ld [%sp+64], %o0
 ; HARD32-NEXT:    fsqrtq %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    std %f0, [%o0]
 ;
 ; HARD-LABEL: fsqrt_f128:
@@ -327,7 +327,7 @@ define fp128 @fpext_f64_f128(double %a) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-24], %f2
 ; SOFT32-NEXT:    std %f0, [%i0+8]
 ; SOFT32-NEXT:    std %f2, [%i0]
-; SOFT32-NEXT:    ret
+; SOFT32-NEXT:    jmp %i7+12
 ; SOFT32-NEXT:    restore
 ;
 ; SOFT64-LABEL: fpext_f64_f128:
@@ -353,7 +353,7 @@ define fp128 @fpext_f64_f128(double %a) nounwind strictfp {
 ; HARD32-NEXT:    fdtoq %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
 ; HARD32-NEXT:    std %f0, [%o0]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    add %sp, 104, %sp
 ;
 ; HARD-LABEL: fpext_f64_f128:
@@ -646,8 +646,8 @@ define fp128 @sitofp_i32_f128(i32 %a) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i1+8]
 ; SOFT32-NEXT:    std %f2, [%i1]
-; SOFT32-NEXT:    ret
-; SOFT32-NEXT:    restore
+; SOFT32-NEXT:    jmp %i7+12
+; SOFT32-NEXT:    restore %g0, %i1, %o0
 ;
 ; SOFT64-LABEL: sitofp_i32_f128:
 ; SOFT64:       ! %bb.0:
@@ -669,7 +669,7 @@ define fp128 @sitofp_i32_f128(i32 %a) nounwind strictfp {
 ; HARD32-NEXT:    fitoq %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
 ; HARD32-NEXT:    std %f0, [%o0]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    add %sp, 96, %sp
 ;
 ; HARD-LABEL: sitofp_i32_f128:
@@ -699,8 +699,8 @@ define fp128 @sitofp_i64_f128(i64 %a) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i2+8]
 ; SOFT32-NEXT:    std %f2, [%i2]
-; SOFT32-NEXT:    ret
-; SOFT32-NEXT:    restore
+; SOFT32-NEXT:    jmp %i7+12
+; SOFT32-NEXT:    restore %g0, %i2, %o0
 ;
 ; SOFT64-LABEL: sitofp_i64_f128:
 ; SOFT64:       ! %bb.0:
@@ -727,8 +727,8 @@ define fp128 @sitofp_i64_f128(i64 %a) nounwind strictfp {
 ; HARD32-NEXT:    ldd [%fp+-16], %f2
 ; HARD32-NEXT:    std %f0, [%i2+8]
 ; HARD32-NEXT:    std %f2, [%i2]
-; HARD32-NEXT:    ret
-; HARD32-NEXT:    restore
+; HARD32-NEXT:    jmp %i7+12
+; HARD32-NEXT:    restore %g0, %i2, %o0
 ;
 ; HARD-LABEL: sitofp_i64_f128:
 ; HARD:       ! %bb.0:
@@ -756,8 +756,8 @@ define fp128 @uitofp_i32_f128(i32 %a) nounwind strictfp {
 ; SOFT32-NEXT:    ldd [%fp+-16], %f2
 ; SOFT32-NEXT:    std %f0, [%i1+8]
 ; SOFT32-NEXT:    std %f2, [%i1]
-; SOFT32-NEXT:    ret
-; SOFT32-NEXT:    restore
+; SOFT32-NEXT:    jmp %i7+12
+; SOFT32-NEXT:    restore %g0, %i1, %o0
 ;
 ; SOFT64-LABEL: uitofp_i32_f128:
 ; SOFT64:       ! %bb.0:
@@ -786,7 +786,7 @@ define fp128 @uitofp_i32_f128(i32 %a) nounwind strictfp {
 ; HARD32-NEXT:    fdtoq %f0, %f0
 ; HARD32-NEXT:    std %f2, [%o0+8]
 ; HARD32-NEXT:    std %f0, [%o0]
-; HARD32-NEXT:    retl
+; HARD32-NEXT:    jmp %o7+12
 ; HARD32-NEXT:    add %sp, 104, %sp
 ;
 ; HARD-LABEL: uitofp_i32_f128:
diff --git a/llvm/test/CodeGen/SPARC/half.ll b/llvm/test/CodeGen/SPARC/half.ll
index 3721c7456d02b7..f2449a16a4cfa4 100644
--- a/llvm/test/CodeGen/SPARC/half.ll
+++ b/llvm/test/CodeGen/SPARC/half.ll
@@ -387,8 +387,8 @@ define <4 x double> @test_extend64_vec4(ptr %p) nounwind {
 ; SPARC32-NEXT:    std %f0, [%i1+8]
 ; SPARC32-NEXT:    ldd [%fp+-24], %f0 ! 8-byte Folded Reload
 ; SPARC32-NEXT:    std %f0, [%i1]
-; SPARC32-NEXT:    ret
-; SPARC32-NEXT:    restore
+; SPARC32-NEXT:    jmp %i7+12
+; SPARC32-NEXT:    restore %g0, %i1, %o0
 ;
 ; SPARC64-LABEL: test_extend64_vec4:
 ; SPARC64:       ! %bb.0:
diff --git a/llvm/test/CodeGen/SPARC/llvm.sincos.ll b/llvm/test/CodeGen/SPARC/llvm.sincos.ll
index 42a89af184789d..664145c0d592bb 100644
--- a/llvm/test/CodeGen/SPARC/llvm.sincos.ll
+++ b/llvm/test/CodeGen/SPARC/llvm.sincos.ll
@@ -569,42 +569,42 @@ define { <3 x float>, <3 x float> } @test_sincos_v3f32(<3 x float> %a) #0 {
 ; SPARC32-NEXT:    ld [%fp+-12], %f0
 ; SPARC32-NEXT:    ld [%fp+-8], %f1
 ; SPARC32-NEXT:    ld [%fp+-4], %f2
-; SPARC32-NEXT:    ld [%fp+64], %i3
+; SPARC32-NEXT:    ld [%fp+64], %i0
 ; SPARC32-NEXT:    st %f0, [%fp+-24]
 ; SPARC32-NEXT:    st %f1, [%fp+-20]
 ; SPARC32-NEXT:    st %f2, [%fp+-16]
-; SPARC32-NEXT:    ld [%fp+-24], %i0
+; SPARC32-NEXT:    ld [%fp+-24], %i1
 ; SPARC32-NEXT:    call sinf
-; SPARC32-NEXT:    mov %i0, %o0
+; SPARC32-NEXT:    mov %i1, %o0
 ; SPARC32-NEXT:    st %f0, [%fp+-28] ! 4-byte Folded Spill
-; SPARC32-NEXT:    ld [%fp+-20], %i1
+; SPARC32-NEXT:    ld [%fp+-20], %i2
 ; SPARC32-NEXT:    call sinf
-; SPARC32-NEXT:    mov %i1, %o0
+; SPARC32-NEXT:    mov %i2, %o0
 ; SPARC32-NEXT:    st %f0, [%fp+-32] ! 4-byte Folded Spill
-; SPARC32-NEXT:    ld [%fp+-16], %i2
+; SPARC32-NEXT:    ld [%fp+-16], %i3
 ; SPARC32-NEXT:    call sinf
-; SPARC32-NEXT:    mov %i2, %o0
+; SPARC32-NEXT:    mov %i3, %o0
 ; SPARC32-NEXT:    st %f0, [%fp+-36] ! 4-byte Folded Spill
 ; SPARC32-NEXT:    call cosf
-; SPARC32-NEXT:    mov %i0, %o0
+; SPARC32-NEXT:    mov %i1, %o0
 ; SPARC32-NEXT:    st %f0, [%fp+-40] ! 4-byte Folded Spill
 ; SPARC32-NEXT:    call cosf
-; SPARC32-NEXT:    mov %i1, %o0
+; SPARC32-NEXT:    mov %i2, %o0
 ; SPARC32-NEXT:    st %f0, [%fp+-44] ! 4-byte Folded Spill
 ; SPARC32-NEXT:    call cosf
-; SPARC32-NEXT:    mov %i2, %o0
-; SPARC32-NEXT:    st %f0, [%i3+24]
+; SPARC32-NEXT:    mov %i3, %o0
+; SPARC32-NEXT:    st %f0, [%i0+24]
 ; SPARC32-NEXT:    ld [%fp+-44], %f0 ! 4-byte Folded Reload
-; SPARC32-NEXT:    st %f0, [%i3+20]
+; SPARC32-NEXT:    st %f0, [%i0+20]
 ; SPARC32-NEXT:    ld [%fp+-40], %f0 ! 4-byte Folded Reload
-; SPARC32-NEXT:    st %f0, [%i3+16]
+; SPARC32-NEXT:    st %f0, [%i0+16]
 ; SPARC32-NEXT:    ld [%fp+-36], %f0 ! 4-byte Folded Reload
-; SPARC32-NEXT:    st %f0, [%i3+8]
+; SPARC32-NEXT:    st %f0, [%i0+8]
 ; SPARC32-NEXT:    ld [%fp+-32], %f0 ! 4-byte Folded Reload
-; SPARC32-NEXT:    st %f0, [%i3+4]
+; SPARC32-NEXT:    st %f0, [%i0+4]
 ; SPARC32-NEXT:    ld [%fp+-28], %f0 ! 4-byte Folded Reload
-; SPARC32-NEXT:    st %f0, [%i3]
-; SPARC32-NEXT:    ret
+; SPARC32-NEXT:    st %f0, [%i0]
+; SPARC32-NEXT:    jmp %i7+12
 ; SPARC32-NEXT:    restore
 ;
 ; SPARC64-LABEL: test_sincos_v3f32:
@@ -654,8 +654,8 @@ define { <3 x float>, <3 x float> } @test_sincos_v3f32(<3 x float> %a) #0 {
 ; GNU32-NEXT:    mov %i0, %o0
 ; GNU32-NEXT:    call sincosf
 ; GNU32-NEXT:    mov %i3, %o1
-; GNU32-NEXT:    ret
-; GNU32-NEXT:    restore
+; GNU32-NEXT:    jmp %i7+12
+; GNU32-NEXT:    restore %g0, %i3, %o0
 ;
 ; GNU64-LABEL: test_sincos_v3f32:
 ; GNU64:       ! %bb.0:
@@ -808,34 +808,34 @@ define { <2 x double>, <2 x double> } @test_sincos_v2f64(<2 x double> %a) #0 {
 ; SPARC32-NEXT:    std %i0, [%fp+-8]
 ; SPARC32-NEXT:    ldd [%fp+-8], %f0
 ; SPARC32-NEXT:    ldd [%fp+-16], %f2
-; SPARC32-NEXT:    ld [%fp+64], %i4
+; SPARC32-NEXT:    ld [%fp+64], %i0
 ; SPARC32-NEXT:    std %f0, [%fp+-32]
 ; SPARC32-NEXT:    std %f2, [%fp+-24]
-; SPARC32-NEXT:    ldd [%fp+-32], %i0
-; SPARC32-NEXT:    mov %i0, %o0
-; SPARC32-NEXT:    call sin
-; SPARC32-NEXT:    mov %i1, %o1
-; SPARC32-NEXT:    std %f0, [%fp+-40] ! 8-byte Folded Spill
-; SPARC32-NEXT:    ldd [%fp+-24], %i2
+; SPARC32-NEXT:    ldd [%fp+-32], %i2
 ; SPARC32-NEXT:    mov %i2, %o0
 ; SPARC32-NEXT:    call sin
 ; SPARC32-NEXT:    mov %i3, %o1
+; SPARC32-NEXT:    std %f0, [%fp+-40] ! 8-byte Folded Spill
+; SPARC32-NEXT:    ldd [%fp+-24], %i4
+; SPARC32-NEXT:    mov %i4, %o0
+; SPARC32-NEXT:    call sin
+; SPARC32-NEXT:    mov %i5, %o1
 ; SPARC32-NEXT:    std %f0, [%fp+-48] ! 8-byte Folded Spill
-; SPARC32-NEXT:    mov %i0, %o0
-; SPARC32-NEXT:    call cos
-; SPARC32-NEXT:    mov %i1, %o1
-; SPARC32-NEXT:    std %f0, [%fp+-56] ! 8-byte Folded Spill
 ; SPARC32-NEXT:    mov %i2, %o0
 ; SPARC32-NEXT:    call cos
 ; SPARC32-NEXT:    mov %i3, %o1
-; SPARC32-NEXT:    std %f0, [%i4+24]
+; SPARC32-NEXT:    std %f0, [%fp+-56] ! 8-byte Folded Spill
+; SPARC32-NEXT:    mov %i4, %o0
+; SPARC32-NEXT:    call cos
+; SPARC32-NEXT:    mov %i5, %o1
+; SPARC32-NEXT:    std %f0, [%i0+24]
 ; SPARC32-NEXT:    ldd [%fp+-56], %f0 ! 8-byte Folded Reload
-; SPARC32-NEXT:    std %f0, [%i4+16]
+; SPARC32-NEXT:    std %f0, [%i0+16]
 ; SPARC32-NEXT:    ldd [%fp+-48], %f0 ! 8-byte Folded Reload
-; SPARC32-NEXT:    std %f0, [%i4+8]
+; SPARC32-NEXT:    std %f0, [%i0+8]
 ; SPARC32-NEXT:    ldd [%fp+-40], %f0 ! 8-byte Folded Reload
-; SPARC32-NEXT:    std %f0, [%i4]
-; SPARC32-NEXT:    ret
+; SPARC32-NEXT:    std %f0, [%i0]
+; SPARC32-NEXT:    jmp %i7+12
 ; SPARC32-NEXT:    restore
 ;
 ; SPARC64-LABEL: test_sincos_v2f64:
@@ -884,7 +884,7 @@ define { <2 x double>, <2 x double> } @test_sincos_v2f64(<2 x double> %a) #0 {
 ; GNU32-NEXT:    add %i0, 16, %o3
 ; GNU32-NEXT:    call sincos
 ; GNU32-NEXT:    mov %i0, %o2
-; GNU32-NEXT:    ret
+; GNU32-NEXT:    jmp %i7+12
 ; GNU32-NEXT:    restore
 ;
 ; GNU64-LABEL: test_sincos_v2f64:
diff --git a/llvm/test/CodeGen/SPARC/sret-demoted.ll b/llvm/test/CodeGen/SPARC/sret-demoted.ll
new file mode 100644
index 00000000000000..93fb1fa939ef4e
--- /dev/null
+++ b/llvm/test/CodeGen/SPARC/sret-demoted.ll
@@ -0,0 +1,138 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=sparc | FileCheck %s --check-prefixes=CHECK,LEAF
+; RUN: llc < %s -mtriple=sparc -disable-sparc-leaf-proc | ...
[truncated]

@alexrp

alexrp commented Nov 7, 2026

Copy link
Copy Markdown
Member

Seems like IsEligibleForTailCallOptimization() also needs to be fixed?

On 32-bit SPARC, a return value that does not fit in the return
registers (e.g. fp128, or a struct larger than six i32s) is demoted
to a hidden sret argument by the generic call lowering. The caller
side keys off the sret argument flags and correctly emits the
struct-return sequence with an unimp after the call. The callee side,
however, only checked hasStructRetAttr() on the IR function, which
is false for a demoted return. As a result the callee neither copied
the sret pointer to %i0 nor returned past the unimp, so control
landed on the unimp in the caller and trapped.

Check the lowered sret argument / SRetReturnReg instead of the IR
attribute so demoted returns are handled the same way as an
explicit sret parameter.

IsEligibleForTailCallOptimization() had the same problem: a caller
whose return value was demoted to sret has no sret attribute, and the
check was skipped entirely when the callee has no arguments. In both
cases a struct-returning function could tail call a non-sret function,
which then returns to %o7+8 and lands on the unimp. Treat the caller
as struct-returning if it has an SRetReturnReg, and compare against
the callee even when it has no arguments.

Fixes llvm#232963

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brad0
brad0 force-pushed the sparc-fp128-demoted-sret branch from 300ecc8 to 22cd917 Compare November 7, 2026 23:19

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Sparc] Miscompilation for fp128 return value without sret

2 participants