[llvm] b07fc8a - [SPARC] Do not use the unimp struct-return convention for zero-sized types (#213733)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 12:00:53 PDT 2026


Author: Folkert de Vries
Date: 2026-08-04T21:00:46+02:00
New Revision: b07fc8a44aa6ac03ec8e0c79b04134bda87b5279

URL: https://github.com/llvm/llvm-project/commit/b07fc8a44aa6ac03ec8e0c79b04134bda87b5279
DIFF: https://github.com/llvm/llvm-project/commit/b07fc8a44aa6ac03ec8e0c79b04134bda87b5279.diff

LOG: [SPARC] Do not use the unimp struct-return convention for zero-sized types (#213733)

GCC has special handling for an `sret` of a zero-sized type, match that
handling.

https://godbolt.org/z/Tcraao7rT

It seems unlikely someone is actually relying on this, so I haven't
added the abi compatibility logic. Rust however uses zero-sized types
quite heavily, so having this work correctly is useful there.

Added: 
    

Modified: 
    llvm/lib/Target/Sparc/DelaySlotFiller.cpp
    llvm/lib/Target/Sparc/SparcISelLowering.cpp
    llvm/test/CodeGen/SPARC/2011-01-22-SRet.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/Sparc/DelaySlotFiller.cpp b/llvm/lib/Target/Sparc/DelaySlotFiller.cpp
index 7d7aa20869c20..03ca5e7ae98d2 100644
--- a/llvm/lib/Target/Sparc/DelaySlotFiller.cpp
+++ b/llvm/lib/Target/Sparc/DelaySlotFiller.cpp
@@ -391,8 +391,12 @@ bool Filler::needsUnimp(MachineBasicBlock::iterator I, unsigned &StructSize)
   const MachineOperand &MO = I->getOperand(structSizeOpNum);
   if (!MO.isImm())
     return false;
+
+  // A zero-sized return value has nothing for the callee to copy, so GCC emits
+  // no unimp for it and returns to the instruction right after the delay slot.
+  // We replicate this behavior here.
   StructSize = MO.getImm();
-  return true;
+  return StructSize != 0;
 }
 
 static bool combineRestoreADD(MachineBasicBlock &MBB,

diff  --git a/llvm/lib/Target/Sparc/SparcISelLowering.cpp b/llvm/lib/Target/Sparc/SparcISelLowering.cpp
index 488cc2f8e900f..f705c95d8531c 100644
--- a/llvm/lib/Target/Sparc/SparcISelLowering.cpp
+++ b/llvm/lib/Target/Sparc/SparcISelLowering.cpp
@@ -328,7 +328,13 @@ SparcTargetLowering::LowerReturn_32(SDValue Chain, CallingConv::ID CallConv,
     Chain = DAG.getCopyToReg(Chain, DL, SP::I0, Val, Glue);
     Glue = Chain.getValue(1);
     RetOps.push_back(DAG.getRegister(SP::I0, PtrVT));
-    RetAddrOffset = 12; // CallInst + Delay Slot + Unimp
+
+    // 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);
+    if (!RetType->isEmptyTy())
+      RetAddrOffset = 12; // CallInst + Delay Slot + Unimp
   }
 
   RetOps[0] = Chain;  // Update chain.

diff  --git a/llvm/test/CodeGen/SPARC/2011-01-22-SRet.ll b/llvm/test/CodeGen/SPARC/2011-01-22-SRet.ll
index d8a8caf0d83f5..0bdec29de39df 100644
--- a/llvm/test/CodeGen/SPARC/2011-01-22-SRet.ll
+++ b/llvm/test/CodeGen/SPARC/2011-01-22-SRet.ll
@@ -1,12 +1,21 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
 ;RUN: llc -mtriple=sparc < %s | FileCheck %s
 
 %struct.foo_t = type { i32, i32, i32 }
+%struct.empty_t = type { { } }
+%struct.emptyarr_t = type { [0 x i32] }
+%struct.tiny_t = type { i8 }
 
 define weak void @make_foo(ptr noalias sret(%struct.foo_t) %agg.result, i32 %a, i32 %b, i32 %c) nounwind {
+; CHECK-LABEL: make_foo:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    ld [%sp+64], %o3
+; CHECK-NEXT:    st %o0, [%o3]
+; CHECK-NEXT:    st %o1, [%o3+4]
+; CHECK-NEXT:    st %o2, [%o3+8]
+; CHECK-NEXT:    jmp %o7+12
+; CHECK-NEXT:    mov %o3, %o0
 entry:
-;CHECK-LABEL: make_foo:
-;CHECK: ld [%sp+64], {{.+}}
-;CHECK: jmp %o7+12
   %0 = getelementptr inbounds %struct.foo_t, ptr %agg.result, i32 0, i32 0
   store i32 %a, ptr %0, align 4
   %1 = getelementptr inbounds %struct.foo_t, ptr %agg.result, i32 0, i32 1
@@ -17,11 +26,24 @@ entry:
 }
 
 define i32 @test() nounwind {
+; CHECK-LABEL: test:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    save %sp, -112, %sp
+; CHECK-NEXT:    add %fp, -16, %i0
+; CHECK-NEXT:    mov 10, %o0
+; CHECK-NEXT:    mov 20, %o1
+; CHECK-NEXT:    mov 30, %o2
+; CHECK-NEXT:    call make_foo
+; CHECK-NEXT:    st %i0, [%sp+64]
+; CHECK-NEXT:    unimp 12
+; CHECK-NEXT:    ld [%fp+-16], %i1
+; CHECK-NEXT:    or %i0, 4, %i0
+; CHECK-NEXT:    ld [%i0], %i0
+; CHECK-NEXT:    ld [%fp+-8], %i2
+; CHECK-NEXT:    add %i0, %i1, %i0
+; CHECK-NEXT:    ret
+; CHECK-NEXT:    restore %i0, %i2, %o0
 entry:
-;CHECK-LABEL: test:
-;CHECK: call make_foo
-;CHECK: st {{.+}}, [%sp+64]
-;CHECK: unimp 12
   %f = alloca %struct.foo_t, align 8
   call void @make_foo(ptr noalias sret(%struct.foo_t) %f, i32 10, i32 20, i32 30) nounwind
   %0 = getelementptr inbounds %struct.foo_t, ptr %f, i32 0, i32 0
@@ -34,3 +56,82 @@ entry:
   %7 = add nsw i32 %6, %5
   ret i32 %7
 }
+
+define weak void @make_tiny(ptr noalias sret(%struct.tiny_t) %agg.result) nounwind {
+; CHECK-LABEL: make_tiny:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    ld [%sp+64], %o0
+; CHECK-NEXT:    mov 1, %o1
+; CHECK-NEXT:    jmp %o7+12
+; CHECK-NEXT:    stb %o1, [%o0]
+entry:
+  store i8 1, ptr %agg.result
+  ret void
+}
+
+define i32 @test_tiny() nounwind {
+; CHECK-LABEL: test_tiny:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    save %sp, -96, %sp
+; CHECK-NEXT:    add %fp, -1, %i0
+; CHECK-NEXT:    call make_tiny
+; CHECK-NEXT:    st %i0, [%sp+64]
+; CHECK-NEXT:    unimp 1
+; CHECK-NEXT:    ret
+; CHECK-NEXT:    restore %g0, %g0, %o0
+entry:
+  %e = alloca %struct.tiny_t, align 1
+  call void @make_tiny(ptr noalias sret(%struct.tiny_t) %e) nounwind
+  ret i32 0
+}
+
+; A zero-sized struct has nothing for the callee to copy, so the caller emits no
+; unimp after the call and the callee returns to %o7+8, which is spelled `retl`.
+define weak void @make_empty(ptr noalias sret(%struct.empty_t) %agg.result) nounwind {
+; CHECK-LABEL: make_empty:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    retl
+; CHECK-NEXT:    ld [%sp+64], %o0
+entry:
+  ret void
+}
+
+define i32 @test_empty() nounwind {
+; CHECK-LABEL: test_empty:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    save %sp, -96, %sp
+; CHECK-NEXT:    add %fp, -1, %i0
+; CHECK-NEXT:    call make_empty
+; CHECK-NEXT:    st %i0, [%sp+64]
+; CHECK-NEXT:    ret
+; CHECK-NEXT:    restore %g0, %g0, %o0
+entry:
+  %e = alloca %struct.empty_t, align 1
+  call void @make_empty(ptr noalias sret(%struct.empty_t) %e) nounwind
+  ret i32 0
+}
+
+; Test with empty arrays too.
+define weak void @make_emptyarr(ptr noalias sret(%struct.emptyarr_t) %agg.result) nounwind {
+; CHECK-LABEL: make_emptyarr:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    retl
+; CHECK-NEXT:    ld [%sp+64], %o0
+entry:
+  ret void
+}
+
+define i32 @test_emptyarr() nounwind {
+; CHECK-LABEL: test_emptyarr:
+; CHECK:       ! %bb.0: ! %entry
+; CHECK-NEXT:    save %sp, -96, %sp
+; CHECK-NEXT:    add %fp, -4, %i0
+; CHECK-NEXT:    call make_emptyarr
+; CHECK-NEXT:    st %i0, [%sp+64]
+; CHECK-NEXT:    ret
+; CHECK-NEXT:    restore %g0, %g0, %o0
+entry:
+  %e = alloca %struct.emptyarr_t, align 4
+  call void @make_emptyarr(ptr noalias sret(%struct.emptyarr_t) %e) nounwind
+  ret i32 0
+}


        


More information about the llvm-commits mailing list