//===- 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/TypeUtilities.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/STLExtras.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;

    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.
///
/// SPIR-V does not have a direct operations for counting leading zeros. 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();

    // We can only support 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.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.log2 and math.log10 to SPIR-V ops.
///
/// SPIR-V does not have direct operations for log2 and log10. Explicitly
/// lower to these operations using:
///   log2(x) = log(x) * 1/log(2)
///   log10(x) = log(x) * 1/log(10)

template <typename MathLogOp, typename SpirvLogOp>
struct Log2Log10OpPattern final : public OpConversionPattern<MathLogOp> {
  using OpConversionPattern<MathLogOp>::OpConversionPattern;
  using typename OpConversionPattern<MathLogOp>::OpAdaptor;

  static constexpr double log2Reciprocal =
      1.442695040888963407359924681001892137426645954152985934135449407;
  static constexpr double log10Reciprocal =
      0.4342944819032518276511289189166050822943970058036665661144537832;

  LogicalResult
  matchAndRewrite(MathLogOp 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 types for log2/log10");
    };

    Value constantValue = getConstantValue(
        std::is_same<MathLogOp, math::Log2Op>() ? log2Reciprocal
                                                : log10Reciprocal);
    Value log = SpirvLogOp::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();

    // Get the scalar float type.
    FloatType scalarFloatType;
    if (auto scalarType = dyn_cast<FloatType>(powfOp.getType())) {
      scalarFloatType = scalarType;
    } else if (auto vectorType = dyn_cast<VectorType>(powfOp.getType())) {
      scalarFloatType = cast<FloatType>(vectorType.getElementType());
    } else {
      return failure();
    }

    // Get int type of the same shape as the float type.
    Type scalarIntType = rewriter.getIntegerType(32);
    Type intType = scalarIntType;
    auto operandType = adaptor.getRhs().getType();
    if (auto vectorType = dyn_cast<VectorType>(operandType)) {
      auto shape = vectorType.getShape();
      intType = VectorType::get(shape, scalarIntType);
    }

    // Per GL Pow extended instruction spec:
    // "Result is undefined if x < 0. Result is undefined if x = 0 and y <= 0."
    Location loc = powfOp.getLoc();
    Value zero = spirv::ConstantOp::getZero(operandType, loc, rewriter);
    Value lessThan =
        spirv::FOrdLessThanOp::create(rewriter, loc, adaptor.getLhs(), zero);

    // Per C/C++ spec:
    // > pow(base, exponent) returns NaN (and raises FE_INVALID) if base is
    // > finite and negative and exponent is finite and non-integer.
    // Calculate the reminder from the exponent and check whether it is zero.
    Value floatOne = spirv::ConstantOp::getOne(operandType, loc, rewriter);
    Value expRem =
        spirv::FRemOp::create(rewriter, loc, adaptor.getRhs(), floatOne);
    Value expRemNonZero =
        spirv::FOrdNotEqualOp::create(rewriter, loc, expRem, zero);
    Value cmpNegativeWithFractionalExp =
        spirv::LogicalAndOp::create(rewriter, loc, expRemNonZero, lessThan);
    // Create NaN result and replace base value if conditions are met.
    const auto &floatSemantics = scalarFloatType.getFloatSemantics();
    const auto nan = APFloat::getNaN(floatSemantics);
    Attribute nanAttr = rewriter.getFloatAttr(scalarFloatType, nan);
    if (auto vectorType = dyn_cast<VectorType>(operandType))
      nanAttr = DenseElementsAttr::get(vectorType, nan);

    Value nanValue =
        spirv::ConstantOp::create(rewriter, loc, operandType, nanAttr);
    Value lhs =
        spirv::SelectOp::create(rewriter, loc, cmpNegativeWithFractionalExp,
                                nanValue, adaptor.getLhs());
    Value abs = spirv::GLFAbsOp::create(rewriter, loc, lhs);

    // TODO: The following just forcefully casts y into an integer value in
    // order to properly propagate the sign, assuming integer y cases. It
    // doesn't cover other cases and should be fixed.

    // Cast exponent to integer and calculate exponent % 2 != 0.
    Value intRhs =
        spirv::ConvertFToSOp::create(rewriter, loc, intType, adaptor.getRhs());
    Value intOne = spirv::ConstantOp::getOne(intType, loc, rewriter);
    Value bitwiseAndOne =
        spirv::BitwiseAndOp::create(rewriter, loc, intRhs, intOne);
    Value isOdd = spirv::IEqualOp::create(rewriter, loc, bitwiseAndOne, intOne);

    // calculate pow based on abs(lhs)^rhs.
    Value pow = spirv::GLPowOp::create(rewriter, loc, abs, adaptor.getRhs());
    Value negate = spirv::FNegateOp::create(rewriter, loc, pow);
    // if the exponent is odd and lhs < 0, negate the result.
    Value shouldNegate =
        spirv::LogicalAndOp::create(rewriter, loc, lessThan, isOdd);
    rewriter.replaceOpWithNewOp<spirv::SelectOp>(powfOp, shouldNegate, negate,
                                                 pow);
    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::IsInfOp, spirv::IsInfOp>,
           CheckedElementwiseOpPattern<math::IsNaNOp, spirv::IsNanOp>,
           CheckedElementwiseOpPattern<math::IsFiniteOp, spirv::IsFiniteOp>>(
          typeConverter, patterns.getContext());

  // GLSL patterns
  patterns
      .add<CountLeadingZerosPattern, Log1pOpPattern<spirv::GLLogOp>,
           Log2Log10OpPattern<math::Log2Op, spirv::GLLogOp>,
           Log2Log10OpPattern<math::Log10Op, spirv::GLLogOp>,
           ExpM1OpPattern<spirv::GLExpOp>, PowFOpPattern, RoundOpPattern,
           CheckedElementwiseOpPattern<math::AbsFOp, spirv::GLFAbsOp>,
           CheckedElementwiseOpPattern<math::AbsIOp, spirv::GLSAbsOp>,
           CheckedElementwiseOpPattern<math::AtanOp, spirv::GLAtanOp>,
           CheckedElementwiseOpPattern<math::CeilOp, spirv::GLCeilOp>,
           CheckedElementwiseOpPattern<math::CosOp, spirv::GLCosOp>,
           CheckedElementwiseOpPattern<math::ExpOp, spirv::GLExpOp>,
           CheckedElementwiseOpPattern<math::FloorOp, spirv::GLFloorOp>,
           CheckedElementwiseOpPattern<math::FmaOp, spirv::GLFmaOp>,
           CheckedElementwiseOpPattern<math::LogOp, spirv::GLLogOp>,
           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::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>,
               Log2Log10OpPattern<math::Log2Op, spirv::CLLogOp>,
               Log2Log10OpPattern<math::Log10Op, spirv::CLLogOp>,
               CheckedElementwiseOpPattern<math::AbsFOp, spirv::CLFAbsOp>,
               CheckedElementwiseOpPattern<math::AbsIOp, spirv::CLSAbsOp>,
               CheckedElementwiseOpPattern<math::AtanOp, spirv::CLAtanOp>,
               CheckedElementwiseOpPattern<math::Atan2Op, spirv::CLAtan2Op>,
               CheckedElementwiseOpPattern<math::CeilOp, spirv::CLCeilOp>,
               CheckedElementwiseOpPattern<math::CosOp, spirv::CLCosOp>,
               CheckedElementwiseOpPattern<math::ErfOp, spirv::CLErfOp>,
               CheckedElementwiseOpPattern<math::ExpOp, spirv::CLExpOp>,
               CheckedElementwiseOpPattern<math::FloorOp, spirv::CLFloorOp>,
               CheckedElementwiseOpPattern<math::FmaOp, spirv::CLFmaOp>,
               CheckedElementwiseOpPattern<math::LogOp, spirv::CLLogOp>,
               CheckedElementwiseOpPattern<math::PowFOp, spirv::CLPowOp>,
               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::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
