//===-- MathToNVVM.cpp - conversion from Math to CUDA libdevice calls ----===// // // 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 // //===----------------------------------------------------------------------===// #include "mlir/Conversion/MathToNVVM/MathToNVVM.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/LLVMIR/NVVMDialect.h" #include "mlir/Dialect/Math/IR/Math.h" #include "mlir/Dialect/Vector/IR/VectorOps.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/Pass/Pass.h" #include "../GPUCommon/GPUOpsLowering.h" #include "../GPUCommon/OpToFuncCallLowering.h" namespace mlir { #define GEN_PASS_DEF_CONVERTMATHTONVVM #include "mlir/Conversion/Passes.h.inc" } // namespace mlir using namespace mlir; #define DEBUG_TYPE "math-to-nvvm" template static void populateOpPatterns(const LLVMTypeConverter &converter, RewritePatternSet &patterns, PatternBenefit benefit, StringRef f32Func, StringRef f64Func, StringRef f32ApproxFunc = "", StringRef f16Func = "") { patterns.add>(converter, benefit); patterns.add>(converter, f32Func, f64Func, f32ApproxFunc, f16Func, /*i32Func=*/"", benefit); } template static void populateIntOpPatterns(const LLVMTypeConverter &converter, RewritePatternSet &patterns, PatternBenefit benefit, StringRef i32Func) { patterns.add>(converter, benefit); patterns.add>(converter, "", "", "", "", i32Func, benefit); } template static void populateFloatIntOpPatterns(const LLVMTypeConverter &converter, RewritePatternSet &patterns, PatternBenefit benefit, StringRef f32Func, StringRef f64Func) { patterns.add>(converter, benefit); patterns.add>(converter, f32Func, f64Func, "", "", /*i32Func=*/"", benefit); } // Custom pattern for sincos since it returns two values struct SincosOpLowering : public ConvertOpToLLVMPattern { using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern; LogicalResult matchAndRewrite(math::SincosOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Location loc = op.getLoc(); Value input = adaptor.getOperand(); Type inputType = input.getType(); auto convertedInput = maybeExt(input, rewriter); auto computeType = convertedInput.getType(); StringRef sincosFunc; if (isa(computeType)) { const arith::FastMathFlags flag = op.getFastmath(); const bool useApprox = mlir::arith::bitEnumContainsAny(flag, arith::FastMathFlags::afn); sincosFunc = useApprox ? "__nv_fast_sincosf" : "__nv_sincosf"; } else if (isa(computeType)) { sincosFunc = "__nv_sincos"; } else { return rewriter.notifyMatchFailure(op, "unsupported operand type for sincos"); } auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext()); Value sinPtr, cosPtr; { OpBuilder::InsertionGuard guard(rewriter); auto *scope = op->getParentWithTrait(); assert(scope && "Expected op to be inside automatic allocation scope"); rewriter.setInsertionPointToStart(&scope->getRegion(0).front()); auto one = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1)); sinPtr = LLVM::AllocaOp::create(rewriter, loc, ptrType, computeType, one, 0); cosPtr = LLVM::AllocaOp::create(rewriter, loc, ptrType, computeType, one, 0); } createSincosCall(rewriter, loc, sincosFunc, convertedInput, sinPtr, cosPtr, op); auto sinResult = LLVM::LoadOp::create(rewriter, loc, computeType, sinPtr); auto cosResult = LLVM::LoadOp::create(rewriter, loc, computeType, cosPtr); rewriter.replaceOp(op, {maybeTrunc(sinResult, inputType, rewriter), maybeTrunc(cosResult, inputType, rewriter)}); return success(); } private: Value maybeExt(Value operand, PatternRewriter &rewriter) const { if (isa(operand.getType())) return LLVM::FPExtOp::create(rewriter, operand.getLoc(), Float32Type::get(rewriter.getContext()), operand); return operand; } Value maybeTrunc(Value operand, Type type, PatternRewriter &rewriter) const { if (operand.getType() != type) return LLVM::FPTruncOp::create(rewriter, operand.getLoc(), type, operand); return operand; } void createSincosCall(ConversionPatternRewriter &rewriter, Location loc, StringRef funcName, Value input, Value sinPtr, Value cosPtr, Operation *op) const { auto voidType = LLVM::LLVMVoidType::get(rewriter.getContext()); auto ptrType = sinPtr.getType(); SmallVector operandTypes = {input.getType(), ptrType, ptrType}; auto funcType = LLVM::LLVMFunctionType::get(voidType, operandTypes); auto funcAttr = StringAttr::get(op->getContext(), funcName); auto funcOp = SymbolTable::lookupNearestSymbolFrom(op, funcAttr); if (!funcOp) { auto parentFunc = op->getParentOfType(); assert(parentFunc && "expected there to be a parent function"); OpBuilder b(parentFunc); auto globalloc = loc->findInstanceOfOrUnknown(); funcOp = LLVM::LLVMFuncOp::create(b, globalloc, funcName, funcType); } SmallVector callOperands = {input, sinPtr, cosPtr}; LLVM::CallOp::create(rewriter, loc, funcOp, callOperands); } }; void mlir::populateLibDeviceConversionPatterns( const LLVMTypeConverter &converter, RewritePatternSet &patterns, PatternBenefit benefit) { populateOpPatterns(converter, patterns, benefit, "__nv_fmodf", "__nv_fmod"); populateOpPatterns(converter, patterns, benefit, "__nv_fmaxf", "__nv_fmax"); populateOpPatterns(converter, patterns, benefit, "__nv_fminf", "__nv_fmin"); populateIntOpPatterns(converter, patterns, benefit, "__nv_abs"); populateOpPatterns(converter, patterns, benefit, "__nv_fabsf", "__nv_fabs"); populateOpPatterns(converter, patterns, benefit, "__nv_acosf", "__nv_acos"); populateOpPatterns(converter, patterns, benefit, "__nv_acoshf", "__nv_acosh"); populateOpPatterns(converter, patterns, benefit, "__nv_asinf", "__nv_asin"); populateOpPatterns(converter, patterns, benefit, "__nv_asinhf", "__nv_asinh"); populateOpPatterns(converter, patterns, benefit, "__nv_atanf", "__nv_atan"); populateOpPatterns(converter, patterns, benefit, "__nv_atan2f", "__nv_atan2"); populateOpPatterns(converter, patterns, benefit, "__nv_atanhf", "__nv_atanh"); populateOpPatterns(converter, patterns, benefit, "__nv_cbrtf", "__nv_cbrt"); populateOpPatterns(converter, patterns, benefit, "__nv_ceilf", "__nv_ceil"); populateOpPatterns(converter, patterns, benefit, "__nv_copysignf", "__nv_copysign"); populateOpPatterns(converter, patterns, benefit, "__nv_cosf", "__nv_cos", "__nv_fast_cosf"); populateOpPatterns(converter, patterns, benefit, "__nv_coshf", "__nv_cosh"); populateOpPatterns(converter, patterns, benefit, "__nv_erff", "__nv_erf"); populateOpPatterns(converter, patterns, benefit, "__nv_erfcf", "__nv_erfc"); populateOpPatterns(converter, patterns, benefit, "__nv_expf", "__nv_exp", "__nv_fast_expf"); populateOpPatterns(converter, patterns, benefit, "__nv_exp2f", "__nv_exp2"); populateOpPatterns(converter, patterns, benefit, "__nv_expm1f", "__nv_expm1"); populateOpPatterns(converter, patterns, benefit, "__nv_floorf", "__nv_floor"); populateOpPatterns(converter, patterns, benefit, "__nv_fmaf", "__nv_fma"); // Note: libdevice uses a different name for 32-bit finite checking populateOpPatterns(converter, patterns, benefit, "__nv_finitef", "__nv_isfinited"); populateOpPatterns(converter, patterns, benefit, "__nv_isinff", "__nv_isinfd"); populateOpPatterns(converter, patterns, benefit, "__nv_isnanf", "__nv_isnand"); populateOpPatterns(converter, patterns, benefit, "__nv_logf", "__nv_log", "__nv_fast_logf"); populateOpPatterns(converter, patterns, benefit, "__nv_log10f", "__nv_log10", "__nv_fast_log10f"); populateOpPatterns(converter, patterns, benefit, "__nv_log1pf", "__nv_log1p"); populateOpPatterns(converter, patterns, benefit, "__nv_log2f", "__nv_log2", "__nv_fast_log2f"); populateOpPatterns(converter, patterns, benefit, "__nv_powf", "__nv_pow", "__nv_fast_powf"); populateFloatIntOpPatterns(converter, patterns, benefit, "__nv_powif", "__nv_powi"); populateOpPatterns(converter, patterns, benefit, "__nv_roundf", "__nv_round"); populateOpPatterns(converter, patterns, benefit, "__nv_rintf", "__nv_rint"); populateOpPatterns(converter, patterns, benefit, "__nv_rsqrtf", "__nv_rsqrt"); populateOpPatterns(converter, patterns, benefit, "__nv_sinf", "__nv_sin", "__nv_fast_sinf"); populateOpPatterns(converter, patterns, benefit, "__nv_sinhf", "__nv_sinh"); populateOpPatterns(converter, patterns, benefit, "__nv_sqrtf", "__nv_sqrt"); populateOpPatterns(converter, patterns, benefit, "__nv_tanf", "__nv_tan", "__nv_fast_tanf"); populateOpPatterns(converter, patterns, benefit, "__nv_tanhf", "__nv_tanh"); // Custom pattern for sincos since it returns two values patterns.add(converter, benefit); } namespace { struct ConvertMathToNVVMPass final : impl::ConvertMathToNVVMBase { using impl::ConvertMathToNVVMBase< ConvertMathToNVVMPass>::ConvertMathToNVVMBase; void runOnOperation() override; }; } // namespace void ConvertMathToNVVMPass::runOnOperation() { auto m = getOperation(); MLIRContext *ctx = m.getContext(); RewritePatternSet patterns(&getContext()); LowerToLLVMOptions options(ctx, DataLayout(m)); LLVMTypeConverter converter(ctx, options); populateLibDeviceConversionPatterns(converter, patterns, /*benefit=*/1); ConversionTarget target(getContext()); target .addLegalDialect(); target.addIllegalOp(); if (failed(applyPartialConversion(m, target, std::move(patterns)))) signalPassFailure(); }