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