[llvm] 0cce782 - [SPIR-V] Lower `select` instructions with aggregate operands (#201417)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 02:50:10 PDT 2026
Author: Tim Besard
Date: 2026-06-11T09:50:04Z
New Revision: 0cce78251f4c534b0d0a5ad55dd470e101ea9b94
URL: https://github.com/llvm/llvm-project/commit/0cce78251f4c534b0d0a5ad55dd470e101ea9b94
DIFF: https://github.com/llvm/llvm-project/commit/0cce78251f4c534b0d0a5ad55dd470e101ea9b94.diff
LOG: [SPIR-V] Lower `select` instructions with aggregate operands (#201417)
Context: `SPIRVEmitIntrinsics` represents aggregate (array/struct) SSA
values as i32 value-ids, keeping the real type on the side for SPIR-V
emission. `preprocessCompositeConstants()` rewrites composite constant
operands into those value-ids.
A `select` takes its result type from its operands, so rewriting one arm
leaves the select with an aggregate result type but an i32 operand,
which is invalid. The exact failure mode depends: a composite-constant
arm tripped the verifier ("Select values must have same type as select
instruction"), while a non-constant arm (say a load) only became a
value-id later, in the visitor pass, at which point
`replaceMemInstrUses()` found a `select` among its users and hit an
unreachable.
I pushed two commits fixing this, one limited to my use case, another
more general:
1. Constant arms only. The common case is a select between two composite
constants, such as two complex literals. Once both arms are value-ids,
mutate the select to i32 and record its real type in `AggrConstTypes`;
the existing visitor turns its `extractvalue` users into `spv_extractv`.
2. An arm can also be a load or `insertvalue` result, which only becomes
a value-id later, in the visitor pass. By then the select has already
been mutated to i32, so its operand has to be reconciled when the arm is
lowered. This commit makes `select` behave like `PHINode` (which already
handles this): mutate every aggregate select to i32 up front, and handle
`SelectInst` in `replaceMemInstrUses()` so the operand and its
`extractvalue` users get fixed up as each arm is lowered. Nested
aggregate selects fall out of the same up-front mutation.
Developed with the help of Claude 4.8.
Closes https://github.com/llvm/llvm-project/issues/151344
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
Added:
llvm/test/CodeGen/SPIRV/select-aggregate.ll
llvm/test/CodeGen/SPIRV/select-composite-constant.ll
Modified:
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index f5c462bdc629b..42e398196438d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -190,7 +190,6 @@ class SPIRVEmitIntrinsics
DenseMap<Instruction *, Constant *> AggrConsts;
DenseMap<Instruction *, Type *> AggrConstTypes;
DenseSet<Instruction *> AggrStores;
- SmallPtrSet<Instruction *, 8> DeletedInstrs;
GlobalVariableUsers GVUsers;
std::unordered_set<Value *> Named;
@@ -1577,29 +1576,14 @@ void SPIRVEmitIntrinsics::replaceMemInstrUses(Instruction *Old,
CI->setCalledFunction(NewF);
}
}
- } else if (auto *Phi = dyn_cast<PHINode>(U)) {
- if (Phi->getType() != New->getType()) {
- Phi->mutateType(New->getType());
- Phi->replaceUsesOfWith(Old, New);
- // Convert extractvalue users of the mutated PHI to spv_extractv
- SmallVector<ExtractValueInst *, 4> EVUsers;
- for (User *PhiUser : Phi->users())
- if (auto *EV = dyn_cast<ExtractValueInst>(PhiUser))
- EVUsers.push_back(EV);
- for (ExtractValueInst *EV : EVUsers) {
- B.SetInsertPoint(EV);
- SmallVector<Value *> Args(EV->operand_values());
- for (unsigned Idx : EV->indices())
- Args.push_back(B.getInt32(Idx));
- auto *NewEV =
- B.CreateIntrinsic(Intrinsic::spv_extractv, {EV->getType()}, Args);
- EV->replaceAllUsesWith(NewEV);
- DeletedInstrs.insert(EV);
- EV->eraseFromParent();
- }
- } else {
- Phi->replaceUsesOfWith(Old, New);
- }
+ } else if (isa<PHINode>(U) || isa<SelectInst>(U)) {
+ // Aggregate-typed PHIs and selects have already been mutated to the
+ // i32 value-id type up front in runOnFunction, so only the operand
+ // needs replacing here; their extractvalue users are lowered to
+ // spv_extractv by visitExtractValueInst.
+ assert(U->getType() == New->getType() &&
+ "aggregate PHI/select should have been mutated to value-id type");
+ U->replaceUsesOfWith(Old, New);
} else {
llvm_unreachable("illegal aggregate intrinsic user");
}
@@ -3475,7 +3459,6 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
AggrConsts.clear();
AggrConstTypes.clear();
AggrStores.clear();
- DeletedInstrs.clear();
processParamTypesByFunHeader(CurrF, B);
@@ -3531,12 +3514,21 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
simplifyNullAddrSpaceCasts();
preprocessCompositeConstants(B);
- for (BasicBlock &BB : Func)
- for (PHINode &Phi : BB.phis())
- if (Phi.getType()->isAggregateType()) {
- AggrConstTypes[&Phi] = Phi.getType();
- Phi.mutateType(B.getInt32Ty());
- }
+ // A PHINode or SelectInst takes its result type from its operands. Aggregate
+ // arms are lowered to i32 value-ids (composite constants here, loads and
+ // other producers during the visitor pass below), so mutate an aggregate PHI
+ // or select to match. The original type is tracked in AggrConstTypes (used to
+ // assign the SPIR-V type) and its extractvalue users are lowered to
+ // spv_extractv.
+ Type *I32Ty = B.getInt32Ty();
+ for (Instruction &I : instructions(Func)) {
+ if (!isa<PHINode>(I) && !isa<SelectInst>(I))
+ continue;
+ if (!I.getType()->isAggregateType())
+ continue;
+ AggrConstTypes[&I] = I.getType();
+ I.mutateType(I32Ty);
+ }
preprocessBoolVectorBitcasts(Func);
SmallVector<Instruction *> Worklist(
@@ -3578,8 +3570,6 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
deduceOperandElementType(&Phi, nullptr);
for (auto *I : Worklist) {
- if (DeletedInstrs.count(I))
- continue;
TrackConstants = true;
if (!I->getType()->isVoidTy() || isa<StoreInst>(I))
setInsertPointAfterDef(B, I);
diff --git a/llvm/test/CodeGen/SPIRV/select-aggregate.ll b/llvm/test/CodeGen/SPIRV/select-aggregate.ll
new file mode 100644
index 0000000000000..5aca4cbf28a8a
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/select-aggregate.ll
@@ -0,0 +1,80 @@
+; SPIRVEmitIntrinsics rewrites aggregate (array/struct) SSA values to i32
+; value-ids. A select takes its result type from its arms, so rewriting an arm
+; without updating the select leaves it with i32 operands under an aggregate
+; result type, which is invalid. Check that these selects lower to a valid
+; OpSelect over the composite type; select-composite-constant.ll covers the
+; all-constant case.
+
+; RUN: llc -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: %[[#Int:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#Two:]] = OpConstant %[[#Int]] 2
+; CHECK-DAG: %[[#Array:]] = OpTypeArray %[[#Float]] %[[#Two]]
+; CHECK-DAG: %[[#Struct:]] = OpTypeStruct %[[#Float]] %[[#Float]]
+
+; Both arms are loaded (non-constant) aggregates.
+; CHECK: %[[#A:]] = OpLoad %[[#Array]]
+; CHECK: %[[#B:]] = OpLoad %[[#Array]]
+; CHECK: %[[#Sel0:]] = OpSelect %[[#Array]] %[[#]] %[[#A]] %[[#B]]
+; CHECK: OpCompositeExtract %[[#Float]] %[[#Sel0]] 0
+; CHECK: OpCompositeExtract %[[#Float]] %[[#Sel0]] 1
+define spir_kernel void @both_loaded(ptr addrspace(1) %out, ptr addrspace(1) %pa, ptr addrspace(1) %pb, i1 %c) {
+ %a = load [2 x float], ptr addrspace(1) %pa
+ %b = load [2 x float], ptr addrspace(1) %pb
+ %v = select i1 %c, [2 x float] %a, [2 x float] %b
+ %e0 = extractvalue [2 x float] %v, 0
+ %e1 = extractvalue [2 x float] %v, 1
+ store float %e0, ptr addrspace(1) %out
+ %p1 = getelementptr float, ptr addrspace(1) %out, i64 1
+ store float %e1, ptr addrspace(1) %p1
+ ret void
+}
+
+; Mixed: one arm loaded, the other a composite constant.
+; CHECK: %[[#M:]] = OpLoad %[[#Array]]
+; CHECK: %[[#Sel1:]] = OpSelect %[[#Array]] %[[#]] %[[#M]] %[[#]]
+; CHECK: OpCompositeExtract %[[#Float]] %[[#Sel1]] 0
+define spir_kernel void @mixed_array(ptr addrspace(1) %out, ptr addrspace(1) %pa, i1 %c) {
+ %a = load [2 x float], ptr addrspace(1) %pa
+ %v = select i1 %c, [2 x float] %a, [2 x float] [float 1.000000e+00, float 0.000000e+00]
+ %e0 = extractvalue [2 x float] %v, 0
+ store float %e0, ptr addrspace(1) %out
+ ret void
+}
+
+; Mixed struct-typed select.
+; CHECK: %[[#S:]] = OpLoad %[[#Struct]]
+; CHECK: %[[#Sel2:]] = OpSelect %[[#Struct]] %[[#]] %[[#S]] %[[#]]
+; CHECK: OpCompositeExtract %[[#Float]] %[[#Sel2]] 0
+define spir_kernel void @mixed_struct(ptr addrspace(1) %out, ptr addrspace(1) %pa, i1 %c) {
+ %a = load { float, float }, ptr addrspace(1) %pa
+ %v = select i1 %c, { float, float } %a, { float, float } { float 1.000000e+00, float 2.000000e+00 }
+ %e0 = extractvalue { float, float } %v, 0
+ store float %e0, ptr addrspace(1) %out
+ ret void
+}
+
+; A select whose arm is itself a select.
+; CHECK: %[[#Inner:]] = OpSelect %[[#Array]] %[[#]] %[[#]] %[[#]]
+; CHECK: %[[#Outer:]] = OpSelect %[[#Array]] %[[#]] %[[#Inner]] %[[#]]
+; CHECK: OpCompositeExtract %[[#Float]] %[[#Outer]] 0
+define spir_kernel void @nested(ptr addrspace(1) %out, ptr addrspace(1) %pa, i1 %c, i1 %d) {
+ %a = load [2 x float], ptr addrspace(1) %pa
+ %inner = select i1 %d, [2 x float] %a, [2 x float] zeroinitializer
+ %v = select i1 %c, [2 x float] %inner, [2 x float] [float 1.000000e+00, float 0.000000e+00]
+ %e0 = extractvalue [2 x float] %v, 0
+ store float %e0, ptr addrspace(1) %out
+ ret void
+}
+
+; The aggregate select result is stored directly, without an extractvalue.
+; CHECK: %[[#Sel3:]] = OpSelect %[[#Array]] %[[#]] %[[#]] %[[#]]
+; CHECK: OpStore %[[#]] %[[#Sel3]]
+define spir_kernel void @store_direct(ptr addrspace(1) %out, ptr addrspace(1) %pa, i1 %c) {
+ %a = load [2 x float], ptr addrspace(1) %pa
+ %v = select i1 %c, [2 x float] %a, [2 x float] zeroinitializer
+ store [2 x float] %v, ptr addrspace(1) %out
+ ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/select-composite-constant.ll b/llvm/test/CodeGen/SPIRV/select-composite-constant.ll
new file mode 100644
index 0000000000000..ef51a339f5aeb
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/select-composite-constant.ll
@@ -0,0 +1,41 @@
+; A `select` whose arms are composite (aggregate) constants used to crash in
+; SPIRVEmitIntrinsics: preprocessCompositeConstants() rewrites the composite
+; constant operands into i32 value-ids, which left the select with an aggregate
+; result type but i32 operands -- an invalid state rejected by the verifier with
+; "Select values must have same type as select instruction". Check that such a
+; select is now lowered to a valid OpSelect over the composite type.
+
+; RUN: llc -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: %[[#Int:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#Two:]] = OpConstant %[[#Int]] 2
+; CHECK-DAG: %[[#Array:]] = OpTypeArray %[[#Float]] %[[#Two]]
+; CHECK-DAG: %[[#Struct:]] = OpTypeStruct %[[#Float]] %[[#Float]]
+
+; The selects must be lowered to OpSelect over the composite type (not i32).
+; CHECK-DAG: %[[#ArrSel:]] = OpSelect %[[#Array]] %[[#]] %[[#]] %[[#]]
+; CHECK-DAG: %[[#StructSel:]] = OpSelect %[[#Struct]] %[[#]] %[[#]] %[[#]]
+; CHECK-DAG: OpCompositeExtract %[[#Float]] %[[#ArrSel]] 0
+; CHECK-DAG: OpCompositeExtract %[[#Float]] %[[#ArrSel]] 1
+; CHECK-DAG: OpCompositeExtract %[[#Float]] %[[#StructSel]] 0
+
+; Array-typed composite constant (e.g. a Julia Complex{Float32}).
+define spir_kernel void @select_array_constant(ptr addrspace(1) %out, i1 %c) {
+ %v = select i1 %c, [2 x float] [float 1.000000e+00, float 0.000000e+00], [2 x float] zeroinitializer
+ %e0 = extractvalue [2 x float] %v, 0
+ %e1 = extractvalue [2 x float] %v, 1
+ store float %e0, ptr addrspace(1) %out
+ %p1 = getelementptr float, ptr addrspace(1) %out, i64 1
+ store float %e1, ptr addrspace(1) %p1
+ ret void
+}
+
+; Struct-typed composite constant (e.g. a C _Complex float).
+define spir_kernel void @select_struct_constant(ptr addrspace(1) %out, i1 %c) {
+ %v = select i1 %c, { float, float } { float 1.000000e+00, float 2.000000e+00 }, { float, float } zeroinitializer
+ %e0 = extractvalue { float, float } %v, 0
+ store float %e0, ptr addrspace(1) %out
+ ret void
+}
More information about the llvm-commits
mailing list