MLIR 24.0.0git
PolynomialApproximation.cpp
Go to the documentation of this file.
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 <cmath>
16#include <cstddef>
17
26#include "mlir/IR/Builders.h"
32#include "llvm/ADT/ArrayRef.h"
33#include "llvm/ADT/STLExtras.h"
34#include "llvm/Support/MathExtras.h"
35
36using namespace mlir;
37using namespace mlir::math;
38using namespace mlir::vector;
39
40// Helper to encapsulate a vector's shape (including scalable dims).
45
46// Returns vector shape if the type is a vector, otherwise return nullopt.
47static std::optional<VectorShape> vectorShape(Type type) {
48 if (auto vectorType = dyn_cast<VectorType>(type)) {
49 return VectorShape{vectorType.getShape(), vectorType.getScalableDims()};
50 }
51 return std::nullopt;
52}
53
54static std::optional<VectorShape> vectorShape(Value value) {
55 return vectorShape(value.getType());
56}
57
58//----------------------------------------------------------------------------//
59// Broadcast scalar types and values into vector types and values.
60//----------------------------------------------------------------------------//
61
62// Broadcasts scalar type into vector type (iff shape is non-scalar).
63static Type broadcast(Type type, std::optional<VectorShape> shape) {
64 assert(!isa<VectorType>(type) && "must be scalar type");
65 return shape ? VectorType::get(shape->sizes, type, shape->scalableFlags)
66 : type;
67}
68
69// Broadcasts scalar value into vector (iff shape is non-scalar).
71 std::optional<VectorShape> shape) {
72 assert(!isa<VectorType>(value.getType()) && "must be scalar value");
73 auto type = broadcast(value.getType(), shape);
74 return shape ? BroadcastOp::create(builder, type, value) : value;
75}
76
77//----------------------------------------------------------------------------//
78// Helper function to handle n-D vectors with 1-D operations.
79//----------------------------------------------------------------------------//
80
81// Expands and unrolls n-D vector operands into multiple fixed size 1-D vectors
82// and calls the compute function with 1-D vector operands. Stitches back all
83// results into the original n-D vector result.
84//
85// Examples: vectorWidth = 8
86// - vector<4x8xf32> unrolled 4 times
87// - vector<16xf32> expanded to vector<2x8xf32> and unrolled 2 times
88// - vector<4x16xf32> expanded to vector<4x2x8xf32> and unrolled 4*2 times
89//
90// Some math approximations rely on ISA-specific operations that only accept
91// fixed size 1-D vectors (e.g. AVX expects vectors of width 8).
92//
93// It is the caller's responsibility to verify that the inner dimension is
94// divisible by the vectorWidth, and that all operands have the same vector
95// shape.
96static Value
98 ValueRange operands, int64_t vectorWidth,
100 assert(!operands.empty() && "operands must be not empty");
101 assert(vectorWidth > 0 && "vector width must be larger than 0");
102
103 VectorType inputType = cast<VectorType>(operands[0].getType());
104 ArrayRef<int64_t> inputShape = inputType.getShape();
105
106 // If input shape matches target vector width, we can just call the
107 // user-provided compute function with the operands.
108 if (inputShape == llvm::ArrayRef(vectorWidth))
109 return compute(operands);
110
111 // Check if the inner dimension has to be expanded, or we can directly iterate
112 // over the outer dimensions of the vector.
113 int64_t innerDim = inputShape.back();
114 int64_t expansionDim = innerDim / vectorWidth;
115 assert((innerDim % vectorWidth == 0) && "invalid inner dimension size");
116
117 // Maybe expand operands to the higher rank vector shape that we'll use to
118 // iterate over and extract one dimensional vectors.
119 SmallVector<int64_t> expandedShape(inputShape);
120 SmallVector<Value> expandedOperands(operands);
121
122 if (expansionDim > 1) {
123 // Expand shape from [..., innerDim] to [..., expansionDim, vectorWidth].
124 expandedShape.insert(expandedShape.end() - 1, expansionDim);
125 expandedShape.back() = vectorWidth;
126
127 for (unsigned i = 0; i < operands.size(); ++i) {
128 auto operand = operands[i];
129 auto eltType = cast<VectorType>(operand.getType()).getElementType();
130 auto expandedType = VectorType::get(expandedShape, eltType);
131 expandedOperands[i] =
132 vector::ShapeCastOp::create(builder, expandedType, operand);
133 }
134 }
135
136 // Iterate over all outer dimensions of the compute shape vector type.
137 auto iterationDims = ArrayRef<int64_t>(expandedShape).drop_back();
138 int64_t maxIndex = computeMaxLinearIndex(iterationDims);
139 auto strides = computeStrides(iterationDims);
140
141 // Compute results for each one dimensional vector.
142 SmallVector<Value> results(maxIndex);
143
144 for (int64_t i = 0; i < maxIndex; ++i) {
145 auto offsets = delinearize(i, strides);
146
147 SmallVector<Value> extracted(expandedOperands.size());
148 for (const auto &tuple : llvm::enumerate(expandedOperands))
149 extracted[tuple.index()] =
150 vector::ExtractOp::create(builder, tuple.value(), offsets);
151
152 results[i] = compute(extracted);
153 }
154
155 // Stitch results together into one large vector.
156 Type resultEltType = cast<VectorType>(results[0].getType()).getElementType();
157 Type resultExpandedType = VectorType::get(expandedShape, resultEltType);
158 Value result = arith::ConstantOp::create(
159 builder, resultExpandedType, builder.getZeroAttr(resultExpandedType));
160
161 for (int64_t i = 0; i < maxIndex; ++i)
162 result = vector::InsertOp::create(builder, results[i], result,
163 delinearize(i, strides));
164
165 // Reshape back to the original vector shape.
166 return vector::ShapeCastOp::create(
167 builder, VectorType::get(inputShape, resultEltType), result);
168}
169
170//----------------------------------------------------------------------------//
171// Helper functions to create constants.
172//----------------------------------------------------------------------------//
173
174static Value boolCst(ImplicitLocOpBuilder &builder, bool value) {
175 return arith::ConstantOp::create(builder, builder.getBoolAttr(value));
176}
177
178static Value floatCst(ImplicitLocOpBuilder &builder, float value,
179 Type elementType) {
180 assert((elementType.isF16() || elementType.isF32()) &&
181 "x must be f16 or f32 type.");
182 return arith::ConstantOp::create(builder,
183 builder.getFloatAttr(elementType, value));
184}
185
186static Value f32Cst(ImplicitLocOpBuilder &builder, double value) {
187 return arith::ConstantOp::create(builder, builder.getF32FloatAttr(value));
188}
189
190static Value i32Cst(ImplicitLocOpBuilder &builder, int32_t value) {
191 return arith::ConstantOp::create(builder, builder.getI32IntegerAttr(value));
192}
193
194static Value f32FromBits(ImplicitLocOpBuilder &builder, uint32_t bits) {
195 Value i32Value = i32Cst(builder, static_cast<int32_t>(bits));
196 return arith::BitcastOp::create(builder, builder.getF32Type(), i32Value);
197}
198
199//----------------------------------------------------------------------------//
200// Helper functions to build math functions approximations.
201//----------------------------------------------------------------------------//
202
203// Return the minimum of the two values or NaN if value is NaN
204static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound) {
205 return arith::SelectOp::create(
206 builder,
207 arith::CmpFOp::create(builder, arith::CmpFPredicate::ULT, value, bound),
208 value, bound);
209}
210
211// Return the maximum of the two values or NaN if value is NaN
212static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound) {
213 return arith::SelectOp::create(
214 builder,
215 arith::CmpFOp::create(builder, arith::CmpFPredicate::UGT, value, bound),
216 value, bound);
217}
218
219// Return the clamped value or NaN if value is NaN
220static Value clamp(ImplicitLocOpBuilder &builder, Value value, Value lowerBound,
221 Value upperBound) {
222 return max(builder, min(builder, value, upperBound), lowerBound);
223}
224
225// Decomposes given floating point value `arg` into a normalized fraction and
226// an integral power of two (see std::frexp). Returned values have float type.
227static std::pair<Value, Value> frexp(ImplicitLocOpBuilder &builder, Value arg,
228 bool isPositive = false) {
229 assert(getElementTypeOrSelf(arg).isF32() && "arg must be f32 type");
230 std::optional<VectorShape> shape = vectorShape(arg);
231
232 auto bcast = [&](Value value) -> Value {
233 return broadcast(builder, value, shape);
234 };
235
236 auto i32 = builder.getIntegerType(32);
237 auto i32Vec = broadcast(i32, shape);
238 auto f32Vec = broadcast(builder.getF32Type(), shape);
239
240 Value cst126f = f32Cst(builder, 126.0f);
241 Value cstHalf = f32Cst(builder, 0.5f);
242 Value cstInvMantMask = f32FromBits(builder, ~0x7f800000u);
243
244 // Bitcast to i32 for bitwise operations.
245 Value i32Half = arith::BitcastOp::create(builder, i32, cstHalf);
246 Value i32InvMantMask = arith::BitcastOp::create(builder, i32, cstInvMantMask);
247 Value i32Arg = arith::BitcastOp::create(builder, i32Vec, arg);
248
249 // Compute normalized fraction.
250 Value tmp0 = arith::AndIOp::create(builder, i32Arg, bcast(i32InvMantMask));
251 Value tmp1 = arith::OrIOp::create(builder, tmp0, bcast(i32Half));
252 Value normalizedFraction = arith::BitcastOp::create(builder, f32Vec, tmp1);
253
254 // Compute exponent.
255 Value arg0 = isPositive ? arg : math::AbsFOp::create(builder, arg);
256 Value biasedExponentBits = arith::ShRUIOp::create(
257 builder, arith::BitcastOp::create(builder, i32Vec, arg0),
258 bcast(i32Cst(builder, 23)));
259 Value biasedExponent =
260 arith::SIToFPOp::create(builder, f32Vec, biasedExponentBits);
261 Value exponent =
262 arith::SubFOp::create(builder, biasedExponent, bcast(cst126f));
263
264 return {normalizedFraction, exponent};
265}
266
267// Computes exp2 for an i32 argument.
269 assert(getElementTypeOrSelf(arg).isInteger(32) && "arg must be i32 type");
270 std::optional<VectorShape> shape = vectorShape(arg);
271
272 auto bcast = [&](Value value) -> Value {
273 return broadcast(builder, value, shape);
274 };
275
276 auto f32Vec = broadcast(builder.getF32Type(), shape);
277 // The exponent of f32 located at 23-bit.
278 auto exponetBitLocation = bcast(i32Cst(builder, 23));
279 // Set the exponent bias to zero.
280 auto bias = bcast(i32Cst(builder, 127));
281
282 Value biasedArg = arith::AddIOp::create(builder, arg, bias);
283 Value exp2ValueInt =
284 arith::ShLIOp::create(builder, biasedArg, exponetBitLocation);
285 Value exp2ValueF32 = arith::BitcastOp::create(builder, f32Vec, exp2ValueInt);
286
287 return exp2ValueF32;
288}
289
290namespace {
291Value makePolynomialCalculation(ImplicitLocOpBuilder &builder,
292 llvm::ArrayRef<Value> coeffs, Value x) {
293 Type elementType = getElementTypeOrSelf(x);
294 assert((elementType.isF32() || elementType.isF16()) &&
295 "x must be f32 or f16 type");
296 std::optional<VectorShape> shape = vectorShape(x);
297
298 if (coeffs.empty())
299 return broadcast(builder, floatCst(builder, 0.0f, elementType), shape);
300
301 if (coeffs.size() == 1)
302 return coeffs[0];
303
304 Value res = math::FmaOp::create(builder, x, coeffs[coeffs.size() - 1],
305 coeffs[coeffs.size() - 2]);
306 for (auto i = ptrdiff_t(coeffs.size()) - 3; i >= 0; --i) {
307 res = math::FmaOp::create(builder, x, res, coeffs[i]);
308 }
309 return res;
310}
311} // namespace
312
313//----------------------------------------------------------------------------//
314// Helper function/pattern to insert casts for reusing F32 bit expansion.
315//----------------------------------------------------------------------------//
316
317template <typename T>
318LogicalResult insertCasts(Operation *op, PatternRewriter &rewriter) {
319 // Conservatively only allow where the operand and result types are exactly 1.
320 Type origType = op->getResultTypes().front();
321 for (Type t : llvm::drop_begin(op->getResultTypes()))
322 if (origType != t)
323 return rewriter.notifyMatchFailure(op, "required all types to match");
324 for (Type t : op->getOperandTypes())
325 if (origType != t)
326 return rewriter.notifyMatchFailure(op, "required all types to match");
327
328 // Skip if already F32 or larger than 32 bits.
329 if (getElementTypeOrSelf(origType).isF32() ||
330 getElementTypeOrSelf(origType).getIntOrFloatBitWidth() > 32)
331 return failure();
332
333 // Create F32 equivalent type.
334 Type newType;
335 if (auto shaped = dyn_cast<ShapedType>(origType)) {
336 newType = shaped.clone(rewriter.getF32Type());
337 } else if (isa<FloatType>(origType)) {
338 newType = rewriter.getF32Type();
339 } else {
340 return rewriter.notifyMatchFailure(op,
341 "unable to find F32 equivalent type");
342 }
343
344 Location loc = op->getLoc();
345 SmallVector<Value> operands;
346 for (auto operand : op->getOperands())
347 operands.push_back(arith::ExtFOp::create(rewriter, loc, TypeRange{newType},
348 ValueRange{operand},
349 arith::ExtFOp::Properties{}));
350 auto result = T::create(rewriter, loc, TypeRange{newType}, operands,
351 cast<T>(op).getProperties(),
352 op->getDiscardableAttrDictionary().getValue());
353 rewriter.replaceOpWithNewOp<arith::TruncFOp>(op, origType, result);
354 return success();
355}
356
357namespace {
358// Pattern to cast to F32 to reuse F32 expansion as fallback for single-result
359// op.
360// TODO: Consider revising to avoid adding multiple casts for a subgraph that is
361// all in lower precision. Currently this is only fallback support and performs
362// simplistic casting.
363template <typename T>
364struct ReuseF32Expansion : public OpRewritePattern<T> {
365public:
366 using OpRewritePattern<T>::OpRewritePattern;
367 LogicalResult matchAndRewrite(T op, PatternRewriter &rewriter) const final {
368 static_assert(
369 T::template hasTrait<mlir::OpTrait::SameOperandsAndResultType>(),
370 "requires same operands and result types");
371 return insertCasts<T>(op, rewriter);
372 }
373};
374} // namespace
375
376//----------------------------------------------------------------------------//
377// AtanOp approximation.
378//----------------------------------------------------------------------------//
379
380namespace {
381struct AtanApproximation : public OpRewritePattern<math::AtanOp> {
382public:
384
385 LogicalResult matchAndRewrite(math::AtanOp op,
386 PatternRewriter &rewriter) const final;
387};
388} // namespace
389
390LogicalResult
391AtanApproximation::matchAndRewrite(math::AtanOp op,
392 PatternRewriter &rewriter) const {
393 auto operand = op.getOperand();
394 if (!getElementTypeOrSelf(operand).isF32())
395 return rewriter.notifyMatchFailure(op, "unsupported operand type");
396
397 std::optional<VectorShape> shape = vectorShape(op.getOperand());
398
399 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
400 Value abs = math::AbsFOp::create(builder, operand);
401
402 auto one = broadcast(builder, f32Cst(builder, 1.0), shape);
403
404 // When 0.66 < x <= 2.41 we do (x-1) / (x+1):
405 auto twoThirds = broadcast(builder, f32Cst(builder, 0.66), shape);
406 Value cmp2 =
407 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, abs, twoThirds);
408 Value addone = arith::AddFOp::create(builder, abs, one);
409 Value subone = arith::SubFOp::create(builder, abs, one);
410 Value xnum = arith::SelectOp::create(builder, cmp2, subone, abs);
411 Value xden = arith::SelectOp::create(builder, cmp2, addone, one);
412
413 auto bcast = [&](Value value) -> Value {
414 return broadcast(builder, value, shape);
415 };
416
417 // Break into the <= 0.66 or > 2.41 we do x or 1/x:
418 auto tan3pio8 = bcast(f32Cst(builder, 2.41421356237309504880));
419 Value cmp1 =
420 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, abs, tan3pio8);
421 xnum = arith::SelectOp::create(builder, cmp1, one, xnum);
422 xden = arith::SelectOp::create(builder, cmp1, abs, xden);
423
424 Value x = arith::DivFOp::create(builder, xnum, xden);
425 Value xx = arith::MulFOp::create(builder, x, x);
426
427 // Perform the Taylor series approximation for atan over the range
428 // [0.0, 0.66].
429 auto p0 = bcast(f32Cst(builder, -8.750608600031904122785e-01));
430 auto p1 = bcast(f32Cst(builder, -1.615753718733365076637e+01));
431 auto p2 = bcast(f32Cst(builder, -7.500855792314704667340e+01));
432 auto p3 = bcast(f32Cst(builder, -1.228866684490136173410e+02));
433 auto p4 = bcast(f32Cst(builder, -6.485021904942025371773e+01));
434 auto q0 = bcast(f32Cst(builder, +2.485846490142306297962e+01));
435 auto q1 = bcast(f32Cst(builder, +1.650270098316988542046e+02));
436 auto q2 = bcast(f32Cst(builder, +4.328810604912902668951e+02));
437 auto q3 = bcast(f32Cst(builder, +4.853903996359136964868e+02));
438 auto q4 = bcast(f32Cst(builder, +1.945506571482613964425e+02));
439
440 // Apply the polynomial approximation for the numerator:
441 Value n = p0;
442 n = math::FmaOp::create(builder, xx, n, p1);
443 n = math::FmaOp::create(builder, xx, n, p2);
444 n = math::FmaOp::create(builder, xx, n, p3);
445 n = math::FmaOp::create(builder, xx, n, p4);
446 n = arith::MulFOp::create(builder, n, xx);
447
448 // Apply the polynomial approximation for the denominator:
449 Value d = q0;
450 d = math::FmaOp::create(builder, xx, d, q1);
451 d = math::FmaOp::create(builder, xx, d, q2);
452 d = math::FmaOp::create(builder, xx, d, q3);
453 d = math::FmaOp::create(builder, xx, d, q4);
454
455 // Compute approximation of theta:
456 Value ans0 = arith::DivFOp::create(builder, n, d);
457 ans0 = math::FmaOp::create(builder, ans0, x, x);
458
459 // Correct for the input mapping's angles:
460 Value mpi4 = bcast(f32Cst(builder, llvm::numbers::pi / 4));
461 Value ans2 = arith::AddFOp::create(builder, mpi4, ans0);
462 Value ans = arith::SelectOp::create(builder, cmp2, ans2, ans0);
463
464 Value mpi2 = bcast(f32Cst(builder, llvm::numbers::pi / 2));
465 Value ans1 = arith::SubFOp::create(builder, mpi2, ans0);
466 ans = arith::SelectOp::create(builder, cmp1, ans1, ans);
467
468 // Correct for signing of the input.
469 rewriter.replaceOpWithNewOp<math::CopySignOp>(op, ans, operand);
470 return success();
471}
472
473//----------------------------------------------------------------------------//
474// AtanOp approximation.
475//----------------------------------------------------------------------------//
476
477namespace {
478struct Atan2Approximation : public OpRewritePattern<math::Atan2Op> {
479public:
481
482 LogicalResult matchAndRewrite(math::Atan2Op op,
483 PatternRewriter &rewriter) const final;
484};
485} // namespace
486
487LogicalResult
488Atan2Approximation::matchAndRewrite(math::Atan2Op op,
489 PatternRewriter &rewriter) const {
490 auto y = op.getOperand(0);
491 auto x = op.getOperand(1);
492 if (!getElementTypeOrSelf(x).isF32())
493 return rewriter.notifyMatchFailure(op, "unsupported operand type");
494
495 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
496 std::optional<VectorShape> shape = vectorShape(op.getResult());
497
498 // Compute atan in the valid range.
499 auto div = arith::DivFOp::create(builder, y, x);
500 auto atan = math::AtanOp::create(builder, div);
501
502 // Determine what the atan would be for a 180 degree rotation.
503 auto zero = broadcast(builder, f32Cst(builder, 0.0f), shape);
504 auto pi = broadcast(builder, f32Cst(builder, 3.14159265359f), shape);
505 auto addPi = arith::AddFOp::create(builder, atan, pi);
506 auto subPi = arith::SubFOp::create(builder, atan, pi);
507 auto atanGt =
508 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, atan, zero);
509 auto flippedAtan = arith::SelectOp::create(builder, atanGt, subPi, addPi);
510
511 // Determine whether to directly use atan or use the 180 degree flip
512 auto xGt = arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, x, zero);
513 Value result = arith::SelectOp::create(builder, xGt, atan, flippedAtan);
514
515 // Handle x = 0, y > 0
516 Value xZero =
517 arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ, x, zero);
518 Value yGt =
519 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, y, zero);
520 Value isHalfPi = arith::AndIOp::create(builder, xZero, yGt);
521 auto halfPi = broadcast(builder, f32Cst(builder, 1.57079632679f), shape);
522 result = arith::SelectOp::create(builder, isHalfPi, halfPi, result);
523
524 // Handle x = 0, y < 0
525 Value yLt =
526 arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, y, zero);
527 Value isNegativeHalfPiPi = arith::AndIOp::create(builder, xZero, yLt);
528 auto negativeHalfPiPi =
529 broadcast(builder, f32Cst(builder, -1.57079632679f), shape);
530 result = arith::SelectOp::create(builder, isNegativeHalfPiPi,
531 negativeHalfPiPi, result);
532
533 // Handle x = 0, y = 0;
534 Value yZero =
535 arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ, y, zero);
536 Value isNan = arith::AndIOp::create(builder, xZero, yZero);
537 Value cstNan = broadcast(builder, f32FromBits(builder, 0x7fc00000), shape);
538 result = arith::SelectOp::create(builder, isNan, cstNan, result);
539
540 rewriter.replaceOp(op, result);
541 return success();
542}
543
544//----------------------------------------------------------------------------//
545// TanhOp approximation.
546//----------------------------------------------------------------------------//
547
548namespace {
549struct TanhApproximation : public OpRewritePattern<math::TanhOp> {
550public:
552
553 LogicalResult matchAndRewrite(math::TanhOp op,
554 PatternRewriter &rewriter) const final;
555};
556} // namespace
557
558LogicalResult
559TanhApproximation::matchAndRewrite(math::TanhOp op,
560 PatternRewriter &rewriter) const {
561 if (!getElementTypeOrSelf(op.getOperand()).isF32())
562 return rewriter.notifyMatchFailure(op, "unsupported operand type");
563
564 std::optional<VectorShape> shape = vectorShape(op.getOperand());
565
566 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
567 auto bcast = [&](Value value) -> Value {
568 return broadcast(builder, value, shape);
569 };
570
571 // Clamp operand into [plusClamp, minusClamp] range.
572 Value minusClamp = bcast(f32Cst(builder, -7.99881172180175781f));
573 Value plusClamp = bcast(f32Cst(builder, 7.99881172180175781f));
574 Value x = clamp(builder, op.getOperand(), minusClamp, plusClamp);
575
576 // Mask for tiny values that are approximated with `operand`.
577 Value tiny = bcast(f32Cst(builder, 0.0004f));
578 Value tinyMask = arith::CmpFOp::create(
579 builder, arith::CmpFPredicate::OLT,
580 math::AbsFOp::create(builder, op.getOperand()), tiny);
581
582 // The monomial coefficients of the numerator polynomial (odd).
583 Value alpha1 = bcast(f32Cst(builder, 4.89352455891786e-03f));
584 Value alpha3 = bcast(f32Cst(builder, 6.37261928875436e-04f));
585 Value alpha5 = bcast(f32Cst(builder, 1.48572235717979e-05f));
586 Value alpha7 = bcast(f32Cst(builder, 5.12229709037114e-08f));
587 Value alpha9 = bcast(f32Cst(builder, -8.60467152213735e-11f));
588 Value alpha11 = bcast(f32Cst(builder, 2.00018790482477e-13f));
589 Value alpha13 = bcast(f32Cst(builder, -2.76076847742355e-16f));
590
591 // The monomial coefficients of the denominator polynomial (even).
592 Value beta0 = bcast(f32Cst(builder, 4.89352518554385e-03f));
593 Value beta2 = bcast(f32Cst(builder, 2.26843463243900e-03f));
594 Value beta4 = bcast(f32Cst(builder, 1.18534705686654e-04f));
595 Value beta6 = bcast(f32Cst(builder, 1.19825839466702e-06f));
596
597 // Since the polynomials are odd/even, we need x^2.
598 Value x2 = arith::MulFOp::create(builder, x, x);
599
600 // Evaluate the numerator polynomial p.
601 Value p = math::FmaOp::create(builder, x2, alpha13, alpha11);
602 p = math::FmaOp::create(builder, x2, p, alpha9);
603 p = math::FmaOp::create(builder, x2, p, alpha7);
604 p = math::FmaOp::create(builder, x2, p, alpha5);
605 p = math::FmaOp::create(builder, x2, p, alpha3);
606 p = math::FmaOp::create(builder, x2, p, alpha1);
607 p = arith::MulFOp::create(builder, x, p);
608
609 // Evaluate the denominator polynomial q.
610 Value q = math::FmaOp::create(builder, x2, beta6, beta4);
611 q = math::FmaOp::create(builder, x2, q, beta2);
612 q = math::FmaOp::create(builder, x2, q, beta0);
613
614 // Divide the numerator by the denominator.
615 Value res = arith::SelectOp::create(builder, tinyMask, x,
616 arith::DivFOp::create(builder, p, q));
617
618 rewriter.replaceOp(op, res);
619
620 return success();
621}
622
623#define LN2_VALUE \
624 0.693147180559945309417232121458176568075500134360255254120680009493393621L
625#define LOG2E_VALUE \
626 1.442695040888963407359924681001892137426645954152985934135449406931109219L
627
628//----------------------------------------------------------------------------//
629// LogOp and Log2Op approximation.
630//----------------------------------------------------------------------------//
631
632namespace {
633template <typename Op>
634struct LogApproximationBase : public OpRewritePattern<Op> {
636
637 /// Base 2 if 'base2' is set; natural logarithm (base e) otherwise.
638 LogicalResult logMatchAndRewrite(Op op, PatternRewriter &rewriter,
639 bool base2) const;
640};
641} // namespace
642
643// This approximation comes from Julien Pommier's SSE math library.
644// Link: http://gruntthepeon.free.fr/ssemath
645template <typename Op>
646LogicalResult
647LogApproximationBase<Op>::logMatchAndRewrite(Op op, PatternRewriter &rewriter,
648 bool base2) const {
649 if (!getElementTypeOrSelf(op.getOperand()).isF32())
650 return rewriter.notifyMatchFailure(op, "unsupported operand type");
651
652 std::optional<VectorShape> shape = vectorShape(op.getOperand());
653
654 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
655 auto bcast = [&](Value value) -> Value {
656 return broadcast(builder, value, shape);
657 };
658
659 Value cstZero = bcast(f32Cst(builder, 0.0f));
660 Value cstOne = bcast(f32Cst(builder, 1.0f));
661 Value cstNegHalf = bcast(f32Cst(builder, -0.5f));
662
663 // The smallest non denormalized float number.
664 Value cstMinNormPos = bcast(f32FromBits(builder, 0x00800000u));
665 Value cstMinusInf = bcast(f32FromBits(builder, 0xff800000u));
666 Value cstPosInf = bcast(f32FromBits(builder, 0x7f800000u));
667 Value cstNan = bcast(f32FromBits(builder, 0x7fc00000));
668
669 // Polynomial coefficients.
670 Value cstCephesSQRTHF = bcast(f32Cst(builder, 0.707106781186547524f));
671 Value cstCephesLogP0 = bcast(f32Cst(builder, 7.0376836292E-2f));
672 Value cstCephesLogP1 = bcast(f32Cst(builder, -1.1514610310E-1f));
673 Value cstCephesLogP2 = bcast(f32Cst(builder, 1.1676998740E-1f));
674 Value cstCephesLogP3 = bcast(f32Cst(builder, -1.2420140846E-1f));
675 Value cstCephesLogP4 = bcast(f32Cst(builder, +1.4249322787E-1f));
676 Value cstCephesLogP5 = bcast(f32Cst(builder, -1.6668057665E-1f));
677 Value cstCephesLogP6 = bcast(f32Cst(builder, +2.0000714765E-1f));
678 Value cstCephesLogP7 = bcast(f32Cst(builder, -2.4999993993E-1f));
679 Value cstCephesLogP8 = bcast(f32Cst(builder, +3.3333331174E-1f));
680
681 Value x = op.getOperand();
682
683 // Truncate input values to the minimum positive normal.
684 x = max(builder, x, cstMinNormPos);
685
686 // Extract significant in the range [0.5,1) and exponent.
687 std::pair<Value, Value> pair = frexp(builder, x, /*isPositive=*/true);
688 x = pair.first;
689 Value e = pair.second;
690
691 // Shift the inputs from the range [0.5,1) to [sqrt(1/2), sqrt(2)) and shift
692 // by -1.0. The values are then centered around 0, which improves the
693 // stability of the polynomial evaluation:
694 //
695 // if( x < SQRTHF ) {
696 // e -= 1;
697 // x = x + x - 1.0;
698 // } else { x = x - 1.0; }
699 Value mask = arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, x,
700 cstCephesSQRTHF);
701 Value tmp = arith::SelectOp::create(builder, mask, x, cstZero);
702
703 x = arith::SubFOp::create(builder, x, cstOne);
704 e = arith::SubFOp::create(
705 builder, e, arith::SelectOp::create(builder, mask, cstOne, cstZero));
706 x = arith::AddFOp::create(builder, x, tmp);
707
708 Value x2 = arith::MulFOp::create(builder, x, x);
709 Value x3 = arith::MulFOp::create(builder, x2, x);
710
711 // Evaluate the polynomial approximant of degree 8 in three parts.
712 Value y0, y1, y2;
713 y0 = math::FmaOp::create(builder, cstCephesLogP0, x, cstCephesLogP1);
714 y1 = math::FmaOp::create(builder, cstCephesLogP3, x, cstCephesLogP4);
715 y2 = math::FmaOp::create(builder, cstCephesLogP6, x, cstCephesLogP7);
716 y0 = math::FmaOp::create(builder, y0, x, cstCephesLogP2);
717 y1 = math::FmaOp::create(builder, y1, x, cstCephesLogP5);
718 y2 = math::FmaOp::create(builder, y2, x, cstCephesLogP8);
719 y0 = math::FmaOp::create(builder, y0, x3, y1);
720 y0 = math::FmaOp::create(builder, y0, x3, y2);
721 y0 = arith::MulFOp::create(builder, y0, x3);
722
723 y0 = math::FmaOp::create(builder, cstNegHalf, x2, y0);
724 x = arith::AddFOp::create(builder, x, y0);
725
726 if (base2) {
727 Value cstLog2e = bcast(f32Cst(builder, static_cast<float>(LOG2E_VALUE)));
728 x = math::FmaOp::create(builder, x, cstLog2e, e);
729 } else {
730 Value cstLn2 = bcast(f32Cst(builder, static_cast<float>(LN2_VALUE)));
731 x = math::FmaOp::create(builder, e, cstLn2, x);
732 }
733
734 Value invalidMask = arith::CmpFOp::create(builder, arith::CmpFPredicate::ULT,
735 op.getOperand(), cstZero);
736 Value zeroMask = arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ,
737 op.getOperand(), cstZero);
738 Value posInfMask = arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ,
739 op.getOperand(), cstPosInf);
740
741 // Filter out invalid values:
742 // • x == 0 -> -INF
743 // • x < 0 -> NAN
744 // • x == +INF -> +INF
745 Value aproximation = arith::SelectOp::create(
746 builder, zeroMask, cstMinusInf,
747 arith::SelectOp::create(
748 builder, invalidMask, cstNan,
749 arith::SelectOp::create(builder, posInfMask, cstPosInf, x)));
750
751 rewriter.replaceOp(op, aproximation);
752
753 return success();
754}
755
756namespace {
757struct LogApproximation : public LogApproximationBase<math::LogOp> {
758 using LogApproximationBase::LogApproximationBase;
759
760 LogicalResult matchAndRewrite(math::LogOp op,
761 PatternRewriter &rewriter) const final {
762 return logMatchAndRewrite(op, rewriter, /*base2=*/false);
763 }
764};
765} // namespace
766
767namespace {
768struct Log2Approximation : public LogApproximationBase<math::Log2Op> {
769 using LogApproximationBase::LogApproximationBase;
770
771 LogicalResult matchAndRewrite(math::Log2Op op,
772 PatternRewriter &rewriter) const final {
773 return logMatchAndRewrite(op, rewriter, /*base2=*/true);
774 }
775};
776} // namespace
777
778//----------------------------------------------------------------------------//
779// Log1p approximation.
780//----------------------------------------------------------------------------//
781
782namespace {
783struct Log1pApproximation : public OpRewritePattern<math::Log1pOp> {
784public:
786
787 LogicalResult matchAndRewrite(math::Log1pOp op,
788 PatternRewriter &rewriter) const final;
789};
790} // namespace
791
792// Approximate log(1+x).
793LogicalResult
794Log1pApproximation::matchAndRewrite(math::Log1pOp op,
795 PatternRewriter &rewriter) const {
796 if (!getElementTypeOrSelf(op.getOperand()).isF32())
797 return rewriter.notifyMatchFailure(op, "unsupported operand type");
798
799 std::optional<VectorShape> shape = vectorShape(op.getOperand());
800
801 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
802 auto bcast = [&](Value value) -> Value {
803 return broadcast(builder, value, shape);
804 };
805
806 // Approximate log(1+x) using the following, due to W. Kahan:
807 // u = x + 1.0;
808 // if (u == 1.0 || u == inf) return x;
809 // return x * log(u) / (u - 1.0);
810 // ^^^^^^^^^^^^^^^^^^^^^^
811 // "logLarge" below.
812 Value cstOne = bcast(f32Cst(builder, 1.0f));
813 Value x = op.getOperand();
814 Value u = arith::AddFOp::create(builder, x, cstOne);
815 Value uSmall =
816 arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ, u, cstOne);
817 Value logU = math::LogOp::create(builder, u);
818 Value uInf =
819 arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ, u, logU);
820 Value logLarge = arith::MulFOp::create(
821 builder, x,
822 arith::DivFOp::create(builder, logU,
823 arith::SubFOp::create(builder, u, cstOne)));
824 Value approximation = arith::SelectOp::create(
825 builder, arith::OrIOp::create(builder, uSmall, uInf), x, logLarge);
826 rewriter.replaceOp(op, approximation);
827 return success();
828}
829
830//----------------------------------------------------------------------------//
831// Asin approximation.
832//----------------------------------------------------------------------------//
833
834// Approximates asin(x).
835// This approximation is based on the following stackoverflow post:
836// https://stackoverflow.com/a/42683455
837namespace {
838struct AsinPolynomialApproximation : public OpRewritePattern<math::AsinOp> {
839public:
841
842 LogicalResult matchAndRewrite(math::AsinOp op,
843 PatternRewriter &rewriter) const final;
844};
845} // namespace
846LogicalResult
847AsinPolynomialApproximation::matchAndRewrite(math::AsinOp op,
848 PatternRewriter &rewriter) const {
849 Value operand = op.getOperand();
850 Type elementType = getElementTypeOrSelf(operand);
851
852 if (!(elementType.isF32() || elementType.isF16()))
853 return rewriter.notifyMatchFailure(op,
854 "only f32 and f16 type is supported.");
855 std::optional<VectorShape> shape = vectorShape(operand);
856
857 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
858 auto bcast = [&](Value value) -> Value {
859 return broadcast(builder, value, shape);
860 };
861
862 auto fma = [&](Value a, Value b, Value c) -> Value {
863 return math::FmaOp::create(builder, a, b, c);
864 };
865
866 auto mul = [&](Value a, Value b) -> Value {
867 return arith::MulFOp::create(builder, a, b);
868 };
869
870 auto sub = [&](Value a, Value b) -> Value {
871 return arith::SubFOp::create(builder, a, b);
872 };
873
874 auto abs = [&](Value a) -> Value { return math::AbsFOp::create(builder, a); };
875
876 auto sqrt = [&](Value a) -> Value {
877 return math::SqrtOp::create(builder, a);
878 };
879
880 auto scopy = [&](Value a, Value b) -> Value {
881 return math::CopySignOp::create(builder, a, b);
882 };
883
884 auto sel = [&](Value a, Value b, Value c) -> Value {
885 return arith::SelectOp::create(builder, a, b, c);
886 };
887
888 Value abso = abs(operand);
889 Value aa = mul(operand, operand);
890 Value opp = sqrt(sub(bcast(floatCst(builder, 1.0, elementType)), aa));
891
892 Value gt = arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, aa,
893 bcast(floatCst(builder, 0.5, elementType)));
894
895 Value x = sel(gt, opp, abso);
896
897 // Asin(x) approximation for x = [-9/16, 9/16]:
898 Value s = mul(x, x);
899 Value q = mul(s, s);
900 Value r = bcast(floatCst(builder, 5.5579749017470502e-2, elementType));
901 Value t = bcast(floatCst(builder, -6.2027913464120114e-2, elementType));
902
903 r = fma(r, q, bcast(floatCst(builder, 5.4224464349245036e-2, elementType)));
904 t = fma(t, q, bcast(floatCst(builder, -1.1326992890324464e-2, elementType)));
905 r = fma(r, q, bcast(floatCst(builder, 1.5268872539397656e-2, elementType)));
906 t = fma(t, q, bcast(floatCst(builder, 1.0493798473372081e-2, elementType)));
907 r = fma(r, q, bcast(floatCst(builder, 1.4106045900607047e-2, elementType)));
908 t = fma(t, q, bcast(floatCst(builder, 1.7339776384962050e-2, elementType)));
909 r = fma(r, q, bcast(floatCst(builder, 2.2372961589651054e-2, elementType)));
910 t = fma(t, q, bcast(floatCst(builder, 3.0381912707941005e-2, elementType)));
911 r = fma(r, q, bcast(floatCst(builder, 4.4642857881094775e-2, elementType)));
912 t = fma(t, q, bcast(floatCst(builder, 7.4999999991367292e-2, elementType)));
913 r = fma(r, s, t);
914 r = fma(r, s, bcast(floatCst(builder, 1.6666666666670193e-1, elementType)));
915 t = mul(x, s);
916 r = fma(r, t, x);
917
918 Value rsub = sub(bcast(floatCst(builder, 1.57079632679, elementType)), r);
919 r = sel(gt, rsub, r);
920 r = scopy(r, operand);
921
922 rewriter.replaceOp(op, r);
923 return success();
924}
925
926//----------------------------------------------------------------------------//
927// Acos approximation.
928//----------------------------------------------------------------------------//
929
930// Approximates acos(x).
931// This approximation is based on the following stackoverflow post:
932// https://stackoverflow.com/a/42683455
933namespace {
934struct AcosPolynomialApproximation : public OpRewritePattern<math::AcosOp> {
935public:
937
938 LogicalResult matchAndRewrite(math::AcosOp op,
939 PatternRewriter &rewriter) const final;
940};
941} // namespace
942LogicalResult
943AcosPolynomialApproximation::matchAndRewrite(math::AcosOp op,
944 PatternRewriter &rewriter) const {
945 Value operand = op.getOperand();
946 Type elementType = getElementTypeOrSelf(operand);
947
948 if (!(elementType.isF32() || elementType.isF16()))
949 return rewriter.notifyMatchFailure(op,
950 "only f32 and f16 type is supported.");
951 std::optional<VectorShape> shape = vectorShape(operand);
952
953 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
954 auto bcast = [&](Value value) -> Value {
955 return broadcast(builder, value, shape);
956 };
957
958 auto fma = [&](Value a, Value b, Value c) -> Value {
959 return math::FmaOp::create(builder, a, b, c);
960 };
961
962 auto mul = [&](Value a, Value b) -> Value {
963 return arith::MulFOp::create(builder, a, b);
964 };
965
966 Value negOperand = arith::NegFOp::create(builder, operand);
967 Value zero = bcast(floatCst(builder, 0.0, elementType));
968 Value half = bcast(floatCst(builder, 0.5, elementType));
969 Value negOne = bcast(floatCst(builder, -1.0, elementType));
970 Value selR =
971 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, operand, zero);
972 Value r = arith::SelectOp::create(builder, selR, negOperand, operand);
973 Value chkConst = bcast(floatCst(builder, -0.5625, elementType));
974 Value firstPred =
975 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, r, chkConst);
976
977 Value trueVal =
978 fma(bcast(floatCst(builder, 9.3282184640716537e-1, elementType)),
979 bcast(floatCst(builder, 1.6839188885261840e+0, elementType)),
980 math::AsinOp::create(builder, r));
981
982 Value falseVal = math::SqrtOp::create(builder, fma(half, r, half));
983 falseVal = math::AsinOp::create(builder, falseVal);
984 falseVal = mul(bcast(floatCst(builder, 2.0, elementType)), falseVal);
985
986 r = arith::SelectOp::create(builder, firstPred, trueVal, falseVal);
987
988 // Check whether the operand lies in between [-1.0, 0.0).
989 Value greaterThanNegOne = arith::CmpFOp::create(
990 builder, arith::CmpFPredicate::OGE, operand, negOne);
991
992 Value lessThanZero =
993 arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, operand, zero);
994
995 Value betweenNegOneZero =
996 arith::AndIOp::create(builder, greaterThanNegOne, lessThanZero);
997
998 trueVal = fma(bcast(floatCst(builder, 1.8656436928143307e+0, elementType)),
999 bcast(floatCst(builder, 1.6839188885261840e+0, elementType)),
1000 arith::NegFOp::create(builder, r));
1001
1002 Value finalVal =
1003 arith::SelectOp::create(builder, betweenNegOneZero, trueVal, r);
1004
1005 rewriter.replaceOp(op, finalVal);
1006 return success();
1007}
1008
1009//----------------------------------------------------------------------------//
1010// Erf approximation.
1011//----------------------------------------------------------------------------//
1012
1013// Approximates erf(x) with
1014// a - P(x)/Q(x)
1015// where P and Q are polynomials of degree 4.
1016// Different coefficients are chosen based on the value of x.
1017// The approximation error is ~2.5e-07.
1018// Boost's minimax tool that utilizes the Remez method was used to find the
1019// coefficients.
1020LogicalResult
1022 PatternRewriter &rewriter) const {
1023 Value operand = op.getOperand();
1024 Type elementType = getElementTypeOrSelf(operand);
1025
1026 if (!(elementType.isF32() || elementType.isF16()))
1027 return rewriter.notifyMatchFailure(op,
1028 "only f32 and f16 type is supported.");
1029 std::optional<VectorShape> shape = vectorShape(operand);
1030
1031 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
1032 auto bcast = [&](Value value) -> Value {
1033 return broadcast(builder, value, shape);
1034 };
1035
1036 const int intervalsCount = 3;
1037 const int polyDegree = 4;
1038
1039 Value zero = bcast(floatCst(builder, 0, elementType));
1040 Value one = bcast(floatCst(builder, 1, elementType));
1041 Value pp[intervalsCount][polyDegree + 1];
1042 pp[0][0] = bcast(floatCst(builder, +0.00000000000000000e+00f, elementType));
1043 pp[0][1] = bcast(floatCst(builder, +1.12837916222975858e+00f, elementType));
1044 pp[0][2] = bcast(floatCst(builder, -5.23018562988006470e-01f, elementType));
1045 pp[0][3] = bcast(floatCst(builder, +2.09741709609267072e-01f, elementType));
1046 pp[0][4] = bcast(floatCst(builder, +2.58146801602987875e-02f, elementType));
1047 pp[1][0] = bcast(floatCst(builder, +0.00000000000000000e+00f, elementType));
1048 pp[1][1] = bcast(floatCst(builder, +1.12750687816789140e+00f, elementType));
1049 pp[1][2] = bcast(floatCst(builder, -3.64721408487825775e-01f, elementType));
1050 pp[1][3] = bcast(floatCst(builder, +1.18407396425136952e-01f, elementType));
1051 pp[1][4] = bcast(floatCst(builder, +3.70645533056476558e-02f, elementType));
1052 pp[2][0] = bcast(floatCst(builder, -3.30093071049483172e-03f, elementType));
1053 pp[2][1] = bcast(floatCst(builder, +3.51961938357697011e-03f, elementType));
1054 pp[2][2] = bcast(floatCst(builder, -1.41373622814988039e-03f, elementType));
1055 pp[2][3] = bcast(floatCst(builder, +2.53447094961941348e-04f, elementType));
1056 pp[2][4] = bcast(floatCst(builder, -1.71048029455037401e-05f, elementType));
1057
1058 Value qq[intervalsCount][polyDegree + 1];
1059 qq[0][0] = bcast(floatCst(builder, +1.000000000000000000e+00f, elementType));
1060 qq[0][1] = bcast(floatCst(builder, -4.635138185962547255e-01f, elementType));
1061 qq[0][2] = bcast(floatCst(builder, +5.192301327279782447e-01f, elementType));
1062 qq[0][3] = bcast(floatCst(builder, -1.318089722204810087e-01f, elementType));
1063 qq[0][4] = bcast(floatCst(builder, +7.397964654672315005e-02f, elementType));
1064 qq[1][0] = bcast(floatCst(builder, +1.00000000000000000e+00f, elementType));
1065 qq[1][1] = bcast(floatCst(builder, -3.27607011824493086e-01f, elementType));
1066 qq[1][2] = bcast(floatCst(builder, +4.48369090658821977e-01f, elementType));
1067 qq[1][3] = bcast(floatCst(builder, -8.83462621207857930e-02f, elementType));
1068 qq[1][4] = bcast(floatCst(builder, +5.72442770283176093e-02f, elementType));
1069 qq[2][0] = bcast(floatCst(builder, +1.00000000000000000e+00f, elementType));
1070 qq[2][1] = bcast(floatCst(builder, -2.06069165953913769e+00f, elementType));
1071 qq[2][2] = bcast(floatCst(builder, +1.62705939945477759e+00f, elementType));
1072 qq[2][3] = bcast(floatCst(builder, -5.83389859211130017e-01f, elementType));
1073 qq[2][4] = bcast(floatCst(builder, +8.21908939856640930e-02f, elementType));
1074
1075 Value offsets[intervalsCount];
1076 offsets[0] = bcast(floatCst(builder, 0.0f, elementType));
1077 offsets[1] = bcast(floatCst(builder, 0.0f, elementType));
1078 offsets[2] = bcast(floatCst(builder, 1.0f, elementType));
1079
1080 Value bounds[intervalsCount];
1081 bounds[0] = bcast(floatCst(builder, 0.8f, elementType));
1082 bounds[1] = bcast(floatCst(builder, 2.0f, elementType));
1083 bounds[2] = bcast(floatCst(builder, 3.75f, elementType));
1084
1085 Value isNegativeArg =
1086 arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, operand, zero);
1087 Value negArg = arith::NegFOp::create(builder, operand);
1088 Value x = arith::SelectOp::create(builder, isNegativeArg, negArg, operand);
1089
1090 Value offset = offsets[0];
1091 Value p[polyDegree + 1];
1092 Value q[polyDegree + 1];
1093 for (int i = 0; i <= polyDegree; ++i) {
1094 p[i] = pp[0][i];
1095 q[i] = qq[0][i];
1096 }
1097
1098 // TODO: maybe use vector stacking to reduce the number of selects.
1099 Value isLessThanBound[intervalsCount];
1100 for (int j = 0; j < intervalsCount - 1; ++j) {
1101 isLessThanBound[j] =
1102 arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, x, bounds[j]);
1103 for (int i = 0; i <= polyDegree; ++i) {
1104 p[i] = arith::SelectOp::create(builder, isLessThanBound[j], p[i],
1105 pp[j + 1][i]);
1106 q[i] = arith::SelectOp::create(builder, isLessThanBound[j], q[i],
1107 qq[j + 1][i]);
1108 }
1109 offset = arith::SelectOp::create(builder, isLessThanBound[j], offset,
1110 offsets[j + 1]);
1111 }
1112 isLessThanBound[intervalsCount - 1] = arith::CmpFOp::create(
1113 builder, arith::CmpFPredicate::ULT, x, bounds[intervalsCount - 1]);
1114
1115 Value pPoly = makePolynomialCalculation(builder, p, x);
1116 Value qPoly = makePolynomialCalculation(builder, q, x);
1117 Value rationalPoly = arith::DivFOp::create(builder, pPoly, qPoly);
1118 Value formula = arith::AddFOp::create(builder, offset, rationalPoly);
1119 formula = arith::SelectOp::create(
1120 builder, isLessThanBound[intervalsCount - 1], formula, one);
1121
1122 // erf is odd function: erf(x) = -erf(-x).
1123 Value negFormula = arith::NegFOp::create(builder, formula);
1124 Value res =
1125 arith::SelectOp::create(builder, isNegativeArg, negFormula, formula);
1126
1127 rewriter.replaceOp(op, res);
1128
1129 return success();
1130}
1131
1132// Approximates erfc(x) with p((x - 2) / (x + 2)), where p is a 9 degree
1133// polynomial.This approximation is based on the following stackoverflow post:
1134// https://stackoverflow.com/questions/35966695/vectorizable-implementation-of-complementary-error-function-erfcf
1135// The stackoverflow post is in turn based on:
1136// M. M. Shepherd and J. G. Laframboise, "Chebyshev Approximation of
1137// (1+2x)exp(x^2)erfc x in 0 <= x < INF", Mathematics of Computation, Vol. 36,
1138// No. 153, January 1981, pp. 249-253.
1139//
1140// Maximum error: 2.65 ulps
1141LogicalResult
1143 PatternRewriter &rewriter) const {
1144 Value x = op.getOperand();
1145 Type et = getElementTypeOrSelf(x);
1146
1147 if (!et.isF32())
1148 return rewriter.notifyMatchFailure(op, "only f32 type is supported.");
1149 std::optional<VectorShape> shape = vectorShape(x);
1150
1151 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
1152 auto bcast = [&](Value value) -> Value {
1153 return broadcast(builder, value, shape);
1154 };
1155
1156 Value trueValue = bcast(boolCst(builder, true));
1157 Value zero = bcast(floatCst(builder, 0.0f, et));
1158 Value one = bcast(floatCst(builder, 1.0f, et));
1159 Value onehalf = bcast(floatCst(builder, 0.5f, et));
1160 Value neg4 = bcast(floatCst(builder, -4.0f, et));
1161 Value neg2 = bcast(floatCst(builder, -2.0f, et));
1162 Value pos2 = bcast(floatCst(builder, 2.0f, et));
1163 Value posInf = bcast(floatCst(builder, INFINITY, et));
1164 Value clampVal = bcast(floatCst(builder, 10.0546875f, et));
1165
1166 Value a = math::AbsFOp::create(builder, x);
1167 Value p = arith::AddFOp::create(builder, a, pos2);
1168 Value r = arith::DivFOp::create(builder, one, p);
1169 Value q = math::FmaOp::create(builder, neg4, r, one);
1170 Value t = math::FmaOp::create(builder, arith::AddFOp::create(builder, q, one),
1171 neg2, a);
1172 Value e =
1173 math::FmaOp::create(builder, arith::NegFOp::create(builder, a), q, t);
1174 q = math::FmaOp::create(builder, r, e, q);
1175
1176 p = bcast(floatCst(builder, -0x1.a4a000p-12f, et)); // -4.01139259e-4
1177 Value c1 = bcast(floatCst(builder, -0x1.42a260p-10f, et)); // -1.23075210e-3
1178 p = math::FmaOp::create(builder, p, q, c1);
1179 Value c2 = bcast(floatCst(builder, 0x1.585714p-10f, et)); // 1.31355342e-3
1180 p = math::FmaOp::create(builder, p, q, c2);
1181 Value c3 = bcast(floatCst(builder, 0x1.1adcc4p-07f, et)); // 8.63227434e-3
1182 p = math::FmaOp::create(builder, p, q, c3);
1183 Value c4 = bcast(floatCst(builder, -0x1.081b82p-07f, et)); // -8.05991981e-3
1184 p = math::FmaOp::create(builder, p, q, c4);
1185 Value c5 = bcast(floatCst(builder, -0x1.bc0b6ap-05f, et)); // -5.42046614e-2
1186 p = math::FmaOp::create(builder, p, q, c5);
1187 Value c6 = bcast(floatCst(builder, 0x1.4ffc46p-03f, et)); // 1.64055392e-1
1188 p = math::FmaOp::create(builder, p, q, c6);
1189 Value c7 = bcast(floatCst(builder, -0x1.540840p-03f, et)); // -1.66031361e-1
1190 p = math::FmaOp::create(builder, p, q, c7);
1191 Value c8 = bcast(floatCst(builder, -0x1.7bf616p-04f, et)); // -9.27639827e-2
1192 p = math::FmaOp::create(builder, p, q, c8);
1193 Value c9 = bcast(floatCst(builder, 0x1.1ba03ap-02f, et)); // 2.76978403e-1
1194 p = math::FmaOp::create(builder, p, q, c9);
1195
1196 Value d = math::FmaOp::create(builder, pos2, a, one);
1197 r = arith::DivFOp::create(builder, one, d);
1198 q = math::FmaOp::create(builder, p, r, r);
1199 Value negfa = arith::NegFOp::create(builder, a);
1200 Value fmaqah = math::FmaOp::create(builder, q, negfa, onehalf);
1201 Value psubq = arith::SubFOp::create(builder, p, q);
1202 e = math::FmaOp::create(builder, fmaqah, pos2, psubq);
1203 r = math::FmaOp::create(builder, e, r, q);
1204
1205 Value s = arith::MulFOp::create(builder, a, a);
1206 e = math::ExpOp::create(builder, arith::NegFOp::create(builder, s));
1207
1208 t = math::FmaOp::create(builder, arith::NegFOp::create(builder, a), a, s);
1209 r = math::FmaOp::create(
1210 builder, r, e,
1211 arith::MulFOp::create(builder, arith::MulFOp::create(builder, r, e), t));
1212
1213 Value isNotLessThanInf = arith::XOrIOp::create(
1214 builder,
1215 arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, a, posInf),
1216 trueValue);
1217 r = arith::SelectOp::create(builder, isNotLessThanInf,
1218 arith::AddFOp::create(builder, x, x), r);
1219 Value isGreaterThanClamp =
1220 arith::CmpFOp::create(builder, arith::CmpFPredicate::OGT, a, clampVal);
1221 r = arith::SelectOp::create(builder, isGreaterThanClamp, zero, r);
1222
1223 Value isNegative =
1224 arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT, x, zero);
1225 r = arith::SelectOp::create(builder, isNegative,
1226 arith::SubFOp::create(builder, pos2, r), r);
1227
1228 rewriter.replaceOp(op, r);
1229 return success();
1230}
1231//----------------------------------------------------------------------------//
1232// Exp approximation.
1233//----------------------------------------------------------------------------//
1234
1235namespace {
1236
1237Value clampWithNormals(ImplicitLocOpBuilder &builder,
1238 const std::optional<VectorShape> shape, Value value,
1239 float lowerBound, float upperBound) {
1240 assert(!std::isnan(lowerBound));
1241 assert(!std::isnan(upperBound));
1242
1243 auto bcast = [&](Value value) -> Value {
1244 return broadcast(builder, value, shape);
1245 };
1246
1247 auto selectCmp = [&builder](auto pred, Value value, Value bound) {
1248 return arith::SelectOp::create(
1249 builder, arith::CmpFOp::create(builder, pred, value, bound), value,
1250 bound);
1251 };
1252
1253 // Note: prefer UGE/ULE vs. UGT/ULT, since they generate vmaxps/vminps vs.
1254 // vcmpleps+vmovaps on x86_64. The latter outcome is also obtained with
1255 // arith::{Max,Min}FOp.
1256 value = selectCmp(arith::CmpFPredicate::UGE, value,
1257 bcast(f32Cst(builder, lowerBound)));
1258 value = selectCmp(arith::CmpFPredicate::ULE, value,
1259 bcast(f32Cst(builder, upperBound)));
1260 return value;
1261}
1262
1263struct ExpApproximation : public OpRewritePattern<math::ExpOp> {
1264public:
1266
1267 LogicalResult matchAndRewrite(math::ExpOp op,
1268 PatternRewriter &rewriter) const final;
1269};
1270
1271LogicalResult
1272ExpApproximation::matchAndRewrite(math::ExpOp op,
1273 PatternRewriter &rewriter) const {
1274 auto shape = vectorShape(op.getOperand().getType());
1275 auto elementTy = getElementTypeOrSelf(op.getType());
1276 if (!elementTy.isF32())
1277 return rewriter.notifyMatchFailure(op, "unsupported operand type");
1278
1279 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
1280
1281 auto add = [&](Value a, Value b) -> Value {
1282 return arith::AddFOp::create(builder, a, b);
1283 };
1284 auto bcast = [&](Value value) -> Value {
1285 return broadcast(builder, value, shape);
1286 };
1287 auto floor = [&](Value a) { return math::FloorOp::create(builder, a); };
1288 auto fmla = [&](Value a, Value b, Value c) {
1289 return math::FmaOp::create(builder, a, b, c);
1290 };
1291 auto mul = [&](Value a, Value b) -> Value {
1292 return arith::MulFOp::create(builder, a, b);
1293 };
1294
1295 // Polynomial approximation from Cephes.
1296 //
1297 // To compute e^x, we re-express it as
1298 //
1299 // e^x = e^(a + b)
1300 // = e^(a + n log(2))
1301 // = e^a * 2^n.
1302 //
1303 // We choose n = round(x / log(2)), restricting the value of `a` to
1304 // (-log(2)/2, log(2)/2). We then use a polynomial to compute e^a. The
1305 // relative error between our approximation and the true value of e^a is less
1306 // than 2^-22.5 for all values of `a` within this range.
1307
1308 // Restrict input to a small range, including some values that evaluate to
1309 // +/- inf. Note that for our lower bound, we choose log(2^-126) instead of
1310 // log(F32_EPSILON). We do so because this routine always flushes denormal
1311 // floating points to 0. Therefore, we only need to worry about exponentiating
1312 // up to the smallest representable non-denormal floating point, which is
1313 // 2^-126.
1314
1315 // Constants.
1316 Value cstHalf = bcast(f32Cst(builder, 0.5f));
1317 Value cstOne = bcast(f32Cst(builder, 1.0f));
1318
1319 // 1/log(2)
1320 Value cstLog2ef = bcast(f32Cst(builder, 1.44269504088896341f));
1321
1322 Value cstExpC1 = bcast(f32Cst(builder, -0.693359375f));
1323 Value cstExpC2 = bcast(f32Cst(builder, 2.12194440e-4f));
1324 Value cstExpP0 = bcast(f32Cst(builder, 1.9875691500E-4f));
1325 Value cstExpP1 = bcast(f32Cst(builder, 1.3981999507E-3f));
1326 Value cstExpP2 = bcast(f32Cst(builder, 8.3334519073E-3f));
1327 Value cstExpP3 = bcast(f32Cst(builder, 4.1665795894E-2f));
1328 Value cstExpP4 = bcast(f32Cst(builder, 1.6666665459E-1f));
1329 Value cstExpP5 = bcast(f32Cst(builder, 5.0000001201E-1f));
1330
1331 // Our computations below aren't particularly sensitive to the exact choices
1332 // here, so we choose values a bit larger/smaller than
1333 //
1334 // log(F32_MAX) = 88.723...
1335 // log(2^-126) = -87.337...
1336 Value x = op.getOperand();
1337 x = clampWithNormals(builder, shape, x, -87.8f, 88.8f);
1338 Value n = floor(fmla(x, cstLog2ef, cstHalf));
1339
1340 // When we eventually do the multiplication in e^a * 2^n, we need to handle
1341 // the case when n > 127, the max fp32 exponent (so 2^n == inf) but e^a < 1
1342 // (so e^a * 2^n != inf). There's a similar problem for n < -126, the
1343 // smallest fp32 exponent.
1344 //
1345 // A straightforward solution would be to detect n out of range and split it
1346 // up, doing
1347 //
1348 // e^a * 2^n = e^a * 2^(n1 + n2)
1349 // = (2^n1 * e^a) * 2^n2.
1350 //
1351 // But it turns out this approach is quite slow, probably because it
1352 // manipulates subnormal values.
1353 //
1354 // The approach we use instead is to clamp n to [-127, 127]. Let n' be the
1355 // value of n clamped to [-127, 127]. In the case where n' = 127, `a` can grow
1356 // up to as large as 88.8 - 127 * log(2) which is about 0.7703. Even though
1357 // this value of `a` is outside our previously specified range, e^a will still
1358 // only have a relative error of approximately 2^-16 at worse. In practice
1359 // this seems to work well enough; it passes our exhaustive tests, breaking
1360 // only one result, and by one ulp (we return exp(88.7228394) = max-float but
1361 // we should return inf).
1362 //
1363 // In the case where n' = -127, the original input value of x is so small that
1364 // e^x, our final answer, is less than 2^-126. Since 2^-126 is the smallest
1365 // normal floating point, and since we flush denormals, we simply return 0. We
1366 // do this in a branchless way by observing that our code for constructing 2^n
1367 // produces 0 if n = -127.
1368 //
1369 // The proof that n' = -127 implies e^x < 2^-126 is as follows:
1370 //
1371 // n' = -127 implies n <= -127
1372 // implies round(x / log(2)) <= -127
1373 // implies x/log(2) < -126.5
1374 // implies x < -126.5 * log(2)
1375 // implies e^x < e^(-126.5 * log(2))
1376 // implies e^x < 2^-126.5 < 2^-126
1377 //
1378 // This proves that n' = -127 implies e^x < 2^-126.
1379 n = clampWithNormals(builder, shape, n, -127.0f, 127.0f);
1380
1381 // Computes x = x - n' * log(2), the value for `a`
1382 x = fmla(cstExpC1, n, x);
1383 x = fmla(cstExpC2, n, x);
1384
1385 // Polynomial to compute z = e^a, accurate for a in (-0.5, 0.5).
1386 Value z = fmla(x, cstExpP0, cstExpP1);
1387 z = fmla(z, x, cstExpP2);
1388 z = fmla(z, x, cstExpP3);
1389 z = fmla(z, x, cstExpP4);
1390 z = fmla(z, x, cstExpP5);
1391 z = fmla(z, mul(x, x), x);
1392 z = add(cstOne, z);
1393
1394 // Convert n' to an i32. This is safe because we clamped it above.
1395 auto i32Vec = broadcast(builder.getI32Type(), shape);
1396 Value nI32 = arith::FPToSIOp::create(builder, i32Vec, n);
1397
1398 // Creates the value 2^n' if -126 <= n' <= 127 and 0 if n' = -127.
1399 Value pow2 = exp2I32(builder, nI32);
1400
1401 // Return z * 2^n' if -126 <= n' <= 127 and 0 if n = -127.
1402 Value ret = mul(z, pow2);
1403
1404 rewriter.replaceOp(op, ret);
1405 return mlir::success();
1406}
1407
1408} // namespace
1409
1410//----------------------------------------------------------------------------//
1411// ExpM1 approximation.
1412//----------------------------------------------------------------------------//
1413
1414namespace {
1415
1416struct ExpM1Approximation : public OpRewritePattern<math::ExpM1Op> {
1417public:
1419
1420 LogicalResult matchAndRewrite(math::ExpM1Op op,
1421 PatternRewriter &rewriter) const final;
1422};
1423} // namespace
1424
1425LogicalResult
1426ExpM1Approximation::matchAndRewrite(math::ExpM1Op op,
1427 PatternRewriter &rewriter) const {
1428 if (!getElementTypeOrSelf(op.getOperand()).isF32())
1429 return rewriter.notifyMatchFailure(op, "unsupported operand type");
1430
1431 std::optional<VectorShape> shape = vectorShape(op.getOperand());
1432
1433 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
1434 auto bcast = [&](Value value) -> Value {
1435 return broadcast(builder, value, shape);
1436 };
1437
1438 // expm1(x) = exp(x) - 1 = u - 1.
1439 // We have to handle it carefully when x is near 0, i.e. u ~= 1,
1440 // and when the input is ~= -inf, i.e. u - 1 ~= -1.
1441 Value cstOne = bcast(f32Cst(builder, 1.0f));
1442 Value cstNegOne = bcast(f32Cst(builder, -1.0f));
1443 Value x = op.getOperand();
1444 Value u = math::ExpOp::create(builder, x);
1445 Value uEqOneOrNaN =
1446 arith::CmpFOp::create(builder, arith::CmpFPredicate::UEQ, u, cstOne);
1447 Value uMinusOne = arith::SubFOp::create(builder, u, cstOne);
1448 Value uMinusOneEqNegOne = arith::CmpFOp::create(
1449 builder, arith::CmpFPredicate::OEQ, uMinusOne, cstNegOne);
1450 // logU = log(u) ~= x
1451 Value logU = math::LogOp::create(builder, u);
1452
1453 // Detect exp(x) = +inf; written this way to avoid having to form +inf.
1454 Value isInf =
1455 arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ, logU, u);
1456
1457 // (u - 1) * (x / ~x)
1458 Value expm1 = arith::MulFOp::create(builder, uMinusOne,
1459 arith::DivFOp::create(builder, x, logU));
1460 expm1 = arith::SelectOp::create(builder, isInf, u, expm1);
1461 Value approximation = arith::SelectOp::create(
1462 builder, uEqOneOrNaN, x,
1463 arith::SelectOp::create(builder, uMinusOneEqNegOne, cstNegOne, expm1));
1464 rewriter.replaceOp(op, approximation);
1465 return success();
1466}
1467
1468//----------------------------------------------------------------------------//
1469// Sin and Cos approximation.
1470//----------------------------------------------------------------------------//
1471
1472namespace {
1473
1474template <bool isSine, typename OpTy>
1475struct SinAndCosApproximation : public OpRewritePattern<OpTy> {
1476public:
1477 using OpRewritePattern<OpTy>::OpRewritePattern;
1478
1479 LogicalResult matchAndRewrite(OpTy op, PatternRewriter &rewriter) const final;
1480};
1481} // namespace
1482
1483#define TWO_OVER_PI \
1484 0.6366197723675813430755350534900574481378385829618257949906693762L
1485#define PI_OVER_2 \
1486 1.5707963267948966192313216916397514420985846996875529104874722961L
1487
1488// Approximates sin(x) or cos(x) by finding the best approximation polynomial in
1489// the reduced range [0, pi/2] for both sin(x) and cos(x). Then given y in the
1490// reduced range sin(x) will be computed as sin(y), -sin(y), cos(y) or -cos(y).
1491template <bool isSine, typename OpTy>
1492LogicalResult SinAndCosApproximation<isSine, OpTy>::matchAndRewrite(
1493 OpTy op, PatternRewriter &rewriter) const {
1494 static_assert(
1495 llvm::is_one_of<OpTy, math::SinOp, math::CosOp>::value,
1496 "SinAndCosApproximation pattern expects math::SinOp or math::CosOp");
1497
1498 if (!getElementTypeOrSelf(op.getOperand()).isF32())
1499 return rewriter.notifyMatchFailure(op, "unsupported operand type");
1500
1501 std::optional<VectorShape> shape = vectorShape(op.getOperand());
1502
1503 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
1504 auto bcast = [&](Value value) -> Value {
1505 return broadcast(builder, value, shape);
1506 };
1507 auto mul = [&](Value a, Value b) -> Value {
1508 return arith::MulFOp::create(builder, a, b);
1509 };
1510 auto sub = [&](Value a, Value b) -> Value {
1511 return arith::SubFOp::create(builder, a, b);
1512 };
1513 auto floor = [&](Value a) { return math::FloorOp::create(builder, a); };
1514
1515 auto i32Vec = broadcast(builder.getI32Type(), shape);
1516 auto fPToSingedInteger = [&](Value a) -> Value {
1517 return arith::FPToSIOp::create(builder, i32Vec, a);
1518 };
1519
1520 auto modulo4 = [&](Value a) -> Value {
1521 return arith::AndIOp::create(builder, a, bcast(i32Cst(builder, 3)));
1522 };
1523
1524 auto isEqualTo = [&](Value a, Value b) -> Value {
1525 return arith::CmpIOp::create(builder, arith::CmpIPredicate::eq, a, b);
1526 };
1527
1528 auto isGreaterThan = [&](Value a, Value b) -> Value {
1529 return arith::CmpIOp::create(builder, arith::CmpIPredicate::sgt, a, b);
1530 };
1531
1532 auto select = [&](Value cond, Value t, Value f) -> Value {
1533 return arith::SelectOp::create(builder, cond, t, f);
1534 };
1535
1536 auto fmla = [&](Value a, Value b, Value c) {
1537 return math::FmaOp::create(builder, a, b, c);
1538 };
1539
1540 auto bitwiseOr = [&](Value a, Value b) {
1541 return arith::OrIOp::create(builder, a, b);
1542 };
1543
1544 Value twoOverPi = bcast(f32Cst(builder, (float)TWO_OVER_PI));
1545 Value piOverTwo = bcast(f32Cst(builder, (float)PI_OVER_2));
1546
1547 Value x = op.getOperand();
1548
1549 Value k = floor(mul(x, twoOverPi));
1550
1551 Value y = sub(x, mul(k, piOverTwo));
1552
1553 Value cstOne = bcast(f32Cst(builder, 1.0));
1554 Value cstNegativeOne = bcast(f32Cst(builder, -1.0));
1555
1556 Value cstSC2 = bcast(f32Cst(builder, -0.16666667163372039794921875f));
1557 Value cstSC4 = bcast(f32Cst(builder, 8.333347737789154052734375e-3f));
1558 Value cstSC6 = bcast(f32Cst(builder, -1.9842604524455964565277099609375e-4f));
1559 Value cstSC8 =
1560 bcast(f32Cst(builder, 2.760012648650445044040679931640625e-6f));
1561 Value cstSC10 =
1562 bcast(f32Cst(builder, -2.50293279435709337121807038784027099609375e-8f));
1563
1564 Value cstCC2 = bcast(f32Cst(builder, -0.5f));
1565 Value cstCC4 = bcast(f32Cst(builder, 4.166664183139801025390625e-2f));
1566 Value cstCC6 = bcast(f32Cst(builder, -1.388833043165504932403564453125e-3f));
1567 Value cstCC8 = bcast(f32Cst(builder, 2.47562347794882953166961669921875e-5f));
1568 Value cstCC10 =
1569 bcast(f32Cst(builder, -2.59630184018533327616751194000244140625e-7f));
1570
1571 Value kMod4 = modulo4(fPToSingedInteger(k));
1572
1573 Value kR0 = isEqualTo(kMod4, bcast(i32Cst(builder, 0)));
1574 Value kR1 = isEqualTo(kMod4, bcast(i32Cst(builder, 1)));
1575 Value kR2 = isEqualTo(kMod4, bcast(i32Cst(builder, 2)));
1576 Value kR3 = isEqualTo(kMod4, bcast(i32Cst(builder, 3)));
1577
1578 Value sinuseCos = isSine ? bitwiseOr(kR1, kR3) : bitwiseOr(kR0, kR2);
1579 Value negativeRange = isSine ? isGreaterThan(kMod4, bcast(i32Cst(builder, 1)))
1580 : bitwiseOr(kR1, kR2);
1581
1582 Value y2 = mul(y, y);
1583
1584 Value base = select(sinuseCos, cstOne, y);
1585 Value cstC2 = select(sinuseCos, cstCC2, cstSC2);
1586 Value cstC4 = select(sinuseCos, cstCC4, cstSC4);
1587 Value cstC6 = select(sinuseCos, cstCC6, cstSC6);
1588 Value cstC8 = select(sinuseCos, cstCC8, cstSC8);
1589 Value cstC10 = select(sinuseCos, cstCC10, cstSC10);
1590
1591 Value v1 = fmla(y2, cstC10, cstC8);
1592 Value v2 = fmla(y2, v1, cstC6);
1593 Value v3 = fmla(y2, v2, cstC4);
1594 Value v4 = fmla(y2, v3, cstC2);
1595 Value v5 = fmla(y2, v4, cstOne);
1596 Value v6 = mul(base, v5);
1597
1598 Value approximation = select(negativeRange, mul(cstNegativeOne, v6), v6);
1599
1600 rewriter.replaceOp(op, approximation);
1601
1602 return success();
1603}
1604
1605//----------------------------------------------------------------------------//
1606// Cbrt approximation.
1607//----------------------------------------------------------------------------//
1608
1609namespace {
1610struct CbrtApproximation : public OpRewritePattern<math::CbrtOp> {
1612
1613 LogicalResult matchAndRewrite(math::CbrtOp op,
1614 PatternRewriter &rewriter) const final;
1615};
1616} // namespace
1617
1618// Estimation of cube-root using an algorithm defined in
1619// Hacker's Delight 2nd Edition.
1620LogicalResult
1621CbrtApproximation::matchAndRewrite(math::CbrtOp op,
1622 PatternRewriter &rewriter) const {
1623 auto operand = op.getOperand();
1624 if (!getElementTypeOrSelf(operand).isF32())
1625 return rewriter.notifyMatchFailure(op, "unsupported operand type");
1626
1627 ImplicitLocOpBuilder b(op->getLoc(), rewriter);
1628 std::optional<VectorShape> shape = vectorShape(operand);
1629
1630 Type floatTy = getElementTypeOrSelf(operand.getType());
1631 Type intTy = b.getIntegerType(floatTy.getIntOrFloatBitWidth());
1632
1633 // Convert to vector types if necessary.
1634 floatTy = broadcast(floatTy, shape);
1635 intTy = broadcast(intTy, shape);
1636
1637 auto bconst = [&](TypedAttr attr) -> Value {
1638 Value value = arith::ConstantOp::create(b, attr);
1639 return broadcast(b, value, shape);
1640 };
1641
1642 // Declare the initial values:
1643 Value intTwo = bconst(b.getI32IntegerAttr(2));
1644 Value intFour = bconst(b.getI32IntegerAttr(4));
1645 Value intEight = bconst(b.getI32IntegerAttr(8));
1646 Value intMagic = bconst(b.getI32IntegerAttr(0x2a5137a0));
1647 Value fpThird = bconst(b.getF32FloatAttr(0.33333333f));
1648 Value fpTwo = bconst(b.getF32FloatAttr(2.0f));
1649 Value fpZero = bconst(b.getF32FloatAttr(0.0f));
1650
1651 // Compute an approximation of one third:
1652 // union {int ix; float x;};
1653 // x = x0;
1654 // ix = ix/4 + ix/16;
1655 Value absValue = math::AbsFOp::create(b, operand);
1656 Value intValue = arith::BitcastOp::create(b, intTy, absValue);
1657 Value divideBy4 = arith::ShRSIOp::create(b, intValue, intTwo);
1658 Value divideBy16 = arith::ShRSIOp::create(b, intValue, intFour);
1659 intValue = arith::AddIOp::create(b, divideBy4, divideBy16);
1660
1661 // ix = ix + ix/16;
1662 divideBy16 = arith::ShRSIOp::create(b, intValue, intFour);
1663 intValue = arith::AddIOp::create(b, intValue, divideBy16);
1664
1665 // ix = ix + ix/256;
1666 Value divideBy256 = arith::ShRSIOp::create(b, intValue, intEight);
1667 intValue = arith::AddIOp::create(b, intValue, divideBy256);
1668
1669 // ix = 0x2a5137a0 + ix;
1670 intValue = arith::AddIOp::create(b, intValue, intMagic);
1671
1672 // Perform one newtons step:
1673 // x = 0.33333333f*(2.0f*x + x0/(x*x));
1674 Value floatValue = arith::BitcastOp::create(b, floatTy, intValue);
1675 Value squared = arith::MulFOp::create(b, floatValue, floatValue);
1676 Value mulTwo = arith::MulFOp::create(b, floatValue, fpTwo);
1677 Value divSquared = arith::DivFOp::create(b, absValue, squared);
1678 floatValue = arith::AddFOp::create(b, mulTwo, divSquared);
1679 floatValue = arith::MulFOp::create(b, floatValue, fpThird);
1680
1681 // x = 0.33333333f*(2.0f*x + x0/(x*x));
1682 squared = arith::MulFOp::create(b, floatValue, floatValue);
1683 mulTwo = arith::MulFOp::create(b, floatValue, fpTwo);
1684 divSquared = arith::DivFOp::create(b, absValue, squared);
1685 floatValue = arith::AddFOp::create(b, mulTwo, divSquared);
1686 floatValue = arith::MulFOp::create(b, floatValue, fpThird);
1687
1688 // Check for zero and restore sign.
1689 Value isZero =
1690 arith::CmpFOp::create(b, arith::CmpFPredicate::OEQ, absValue, fpZero);
1691 floatValue = arith::SelectOp::create(b, isZero, fpZero, floatValue);
1692 floatValue = math::CopySignOp::create(b, floatValue, operand);
1693
1694 rewriter.replaceOp(op, floatValue);
1695 return success();
1696}
1697
1698//----------------------------------------------------------------------------//
1699// Rsqrt approximation.
1700//----------------------------------------------------------------------------//
1701
1702namespace {
1703struct RsqrtApproximation : public OpRewritePattern<math::RsqrtOp> {
1705
1706 LogicalResult matchAndRewrite(math::RsqrtOp op,
1707 PatternRewriter &rewriter) const final;
1708};
1709} // namespace
1710
1711LogicalResult
1712RsqrtApproximation::matchAndRewrite(math::RsqrtOp op,
1713 PatternRewriter &rewriter) const {
1714 if (!getElementTypeOrSelf(op.getOperand()).isF32())
1715 return rewriter.notifyMatchFailure(op, "unsupported operand type");
1716
1717 std::optional<VectorShape> shape = vectorShape(op.getOperand());
1718
1719 // Only support already-vectorized rsqrt's.
1720 if (!shape || shape->sizes.empty() || shape->sizes.back() % 8 != 0)
1721 return rewriter.notifyMatchFailure(op, "unsupported operand type");
1722
1723 ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
1724 auto bcast = [&](Value value) -> Value {
1725 return broadcast(builder, value, shape);
1726 };
1727
1728 Value cstPosInf = bcast(f32FromBits(builder, 0x7f800000u));
1729 Value cstOnePointFive = bcast(f32Cst(builder, 1.5f));
1730 Value cstNegHalf = bcast(f32Cst(builder, -0.5f));
1731 Value cstMinNormPos = bcast(f32FromBits(builder, 0x00800000u));
1732
1733 Value negHalf = arith::MulFOp::create(builder, op.getOperand(), cstNegHalf);
1734
1735 // Select only the inverse sqrt of positive normals (denormals are
1736 // flushed to zero).
1737 Value ltMinMask = arith::CmpFOp::create(builder, arith::CmpFPredicate::OLT,
1738 op.getOperand(), cstMinNormPos);
1739 Value infMask = arith::CmpFOp::create(builder, arith::CmpFPredicate::OEQ,
1740 op.getOperand(), cstPosInf);
1741 Value notNormalFiniteMask = arith::OrIOp::create(builder, ltMinMask, infMask);
1742
1743 // Compute an approximate result.
1744 Value yApprox = handleMultidimensionalVectors(
1745 builder, op->getOperands(), 8, [&builder](ValueRange operands) -> Value {
1746 return x86::avx::RsqrtOp::create(builder, operands);
1747 });
1748
1749 // Do a single step of Newton-Raphson iteration to improve the approximation.
1750 // This uses the formula y_{n+1} = y_n * (1.5 - y_n * (0.5 * x) * y_n).
1751 // It is essential to evaluate the inner term like this because forming
1752 // y_n^2 may over- or underflow.
1753 Value inner = arith::MulFOp::create(builder, negHalf, yApprox);
1754 Value fma = math::FmaOp::create(builder, yApprox, inner, cstOnePointFive);
1755 Value yNewton = arith::MulFOp::create(builder, yApprox, fma);
1756
1757 // Select the result of the Newton-Raphson step for positive normal arguments.
1758 // For other arguments, choose the output of the intrinsic. This will
1759 // return rsqrt(+inf) = 0, rsqrt(x) = NaN if x < 0, and rsqrt(x) = +inf if
1760 // x is zero or a positive denormalized float (equivalent to flushing positive
1761 // denormalized inputs to zero).
1762 Value res =
1763 arith::SelectOp::create(builder, notNormalFiniteMask, yApprox, yNewton);
1764 rewriter.replaceOp(op, res);
1765
1766 return success();
1767}
1768
1769//----------------------------------------------------------------------------//
1770
1772 RewritePatternSet &patterns) {
1773 patterns.add<TanhApproximation>(patterns.getContext());
1774}
1775
1780
1785
1786template <typename OpType>
1787static void
1789 llvm::function_ref<bool(StringRef)> predicate,
1790 PatternBenefit benefit) {
1791 if (predicate(OpType::getOperationName())) {
1792 patterns.add<ReuseF32Expansion<OpType>>(patterns.getContext(), benefit);
1793 }
1794}
1795
1797 RewritePatternSet &patterns, llvm::function_ref<bool(StringRef)> predicate,
1798 PatternBenefit benefit) {
1799 populateMathF32ExpansionPattern<math::AcosOp>(patterns, predicate, benefit);
1800 populateMathF32ExpansionPattern<math::AcoshOp>(patterns, predicate, benefit);
1801 populateMathF32ExpansionPattern<math::AsinOp>(patterns, predicate, benefit);
1802 populateMathF32ExpansionPattern<math::AsinhOp>(patterns, predicate, benefit);
1803 populateMathF32ExpansionPattern<math::AtanOp>(patterns, predicate, benefit);
1804 populateMathF32ExpansionPattern<math::Atan2Op>(patterns, predicate, benefit);
1805 populateMathF32ExpansionPattern<math::AtanhOp>(patterns, predicate, benefit);
1806 populateMathF32ExpansionPattern<math::CbrtOp>(patterns, predicate, benefit);
1807 populateMathF32ExpansionPattern<math::CosOp>(patterns, predicate, benefit);
1808 populateMathF32ExpansionPattern<math::CoshOp>(patterns, predicate, benefit);
1809 populateMathF32ExpansionPattern<math::ErfOp>(patterns, predicate, benefit);
1810 populateMathF32ExpansionPattern<math::ErfcOp>(patterns, predicate, benefit);
1811 populateMathF32ExpansionPattern<math::ExpOp>(patterns, predicate, benefit);
1812 populateMathF32ExpansionPattern<math::Exp2Op>(patterns, predicate, benefit);
1813 populateMathF32ExpansionPattern<math::ExpM1Op>(patterns, predicate, benefit);
1814 populateMathF32ExpansionPattern<math::LogOp>(patterns, predicate, benefit);
1815 populateMathF32ExpansionPattern<math::Log10Op>(patterns, predicate, benefit);
1816 populateMathF32ExpansionPattern<math::Log1pOp>(patterns, predicate, benefit);
1817 populateMathF32ExpansionPattern<math::Log2Op>(patterns, predicate, benefit);
1818 populateMathF32ExpansionPattern<math::PowFOp>(patterns, predicate, benefit);
1819 populateMathF32ExpansionPattern<math::RsqrtOp>(patterns, predicate, benefit);
1820 populateMathF32ExpansionPattern<math::SinOp>(patterns, predicate, benefit);
1821 populateMathF32ExpansionPattern<math::SinhOp>(patterns, predicate, benefit);
1822 populateMathF32ExpansionPattern<math::SqrtOp>(patterns, predicate, benefit);
1823 populateMathF32ExpansionPattern<math::TanOp>(patterns, predicate, benefit);
1824 populateMathF32ExpansionPattern<math::TanhOp>(patterns, predicate, benefit);
1825}
1826
1827template <typename OpType, typename PatternType>
1829 RewritePatternSet &patterns, llvm::function_ref<bool(StringRef)> predicate,
1830 PatternBenefit benefit) {
1831 if (predicate(OpType::getOperationName())) {
1832 patterns.add<PatternType>(patterns.getContext(), benefit);
1833 }
1834}
1835
1837 RewritePatternSet &patterns, llvm::function_ref<bool(StringRef)> predicate,
1838 PatternBenefit benefit) {
1840 AcosPolynomialApproximation>(
1841 patterns, predicate, benefit);
1843 AsinPolynomialApproximation>(
1844 patterns, predicate, benefit);
1846 patterns, predicate, benefit);
1848 patterns, predicate, benefit);
1850 patterns, predicate, benefit);
1852 CosOp, SinAndCosApproximation<false, math::CosOp>>(patterns, predicate,
1853 benefit);
1855 patterns, predicate, benefit);
1858 patterns, predicate, benefit);
1860 patterns, predicate, benefit);
1862 patterns, predicate, benefit);
1864 patterns, predicate, benefit);
1866 patterns, predicate, benefit);
1868 patterns, predicate, benefit);
1870 patterns, predicate, benefit);
1872 SinOp, SinAndCosApproximation<true, math::SinOp>>(patterns, predicate,
1873 benefit);
1875 patterns, predicate, benefit);
1876}
1877
1879 RewritePatternSet &patterns,
1881 mlir::populateMathF32ExpansionPatterns(patterns, [](StringRef name) -> bool {
1882 return llvm::is_contained(
1883 {math::AtanOp::getOperationName(), math::Atan2Op::getOperationName(),
1884 math::TanhOp::getOperationName(), math::LogOp::getOperationName(),
1885 math::Log2Op::getOperationName(), math::Log1pOp::getOperationName(),
1886 math::ErfOp::getOperationName(), math::ErfcOp::getOperationName(),
1887 math::ExpOp::getOperationName(), math::ExpM1Op::getOperationName(),
1888 math::CbrtOp::getOperationName(), math::SinOp::getOperationName(),
1889 math::CosOp::getOperationName()},
1890 name);
1891 });
1892
1894 patterns, [](StringRef name) -> bool {
1895 return llvm::is_contained(
1896 {math::AtanOp::getOperationName(),
1897 math::Atan2Op::getOperationName(),
1898 math::TanhOp::getOperationName(), math::LogOp::getOperationName(),
1899 math::Log2Op::getOperationName(),
1900 math::Log1pOp::getOperationName(), math::ErfOp::getOperationName(),
1901 math::ErfcOp::getOperationName(), math::AsinOp::getOperationName(),
1902 math::AcosOp::getOperationName(), math::ExpOp::getOperationName(),
1903 math::ExpM1Op::getOperationName(),
1904 math::CbrtOp::getOperationName(), math::SinOp::getOperationName(),
1905 math::CosOp::getOperationName()},
1906 name);
1907 });
1908
1909 if (options.enableAvx2) {
1910 auto predicateRsqrt = [](StringRef name) {
1911 return name == math::RsqrtOp::getOperationName();
1912 };
1913 mlir::populateMathF32ExpansionPatterns(patterns, predicateRsqrt);
1914 mlir::populateMathPolynomialApproximationPatterns(patterns, predicateRsqrt);
1915 }
1916}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static llvm::ManagedStatic< PassManagerOptions > options
#define LN2_VALUE
static Value exp2I32(ImplicitLocOpBuilder &builder, Value arg)
#define PI_OVER_2
static void populateMathF32ExpansionPattern(RewritePatternSet &patterns, llvm::function_ref< bool(StringRef)> predicate, PatternBenefit benefit)
#define TWO_OVER_PI
static Value boolCst(ImplicitLocOpBuilder &builder, bool value)
static Value floatCst(ImplicitLocOpBuilder &builder, float value, Type elementType)
static Value handleMultidimensionalVectors(ImplicitLocOpBuilder &builder, ValueRange operands, int64_t vectorWidth, llvm::function_ref< Value(ValueRange)> compute)
LogicalResult insertCasts(Operation *op, PatternRewriter &rewriter)
static Value clamp(ImplicitLocOpBuilder &builder, Value value, Value lowerBound, Value upperBound)
static std::pair< Value, Value > frexp(ImplicitLocOpBuilder &builder, Value arg, bool isPositive=false)
static std::optional< VectorShape > vectorShape(Type type)
static Value i32Cst(ImplicitLocOpBuilder &builder, int32_t value)
static Type broadcast(Type type, std::optional< VectorShape > shape)
#define LOG2E_VALUE
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value f32FromBits(ImplicitLocOpBuilder &builder, uint32_t bits)
static Value f32Cst(ImplicitLocOpBuilder &builder, double value)
static void populateMathPolynomialApproximationPattern(RewritePatternSet &patterns, llvm::function_ref< bool(StringRef)> predicate, PatternBenefit benefit)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value broadcast(Location loc, Value toBroadcast, unsigned numElements, const TypeConverter &typeConverter, ConversionPatternRewriter &rewriter)
Broadcasts the value to vector with numElements number of elements.
#define mul(a, b)
#define add(a, b)
#define div(a, b)
IntegerAttr getI32IntegerAttr(int32_t value)
Definition Builders.cpp:208
FloatType getF32Type()
Definition Builders.cpp:51
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
IntegerType getI32Type()
Definition Builders.cpp:71
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
FloatAttr getF32FloatAttr(float value)
Definition Builders.cpp:255
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
Location getLoc()
The source location the operation was defined or derived from.
This provides public APIs that all operations should have.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
operand_type_range getOperandTypes()
Definition Operation.h:422
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isF32() const
Definition Types.cpp:40
bool isF16() const
Definition Types.cpp:38
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
Type front()
Return first type in the range.
Definition TypeRange.h:164
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
DynamicAPInt floor(const Fraction &f)
Definition Fraction.h:77
Fraction abs(const Fraction &f)
Definition Fraction.h:107
Include the generated interface declarations.
void populatePolynomialApproximateErfcPattern(RewritePatternSet &patterns)
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
void populateMathF32ExpansionPatterns(RewritePatternSet &patterns, llvm::function_ref< bool(StringRef)> predicate, PatternBenefit=1)
void populatePolynomialApproximateErfPattern(RewritePatternSet &patterns)
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
void populatePolynomialApproximateTanhPattern(RewritePatternSet &patterns)
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.
int64_t computeMaxLinearIndex(ArrayRef< int64_t > basis)
Return the number of elements of basis (i.e.
void populateMathPolynomialApproximationPatterns(RewritePatternSet &patterns, llvm::function_ref< bool(StringRef)> predicate, PatternBenefit=1)
ArrayRef< int64_t > sizes
ArrayRef< bool > scalableFlags
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
Patterns must specify the root operation name they match against, and can also specify the benefit of...
LogicalResult matchAndRewrite(math::ErfOp op, PatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(math::ErfcOp op, PatternRewriter &rewriter) const final
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.