[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