1 //===- PolynomialApproximation.cpp - Approximate 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 math operations to fast approximations 10 // that do not rely on any of the library functions. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include <climits> 15 #include <cstddef> 16 17 #include "mlir/Dialect/Arithmetic/IR/Arithmetic.h" 18 #include "mlir/Dialect/Math/IR/Math.h" 19 #include "mlir/Dialect/Math/Transforms/Approximation.h" 20 #include "mlir/Dialect/Math/Transforms/Passes.h" 21 #include "mlir/Dialect/Utils/IndexingUtils.h" 22 #include "mlir/Dialect/Vector/IR/VectorOps.h" 23 #include "mlir/Dialect/Vector/Utils/VectorUtils.h" 24 #include "mlir/Dialect/X86Vector/X86VectorDialect.h" 25 #include "mlir/IR/Builders.h" 26 #include "mlir/IR/ImplicitLocOpBuilder.h" 27 #include "mlir/IR/TypeUtilities.h" 28 #include "mlir/Transforms/DialectConversion.h" 29 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 30 #include "llvm/ADT/ArrayRef.h" 31 32 using namespace mlir; 33 using namespace mlir::math; 34 using namespace mlir::vector; 35 36 // Returns vector shape if the type is a vector. Returns an empty shape if it is 37 // not a vector. 38 static ArrayRef<int64_t> vectorShape(Type type) { 39 auto vectorType = type.dyn_cast<VectorType>(); 40 return vectorType ? vectorType.getShape() : ArrayRef<int64_t>(); 41 } 42 43 static ArrayRef<int64_t> vectorShape(Value value) { 44 return vectorShape(value.getType()); 45 } 46 47 //----------------------------------------------------------------------------// 48 // Broadcast scalar types and values into vector types and values. 49 //----------------------------------------------------------------------------// 50 51 // Broadcasts scalar type into vector type (iff shape is non-scalar). 52 static Type broadcast(Type type, ArrayRef<int64_t> shape) { 53 assert(!type.isa<VectorType>() && "must be scalar type"); 54 return !shape.empty() ? VectorType::get(shape, type) : type; 55 } 56 57 // Broadcasts scalar value into vector (iff shape is non-scalar). 58 static Value broadcast(ImplicitLocOpBuilder &builder, Value value, 59 ArrayRef<int64_t> shape) { 60 assert(!value.getType().isa<VectorType>() && "must be scalar value"); 61 auto type = broadcast(value.getType(), shape); 62 return !shape.empty() ? builder.create<BroadcastOp>(type, value) : value; 63 } 64 65 //----------------------------------------------------------------------------// 66 // Helper function to handle n-D vectors with 1-D operations. 67 //----------------------------------------------------------------------------// 68 69 // Expands and unrolls n-D vector operands into multiple fixed size 1-D vectors 70 // and calls the compute function with 1-D vector operands. Stitches back all 71 // results into the original n-D vector result. 72 // 73 // Examples: vectorWidth = 8 74 // - vector<4x8xf32> unrolled 4 times 75 // - vector<16xf32> expanded to vector<2x8xf32> and unrolled 2 times 76 // - vector<4x16xf32> expanded to vector<4x2x8xf32> and unrolled 4*2 times 77 // 78 // Some math approximations rely on ISA-specific operations that only accept 79 // fixed size 1-D vectors (e.g. AVX expects vectors of width 8). 80 // 81 // It is the caller's responsibility to verify that the inner dimension is 82 // divisible by the vectorWidth, and that all operands have the same vector 83 // shape. 84 static Value 85 handleMultidimensionalVectors(ImplicitLocOpBuilder &builder, 86 ValueRange operands, int64_t vectorWidth, 87 llvm::function_ref<Value(ValueRange)> compute) { 88 assert(!operands.empty() && "operands must be not empty"); 89 assert(vectorWidth > 0 && "vector width must be larger than 0"); 90 91 VectorType inputType = operands[0].getType().cast<VectorType>(); 92 ArrayRef<int64_t> inputShape = inputType.getShape(); 93 94 // If input shape matches target vector width, we can just call the 95 // user-provided compute function with the operands. 96 if (inputShape == llvm::makeArrayRef(vectorWidth)) 97 return compute(operands); 98 99 // Check if the inner dimension has to be expanded, or we can directly iterate 100 // over the outer dimensions of the vector. 101 int64_t innerDim = inputShape.back(); 102 int64_t expansionDim = innerDim / vectorWidth; 103 assert((innerDim % vectorWidth == 0) && "invalid inner dimension size"); 104 105 // Maybe expand operands to the higher rank vector shape that we'll use to 106 // iterate over and extract one dimensional vectors. 107 SmallVector<int64_t> expandedShape(inputShape.begin(), inputShape.end()); 108 SmallVector<Value> expandedOperands(operands); 109 110 if (expansionDim > 1) { 111 // Expand shape from [..., innerDim] to [..., expansionDim, vectorWidth]. 112 expandedShape.insert(expandedShape.end() - 1, expansionDim); 113 expandedShape.back() = vectorWidth; 114 115 for (unsigned i = 0; i < operands.size(); ++i) { 116 auto operand = operands[i]; 117 auto eltType = operand.getType().cast<VectorType>().getElementType(); 118 auto expandedType = VectorType::get(expandedShape, eltType); 119 expandedOperands[i] = 120 builder.create<vector::ShapeCastOp>(expandedType, operand); 121 } 122 } 123 124 // Iterate over all outer dimensions of the compute shape vector type. 125 auto iterationDims = ArrayRef<int64_t>(expandedShape).drop_back(); 126 int64_t maxLinearIndex = computeMaxLinearIndex(iterationDims); 127 128 SmallVector<int64_t> ones(iterationDims.size(), 1); 129 auto strides = computeStrides(iterationDims, ones); 130 131 // Compute results for each one dimensional vector. 132 SmallVector<Value> results(maxLinearIndex); 133 134 for (int64_t i = 0; i < maxLinearIndex; ++i) { 135 auto offsets = delinearize(strides, i); 136 137 SmallVector<Value> extracted(expandedOperands.size()); 138 for (const auto &tuple : llvm::enumerate(expandedOperands)) 139 extracted[tuple.index()] = 140 builder.create<vector::ExtractOp>(tuple.value(), offsets); 141 142 results[i] = compute(extracted); 143 } 144 145 // Stitch results together into one large vector. 146 Type resultEltType = results[0].getType().cast<VectorType>().getElementType(); 147 Type resultExpandedType = VectorType::get(expandedShape, resultEltType); 148 Value result = builder.create<arith::ConstantOp>( 149 resultExpandedType, builder.getZeroAttr(resultExpandedType)); 150 151 for (int64_t i = 0; i < maxLinearIndex; ++i) 152 result = builder.create<vector::InsertOp>(results[i], result, 153 delinearize(strides, i)); 154 155 // Reshape back to the original vector shape. 156 return builder.create<vector::ShapeCastOp>( 157 VectorType::get(inputShape, resultEltType), result); 158 } 159 160 //----------------------------------------------------------------------------// 161 // Helper functions to create constants. 162 //----------------------------------------------------------------------------// 163 164 static Value f32Cst(ImplicitLocOpBuilder &builder, float value) { 165 return builder.create<arith::ConstantOp>(builder.getF32FloatAttr(value)); 166 } 167 168 static Value i32Cst(ImplicitLocOpBuilder &builder, int32_t value) { 169 return builder.create<arith::ConstantOp>(builder.getI32IntegerAttr(value)); 170 } 171 172 static Value f32FromBits(ImplicitLocOpBuilder &builder, uint32_t bits) { 173 Value i32Value = i32Cst(builder, static_cast<int32_t>(bits)); 174 return builder.create<arith::BitcastOp>(builder.getF32Type(), i32Value); 175 } 176 177 //----------------------------------------------------------------------------// 178 // Helper functions to build math functions approximations. 179 //----------------------------------------------------------------------------// 180 181 static Value min(ImplicitLocOpBuilder &builder, Value a, Value b) { 182 return builder.create<arith::SelectOp>( 183 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OLT, a, b), a, b); 184 } 185 186 static Value max(ImplicitLocOpBuilder &builder, Value a, Value b) { 187 return builder.create<arith::SelectOp>( 188 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OGT, a, b), a, b); 189 } 190 191 static Value clamp(ImplicitLocOpBuilder &builder, Value value, Value lowerBound, 192 Value upperBound) { 193 return max(builder, min(builder, value, upperBound), lowerBound); 194 } 195 196 // Decomposes given floating point value `arg` into a normalized fraction and 197 // an integral power of two (see std::frexp). Returned values have float type. 198 static std::pair<Value, Value> frexp(ImplicitLocOpBuilder &builder, Value arg, 199 bool isPositive = false) { 200 assert(getElementTypeOrSelf(arg).isF32() && "arg must be f32 type"); 201 ArrayRef<int64_t> shape = vectorShape(arg); 202 203 auto bcast = [&](Value value) -> Value { 204 return broadcast(builder, value, shape); 205 }; 206 207 auto i32 = builder.getIntegerType(32); 208 auto i32Vec = broadcast(i32, shape); 209 auto f32Vec = broadcast(builder.getF32Type(), shape); 210 211 Value cst126f = f32Cst(builder, 126.0f); 212 Value cstHalf = f32Cst(builder, 0.5f); 213 Value cstInvMantMask = f32FromBits(builder, ~0x7f800000u); 214 215 // Bitcast to i32 for bitwise operations. 216 Value i32Half = builder.create<arith::BitcastOp>(i32, cstHalf); 217 Value i32InvMantMask = builder.create<arith::BitcastOp>(i32, cstInvMantMask); 218 Value i32Arg = builder.create<arith::BitcastOp>(i32Vec, arg); 219 220 // Compute normalized fraction. 221 Value tmp0 = builder.create<arith::AndIOp>(i32Arg, bcast(i32InvMantMask)); 222 Value tmp1 = builder.create<arith::OrIOp>(tmp0, bcast(i32Half)); 223 Value normalizedFraction = builder.create<arith::BitcastOp>(f32Vec, tmp1); 224 225 // Compute exponent. 226 Value arg0 = isPositive ? arg : builder.create<math::AbsOp>(arg); 227 Value biasedExponentBits = builder.create<arith::ShRUIOp>( 228 builder.create<arith::BitcastOp>(i32Vec, arg0), 229 bcast(i32Cst(builder, 23))); 230 Value biasedExponent = 231 builder.create<arith::SIToFPOp>(f32Vec, biasedExponentBits); 232 Value exponent = 233 builder.create<arith::SubFOp>(biasedExponent, bcast(cst126f)); 234 235 return {normalizedFraction, exponent}; 236 } 237 238 // Computes exp2 for an i32 argument. 239 static Value exp2I32(ImplicitLocOpBuilder &builder, Value arg) { 240 assert(getElementTypeOrSelf(arg).isInteger(32) && "arg must be i32 type"); 241 ArrayRef<int64_t> shape = vectorShape(arg); 242 243 auto bcast = [&](Value value) -> Value { 244 return broadcast(builder, value, shape); 245 }; 246 247 auto f32Vec = broadcast(builder.getF32Type(), shape); 248 // The exponent of f32 located at 23-bit. 249 auto exponetBitLocation = bcast(i32Cst(builder, 23)); 250 // Set the exponent bias to zero. 251 auto bias = bcast(i32Cst(builder, 127)); 252 253 Value biasedArg = builder.create<arith::AddIOp>(arg, bias); 254 Value exp2ValueInt = 255 builder.create<arith::ShLIOp>(biasedArg, exponetBitLocation); 256 Value exp2ValueF32 = builder.create<arith::BitcastOp>(f32Vec, exp2ValueInt); 257 258 return exp2ValueF32; 259 } 260 261 namespace { 262 Value makePolynomialCalculation(ImplicitLocOpBuilder &builder, 263 llvm::ArrayRef<Value> coeffs, Value x) { 264 assert(getElementTypeOrSelf(x).isF32() && "x must be f32 type"); 265 ArrayRef<int64_t> shape = vectorShape(x); 266 267 if (coeffs.empty()) 268 return broadcast(builder, f32Cst(builder, 0.0f), shape); 269 270 if (coeffs.size() == 1) 271 return coeffs[0]; 272 273 Value res = builder.create<math::FmaOp>(x, coeffs[coeffs.size() - 1], 274 coeffs[coeffs.size() - 2]); 275 for (auto i = ptrdiff_t(coeffs.size()) - 3; i >= 0; --i) { 276 res = builder.create<math::FmaOp>(x, res, coeffs[i]); 277 } 278 return res; 279 } 280 } // namespace 281 282 //----------------------------------------------------------------------------// 283 // AtanOp approximation. 284 //----------------------------------------------------------------------------// 285 286 namespace { 287 struct AtanApproximation : public OpRewritePattern<math::AtanOp> { 288 public: 289 using OpRewritePattern::OpRewritePattern; 290 291 LogicalResult matchAndRewrite(math::AtanOp op, 292 PatternRewriter &rewriter) const final; 293 }; 294 } // namespace 295 296 LogicalResult 297 AtanApproximation::matchAndRewrite(math::AtanOp op, 298 PatternRewriter &rewriter) const { 299 auto operand = op.getOperand(); 300 if (!getElementTypeOrSelf(operand).isF32()) 301 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 302 303 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 304 305 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 306 auto one = broadcast(builder, f32Cst(builder, 1.0f), shape); 307 308 // Remap the problem over [0.0, 1.0] by looking at the absolute value and the 309 // handling symmetry. 310 Value abs = builder.create<math::AbsOp>(operand); 311 Value reciprocal = builder.create<arith::DivFOp>(one, abs); 312 Value compare = 313 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OLT, abs, reciprocal); 314 Value x = builder.create<arith::SelectOp>(compare, abs, reciprocal); 315 316 // Perform the Taylor series approximation for atan over the range 317 // [-1.0, 1.0]. 318 auto n1 = broadcast(builder, f32Cst(builder, 0.14418283f), shape); 319 auto n2 = broadcast(builder, f32Cst(builder, -0.34999234f), shape); 320 auto n3 = broadcast(builder, f32Cst(builder, -0.01067831f), shape); 321 auto n4 = broadcast(builder, f32Cst(builder, 1.00209986f), shape); 322 323 Value p = builder.create<math::FmaOp>(x, n1, n2); 324 p = builder.create<math::FmaOp>(x, p, n3); 325 p = builder.create<math::FmaOp>(x, p, n4); 326 p = builder.create<arith::MulFOp>(x, p); 327 328 // Remap the solution for over [0.0, 1.0] to [0.0, inf] 329 auto halfPi = broadcast(builder, f32Cst(builder, 1.57079632679f), shape); 330 Value sub = builder.create<arith::SubFOp>(halfPi, p); 331 Value select = builder.create<arith::SelectOp>(compare, p, sub); 332 333 // Correct for signing of the input. 334 rewriter.replaceOpWithNewOp<math::CopySignOp>(op, select, operand); 335 return success(); 336 } 337 338 //----------------------------------------------------------------------------// 339 // AtanOp approximation. 340 //----------------------------------------------------------------------------// 341 342 namespace { 343 struct Atan2Approximation : public OpRewritePattern<math::Atan2Op> { 344 public: 345 using OpRewritePattern::OpRewritePattern; 346 347 LogicalResult matchAndRewrite(math::Atan2Op op, 348 PatternRewriter &rewriter) const final; 349 }; 350 } // namespace 351 352 LogicalResult 353 Atan2Approximation::matchAndRewrite(math::Atan2Op op, 354 PatternRewriter &rewriter) const { 355 auto y = op.getOperand(0); 356 auto x = op.getOperand(1); 357 if (!getElementTypeOrSelf(x).isF32()) 358 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 359 360 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 361 ArrayRef<int64_t> shape = vectorShape(op.getResult()); 362 363 // Compute atan in the valid range. 364 auto div = builder.create<arith::DivFOp>(y, x); 365 auto atan = builder.create<math::AtanOp>(div); 366 367 // Determine what the atan would be for a 180 degree rotation. 368 auto zero = broadcast(builder, f32Cst(builder, 0.0f), shape); 369 auto pi = broadcast(builder, f32Cst(builder, 3.14159265359f), shape); 370 auto addPi = builder.create<arith::AddFOp>(atan, pi); 371 auto subPi = builder.create<arith::SubFOp>(atan, pi); 372 auto atanGt = 373 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OGT, atan, zero); 374 auto flippedAtan = builder.create<arith::SelectOp>(atanGt, subPi, addPi); 375 376 // Determine whether to directly use atan or use the 180 degree flip 377 auto xGt = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OGT, x, zero); 378 Value result = builder.create<arith::SelectOp>(xGt, atan, flippedAtan); 379 380 // Handle x = 0, y > 0 381 Value xZero = 382 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, x, zero); 383 Value yGt = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OGT, y, zero); 384 Value isHalfPi = builder.create<arith::AndIOp>(xZero, yGt); 385 auto halfPi = broadcast(builder, f32Cst(builder, 1.57079632679f), shape); 386 result = builder.create<arith::SelectOp>(isHalfPi, halfPi, result); 387 388 // Handle x = 0, y < 0 389 Value yLt = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OLT, y, zero); 390 Value isNegativeHalfPiPi = builder.create<arith::AndIOp>(xZero, yLt); 391 auto negativeHalfPiPi = 392 broadcast(builder, f32Cst(builder, -1.57079632679f), shape); 393 result = builder.create<arith::SelectOp>(isNegativeHalfPiPi, negativeHalfPiPi, 394 result); 395 396 // Handle x = 0, y = 0; 397 Value yZero = 398 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, y, zero); 399 Value isNan = builder.create<arith::AndIOp>(xZero, yZero); 400 Value cstNan = broadcast(builder, f32FromBits(builder, 0x7fc00000), shape); 401 result = builder.create<arith::SelectOp>(isNan, cstNan, result); 402 403 rewriter.replaceOp(op, result); 404 return success(); 405 } 406 407 //----------------------------------------------------------------------------// 408 // TanhOp approximation. 409 //----------------------------------------------------------------------------// 410 411 namespace { 412 struct TanhApproximation : public OpRewritePattern<math::TanhOp> { 413 public: 414 using OpRewritePattern::OpRewritePattern; 415 416 LogicalResult matchAndRewrite(math::TanhOp op, 417 PatternRewriter &rewriter) const final; 418 }; 419 } // namespace 420 421 LogicalResult 422 TanhApproximation::matchAndRewrite(math::TanhOp op, 423 PatternRewriter &rewriter) const { 424 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 425 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 426 427 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 428 429 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 430 auto bcast = [&](Value value) -> Value { 431 return broadcast(builder, value, shape); 432 }; 433 434 // Clamp operand into [plusClamp, minusClamp] range. 435 Value minusClamp = bcast(f32Cst(builder, -7.99881172180175781f)); 436 Value plusClamp = bcast(f32Cst(builder, 7.99881172180175781f)); 437 Value x = clamp(builder, op.getOperand(), minusClamp, plusClamp); 438 439 // Mask for tiny values that are approximated with `operand`. 440 Value tiny = bcast(f32Cst(builder, 0.0004f)); 441 Value tinyMask = builder.create<arith::CmpFOp>( 442 arith::CmpFPredicate::OLT, builder.create<math::AbsOp>(op.getOperand()), 443 tiny); 444 445 // The monomial coefficients of the numerator polynomial (odd). 446 Value alpha1 = bcast(f32Cst(builder, 4.89352455891786e-03f)); 447 Value alpha3 = bcast(f32Cst(builder, 6.37261928875436e-04f)); 448 Value alpha5 = bcast(f32Cst(builder, 1.48572235717979e-05f)); 449 Value alpha7 = bcast(f32Cst(builder, 5.12229709037114e-08f)); 450 Value alpha9 = bcast(f32Cst(builder, -8.60467152213735e-11f)); 451 Value alpha11 = bcast(f32Cst(builder, 2.00018790482477e-13f)); 452 Value alpha13 = bcast(f32Cst(builder, -2.76076847742355e-16f)); 453 454 // The monomial coefficients of the denominator polynomial (even). 455 Value beta0 = bcast(f32Cst(builder, 4.89352518554385e-03f)); 456 Value beta2 = bcast(f32Cst(builder, 2.26843463243900e-03f)); 457 Value beta4 = bcast(f32Cst(builder, 1.18534705686654e-04f)); 458 Value beta6 = bcast(f32Cst(builder, 1.19825839466702e-06f)); 459 460 // Since the polynomials are odd/even, we need x^2. 461 Value x2 = builder.create<arith::MulFOp>(x, x); 462 463 // Evaluate the numerator polynomial p. 464 Value p = builder.create<math::FmaOp>(x2, alpha13, alpha11); 465 p = builder.create<math::FmaOp>(x2, p, alpha9); 466 p = builder.create<math::FmaOp>(x2, p, alpha7); 467 p = builder.create<math::FmaOp>(x2, p, alpha5); 468 p = builder.create<math::FmaOp>(x2, p, alpha3); 469 p = builder.create<math::FmaOp>(x2, p, alpha1); 470 p = builder.create<arith::MulFOp>(x, p); 471 472 // Evaluate the denominator polynomial q. 473 Value q = builder.create<math::FmaOp>(x2, beta6, beta4); 474 q = builder.create<math::FmaOp>(x2, q, beta2); 475 q = builder.create<math::FmaOp>(x2, q, beta0); 476 477 // Divide the numerator by the denominator. 478 Value res = builder.create<arith::SelectOp>( 479 tinyMask, x, builder.create<arith::DivFOp>(p, q)); 480 481 rewriter.replaceOp(op, res); 482 483 return success(); 484 } 485 486 #define LN2_VALUE \ 487 0.693147180559945309417232121458176568075500134360255254120680009493393621L 488 #define LOG2E_VALUE \ 489 1.442695040888963407359924681001892137426645954152985934135449406931109219L 490 491 //----------------------------------------------------------------------------// 492 // LogOp and Log2Op approximation. 493 //----------------------------------------------------------------------------// 494 495 namespace { 496 template <typename Op> 497 struct LogApproximationBase : public OpRewritePattern<Op> { 498 using OpRewritePattern<Op>::OpRewritePattern; 499 500 /// Base 2 if 'base2' is set; natural logarithm (base e) otherwise. 501 LogicalResult logMatchAndRewrite(Op op, PatternRewriter &rewriter, 502 bool base2) const; 503 }; 504 } // namespace 505 506 // This approximation comes from Julien Pommier's SSE math library. 507 // Link: http://gruntthepeon.free.fr/ssemath 508 template <typename Op> 509 LogicalResult 510 LogApproximationBase<Op>::logMatchAndRewrite(Op op, PatternRewriter &rewriter, 511 bool base2) const { 512 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 513 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 514 515 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 516 517 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 518 auto bcast = [&](Value value) -> Value { 519 return broadcast(builder, value, shape); 520 }; 521 522 Value cstZero = bcast(f32Cst(builder, 0.0f)); 523 Value cstOne = bcast(f32Cst(builder, 1.0f)); 524 Value cstNegHalf = bcast(f32Cst(builder, -0.5f)); 525 526 // The smallest non denormalized float number. 527 Value cstMinNormPos = bcast(f32FromBits(builder, 0x00800000u)); 528 Value cstMinusInf = bcast(f32FromBits(builder, 0xff800000u)); 529 Value cstPosInf = bcast(f32FromBits(builder, 0x7f800000u)); 530 Value cstNan = bcast(f32FromBits(builder, 0x7fc00000)); 531 532 // Polynomial coefficients. 533 Value cstCephesSQRTHF = bcast(f32Cst(builder, 0.707106781186547524f)); 534 Value cstCephesLogP0 = bcast(f32Cst(builder, 7.0376836292E-2f)); 535 Value cstCephesLogP1 = bcast(f32Cst(builder, -1.1514610310E-1f)); 536 Value cstCephesLogP2 = bcast(f32Cst(builder, 1.1676998740E-1f)); 537 Value cstCephesLogP3 = bcast(f32Cst(builder, -1.2420140846E-1f)); 538 Value cstCephesLogP4 = bcast(f32Cst(builder, +1.4249322787E-1f)); 539 Value cstCephesLogP5 = bcast(f32Cst(builder, -1.6668057665E-1f)); 540 Value cstCephesLogP6 = bcast(f32Cst(builder, +2.0000714765E-1f)); 541 Value cstCephesLogP7 = bcast(f32Cst(builder, -2.4999993993E-1f)); 542 Value cstCephesLogP8 = bcast(f32Cst(builder, +3.3333331174E-1f)); 543 544 Value x = op.getOperand(); 545 546 // Truncate input values to the minimum positive normal. 547 x = max(builder, x, cstMinNormPos); 548 549 // Extract significant in the range [0.5,1) and exponent. 550 std::pair<Value, Value> pair = frexp(builder, x, /*isPositive=*/true); 551 x = pair.first; 552 Value e = pair.second; 553 554 // Shift the inputs from the range [0.5,1) to [sqrt(1/2), sqrt(2)) and shift 555 // by -1.0. The values are then centered around 0, which improves the 556 // stability of the polynomial evaluation: 557 // 558 // if( x < SQRTHF ) { 559 // e -= 1; 560 // x = x + x - 1.0; 561 // } else { x = x - 1.0; } 562 Value mask = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OLT, x, 563 cstCephesSQRTHF); 564 Value tmp = builder.create<arith::SelectOp>(mask, x, cstZero); 565 566 x = builder.create<arith::SubFOp>(x, cstOne); 567 e = builder.create<arith::SubFOp>( 568 e, builder.create<arith::SelectOp>(mask, cstOne, cstZero)); 569 x = builder.create<arith::AddFOp>(x, tmp); 570 571 Value x2 = builder.create<arith::MulFOp>(x, x); 572 Value x3 = builder.create<arith::MulFOp>(x2, x); 573 574 // Evaluate the polynomial approximant of degree 8 in three parts. 575 Value y0, y1, y2; 576 y0 = builder.create<math::FmaOp>(cstCephesLogP0, x, cstCephesLogP1); 577 y1 = builder.create<math::FmaOp>(cstCephesLogP3, x, cstCephesLogP4); 578 y2 = builder.create<math::FmaOp>(cstCephesLogP6, x, cstCephesLogP7); 579 y0 = builder.create<math::FmaOp>(y0, x, cstCephesLogP2); 580 y1 = builder.create<math::FmaOp>(y1, x, cstCephesLogP5); 581 y2 = builder.create<math::FmaOp>(y2, x, cstCephesLogP8); 582 y0 = builder.create<math::FmaOp>(y0, x3, y1); 583 y0 = builder.create<math::FmaOp>(y0, x3, y2); 584 y0 = builder.create<arith::MulFOp>(y0, x3); 585 586 y0 = builder.create<math::FmaOp>(cstNegHalf, x2, y0); 587 x = builder.create<arith::AddFOp>(x, y0); 588 589 if (base2) { 590 Value cstLog2e = bcast(f32Cst(builder, static_cast<float>(LOG2E_VALUE))); 591 x = builder.create<math::FmaOp>(x, cstLog2e, e); 592 } else { 593 Value cstLn2 = bcast(f32Cst(builder, static_cast<float>(LN2_VALUE))); 594 x = builder.create<math::FmaOp>(e, cstLn2, x); 595 } 596 597 Value invalidMask = builder.create<arith::CmpFOp>(arith::CmpFPredicate::ULT, 598 op.getOperand(), cstZero); 599 Value zeroMask = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, 600 op.getOperand(), cstZero); 601 Value posInfMask = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, 602 op.getOperand(), cstPosInf); 603 604 // Filter out invalid values: 605 // • x == 0 -> -INF 606 // • x < 0 -> NAN 607 // • x == +INF -> +INF 608 Value aproximation = builder.create<arith::SelectOp>( 609 zeroMask, cstMinusInf, 610 builder.create<arith::SelectOp>( 611 invalidMask, cstNan, 612 builder.create<arith::SelectOp>(posInfMask, cstPosInf, x))); 613 614 rewriter.replaceOp(op, aproximation); 615 616 return success(); 617 } 618 619 namespace { 620 struct LogApproximation : public LogApproximationBase<math::LogOp> { 621 using LogApproximationBase::LogApproximationBase; 622 623 LogicalResult matchAndRewrite(math::LogOp op, 624 PatternRewriter &rewriter) const final { 625 return logMatchAndRewrite(op, rewriter, /*base2=*/false); 626 } 627 }; 628 } // namespace 629 630 namespace { 631 struct Log2Approximation : public LogApproximationBase<math::Log2Op> { 632 using LogApproximationBase::LogApproximationBase; 633 634 LogicalResult matchAndRewrite(math::Log2Op op, 635 PatternRewriter &rewriter) const final { 636 return logMatchAndRewrite(op, rewriter, /*base2=*/true); 637 } 638 }; 639 } // namespace 640 641 //----------------------------------------------------------------------------// 642 // Log1p approximation. 643 //----------------------------------------------------------------------------// 644 645 namespace { 646 struct Log1pApproximation : public OpRewritePattern<math::Log1pOp> { 647 public: 648 using OpRewritePattern::OpRewritePattern; 649 650 LogicalResult matchAndRewrite(math::Log1pOp op, 651 PatternRewriter &rewriter) const final; 652 }; 653 } // namespace 654 655 // Approximate log(1+x). 656 LogicalResult 657 Log1pApproximation::matchAndRewrite(math::Log1pOp op, 658 PatternRewriter &rewriter) const { 659 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 660 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 661 662 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 663 664 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 665 auto bcast = [&](Value value) -> Value { 666 return broadcast(builder, value, shape); 667 }; 668 669 // Approximate log(1+x) using the following, due to W. Kahan: 670 // u = x + 1.0; 671 // if (u == 1.0 || u == inf) return x; 672 // return x * log(u) / (u - 1.0); 673 // ^^^^^^^^^^^^^^^^^^^^^^ 674 // "logLarge" below. 675 Value cstOne = bcast(f32Cst(builder, 1.0f)); 676 Value x = op.getOperand(); 677 Value u = builder.create<arith::AddFOp>(x, cstOne); 678 Value uSmall = 679 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, u, cstOne); 680 Value logU = builder.create<math::LogOp>(u); 681 Value uInf = 682 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, u, logU); 683 Value logLarge = builder.create<arith::MulFOp>( 684 x, builder.create<arith::DivFOp>( 685 logU, builder.create<arith::SubFOp>(u, cstOne))); 686 Value approximation = builder.create<arith::SelectOp>( 687 builder.create<arith::OrIOp>(uSmall, uInf), x, logLarge); 688 rewriter.replaceOp(op, approximation); 689 return success(); 690 } 691 692 //----------------------------------------------------------------------------// 693 // Erf approximation. 694 //----------------------------------------------------------------------------// 695 696 // Approximates erf(x) with 697 // a - P(x)/Q(x) 698 // where P and Q are polynomials of degree 4. 699 // Different coefficients are chosen based on the value of x. 700 // The approximation error is ~2.5e-07. 701 // Boost's minimax tool that utilizes the Remez method was used to find the 702 // coefficients. 703 LogicalResult 704 ErfPolynomialApproximation::matchAndRewrite(math::ErfOp op, 705 PatternRewriter &rewriter) const { 706 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 707 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 708 709 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 710 711 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 712 auto bcast = [&](Value value) -> Value { 713 return broadcast(builder, value, shape); 714 }; 715 716 const int intervalsCount = 3; 717 const int polyDegree = 4; 718 719 Value zero = bcast(f32Cst(builder, 0)); 720 Value one = bcast(f32Cst(builder, 1)); 721 Value pp[intervalsCount][polyDegree + 1]; 722 pp[0][0] = bcast(f32Cst(builder, +0.00000000000000000e+00f)); 723 pp[0][1] = bcast(f32Cst(builder, +1.12837916222975858e+00f)); 724 pp[0][2] = bcast(f32Cst(builder, -5.23018562988006470e-01f)); 725 pp[0][3] = bcast(f32Cst(builder, +2.09741709609267072e-01f)); 726 pp[0][4] = bcast(f32Cst(builder, +2.58146801602987875e-02f)); 727 pp[1][0] = bcast(f32Cst(builder, +0.00000000000000000e+00f)); 728 pp[1][1] = bcast(f32Cst(builder, +1.12750687816789140e+00f)); 729 pp[1][2] = bcast(f32Cst(builder, -3.64721408487825775e-01f)); 730 pp[1][3] = bcast(f32Cst(builder, +1.18407396425136952e-01f)); 731 pp[1][4] = bcast(f32Cst(builder, +3.70645533056476558e-02f)); 732 pp[2][0] = bcast(f32Cst(builder, -3.30093071049483172e-03f)); 733 pp[2][1] = bcast(f32Cst(builder, +3.51961938357697011e-03f)); 734 pp[2][2] = bcast(f32Cst(builder, -1.41373622814988039e-03f)); 735 pp[2][3] = bcast(f32Cst(builder, +2.53447094961941348e-04f)); 736 pp[2][4] = bcast(f32Cst(builder, -1.71048029455037401e-05f)); 737 738 Value qq[intervalsCount][polyDegree + 1]; 739 qq[0][0] = bcast(f32Cst(builder, +1.000000000000000000e+00f)); 740 qq[0][1] = bcast(f32Cst(builder, -4.635138185962547255e-01f)); 741 qq[0][2] = bcast(f32Cst(builder, +5.192301327279782447e-01f)); 742 qq[0][3] = bcast(f32Cst(builder, -1.318089722204810087e-01f)); 743 qq[0][4] = bcast(f32Cst(builder, +7.397964654672315005e-02f)); 744 qq[1][0] = bcast(f32Cst(builder, +1.00000000000000000e+00f)); 745 qq[1][1] = bcast(f32Cst(builder, -3.27607011824493086e-01f)); 746 qq[1][2] = bcast(f32Cst(builder, +4.48369090658821977e-01f)); 747 qq[1][3] = bcast(f32Cst(builder, -8.83462621207857930e-02f)); 748 qq[1][4] = bcast(f32Cst(builder, +5.72442770283176093e-02f)); 749 qq[2][0] = bcast(f32Cst(builder, +1.00000000000000000e+00f)); 750 qq[2][1] = bcast(f32Cst(builder, -2.06069165953913769e+00f)); 751 qq[2][2] = bcast(f32Cst(builder, +1.62705939945477759e+00f)); 752 qq[2][3] = bcast(f32Cst(builder, -5.83389859211130017e-01f)); 753 qq[2][4] = bcast(f32Cst(builder, +8.21908939856640930e-02f)); 754 755 Value offsets[intervalsCount]; 756 offsets[0] = bcast(f32Cst(builder, 0.0f)); 757 offsets[1] = bcast(f32Cst(builder, 0.0f)); 758 offsets[2] = bcast(f32Cst(builder, 1.0f)); 759 760 Value bounds[intervalsCount]; 761 bounds[0] = bcast(f32Cst(builder, 0.8f)); 762 bounds[1] = bcast(f32Cst(builder, 2.0f)); 763 bounds[2] = bcast(f32Cst(builder, 3.75f)); 764 765 Value isNegativeArg = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OLT, 766 op.getOperand(), zero); 767 Value negArg = builder.create<arith::NegFOp>(op.getOperand()); 768 Value x = 769 builder.create<arith::SelectOp>(isNegativeArg, negArg, op.getOperand()); 770 771 Value offset = offsets[0]; 772 Value p[polyDegree + 1]; 773 Value q[polyDegree + 1]; 774 for (int i = 0; i <= polyDegree; ++i) { 775 p[i] = pp[0][i]; 776 q[i] = qq[0][i]; 777 } 778 779 // TODO: maybe use vector stacking to reduce the number of selects. 780 Value isLessThanBound[intervalsCount]; 781 for (int j = 0; j < intervalsCount - 1; ++j) { 782 isLessThanBound[j] = 783 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OLT, x, bounds[j]); 784 for (int i = 0; i <= polyDegree; ++i) { 785 p[i] = builder.create<arith::SelectOp>(isLessThanBound[j], p[i], 786 pp[j + 1][i]); 787 q[i] = builder.create<arith::SelectOp>(isLessThanBound[j], q[i], 788 qq[j + 1][i]); 789 } 790 offset = builder.create<arith::SelectOp>(isLessThanBound[j], offset, 791 offsets[j + 1]); 792 } 793 isLessThanBound[intervalsCount - 1] = builder.create<arith::CmpFOp>( 794 arith::CmpFPredicate::ULT, x, bounds[intervalsCount - 1]); 795 796 Value pPoly = makePolynomialCalculation(builder, p, x); 797 Value qPoly = makePolynomialCalculation(builder, q, x); 798 Value rationalPoly = builder.create<arith::DivFOp>(pPoly, qPoly); 799 Value formula = builder.create<arith::AddFOp>(offset, rationalPoly); 800 formula = builder.create<arith::SelectOp>(isLessThanBound[intervalsCount - 1], 801 formula, one); 802 803 // erf is odd function: erf(x) = -erf(-x). 804 Value negFormula = builder.create<arith::NegFOp>(formula); 805 Value res = 806 builder.create<arith::SelectOp>(isNegativeArg, negFormula, formula); 807 808 rewriter.replaceOp(op, res); 809 810 return success(); 811 } 812 813 //----------------------------------------------------------------------------// 814 // Exp approximation. 815 //----------------------------------------------------------------------------// 816 817 namespace { 818 819 struct ExpApproximation : public OpRewritePattern<math::ExpOp> { 820 public: 821 using OpRewritePattern::OpRewritePattern; 822 823 LogicalResult matchAndRewrite(math::ExpOp op, 824 PatternRewriter &rewriter) const final; 825 }; 826 } // namespace 827 828 // Approximate exp(x) using its reduced range exp(y) where y is in the range 829 // [0, ln(2)], let y = x - floor(x / ln(2)) * ln(2) = x - k * ln(2), exp(x) 830 // = exp(y) * 2^k. exp(y). 831 LogicalResult 832 ExpApproximation::matchAndRewrite(math::ExpOp op, 833 PatternRewriter &rewriter) const { 834 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 835 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 836 837 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 838 839 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 840 841 // TODO: Consider a common pattern rewriter with all methods below to 842 // write the approximations. 843 auto bcast = [&](Value value) -> Value { 844 return broadcast(builder, value, shape); 845 }; 846 auto fmla = [&](Value a, Value b, Value c) { 847 return builder.create<math::FmaOp>(a, b, c); 848 }; 849 auto mul = [&](Value a, Value b) -> Value { 850 return builder.create<arith::MulFOp>(a, b); 851 }; 852 auto sub = [&](Value a, Value b) -> Value { 853 return builder.create<arith::SubFOp>(a, b); 854 }; 855 auto floor = [&](Value a) { return builder.create<math::FloorOp>(a); }; 856 857 Value cstLn2 = bcast(f32Cst(builder, static_cast<float>(LN2_VALUE))); 858 Value cstLog2E = bcast(f32Cst(builder, static_cast<float>(LOG2E_VALUE))); 859 860 // Polynomial coefficients. 861 Value cstCephesExpP0 = bcast(f32Cst(builder, 1.0)); 862 Value cstCephesExpP1 = bcast(f32Cst(builder, 1.0)); 863 Value cstCephesExpP2 = bcast(f32Cst(builder, 0.49970514590562437052f)); 864 Value cstCephesExpP3 = bcast(f32Cst(builder, 0.16873890085469545053f)); 865 Value cstCephesExpP4 = bcast(f32Cst(builder, 0.03668965196652099192f)); 866 Value cstCephesExpP5 = bcast(f32Cst(builder, 0.01314350012789660196f)); 867 868 Value x = op.getOperand(); 869 870 // Reduced y = x - floor(x / ln(2)) * ln(2) = x - k * ln(2) 871 Value xL2Inv = mul(x, cstLog2E); 872 Value kF32 = floor(xL2Inv); 873 Value kLn2 = mul(kF32, cstLn2); 874 Value y = sub(x, kLn2); 875 876 // Use Estrin's evaluation scheme with 3 independent parts: 877 // P(y)^y : (c0 + c1 y) + (c2 + c3 y) y^2 + (c4 + c5 y) y^4 878 Value y2 = mul(y, y); 879 Value y4 = mul(y2, y2); 880 881 Value q0 = fmla(cstCephesExpP1, y, cstCephesExpP0); 882 Value q1 = fmla(cstCephesExpP3, y, cstCephesExpP2); 883 Value q2 = fmla(cstCephesExpP5, y, cstCephesExpP4); 884 Value expY = fmla(q1, y2, q0); 885 expY = fmla(q2, y4, expY); 886 887 auto i32Vec = broadcast(builder.getI32Type(), shape); 888 889 // exp2(k) 890 Value k = builder.create<arith::FPToSIOp>(i32Vec, kF32); 891 Value exp2KValue = exp2I32(builder, k); 892 893 // exp(x) = exp(y) * exp2(k) 894 expY = mul(expY, exp2KValue); 895 896 // Handle overflow, inf and underflow of exp(x). exp(x) range is [0, inf], its 897 // partitioned as the following: 898 // exp(x) = 0, x <= -inf 899 // exp(x) = underflow (min_float), x <= -88 900 // exp(x) = inf (min_float), x >= 88 901 // Note: |k| = 127 is the value where the 8-bits exponent saturates. 902 Value zerof32Const = bcast(f32Cst(builder, 0)); 903 auto constPosInfinity = 904 bcast(f32Cst(builder, std::numeric_limits<float>::infinity())); 905 auto constNegIfinity = 906 bcast(f32Cst(builder, -std::numeric_limits<float>::infinity())); 907 auto underflow = bcast(f32Cst(builder, std::numeric_limits<float>::min())); 908 909 Value kMaxConst = bcast(i32Cst(builder, 127)); 910 Value kMaxNegConst = bcast(i32Cst(builder, -127)); 911 Value rightBound = 912 builder.create<arith::CmpIOp>(arith::CmpIPredicate::sle, k, kMaxConst); 913 Value leftBound = 914 builder.create<arith::CmpIOp>(arith::CmpIPredicate::sge, k, kMaxNegConst); 915 916 Value isNegInfinityX = builder.create<arith::CmpFOp>( 917 arith::CmpFPredicate::OEQ, x, constNegIfinity); 918 Value isPosInfinityX = builder.create<arith::CmpFOp>( 919 arith::CmpFPredicate::OEQ, x, constPosInfinity); 920 Value isPostiveX = 921 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OGT, x, zerof32Const); 922 Value isComputable = builder.create<arith::AndIOp>(rightBound, leftBound); 923 924 expY = builder.create<arith::SelectOp>( 925 isNegInfinityX, zerof32Const, 926 builder.create<arith::SelectOp>( 927 isPosInfinityX, constPosInfinity, 928 builder.create<arith::SelectOp>( 929 isComputable, expY, 930 builder.create<arith::SelectOp>(isPostiveX, constPosInfinity, 931 underflow)))); 932 933 rewriter.replaceOp(op, expY); 934 935 return success(); 936 } 937 938 //----------------------------------------------------------------------------// 939 // ExpM1 approximation. 940 //----------------------------------------------------------------------------// 941 942 namespace { 943 944 struct ExpM1Approximation : public OpRewritePattern<math::ExpM1Op> { 945 public: 946 using OpRewritePattern::OpRewritePattern; 947 948 LogicalResult matchAndRewrite(math::ExpM1Op op, 949 PatternRewriter &rewriter) const final; 950 }; 951 } // namespace 952 953 LogicalResult 954 ExpM1Approximation::matchAndRewrite(math::ExpM1Op op, 955 PatternRewriter &rewriter) const { 956 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 957 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 958 959 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 960 961 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 962 auto bcast = [&](Value value) -> Value { 963 return broadcast(builder, value, shape); 964 }; 965 966 // expm1(x) = exp(x) - 1 = u - 1. 967 // We have to handle it carefully when x is near 0, i.e. u ~= 1, 968 // and when the input is ~= -inf, i.e. u - 1 ~= -1. 969 Value cstOne = bcast(f32Cst(builder, 1.0f)); 970 Value cstNegOne = bcast(f32Cst(builder, -1.0f)); 971 Value x = op.getOperand(); 972 Value u = builder.create<math::ExpOp>(x); 973 Value uEqOne = 974 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, u, cstOne); 975 Value uMinusOne = builder.create<arith::SubFOp>(u, cstOne); 976 Value uMinusOneEqNegOne = builder.create<arith::CmpFOp>( 977 arith::CmpFPredicate::OEQ, uMinusOne, cstNegOne); 978 // logU = log(u) ~= x 979 Value logU = builder.create<math::LogOp>(u); 980 981 // Detect exp(x) = +inf; written this way to avoid having to form +inf. 982 Value isInf = 983 builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, logU, u); 984 985 // (u - 1) * (x / ~x) 986 Value expm1 = builder.create<arith::MulFOp>( 987 uMinusOne, builder.create<arith::DivFOp>(x, logU)); 988 expm1 = builder.create<arith::SelectOp>(isInf, u, expm1); 989 Value approximation = builder.create<arith::SelectOp>( 990 uEqOne, x, 991 builder.create<arith::SelectOp>(uMinusOneEqNegOne, cstNegOne, expm1)); 992 rewriter.replaceOp(op, approximation); 993 return success(); 994 } 995 996 //----------------------------------------------------------------------------// 997 // Sin and Cos approximation. 998 //----------------------------------------------------------------------------// 999 1000 namespace { 1001 1002 template <bool isSine, typename OpTy> 1003 struct SinAndCosApproximation : public OpRewritePattern<OpTy> { 1004 public: 1005 using OpRewritePattern<OpTy>::OpRewritePattern; 1006 1007 LogicalResult matchAndRewrite(OpTy op, PatternRewriter &rewriter) const final; 1008 }; 1009 } // namespace 1010 1011 #define TWO_OVER_PI \ 1012 0.6366197723675813430755350534900574481378385829618257949906693762L 1013 #define PI_OVER_2 \ 1014 1.5707963267948966192313216916397514420985846996875529104874722961L 1015 1016 // Approximates sin(x) or cos(x) by finding the best approximation polynomial in 1017 // the reduced range [0, pi/2] for both sin(x) and cos(x). Then given y in the 1018 // reduced range sin(x) will be computed as sin(y), -sin(y), cos(y) or -cos(y). 1019 template <bool isSine, typename OpTy> 1020 LogicalResult SinAndCosApproximation<isSine, OpTy>::matchAndRewrite( 1021 OpTy op, PatternRewriter &rewriter) const { 1022 static_assert( 1023 llvm::is_one_of<OpTy, math::SinOp, math::CosOp>::value, 1024 "SinAndCosApproximation pattern expects math::SinOp or math::CosOp"); 1025 1026 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 1027 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 1028 1029 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 1030 1031 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 1032 auto bcast = [&](Value value) -> Value { 1033 return broadcast(builder, value, shape); 1034 }; 1035 auto mul = [&](Value a, Value b) -> Value { 1036 return builder.create<arith::MulFOp>(a, b); 1037 }; 1038 auto sub = [&](Value a, Value b) -> Value { 1039 return builder.create<arith::SubFOp>(a, b); 1040 }; 1041 auto floor = [&](Value a) { return builder.create<math::FloorOp>(a); }; 1042 1043 auto i32Vec = broadcast(builder.getI32Type(), shape); 1044 auto fPToSingedInteger = [&](Value a) -> Value { 1045 return builder.create<arith::FPToSIOp>(i32Vec, a); 1046 }; 1047 1048 auto modulo4 = [&](Value a) -> Value { 1049 return builder.create<arith::AndIOp>(a, bcast(i32Cst(builder, 3))); 1050 }; 1051 1052 auto isEqualTo = [&](Value a, Value b) -> Value { 1053 return builder.create<arith::CmpIOp>(arith::CmpIPredicate::eq, a, b); 1054 }; 1055 1056 auto isGreaterThan = [&](Value a, Value b) -> Value { 1057 return builder.create<arith::CmpIOp>(arith::CmpIPredicate::sgt, a, b); 1058 }; 1059 1060 auto select = [&](Value cond, Value t, Value f) -> Value { 1061 return builder.create<arith::SelectOp>(cond, t, f); 1062 }; 1063 1064 auto fmla = [&](Value a, Value b, Value c) { 1065 return builder.create<math::FmaOp>(a, b, c); 1066 }; 1067 1068 auto bitwiseOr = [&](Value a, Value b) { 1069 return builder.create<arith::OrIOp>(a, b); 1070 }; 1071 1072 Value twoOverPi = bcast(f32Cst(builder, (float)TWO_OVER_PI)); 1073 Value piOverTwo = bcast(f32Cst(builder, (float)PI_OVER_2)); 1074 1075 Value x = op.getOperand(); 1076 1077 Value k = floor(mul(x, twoOverPi)); 1078 1079 Value y = sub(x, mul(k, piOverTwo)); 1080 1081 Value cstOne = bcast(f32Cst(builder, 1.0)); 1082 Value cstNegativeOne = bcast(f32Cst(builder, -1.0)); 1083 1084 Value cstSC2 = bcast(f32Cst(builder, -0.16666667163372039794921875f)); 1085 Value cstSC4 = bcast(f32Cst(builder, 8.333347737789154052734375e-3f)); 1086 Value cstSC6 = bcast(f32Cst(builder, -1.9842604524455964565277099609375e-4f)); 1087 Value cstSC8 = 1088 bcast(f32Cst(builder, 2.760012648650445044040679931640625e-6f)); 1089 Value cstSC10 = 1090 bcast(f32Cst(builder, -2.50293279435709337121807038784027099609375e-8f)); 1091 1092 Value cstCC2 = bcast(f32Cst(builder, -0.5f)); 1093 Value cstCC4 = bcast(f32Cst(builder, 4.166664183139801025390625e-2f)); 1094 Value cstCC6 = bcast(f32Cst(builder, -1.388833043165504932403564453125e-3f)); 1095 Value cstCC8 = bcast(f32Cst(builder, 2.47562347794882953166961669921875e-5f)); 1096 Value cstCC10 = 1097 bcast(f32Cst(builder, -2.59630184018533327616751194000244140625e-7f)); 1098 1099 Value kMod4 = modulo4(fPToSingedInteger(k)); 1100 1101 Value kR0 = isEqualTo(kMod4, bcast(i32Cst(builder, 0))); 1102 Value kR1 = isEqualTo(kMod4, bcast(i32Cst(builder, 1))); 1103 Value kR2 = isEqualTo(kMod4, bcast(i32Cst(builder, 2))); 1104 Value kR3 = isEqualTo(kMod4, bcast(i32Cst(builder, 3))); 1105 1106 Value sinuseCos = isSine ? bitwiseOr(kR1, kR3) : bitwiseOr(kR0, kR2); 1107 Value negativeRange = isSine ? isGreaterThan(kMod4, bcast(i32Cst(builder, 1))) 1108 : bitwiseOr(kR1, kR2); 1109 1110 Value y2 = mul(y, y); 1111 1112 Value base = select(sinuseCos, cstOne, y); 1113 Value cstC2 = select(sinuseCos, cstCC2, cstSC2); 1114 Value cstC4 = select(sinuseCos, cstCC4, cstSC4); 1115 Value cstC6 = select(sinuseCos, cstCC6, cstSC6); 1116 Value cstC8 = select(sinuseCos, cstCC8, cstSC8); 1117 Value cstC10 = select(sinuseCos, cstCC10, cstSC10); 1118 1119 Value v1 = fmla(y2, cstC10, cstC8); 1120 Value v2 = fmla(y2, v1, cstC6); 1121 Value v3 = fmla(y2, v2, cstC4); 1122 Value v4 = fmla(y2, v3, cstC2); 1123 Value v5 = fmla(y2, v4, cstOne); 1124 Value v6 = mul(base, v5); 1125 1126 Value approximation = select(negativeRange, mul(cstNegativeOne, v6), v6); 1127 1128 rewriter.replaceOp(op, approximation); 1129 1130 return success(); 1131 } 1132 1133 //----------------------------------------------------------------------------// 1134 // Rsqrt approximation. 1135 //----------------------------------------------------------------------------// 1136 1137 namespace { 1138 struct RsqrtApproximation : public OpRewritePattern<math::RsqrtOp> { 1139 using OpRewritePattern::OpRewritePattern; 1140 1141 LogicalResult matchAndRewrite(math::RsqrtOp op, 1142 PatternRewriter &rewriter) const final; 1143 }; 1144 } // namespace 1145 1146 LogicalResult 1147 RsqrtApproximation::matchAndRewrite(math::RsqrtOp op, 1148 PatternRewriter &rewriter) const { 1149 if (!getElementTypeOrSelf(op.getOperand()).isF32()) 1150 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 1151 1152 ArrayRef<int64_t> shape = vectorShape(op.getOperand()); 1153 1154 // Only support already-vectorized rsqrt's. 1155 if (shape.empty() || shape.back() % 8 != 0) 1156 return rewriter.notifyMatchFailure(op, "unsupported operand type"); 1157 1158 ImplicitLocOpBuilder builder(op->getLoc(), rewriter); 1159 auto bcast = [&](Value value) -> Value { 1160 return broadcast(builder, value, shape); 1161 }; 1162 1163 Value cstPosInf = bcast(f32FromBits(builder, 0x7f800000u)); 1164 Value cstOnePointFive = bcast(f32Cst(builder, 1.5f)); 1165 Value cstNegHalf = bcast(f32Cst(builder, -0.5f)); 1166 Value cstMinNormPos = bcast(f32FromBits(builder, 0x00800000u)); 1167 1168 Value negHalf = builder.create<arith::MulFOp>(op.getOperand(), cstNegHalf); 1169 1170 // Select only the inverse sqrt of positive normals (denormals are 1171 // flushed to zero). 1172 Value ltMinMask = builder.create<arith::CmpFOp>( 1173 arith::CmpFPredicate::OLT, op.getOperand(), cstMinNormPos); 1174 Value infMask = builder.create<arith::CmpFOp>(arith::CmpFPredicate::OEQ, 1175 op.getOperand(), cstPosInf); 1176 Value notNormalFiniteMask = builder.create<arith::OrIOp>(ltMinMask, infMask); 1177 1178 // Compute an approximate result. 1179 Value yApprox = handleMultidimensionalVectors( 1180 builder, op->getOperands(), 8, [&builder](ValueRange operands) -> Value { 1181 return builder.create<x86vector::RsqrtOp>(operands); 1182 }); 1183 1184 // Do a single step of Newton-Raphson iteration to improve the approximation. 1185 // This uses the formula y_{n+1} = y_n * (1.5 - y_n * (0.5 * x) * y_n). 1186 // It is essential to evaluate the inner term like this because forming 1187 // y_n^2 may over- or underflow. 1188 Value inner = builder.create<arith::MulFOp>(negHalf, yApprox); 1189 Value fma = builder.create<math::FmaOp>(yApprox, inner, cstOnePointFive); 1190 Value yNewton = builder.create<arith::MulFOp>(yApprox, fma); 1191 1192 // Select the result of the Newton-Raphson step for positive normal arguments. 1193 // For other arguments, choose the output of the intrinsic. This will 1194 // return rsqrt(+inf) = 0, rsqrt(x) = NaN if x < 0, and rsqrt(x) = +inf if 1195 // x is zero or a positive denormalized float (equivalent to flushing positive 1196 // denormalized inputs to zero). 1197 Value res = 1198 builder.create<arith::SelectOp>(notNormalFiniteMask, yApprox, yNewton); 1199 rewriter.replaceOp(op, res); 1200 1201 return success(); 1202 } 1203 1204 //----------------------------------------------------------------------------// 1205 1206 void mlir::populateMathPolynomialApproximationPatterns( 1207 RewritePatternSet &patterns, 1208 const MathPolynomialApproximationOptions &options) { 1209 patterns.add<AtanApproximation, Atan2Approximation, TanhApproximation, 1210 LogApproximation, Log2Approximation, Log1pApproximation, 1211 ErfPolynomialApproximation, ExpApproximation, ExpM1Approximation, 1212 SinAndCosApproximation<true, math::SinOp>, 1213 SinAndCosApproximation<false, math::CosOp>>( 1214 patterns.getContext()); 1215 if (options.enableAvx2) 1216 patterns.add<RsqrtApproximation>(patterns.getContext()); 1217 } 1218