MLIR 23.0.0git
ExpandOps.cpp
Go to the documentation of this file.
1//===- ExpandPatterns.cpp - Code to expand various math operations. -------===//
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//
9// This file implements expansion of various math operations.
10//
11//===----------------------------------------------------------------------===//
12
17#include "mlir/IR/Builders.h"
18#include "mlir/IR/Matchers.h"
21
22using namespace mlir;
23
24namespace mlir::math {
25#define GEN_PASS_DEF_MATHEXPANDOPSPASS
26#include "mlir/Dialect/Math/Transforms/Passes.h.inc"
27} // namespace mlir::math
28
29/// Returns true if the type is an unranked shaped type (e.g., tensor<*xf32>).
30/// Unranked types can't be expanded because tensor.splat requires ranked types.
31static bool isUnrankedShaped(Type type) {
32 auto shapedTy = dyn_cast<ShapedType>(type);
33 return shapedTy && !shapedTy.hasRank();
34}
35
36/// Create a float constant. For dynamically-shaped tensors, creates a scalar
37/// constant and uses tensor.dim + tensor.splat to broadcast to the target
38/// shape. The optional `dynamicShapeRef` provides the runtime dimension sizes.
39static Value createFloatConst(Location loc, Type type, APFloat value,
40 OpBuilder &b, Value dynamicShapeRef = Value()) {
41 bool losesInfo = false;
42 auto eltType = getElementTypeOrSelf(type);
43 // Convert double to the given `FloatType` with round-to-nearest-ties-to-even.
44 value.convert(cast<FloatType>(eltType).getFloatSemantics(),
45 APFloat::rmNearestTiesToEven, &losesInfo);
46 auto attr = b.getFloatAttr(eltType, value);
47 if (auto shapedTy = dyn_cast<ShapedType>(type)) {
48 if (shapedTy.hasStaticShape())
49 return arith::ConstantOp::create(b, loc,
50 DenseElementsAttr::get(shapedTy, attr));
52 // Dynamic shape: create scalar constant and splat to the target shape.
53 Value scalar = arith::ConstantOp::create(b, loc, eltType, attr);
54 SmallVector<Value> dynamicSizes;
55 for (int64_t i = 0; i < shapedTy.getRank(); ++i) {
56 if (shapedTy.isDynamicDim(i))
57 dynamicSizes.push_back(
58 tensor::DimOp::create(b, loc, dynamicShapeRef, i));
59 }
60 return tensor::SplatOp::create(b, loc, type, scalar, dynamicSizes);
61 }
63 return arith::ConstantOp::create(b, loc, attr);
64}
65
66static Value createFloatConst(Location loc, Type type, double value,
67 OpBuilder &b, Value dynamicShapeRef = Value()) {
68 return createFloatConst(loc, type, APFloat(value), b, dynamicShapeRef);
69}
70
71/// Create an integer constant. For dynamically-shaped tensors, creates a scalar
72/// constant and uses tensor.dim + tensor.splat to broadcast to the target
73/// shape. The optional `dynamicShapeRef` provides the runtime dimension sizes.
74static Value createIntConst(Location loc, Type type, int64_t value,
75 OpBuilder &b, Value dynamicShapeRef = Value()) {
76 auto eltType = getElementTypeOrSelf(type);
77 auto attr = b.getIntegerAttr(eltType, value);
78 if (auto shapedTy = dyn_cast<ShapedType>(type)) {
79 if (shapedTy.hasStaticShape())
80 return arith::ConstantOp::create(b, loc,
81 DenseElementsAttr::get(shapedTy, attr));
83 // Dynamic shape: create scalar constant and splat to the target shape.
84 Value scalar = arith::ConstantOp::create(b, loc, eltType, attr);
85 SmallVector<Value> dynamicSizes;
86 for (int64_t i = 0; i < shapedTy.getRank(); ++i) {
87 if (shapedTy.isDynamicDim(i))
88 dynamicSizes.push_back(
89 tensor::DimOp::create(b, loc, dynamicShapeRef, i));
90 }
91 return tensor::SplatOp::create(b, loc, type, scalar, dynamicSizes);
92 }
94 return arith::ConstantOp::create(b, loc, attr);
95}
96
98 Type opType = operand.getType();
99 Type i64Ty = b.getI64Type();
100 if (auto shapedTy = dyn_cast<ShapedType>(opType))
101 i64Ty = shapedTy.clone(i64Ty);
102 Value fixedConvert = arith::FPToSIOp::create(b, i64Ty, operand);
103 Value fpFixedConvert = arith::SIToFPOp::create(b, opType, fixedConvert);
104 // The truncation does not preserve the sign when the truncated
105 // value is -0. So here the sign is copied again.
106 return math::CopySignOp::create(b, fpFixedConvert, operand);
107}
108
109// sinhf(float x) -> (exp(x) - exp(-x)) / 2
110static LogicalResult convertSinhOp(math::SinhOp op, PatternRewriter &rewriter) {
111 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
112 Value operand = op.getOperand();
113 Type opType = operand.getType();
114
115 if (isUnrankedShaped(opType))
116 return failure();
117
118 Value exp = math::ExpOp::create(b, operand);
119 Value neg = arith::NegFOp::create(b, operand);
120 Value nexp = math::ExpOp::create(b, neg);
121 Value sub = arith::SubFOp::create(b, exp, nexp);
122 Value half = createFloatConst(op->getLoc(), opType, 0.5, rewriter, operand);
123 Value res = arith::MulFOp::create(b, sub, half);
124 rewriter.replaceOp(op, res);
125 return success();
126}
127
128// coshf(float x) -> (exp(x) + exp(-x)) / 2
129static LogicalResult convertCoshOp(math::CoshOp op, PatternRewriter &rewriter) {
130 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
131 Value operand = op.getOperand();
132 Type opType = operand.getType();
133
134 if (isUnrankedShaped(opType))
135 return failure();
136
137 Value exp = math::ExpOp::create(b, operand);
138 Value neg = arith::NegFOp::create(b, operand);
139 Value nexp = math::ExpOp::create(b, neg);
140 Value add = arith::AddFOp::create(b, exp, nexp);
141 Value half = createFloatConst(op->getLoc(), opType, 0.5, rewriter, operand);
142 Value res = arith::MulFOp::create(b, add, half);
143 rewriter.replaceOp(op, res);
144 return success();
145}
146
147/// Expands tanh op into
148/// 1-exp^{-2x} / 1+exp^{-2x}
149/// To avoid overflow we exploit the reflection symmetry `tanh(-x) = -tanh(x)`.
150/// We compute a "signs" value which is -1 if input is negative and +1 if input
151/// is positive. Then multiply the input by this value, guaranteeing that the
152/// result is positive, which also guarantees `exp^{-2x * sign(x)}` is in (0,
153/// 1]. Expand the computation on the input `x * sign(x)`, then multiply the
154/// result by `sign(x)` to retain sign of the real result.
155static LogicalResult convertTanhOp(math::TanhOp op, PatternRewriter &rewriter) {
156 Value operand = op.getOperand();
157 auto floatType = operand.getType();
158
159 if (isUnrankedShaped(floatType))
160 return failure();
161
162 Location loc = op.getLoc();
163 Value zero = createFloatConst(loc, floatType, 0.0, rewriter, operand);
164 Value one = createFloatConst(loc, floatType, 1.0, rewriter, operand);
165 Value negTwo = createFloatConst(loc, floatType, -2.0, rewriter, operand);
166
167 // Compute sign(x) = cast<float_type>(x < 0) * (-2) + 1
168 Value isNegative = arith::CmpFOp::create(
169 rewriter, loc, arith::CmpFPredicate::OLT, operand, zero);
170 Value isNegativeFloat =
171 arith::UIToFPOp::create(rewriter, loc, floatType, isNegative);
172 Value isNegativeTimesNegTwo =
173 arith::MulFOp::create(rewriter, loc, isNegativeFloat, negTwo);
174 Value sign = arith::AddFOp::create(rewriter, loc, isNegativeTimesNegTwo, one);
175
176 // Normalize input to positive value: y = sign(x) * x
177 Value positiveX = arith::MulFOp::create(rewriter, loc, sign, operand);
178
179 // Decompose on normalized input
180 Value negDoubledX = arith::MulFOp::create(rewriter, loc, negTwo, positiveX);
181 Value exp2x = math::ExpOp::create(rewriter, loc, negDoubledX);
182 Value dividend = arith::SubFOp::create(rewriter, loc, one, exp2x);
183 Value divisor = arith::AddFOp::create(rewriter, loc, one, exp2x);
184 Value positiveRes = arith::DivFOp::create(rewriter, loc, dividend, divisor);
185
186 // Multiply result by sign(x) to retain signs from negative inputs
187 rewriter.replaceOpWithNewOp<arith::MulFOp>(op, sign, positiveRes);
188
189 return success();
190}
191
192// Converts math.tan to math.sin, math.cos, and arith.divf.
193static LogicalResult convertTanOp(math::TanOp op, PatternRewriter &rewriter) {
194 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
195 Value operand = op.getOperand();
196 Type type = operand.getType();
197 Value sin = math::SinOp::create(b, type, operand);
198 Value cos = math::CosOp::create(b, type, operand);
199 Value div = arith::DivFOp::create(b, type, sin, cos);
200 rewriter.replaceOp(op, div);
201 return success();
202}
203
204// asinh(float x) -> log(x + sqrt(x**2 + 1))
205static LogicalResult convertAsinhOp(math::AsinhOp op,
206 PatternRewriter &rewriter) {
207 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
208 Value operand = op.getOperand();
209 Type opType = operand.getType();
210
211 if (isUnrankedShaped(opType))
212 return failure();
213
214 Value one = createFloatConst(op->getLoc(), opType, 1.0, rewriter, operand);
215 Value fma = math::FmaOp::create(b, operand, operand, one);
216 Value sqrt = math::SqrtOp::create(b, fma);
217 Value add = arith::AddFOp::create(b, operand, sqrt);
218 Value res = math::LogOp::create(b, add);
219 rewriter.replaceOp(op, res);
220 return success();
221}
222
223// acosh(float x) -> log(x + sqrt(x**2 - 1))
224static LogicalResult convertAcoshOp(math::AcoshOp op,
225 PatternRewriter &rewriter) {
226 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
227 Value operand = op.getOperand();
228 Type opType = operand.getType();
229
230 if (isUnrankedShaped(opType))
231 return failure();
232
233 Value negOne =
234 createFloatConst(op->getLoc(), opType, -1.0, rewriter, operand);
235 Value fma = math::FmaOp::create(b, operand, operand, negOne);
236 Value sqrt = math::SqrtOp::create(b, fma);
237 Value add = arith::AddFOp::create(b, operand, sqrt);
238 Value res = math::LogOp::create(b, add);
239 rewriter.replaceOp(op, res);
240 return success();
241}
242
243// atanh(float x) -> log((1 + x) / (1 - x)) / 2
244static LogicalResult convertAtanhOp(math::AtanhOp op,
245 PatternRewriter &rewriter) {
246 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
247 Value operand = op.getOperand();
248 Type opType = operand.getType();
249
250 if (isUnrankedShaped(opType))
251 return failure();
252
253 Value one = createFloatConst(op->getLoc(), opType, 1.0, rewriter, operand);
254 Value add = arith::AddFOp::create(b, operand, one);
255 Value neg = arith::NegFOp::create(b, operand);
256 Value sub = arith::AddFOp::create(b, neg, one);
257 Value div = arith::DivFOp::create(b, add, sub);
258 Value log = math::LogOp::create(b, div);
259 Value half = createFloatConst(op->getLoc(), opType, 0.5, rewriter, operand);
260 Value res = arith::MulFOp::create(b, log, half);
261 rewriter.replaceOp(op, res);
262 return success();
263}
264
265static LogicalResult convertFmaFOp(math::FmaOp op, PatternRewriter &rewriter) {
266 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
267 Value operandA = op.getOperand(0);
268 Value operandB = op.getOperand(1);
269 Value operandC = op.getOperand(2);
270 Type type = op.getType();
271 Value mult = arith::MulFOp::create(b, type, operandA, operandB);
272 Value add = arith::AddFOp::create(b, type, mult, operandC);
273 rewriter.replaceOp(op, add);
274 return success();
275}
276
277// Converts a ceilf() function to the following:
278// ceilf(float x) ->
279// y = (float)(int) x
280// if (x > y) then incr = 1 else incr = 0
281// y = y + incr <= replace this op with the ceilf op.
282static LogicalResult convertCeilOp(math::CeilOp op, PatternRewriter &rewriter) {
283 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
284 Value operand = op.getOperand();
285 Type opType = operand.getType();
286
287 if (isUnrankedShaped(opType))
288 return failure();
289
290 Type operandETy = getElementTypeOrSelf(opType);
291 FloatType floatTy = llvm::dyn_cast<FloatType>(operandETy);
292 const llvm::fltSemantics &semantics = floatTy.getFloatSemantics();
293
294 unsigned bitWidth = floatTy.getWidth();
295 unsigned mantissaWidth = floatTy.getFPMantissaWidth() - 1;
296 const int bias = (&semantics == &APFloat::Float8E8M0FNU())
297 ? -semantics.minExponent
298 : -(semantics.minExponent - 1);
299 bool hasNegativeZeroNaNEncoding =
300 (semantics.nanEncoding == llvm::fltNanEncoding::NegativeZero);
301
302 Type iTy = rewriter.getIntegerType(bitWidth);
303 if (auto shapedTy = dyn_cast<ShapedType>(opType))
304 iTy = shapedTy.clone(iTy);
305
306 // For IEEE-like floating-point formats with an unbiased exponent ≥
307 // `mantissaWidth` falls into one of these categories:
308 // - a large finite value (|x| ≥ 2^mantissaWidth), where all representable
309 // numbers are already integral, or
310 // - a special value (NaN or ±Inf), which also satisfies this exponent
311 // condition.
312 // For all such cases, `ceilf(x)` is defined to return `x` directly.
313 Value operandBitcast = arith::BitcastOp::create(b, iTy, operand);
314 Value cMask = createIntConst(
315 op->getLoc(), iTy, static_cast<int64_t>((1ull << (bitWidth - 1)) - 1), b,
316 operand);
317 Value unsignedBits = arith::AndIOp::create(b, operandBitcast, cMask);
318 Value cThreshold = createIntConst(
319 op->getLoc(), iTy,
320 static_cast<int64_t>((uint64_t(bias + mantissaWidth)) << mantissaWidth),
321 b, operand);
322 Value isLargeExp = arith::CmpIOp::create(b, arith::CmpIPredicate::uge,
323 unsignedBits, cThreshold);
324 Value isSpecialValOrLargeVal = isLargeExp;
325
326 // In FNUZ-suffixed floating point, NaN is represented by a sign bit of 1 and
327 // all 0s in the exponent and mantissa, therefore requires an explicit check.
328 if (hasNegativeZeroNaNEncoding) {
329 Value cNegZeroBits = createIntConst(
330 op->getLoc(), iTy, static_cast<int64_t>(1ull << (bitWidth - 1)), b,
331 operand);
332 Value isNegZeroEncoding = arith::CmpIOp::create(
333 b, arith::CmpIPredicate::eq, operandBitcast, cNegZeroBits);
334 isSpecialValOrLargeVal =
335 arith::OrIOp::create(b, isLargeExp, isNegZeroEncoding);
336 }
337
338 Value fpFixedConvert = createTruncatedFPValue(operand, b);
339
340 // Creating constants for later use.
341 Value zero = createFloatConst(op->getLoc(), opType, 0.00, rewriter, operand);
342 Value one = createFloatConst(op->getLoc(), opType, 1.00, rewriter, operand);
343
344 Value gtCheck = arith::CmpFOp::create(b, arith::CmpFPredicate::OGT, operand,
345 fpFixedConvert);
346 Value incrValue =
347 arith::SelectOp::create(b, op->getLoc(), gtCheck, one, zero);
348
349 Value add = arith::AddFOp::create(b, opType, fpFixedConvert, incrValue);
350 Value ret = arith::SelectOp::create(b, isSpecialValOrLargeVal, operand, add);
351 rewriter.replaceOp(op, ret);
352 return success();
353}
354
355// Convert `math.fpowi` to a series of `arith.mulf` operations.
356// If the power is negative, we divide one by the result.
357// If both the base and power are zero, the result is 1.
358// In the case of non constant power, we convert the operation to `math.powf`.
359static LogicalResult convertFPowIOp(math::FPowIOp op,
360 PatternRewriter &rewriter) {
361 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
362 Value base = op.getOperand(0);
363 Value power = op.getOperand(1);
364 Type baseType = base.getType();
365
366 auto convertFPowItoPowf = [&]() -> LogicalResult {
367 Value castPowerToFp =
368 arith::SIToFPOp::create(rewriter, op.getLoc(), baseType, power);
369 Value res = math::PowFOp::create(rewriter, op.getLoc(), baseType, base,
370 castPowerToFp);
371 rewriter.replaceOp(op, res);
372 return success();
373 };
374
375 Attribute cstAttr;
376 if (!matchPattern(power, m_Constant(&cstAttr)))
377 return convertFPowItoPowf();
378
379 APInt value;
380 if (!matchPattern(cstAttr, m_ConstantInt(&value)))
381 return convertFPowItoPowf();
382
383 int64_t powerInt = value.getSExtValue();
384 bool isNegative = powerInt < 0;
385 int64_t absPower = std::abs(powerInt);
386 Value one = createFloatConst(op->getLoc(), baseType, 1.00, rewriter);
387 Value res = createFloatConst(op->getLoc(), baseType, 1.00, rewriter);
388
389 while (absPower > 0) {
390 if (absPower & 1)
391 res = arith::MulFOp::create(b, baseType, base, res);
392 absPower >>= 1;
393 base = arith::MulFOp::create(b, baseType, base, base);
394 }
395
396 // Make sure not to introduce UB in case of negative power.
397 if (isNegative) {
398 auto &sem = dyn_cast<mlir::FloatType>(getElementTypeOrSelf(baseType))
399 .getFloatSemantics();
400 Value zero =
401 createFloatConst(op->getLoc(), baseType,
402 APFloat::getZero(sem, /*Negative=*/false), rewriter);
403 Value negZero =
404 createFloatConst(op->getLoc(), baseType,
405 APFloat::getZero(sem, /*Negative=*/true), rewriter);
406 Value posInfinity =
407 createFloatConst(op->getLoc(), baseType,
408 APFloat::getInf(sem, /*Negative=*/false), rewriter);
409 Value negInfinity =
410 createFloatConst(op->getLoc(), baseType,
411 APFloat::getInf(sem, /*Negative=*/true), rewriter);
412 Value zeroEqCheck =
413 arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, res, zero);
414 Value negZeroEqCheck =
415 arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, res, negZero);
416 res = arith::DivFOp::create(b, baseType, one, res);
417 res =
418 arith::SelectOp::create(b, op->getLoc(), zeroEqCheck, posInfinity, res);
419 res = arith::SelectOp::create(b, op->getLoc(), negZeroEqCheck, negInfinity,
420 res);
421 }
422
423 rewriter.replaceOp(op, res);
424 return success();
425}
426
427// Converts Powf(float a, float b) (meaning a^b) to exp^(b * ln(a))
428// Some special cases where b is constant are handled separately:
429// when b == 0, or |b| == 0.5, 1.0, or 2.0.
430static LogicalResult convertPowfOp(math::PowFOp op, PatternRewriter &rewriter) {
431 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
432 Value operandA = op.getOperand(0);
433 Value operandB = op.getOperand(1);
434 auto typeA = operandA.getType();
435 auto typeB = operandB.getType();
436
437 auto &sem =
438 cast<mlir::FloatType>(getElementTypeOrSelf(typeB)).getFloatSemantics();
439 APFloat valueB(sem);
440 auto mulf = [&](Value x, Value y) -> Value {
441 return arith::MulFOp::create(b, x, y);
442 };
443 if (matchPattern(operandB, m_ConstantFloat(&valueB))) {
444 if (valueB.isZero()) {
445 // a^0 -> 1
446 Value one = createFloatConst(op->getLoc(), typeA, 1.0, rewriter);
447 rewriter.replaceOp(op, one);
448 return success();
449 }
450 if (valueB.isExactlyValue(1.0)) {
451 // a^1 -> a
452 rewriter.replaceOp(op, operandA);
453 return success();
454 }
455 if (valueB.isExactlyValue(-1.0)) {
456 // a^(-1) -> 1 / a
457 Value one = createFloatConst(op->getLoc(), typeA, 1.0, rewriter);
458 Value div = arith::DivFOp::create(b, one, operandA);
459 rewriter.replaceOp(op, div);
460 return success();
461 }
462 if (valueB.isExactlyValue(0.5)) {
463 // a^(1/2) -> sqrt(a)
464 Value sqrt = math::SqrtOp::create(b, operandA);
465 rewriter.replaceOp(op, sqrt);
466 return success();
467 }
468 if (valueB.isExactlyValue(-0.5)) {
469 // a^(-1/2) -> 1 / sqrt(a)
470 Value rsqrt = math::RsqrtOp::create(b, operandA);
471 rewriter.replaceOp(op, rsqrt);
472 return success();
473 }
474 if (valueB.isExactlyValue(2.0)) {
475 // a^2 -> a * a
476 rewriter.replaceOp(op, mulf(operandA, operandA));
477 return success();
478 }
479 if (valueB.isExactlyValue(-2.0)) {
480 // a^(-2) -> 1 / (a * a)
481 Value one =
482 createFloatConst(op->getLoc(), operandA.getType(), 1.0, rewriter);
483 Value div = arith::DivFOp::create(b, one, mulf(operandA, operandA));
484 rewriter.replaceOp(op, div);
485 return success();
486 }
487 if (valueB.isExactlyValue(3.0)) {
488 rewriter.replaceOp(op, mulf(mulf(operandA, operandA), operandA));
489 return success();
490 }
491 }
492
493 Value logA = math::LogOp::create(b, operandA);
494 Value mult = arith::MulFOp::create(b, operandB, logA);
495 Value expResult = math::ExpOp::create(b, mult);
496 rewriter.replaceOp(op, expResult);
497 return success();
498}
499
500// exp2f(float x) -> exp(x * ln(2))
501// Proof: Let's say 2^x = y
502// ln(2^x) = ln(y)
503// x * ln(2) = ln(y) => e ^(x*ln(2)) = y
504static LogicalResult convertExp2fOp(math::Exp2Op op,
505 PatternRewriter &rewriter) {
506 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
507 Value operand = op.getOperand();
508 Type opType = operand.getType();
509
510 if (isUnrankedShaped(opType))
511 return failure();
512
513 Value ln2 =
514 createFloatConst(op->getLoc(), opType, llvm::numbers::ln2, b, operand);
515 Value mult = arith::MulFOp::create(b, opType, operand, ln2);
516 Value exp = math::ExpOp::create(b, op->getLoc(), mult);
517 rewriter.replaceOp(op, exp);
518 return success();
519}
520
521static LogicalResult convertRoundOp(math::RoundOp op,
522 PatternRewriter &rewriter) {
523 Location loc = op.getLoc();
524 ImplicitLocOpBuilder b(loc, rewriter);
525 Value operand = op.getOperand();
526 Type opType = operand.getType();
527 Type opEType = getElementTypeOrSelf(opType);
528
529 if (isUnrankedShaped(opType))
530 return failure();
531
532 if (!opEType.isF32()) {
533 return rewriter.notifyMatchFailure(op, "not a round of f32.");
534 }
535
536 Type i32Ty = b.getI32Type();
537 if (auto shapedTy = dyn_cast<ShapedType>(opType))
538 i32Ty = shapedTy.clone(i32Ty);
539
540 Value half = createFloatConst(loc, opType, 0.5, b, operand);
541 Value c23 = createIntConst(loc, i32Ty, 23, b, operand);
542 Value c127 = createIntConst(loc, i32Ty, 127, b, operand);
543 Value expMask = createIntConst(loc, i32Ty, (1 << 8) - 1, b, operand);
544
545 Value incrValue = math::CopySignOp::create(b, half, operand);
546 Value add = arith::AddFOp::create(b, opType, operand, incrValue);
547 Value fpFixedConvert = createTruncatedFPValue(add, b);
548
549 // There are three cases where adding 0.5 to the value and truncating by
550 // converting to an i64 does not result in the correct behavior:
551 //
552 // 1. Special values: +-inf and +-nan
553 // Casting these special values to i64 has undefined behavior. To identify
554 // these values, we use the fact that these values are the only float
555 // values with the maximum possible biased exponent.
556 //
557 // 2. Large values: 2^23 <= |x| <= INT_64_MAX
558 // Adding 0.5 to a float larger than or equal to 2^23 results in precision
559 // errors that sometimes round the value up and sometimes round the value
560 // down. For example:
561 // 8388608.0 + 0.5 = 8388608.0
562 // 8388609.0 + 0.5 = 8388610.0
563 //
564 // 3. Very large values: |x| > INT_64_MAX
565 // Casting to i64 a value greater than the max i64 value will overflow the
566 // i64 leading to wrong outputs.
567 //
568 // All three cases satisfy the property `biasedExp >= 23`.
569 Value operandBitcast = arith::BitcastOp::create(b, i32Ty, operand);
570 Value operandExp = arith::AndIOp::create(
571 b, arith::ShRUIOp::create(b, operandBitcast, c23), expMask);
572 Value operandBiasedExp = arith::SubIOp::create(b, operandExp, c127);
573 Value isSpecialValOrLargeVal = arith::CmpIOp::create(
574 b, arith::CmpIPredicate::sge, operandBiasedExp, c23);
575
576 Value result = arith::SelectOp::create(b, isSpecialValOrLargeVal, operand,
577 fpFixedConvert);
578 rewriter.replaceOp(op, result);
579 return success();
580}
581
582// Converts math.ctlz to scf and arith operations. This is done
583// by performing a binary search on the bits.
584static LogicalResult convertCtlzOp(math::CountLeadingZerosOp op,
585 PatternRewriter &rewriter) {
586 auto operand = op.getOperand();
587 auto operandTy = operand.getType();
588 auto eTy = getElementTypeOrSelf(operandTy);
589 Location loc = op.getLoc();
590
591 if (isUnrankedShaped(operandTy))
592 return failure();
593
594 // Only expand for integer or float element types (index has no fixed bitwidth).
595 if (!eTy.isIntOrFloat()) {
596 return rewriter.notifyMatchFailure(op, "ctlz expansion only supports int or float types");
597 }
598
599 int32_t bitwidth = eTy.getIntOrFloatBitWidth();
600 if (bitwidth > 64)
601 return failure();
602
603 uint64_t allbits = -1;
604 if (bitwidth < 64) {
605 allbits = allbits >> (64 - bitwidth);
606 }
607
608 Value x = operand;
609 Value count = createIntConst(loc, operandTy, 0, rewriter, operand);
610 for (int32_t bw = bitwidth; bw > 1; bw = bw / 2) {
611 auto half = bw / 2;
612 auto bits = createIntConst(loc, operandTy, half, rewriter, operand);
613 auto mask =
614 createIntConst(loc, operandTy, allbits >> half, rewriter, operand);
615
616 Value pred = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::ule,
617 x, mask);
618 Value add = arith::AddIOp::create(rewriter, loc, count, bits);
619 Value shift = arith::ShLIOp::create(rewriter, loc, x, bits);
620
621 x = arith::SelectOp::create(rewriter, loc, pred, shift, x);
622 count = arith::SelectOp::create(rewriter, loc, pred, add, count);
623 }
624
625 Value zero = createIntConst(loc, operandTy, 0, rewriter, operand);
626 Value pred = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
627 operand, zero);
628
629 Value bwval = createIntConst(loc, operandTy, bitwidth, rewriter, operand);
630 Value sel = arith::SelectOp::create(rewriter, loc, pred, bwval, count);
631 rewriter.replaceOp(op, sel);
632 return success();
633}
634
635// Convert `math.roundeven` into `math.round` + arith ops
636static LogicalResult convertRoundEvenOp(math::RoundEvenOp op,
637 PatternRewriter &rewriter) {
638 Location loc = op.getLoc();
639 ImplicitLocOpBuilder b(loc, rewriter);
640 auto operand = op.getOperand();
641 Type operandTy = operand.getType();
642 Type resultTy = op.getType();
643 Type operandETy = getElementTypeOrSelf(operandTy);
644 Type resultETy = getElementTypeOrSelf(resultTy);
645
646 if (isUnrankedShaped(operandTy))
647 return failure();
648
649 if (!isa<FloatType>(operandETy) || !isa<FloatType>(resultETy)) {
650 return rewriter.notifyMatchFailure(op, "not a roundeven of f16 or f32.");
651 }
652
653 Type fTy = operandTy;
654 Type iTy = rewriter.getIntegerType(operandETy.getIntOrFloatBitWidth());
655 if (auto shapedTy = dyn_cast<ShapedType>(fTy)) {
656 iTy = shapedTy.clone(iTy);
657 }
658
659 unsigned bitWidth = operandETy.getIntOrFloatBitWidth();
660 // The width returned by getFPMantissaWidth includes the integer bit.
661 unsigned mantissaWidth =
662 llvm::cast<FloatType>(operandETy).getFPMantissaWidth() - 1;
663 unsigned exponentWidth = bitWidth - mantissaWidth - 1;
664
665 // The names of the variables correspond to f32.
666 // f64: 1 bit sign | 11 bits exponent | 52 bits mantissa.
667 // f32: 1 bit sign | 8 bits exponent | 23 bits mantissa.
668 // f16: 1 bit sign | 5 bits exponent | 10 bits mantissa.
669 Value c1Float = createFloatConst(loc, fTy, 1.0, b, operand);
670 Value c0 = createIntConst(loc, iTy, 0, b, operand);
671 Value c1 = createIntConst(loc, iTy, 1, b, operand);
672 Value cNeg1 = createIntConst(loc, iTy, -1, b, operand);
673 Value c23 = createIntConst(loc, iTy, mantissaWidth, b, operand);
674 Value c31 = createIntConst(loc, iTy, bitWidth - 1, b, operand);
675 Value c127 =
676 createIntConst(loc, iTy, (1ull << (exponentWidth - 1)) - 1, b, operand);
677 Value c2To22 =
678 createIntConst(loc, iTy, 1ull << (mantissaWidth - 1), b, operand);
679 Value c23Mask =
680 createIntConst(loc, iTy, (1ull << mantissaWidth) - 1, b, operand);
681 Value expMask =
682 createIntConst(loc, iTy, (1ull << exponentWidth) - 1, b, operand);
683
684 Value operandBitcast = arith::BitcastOp::create(b, iTy, operand);
685 Value round = math::RoundOp::create(b, operand);
686 Value roundBitcast = arith::BitcastOp::create(b, iTy, round);
687
688 // Get biased exponents for operand and round(operand)
689 Value operandExp = arith::AndIOp::create(
690 b, arith::ShRUIOp::create(b, operandBitcast, c23), expMask);
691 Value operandBiasedExp = arith::SubIOp::create(b, operandExp, c127);
692 Value roundExp = arith::AndIOp::create(
693 b, arith::ShRUIOp::create(b, roundBitcast, c23), expMask);
694 Value roundBiasedExp = arith::SubIOp::create(b, roundExp, c127);
695
696 auto safeShiftRight = [&](Value x, Value shift) -> Value {
697 // Clamp shift to valid range [0, bitwidth - 1] to avoid undefined behavior
698 Value clampedShift = arith::MaxSIOp::create(b, shift, c0);
699 clampedShift = arith::MinSIOp::create(b, clampedShift, c31);
700 return arith::ShRUIOp::create(b, x, clampedShift);
701 };
702
703 auto maskMantissa = [&](Value mantissa,
704 Value mantissaMaskRightShift) -> Value {
705 Value shiftedMantissaMask = safeShiftRight(c23Mask, mantissaMaskRightShift);
706 return arith::AndIOp::create(b, mantissa, shiftedMantissaMask);
707 };
708
709 // A whole number `x`, such that `|x| != 1`, is even if the mantissa, ignoring
710 // the leftmost `clamp(biasedExp - 1, 0, 23)` bits, is zero. Large numbers
711 // with `biasedExp > 23` (numbers where there is not enough precision to store
712 // decimals) are always even, and they satisfy the even condition trivially
713 // since the mantissa without all its bits is zero. The even condition
714 // is also true for +-0, since they have `biasedExp = -127` and the entire
715 // mantissa is zero. The case of +-1 has to be handled separately. Here
716 // we identify these values by noting that +-1 are the only whole numbers with
717 // `biasedExp == 0`.
718 //
719 // The special values +-inf and +-nan also satisfy the same property that
720 // whole non-unit even numbers satisfy. In particular, the special values have
721 // `biasedExp > 23`, so they get treated as large numbers with no room for
722 // decimals, which are always even.
723 Value roundBiasedExpEq0 =
724 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, roundBiasedExp, c0);
725 Value roundBiasedExpMinus1 = arith::SubIOp::create(b, roundBiasedExp, c1);
726 Value roundMaskedMantissa = maskMantissa(roundBitcast, roundBiasedExpMinus1);
727 Value roundIsNotEvenOrSpecialVal = arith::CmpIOp::create(
728 b, arith::CmpIPredicate::ne, roundMaskedMantissa, c0);
729 roundIsNotEvenOrSpecialVal =
730 arith::OrIOp::create(b, roundIsNotEvenOrSpecialVal, roundBiasedExpEq0);
731
732 // A value `x` with `0 <= biasedExp < 23`, is halfway between two consecutive
733 // integers if the bit at index `biasedExp` starting from the left in the
734 // mantissa is 1 and all the bits to the right are zero. Values with
735 // `biasedExp >= 23` don't have decimals, so they are never halfway. The
736 // values +-0.5 are the only halfway values that have `biasedExp == -1 < 0`,
737 // so these are handled separately. In particular, if `biasedExp == -1`, the
738 // value is halfway if the entire mantissa is zero.
739 Value operandBiasedExpEqNeg1 = arith::CmpIOp::create(
740 b, arith::CmpIPredicate::eq, operandBiasedExp, cNeg1);
741 Value expectedOperandMaskedMantissa = arith::SelectOp::create(
742 b, operandBiasedExpEqNeg1, c0, safeShiftRight(c2To22, operandBiasedExp));
743 Value operandMaskedMantissa = maskMantissa(operandBitcast, operandBiasedExp);
744 Value operandIsHalfway =
745 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, operandMaskedMantissa,
746 expectedOperandMaskedMantissa);
747 // Ensure `biasedExp` is in the valid range for half values.
748 Value operandBiasedExpGeNeg1 = arith::CmpIOp::create(
749 b, arith::CmpIPredicate::sge, operandBiasedExp, cNeg1);
750 Value operandBiasedExpLt23 = arith::CmpIOp::create(
751 b, arith::CmpIPredicate::slt, operandBiasedExp, c23);
752 operandIsHalfway =
753 arith::AndIOp::create(b, operandIsHalfway, operandBiasedExpLt23);
754 operandIsHalfway =
755 arith::AndIOp::create(b, operandIsHalfway, operandBiasedExpGeNeg1);
756
757 // Adjust rounded operand with `round(operand) - sign(operand)` to correct the
758 // case where `round` rounded in the opposite direction of `roundeven`.
759 Value sign = math::CopySignOp::create(b, c1Float, operand);
760 Value roundShifted = arith::SubFOp::create(b, round, sign);
761 // If the rounded value is even or a special value, we default to the behavior
762 // of `math.round`.
763 Value needsShift =
764 arith::AndIOp::create(b, roundIsNotEvenOrSpecialVal, operandIsHalfway);
765 Value result = arith::SelectOp::create(b, needsShift, roundShifted, round);
766 // The `x - sign` adjustment does not preserve the sign when we are adjusting
767 // the value -1 to -0. So here the sign is copied again to ensure that -0.5 is
768 // rounded to -0.0.
769 result = math::CopySignOp::create(b, result, operand);
770 rewriter.replaceOp(op, result);
771 return success();
772}
773
774// Convert `math.rsqrt` into `arith.divf` + `math.sqrt`
775static LogicalResult convertRsqrtOp(math::RsqrtOp op,
776 PatternRewriter &rewriter) {
777 auto operand = op.getOperand();
778 auto operandTy = operand.getType();
779
780 if (isUnrankedShaped(operandTy))
781 return failure();
782
783 auto eTy = getElementTypeOrSelf(operandTy);
784 if (!isa<FloatType>(eTy))
785 return failure();
786
787 Location loc = op->getLoc();
788 auto constOneFloat = createFloatConst(loc, operandTy, 1.0, rewriter, operand);
789 auto sqrtOp = math::SqrtOp::create(rewriter, loc, operand);
790 rewriter.replaceOpWithNewOp<arith::DivFOp>(op, constOneFloat, sqrtOp);
791 return success();
792}
793
794// Convert `math.clampf` into `arith.minimumf` + `arith.maximumf`
795static LogicalResult convertClampfOp(math::ClampFOp op,
796 PatternRewriter &rewriter) {
797 auto minOp = arith::MinimumFOp::create(rewriter, op.getLoc(), op.getValue(),
798 op.getMax(), op.getFastmath());
799 rewriter.replaceOpWithNewOp<arith::MaximumFOp>(op, minOp, op.getMin(),
800 op.getFastmath());
801 return success();
802}
803
805 ArrayRef<StringRef> opMnemonics) {
806 auto filter = [&](StringRef name) {
807 // This should be a static assert and `consume_front` take a twine, but none
808 // is currently possible. TODO: augment `StringRef::consume_front` and make
809 // `getDialectNamespace` use `std::string_view`.
810 assert("math" == MathDialect::getDialectNamespace());
811 name.consume_front("math.");
812 return opMnemonics.empty() || (llvm::count(opMnemonics, name) > 0);
813 };
814 if (filter(CountLeadingZerosOp::getOperationName()))
815 patterns.add(convertCtlzOp);
816 if (filter(SinhOp::getOperationName()))
817 patterns.add(convertSinhOp);
818 if (filter(CoshOp::getOperationName()))
819 patterns.add(convertCoshOp);
820 if (filter(TanOp::getOperationName()))
821 patterns.add(convertTanOp);
822 if (filter(TanhOp::getOperationName()))
823 patterns.add(convertTanhOp);
824 if (filter(AsinhOp::getOperationName()))
825 patterns.add(convertAsinhOp);
826 if (filter(AcoshOp::getOperationName()))
827 patterns.add(convertAcoshOp);
828 if (filter(AtanhOp::getOperationName()))
829 patterns.add(convertAtanhOp);
830 if (filter(FmaOp::getOperationName()))
831 patterns.add(convertFmaFOp);
832 if (filter(CeilOp::getOperationName()))
833 patterns.add(convertCeilOp);
834 if (filter(Exp2Op::getOperationName()))
835 patterns.add(convertExp2fOp);
836 if (filter(PowFOp::getOperationName()))
837 patterns.add(convertPowfOp);
838 if (filter(FPowIOp::getOperationName()))
839 patterns.add(convertFPowIOp);
840 if (filter(RoundOp::getOperationName()))
841 patterns.add(convertRoundOp);
842 if (filter(RoundEvenOp::getOperationName()))
843 patterns.add(convertRoundEvenOp);
844 if (filter(RsqrtOp::getOperationName()))
845 patterns.add(convertRsqrtOp);
846 if (filter(ClampFOp::getOperationName()))
847 patterns.add(convertClampfOp);
848}
849
850//===----------------------------------------------------------------------===//
851// MathExpandOpsPass pass
852//===----------------------------------------------------------------------===//
853namespace {
854struct MathExpandOpsPass final
855 : math::impl::MathExpandOpsPassBase<MathExpandOpsPass> {
856 using MathExpandOpsPassBase::MathExpandOpsPassBase;
857
858 void runOnOperation() override {
859 RewritePatternSet patterns(&getContext());
860 SmallVector<StringRef> mnemonics =
861 llvm::to_vector_of<StringRef>(opMnemonics);
862 math::populateExpansionPatterns(patterns, mnemonics);
863 if (failed(applyPatternsGreedily(getOperation(), std::move(patterns))))
864 return signalPassFailure();
865 }
866};
867} // namespace
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static LogicalResult convertRsqrtOp(math::RsqrtOp op, PatternRewriter &rewriter)
static Value createTruncatedFPValue(Value operand, ImplicitLocOpBuilder &b)
Definition ExpandOps.cpp:97
static LogicalResult convertFPowIOp(math::FPowIOp op, PatternRewriter &rewriter)
static Value createFloatConst(Location loc, Type type, APFloat value, OpBuilder &b, Value dynamicShapeRef=Value())
Create a float constant.
Definition ExpandOps.cpp:39
static Value createIntConst(Location loc, Type type, int64_t value, OpBuilder &b, Value dynamicShapeRef=Value())
Create an integer constant.
Definition ExpandOps.cpp:74
static LogicalResult convertPowfOp(math::PowFOp op, PatternRewriter &rewriter)
static LogicalResult convertClampfOp(math::ClampFOp op, PatternRewriter &rewriter)
static LogicalResult convertRoundOp(math::RoundOp op, PatternRewriter &rewriter)
static bool isUnrankedShaped(Type type)
Returns true if the type is an unranked shaped type (e.g., tensor<*xf32>).
Definition ExpandOps.cpp:31
static LogicalResult convertTanOp(math::TanOp op, PatternRewriter &rewriter)
static LogicalResult convertCtlzOp(math::CountLeadingZerosOp op, PatternRewriter &rewriter)
static LogicalResult convertFmaFOp(math::FmaOp op, PatternRewriter &rewriter)
static LogicalResult convertAtanhOp(math::AtanhOp op, PatternRewriter &rewriter)
static LogicalResult convertCeilOp(math::CeilOp op, PatternRewriter &rewriter)
static LogicalResult convertRoundEvenOp(math::RoundEvenOp op, PatternRewriter &rewriter)
static LogicalResult convertCoshOp(math::CoshOp op, PatternRewriter &rewriter)
static LogicalResult convertSinhOp(math::SinhOp op, PatternRewriter &rewriter)
static LogicalResult convertAsinhOp(math::AsinhOp op, PatternRewriter &rewriter)
static LogicalResult convertTanhOp(math::TanhOp op, PatternRewriter &rewriter)
Expands tanh op into 1-exp^{-2x} / 1+exp^{-2x} To avoid overflow we exploit the reflection symmetry t...
static LogicalResult convertAcoshOp(math::AcoshOp op, PatternRewriter &rewriter)
static LogicalResult convertExp2fOp(math::Exp2Op op, PatternRewriter &rewriter)
#define add(a, b)
#define div(a, b)
Attributes are known-constant values of operations.
Definition Attributes.h:25
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:71
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
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:209
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
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...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isF32() const
Definition Types.cpp:40
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 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
void populateExpansionPatterns(RewritePatternSet &patterns, ArrayRef< StringRef > opMnemonics={})
Adds patterns to expand math operations into other more fundamental operations.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
detail::constant_float_value_binder m_ConstantFloat(FloatAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor float (splat) and writes the float value to bind_va...
Definition Matchers.h:520