| //===- MathToSPIRV.cpp - Math to SPIR-V Patterns --------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements patterns to convert Math dialect to SPIR-V dialect. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "../SPIRVCommon/Pattern.h" | |
| #include "mlir/Dialect/Math/IR/Math.h" | |
| #include "mlir/Dialect/SPIRV/IR/SPIRVOps.h" | |
| #include "mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h" | |
| #include "mlir/IR/BuiltinTypes.h" | |
| #include "mlir/IR/Matchers.h" | |
| #include "mlir/IR/TypeUtilities.h" | |
| #include "mlir/Transforms/DialectConversion.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/TypeSwitch.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #define DEBUG_TYPE "math-to-spirv-pattern" | |
| using namespace mlir; | |
| //===----------------------------------------------------------------------===// | |
| // Utility functions | |
| //===----------------------------------------------------------------------===// | |
| /// Creates a 32-bit scalar/vector integer constant. Returns nullptr if the | |
| /// given type is not a 32-bit scalar/vector type. | |
| static Value getScalarOrVectorI32Constant(Type type, int value, | |
| OpBuilder &builder, Location loc) { | |
| if (auto vectorType = dyn_cast<VectorType>(type)) { | |
| if (!vectorType.getElementType().isInteger(32)) | |
| return nullptr; | |
| SmallVector<int> values(vectorType.getNumElements(), value); | |
| return spirv::ConstantOp::create(builder, loc, type, | |
| builder.getI32VectorAttr(values)); | |
| } | |
| if (type.isInteger(32)) | |
| return spirv::ConstantOp::create(builder, loc, type, | |
| builder.getI32IntegerAttr(value)); | |
| return nullptr; | |
| } | |
| /// Check if the type is supported by math-to-spirv conversion. We expect to | |
| /// only see scalars and vectors at this point, with higher-level types already | |
| /// lowered. | |
| static bool isSupportedSourceType(Type originalType) { | |
| if (originalType.isIntOrIndexOrFloat()) | |
| return true; | |
| if (auto vecTy = dyn_cast<VectorType>(originalType)) { | |
| if (!vecTy.getElementType().isIntOrIndexOrFloat()) | |
| return false; | |
| if (vecTy.isScalable()) | |
| return false; | |
| if (vecTy.getRank() > 1) | |
| return false; | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Check if all `sourceOp` types are supported by math-to-spirv conversion. | |
| /// Notify of a match failure othwerise and return a `failure` result. | |
| /// This is intended to simplify type checks in `OpConversionPattern`s. | |
| static LogicalResult checkSourceOpTypes(ConversionPatternRewriter &rewriter, | |
| Operation *sourceOp) { | |
| auto allTypes = llvm::to_vector(sourceOp->getOperandTypes()); | |
| llvm::append_range(allTypes, sourceOp->getResultTypes()); | |
| for (Type ty : allTypes) { | |
| if (!isSupportedSourceType(ty)) { | |
| return rewriter.notifyMatchFailure( | |
| sourceOp, | |
| llvm::formatv( | |
| "unsupported source type for Math to SPIR-V conversion: {0}", | |
| ty)); | |
| } | |
| } | |
| return success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Operation conversion | |
| //===----------------------------------------------------------------------===// | |
| // Note that DRR cannot be used for the patterns in this file: we may need to | |
| // convert type along the way, which requires ConversionPattern. DRR generates | |
| // normal RewritePattern. | |
| namespace { | |
| /// Converts elementwise unary, binary, and ternary standard operations to | |
| /// SPIR-V operations. Checks that source `Op` types are supported. | |
| template <typename Op, typename SPIRVOp> | |
| struct CheckedElementwiseOpPattern final | |
| : public spirv::ElementwiseOpPattern<Op, SPIRVOp> { | |
| using BasePattern = typename spirv::ElementwiseOpPattern<Op, SPIRVOp>; | |
| using BasePattern::BasePattern; | |
| LogicalResult | |
| matchAndRewrite(Op op, typename Op::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, op); failed(res)) | |
| return res; | |
| return BasePattern::matchAndRewrite(op, adaptor, rewriter); | |
| } | |
| }; | |
| /// Converts math.copysign to SPIR-V ops. | |
| struct CopySignPattern final : public OpConversionPattern<math::CopySignOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::CopySignOp copySignOp, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, copySignOp); | |
| failed(res)) | |
| return res; | |
| // Defer to the CL copysign op on Kernel targets. | |
| auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>(); | |
| if (typeConverter.getTargetEnv().allows(spirv::Capability::Kernel)) | |
| return rewriter.notifyMatchFailure(copySignOp, | |
| "Kernel target has native CL op"); | |
| Type type = getTypeConverter()->convertType(copySignOp.getType()); | |
| if (!type) | |
| return failure(); | |
| FloatType floatType; | |
| if (auto scalarType = dyn_cast<FloatType>(copySignOp.getType())) { | |
| floatType = scalarType; | |
| } else if (auto vectorType = dyn_cast<VectorType>(copySignOp.getType())) { | |
| floatType = cast<FloatType>(vectorType.getElementType()); | |
| } else { | |
| return failure(); | |
| } | |
| Location loc = copySignOp.getLoc(); | |
| int bitwidth = floatType.getWidth(); | |
| Type intType = rewriter.getIntegerType(bitwidth); | |
| uint64_t intValue = uint64_t(1) << (bitwidth - 1); | |
| Value signMask = spirv::ConstantOp::create( | |
| rewriter, loc, intType, rewriter.getIntegerAttr(intType, intValue)); | |
| Value valueMask = spirv::ConstantOp::create( | |
| rewriter, loc, intType, | |
| rewriter.getIntegerAttr(intType, intValue - 1u)); | |
| if (auto vectorType = dyn_cast<VectorType>(type)) { | |
| assert(vectorType.getRank() == 1); | |
| int count = vectorType.getNumElements(); | |
| intType = VectorType::get(count, intType); | |
| Repeated<Value> signSplat(count, signMask); | |
| signMask = spirv::CompositeConstructOp::create(rewriter, loc, intType, | |
| signSplat); | |
| Repeated<Value> valueSplat(count, valueMask); | |
| valueMask = spirv::CompositeConstructOp::create(rewriter, loc, intType, | |
| valueSplat); | |
| } | |
| Value lhsCast = | |
| spirv::BitcastOp::create(rewriter, loc, intType, adaptor.getLhs()); | |
| Value rhsCast = | |
| spirv::BitcastOp::create(rewriter, loc, intType, adaptor.getRhs()); | |
| Value value = spirv::BitwiseAndOp::create(rewriter, loc, intType, | |
| ValueRange{lhsCast, valueMask}); | |
| Value sign = spirv::BitwiseAndOp::create(rewriter, loc, intType, | |
| ValueRange{rhsCast, signMask}); | |
| Value result = spirv::BitwiseOrOp::create(rewriter, loc, intType, | |
| ValueRange{value, sign}); | |
| rewriter.replaceOpWithNewOp<spirv::BitcastOp>(copySignOp, type, result); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.ctlz to SPIR-V ops. | |
| /// | |
| /// OpenCL targets lower math.ctlz directly to OpenCL.std clz via the generic | |
| /// elementwise pattern. This pattern handles the shader fallback. | |
| /// | |
| /// SPIR-V does not have a direct operations for counting leading zeros for | |
| /// glsl. If Shader capability is supported, we can leverage GL FindUMsb to | |
| /// calculate it. | |
| struct CountLeadingZerosPattern final | |
| : public OpConversionPattern<math::CountLeadingZerosOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::CountLeadingZerosOp countOp, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, countOp); failed(res)) | |
| return res; | |
| Type type = getTypeConverter()->convertType(countOp.getType()); | |
| if (!type) | |
| return failure(); | |
| auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>(); | |
| if (!typeConverter.getTargetEnv().allows(spirv::Capability::Shader)) | |
| return rewriter.notifyMatchFailure(countOp, "requires Shader capability"); | |
| // The GL FindUMsb fallback only supports 32-bit integer types for now. | |
| unsigned bitwidth = 0; | |
| if (isa<IntegerType>(type)) | |
| bitwidth = type.getIntOrFloatBitWidth(); | |
| if (auto vectorType = dyn_cast<VectorType>(type)) | |
| bitwidth = vectorType.getElementTypeBitWidth(); | |
| if (bitwidth != 32) | |
| return failure(); | |
| Location loc = countOp.getLoc(); | |
| Value input = adaptor.getOperand(); | |
| Value val1 = getScalarOrVectorI32Constant(type, 1, rewriter, loc); | |
| Value val31 = getScalarOrVectorI32Constant(type, 31, rewriter, loc); | |
| Value val32 = getScalarOrVectorI32Constant(type, 32, rewriter, loc); | |
| Value msb = spirv::GLFindUMsbOp::create(rewriter, loc, input); | |
| // We need to subtract from 31 given that the index returned by GLSL | |
| // FindUMsb is counted from the least significant bit. Theoretically this | |
| // also gives the correct result even if the integer has all zero bits, in | |
| // which case GL FindUMsb would return -1. | |
| Value subMsb = spirv::ISubOp::create(rewriter, loc, val31, msb); | |
| // However, certain Vulkan implementations have driver bugs for the corner | |
| // case where the input is zero. And.. it can be smart to optimize a select | |
| // only involving the corner case. So separately compute the result when the | |
| // input is either zero or one. | |
| Value subInput = spirv::ISubOp::create(rewriter, loc, val32, input); | |
| Value cmp = spirv::ULessThanEqualOp::create(rewriter, loc, input, val1); | |
| rewriter.replaceOpWithNewOp<spirv::SelectOp>(countOp, cmp, subInput, | |
| subMsb); | |
| return success(); | |
| } | |
| }; | |
| /// 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; | |
| LogicalResult | |
| matchAndRewrite(math::CountTrailingZerosOp countOp, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, countOp); failed(res)) | |
| return res; | |
| Type type = getTypeConverter()->convertType(countOp.getType()); | |
| if (!type) | |
| return failure(); | |
| auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>(); | |
| if (!typeConverter.getTargetEnv().allows(spirv::Capability::Shader)) | |
| return rewriter.notifyMatchFailure(countOp, "requires Shader capability"); | |
| unsigned bitwidth = 0; | |
| if (isa<IntegerType>(type)) | |
| bitwidth = type.getIntOrFloatBitWidth(); | |
| else if (auto vectorType = dyn_cast<VectorType>(type)) | |
| bitwidth = vectorType.getElementTypeBitWidth(); | |
| Location loc = countOp.getLoc(); | |
| Value input = adaptor.getOperand(); | |
| Value val0 = spirv::ConstantOp::getZero(type, loc, rewriter); | |
| Type elemType = getElementTypeOrSelf(type); | |
| Attribute bwAttr = IntegerAttr::get(elemType, bitwidth); | |
| if (auto vecType = dyn_cast<VectorType>(type)) | |
| bwAttr = SplatElementsAttr::get(vecType, bwAttr); | |
| Value valBitwidth = spirv::ConstantOp::create(rewriter, loc, type, bwAttr); | |
| Value lsb = spirv::GLFindILsbOp::create(rewriter, loc, input); | |
| Value isZero = spirv::IEqualOp::create(rewriter, loc, input, val0); | |
| rewriter.replaceOpWithNewOp<spirv::SelectOp>(countOp, isZero, valBitwidth, | |
| lsb); | |
| return success(); | |
| } | |
| }; | |
| /// 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(); | |
| Value input = adaptor.getOperand(); | |
| Type elementType = getElementTypeOrSelf(type); | |
| 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(); | |
| // 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 | |
| // 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 | |
| /// these operations. | |
| template <typename ExpOp> | |
| struct ExpM1OpPattern final : public OpConversionPattern<math::ExpM1Op> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::ExpM1Op operation, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| assert(adaptor.getOperands().size() == 1); | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, operation); | |
| failed(res)) | |
| return res; | |
| Location loc = operation.getLoc(); | |
| Type type = this->getTypeConverter()->convertType(operation.getType()); | |
| if (!type) | |
| return failure(); | |
| Value exp = ExpOp::create(rewriter, loc, type, adaptor.getOperand()); | |
| auto one = spirv::ConstantOp::getOne(type, loc, rewriter); | |
| rewriter.replaceOpWithNewOp<spirv::FSubOp>(operation, exp, one); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.log1p to SPIR-V ops. | |
| /// | |
| /// SPIR-V does not have a direct operations for log(1+x). Explicitly lower to | |
| /// these operations. | |
| template <typename LogOp> | |
| struct Log1pOpPattern final : public OpConversionPattern<math::Log1pOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::Log1pOp operation, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| assert(adaptor.getOperands().size() == 1); | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, operation); | |
| failed(res)) | |
| return res; | |
| Location loc = operation.getLoc(); | |
| Type type = this->getTypeConverter()->convertType(operation.getType()); | |
| if (!type) | |
| return failure(); | |
| auto one = spirv::ConstantOp::getOne(type, operation.getLoc(), rewriter); | |
| Value onePlus = | |
| spirv::FAddOp::create(rewriter, loc, one, adaptor.getOperand()); | |
| rewriter.replaceOpWithNewOp<LogOp>(operation, type, onePlus); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.log10 to GLSL SPIR-V ops. | |
| /// | |
| /// GLSL.std.450 has no Log10 instruction. Lower it as: | |
| /// log10(x) = log(x) * 1/log(10) | |
| struct Log10OpPattern final : public OpConversionPattern<math::Log10Op> { | |
| using Base::Base; | |
| static constexpr double log10Reciprocal = | |
| 0.4342944819032518276511289189166050822943970058036665661144537832; | |
| LogicalResult | |
| matchAndRewrite(math::Log10Op operation, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| assert(adaptor.getOperands().size() == 1); | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, operation); | |
| failed(res)) | |
| return res; | |
| Location loc = operation.getLoc(); | |
| Type type = this->getTypeConverter()->convertType(operation.getType()); | |
| if (!type) | |
| return rewriter.notifyMatchFailure(operation, "type conversion failed"); | |
| auto getConstantValue = [&](double value) { | |
| if (auto floatType = dyn_cast<FloatType>(type)) { | |
| return spirv::ConstantOp::create( | |
| rewriter, loc, type, rewriter.getFloatAttr(floatType, value)); | |
| } | |
| if (auto vectorType = dyn_cast<VectorType>(type)) { | |
| Type elemType = vectorType.getElementType(); | |
| if (isa<FloatType>(elemType)) { | |
| return spirv::ConstantOp::create( | |
| rewriter, loc, type, | |
| DenseFPElementsAttr::get( | |
| vectorType, FloatAttr::get(elemType, value).getValue())); | |
| } | |
| } | |
| llvm_unreachable("unimplemented type for log10"); | |
| }; | |
| Value constantValue = getConstantValue(log10Reciprocal); | |
| Value log = spirv::GLLogOp::create(rewriter, loc, adaptor.getOperand()); | |
| rewriter.replaceOpWithNewOp<spirv::FMulOp>(operation, type, log, | |
| constantValue); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.powf to SPIRV-Ops. | |
| struct PowFOpPattern final : public OpConversionPattern<math::PowFOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::PowFOp powfOp, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, powfOp); failed(res)) | |
| return res; | |
| Type dstType = getTypeConverter()->convertType(powfOp.getType()); | |
| if (!dstType) | |
| return failure(); | |
| Location loc = powfOp.getLoc(); | |
| Type operandType = adaptor.getRhs().getType(); | |
| // Parity-based lowering requires an integer-valued constant exponent. | |
| // Otherwise fall back to exp(y*log(x)), which yields NaN for x<0 (matches | |
| // C). | |
| auto isOdd = [](const APFloat &v) { | |
| APSInt i(/*BitWidth=*/64, /*isUnsigned=*/false); | |
| bool ignored; | |
| v.convertToInteger(i, APFloat::rmTowardZero, &ignored); | |
| return i[0]; | |
| }; | |
| SmallVector<bool> oddMask; | |
| Attribute rhsAttr; | |
| if (matchPattern(adaptor.getRhs(), m_Constant(&rhsAttr))) { | |
| TypeSwitch<Attribute>(rhsAttr) | |
| .Case([&](FloatAttr a) { | |
| if (a.getValue().isInteger()) | |
| oddMask.push_back(isOdd(a.getValue())); | |
| }) | |
| .Case([&](SplatElementsAttr a) { | |
| APFloat splat = a.getSplatValue<APFloat>(); | |
| if (splat.isInteger()) | |
| oddMask.push_back(isOdd(splat)); | |
| }) | |
| .Case([&](DenseElementsAttr a) { | |
| SmallVector<bool> mask; | |
| for (const APFloat &elt : a.getValues<APFloat>()) { | |
| if (!elt.isInteger()) | |
| return; | |
| mask.push_back(isOdd(elt)); | |
| } | |
| oddMask = std::move(mask); | |
| }); | |
| } | |
| if (oddMask.empty()) { | |
| Value log = spirv::GLLogOp::create(rewriter, loc, adaptor.getLhs()); | |
| Value mul = spirv::FMulOp::create(rewriter, loc, adaptor.getRhs(), log); | |
| rewriter.replaceOpWithNewOp<spirv::GLExpOp>(powfOp, mul); | |
| return success(); | |
| } | |
| // GL.Pow is undefined for x < 0; take abs and conditionally negate the | |
| // result for lanes whose exponent is odd. | |
| Value abs = spirv::GLFAbsOp::create(rewriter, loc, adaptor.getLhs()); | |
| Value pow = spirv::GLPowOp::create(rewriter, loc, abs, adaptor.getRhs()); | |
| // No odd-parity element: result has the same sign as |lhs|^rhs >= 0. | |
| if (llvm::none_of(oddMask, [](bool b) { return b; })) { | |
| rewriter.replaceOp(powfOp, pow); | |
| return success(); | |
| } | |
| Value zero = spirv::ConstantOp::getZero(operandType, loc, rewriter); | |
| Value lessThan = | |
| spirv::FOrdLessThanOp::create(rewriter, loc, adaptor.getLhs(), zero); | |
| Value negate = spirv::FNegateOp::create(rewriter, loc, pow); | |
| Value shouldNegate; | |
| if (llvm::all_equal(oddMask)) { | |
| // Every lane has odd exponent: negate iff lhs < 0. | |
| shouldNegate = lessThan; | |
| } else { | |
| // Mixed parity (non-splat dense vector): AND lhs<0 with a per-element | |
| // constant odd-mask. | |
| auto vecType = cast<VectorType>(operandType); | |
| auto maskType = VectorType::get(vecType.getShape(), rewriter.getI1Type()); | |
| Value oddConst = spirv::ConstantOp::create( | |
| rewriter, loc, maskType, DenseElementsAttr::get(maskType, oddMask)); | |
| shouldNegate = | |
| spirv::LogicalAndOp::create(rewriter, loc, lessThan, oddConst); | |
| } | |
| rewriter.replaceOpWithNewOp<spirv::SelectOp>(powfOp, shouldNegate, negate, | |
| pow); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.fpowi to spirv.CL.pown. | |
| struct PowIOpPattern final : public OpConversionPattern<math::FPowIOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::FPowIOp op, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, op); failed(res)) | |
| return res; | |
| Type dstType = getTypeConverter()->convertType(op.getType()); | |
| if (!dstType) | |
| return failure(); | |
| rewriter.replaceOpWithNewOp<spirv::CLPownOp>(op, dstType, adaptor.getLhs(), | |
| adaptor.getRhs()); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.fpowi to GLSL SPIR-V ops. GL has no integer-power op, so the | |
| /// exponent is converted to float and lowered through spirv.GL.Pow. As GL.Pow | |
| /// is undefined for a negative base, the base is made positive and the result | |
| /// is negated when the base is negative and the exponent is odd. | |
| struct PowIOpGLPattern final : public OpConversionPattern<math::FPowIOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::FPowIOp op, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, op); failed(res)) | |
| return res; | |
| Type dstType = getTypeConverter()->convertType(op.getType()); | |
| if (!dstType) | |
| return failure(); | |
| Location loc = op.getLoc(); | |
| Value base = adaptor.getLhs(); | |
| Value power = adaptor.getRhs(); | |
| Value expFloat = | |
| spirv::ConvertSToFOp::create(rewriter, loc, dstType, power); | |
| Value abs = spirv::GLFAbsOp::create(rewriter, loc, base); | |
| Value pow = spirv::GLPowOp::create(rewriter, loc, abs, expFloat); | |
| Value zeroF = spirv::ConstantOp::getZero(dstType, loc, rewriter); | |
| Value lessThan = spirv::FOrdLessThanOp::create(rewriter, loc, base, zeroF); | |
| Type powerType = power.getType(); | |
| Value oneI = spirv::ConstantOp::getOne(powerType, loc, rewriter); | |
| Value lowBit = spirv::BitwiseAndOp::create(rewriter, loc, power, oneI); | |
| Value isOdd = spirv::IEqualOp::create(rewriter, loc, lowBit, oneI); | |
| Value shouldNegate = | |
| spirv::LogicalAndOp::create(rewriter, loc, lessThan, isOdd); | |
| Value negate = spirv::FNegateOp::create(rewriter, loc, pow); | |
| rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, shouldNegate, negate, pow); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.sincos to SPIR-V ops. | |
| /// | |
| /// SPIR-V has no fused sincos instruction, so emit separate sin and cos ops | |
| /// sharing the same operand. | |
| template <typename SinOp, typename CosOp> | |
| struct SincosOpPattern final : public OpConversionPattern<math::SincosOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::SincosOp operation, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, operation); | |
| failed(res)) | |
| return res; | |
| Type type = | |
| getTypeConverter()->convertType(operation.getOperand().getType()); | |
| if (!type) | |
| return failure(); | |
| Location loc = operation.getLoc(); | |
| Value sin = SinOp::create(rewriter, loc, type, adaptor.getOperand()); | |
| Value cos = CosOp::create(rewriter, loc, type, adaptor.getOperand()); | |
| rewriter.replaceOp(operation, {sin, cos}); | |
| return success(); | |
| } | |
| }; | |
| /// Converts math.round to GLSL SPIRV extended ops. | |
| struct RoundOpPattern final : public OpConversionPattern<math::RoundOp> { | |
| using Base::Base; | |
| LogicalResult | |
| matchAndRewrite(math::RoundOp roundOp, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| if (LogicalResult res = checkSourceOpTypes(rewriter, roundOp); failed(res)) | |
| return res; | |
| Location loc = roundOp.getLoc(); | |
| auto ty = getTypeConverter()->convertType(adaptor.getOperand().getType()); | |
| if (!ty) { | |
| return rewriter.notifyMatchFailure( | |
| roundOp->getLoc(), | |
| llvm::formatv("failed to convert type {0} for SPIR-V", | |
| roundOp.getType())); | |
| } | |
| Type ety = getElementTypeOrSelf(ty); | |
| auto zero = spirv::ConstantOp::getZero(ty, loc, rewriter); | |
| auto one = spirv::ConstantOp::getOne(ty, loc, rewriter); | |
| Value half; | |
| if (VectorType vty = dyn_cast<VectorType>(ty)) { | |
| half = spirv::ConstantOp::create( | |
| rewriter, loc, vty, | |
| DenseElementsAttr::get(vty, | |
| rewriter.getFloatAttr(ety, 0.5).getValue())); | |
| } else { | |
| half = spirv::ConstantOp::create(rewriter, loc, ty, | |
| rewriter.getFloatAttr(ety, 0.5)); | |
| } | |
| auto abs = spirv::GLFAbsOp::create(rewriter, loc, adaptor.getOperand()); | |
| auto floor = spirv::GLFloorOp::create(rewriter, loc, abs); | |
| auto sub = spirv::FSubOp::create(rewriter, loc, abs, floor); | |
| auto greater = | |
| spirv::FOrdGreaterThanEqualOp::create(rewriter, loc, sub, half); | |
| auto select = spirv::SelectOp::create(rewriter, loc, greater, one, zero); | |
| auto add = spirv::FAddOp::create(rewriter, loc, floor, select); | |
| rewriter.replaceOpWithNewOp<math::CopySignOp>(roundOp, add, | |
| adaptor.getOperand()); | |
| return success(); | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // Pattern population | |
| //===----------------------------------------------------------------------===// | |
| namespace mlir { | |
| void populateMathToSPIRVPatterns(const SPIRVTypeConverter &typeConverter, | |
| RewritePatternSet &patterns) { | |
| // Core patterns | |
| patterns | |
| .add<CopySignPattern, | |
| CheckedElementwiseOpPattern<math::CtPopOp, spirv::BitCountOp>, | |
| CheckedElementwiseOpPattern<math::IsInfOp, spirv::IsInfOp>, | |
| CheckedElementwiseOpPattern<math::IsNaNOp, spirv::IsNanOp>, | |
| CheckedElementwiseOpPattern<math::IsFiniteOp, spirv::IsFiniteOp>, | |
| CheckedElementwiseOpPattern<math::IsNormalOp, spirv::IsNormalOp>>( | |
| typeConverter, patterns.getContext()); | |
| // GLSL patterns | |
| patterns | |
| .add<CountLeadingZerosPattern, CountTrailingZerosPattern, | |
| Log1pOpPattern<spirv::GLLogOp>, Log10OpPattern, | |
| ExpM1OpPattern<spirv::GLExpOp>, PowFOpPattern, PowIOpGLPattern, | |
| RoundOpPattern, SincosOpPattern<spirv::GLSinOp, spirv::GLCosOp>, | |
| CheckedElementwiseOpPattern<math::AbsFOp, spirv::GLFAbsOp>, | |
| CheckedElementwiseOpPattern<math::AbsIOp, spirv::GLSAbsOp>, | |
| CheckedElementwiseOpPattern<math::AtanOp, spirv::GLAtanOp>, | |
| CheckedElementwiseOpPattern<math::Atan2Op, spirv::GLAtan2Op>, | |
| CheckedElementwiseOpPattern<math::CeilOp, spirv::GLCeilOp>, | |
| CheckedElementwiseOpPattern<math::ClampFOp, spirv::GLFClampOp>, | |
| CheckedElementwiseOpPattern<math::CosOp, spirv::GLCosOp>, | |
| CheckedElementwiseOpPattern<math::ExpOp, spirv::GLExpOp>, | |
| CheckedElementwiseOpPattern<math::Exp2Op, spirv::GLExp2Op>, | |
| CheckedElementwiseOpPattern<math::FloorOp, spirv::GLFloorOp>, | |
| CheckedElementwiseOpPattern<math::FmaOp, spirv::GLFmaOp>, | |
| CheckedElementwiseOpPattern<math::LogOp, spirv::GLLogOp>, | |
| CheckedElementwiseOpPattern<math::Log2Op, spirv::GLLog2Op>, | |
| CheckedElementwiseOpPattern<math::RoundEvenOp, spirv::GLRoundEvenOp>, | |
| CheckedElementwiseOpPattern<math::RsqrtOp, spirv::GLInverseSqrtOp>, | |
| CheckedElementwiseOpPattern<math::SinOp, spirv::GLSinOp>, | |
| CheckedElementwiseOpPattern<math::SqrtOp, spirv::GLSqrtOp>, | |
| CheckedElementwiseOpPattern<math::TanhOp, spirv::GLTanhOp>, | |
| CheckedElementwiseOpPattern<math::TanOp, spirv::GLTanOp>, | |
| CheckedElementwiseOpPattern<math::TruncOp, spirv::GLTruncOp>, | |
| CheckedElementwiseOpPattern<math::AsinOp, spirv::GLAsinOp>, | |
| CheckedElementwiseOpPattern<math::AcosOp, spirv::GLAcosOp>, | |
| CheckedElementwiseOpPattern<math::SinhOp, spirv::GLSinhOp>, | |
| CheckedElementwiseOpPattern<math::CoshOp, spirv::GLCoshOp>, | |
| CheckedElementwiseOpPattern<math::AsinhOp, spirv::GLAsinhOp>, | |
| CheckedElementwiseOpPattern<math::AcoshOp, spirv::GLAcoshOp>, | |
| CheckedElementwiseOpPattern<math::AtanhOp, spirv::GLAtanhOp>>( | |
| typeConverter, patterns.getContext()); | |
| // OpenCL patterns | |
| patterns.add< | |
| Log1pOpPattern<spirv::CLLogOp>, ExpM1OpPattern<spirv::CLExpOp>, | |
| SincosOpPattern<spirv::CLSinOp, spirv::CLCosOp>, | |
| 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>, | |
| CheckedElementwiseOpPattern<math::CeilOp, spirv::CLCeilOp>, | |
| CheckedElementwiseOpPattern<math::CopySignOp, spirv::CLCopysignOp>, | |
| CheckedElementwiseOpPattern<math::CosOp, spirv::CLCosOp>, | |
| CheckedElementwiseOpPattern<math::ErfOp, spirv::CLErfOp>, | |
| CheckedElementwiseOpPattern<math::ErfcOp, spirv::CLErfcOp>, | |
| CheckedElementwiseOpPattern<math::ExpOp, spirv::CLExpOp>, | |
| CheckedElementwiseOpPattern<math::Exp2Op, spirv::CLExp2Op>, | |
| CheckedElementwiseOpPattern<math::FloorOp, spirv::CLFloorOp>, | |
| CheckedElementwiseOpPattern<math::FmaOp, spirv::CLFmaOp>, | |
| CheckedElementwiseOpPattern<math::LogOp, spirv::CLLogOp>, | |
| CheckedElementwiseOpPattern<math::Log2Op, spirv::CLLog2Op>, | |
| CheckedElementwiseOpPattern<math::Log10Op, spirv::CLLog10Op>, | |
| CheckedElementwiseOpPattern<math::PowFOp, spirv::CLPowOp>, PowIOpPattern, | |
| CheckedElementwiseOpPattern<math::RoundEvenOp, spirv::CLRintOp>, | |
| CheckedElementwiseOpPattern<math::RoundOp, spirv::CLRoundOp>, | |
| CheckedElementwiseOpPattern<math::RsqrtOp, spirv::CLRsqrtOp>, | |
| CheckedElementwiseOpPattern<math::SinOp, spirv::CLSinOp>, | |
| CheckedElementwiseOpPattern<math::SqrtOp, spirv::CLSqrtOp>, | |
| CheckedElementwiseOpPattern<math::TanhOp, spirv::CLTanhOp>, | |
| CheckedElementwiseOpPattern<math::TanOp, spirv::CLTanOp>, | |
| CheckedElementwiseOpPattern<math::TruncOp, spirv::CLTruncOp>, | |
| CheckedElementwiseOpPattern<math::AsinOp, spirv::CLAsinOp>, | |
| CheckedElementwiseOpPattern<math::AcosOp, spirv::CLAcosOp>, | |
| CheckedElementwiseOpPattern<math::SinhOp, spirv::CLSinhOp>, | |
| CheckedElementwiseOpPattern<math::CoshOp, spirv::CLCoshOp>, | |
| CheckedElementwiseOpPattern<math::AsinhOp, spirv::CLAsinhOp>, | |
| CheckedElementwiseOpPattern<math::AcoshOp, spirv::CLAcoshOp>, | |
| CheckedElementwiseOpPattern<math::AtanhOp, spirv::CLAtanhOp>>( | |
| typeConverter, patterns.getContext()); | |
| } | |
| } // namespace mlir |