[llvm] 1cc730f - [SPIRV] Legalize i1 min/max before selection (#204231)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 6 05:37:43 PDT 2026
Author: Tim Besard
Date: 2026-08-06T13:37:37+01:00
New Revision: 1cc730fd7b31cdc240a6357b66e7a1b05bbfe135
URL: https://github.com/llvm/llvm-project/commit/1cc730fd7b31cdc240a6357b66e7a1b05bbfe135
DIFF: https://github.com/llvm/llvm-project/commit/1cc730fd7b31cdc240a6357b66e7a1b05bbfe135.diff
LOG: [SPIRV] Legalize i1 min/max before selection (#204231)
LLVM can produce `llvm.{s,u}{min,max}.i1` from boolean value patterns.
In SPIR-V, however, LLVM `i1` is represented as `OpTypeBool`, not as an
integer type. The OpenCL/GLSL extended min/max instructions require
integer scalar or integer vector operands and results, so selecting
those instructions directly for `i1` can produce invalid SPIR-V.
This patch makes the SPIR-V legalizer reflect that type-system boundary.
Nonstandard integer widths remain legal when the relevant extensions are
enabled, but `s1` is not treated as an extended integer width. Scalar
`i1` min/max is widened to `i32` before instruction selection and
converted back to bool. Boolean vectors are scalarized first, then each
lane follows the same scalar legalization path.
The result is deliberately conservative: it preserves the existing
extended-integer behavior while preventing boolean values from reaching
integer-only SPIR-V extended instructions.
---
The Khronos SPIRV-LLVM-Translator follows the same type-model premise:
`SPIRVWriter::transType` maps LLVM `i1` to `OpTypeBool`, while wider
LLVM integer types map to `OpTypeInt`.
The translator also avoids using OpenCL/GLSL extended min/max
instructions for LLVM min/max intrinsics. Its `SPIRVWriter.cpp` lowering
for `llvm.umin`, `llvm.umax`, `llvm.smin`, and `llvm.smax` emits an
integer comparison followed by `OpSelect`. That compare/select strategy
does not provide evidence that boolean extended min/max is valid; it
sidesteps the integer-only extended-instruction constraint entirely.
There is also a useful precedent in the translator regularization pass
for shifts: because SPIR-V shift operands must be integer scalar/vector
types, LLVM `i1` operands are treated as boolean and extended to `i32`
before the integer operation, then converted back to bool. This patch
applies the same principle to the LLVM backend path that currently uses
extended min/max instructions.
---------
Co-authored-by: Codex <noreply at openai.com>
Added:
llvm/test/CodeGen/SPIRV/llvm-intrinsics/minmax-i1.ll
Modified:
llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index 0c7320e91ee5f..516ba98d5bf7d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -130,6 +130,7 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
v8s16, v8s32, v8s64, v16s8, v16s16, v16s32, v16s64};
auto allBoolScalarsAndVectors = {s1, v2s1, v3s1, v4s1, v8s1, v16s1};
+ auto allBoolVectors = {v2s1, v3s1, v4s1, v8s1, v16s1};
auto allIntScalars = {s8, s16, s32, s64, s128};
@@ -165,10 +166,11 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
HasArbitraryPrecisionInts ||
ST.canUseExtension(SPIRV::Extension::SPV_KHR_bit_instructions) ||
ST.canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
- auto ExtendedScalarsAndVectors =
+ auto ExtendedIntScalarsAndVectors =
[IsExtendedInts](const LegalityQuery &Query) {
const LLT Ty = Query.Types[0];
- return IsExtendedInts && Ty.isValid() && !Ty.isPointerOrPointerVector();
+ return IsExtendedInts && Ty.isValid() &&
+ !Ty.isPointerOrPointerVector() && Ty.getScalarSizeInBits() > 1;
};
auto ExtendedScalarsAndVectorsProduct = [IsExtendedInts](
const LegalityQuery &Query) {
@@ -331,7 +333,10 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
G_BITREVERSE, G_SADDSAT, G_UADDSAT, G_SSUBSAT,
G_USUBSAT, G_SCMP, G_UCMP})
.legalFor(allIntScalarsAndVectors)
- .legalIf(ExtendedScalarsAndVectors);
+ .legalIf(ExtendedIntScalarsAndVectors)
+ // LLVM i1 maps to OpTypeBool, not OpTypeInt.
+ .scalarizeIf(typeInSet(0, allBoolVectors), 0)
+ .minScalar(0, s32);
getActionDefinitionsBuilder({G_SSHLSAT, G_USHLSAT}).lower();
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/minmax-i1.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/minmax-i1.ll
new file mode 100644
index 0000000000000..1c8a99790a230
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/minmax-i1.ll
@@ -0,0 +1,66 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; RUN: %if spirv-tools %{ llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[#BOOL:]] = OpTypeBool
+; CHECK-DAG: %[[#INT:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#VBOOL:]] = OpTypeVector %[[#BOOL]] 2
+; CHECK-DAG: %[[#ZERO:]] = OpConstantNull %[[#INT]]
+; CHECK-DAG: %[[#ONE:]] = OpConstant %[[#INT]] 1
+
+define spir_func i1 @test_umin_i1(i1 %a, i1 %b) {
+entry:
+; CHECK: OpSelect %[[#INT]] %[[#]] %[[#ONE]] %[[#ZERO]]
+; CHECK: OpSelect %[[#INT]] %[[#]] %[[#ONE]] %[[#ZERO]]
+; CHECK: OpExtInst %[[#INT]] %[[#]] u_min %[[#]] %[[#]]
+; CHECK: OpBitwiseAnd %[[#INT]] %[[#]] %[[#ONE]]
+; CHECK: OpINotEqual %[[#BOOL]] %[[#]] %[[#ZERO]]
+ %r = call i1 @llvm.umin.i1(i1 %a, i1 %b)
+ ret i1 %r
+}
+
+define spir_func i1 @test_umax_i1(i1 %a, i1 %b) {
+entry:
+; CHECK: OpExtInst %[[#INT]] %[[#]] u_max %[[#]] %[[#]]
+; CHECK: OpINotEqual %[[#BOOL]] %[[#]] %[[#ZERO]]
+ %r = call i1 @llvm.umax.i1(i1 %a, i1 %b)
+ ret i1 %r
+}
+
+define spir_func i1 @test_smin_i1(i1 %a, i1 %b) {
+entry:
+; CHECK: OpExtInst %[[#INT]] %[[#]] s_min %[[#]] %[[#]]
+; CHECK: OpINotEqual %[[#BOOL]] %[[#]] %[[#ZERO]]
+ %r = call i1 @llvm.smin.i1(i1 %a, i1 %b)
+ ret i1 %r
+}
+
+define spir_func i1 @test_smax_i1(i1 %a, i1 %b) {
+entry:
+; CHECK: OpExtInst %[[#INT]] %[[#]] s_max %[[#]] %[[#]]
+; CHECK: OpINotEqual %[[#BOOL]] %[[#]] %[[#ZERO]]
+ %r = call i1 @llvm.smax.i1(i1 %a, i1 %b)
+ ret i1 %r
+}
+
+define spir_func <2 x i1> @test_umin_v2i1(<2 x i1> %a, <2 x i1> %b) {
+entry:
+; CHECK: OpCompositeExtract %[[#BOOL]] %[[#]] 0
+; CHECK: OpCompositeExtract %[[#BOOL]] %[[#]] 1
+; CHECK: OpCompositeExtract %[[#BOOL]] %[[#]] 0
+; CHECK: OpCompositeExtract %[[#BOOL]] %[[#]] 1
+; CHECK: OpExtInst %[[#INT]] %[[#]] u_min %[[#]] %[[#]]
+; CHECK: OpINotEqual %[[#BOOL]] %[[#]] %[[#ZERO]]
+; CHECK: OpExtInst %[[#INT]] %[[#]] u_min %[[#]] %[[#]]
+; CHECK: OpINotEqual %[[#BOOL]] %[[#]] %[[#ZERO]]
+; CHECK: OpCompositeConstruct %[[#VBOOL]] %[[#]] %[[#]]
+ %r = call <2 x i1> @llvm.umin.v2i1(<2 x i1> %a, <2 x i1> %b)
+ ret <2 x i1> %r
+}
+
+declare i1 @llvm.umin.i1(i1, i1)
+declare i1 @llvm.umax.i1(i1, i1)
+declare i1 @llvm.smin.i1(i1, i1)
+declare i1 @llvm.smax.i1(i1, i1)
+declare <2 x i1> @llvm.umin.v2i1(<2 x i1>, <2 x i1>)
More information about the llvm-commits
mailing list