MLIR 24.0.0git
MathToXeVM.cpp
Go to the documentation of this file.
1//===-- MathToXeVM.cpp - conversion from Math to XeVM ---------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
18#include "mlir/Pass/Pass.h"
19#include "llvm/Support/FormatVariadic.h"
20
23
24namespace mlir {
25#define GEN_PASS_DEF_CONVERTMATHTOXEVM
26#include "mlir/Conversion/Passes.h.inc"
27} // namespace mlir
28
29using namespace mlir;
30
31#define DEBUG_TYPE "math-to-xevm"
32
33static bool isSizeOneVector(Type type) {
34 auto vecType = dyn_cast<VectorType>(type);
35 return vecType && vecType.getShape().size() == 1 &&
36 vecType.getShape()[0] == 1 && vecType.getElementType().isFloat();
37}
38
40 if (type.isFloat())
41 return true;
42 if (auto vecType = dyn_cast<VectorType>(type)) {
43 if (!vecType.getElementType().isFloat())
44 return false;
45 // SPIRV distinguishes between vectors and matrices: OpenCL native math
46 // intrsinics are not compatible with matrices.
47 ArrayRef<int64_t> shape = vecType.getShape();
48 if (shape.size() != 1)
49 return false;
50 // SPIRV has no size-1 vector type; such degenerate vectors are handled
51 // by unwrapping to the scalar intrinsic (see matchAndRewrite).
52 if (shape[0] == 1)
53 return true;
54 // SPIRV only allows vectors of size 2, 3, 4, 8, 16.
55 if (shape[0] == 2 || shape[0] == 3 || shape[0] == 4 || shape[0] == 8 ||
56 shape[0] == 16)
57 return true;
58 }
59 return false;
60}
61
62/// Convert math ops marked with `fast` (`afn`) to native OpenCL intrinsics.
63template <typename Op>
64struct ConvertNativeFuncPattern final : public OpConversionPattern<Op> {
65
67 PatternBenefit benefit = 1)
68 : OpConversionPattern<Op>(context, benefit), nativeFunc(nativeFunc) {}
69
70 inline std::string
71 getMangledNativeFuncName(const ArrayRef<Type> operandTypes) const {
72 std::string mangledFuncName =
73 "_Z" + std::to_string(nativeFunc.size()) + nativeFunc.str();
74
75 auto appendFloatToMangledFunc = [&mangledFuncName](Type type) {
76 if (type.isF32())
77 mangledFuncName += "f";
78 else if (type.isF16())
79 mangledFuncName += "Dh";
80 else if (type.isF64())
81 mangledFuncName += "d";
82 };
83
84 for (auto type : operandTypes) {
85 if (auto vecType = dyn_cast<VectorType>(type)) {
86 mangledFuncName += "Dv" + std::to_string(vecType.getShape()[0]) + "_";
87 appendFloatToMangledFunc(vecType.getElementType());
88 } else
89 appendFloatToMangledFunc(type);
90 }
91
92 return mangledFuncName;
93 }
94
95 LogicalResult
96 matchAndRewrite(Op op, typename Op::Adaptor adaptor,
97 ConversionPatternRewriter &rewriter) const override {
98 if (!isSPIRVCompatibleFloatOrVec(op.getType()))
99 return failure();
100
101 arith::FastMathFlags fastFlags = op.getFastmath();
102 if (!arith::bitEnumContainsAll(fastFlags, arith::FastMathFlags::afn))
103 return rewriter.notifyMatchFailure(op, "not a fastmath `afn` operation");
104
105 Location loc = op.getLoc();
106
107 // SPIRV has no size-1 vector type: such vectors are the degenerate result
108 // of distributing/linearizing larger vectors down to a single element (e.g.
109 // by the XeGPU lowering pipeline). They have no OpenCL vector intrinsic, so
110 // unwrap them to the scalar element type and use the scalar intrinsic.
111 SmallVector<Value, 1> operands(adaptor.getOperands());
112 SmallVector<Type, 1> operandTypes;
113 bool unwrapSizeOneVec = isSizeOneVector(op.getType());
114 for (Value &operand : operands) {
115 Type opTy = operand.getType();
116 // This pass only supports operations on vectors that are already in SPIRV
117 // supported vector sizes: Distributing unsupported vector sizes to SPIRV
118 // supported vector sizes are done in other blocking optimization passes.
120 return rewriter.notifyMatchFailure(
121 op, llvm::formatv("incompatible operand type: '{0}'", opTy));
122 if (unwrapSizeOneVec) {
123 assert(isSizeOneVector(opTy) &&
124 "expected all operands to be size-1 vectors");
125 opTy = cast<VectorType>(opTy).getElementType();
126 operand = vector::ExtractOp::create(rewriter, loc, operand,
128 }
129 operandTypes.push_back(opTy);
130 }
131
132 Type resultType = unwrapSizeOneVec
133 ? cast<VectorType>(op.getType()).getElementType()
134 : op.getType();
135
136 auto moduleOp = op->template getParentWithTrait<OpTrait::SymbolTable>();
137 auto funcOpRes = LLVM::lookupOrCreateFn(
138 rewriter, moduleOp, getMangledNativeFuncName(operandTypes),
139 operandTypes, resultType);
140 assert(!failed(funcOpRes));
141 LLVM::LLVMFuncOp funcOp = funcOpRes.value();
142
143 auto callOp = LLVM::CallOp::create(rewriter, loc, funcOp, operands);
144 // Preserve fastmath flags in our MLIR op when converting to llvm function
145 // calls, in order to allow further fastmath optimizations: We thus need to
146 // convert arith fastmath attrs into attrs recognized by llvm.
148 callOp.setFastmathFlagsAttr(
149 fastAttrConverter.getProperties().getFastmathFlags());
150 callOp->setDiscardableAttrs(fastAttrConverter.getDiscardableAttrs());
151
152 if (unwrapSizeOneVec) {
153 // Re-wrap the scalar result back into a size-1 vector to preserve types.
154 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(op, op.getType(),
155 callOp.getResult());
156 } else {
157 rewriter.replaceOp(op, callOp);
158 }
159 return success();
160 }
161
162 const StringRef nativeFunc;
163};
164
165template <typename OpTy>
167 RewritePatternSet &patterns,
168 PatternBenefit benefit,
169 StringRef opName) {
170 std::string prefix = "__spirv_ocl_";
171 std::string mangledName = "_Z" +
172 std::to_string(prefix.size() + opName.size()) +
173 prefix + opName.str();
174
175 patterns.add<ScalarizeVectorOpLowering<OpTy>>(converter, benefit);
177 converter, mangledName + "f", mangledName + "d",
178 /*f32ApproxFunc=*/"", /*f16Func=*/"",
179 /*i32Func=*/"", benefit, LLVM::cconv::CConv::SPIR_FUNC);
180}
181
183 const LLVMTypeConverter &converter, RewritePatternSet &patterns,
184 PatternBenefit benefit) {
185 populateOCLExtSetOpPatterns<math::AcosOp>(converter, patterns, benefit,
186 "acos");
187 populateOCLExtSetOpPatterns<math::AcoshOp>(converter, patterns, benefit,
188 "acosh");
189 populateOCLExtSetOpPatterns<math::AsinOp>(converter, patterns, benefit,
190 "asin");
191 populateOCLExtSetOpPatterns<math::AsinhOp>(converter, patterns, benefit,
192 "asinh");
193 populateOCLExtSetOpPatterns<math::AtanOp>(converter, patterns, benefit,
194 "atan");
195 populateOCLExtSetOpPatterns<math::Atan2Op>(converter, patterns, benefit,
196 "atan2");
197 populateOCLExtSetOpPatterns<math::AtanhOp>(converter, patterns, benefit,
198 "atanh");
199 populateOCLExtSetOpPatterns<math::CbrtOp>(converter, patterns, benefit,
200 "cbrt");
201 populateOCLExtSetOpPatterns<math::CopySignOp>(converter, patterns, benefit,
202 "copysign");
203 populateOCLExtSetOpPatterns<math::CosOp>(converter, patterns, benefit, "cos");
204 populateOCLExtSetOpPatterns<math::CoshOp>(converter, patterns, benefit,
205 "cosh");
206 populateOCLExtSetOpPatterns<math::ErfOp>(converter, patterns, benefit, "erf");
207 populateOCLExtSetOpPatterns<math::ErfcOp>(converter, patterns, benefit,
208 "erfc");
209 populateOCLExtSetOpPatterns<math::ExpOp>(converter, patterns, benefit, "exp");
210 populateOCLExtSetOpPatterns<math::Exp2Op>(converter, patterns, benefit,
211 "exp2");
212 populateOCLExtSetOpPatterns<math::ExpM1Op>(converter, patterns, benefit,
213 "expm1");
214 populateOCLExtSetOpPatterns<math::LogOp>(converter, patterns, benefit, "log");
215 populateOCLExtSetOpPatterns<math::Log10Op>(converter, patterns, benefit,
216 "log10");
217 populateOCLExtSetOpPatterns<math::Log1pOp>(converter, patterns, benefit,
218 "log1p");
219 populateOCLExtSetOpPatterns<math::Log2Op>(converter, patterns, benefit,
220 "log2");
221 populateOCLExtSetOpPatterns<math::PowFOp>(converter, patterns, benefit,
222 "pow");
223 populateOCLExtSetOpPatterns<math::RsqrtOp>(converter, patterns, benefit,
224 "rsqrt");
225 populateOCLExtSetOpPatterns<math::SinOp>(converter, patterns, benefit, "sin");
226 populateOCLExtSetOpPatterns<math::SinhOp>(converter, patterns, benefit,
227 "sinh");
228 populateOCLExtSetOpPatterns<math::SqrtOp>(converter, patterns, benefit,
229 "sqrt");
230 populateOCLExtSetOpPatterns<math::TanOp>(converter, patterns, benefit, "tan");
231 populateOCLExtSetOpPatterns<math::TanhOp>(converter, patterns, benefit,
232 "tanh");
233}
234
236 bool convertArith,
237 PatternBenefit benefit) {
239 patterns.getContext(), "__spirv_ocl_native_exp", benefit);
241 patterns.getContext(), "__spirv_ocl_native_cos", benefit);
243 patterns.getContext(), "__spirv_ocl_native_exp2", benefit);
245 patterns.getContext(), "__spirv_ocl_native_log", benefit);
247 patterns.getContext(), "__spirv_ocl_native_log2", benefit);
249 patterns.getContext(), "__spirv_ocl_native_log10", benefit);
251 patterns.getContext(), "__spirv_ocl_native_powr", benefit);
253 patterns.getContext(), "__spirv_ocl_native_rsqrt", benefit);
255 patterns.getContext(), "__spirv_ocl_native_sin", benefit);
257 patterns.getContext(), "__spirv_ocl_native_sqrt", benefit);
259 patterns.getContext(), "__spirv_ocl_native_tan", benefit);
260 if (convertArith)
262 patterns.getContext(), "__spirv_ocl_native_divide", benefit);
263}
264
265namespace {
266struct ConvertMathToXeVMPass
267 : public impl::ConvertMathToXeVMBase<ConvertMathToXeVMPass> {
268 using Base::Base;
269 void runOnOperation() override;
270};
271} // namespace
272
273void ConvertMathToXeVMPass::runOnOperation() {
274 Operation *op = getOperation();
275 MLIRContext *ctx = op->getContext();
276
277 const auto &dl = getAnalysis<DataLayoutAnalysis>();
278
279 RewritePatternSet patterns(&getContext());
280 LowerToLLVMOptions options(ctx, dl.getAtOrAbove(op));
281 LLVMTypeConverter converter(ctx, options);
282 ConversionTarget target(getContext());
283
284 // Native OCL patterns should take precedence for `fast` ops even when
285 // convertToOCL is set.
286 populateMathToXeVMConversionPatterns(patterns, convertArith,
287 convertToOCL + 1);
288 if (convertToOCL) {
290 target
291 .addIllegalOp<LLVM::CosOp, LLVM::ExpOp, LLVM::Exp2Op, LLVM::LogOp,
292 LLVM::Log10Op, LLVM::Log2Op, LLVM::SinOp, LLVM::SqrtOp>();
293 }
294 target.addLegalDialect<BuiltinDialect, LLVM::LLVMDialect>();
295 // The size-1-vector patterns unwrap to the scalar intrinsic via
296 // vector.extract / vector.broadcast; these must be legal for the partial
297 // conversion to succeed.
298 target.addLegalOp<vector::ExtractOp, vector::BroadcastOp>();
299 if (failed(
300 applyPartialConversion(getOperation(), target, std::move(patterns))))
301 signalPassFailure();
302}
return success()
b getContext())
static bool isSizeOneVector(Type type)
static bool isSPIRVCompatibleFloatOrVec(Type type)
static void populateOCLExtSetOpPatterns(const LLVMTypeConverter &converter, RewritePatternSet &patterns, PatternBenefit benefit, StringRef opName)
static llvm::ManagedStatic< PassManagerOptions > options
Conversion from types to the LLVM IR dialect.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
Location getLoc()
The source location the operation was defined or derived from.
This provides public APIs that all operations should have.
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isFloat() const
Return true if this is an float type (with the specified width).
Definition Types.cpp:47
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
TargetOp::Properties getProperties() const
ArrayRef< NamedAttribute > getDiscardableAttrs() const
FailureOr< LLVM::LLVMFuncOp > lookupOrCreateFn(OpBuilder &b, Operation *moduleOp, StringRef name, ArrayRef< Type > paramTypes={}, Type resultType={}, bool isVarArg=false, bool isReserved=false, SymbolTableCollection *symbolTables=nullptr)
Create a FuncOp with signature resultType(paramTypes) and name name`.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Include the generated interface declarations.
void populateMathToScalarOCLExtSetConversionPatterns(const LLVMTypeConverter &converter, RewritePatternSet &patterns, PatternBenefit benefit=1)
Populate the given list with patterns that convert from Math to OCL LLVM-SPV builtin calls.
void populateMathToXeVMConversionPatterns(RewritePatternSet &patterns, bool convertArith, PatternBenefit benefit=1)
Populate the given list with patterns that convert from Math to XeVM calls.
Convert math ops marked with fast (afn) to native OpenCL intrinsics.
const StringRef nativeFunc
ConvertNativeFuncPattern(MLIRContext *context, StringRef nativeFunc, PatternBenefit benefit=1)
std::string getMangledNativeFuncName(const ArrayRef< Type > operandTypes) const
LogicalResult matchAndRewrite(Op op, typename Op::Adaptor adaptor, ConversionPatternRewriter &rewriter) const override
Rewriting that replaces SourceOp with a CallOp to f32Func or f64Func or f32ApproxFunc or f16Func or i...
Unrolls SourceOp to array/vector elements.