[llvm] [SPIRV] Split wide shader PHIs before applying explicit supported types (PR #213806)
Farzon Lotfi via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 4 08:16:28 PDT 2026
https://github.com/farzonl updated https://github.com/llvm/llvm-project/pull/213806
>From 40de00e1fee09f12148656caf4e617e365055791 Mon Sep 17 00:00:00 2001
From: Farzon Lotfi <farzonlotfi at microsoft.com>
Date: Mon, 3 Aug 2026 20:02:32 -0400
Subject: [PATCH 1/2] [SPIRV] Split wide shader PHIs before applying explicit
supported types
fixes #213802
Apply the maximum-vector-size rule before the explicit PHI legality rules for type.
This way if we see a size 16 vector we split it beforehand.
In more precise words this preserves existing legality rules while ensuring
shader PHIs wider than four lanes are split first.
Assisted by Copilot (GPT 5.6 Sol)
---
llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp | 20 ++---
.../instructions/phi-large-vector-shader.ll | 82 +++++++++++++++----
2 files changed, 77 insertions(+), 25 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index 75ac58447f8a6..0c7320e91ee5f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -165,18 +165,18 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
HasArbitraryPrecisionInts ||
ST.canUseExtension(SPIRV::Extension::SPV_KHR_bit_instructions) ||
ST.canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
- auto extendedScalarsAndVectors =
+ auto ExtendedScalarsAndVectors =
[IsExtendedInts](const LegalityQuery &Query) {
const LLT Ty = Query.Types[0];
return IsExtendedInts && Ty.isValid() && !Ty.isPointerOrPointerVector();
};
- auto extendedScalarsAndVectorsProduct = [IsExtendedInts](
+ auto ExtendedScalarsAndVectorsProduct = [IsExtendedInts](
const LegalityQuery &Query) {
const LLT Ty1 = Query.Types[0], Ty2 = Query.Types[1];
return IsExtendedInts && Ty1.isValid() && Ty2.isValid() &&
!Ty1.isPointerOrPointerVector() && !Ty2.isPointerOrPointerVector();
};
- auto extendedPtrsScalarsAndVectors =
+ auto ExtendedPtrsScalarsAndVectors =
[IsExtendedInts](const LegalityQuery &Query) {
const LLT Ty = Query.Types[0];
return IsExtendedInts && Ty.isValid();
@@ -331,7 +331,7 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
G_BITREVERSE, G_SADDSAT, G_UADDSAT, G_SSUBSAT,
G_USUBSAT, G_SCMP, G_UCMP})
.legalFor(allIntScalarsAndVectors)
- .legalIf(extendedScalarsAndVectors);
+ .legalIf(ExtendedScalarsAndVectors);
getActionDefinitionsBuilder({G_SSHLSAT, G_USHLSAT}).lower();
@@ -352,11 +352,11 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
getActionDefinitionsBuilder(G_CTPOP)
.legalForCartesianProduct(allIntScalarsAndVectors)
- .legalIf(extendedScalarsAndVectorsProduct);
+ .legalIf(ExtendedScalarsAndVectorsProduct);
getActionDefinitionsBuilder({G_TRUNC, G_ZEXT, G_SEXT, G_ANYEXT})
.legalForCartesianProduct(allScalarsAndVectors)
- .legalIf(extendedScalarsAndVectorsProduct)
+ .legalIf(ExtendedScalarsAndVectorsProduct)
.moreElementsToNextPow2(0)
.fewerElementsIf(vectorElementCountIsGreaterThan(0, MaxVectorSize),
LegalizeMutations::changeElementCountTo(
@@ -370,12 +370,12 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
.lower();
getActionDefinitionsBuilder(G_PHI)
- .legalFor(allPtrsScalarsAndVectors)
- .legalIf(extendedPtrsScalarsAndVectors)
- .moreElementsToNextPow2(0)
.fewerElementsIf(vectorElementCountIsGreaterThan(0, MaxVectorSize),
LegalizeMutations::changeElementCountTo(
- 0, ElementCount::getFixed(MaxVectorSize)));
+ 0, ElementCount::getFixed(MaxVectorSize)))
+ .legalFor(allPtrsScalarsAndVectors)
+ .legalIf(ExtendedPtrsScalarsAndVectors)
+ .moreElementsToNextPow2(0);
getActionDefinitionsBuilder(G_BITCAST).legalIf(
all(typeInSet(0, allPtrsScalarsAndVectors),
diff --git a/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll b/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll
index 434cab6f4a88c..53386f6c5be4d 100644
--- a/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll
+++ b/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll
@@ -1,25 +1,29 @@
; RUN: llc -O0 -mtriple=spirv-unknown-vulkan %s -o - | FileCheck %s
-; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - -filetype=obj | spirv-val %}
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
; In Shader execution models the SPIR-V max vector size is 4, so a G_PHI on
; a wider vector must be split into multiple PHIs of width 4.
+; Unlike the 32-lane case, the 16-lane type is explicitly listed as legal by
+; the backend, so it must be split before applying the explicit legality rules.
; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
; CHECK-DAG: %[[#V4:]] = OpTypeVector %[[#I32]] 4
-; CHECK-COUNT-8: %[[#PHI:]] = OpPhi %[[#V4]]
+; CHECK-COUNT-12: %[[#PHI:]] = OpPhi %[[#V4]]
; CHECK: OpCompositeExtract %[[#I32]] %[[#PHI]]
- at A = internal addrspace(10) global [8 x <4 x i32>] zeroinitializer
- at Out = internal addrspace(10) global [8 x <4 x i32>] zeroinitializer
+ at A16 = internal addrspace(10) global [4 x <4 x i32>] zeroinitializer
+ at Out16 = internal addrspace(10) global [4 x <4 x i32>] zeroinitializer
+ at A32 = internal addrspace(10) global [8 x <4 x i32>] zeroinitializer
+ at Out32 = internal addrspace(10) global [8 x <4 x i32>] zeroinitializer
@Cond = internal addrspace(10) global i32 zeroinitializer
-define void @main() local_unnamed_addr #0 {
+define internal void @phi_v32() {
entry:
%c = load i32, ptr addrspace(10) @Cond
%cond = icmp ne i32 %c, 0
- %p0 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @A, i32 0, i32 0
+ %p0 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @A32, i32 0, i32 0
%a0 = load <4 x i32>, ptr addrspace(10) %p0
- %p1 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @A, i32 0, i32 1
+ %p1 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @A32, i32 0, i32 1
%a1 = load <4 x i32>, ptr addrspace(10) %p1
%ab = shufflevector <4 x i32> %a0, <4 x i32> %a1,
<8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>
@@ -51,23 +55,71 @@ merge:
%s5 = shufflevector <32 x i32> %p, <32 x i32> poison, <4 x i32> <i32 20, i32 21, i32 22, i32 23>
%s6 = shufflevector <32 x i32> %p, <32 x i32> poison, <4 x i32> <i32 24, i32 25, i32 26, i32 27>
%s7 = shufflevector <32 x i32> %p, <32 x i32> poison, <4 x i32> <i32 28, i32 29, i32 30, i32 31>
- %o0 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 0
+ %o0 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 0
store <4 x i32> %s0, ptr addrspace(10) %o0
- %o1 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 1
+ %o1 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 1
store <4 x i32> %s1, ptr addrspace(10) %o1
- %o2 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 2
+ %o2 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 2
store <4 x i32> %s2, ptr addrspace(10) %o2
- %o3 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 3
+ %o3 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 3
store <4 x i32> %s3, ptr addrspace(10) %o3
- %o4 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 4
+ %o4 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 4
store <4 x i32> %s4, ptr addrspace(10) %o4
- %o5 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 5
+ %o5 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 5
store <4 x i32> %s5, ptr addrspace(10) %o5
- %o6 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 6
+ %o6 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 6
store <4 x i32> %s6, ptr addrspace(10) %o6
- %o7 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out, i32 0, i32 7
+ %o7 = getelementptr [8 x <4 x i32>], ptr addrspace(10) @Out32, i32 0, i32 7
store <4 x i32> %s7, ptr addrspace(10) %o7
ret void
}
+define internal void @phi_v16() {
+entry:
+ %c = load i32, ptr addrspace(10) @Cond
+ %cond = icmp ne i32 %c, 0
+ %p0 = getelementptr [4 x <4 x i32>], ptr addrspace(10) @A16, i32 0, i32 0
+ %a0 = load <4 x i32>, ptr addrspace(10) %p0
+ %p1 = getelementptr [4 x <4 x i32>], ptr addrspace(10) @A16, i32 0, i32 1
+ %a1 = load <4 x i32>, ptr addrspace(10) %p1
+ %ab = shufflevector <4 x i32> %a0, <4 x i32> %a1,
+ <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>
+ %wide_a = shufflevector <8 x i32> %ab, <8 x i32> %ab,
+ <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7,
+ i32 8, i32 9, i32 10, i32 11, i32 12, i32 13, i32 14, i32 15>
+ %wide_b = shufflevector <8 x i32> %ab, <8 x i32> %ab,
+ <16 x i32> <i32 8, i32 9, i32 10, i32 11, i32 12, i32 13, i32 14, i32 15,
+ i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>
+ br i1 %cond, label %then, label %else
+
+then:
+ br label %merge
+
+else:
+ br label %merge
+
+merge:
+ %p = phi <16 x i32> [ %wide_a, %then ], [ %wide_b, %else ]
+ %s0 = shufflevector <16 x i32> %p, <16 x i32> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ %s1 = shufflevector <16 x i32> %p, <16 x i32> poison, <4 x i32> <i32 4, i32 5, i32 6, i32 7>
+ %s2 = shufflevector <16 x i32> %p, <16 x i32> poison, <4 x i32> <i32 8, i32 9, i32 10, i32 11>
+ %s3 = shufflevector <16 x i32> %p, <16 x i32> poison, <4 x i32> <i32 12, i32 13, i32 14, i32 15>
+ %o0 = getelementptr [4 x <4 x i32>], ptr addrspace(10) @Out16, i32 0, i32 0
+ store <4 x i32> %s0, ptr addrspace(10) %o0
+ %o1 = getelementptr [4 x <4 x i32>], ptr addrspace(10) @Out16, i32 0, i32 1
+ store <4 x i32> %s1, ptr addrspace(10) %o1
+ %o2 = getelementptr [4 x <4 x i32>], ptr addrspace(10) @Out16, i32 0, i32 2
+ store <4 x i32> %s2, ptr addrspace(10) %o2
+ %o3 = getelementptr [4 x <4 x i32>], ptr addrspace(10) @Out16, i32 0, i32 3
+ store <4 x i32> %s3, ptr addrspace(10) %o3
+ ret void
+}
+
+define void @main() local_unnamed_addr #0 {
+entry:
+ call void @phi_v16()
+ call void @phi_v32()
+ ret void
+}
+
attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
>From a92c5b1fa59602aa7893664c347d399d995b76c6 Mon Sep 17 00:00:00 2001
From: Farzon Lotfi <farzonlotfi at microsoft.com>
Date: Tue, 4 Aug 2026 11:16:13 -0400
Subject: [PATCH 2/2] make the function checks more explicit
---
.../instructions/phi-large-vector-shader.ll | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll b/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll
index 53386f6c5be4d..9520164f012c1 100644
--- a/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll
+++ b/llvm/test/CodeGen/SPIRV/instructions/phi-large-vector-shader.ll
@@ -3,13 +3,11 @@
; In Shader execution models the SPIR-V max vector size is 4, so a G_PHI on
; a wider vector must be split into multiple PHIs of width 4.
-; Unlike the 32-lane case, the 16-lane type is explicitly listed as legal by
-; the backend, so it must be split before applying the explicit legality rules.
+; Unlike the 32 element case, the 16 case is listed as a legal type by the
+; backend, so it must be split before applying the explicit legality rules.
; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
; CHECK-DAG: %[[#V4:]] = OpTypeVector %[[#I32]] 4
-; CHECK-COUNT-12: %[[#PHI:]] = OpPhi %[[#V4]]
-; CHECK: OpCompositeExtract %[[#I32]] %[[#PHI]]
@A16 = internal addrspace(10) global [4 x <4 x i32>] zeroinitializer
@Out16 = internal addrspace(10) global [4 x <4 x i32>] zeroinitializer
@@ -17,6 +15,12 @@
@Out32 = internal addrspace(10) global [8 x <4 x i32>] zeroinitializer
@Cond = internal addrspace(10) global i32 zeroinitializer
+; CHECK-LABEL: OpFunction %{{[0-9]+}} None %{{[0-9]+}} ; -- Begin function phi_v32
+; CHECK-COUNT-8: %[[#]] = OpPhi %[[#V4]]
+; CHECK: OpCompositeExtract %[[#I32]]
+; CHECK-NOT: OpPhi
+; CHECK: OpFunctionEnd
+
define internal void @phi_v32() {
entry:
%c = load i32, ptr addrspace(10) @Cond
@@ -74,6 +78,12 @@ merge:
ret void
}
+; CHECK-LABEL: OpFunction %{{[0-9]+}} None %{{[0-9]+}} ; -- Begin function phi_v16
+; CHECK-COUNT-4: %[[#]] = OpPhi %[[#V4]]
+; CHECK: OpCompositeExtract %[[#I32]]
+; CHECK-NOT: OpPhi
+; CHECK: OpFunctionEnd
+
define internal void @phi_v16() {
entry:
%c = load i32, ptr addrspace(10) @Cond
More information about the llvm-commits
mailing list