[Mlir-commits] [mlir] [mlir][SPIR-V] Add OpenCL ctz, ilogb, and sincos operations (PR #231713)
Alexander Shaposhnikov
llvmlistbot at llvm.org
Tue Oct 13 22:18:46 PDT 2026
https://github.com/alexander-shaposhnikov updated https://github.com/llvm/llvm-project/pull/231713
>From e71296ec3cb9971e4c1b1522572f8b1f415eefd0 Mon Sep 17 00:00:00 2001
From: Alexander Shaposhnikov <alexander.v.shaposhnikov at gmail.com>
Date: Tue, 13 Oct 2026 17:26:33 -0700
Subject: [PATCH 1/2] [mlir][SPIR-V] Add OpenCL ctz, ilogb, and sincos
operations
Add spirv.CL.ctz, spirv.CL.ilogb, and spirv.CL.sincos with type and shape
verification and generated serialization support. Verify that sincos
writes through a pointer in an OpenCL-supported storage class.
Lower math.cttz to OpenCL.std ctz on Kernel targets. When narrow integers
are emulated using wider types, set a sentinel bit above the source width
to preserve the trailing-zero count, including the zero-input case.
Keep the existing GLSL lowering for Shader targets.
Add scalar and vector parsing, verification, conversion, and round-trip
tests, including native and emulated integer widths and invalid sincos
output pointers.
---
.../mlir/Dialect/SPIRV/IR/SPIRVCLOps.td | 111 ++++++++
.../Conversion/MathToSPIRV/MathToSPIRV.cpp | 46 +++-
mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp | 16 ++
.../MathToSPIRV/math-to-opencl-spirv.mlir | 44 ++++
mlir/test/Dialect/SPIRV/IR/ocl-ops.mlir | 242 ++++++++++++++++++
mlir/test/Target/SPIRV/ocl-ops.mlir | 28 ++
6 files changed, 485 insertions(+), 2 deletions(-)
diff --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVCLOps.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVCLOps.td
index 7a25c42d03e77..4b13c8d2d17d4 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVCLOps.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVCLOps.td
@@ -724,6 +724,44 @@ def SPIRV_CLFmaOp : SPIRV_CLTernaryArithmeticOp<"fma", 26, SPIRV_Float, [AlwaysS
// -----
+def SPIRV_CLIlogbOp : SPIRV_CLOp<"ilogb", 33, [Pure, SameOperandsAndResultShape]> {
+ let summary = "Return the exponent as an integer value.";
+
+ let description = [{
+ Result is the unbiased binary exponent of x, as a signed integer value.
+
+ The operand x must be a scalar or vector whose component type is
+ floating-point.
+
+ Result Type must be a scalar or vector whose component type is a 32-bit
+ integer. The number of components in x and Result Type must be the same.
+ Results are computed per component.
+
+ #### Example:
+
+ ```mlir
+ %2 = spirv.CL.ilogb %0 : f32 -> i32
+ %3 = spirv.CL.ilogb %1 : vector<3xf32> -> vector<3xi32>
+ ```
+ }];
+
+ let arguments = (ins
+ SPIRV_ScalarOrVectorOf<SPIRV_Float>:$x
+ );
+
+ let results = (outs
+ SPIRV_ScalarOrVectorOf<AnyI32>:$result
+ );
+
+ let assemblyFormat = [{
+ attr-dict $x `:` type($x) `->` type($result)
+ }];
+
+ let hasVerifier = 0;
+}
+
+// -----
+
def SPIRV_CLLdexpOp : SPIRV_CLOp<"ldexp", 34, [Pure, AllTypesMatch<["x", "y"]>]> {
let summary = "Builds y such that y = significand * 2^exponent.";
@@ -1138,6 +1176,31 @@ def SPIRV_CLClzOp : SPIRV_CLUnaryArithmeticOp<"clz", 151, SPIRV_Integer> {
// -----
+def SPIRV_CLCtzOp : SPIRV_CLUnaryArithmeticOp<"ctz", 152, SPIRV_Integer> {
+ let summary = "Count trailing zeros in operand";
+
+ let description = [{
+ Returns the number of least-significant 0-bits in x. If x is 0, the
+ result is the bit width of x, or of the component type of x if x is a
+ vector.
+
+ Result Type and x must be integer or vector(2,3,4,8,16) of
+ integer values.
+
+ All of the operands, including the Result Type operand, must be of the
+ same type.
+
+ #### Example:
+
+ ```mlir
+ %2 = spirv.CL.ctz %0 : i32
+ %3 = spirv.CL.ctz %1 : vector<3xi16>
+ ```
+ }];
+}
+
+// -----
+
def SPIRV_CLSMaxOp : SPIRV_CLBinaryArithmeticOp<"s_max", 156, SPIRV_Integer> {
let summary = "Return maximum of two signed integer operands";
@@ -1201,6 +1264,54 @@ def SPIRV_CLSinOp : SPIRV_CLUnaryArithmeticOp<"sin", 57, SPIRV_Float> {
// -----
+def SPIRV_CLSincosOp : SPIRV_CLOp<"sincos", 58, [
+ AllTypesMatch<["x", "result"]>,
+ TypesMatchWith<"`x` type matches pointee type of `cosval`",
+ "cosval", "x",
+ "::llvm::cast<::mlir::spirv::PointerType>($_self)"
+ ".getPointeeType()">]> {
+ let summary = "Compute sine and cosine of x radians.";
+
+ let description = [{
+ The computed sine is the return value and the computed cosine is written
+ to the memory that cosval points to.
+
+ The operand x must be a scalar or vector whose component type is
+ floating-point.
+
+ The cosval operand must be a pointer to a value of the same type as x,
+ in the CrossWorkgroup, Workgroup, Function, or Generic storage class.
+
+ Result Type must be the same type as the type of x. Results are computed
+ per component.
+
+ #### Example:
+
+ ```mlir
+ %sin = spirv.CL.sincos %x, %cosval : f32, !spirv.ptr<f32, Function> -> f32
+ %sin = spirv.CL.sincos %x, %cosval : vector<3xf32>,
+ !spirv.ptr<vector<3xf32>, Function> -> vector<3xf32>
+ ```
+ }];
+
+ let arguments = (ins
+ SPIRV_ScalarOrVectorOf<SPIRV_Float>:$x,
+ SPIRV_AnyPtr:$cosval
+ );
+
+ let results = (outs
+ SPIRV_ScalarOrVectorOf<SPIRV_Float>:$result
+ );
+
+ let assemblyFormat = [{
+ attr-dict $x `,` $cosval `:` type($x) `,` type($cosval) `->` type($result)
+ }];
+
+ let hasVerifier = 1;
+}
+
+// -----
+
def SPIRV_CLSinhOp : SPIRV_CLUnaryArithmeticOp<"sinh", 59, SPIRV_Float > {
let summary = "Compute hyperbolic sine of x radians.";
diff --git a/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp b/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp
index 094caf7fcc2b1..e74a9526e5d6e 100644
--- a/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp
+++ b/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp
@@ -251,6 +251,8 @@ struct CountLeadingZerosPattern final
/// Converts math.cttz to GL FindILsb. GL FindILsb returns -1 for a zero
/// input while math.cttz must return the bitwidth, so the zero case is
/// patched up with a select.
+///
+/// OpenCL targets use OpenCL.std ctz. This pattern handles the shader fallback.
struct CountTrailingZerosPattern final
: public OpConversionPattern<math::CountTrailingZerosOp> {
using Base::Base;
@@ -292,6 +294,47 @@ struct CountTrailingZerosPattern final
}
};
+/// Converts math.cttz to OpenCL.std ctz, preserving the source bit width when
+/// narrow integers are emulated using wider integers.
+struct CountTrailingZerosOpenCLPattern final
+ : public OpConversionPattern<math::CountTrailingZerosOp> {
+ using Base::Base;
+
+ LogicalResult
+ matchAndRewrite(math::CountTrailingZerosOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ if (failed(checkSourceOpTypes(rewriter, op)))
+ return failure();
+
+ auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
+ if (!typeConverter.getTargetEnv().allows(spirv::Capability::Kernel))
+ return rewriter.notifyMatchFailure(op, "requires Kernel capability");
+ Type type = typeConverter.convertType(op.getType());
+ if (!type)
+ return failure();
+
+ Type elementType = getElementTypeOrSelf(type);
+ unsigned sourceWidth =
+ getElementTypeOrSelf(op.getType()).getIntOrFloatBitWidth();
+ unsigned targetWidth = elementType.getIntOrFloatBitWidth();
+ Value input = adaptor.getOperand();
+ if (sourceWidth < targetWidth) {
+ // Set the bit immediately above the original value. This bounds the
+ // count by sourceWidth, even when the emulated value has nonzero high
+ // bits, and gives the correct result for a zero source value.
+ Attribute sentinel = IntegerAttr::get(
+ elementType, APInt::getOneBitSet(targetWidth, sourceWidth));
+ if (auto vectorType = dyn_cast<VectorType>(type))
+ sentinel = SplatElementsAttr::get(vectorType, sentinel);
+ Value mask =
+ spirv::ConstantOp::create(rewriter, op.getLoc(), type, sentinel);
+ input = spirv::BitwiseOrOp::create(rewriter, op.getLoc(), input, mask);
+ }
+ rewriter.replaceOpWithNewOp<spirv::CLCtzOp>(op, type, input);
+ return success();
+ }
+};
+
/// Converts math.expm1 to SPIR-V ops.
///
/// SPIR-V does not have a direct operations for exp(x)-1. Explicitly lower to
@@ -629,7 +672,6 @@ struct RoundOpPattern final : public OpConversionPattern<math::RoundOp> {
return success();
}
};
-
} // namespace
//===----------------------------------------------------------------------===//
@@ -691,6 +733,7 @@ void populateMathToSPIRVPatterns(const SPIRVTypeConverter &typeConverter,
CheckedElementwiseOpPattern<math::AbsFOp, spirv::CLFAbsOp>,
CheckedElementwiseOpPattern<math::AbsIOp, spirv::CLSAbsOp>,
CheckedElementwiseOpPattern<math::CountLeadingZerosOp, spirv::CLClzOp>,
+ CountTrailingZerosOpenCLPattern,
CheckedElementwiseOpPattern<math::AtanOp, spirv::CLAtanOp>,
CheckedElementwiseOpPattern<math::Atan2Op, spirv::CLAtan2Op>,
CheckedElementwiseOpPattern<math::CbrtOp, spirv::CLCbrtOp>,
@@ -724,5 +767,4 @@ void populateMathToSPIRVPatterns(const SPIRVTypeConverter &typeConverter,
CheckedElementwiseOpPattern<math::AtanhOp, spirv::CLAtanhOp>>(
typeConverter, patterns.getContext());
}
-
} // namespace mlir
diff --git a/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
index 37ae6a942e491..5204e36997109 100644
--- a/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
@@ -2291,6 +2291,22 @@ LogicalResult spirv::CLRootnOp::verify() {
getN().getType());
}
+//===----------------------------------------------------------------------===//
+// spirv.CL.sincos
+//===----------------------------------------------------------------------===//
+
+LogicalResult spirv::CLSincosOp::verify() {
+ auto storageClass =
+ cast<spirv::PointerType>(getCosval().getType()).getStorageClass();
+ if (storageClass != spirv::StorageClass::CrossWorkgroup &&
+ storageClass != spirv::StorageClass::Workgroup &&
+ storageClass != spirv::StorageClass::Function &&
+ storageClass != spirv::StorageClass::Generic)
+ return emitOpError("cosval must point to CrossWorkgroup, Workgroup, "
+ "Function, or Generic storage");
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// spirv.ShiftLeftLogicalOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir b/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir
index faa509851a2e2..66b0068fd8296 100644
--- a/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir
+++ b/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir
@@ -183,6 +183,8 @@ func.func @int_unary(%arg0: i32) {
%0 = math.absi %arg0 : i32
// CHECK: spirv.CL.clz %{{.*}} : i32
%1 = math.ctlz %arg0 : i32
+ // CHECK: spirv.CL.ctz %{{.*}} : i32
+ %2 = math.cttz %arg0 : i32
return
}
@@ -190,6 +192,8 @@ func.func @int_unary(%arg0: i32) {
func.func @int_unary_vector(%arg0: vector<2xi32>) {
// CHECK: spirv.CL.clz %{{.*}} : vector<2xi32>
%0 = math.ctlz %arg0 : vector<2xi32>
+ // CHECK: spirv.CL.ctz %{{.*}} : vector<2xi32>
+ %1 = math.cttz %arg0 : vector<2xi32>
return
}
@@ -268,3 +272,43 @@ func.func @sincos_vector(%arg0: vector<3xf32>) {
}
} // end module
+
+// -----
+
+// Emulating narrow types must preserve cttz(0) == source bit width.
+module attributes { spirv.target_env = #spirv.target_env<#spirv.vce<v1.0, [Kernel], []>, #spirv.resource_limits<>> } {
+ // CHECK-LABEL: @cttz_emulated_i8
+ func.func @cttz_emulated_i8(%arg0: i8) -> i8 {
+ // CHECK: [[INPUT:%.*]] = builtin.unrealized_conversion_cast %arg0 : i8 to i32
+ // CHECK: [[SENTINEL:%.*]] = spirv.Constant 256 : i32
+ // CHECK: [[BOUNDED:%.*]] = spirv.BitwiseOr [[INPUT]], [[SENTINEL]] : i32
+ // CHECK: spirv.CL.ctz [[BOUNDED]] : i32
+ %0 = math.cttz %arg0 : i8
+ return %0 : i8
+ }
+ // CHECK-LABEL: @cttz_emulated_vector
+ func.func @cttz_emulated_vector(%arg0: vector<2xi16>) -> vector<2xi16> {
+ // CHECK: [[INPUT:%.*]] = builtin.unrealized_conversion_cast %arg0 : vector<2xi16> to vector<2xi32>
+ // CHECK: [[SENTINEL:%.*]] = spirv.Constant dense<65536> : vector<2xi32>
+ // CHECK: [[BOUNDED:%.*]] = spirv.BitwiseOr [[INPUT]], [[SENTINEL]] : vector<2xi32>
+ // CHECK: spirv.CL.ctz [[BOUNDED]] : vector<2xi32>
+ %0 = math.cttz %arg0 : vector<2xi16>
+ return %0 : vector<2xi16>
+ }
+}
+
+// -----
+
+module attributes { spirv.target_env = #spirv.target_env<#spirv.vce<v1.0, [Kernel, Int8, Int16, Int64], []>, #spirv.resource_limits<>> } {
+ // CHECK-LABEL: @cttz_native_widths
+ func.func @cttz_native_widths(%a: i8, %b: vector<2xi16>, %c: i64) {
+ // CHECK-NOT: spirv.BitwiseOr
+ // CHECK: spirv.CL.ctz %{{.*}} : i8
+ %0 = math.cttz %a : i8
+ // CHECK: spirv.CL.ctz %{{.*}} : vector<2xi16>
+ %1 = math.cttz %b : vector<2xi16>
+ // CHECK: spirv.CL.ctz %{{.*}} : i64
+ %2 = math.cttz %c : i64
+ return
+ }
+}
diff --git a/mlir/test/Dialect/SPIRV/IR/ocl-ops.mlir b/mlir/test/Dialect/SPIRV/IR/ocl-ops.mlir
index 468a710c13a91..82f8ea0fd62a2 100644
--- a/mlir/test/Dialect/SPIRV/IR/ocl-ops.mlir
+++ b/mlir/test/Dialect/SPIRV/IR/ocl-ops.mlir
@@ -418,6 +418,56 @@ func.func @clz(%arg0 : i32) -> () {
// -----
+//===----------------------------------------------------------------------===//
+// spirv.CL.ctz
+//===----------------------------------------------------------------------===//
+
+func.func @ctz(%arg0 : i32) -> () {
+ // CHECK: spirv.CL.ctz {{%.*}} : i32
+ %2 = spirv.CL.ctz %arg0 : i32
+ return
+}
+
+func.func @ctzvec(%arg0 : vector<3xi16>) -> () {
+ // CHECK: spirv.CL.ctz {{%.*}} : vector<3xi16>
+ %2 = spirv.CL.ctz %arg0 : vector<3xi16>
+ return
+}
+
+// -----
+
+func.func @ctz(%arg0 : f32) -> () {
+ // expected-error @+1 {{op operand #0 must be 8/16/32/64-bit integer or fixed-length vector of 8/16/32/64-bit integer values}}
+ %2 = spirv.CL.ctz %arg0 : f32
+ return
+}
+
+// -----
+
+func.func @ctz(%arg0 : vector<5xi32>) -> () {
+ // expected-error @+1 {{op operand #0 must be 8/16/32/64-bit integer or fixed-length vector of 8/16/32/64-bit integer values of length 2/3/4}}
+ %2 = spirv.CL.ctz %arg0 : vector<5xi32>
+ return
+}
+
+// -----
+
+func.func @ctz(%arg0 : i32, %arg1 : i32) -> () {
+ // expected-error @+1 {{expected ':'}}
+ %2 = spirv.CL.ctz %arg0, %arg1 : i32
+ return
+}
+
+// -----
+
+func.func @ctz(%arg0 : i32) -> () {
+ // expected-error @+1 {{expected non-function type}}
+ %2 = spirv.CL.ctz %arg0 :
+ return
+}
+
+// -----
+
//===----------------------------------------------------------------------===//
// spirv.CL.fma
//===----------------------------------------------------------------------===//
@@ -796,6 +846,90 @@ func.func @atan2(%arg0 : vector<4xf16>, %arg1 : vector<4xf16>) -> () {
// -----
+//===----------------------------------------------------------------------===//
+// spirv.CL.ilogb
+//===----------------------------------------------------------------------===//
+
+func.func @ilogb(%arg0 : f32) -> () {
+ // CHECK: {{%.*}} = spirv.CL.ilogb {{%.*}} : f32 -> i32
+ %0 = spirv.CL.ilogb %arg0 : f32 -> i32
+ return
+}
+
+// -----
+
+func.func @ilogb_vec(%arg0 : vector<3xf16>) -> () {
+ // CHECK: {{%.*}} = spirv.CL.ilogb {{%.*}} : vector<3xf16> -> vector<3xi32>
+ %0 = spirv.CL.ilogb %arg0 : vector<3xf16> -> vector<3xi32>
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_operand_type(%arg0 : i32) -> () {
+ // expected-error @+1 {{op operand #0 must be 16/32/64-bit float or fixed-length vector of 16/32/64-bit float values}}
+ %0 = spirv.CL.ilogb %arg0 : i32 -> i32
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_result_type(%arg0 : f32) -> () {
+ // expected-error @+1 {{op result #0 must be 32-bit integer or fixed-length vector of 32-bit integer values}}
+ %0 = spirv.CL.ilogb %arg0 : f32 -> f32
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_result_width(%arg0 : f64) -> () {
+ // expected-error @+1 {{op result #0 must be 32-bit integer or fixed-length vector of 32-bit integer values}}
+ %0 = spirv.CL.ilogb %arg0 : f64 -> i64
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_result_width_vec(%arg0 : vector<3xf32>) -> () {
+ // expected-error @+1 {{op result #0 must be 32-bit integer or fixed-length vector of 32-bit integer values}}
+ %0 = spirv.CL.ilogb %arg0 : vector<3xf32> -> vector<3xi16>
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_shape_scalar(%arg0 : f32) -> () {
+ // expected-error @+1 {{op requires the same shape for all operands and results}}
+ %0 = spirv.CL.ilogb %arg0 : f32 -> vector<2xi32>
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_shape_vec_1(%arg0 : vector<3xf32>) -> () {
+ // expected-error @+1 {{op requires the same shape for all operands and results}}
+ %0 = spirv.CL.ilogb %arg0 : vector<3xf32> -> i32
+ return
+}
+
+// -----
+
+func.func @ilogb_wrong_shape_vec_2(%arg0 : vector<3xf32>) -> () {
+ // expected-error @+1 {{op requires the same shape for all operands and results}}
+ %0 = spirv.CL.ilogb %arg0 : vector<3xf32> -> vector<2xi32>
+ return
+}
+
+// -----
+
+func.func @ilogb_missing_result_type(%arg0 : f32) -> () {
+ // expected-error @+1 {{expected '->'}}
+ %0 = spirv.CL.ilogb %arg0 : f32
+ return
+}
+
+// -----
+
//===----------------------------------------------------------------------===//
// spirv.CL.ldexp
//===----------------------------------------------------------------------===//
@@ -912,6 +1046,74 @@ func.func @rootn_wrong_type_vec(%arg0 : vector<3xf32>, %arg1 : vector<2xi32>) ->
// -----
+//===----------------------------------------------------------------------===//
+// spirv.CL.sincos
+//===----------------------------------------------------------------------===//
+
+func.func @sincos(%arg0 : f32, %arg1 : !spirv.ptr<f32, Function>) -> () {
+ // CHECK: {{%.*}} = spirv.CL.sincos {{%.*}}, {{%.*}} : f32, !spirv.ptr<f32, Function> -> f32
+ %0 = spirv.CL.sincos %arg0, %arg1 : f32, !spirv.ptr<f32, Function> -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_vec(%arg0 : vector<3xf16>, %arg1 : !spirv.ptr<vector<3xf16>, Function>) -> () {
+ // CHECK: {{%.*}} = spirv.CL.sincos {{%.*}}, {{%.*}} : vector<3xf16>, !spirv.ptr<vector<3xf16>, Function> -> vector<3xf16>
+ %0 = spirv.CL.sincos %arg0, %arg1 : vector<3xf16>, !spirv.ptr<vector<3xf16>, Function> -> vector<3xf16>
+ return
+}
+
+// -----
+
+func.func @sincos_wrong_operand_type(%arg0 : i32, %arg1 : !spirv.ptr<i32, Function>) -> () {
+ // expected-error @+1 {{op operand #0 must be 16/32/64-bit float or fixed-length vector of 16/32/64-bit float values}}
+ %0 = spirv.CL.sincos %arg0, %arg1 : i32, !spirv.ptr<i32, Function> -> i32
+ return
+}
+
+// -----
+
+func.func @sincos_cosval_not_pointer(%arg0 : f32, %arg1 : f32) -> () {
+ // expected-error @+1 {{op operand #1 must be any SPIR-V pointer type, but got 'f32'}}
+ %0 = spirv.CL.sincos %arg0, %arg1 : f32, f32 -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_wrong_pointee_type(%arg0 : f32, %arg1 : !spirv.ptr<f64, Function>) -> () {
+ // expected-error @+1 {{op failed to verify that `x` type matches pointee type of `cosval`}}
+ %0 = spirv.CL.sincos %arg0, %arg1 : f32, !spirv.ptr<f64, Function> -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_wrong_pointee_shape(%arg0 : vector<3xf32>, %arg1 : !spirv.ptr<f32, Function>) -> () {
+ // expected-error @+1 {{op failed to verify that `x` type matches pointee type of `cosval`}}
+ %0 = spirv.CL.sincos %arg0, %arg1 : vector<3xf32>, !spirv.ptr<f32, Function> -> vector<3xf32>
+ return
+}
+
+// -----
+
+func.func @sincos_wrong_result_type(%arg0 : f32, %arg1 : !spirv.ptr<f32, Function>) -> () {
+ // expected-error @+1 {{op failed to verify that all of {x, result} have same type}}
+ %0 = spirv.CL.sincos %arg0, %arg1 : f32, !spirv.ptr<f32, Function> -> f64
+ return
+}
+
+// -----
+
+func.func @sincos_missing_cosval(%arg0 : f32) -> () {
+ // expected-error @+1 {{expected ','}}
+ %0 = spirv.CL.sincos %arg0 : f32 -> f32
+ return
+}
+
+// -----
+
//===----------------------------------------------------------------------===//
// spirv.CL.copysign
//===----------------------------------------------------------------------===//
@@ -1029,3 +1231,43 @@ func.func @log1p(%arg0 : vector<4xf16>) -> () {
%2 = spirv.CL.log1p %arg0 : vector<4xf16>
return
}
+
+// -----
+
+func.func @sincos_CrossWorkgroup(%x: f32, %p: !spirv.ptr<f32, CrossWorkgroup>) {
+ // CHECK: spirv.CL.sincos {{%.*}}, {{%.*}} : f32, !spirv.ptr<f32, CrossWorkgroup> -> f32
+ %0 = spirv.CL.sincos %x, %p : f32, !spirv.ptr<f32, CrossWorkgroup> -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_Workgroup(%x: f32, %p: !spirv.ptr<f32, Workgroup>) {
+ // CHECK: spirv.CL.sincos {{%.*}}, {{%.*}} : f32, !spirv.ptr<f32, Workgroup> -> f32
+ %0 = spirv.CL.sincos %x, %p : f32, !spirv.ptr<f32, Workgroup> -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_Generic(%x: f32, %p: !spirv.ptr<f32, Generic>) {
+ // CHECK: spirv.CL.sincos {{%.*}}, {{%.*}} : f32, !spirv.ptr<f32, Generic> -> f32
+ %0 = spirv.CL.sincos %x, %p : f32, !spirv.ptr<f32, Generic> -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_invalid_UniformConstant(%x: f32, %p: !spirv.ptr<f32, UniformConstant>) {
+ // expected-error @+1 {{cosval must point to CrossWorkgroup, Workgroup, Function, or Generic storage}}
+ %0 = spirv.CL.sincos %x, %p : f32, !spirv.ptr<f32, UniformConstant> -> f32
+ return
+}
+
+// -----
+
+func.func @sincos_invalid_Private(%x: f32, %p: !spirv.ptr<f32, Private>) {
+ // expected-error @+1 {{cosval must point to CrossWorkgroup, Workgroup, Function, or Generic storage}}
+ %0 = spirv.CL.sincos %x, %p : f32, !spirv.ptr<f32, Private> -> f32
+ return
+}
diff --git a/mlir/test/Target/SPIRV/ocl-ops.mlir b/mlir/test/Target/SPIRV/ocl-ops.mlir
index b94c8ab1e8ee0..33d0da6421200 100644
--- a/mlir/test/Target/SPIRV/ocl-ops.mlir
+++ b/mlir/test/Target/SPIRV/ocl-ops.mlir
@@ -63,6 +63,14 @@ spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Kernel, Addresses, Vec
%0 = spirv.CL.s_abs %arg0 : i32
// CHECK: {{%.*}} = spirv.CL.clz {{%.*}} : i32
%1 = spirv.CL.clz %arg0 : i32
+ // CHECK: {{%.*}} = spirv.CL.ctz {{%.*}} : i32
+ %2 = spirv.CL.ctz %arg0 : i32
+ spirv.Return
+ }
+
+ spirv.func @integer_vec_insts(%arg0 : vector<3xi32>) "None" {
+ // CHECK: {{%.*}} = spirv.CL.ctz {{%.*}} : vector<3xi32>
+ %0 = spirv.CL.ctz %arg0 : vector<3xi32>
spirv.Return
}
@@ -98,6 +106,26 @@ spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Kernel, Addresses, Vec
spirv.Return
}
+ spirv.func @ilogb(%arg0 : f32, %arg1 : vector<3xf32>) "None" {
+ // CHECK: {{%.*}} = spirv.CL.ilogb {{%.*}} : f32 -> i32
+ %0 = spirv.CL.ilogb %arg0 : f32 -> i32
+ // CHECK: {{%.*}} = spirv.CL.ilogb {{%.*}} : vector<3xf32> -> vector<3xi32>
+ %1 = spirv.CL.ilogb %arg1 : vector<3xf32> -> vector<3xi32>
+ spirv.Return
+ }
+
+ spirv.func @sincos(%arg0 : f32, %arg1 : vector<3xf32>) "None" {
+ // CHECK: [[COS:%.*]] = spirv.Variable : !spirv.ptr<f32, Function>
+ // CHECK: [[COSVEC:%.*]] = spirv.Variable : !spirv.ptr<vector<3xf32>, Function>
+ %cos = spirv.Variable : !spirv.ptr<f32, Function>
+ %cosvec = spirv.Variable : !spirv.ptr<vector<3xf32>, Function>
+ // CHECK: {{%.*}} = spirv.CL.sincos {{%.*}}, [[COS]] : f32, !spirv.ptr<f32, Function> -> f32
+ %0 = spirv.CL.sincos %arg0, %cos : f32, !spirv.ptr<f32, Function> -> f32
+ // CHECK: {{%.*}} = spirv.CL.sincos {{%.*}}, [[COSVEC]] : vector<3xf32>, !spirv.ptr<vector<3xf32>, Function> -> vector<3xf32>
+ %1 = spirv.CL.sincos %arg1, %cosvec : vector<3xf32>, !spirv.ptr<vector<3xf32>, Function> -> vector<3xf32>
+ spirv.Return
+ }
+
spirv.func @maxmin(%arg0 : f32, %arg1 : f32, %arg2 : i32, %arg3 : i32) "None" {
// CHECK: {{%.*}} = spirv.CL.fmax {{%.*}}, {{%.*}} : f32
%1 = spirv.CL.fmax %arg0, %arg1 : f32
>From a8b5cd9a84e7b32ce00371c2258db3a6e598c4ec Mon Sep 17 00:00:00 2001
From: Alexander Shaposhnikov <alexander.v.shaposhnikov at gmail.com>
Date: Tue, 13 Oct 2026 22:18:33 -0700
Subject: [PATCH 2/2] Address comments
---
.../Conversion/MathToSPIRV/MathToSPIRV.cpp | 15 ++++++--
.../MathToSPIRV/math-to-opencl-spirv.mlir | 37 ++++++++++++++++++-
2 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp b/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp
index e74a9526e5d6e..147d3c328f786 100644
--- a/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp
+++ b/mlir/lib/Conversion/MathToSPIRV/MathToSPIRV.cpp
@@ -313,11 +313,20 @@ struct CountTrailingZerosOpenCLPattern final
if (!type)
return failure();
+ Value input = adaptor.getOperand();
Type elementType = getElementTypeOrSelf(type);
- unsigned sourceWidth =
- getElementTypeOrSelf(op.getType()).getIntOrFloatBitWidth();
+ if (elementType.isInteger(1)) {
+ // cttz(false) is true and cttz(true) is false.
+ rewriter.replaceOpWithNewOp<spirv::LogicalNotOp>(op, type, input);
+ return success();
+ }
+
+ Type sourceElementType = getElementTypeOrSelf(op.getType());
unsigned targetWidth = elementType.getIntOrFloatBitWidth();
- Value input = adaptor.getOperand();
+ // Index values use the bit width chosen by the type converter.
+ unsigned sourceWidth = sourceElementType.isIndex()
+ ? targetWidth
+ : sourceElementType.getIntOrFloatBitWidth();
if (sourceWidth < targetWidth) {
// Set the bit immediately above the original value. This bounds the
// count by sourceWidth, even when the emulated value has nonzero high
diff --git a/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir b/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir
index 66b0068fd8296..22248987fe0ee 100644
--- a/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir
+++ b/mlir/test/Conversion/MathToSPIRV/math-to-opencl-spirv.mlir
@@ -197,6 +197,42 @@ func.func @int_unary_vector(%arg0: vector<2xi32>) {
return
}
+// CHECK-LABEL: @cttz_i1
+func.func @cttz_i1(%arg0: i1) -> i1 {
+ // CHECK: [[RESULT:%.*]] = spirv.LogicalNot %arg0 : i1
+ // CHECK: return [[RESULT]] : i1
+ %0 = math.cttz %arg0 : i1
+ return %0 : i1
+}
+
+// CHECK-LABEL: @cttz_i1_vector
+func.func @cttz_i1_vector(%arg0: vector<2xi1>) -> vector<2xi1> {
+ // CHECK: [[RESULT:%.*]] = spirv.LogicalNot %arg0 : vector<2xi1>
+ // CHECK: return [[RESULT]] : vector<2xi1>
+ %0 = math.cttz %arg0 : vector<2xi1>
+ return %0 : vector<2xi1>
+}
+
+// CHECK-LABEL: @cttz_index
+func.func @cttz_index(%arg0: index) -> index {
+ // CHECK: [[INPUT:%.*]] = builtin.unrealized_conversion_cast %arg0 : index to i32
+ // CHECK: [[RESULT:%.*]] = spirv.CL.ctz [[INPUT]] : i32
+ // CHECK: [[CAST:%.*]] = builtin.unrealized_conversion_cast [[RESULT]] : i32 to index
+ // CHECK: return [[CAST]] : index
+ %0 = math.cttz %arg0 : index
+ return %0 : index
+}
+
+// CHECK-LABEL: @cttz_index_vector
+func.func @cttz_index_vector(%arg0: vector<2xindex>) -> vector<2xindex> {
+ // CHECK: [[INPUT:%.*]] = builtin.unrealized_conversion_cast %arg0 : vector<2xindex> to vector<2xi32>
+ // CHECK: [[RESULT:%.*]] = spirv.CL.ctz [[INPUT]] : vector<2xi32>
+ // CHECK: [[CAST:%.*]] = builtin.unrealized_conversion_cast [[RESULT]] : vector<2xi32> to vector<2xindex>
+ // CHECK: return [[CAST]] : vector<2xindex>
+ %0 = math.cttz %arg0 : vector<2xindex>
+ return %0 : vector<2xindex>
+}
+
} // end module
// -----
@@ -302,7 +338,6 @@ module attributes { spirv.target_env = #spirv.target_env<#spirv.vce<v1.0, [Kerne
module attributes { spirv.target_env = #spirv.target_env<#spirv.vce<v1.0, [Kernel, Int8, Int16, Int64], []>, #spirv.resource_limits<>> } {
// CHECK-LABEL: @cttz_native_widths
func.func @cttz_native_widths(%a: i8, %b: vector<2xi16>, %c: i64) {
- // CHECK-NOT: spirv.BitwiseOr
// CHECK: spirv.CL.ctz %{{.*}} : i8
%0 = math.cttz %a : i8
// CHECK: spirv.CL.ctz %{{.*}} : vector<2xi16>
More information about the Mlir-commits
mailing list