MLIR 24.0.0git
MathToFuncs.cpp
Go to the documentation of this file.
1//===- MathToFuncs.cpp - Math to outlined implementation conversion -------===//
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
10
21#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/DenseMap.h"
24#include "llvm/ADT/TypeSwitch.h"
25#include "llvm/Support/DebugLog.h"
26
27namespace mlir {
28#define GEN_PASS_DEF_CONVERTMATHTOFUNCS
29#include "mlir/Conversion/Passes.h.inc"
30} // namespace mlir
31
32using namespace mlir;
33
34#define DEBUG_TYPE "math-to-funcs"
35
36namespace {
37// Pattern to convert vector operations to scalar operations.
38template <typename Op>
39struct VecOpToScalarOp : public OpRewritePattern<Op> {
40public:
42
43 LogicalResult matchAndRewrite(Op op, PatternRewriter &rewriter) const final;
44};
45
46// Callback type for getting pre-generated FuncOp implementing
47// an operation of the given type.
48using GetFuncCallbackTy = function_ref<func::FuncOp(Operation *, Type)>;
49
50// Pattern to convert scalar IPowIOp into a call of outlined
51// software implementation.
52class IPowIOpLowering : public OpRewritePattern<math::IPowIOp> {
53public:
54 IPowIOpLowering(MLIRContext *context, GetFuncCallbackTy cb)
55 : OpRewritePattern<math::IPowIOp>(context), getFuncOpCallback(cb) {}
56
57 /// Convert IPowI into a call to a local function implementing
58 /// the power operation. The local function computes a scalar result,
59 /// so vector forms of IPowI are linearized.
60 LogicalResult matchAndRewrite(math::IPowIOp op,
61 PatternRewriter &rewriter) const final;
62
63private:
64 GetFuncCallbackTy getFuncOpCallback;
65};
66
67// Pattern to convert scalar FPowIOp into a call of outlined
68// software implementation.
69class FPowIOpLowering : public OpRewritePattern<math::FPowIOp> {
70public:
71 FPowIOpLowering(MLIRContext *context, GetFuncCallbackTy cb)
72 : OpRewritePattern<math::FPowIOp>(context), getFuncOpCallback(cb) {}
73
74 /// Convert FPowI into a call to a local function implementing
75 /// the power operation. The local function computes a scalar result,
76 /// so vector forms of FPowI are linearized.
77 LogicalResult matchAndRewrite(math::FPowIOp op,
78 PatternRewriter &rewriter) const final;
79
80private:
81 GetFuncCallbackTy getFuncOpCallback;
82};
83
84// Pattern to convert scalar ctlz into a call of outlined software
85// implementation.
86class CtlzOpLowering : public OpRewritePattern<math::CountLeadingZerosOp> {
87public:
88 CtlzOpLowering(MLIRContext *context, GetFuncCallbackTy cb)
90 getFuncOpCallback(cb) {}
91
92 /// Convert ctlz into a call to a local function implementing
93 /// the count leading zeros operation.
94 LogicalResult matchAndRewrite(math::CountLeadingZerosOp op,
95 PatternRewriter &rewriter) const final;
96
97private:
98 GetFuncCallbackTy getFuncOpCallback;
99};
100} // namespace
101
102template <typename Op>
103LogicalResult
104VecOpToScalarOp<Op>::matchAndRewrite(Op op, PatternRewriter &rewriter) const {
105 Type opType = op.getType();
106 Location loc = op.getLoc();
107 auto vecType = dyn_cast<VectorType>(opType);
108
109 if (!vecType)
110 return rewriter.notifyMatchFailure(op, "not a vector operation");
111 if (!vecType.hasRank())
112 return rewriter.notifyMatchFailure(op, "unknown vector rank");
113 ArrayRef<int64_t> shape = vecType.getShape();
114 int64_t numElements = vecType.getNumElements();
115
116 Type resultElementType = vecType.getElementType();
117 Attribute initValueAttr;
118 if (isa<FloatType>(resultElementType))
119 initValueAttr = FloatAttr::get(resultElementType, 0.0);
120 else
121 initValueAttr = IntegerAttr::get(resultElementType, 0);
122 Value result = arith::ConstantOp::create(
123 rewriter, loc, DenseElementsAttr::get(vecType, initValueAttr));
125 for (int64_t linearIndex = 0; linearIndex < numElements; ++linearIndex) {
126 SmallVector<int64_t> positions = delinearize(linearIndex, strides);
127 SmallVector<Value> operands;
128 for (Value input : op->getOperands())
129 operands.push_back(
130 vector::ExtractOp::create(rewriter, loc, input, positions));
131 Value scalarOp = Op::create(
132 rewriter, loc, TypeRange{vecType.getElementType()}, operands,
133 op.getProperties(), op->getDiscardableAttrDictionary().getValue());
134 result =
135 vector::InsertOp::create(rewriter, loc, scalarOp, result, positions);
136 }
137 rewriter.replaceOp(op, result);
138 return success();
139}
140
141static FunctionType getElementalFuncTypeForOp(Operation *op) {
142 SmallVector<Type, 1> resultTys(op->getNumResults());
143 SmallVector<Type, 2> inputTys(op->getNumOperands());
144 std::transform(op->result_type_begin(), op->result_type_end(),
145 resultTys.begin(),
146 [](Type ty) { return getElementTypeOrSelf(ty); });
147 std::transform(op->operand_type_begin(), op->operand_type_end(),
148 inputTys.begin(),
149 [](Type ty) { return getElementTypeOrSelf(ty); });
150 return FunctionType::get(op->getContext(), inputTys, resultTys);
151}
152
153/// Create linkonce_odr function to implement the power function with
154/// the given \p elementType type inside \p module. The \p elementType
155/// must be IntegerType, an the created function has
156/// 'IntegerType (*)(IntegerType, IntegerType)' function type.
157///
158/// template <typename T>
159/// T __mlir_math_ipowi_*(T b, T p) {
160/// if (p == T(0))
161/// return T(1);
162/// if (p < T(0)) {
163/// if (b == T(0))
164/// return T(1) / T(0); // trigger div-by-zero
165/// if (b == T(1))
166/// return T(1);
167/// if (b == T(-1)) {
168/// if (p & T(1))
169/// return T(-1);
170/// return T(1);
171/// }
172/// return T(0);
173/// }
174/// T result = T(1);
175/// while (true) {
176/// if (p & T(1))
177/// result *= b;
178/// p >>= T(1);
179/// if (p == T(0))
180/// return result;
181/// b *= b;
182/// }
183/// }
184static func::FuncOp createElementIPowIFunc(ModuleOp *module, Type elementType) {
185 assert(isa<IntegerType>(elementType) &&
186 "non-integer element type for IPowIOp");
187
188 ImplicitLocOpBuilder builder =
189 ImplicitLocOpBuilder::atBlockEnd(module->getLoc(), module->getBody());
190
191 std::string funcName("__mlir_math_ipowi");
192 llvm::raw_string_ostream nameOS(funcName);
193 nameOS << '_' << elementType;
194
195 FunctionType funcType = FunctionType::get(
196 builder.getContext(), {elementType, elementType}, elementType);
197 auto funcOp = func::FuncOp::create(builder, funcName, funcType);
198 LLVM::linkage::Linkage inlineLinkage = LLVM::linkage::Linkage::LinkonceODR;
199 Attribute linkage =
200 LLVM::LinkageAttr::get(builder.getContext(), inlineLinkage);
201 funcOp->setDiscardableAttr("llvm.linkage", linkage);
202 funcOp.setPrivate();
203
204 Block *entryBlock = funcOp.addEntryBlock();
205 Region *funcBody = entryBlock->getParent();
206
207 Value bArg = funcOp.getArgument(0);
208 Value pArg = funcOp.getArgument(1);
209 builder.setInsertionPointToEnd(entryBlock);
210 Value zeroValue = arith::ConstantOp::create(
211 builder, elementType, builder.getIntegerAttr(elementType, 0));
212 Value oneValue = arith::ConstantOp::create(
213 builder, elementType, builder.getIntegerAttr(elementType, 1));
214 Value minusOneValue = arith::ConstantOp::create(
215 builder, elementType,
216 builder.getIntegerAttr(elementType,
217 APInt(elementType.getIntOrFloatBitWidth(), -1ULL,
218 /*isSigned=*/true)));
219
220 // if (p == T(0))
221 // return T(1);
222 auto pIsZero =
223 arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, pArg, zeroValue);
224 Block *thenBlock = builder.createBlock(funcBody);
225 func::ReturnOp::create(builder, oneValue);
226 Block *fallthroughBlock = builder.createBlock(funcBody);
227 // Set up conditional branch for (p == T(0)).
228 builder.setInsertionPointToEnd(pIsZero->getBlock());
229 cf::CondBranchOp::create(builder, pIsZero, thenBlock, fallthroughBlock);
230
231 // if (p < T(0)) {
232 builder.setInsertionPointToEnd(fallthroughBlock);
233 auto pIsNeg = arith::CmpIOp::create(builder, arith::CmpIPredicate::sle, pArg,
234 zeroValue);
235 // if (b == T(0))
236 builder.createBlock(funcBody);
237 auto bIsZero =
238 arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, bArg, zeroValue);
239 // return T(1) / T(0);
240 thenBlock = builder.createBlock(funcBody);
241 func::ReturnOp::create(
242 builder,
243 arith::DivSIOp::create(builder, oneValue, zeroValue).getResult());
244 fallthroughBlock = builder.createBlock(funcBody);
245 // Set up conditional branch for (b == T(0)).
246 builder.setInsertionPointToEnd(bIsZero->getBlock());
247 cf::CondBranchOp::create(builder, bIsZero, thenBlock, fallthroughBlock);
248
249 // if (b == T(1))
250 builder.setInsertionPointToEnd(fallthroughBlock);
251 auto bIsOne =
252 arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, bArg, oneValue);
253 // return T(1);
254 thenBlock = builder.createBlock(funcBody);
255 func::ReturnOp::create(builder, oneValue);
256 fallthroughBlock = builder.createBlock(funcBody);
257 // Set up conditional branch for (b == T(1)).
258 builder.setInsertionPointToEnd(bIsOne->getBlock());
259 cf::CondBranchOp::create(builder, bIsOne, thenBlock, fallthroughBlock);
260
261 // if (b == T(-1)) {
262 builder.setInsertionPointToEnd(fallthroughBlock);
263 auto bIsMinusOne = arith::CmpIOp::create(builder, arith::CmpIPredicate::eq,
264 bArg, minusOneValue);
265 // if (p & T(1))
266 builder.createBlock(funcBody);
267 auto pIsOdd = arith::CmpIOp::create(
268 builder, arith::CmpIPredicate::ne,
269 arith::AndIOp::create(builder, pArg, oneValue), zeroValue);
270 // return T(-1);
271 thenBlock = builder.createBlock(funcBody);
272 func::ReturnOp::create(builder, minusOneValue);
273 fallthroughBlock = builder.createBlock(funcBody);
274 // Set up conditional branch for (p & T(1)).
275 builder.setInsertionPointToEnd(pIsOdd->getBlock());
276 cf::CondBranchOp::create(builder, pIsOdd, thenBlock, fallthroughBlock);
277
278 // return T(1);
279 // } // b == T(-1)
280 builder.setInsertionPointToEnd(fallthroughBlock);
281 func::ReturnOp::create(builder, oneValue);
282 fallthroughBlock = builder.createBlock(funcBody);
283 // Set up conditional branch for (b == T(-1)).
284 builder.setInsertionPointToEnd(bIsMinusOne->getBlock());
285 cf::CondBranchOp::create(builder, bIsMinusOne, pIsOdd->getBlock(),
286 fallthroughBlock);
287
288 // return T(0);
289 // } // (p < T(0))
290 builder.setInsertionPointToEnd(fallthroughBlock);
291 func::ReturnOp::create(builder, zeroValue);
292 Block *loopHeader = builder.createBlock(
293 funcBody, funcBody->end(), {elementType, elementType, elementType},
294 {builder.getLoc(), builder.getLoc(), builder.getLoc()});
295 // Set up conditional branch for (p < T(0)).
296 builder.setInsertionPointToEnd(pIsNeg->getBlock());
297 // Set initial values of 'result', 'b' and 'p' for the loop.
298 cf::CondBranchOp::create(builder, pIsNeg, bIsZero->getBlock(), loopHeader,
299 ValueRange{oneValue, bArg, pArg});
300
301 // T result = T(1);
302 // while (true) {
303 // if (p & T(1))
304 // result *= b;
305 // p >>= T(1);
306 // if (p == T(0))
307 // return result;
308 // b *= b;
309 // }
310 Value resultTmp = loopHeader->getArgument(0);
311 Value baseTmp = loopHeader->getArgument(1);
312 Value powerTmp = loopHeader->getArgument(2);
313 builder.setInsertionPointToEnd(loopHeader);
314
315 // if (p & T(1))
316 auto powerTmpIsOdd = arith::CmpIOp::create(
317 builder, arith::CmpIPredicate::ne,
318 arith::AndIOp::create(builder, powerTmp, oneValue), zeroValue);
319 thenBlock = builder.createBlock(funcBody);
320 // result *= b;
321 Value newResultTmp = arith::MulIOp::create(builder, resultTmp, baseTmp);
322 fallthroughBlock = builder.createBlock(funcBody, funcBody->end(), elementType,
323 builder.getLoc());
324 builder.setInsertionPointToEnd(thenBlock);
325 cf::BranchOp::create(builder, newResultTmp, fallthroughBlock);
326 // Set up conditional branch for (p & T(1)).
327 builder.setInsertionPointToEnd(powerTmpIsOdd->getBlock());
328 cf::CondBranchOp::create(builder, powerTmpIsOdd, thenBlock, fallthroughBlock,
329 resultTmp);
330 // Merged 'result'.
331 newResultTmp = fallthroughBlock->getArgument(0);
332
333 // p >>= T(1);
334 builder.setInsertionPointToEnd(fallthroughBlock);
335 Value newPowerTmp = arith::ShRUIOp::create(builder, powerTmp, oneValue);
336
337 // if (p == T(0))
338 auto newPowerIsZero = arith::CmpIOp::create(builder, arith::CmpIPredicate::eq,
339 newPowerTmp, zeroValue);
340 // return result;
341 thenBlock = builder.createBlock(funcBody);
342 func::ReturnOp::create(builder, newResultTmp);
343 fallthroughBlock = builder.createBlock(funcBody);
344 // Set up conditional branch for (p == T(0)).
345 builder.setInsertionPointToEnd(newPowerIsZero->getBlock());
346 cf::CondBranchOp::create(builder, newPowerIsZero, thenBlock,
347 fallthroughBlock);
348
349 // b *= b;
350 // }
351 builder.setInsertionPointToEnd(fallthroughBlock);
352 Value newBaseTmp = arith::MulIOp::create(builder, baseTmp, baseTmp);
353 // Pass new values for 'result', 'b' and 'p' to the loop header.
354 cf::BranchOp::create(
355 builder, ValueRange{newResultTmp, newBaseTmp, newPowerTmp}, loopHeader);
356 return funcOp;
357}
358
359/// Convert IPowI into a call to a local function implementing
360/// the power operation. The local function computes a scalar result,
361/// so vector forms of IPowI are linearized.
362LogicalResult
363IPowIOpLowering::matchAndRewrite(math::IPowIOp op,
364 PatternRewriter &rewriter) const {
365 auto baseType = dyn_cast<IntegerType>(op.getOperands()[0].getType());
366
367 if (!baseType)
368 return rewriter.notifyMatchFailure(op, "non-integer base operand");
369
370 // The outlined software implementation must have been already
371 // generated.
372 func::FuncOp elementFunc = getFuncOpCallback(op, baseType);
373 if (!elementFunc)
374 return rewriter.notifyMatchFailure(op, "missing software implementation");
375
376 rewriter.replaceOpWithNewOp<func::CallOp>(op, elementFunc, op.getOperands());
377 return success();
378}
379
380/// Create linkonce_odr function to implement the power function with
381/// the given \p funcType type inside \p module. The \p funcType must be
382/// 'FloatType (*)(FloatType, IntegerType)' function type.
383///
384/// template <typename T>
385/// Tb __mlir_math_fpowi_*(Tb b, Tp p) {
386/// if (p == Tp{0})
387/// return Tb{1};
388/// bool isNegativePower{p < Tp{0}}
389/// bool isMin{p == std::numeric_limits<Tp>::min()};
390/// if (isMin) {
391/// p = std::numeric_limits<Tp>::max();
392/// } else if (isNegativePower) {
393/// p = -p;
394/// }
395/// Tb result = Tb{1};
396/// Tb origBase = Tb{b};
397/// while (true) {
398/// if (p & Tp{1})
399/// result *= b;
400/// p >>= Tp{1};
401/// if (p == Tp{0})
402/// break;
403/// b *= b;
404/// }
405/// if (isMin) {
406/// result *= origBase;
407/// }
408/// if (isNegativePower) {
409/// result = Tb{1} / result;
410/// }
411/// return result;
412/// }
413static func::FuncOp createElementFPowIFunc(ModuleOp *module,
414 FunctionType funcType) {
415 auto baseType = cast<FloatType>(funcType.getInput(0));
416 auto powType = cast<IntegerType>(funcType.getInput(1));
417 ImplicitLocOpBuilder builder =
418 ImplicitLocOpBuilder::atBlockEnd(module->getLoc(), module->getBody());
419
420 std::string funcName("__mlir_math_fpowi");
421 llvm::raw_string_ostream nameOS(funcName);
422 nameOS << '_' << baseType;
423 nameOS << '_' << powType;
424 auto funcOp = func::FuncOp::create(builder, funcName, funcType);
425 LLVM::linkage::Linkage inlineLinkage = LLVM::linkage::Linkage::LinkonceODR;
426 Attribute linkage =
427 LLVM::LinkageAttr::get(builder.getContext(), inlineLinkage);
428 funcOp->setDiscardableAttr("llvm.linkage", linkage);
429 funcOp.setPrivate();
430
431 Block *entryBlock = funcOp.addEntryBlock();
432 Region *funcBody = entryBlock->getParent();
433
434 Value bArg = funcOp.getArgument(0);
435 Value pArg = funcOp.getArgument(1);
436 builder.setInsertionPointToEnd(entryBlock);
437 Value oneBValue = arith::ConstantOp::create(
438 builder, baseType, builder.getFloatAttr(baseType, 1.0));
439 Value zeroPValue = arith::ConstantOp::create(
440 builder, powType, builder.getIntegerAttr(powType, 0));
441 Value onePValue = arith::ConstantOp::create(
442 builder, powType, builder.getIntegerAttr(powType, 1));
443 Value minPValue = arith::ConstantOp::create(
444 builder, powType,
445 builder.getIntegerAttr(
446 powType, llvm::APInt::getSignedMinValue(powType.getWidth())));
447 Value maxPValue = arith::ConstantOp::create(
448 builder, powType,
449 builder.getIntegerAttr(
450 powType, llvm::APInt::getSignedMaxValue(powType.getWidth())));
451
452 // if (p == Tp{0})
453 // return Tb{1};
454 auto pIsZero = arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, pArg,
455 zeroPValue);
456 Block *thenBlock = builder.createBlock(funcBody);
457 func::ReturnOp::create(builder, oneBValue);
458 Block *fallthroughBlock = builder.createBlock(funcBody);
459 // Set up conditional branch for (p == Tp{0}).
460 builder.setInsertionPointToEnd(pIsZero->getBlock());
461 cf::CondBranchOp::create(builder, pIsZero, thenBlock, fallthroughBlock);
462
463 builder.setInsertionPointToEnd(fallthroughBlock);
464 // bool isNegativePower{p < Tp{0}}
465 auto pIsNeg = arith::CmpIOp::create(builder, arith::CmpIPredicate::sle, pArg,
466 zeroPValue);
467 // bool isMin{p == std::numeric_limits<Tp>::min()};
468 auto pIsMin =
469 arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, pArg, minPValue);
470
471 // if (isMin) {
472 // p = std::numeric_limits<Tp>::max();
473 // } else if (isNegativePower) {
474 // p = -p;
475 // }
476 Value negP = arith::SubIOp::create(builder, zeroPValue, pArg);
477 auto pInit = arith::SelectOp::create(builder, pIsNeg, negP, pArg);
478 pInit = arith::SelectOp::create(builder, pIsMin, maxPValue, pInit);
479
480 // Tb result = Tb{1};
481 // Tb origBase = Tb{b};
482 // while (true) {
483 // if (p & Tp{1})
484 // result *= b;
485 // p >>= Tp{1};
486 // if (p == Tp{0})
487 // break;
488 // b *= b;
489 // }
490 Block *loopHeader = builder.createBlock(
491 funcBody, funcBody->end(), {baseType, baseType, powType},
492 {builder.getLoc(), builder.getLoc(), builder.getLoc()});
493 // Set initial values of 'result', 'b' and 'p' for the loop.
494 builder.setInsertionPointToEnd(pInit->getBlock());
495 cf::BranchOp::create(builder, loopHeader, ValueRange{oneBValue, bArg, pInit});
496
497 // Create loop body.
498 Value resultTmp = loopHeader->getArgument(0);
499 Value baseTmp = loopHeader->getArgument(1);
500 Value powerTmp = loopHeader->getArgument(2);
501 builder.setInsertionPointToEnd(loopHeader);
502
503 // if (p & Tp{1})
504 auto powerTmpIsOdd = arith::CmpIOp::create(
505 builder, arith::CmpIPredicate::ne,
506 arith::AndIOp::create(builder, powerTmp, onePValue), zeroPValue);
507 thenBlock = builder.createBlock(funcBody);
508 // result *= b;
509 Value newResultTmp = arith::MulFOp::create(builder, resultTmp, baseTmp);
510 fallthroughBlock = builder.createBlock(funcBody, funcBody->end(), baseType,
511 builder.getLoc());
512 builder.setInsertionPointToEnd(thenBlock);
513 cf::BranchOp::create(builder, newResultTmp, fallthroughBlock);
514 // Set up conditional branch for (p & Tp{1}).
515 builder.setInsertionPointToEnd(powerTmpIsOdd->getBlock());
516 cf::CondBranchOp::create(builder, powerTmpIsOdd, thenBlock, fallthroughBlock,
517 resultTmp);
518 // Merged 'result'.
519 newResultTmp = fallthroughBlock->getArgument(0);
520
521 // p >>= Tp{1};
522 builder.setInsertionPointToEnd(fallthroughBlock);
523 Value newPowerTmp = arith::ShRUIOp::create(builder, powerTmp, onePValue);
524
525 // if (p == Tp{0})
526 auto newPowerIsZero = arith::CmpIOp::create(builder, arith::CmpIPredicate::eq,
527 newPowerTmp, zeroPValue);
528 // break;
529 //
530 // The conditional branch is finalized below with a jump to
531 // the loop exit block.
532 fallthroughBlock = builder.createBlock(funcBody);
533
534 // b *= b;
535 // }
536 builder.setInsertionPointToEnd(fallthroughBlock);
537 Value newBaseTmp = arith::MulFOp::create(builder, baseTmp, baseTmp);
538 // Pass new values for 'result', 'b' and 'p' to the loop header.
539 cf::BranchOp::create(
540 builder, ValueRange{newResultTmp, newBaseTmp, newPowerTmp}, loopHeader);
541
542 // Set up conditional branch for early loop exit:
543 // if (p == Tp{0})
544 // break;
545 Block *loopExit = builder.createBlock(funcBody, funcBody->end(), baseType,
546 builder.getLoc());
547 builder.setInsertionPointToEnd(newPowerIsZero->getBlock());
548 cf::CondBranchOp::create(builder, newPowerIsZero, loopExit, newResultTmp,
549 fallthroughBlock, ValueRange{});
550
551 // if (isMin) {
552 // result *= origBase;
553 // }
554 newResultTmp = loopExit->getArgument(0);
555 thenBlock = builder.createBlock(funcBody);
556 fallthroughBlock = builder.createBlock(funcBody, funcBody->end(), baseType,
557 builder.getLoc());
558 builder.setInsertionPointToEnd(loopExit);
559 cf::CondBranchOp::create(builder, pIsMin, thenBlock, fallthroughBlock,
560 newResultTmp);
561 builder.setInsertionPointToEnd(thenBlock);
562 newResultTmp = arith::MulFOp::create(builder, newResultTmp, bArg);
563 cf::BranchOp::create(builder, newResultTmp, fallthroughBlock);
564
565 /// if (isNegativePower) {
566 /// result = Tb{1} / result;
567 /// }
568 newResultTmp = fallthroughBlock->getArgument(0);
569 thenBlock = builder.createBlock(funcBody);
570 Block *returnBlock = builder.createBlock(funcBody, funcBody->end(), baseType,
571 builder.getLoc());
572 builder.setInsertionPointToEnd(fallthroughBlock);
573 cf::CondBranchOp::create(builder, pIsNeg, thenBlock, returnBlock,
574 newResultTmp);
575 builder.setInsertionPointToEnd(thenBlock);
576 newResultTmp = arith::DivFOp::create(builder, oneBValue, newResultTmp);
577 cf::BranchOp::create(builder, newResultTmp, returnBlock);
578
579 // return result;
580 builder.setInsertionPointToEnd(returnBlock);
581 func::ReturnOp::create(builder, returnBlock->getArgument(0));
582
583 return funcOp;
584}
585
586/// Convert FPowI into a call to a local function implementing
587/// the power operation. The local function computes a scalar result,
588/// so vector forms of FPowI are linearized.
589LogicalResult
590FPowIOpLowering::matchAndRewrite(math::FPowIOp op,
591 PatternRewriter &rewriter) const {
592 if (isa<VectorType>(op.getType()))
593 return rewriter.notifyMatchFailure(op, "non-scalar operation");
594
595 FunctionType funcType = getElementalFuncTypeForOp(op);
596
597 // The outlined software implementation must have been already
598 // generated.
599 func::FuncOp elementFunc = getFuncOpCallback(op, funcType);
600 if (!elementFunc)
601 return rewriter.notifyMatchFailure(op, "missing software implementation");
602
603 rewriter.replaceOpWithNewOp<func::CallOp>(op, elementFunc, op.getOperands());
604 return success();
605}
606
607/// Create function to implement the ctlz function the given \p elementType type
608/// inside \p module. The \p elementType must be IntegerType, an the created
609/// function has 'IntegerType (*)(IntegerType)' function type.
610///
611/// template <typename T>
612/// T __mlir_math_ctlz_*(T x) {
613/// bits = sizeof(x) * 8;
614/// if (x == 0)
615/// return bits;
616///
617/// uint32_t n = 0;
618/// for (int i = 1; i < bits; ++i) {
619/// if (x < 0) continue;
620/// n++;
621/// x <<= 1;
622/// }
623/// return n;
624/// }
625///
626/// Converts to (for i32):
627///
628/// func.func private @__mlir_math_ctlz_i32(%arg: i32) -> i32 {
629/// %c_32 = arith.constant 32 : index
630/// %c_0 = arith.constant 0 : i32
631/// %arg_eq_zero = arith.cmpi eq, %arg, %c_0 : i1
632/// %out = scf.if %arg_eq_zero {
633/// scf.yield %c_32 : i32
634/// } else {
635/// %c_1index = arith.constant 1 : index
636/// %c_1i32 = arith.constant 1 : i32
637/// %n = arith.constant 0 : i32
638/// %arg_out, %n_out = scf.for %i = %c_1index to %c_32 step %c_1index
639/// iter_args(%arg_iter = %arg, %n_iter = %n) -> (i32, i32) {
640/// %cond = arith.cmpi slt, %arg_iter, %c_0 : i32
641/// %yield_val = scf.if %cond {
642/// scf.yield %arg_iter, %n_iter : i32, i32
643/// } else {
644/// %arg_next = arith.shli %arg_iter, %c_1i32 : i32
645/// %n_next = arith.addi %n_iter, %c_1i32 : i32
646/// scf.yield %arg_next, %n_next : i32, i32
647/// }
648/// scf.yield %yield_val: i32, i32
649/// }
650/// scf.yield %n_out : i32
651/// }
652/// return %out: i32
653/// }
654static func::FuncOp createCtlzFunc(ModuleOp *module, Type elementType) {
655 if (!isa<IntegerType>(elementType)) {
656 LDBG() << "non-integer element type for CtlzFunc; type was: "
657 << elementType;
658 llvm_unreachable("non-integer element type");
659 }
660 int64_t bitWidth = elementType.getIntOrFloatBitWidth();
661
662 Location loc = module->getLoc();
663 ImplicitLocOpBuilder builder =
664 ImplicitLocOpBuilder::atBlockEnd(loc, module->getBody());
665
666 std::string funcName("__mlir_math_ctlz");
667 llvm::raw_string_ostream nameOS(funcName);
668 nameOS << '_' << elementType;
669 FunctionType funcType =
670 FunctionType::get(builder.getContext(), {elementType}, elementType);
671 auto funcOp = func::FuncOp::create(builder, funcName, funcType);
672
673 // LinkonceODR ensures that there is only one implementation of this function
674 // across all math.ctlz functions that are lowered in this way.
675 LLVM::linkage::Linkage inlineLinkage = LLVM::linkage::Linkage::LinkonceODR;
676 Attribute linkage =
677 LLVM::LinkageAttr::get(builder.getContext(), inlineLinkage);
678 funcOp->setDiscardableAttr("llvm.linkage", linkage);
679 funcOp.setPrivate();
680
681 // set the insertion point to the start of the function
682 Block *funcBody = funcOp.addEntryBlock();
683 builder.setInsertionPointToStart(funcBody);
684
685 Value arg = funcOp.getArgument(0);
686 Type indexType = builder.getIndexType();
687 Value bitWidthValue = arith::ConstantOp::create(
688 builder, elementType, builder.getIntegerAttr(elementType, bitWidth));
689 Value zeroValue = arith::ConstantOp::create(
690 builder, elementType, builder.getIntegerAttr(elementType, 0));
691
692 Value inputEqZero =
693 arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, arg, zeroValue);
694
695 // if input == 0, return bit width, else enter loop.
696 scf::IfOp ifOp =
697 scf::IfOp::create(builder, elementType, inputEqZero,
698 /*addThenBlock=*/true, /*addElseBlock=*/true);
699 auto thenBuilder = ifOp.getThenBodyBuilder();
700 scf::YieldOp::create(thenBuilder, loc, bitWidthValue);
701
702 auto elseBuilder =
703 ImplicitLocOpBuilder::atBlockEnd(loc, &ifOp.getElseRegion().front());
704
705 Value oneIndex = arith::ConstantOp::create(elseBuilder, indexType,
706 elseBuilder.getIndexAttr(1));
707 Value oneValue = arith::ConstantOp::create(
708 elseBuilder, elementType, elseBuilder.getIntegerAttr(elementType, 1));
709 Value bitWidthIndex = arith::ConstantOp::create(
710 elseBuilder, indexType, elseBuilder.getIndexAttr(bitWidth));
711 Value nValue = arith::ConstantOp::create(
712 elseBuilder, elementType, elseBuilder.getIntegerAttr(elementType, 0));
713
714 auto loop = scf::ForOp::create(
715 elseBuilder, oneIndex, bitWidthIndex, oneIndex,
716 // Initial values for two loop induction variables, the arg which is being
717 // shifted left in each iteration, and the n value which tracks the count
718 // of leading zeros.
719 ValueRange{arg, nValue},
720 // Callback to build the body of the for loop
721 // if (arg < 0) {
722 // continue;
723 // } else {
724 // n++;
725 // arg <<= 1;
726 // }
727 [&](OpBuilder &b, Location loc, Value iv, ValueRange args) {
728 Value argIter = args[0];
729 Value nIter = args[1];
730
731 Value argIsNonNegative = arith::CmpIOp::create(
732 b, loc, arith::CmpIPredicate::slt, argIter, zeroValue);
733 scf::IfOp ifOp = scf::IfOp::create(
734 b, loc, argIsNonNegative,
735 [&](OpBuilder &b, Location loc) {
736 // If arg is negative, continue (effectively, break)
737 scf::YieldOp::create(b, loc, ValueRange{argIter, nIter});
738 },
739 [&](OpBuilder &b, Location loc) {
740 // Otherwise, increment n and shift arg left.
741 Value nNext = arith::AddIOp::create(b, loc, nIter, oneValue);
742 Value argNext = arith::ShLIOp::create(b, loc, argIter, oneValue);
743 scf::YieldOp::create(b, loc, ValueRange{argNext, nNext});
744 });
745 scf::YieldOp::create(b, loc, ifOp.getResults());
746 });
747 scf::YieldOp::create(elseBuilder, loop.getResult(1));
748
749 func::ReturnOp::create(builder, ifOp.getResult(0));
750 return funcOp;
751}
752
753/// Convert ctlz into a call to a local function implementing the ctlz
754/// operation.
755LogicalResult CtlzOpLowering::matchAndRewrite(math::CountLeadingZerosOp op,
756 PatternRewriter &rewriter) const {
757 if (isa<VectorType>(op.getType()))
758 return rewriter.notifyMatchFailure(op, "non-scalar operation");
759
760 Type type = getElementTypeOrSelf(op.getResult().getType());
761 func::FuncOp elementFunc = getFuncOpCallback(op, type);
762 if (!elementFunc)
763 return rewriter.notifyMatchFailure(op, [&](::mlir::Diagnostic &diag) {
764 diag << "Missing software implementation for op " << op->getName()
765 << " and type " << type;
766 });
767
768 rewriter.replaceOpWithNewOp<func::CallOp>(op, elementFunc, op.getOperand());
769 return success();
770}
771
772namespace {
773struct ConvertMathToFuncsPass
774 : public impl::ConvertMathToFuncsBase<ConvertMathToFuncsPass> {
775 ConvertMathToFuncsPass() = default;
776 ConvertMathToFuncsPass(const ConvertMathToFuncsOptions &options)
777 : impl::ConvertMathToFuncsBase<ConvertMathToFuncsPass>(options) {}
778
779 void runOnOperation() override;
780
781private:
782 // Return true, if this FPowI operation must be converted
783 // because the width of its exponent's type is greater than
784 // or equal to minWidthOfFPowIExponent option value.
785 bool isFPowIConvertible(math::FPowIOp op);
786
787 // Reture true, if operation is integer type.
788 bool isConvertible(Operation *op);
789
790 // Generate outlined implementations for power operations
791 // and store them in funcImpls map.
792 void generateOpImplementations();
793
794 // A map between pairs of (operation, type) deduced from operations that this
795 // pass will convert, and the corresponding outlined software implementations
796 // of these operations for the given type.
797 DenseMap<std::pair<OperationName, Type>, func::FuncOp> funcImpls;
798};
799} // namespace
800
801bool ConvertMathToFuncsPass::isFPowIConvertible(math::FPowIOp op) {
802 auto expTy =
803 dyn_cast<IntegerType>(getElementTypeOrSelf(op.getRhs().getType()));
804 return (expTy && expTy.getWidth() >= minWidthOfFPowIExponent);
805}
806
807bool ConvertMathToFuncsPass::isConvertible(Operation *op) {
808 return isa<IntegerType>(getElementTypeOrSelf(op->getResult(0).getType()));
809}
810
811void ConvertMathToFuncsPass::generateOpImplementations() {
812 ModuleOp module = getOperation();
813
814 module.walk([&](Operation *op) {
815 TypeSwitch<Operation *>(op)
816 .Case([&](math::CountLeadingZerosOp op) {
817 if (!convertCtlz || !isConvertible(op))
818 return;
819 Type resultType = getElementTypeOrSelf(op.getResult().getType());
820
821 // Generate the software implementation of this operation,
822 // if it has not been generated yet.
823 auto key = std::pair(op->getName(), resultType);
824 auto entry = funcImpls.try_emplace(key, func::FuncOp{});
825 if (entry.second)
826 entry.first->second = createCtlzFunc(&module, resultType);
827 })
828 .Case([&](math::IPowIOp op) {
829 if (!isConvertible(op))
830 return;
831
832 Type resultType = getElementTypeOrSelf(op.getResult().getType());
833
834 // Generate the software implementation of this operation,
835 // if it has not been generated yet.
836 auto key = std::pair(op->getName(), resultType);
837 auto entry = funcImpls.try_emplace(key, func::FuncOp{});
838 if (entry.second)
839 entry.first->second = createElementIPowIFunc(&module, resultType);
840 })
841 .Case([&](math::FPowIOp op) {
842 if (!isFPowIConvertible(op))
843 return;
844
845 FunctionType funcType = getElementalFuncTypeForOp(op);
846
847 // Generate the software implementation of this operation,
848 // if it has not been generated yet.
849 // FPowI implementations are mapped via the FunctionType
850 // created from the operation's result and operands.
851 auto key = std::pair(op->getName(), funcType);
852 auto entry = funcImpls.try_emplace(key, func::FuncOp{});
853 if (entry.second)
854 entry.first->second = createElementFPowIFunc(&module, funcType);
855 });
856 });
857}
858
859void ConvertMathToFuncsPass::runOnOperation() {
860 ModuleOp module = getOperation();
861
862 // Create outlined implementations for power operations.
863 generateOpImplementations();
864
865 RewritePatternSet patterns(&getContext());
866 patterns.add<VecOpToScalarOp<math::IPowIOp>, VecOpToScalarOp<math::FPowIOp>,
867 VecOpToScalarOp<math::CountLeadingZerosOp>>(
868 patterns.getContext());
869
870 // For the given Type Returns FuncOp stored in funcImpls map.
871 auto getFuncOpByType = [&](Operation *op, Type type) -> func::FuncOp {
872 auto it = funcImpls.find(std::pair(op->getName(), type));
873 if (it == funcImpls.end())
874 return {};
875
876 return it->second;
877 };
878 patterns.add<IPowIOpLowering, FPowIOpLowering>(patterns.getContext(),
879 getFuncOpByType);
880
881 if (convertCtlz)
882 patterns.add<CtlzOpLowering>(patterns.getContext(), getFuncOpByType);
883
884 ConversionTarget target(getContext());
885 target.addLegalDialect<arith::ArithDialect, cf::ControlFlowDialect,
886 func::FuncDialect, scf::SCFDialect,
887 vector::VectorDialect>();
888
889 target.addDynamicallyLegalOp<math::IPowIOp>(
890 [this](math::IPowIOp op) { return !isConvertible(op); });
891 if (convertCtlz) {
892 target.addDynamicallyLegalOp<math::CountLeadingZerosOp>(
893 [this](math::CountLeadingZerosOp op) { return !isConvertible(op); });
894 }
895 target.addDynamicallyLegalOp<math::FPowIOp>(
896 [this](math::FPowIOp op) { return !isFPowIConvertible(op); });
897 if (failed(applyPartialConversion(module, target, std::move(patterns))))
898 signalPassFailure();
899}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static func::FuncOp createElementIPowIFunc(ModuleOp *module, Type elementType)
Create linkonce_odr function to implement the power function with the given elementType type inside m...
static FunctionType getElementalFuncTypeForOp(Operation *op)
static func::FuncOp createElementFPowIFunc(ModuleOp *module, FunctionType funcType)
Create linkonce_odr function to implement the power function with the given funcType type inside modu...
static func::FuncOp createCtlzFunc(ModuleOp *module, Type elementType)
Create function to implement the ctlz function the given elementType type inside module.
static std::string diag(const llvm::Value &value)
static llvm::ManagedStatic< PassManagerOptions > options
Attributes are known-constant values of operations.
Definition Attributes.h:25
Block represents an ordered list of Operations.
Definition Block.h:34
BlockArgument getArgument(unsigned i)
Definition Block.h:154
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
Location getLoc() const
Accessors for the implied location.
Definition Builders.h:665
static ImplicitLocOpBuilder atBlockEnd(Location loc, Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to after the last operation in the block but still insid...
Definition Builders.h:649
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
This class helps build Operations.
Definition Builders.h:210
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
Block * getBlock() const
Returns the current block of the builder.
Definition Builders.h:451
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
Location getLoc()
The source location the operation was defined or derived from.
This provides public APIs that all operations should have.
InferredProperties< T > & getProperties()
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
operand_type_iterator operand_type_end()
Definition Operation.h:421
unsigned getNumOperands()
Definition Operation.h:371
result_type_iterator result_type_end()
Definition Operation.h:452
result_type_iterator result_type_begin()
Definition Operation.h:451
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
operand_type_iterator operand_type_begin()
Definition Operation.h:420
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
iterator end()
Definition Region.h:56
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:733
Include the generated interface declarations.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
SmallVector< int64_t > delinearize(int64_t linearIndex, ArrayRef< int64_t > strides)
Given the strides together with a linear index in the dimension space, return the vector-space offset...
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...