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
17#include "mlir/Pass/Pass.h"
18#include "llvm/Support/FormatVariadic.h"
19
22
23namespace mlir {
24#define GEN_PASS_DEF_CONVERTMATHTOXEVM
25#include "mlir/Conversion/Passes.h.inc"
26} // namespace mlir
27
28using namespace mlir;
29
30#define DEBUG_TYPE "math-to-xevm"
31
32static bool isSizeOneVector(Type type) {
33 auto vecType = dyn_cast<VectorType>(type);
34 return vecType && vecType.getShape().size() == 1 &&
35 vecType.getShape()[0] == 1 && vecType.getElementType().isFloat();
36}
37
39 if (type.isFloat())
40 return true;
41 if (auto vecType = dyn_cast<VectorType>(type)) {
42 if (!vecType.getElementType().isFloat())
43 return false;
44 // SPIRV distinguishes between vectors and matrices: OpenCL native math
45 // intrsinics are not compatible with matrices.
46 ArrayRef<int64_t> shape = vecType.getShape();
47 if (shape.size() != 1)
48 return false;
49 // SPIRV has no size-1 vector type; such degenerate vectors are handled
50 // by unwrapping to the scalar intrinsic (see matchAndRewrite).
51 if (shape[0] == 1)
52 return true;
53 // SPIRV only allows vectors of size 2, 3, 4, 8, 16.
54 if (shape[0] == 2 || shape[0] == 3 || shape[0] == 4 || shape[0] == 8 ||
55 shape[0] == 16)
56 return true;
57 }
58 return false;
59}
60
61/// Convert math ops marked with `fast` (`afn`) to native OpenCL intrinsics.
62template <typename Op>
63struct ConvertNativeFuncPattern final : public OpConversionPattern<Op> {
64
66 PatternBenefit benefit = 1)
67 : OpConversionPattern<Op>(context, benefit), nativeFunc(nativeFunc) {}
68
69 inline std::string
70 getMangledNativeFuncName(const ArrayRef<Type> operandTypes) const {
71 std::string mangledFuncName =
72 "_Z" + std::to_string(nativeFunc.size()) + nativeFunc.str();
73
74 auto appendFloatToMangledFunc = [&mangledFuncName](Type type) {
75 if (type.isF32())
76 mangledFuncName += "f";
77 else if (type.isF16())
78 mangledFuncName += "Dh";
79 else if (type.isF64())
80 mangledFuncName += "d";
81 };
82
83 for (auto type : operandTypes) {
84 if (auto vecType = dyn_cast<VectorType>(type)) {
85 mangledFuncName += "Dv" + std::to_string(vecType.getShape()[0]) + "_";
86 appendFloatToMangledFunc(vecType.getElementType());
87 } else
88 appendFloatToMangledFunc(type);
89 }
90
91 return mangledFuncName;
92 }
93
94 LogicalResult
95 matchAndRewrite(Op op, typename Op::Adaptor adaptor,
96 ConversionPatternRewriter &rewriter) const override {
97 if (!isSPIRVCompatibleFloatOrVec(op.getType()))
98 return failure();
99
100 arith::FastMathFlags fastFlags = op.getFastmath();
101 if (!arith::bitEnumContainsAll(fastFlags, arith::FastMathFlags::afn))
102 return rewriter.notifyMatchFailure(op, "not a fastmath `afn` operation");
103
104 Location loc = op.getLoc();
105
106 // SPIRV has no size-1 vector type: such vectors are the degenerate result
107 // of distributing/linearizing larger vectors down to a single element (e.g.
108 // by the XeGPU lowering pipeline). They have no OpenCL vector intrinsic, so
109 // unwrap them to the scalar element type and use the scalar intrinsic.
110 SmallVector<Value, 1> operands(adaptor.getOperands());
111 SmallVector<Type, 1> operandTypes;
112 bool unwrapSizeOneVec = isSizeOneVector(op.getType());
113 for (Value &operand : operands) {
114 Type opTy = operand.getType();
115 // This pass only supports operations on vectors that are already in SPIRV
116 // supported vector sizes: Distributing unsupported vector sizes to SPIRV
117 // supported vector sizes are done in other blocking optimization passes.
119 return rewriter.notifyMatchFailure(
120 op, llvm::formatv("incompatible operand type: '{0}'", opTy));
121 if (unwrapSizeOneVec) {
122 assert(isSizeOneVector(opTy) &&
123 "expected all operands to be size-1 vectors");
124 opTy = cast<VectorType>(opTy).getElementType();
125 operand = vector::ExtractOp::create(rewriter, loc, operand,
127 }
128 operandTypes.push_back(opTy);
129 }
130
131 Type resultType = unwrapSizeOneVec
132 ? cast<VectorType>(op.getType()).getElementType()
133 : op.getType();
134
135 auto moduleOp = op->template getParentWithTrait<OpTrait::SymbolTable>();
136 auto funcOpRes = LLVM::lookupOrCreateFn(
137 rewriter, moduleOp, getMangledNativeFuncName(operandTypes),
138 operandTypes, resultType);
139 assert(!failed(funcOpRes));
140 LLVM::LLVMFuncOp funcOp = funcOpRes.value();
141
142 auto callOp = LLVM::CallOp::create(rewriter, loc, funcOp, operands);
143 // Preserve fastmath flags in our MLIR op when converting to llvm function
144 // calls, in order to allow further fastmath optimizations: We thus need to
145 // convert arith fastmath attrs into attrs recognized by llvm.
147 SmallVector<NamedAttribute> discardableAttrs;
148 for (mlir::NamedAttribute attr : fastAttrConverter.getAttrs()) {
149 if (attr.getName() == LLVM::CallOp::getFastmathAttrName()) {
150 callOp.setFastmathFlagsAttr(
151 cast<LLVM::FastmathFlagsAttr>(attr.getValue()));
152 continue;
153 }
154 discardableAttrs.push_back(attr);
155 }
156 callOp->setDiscardableAttrs(discardableAttrs);
157
158 if (unwrapSizeOneVec) {
159 // Re-wrap the scalar result back into a size-1 vector to preserve types.
160 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(op, op.getType(),
161 callOp.getResult());
162 } else {
163 rewriter.replaceOp(op, callOp);
164 }
165 return success();
166 }
167
168 const StringRef nativeFunc;
169};
170
171template <typename OpTy>
173 RewritePatternSet &patterns,
174 PatternBenefit benefit,
175 StringRef opName) {
176 std::string prefix = "__spirv_ocl_";
177 std::string mangledName = "_Z" +
178 std::to_string(prefix.size() + opName.size()) +
179 prefix + opName.str();
180
181 patterns.add<ScalarizeVectorOpLowering<OpTy>>(converter, benefit);
183 converter, mangledName + "f", mangledName + "d",
184 /*f32ApproxFunc=*/"", /*f16Func=*/"",
185 /*i32Func=*/"", benefit, LLVM::cconv::CConv::SPIR_FUNC);
186}
187
189 const LLVMTypeConverter &converter, RewritePatternSet &patterns,
190 PatternBenefit benefit) {
191 populateOCLExtSetOpPatterns<math::AcosOp>(converter, patterns, benefit,
192 "acos");
193 populateOCLExtSetOpPatterns<math::AcoshOp>(converter, patterns, benefit,
194 "acosh");
195 populateOCLExtSetOpPatterns<math::AsinOp>(converter, patterns, benefit,
196 "asin");
197 populateOCLExtSetOpPatterns<math::AsinhOp>(converter, patterns, benefit,
198 "asinh");
199 populateOCLExtSetOpPatterns<math::AtanOp>(converter, patterns, benefit,
200 "atan");
201 populateOCLExtSetOpPatterns<math::Atan2Op>(converter, patterns, benefit,
202 "atan2");
203 populateOCLExtSetOpPatterns<math::AtanhOp>(converter, patterns, benefit,
204 "atanh");
205 populateOCLExtSetOpPatterns<math::CbrtOp>(converter, patterns, benefit,
206 "cbrt");
207 populateOCLExtSetOpPatterns<math::CopySignOp>(converter, patterns, benefit,
208 "copysign");
209 populateOCLExtSetOpPatterns<math::CosOp>(converter, patterns, benefit, "cos");
210 populateOCLExtSetOpPatterns<math::CoshOp>(converter, patterns, benefit,
211 "cosh");
212 populateOCLExtSetOpPatterns<math::ErfOp>(converter, patterns, benefit, "erf");
213 populateOCLExtSetOpPatterns<math::ErfcOp>(converter, patterns, benefit,
214 "erfc");
215 populateOCLExtSetOpPatterns<math::ExpOp>(converter, patterns, benefit, "exp");
216 populateOCLExtSetOpPatterns<math::Exp2Op>(converter, patterns, benefit,
217 "exp2");
218 populateOCLExtSetOpPatterns<math::ExpM1Op>(converter, patterns, benefit,
219 "expm1");
220 populateOCLExtSetOpPatterns<math::LogOp>(converter, patterns, benefit, "log");
221 populateOCLExtSetOpPatterns<math::Log10Op>(converter, patterns, benefit,
222 "log10");
223 populateOCLExtSetOpPatterns<math::Log1pOp>(converter, patterns, benefit,
224 "log1p");
225 populateOCLExtSetOpPatterns<math::Log2Op>(converter, patterns, benefit,
226 "log2");
227 populateOCLExtSetOpPatterns<math::PowFOp>(converter, patterns, benefit,
228 "pow");
229 populateOCLExtSetOpPatterns<math::RsqrtOp>(converter, patterns, benefit,
230 "rsqrt");
231 populateOCLExtSetOpPatterns<math::SinOp>(converter, patterns, benefit, "sin");
232 populateOCLExtSetOpPatterns<math::SinhOp>(converter, patterns, benefit,
233 "sinh");
234 populateOCLExtSetOpPatterns<math::SqrtOp>(converter, patterns, benefit,
235 "sqrt");
236 populateOCLExtSetOpPatterns<math::TanOp>(converter, patterns, benefit, "tan");
237 populateOCLExtSetOpPatterns<math::TanhOp>(converter, patterns, benefit,
238 "tanh");
239}
240
242 bool convertArith,
243 PatternBenefit benefit) {
245 patterns.getContext(), "__spirv_ocl_native_exp", benefit);
247 patterns.getContext(), "__spirv_ocl_native_cos", benefit);
249 patterns.getContext(), "__spirv_ocl_native_exp2", benefit);
251 patterns.getContext(), "__spirv_ocl_native_log", benefit);
253 patterns.getContext(), "__spirv_ocl_native_log2", benefit);
255 patterns.getContext(), "__spirv_ocl_native_log10", benefit);
257 patterns.getContext(), "__spirv_ocl_native_powr", benefit);
259 patterns.getContext(), "__spirv_ocl_native_rsqrt", benefit);
261 patterns.getContext(), "__spirv_ocl_native_sin", benefit);
263 patterns.getContext(), "__spirv_ocl_native_sqrt", benefit);
265 patterns.getContext(), "__spirv_ocl_native_tan", benefit);
266 if (convertArith)
268 patterns.getContext(), "__spirv_ocl_native_divide", benefit);
269}
270
271namespace {
272struct ConvertMathToXeVMPass
273 : public impl::ConvertMathToXeVMBase<ConvertMathToXeVMPass> {
274 using Base::Base;
275 void runOnOperation() override;
276};
277} // namespace
278
279void ConvertMathToXeVMPass::runOnOperation() {
280 Operation *op = getOperation();
281 MLIRContext *ctx = op->getContext();
282
283 const auto &dl = getAnalysis<DataLayoutAnalysis>();
284
285 RewritePatternSet patterns(&getContext());
286 LowerToLLVMOptions options(ctx, dl.getAtOrAbove(op));
287 LLVMTypeConverter converter(ctx, options);
288 ConversionTarget target(getContext());
289
290 // Native OCL patterns should take precedence for `fast` ops even when
291 // convertToOCL is set.
292 populateMathToXeVMConversionPatterns(patterns, convertArith,
293 convertToOCL + 1);
294 if (convertToOCL) {
296 target
297 .addIllegalOp<LLVM::CosOp, LLVM::ExpOp, LLVM::Exp2Op, LLVM::LogOp,
298 LLVM::Log10Op, LLVM::Log2Op, LLVM::SinOp, LLVM::SqrtOp>();
299 }
300 target.addLegalDialect<BuiltinDialect, LLVM::LLVMDialect>();
301 // The size-1-vector patterns unwrap to the scalar intrinsic via
302 // vector.extract / vector.broadcast; these must be legal for the partial
303 // conversion to succeed.
304 target.addLegalOp<vector::ExtractOp, vector::BroadcastOp>();
305 if (failed(
306 applyPartialConversion(getOperation(), target, std::move(patterns))))
307 signalPassFailure();
308}
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
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
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
ArrayRef< NamedAttribute > getAttrs() 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:717
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.