[llvm] [SPIRV] Fix crash when lowering functions with aggregate return types (PR #209027)
Sparsh via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 17 01:55:03 PDT 2026
https://github.com/SparshGarg999 updated https://github.com/llvm/llvm-project/pull/209027
>From 64b3180d4a51cf8f1ad9f83433b1138047ff22c7 Mon Sep 17 00:00:00 2001
From: Sparsh Garg <sparsh at example.com>
Date: Sun, 12 Jul 2026 20:47:12 +0530
Subject: [PATCH] [SPIRV] Fix crash when lowering functions with aggregate
return types
The SPIR-V backend's SPIRVPrepareFunctions pass clones functions that
return aggregate types (structs or arrays) and mutates the return type
to i32 to prepare them for the IRTranslator. However, it did not update
the ReturnInst instructions in the cloned function, leaving them
returning the original aggregate type. This type mismatch is caught by
the LLVM IR verifier (run between SPIRV passes), causing a crash:
Function return type does not match operand type of return inst!
ret [1 x float] %oldret
i32in function f
LLVM ERROR: Broken function found, compilation aborted!
Additionally, SPIRVEmitIntrinsics::reconstructAggregateReturns only
processed return values that were direct results of CallBase
instructions. Return values derived from extractvalue or other
instructions were silently ignored, leaving invalid IR.
Fix both issues:
1. In removeAggregateTypesFromSignature: after cloning, replace all
ReturnInst instructions that return an aggregate with a valid
'ret i32 poison' placeholder, keeping the IR verifier happy.
The original aggregate return value is preserved in the body of
the function so SPIRVEmitIntrinsics can find it during reconstruction.
2. In reconstructAggregateReturns: remove the isa<CallBase> restriction.
Any instruction producing a value of the original aggregate type is
now accepted (calls, extractvalue, insertvalue, etc.). The i32 poison
placeholder inserted by step 1 is naturally skipped because its type
does not match OrigRetTy.
Fixes: https://github.com/llvm/llvm-project/issues/208899
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 7 +++-
.../Target/SPIRV/SPIRVPrepareFunctions.cpp | 15 ++++++++
.../SPIRV/function/aggregate-return.ll | 35 +++++++++++++++++++
3 files changed, 56 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/SPIRV/function/aggregate-return.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 19e1e71488ee3..bb573026dc856 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2738,7 +2738,11 @@ void SPIRVEmitIntrinsics::reconstructAggregateReturns(Function &Func,
if (!RI)
continue;
Value *RetVal = RI->getReturnValue();
- if (!RetVal || RetVal->getType() != OrigRetTy || !isa<CallBase>(RetVal))
+ // Accept any instruction (call, extractvalue, etc.) that produces the
+ // original aggregate type. The placeholder inserted by
+ // SPIRVPrepareFunctions (ret i32 poison) has no return value matching
+ // OrigRetTy, so we skip those.
+ if (!RetVal || RetVal->getType() != OrigRetTy)
continue;
Type *AggrTy = RetVal->getType();
uint64_t NumElts = isa<StructType>(AggrTy)
@@ -2754,6 +2758,7 @@ void SPIRVEmitIntrinsics::reconstructAggregateReturns(Function &Func,
}
}
+
void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
IRBuilder<> &B) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 2b07f23e717d5..ec30d5d033e1c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -613,6 +613,21 @@ SPIRVPrepareFunctionsImpl::removeAggregateTypesFromSignature(Function *F) {
Returns);
NewF->takeName(F);
+ // After cloning, the ReturnInst instructions in NewF still return the
+ // original aggregate type, but the function signature now returns i32.
+ // This mismatch is caught by the IR verifier and causes a crash. Replace
+ // each such ReturnInst with a valid `ret i32 poison` placeholder; the
+ // actual aggregate value reconstruction is handled later by
+ // SPIRVEmitIntrinsics::reconstructAggregateReturns.
+ if (IsRetAggr) {
+ Value *PoisonI32 = PoisonValue::get(B.getInt32Ty());
+ for (ReturnInst *RI : Returns) {
+ B.SetInsertPoint(RI);
+ B.CreateRet(PoisonI32);
+ RI->eraseFromParent();
+ }
+ }
+
addFunctionTypeMutation(
NewF->getParent()->getOrInsertNamedMetadata("spv.cloned_funcs"),
std::move(ChangedTypes), NewF->getName());
diff --git a/llvm/test/CodeGen/SPIRV/function/aggregate-return.ll b/llvm/test/CodeGen/SPIRV/function/aggregate-return.ll
new file mode 100644
index 0000000000000..5e8dfb491d60a
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/function/aggregate-return.ll
@@ -0,0 +1,35 @@
+; Test that functions returning aggregate types (struct/array) are lowered
+; correctly to SPIR-V without crashing the IR verifier.
+; See https://github.com/llvm/llvm-project/issues/208899
+;
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[#FLOAT:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#ARR:]] = OpTypeArray %[[#FLOAT]] %[[#]]
+; CHECK-DAG: %[[#STRUCT:]] = OpTypeStruct %[[#FLOAT]] %[[#FLOAT]]
+
+; A function returning an array type.
+; CHECK: OpFunction
+; CHECK: OpReturnValue
+define [1 x float] @array_return() {
+ %arr = insertvalue [1 x float] undef, float 1.0, 0
+ ret [1 x float] %arr
+}
+
+; A function returning a struct type.
+; CHECK: OpFunction
+; CHECK: OpReturnValue
+define { float, float } @struct_return() {
+ %s = insertvalue { float, float } undef, float 0.0, 0
+ %s2 = insertvalue { float, float } %s, float 1.0, 1
+ ret { float, float } %s2
+}
+
+; A function returning an array obtained via extractvalue from a call.
+define [1 x float] @extractvalue_return() {
+ %call = call [1 x float] @array_return()
+ %arr = extractvalue [1 x float] %call, 0
+ %result = insertvalue [1 x float] undef, float %arr, 0
+ ret [1 x float] %result
+}
More information about the llvm-commits
mailing list