[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