MLIR 24.0.0git
ExpandOps.cpp
Go to the documentation of this file.
1//===- ExpandOps.cpp - Pass to legalize Arith ops for LLVM lowering --===//
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
13#include "mlir/IR/Location.h"
16
17namespace mlir {
18namespace arith {
19#define GEN_PASS_DEF_ARITHEXPANDOPSPASS
20#include "mlir/Dialect/Arith/Transforms/Passes.h.inc"
21} // namespace arith
22} // namespace mlir
23
24using namespace mlir;
25
26/// Create an integer or index constant.
27static Value createConst(Location loc, Type type, int value,
28 PatternRewriter &rewriter) {
29 auto attr = rewriter.getIntegerAttr(getElementTypeOrSelf(type), value);
30 if (auto shapedTy = dyn_cast<ShapedType>(type)) {
31 return arith::ConstantOp::create(rewriter, loc,
32 DenseElementsAttr::get(shapedTy, attr));
33 }
34 return arith::ConstantOp::create(rewriter, loc, attr);
35}
36
37/// Create an integer constant from an APInt.
38static Value createAPIntConst(Location loc, Type type, const APInt &value,
39 PatternRewriter &rewriter) {
40 auto attr = IntegerAttr::get(getElementTypeOrSelf(type), value);
41 if (auto shapedTy = dyn_cast<ShapedType>(type)) {
42 return arith::ConstantOp::create(rewriter, loc,
43 DenseElementsAttr::get(shapedTy, attr));
44 }
45 return arith::ConstantOp::create(rewriter, loc, attr);
46}
47
48/// Create a float constant.
49static Value createFloatConst(Location loc, Type type, const APFloat &value,
50 PatternRewriter &rewriter) {
51 auto attr = rewriter.getFloatAttr(getElementTypeOrSelf(type), value);
52 if (auto shapedTy = dyn_cast<ShapedType>(type)) {
53 return arith::ConstantOp::create(rewriter, loc,
54 DenseElementsAttr::get(shapedTy, attr));
55 }
56
57 return arith::ConstantOp::create(rewriter, loc, attr);
58}
59
60/// Creates shapedType using shape from cloneFrom and base type from cloneTo
61static Type cloneToShapedType(Type cloneFrom, Type cloneTo) {
62 if (auto shapedTy = dyn_cast<ShapedType>(cloneFrom)) {
63 return shapedTy.clone(cloneTo);
64 }
65 return cloneTo;
66}
67
68namespace {
69
70/// Expands CeilDivUIOp (n, m) into
71/// n == 0 ? 0 : ((n-1) / m) + 1
72struct CeilDivUIOpConverter : public OpRewritePattern<arith::CeilDivUIOp> {
73 using Base::Base;
74 LogicalResult matchAndRewrite(arith::CeilDivUIOp op,
75 PatternRewriter &rewriter) const final {
76 Location loc = op.getLoc();
77 Value a = op.getLhs();
78 Value b = op.getRhs();
79 Value zero = createConst(loc, a.getType(), 0, rewriter);
80 Value compare =
81 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq, a, zero);
82 Value one = createConst(loc, a.getType(), 1, rewriter);
83 Value minusOne = arith::SubIOp::create(rewriter, loc, a, one);
84 Value quotient = arith::DivUIOp::create(rewriter, loc, minusOne, b);
85 Value plusOne = arith::AddIOp::create(rewriter, loc, quotient, one);
86 rewriter.replaceOpWithNewOp<arith::SelectOp>(op, compare, zero, plusOne);
87 return success();
88 }
89};
90
91/// Expands CeilDivSIOp (a, b) into
92/// z = a / b
93/// if (z * b != a && (a < 0) == (b < 0)) {
94/// return z + 1;
95/// } else {
96/// return z;
97/// }
98struct CeilDivSIOpConverter : public OpRewritePattern<arith::CeilDivSIOp> {
99 using Base::Base;
100 LogicalResult matchAndRewrite(arith::CeilDivSIOp op,
101 PatternRewriter &rewriter) const final {
102 Location loc = op.getLoc();
103 Type type = op.getType();
104 Value a = op.getLhs();
105 Value b = op.getRhs();
106
107 Value zero = createConst(loc, type, 0, rewriter);
108 Value one = createConst(loc, type, 1, rewriter);
109
110 Value quotient = arith::DivSIOp::create(rewriter, loc, a, b);
111 Value product = arith::MulIOp::create(rewriter, loc, quotient, b);
112 Value notEqualDivisor = arith::CmpIOp::create(
113 rewriter, loc, arith::CmpIPredicate::ne, a, product);
114
115 Value aNeg = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::slt,
116 a, zero);
117 Value bNeg = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::slt,
118 b, zero);
119
120 Value signEqual = arith::CmpIOp::create(
121 rewriter, loc, arith::CmpIPredicate::eq, aNeg, bNeg);
122 Value cond =
123 arith::AndIOp::create(rewriter, loc, notEqualDivisor, signEqual);
124
125 Value quotientPlusOne = arith::AddIOp::create(rewriter, loc, quotient, one);
126
127 rewriter.replaceOpWithNewOp<arith::SelectOp>(op, cond, quotientPlusOne,
128 quotient);
129 return success();
130 }
131};
132
133/// Expands FloorDivSIOp (x, y) into
134/// z = x / y
135/// if (z * y != x && (x < 0) != (y < 0)) {
136/// return z - 1;
137/// } else {
138/// return z;
139/// }
140struct FloorDivSIOpConverter : public OpRewritePattern<arith::FloorDivSIOp> {
141 using Base::Base;
142 LogicalResult matchAndRewrite(arith::FloorDivSIOp op,
143 PatternRewriter &rewriter) const final {
144 Location loc = op.getLoc();
145 Type type = op.getType();
146 Value a = op.getLhs();
147 Value b = op.getRhs();
148
149 Value quotient = arith::DivSIOp::create(rewriter, loc, a, b);
150 Value product = arith::MulIOp::create(rewriter, loc, quotient, b);
151 Value notEqualDivisor = arith::CmpIOp::create(
152 rewriter, loc, arith::CmpIPredicate::ne, a, product);
153 Value zero = createConst(loc, type, 0, rewriter);
154
155 Value aNeg = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::slt,
156 a, zero);
157 Value bNeg = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::slt,
158 b, zero);
159
160 Value signOpposite = arith::CmpIOp::create(
161 rewriter, loc, arith::CmpIPredicate::ne, aNeg, bNeg);
162 Value cond =
163 arith::AndIOp::create(rewriter, loc, notEqualDivisor, signOpposite);
164
165 Value minusOne = createConst(loc, type, -1, rewriter);
166 Value quotientMinusOne =
167 arith::AddIOp::create(rewriter, loc, quotient, minusOne);
168
169 rewriter.replaceOpWithNewOp<arith::SelectOp>(op, cond, quotientMinusOne,
170 quotient);
171 return success();
172 }
173};
174
175template <typename OpTy, arith::CmpIPredicate pred>
176struct MaxMinIOpConverter : public OpRewritePattern<OpTy> {
177public:
178 using OpRewritePattern<OpTy>::OpRewritePattern;
179
180 LogicalResult matchAndRewrite(OpTy op,
181 PatternRewriter &rewriter) const final {
182 Value lhs = op.getLhs();
183 Value rhs = op.getRhs();
184
185 Value cmp = arith::CmpIOp::create(rewriter, op.getLoc(), pred, lhs, rhs);
186 rewriter.replaceOpWithNewOp<arith::SelectOp>(op, cmp, lhs, rhs);
187 return success();
188 }
189};
190
191template <typename OpTy, arith::CmpFPredicate pred>
192struct MaximumMinimumFOpConverter : public OpRewritePattern<OpTy> {
193public:
194 using OpRewritePattern<OpTy>::OpRewritePattern;
195
196 LogicalResult matchAndRewrite(OpTy op,
197 PatternRewriter &rewriter) const final {
198 Value lhs = op.getLhs();
199 Value rhs = op.getRhs();
200
201 Location loc = op.getLoc();
202 // If any operand is NaN, 'cmp' will be true (and 'select' returns 'lhs').
203 static_assert(pred == arith::CmpFPredicate::UGT ||
204 pred == arith::CmpFPredicate::ULT,
205 "pred must be either UGT or ULT");
206 Value cmp = arith::CmpFOp::create(rewriter, loc, pred, lhs, rhs);
207 Value select = arith::SelectOp::create(rewriter, loc, cmp, lhs, rhs);
208
209 // Handle the case where rhs is NaN: 'isNaN(rhs) ? rhs : select'.
210 Value isNaN = arith::CmpFOp::create(rewriter, loc,
211 arith::CmpFPredicate::UNO, rhs, rhs);
212 rewriter.replaceOpWithNewOp<arith::SelectOp>(op, isNaN, rhs, select);
213 return success();
214 }
215};
216
217template <typename OpTy, arith::CmpFPredicate pred>
218struct MaxNumMinNumFOpConverter : public OpRewritePattern<OpTy> {
219public:
220 using OpRewritePattern<OpTy>::OpRewritePattern;
221
222 LogicalResult matchAndRewrite(OpTy op,
223 PatternRewriter &rewriter) const final {
224 Value lhs = op.getLhs();
225 Value rhs = op.getRhs();
226
227 Location loc = op.getLoc();
228 // If any operand is NaN, 'cmp' will be true (and 'select' returns 'lhs').
229 static_assert(pred == arith::CmpFPredicate::UGT ||
230 pred == arith::CmpFPredicate::ULT,
231 "pred must be either UGT or ULT");
232 Value cmp = arith::CmpFOp::create(rewriter, loc, pred, lhs, rhs);
233 Value select = arith::SelectOp::create(rewriter, loc, cmp, lhs, rhs);
234
235 // Handle the case where lhs is NaN: 'isNaN(lhs) ? rhs : select'.
236 Value isNaN = arith::CmpFOp::create(rewriter, loc,
237 arith::CmpFPredicate::UNO, lhs, lhs);
238 rewriter.replaceOpWithNewOp<arith::SelectOp>(op, isNaN, rhs, select);
239 return success();
240 }
241};
242
243struct BFloat16ExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
244 using Base::Base;
245 LogicalResult matchAndRewrite(arith::ExtFOp op,
246 PatternRewriter &rewriter) const final {
247 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
248 auto operand = op.getOperand();
249 Type operandTy = operand.getType();
250 Type resultTy = op.getType();
251 Type operandETy = getElementTypeOrSelf(operandTy);
252 Type resultETy = getElementTypeOrSelf(resultTy);
253
254 if (!operandETy.isBF16() || !resultETy.isF32()) {
255 return rewriter.notifyMatchFailure(op, "not a ext of bf16 to f32.");
256 }
257
258 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
259 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
260
261 Value bitcast = arith::BitcastOp::create(b, i16Ty, operand);
262 Value exti = arith::ExtUIOp::create(b, i32Ty, bitcast);
263
264 Value c16 = createConst(op.getLoc(), i32Ty, 16, rewriter);
265 Value shl = arith::ShLIOp::create(b, exti, c16);
266 Value result = arith::BitcastOp::create(b, resultTy, shl);
267
268 rewriter.replaceOp(op, result);
269 return success();
270 }
271};
272
273struct BFloat16TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
274 using Base::Base;
275 LogicalResult matchAndRewrite(arith::TruncFOp op,
276 PatternRewriter &rewriter) const final {
277 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
278 auto operand = op.getOperand();
279 Type operandTy = operand.getType();
280 Type resultTy = op.getType();
281 Type operandETy = getElementTypeOrSelf(operandTy);
282 Type resultETy = getElementTypeOrSelf(resultTy);
283
284 if (!operandETy.isF32() || !resultETy.isBF16()) {
285 return rewriter.notifyMatchFailure(op, "not a trunc of f32 to bf16.");
286 }
287
288 if (op.getRoundingmodeAttr()) {
289 return rewriter.notifyMatchFailure(
290 op, "only applicable to default rounding mode.");
291 }
292
293 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
294 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
295
296 // Algorithm borrowed from this excellent code:
297 // https://github.com/pytorch/pytorch/blob/e1502c0cdbfd17548c612f25d5a65b1e4b86224d/c10/util/BFloat16.h#L60-L79
298 // There is a magic idea there, to let the addition of the rounding_bias to
299 // the mantissa simply overflow into the exponent bits. It's a bit of an
300 // aggressive, obfuscating optimization, but it is well-tested code, and it
301 // results in more concise and efficient IR.
302 // The case of NaN is handled separately (see isNaN and the final select).
303 // The case of infinities is NOT handled separately, which deserves an
304 // explanation. As the encoding of infinities has zero mantissa, the
305 // rounding-bias addition never carries into the exponent so that just gets
306 // truncated away, and as bfloat16 and float32 have the same number of
307 // exponent bits, that simple truncation is the desired outcome for
308 // infinities.
309 Value isNan =
310 arith::CmpFOp::create(b, arith::CmpFPredicate::UNE, operand, operand);
311 // Constant used to make the rounding bias.
312 Value c7FFF = createConst(op.getLoc(), i32Ty, 0x7fff, rewriter);
313 // Constant used to generate a quiet NaN.
314 Value c7FC0I16 = createConst(op.getLoc(), i16Ty, 0x7fc0, rewriter);
315 // Small constants used to address bits.
316 Value c16 = createConst(op.getLoc(), i32Ty, 16, rewriter);
317 Value c1 = createConst(op.getLoc(), i32Ty, 1, rewriter);
318 // Reinterpret the input f32 value as bits.
319 Value bitcast = arith::BitcastOp::create(b, i32Ty, operand);
320 // Read bit 16 as a value in {0,1}.
321 Value bit16 =
322 arith::AndIOp::create(b, arith::ShRUIOp::create(b, bitcast, c16), c1);
323 // Determine the rounding bias to add as either 0x7fff or 0x8000 depending
324 // on bit 16, implementing the tie-breaking "to nearest even".
325 Value roundingBias = arith::AddIOp::create(b, bit16, c7FFF);
326 // Add the rounding bias. Generally we want this to be added to the
327 // mantissa, but nothing prevents this to from carrying into the exponent
328 // bits, which would feel like a bug, but this is the magic trick here:
329 // when that happens, the mantissa gets reset to zero and the exponent
330 // gets incremented by the carry... which is actually exactly what we
331 // want.
332 Value biased = arith::AddIOp::create(b, bitcast, roundingBias);
333 // Now that the rounding-bias has been added, truncating the low bits
334 // yields the correctly rounded result.
335 Value biasedAndShifted = arith::ShRUIOp::create(b, biased, c16);
336 Value normalCaseResultI16 =
337 arith::TruncIOp::create(b, i16Ty, biasedAndShifted);
338 // Select either the above-computed result, or a quiet NaN constant
339 // if the input was NaN.
340 Value select =
341 arith::SelectOp::create(b, isNan, c7FC0I16, normalCaseResultI16);
342 Value result = arith::BitcastOp::create(b, resultTy, select);
343 rewriter.replaceOp(op, result);
344 return success();
345 }
346};
347
348/// In this implementation of extf we take advantage of some key patterns we
349/// notice between the binary representation of an F4E2M1 value and its
350/// corresponding value in F32.
351///
352/// Note: x is sign bit
353/// | Binary | F4E2M1 | f32[23:32]
354/// | x000 | 0.0 | x000 0000 00
355/// | x001 | 0.5 | x011 1111 00
356/// | x010 | 1.0 | x011 1111 10
357/// | x011 | 1.5 | x011 1111 11
358/// | x100 | 2.0 | x010 0000 00
359/// | x101 | 3.0 | x010 0000 01
360/// | x110 | 4.0 | x010 0000 10
361/// | x111 | 6.0 | x010 0000 11
362///
363/// 1) There are only two versions of bits [25:31] in the f32 result
364/// F4E2M1 bits[2:3] decide whether:
365/// - F32 bits[25:31] = 0011 1111
366/// - F32 bits[25:31] = 0010 0000
367/// Exception is zero where
368/// - F32 bits[25:31] = 0000 0000
369///
370/// 2) F4E2M1 bits[1:2] = F32 bits[23:24]
371/// Exception is 0.5 where
372/// - F4E2M1 bits[1:2] = 01, F32 bits[23:24] = 00
373///
374/// 3) F4E2M1 bits[4] = F32 bits[32] (sign bits are equal)
375///
376/// 4) F32 bits[1:22] = 0
377struct F4E2M1ExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
378 using Base::Base;
379 LogicalResult matchAndRewrite(arith::ExtFOp op,
380 PatternRewriter &rewriter) const final {
381 Location loc = op.getLoc();
382 ImplicitLocOpBuilder b(loc, rewriter);
383 Value operand = op.getOperand();
384 Type operandTy = operand.getType();
385 Type resultTy = op.getType();
386 Type operandETy = getElementTypeOrSelf(operandTy);
387 Type resultETy = getElementTypeOrSelf(resultTy);
388
389 if (!isa<Float4E2M1FNType>(operandETy))
390 return rewriter.notifyMatchFailure(op, "not a ext of F4E2M1FN");
391
392 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
393 Type i4Ty = cloneToShapedType(operandTy, b.getI4Type());
394 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
395 Value i4Bits = arith::BitcastOp::create(b, i4Ty, operand);
396
397 Value c0x0 = createConst(loc, i4Ty, 0x0, rewriter);
398 Value c0x1 = createConst(loc, i4Ty, 0x1, rewriter);
399 Value c0x2 = createConst(loc, i4Ty, 0x2, rewriter);
400 Value c0x4 = createConst(loc, i4Ty, 0x4, rewriter);
401 Value c0x7 = createConst(loc, i4Ty, 0x7, rewriter);
402
403 Value i4BitsNoSign = arith::AndIOp::create(b, i4Bits, c0x7);
404
405 // Set last Exponent bit and Mantissa.
406 Value c0x00000014 = createConst(loc, i32Ty, 0x14, rewriter);
407 Value bits1To24 = arith::ShLIOp::create(b, i4BitsNoSign, c0x2);
408 Value isHalf =
409 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, i4BitsNoSign, c0x1);
410 bits1To24 = arith::SelectOp::create(b, isHalf, c0x0, bits1To24);
411 bits1To24 = arith::ExtUIOp::create(b, i32Ty, bits1To24);
412 bits1To24 = arith::ShLIOp::create(b, bits1To24, c0x00000014);
413
414 // Set first 7 bits of Exponent.
415 Value zeroExpBits = createConst(loc, i32Ty, 0x00000000, rewriter);
416 Value highExpBits = createConst(loc, i32Ty, 0x40000000, rewriter);
417 Value lowExpBits = createConst(loc, i32Ty, 0x3f000000, rewriter);
418 Value useLargerExp =
419 arith::CmpIOp::create(b, arith::CmpIPredicate::uge, i4BitsNoSign, c0x4);
420 Value bits25To31 =
421 arith::SelectOp::create(b, useLargerExp, highExpBits, lowExpBits);
422 Value zeroExp =
423 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, i4BitsNoSign, c0x0);
424 bits25To31 = arith::SelectOp::create(b, zeroExp, zeroExpBits, bits25To31);
425
426 // Set sign.
427 Value c0x80000000 = createConst(loc, i32Ty, 0x80000000, rewriter);
428 Value c0x8 = createConst(loc, i4Ty, 0x8, rewriter);
429 Value negative =
430 arith::CmpIOp::create(b, arith::CmpIPredicate::uge, i4Bits, c0x8);
431 Value bit32 =
432 arith::SelectOp::create(b, negative, c0x80000000, zeroExpBits);
433
434 // Add segments together.
435 Value bits1To31 = arith::AddIOp::create(b, bits1To24, bits25To31);
436 Value bits1To32 = arith::AddIOp::create(b, bits1To31, bit32);
437 Value result = arith::BitcastOp::create(b, f32Ty, bits1To32);
438 if (!isa<Float32Type>(resultETy))
439 result = arith::TruncFOp::create(b, resultTy, result);
440
441 rewriter.replaceOp(op, result);
442 return success();
443 }
444};
445
446struct F8E8M0ExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
447 using Base::Base;
448 LogicalResult matchAndRewrite(arith::ExtFOp op,
449 PatternRewriter &rewriter) const final {
450 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
451 Value operand = op.getOperand();
452 Type operandTy = operand.getType();
453 Type resultTy = op.getType();
454 Type operandETy = getElementTypeOrSelf(operandTy);
455 Type resultETy = getElementTypeOrSelf(resultTy);
456
457 if (!llvm::isa<Float8E8M0FNUType>(operandETy)) {
458 return rewriter.notifyMatchFailure(op, "not a ext of F8E8M0FNU");
459 }
460
461 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
462 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
463 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
464
465 Value bitcast = arith::BitcastOp::create(b, i8Ty, operand);
466 Value cF32MantissaWidth = createConst(op->getLoc(), i32Ty, 23, rewriter);
467 Value exti = arith::ExtUIOp::create(b, i32Ty, bitcast);
468 Value f32Bits = arith::ShLIOp::create(b, exti, cF32MantissaWidth);
469
470 // If FastMathFlag allows no NaN checks, skip it
471 auto fastMath = op.getFastmathAttr();
472 bool NoNaN = fastMath
473 ? (fastMath.getValue() & arith::FastMathFlags::nnan) ==
474 arith::FastMathFlags::nnan
475 : false;
476 if (!NoNaN) {
477 Value cF8NaN = createConst(op.getLoc(), i8Ty, 0xff, rewriter);
478 Value cF32NaN = createConst(op.getLoc(), i32Ty, 0xffffffff, rewriter);
479 Value isNan =
480 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, bitcast, cF8NaN);
481 // select for NaNs
482 f32Bits = arith::SelectOp::create(b, isNan, cF32NaN, f32Bits);
483 }
484
485 Value result = arith::BitcastOp::create(b, f32Ty, f32Bits);
486 if (resultETy.getIntOrFloatBitWidth() < 32) {
487 result = arith::TruncFOp::create(b, resultTy, result, nullptr,
488 op.getFastmathAttr());
489 } else if (resultETy.getIntOrFloatBitWidth() > 32) {
490 result = arith::ExtFOp::create(b, resultTy, result, op.getFastmathAttr());
491 }
492 rewriter.replaceOp(op, result);
493 return success();
494 }
495};
496
497/// Conversion from F32 to F4E2M1 according to the OCP Spec:
498/// www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
499///
500/// The spec requiers us to perform Round to Nearest, Ties to Even.
501///
502/// This means that after rounding, we should break ties by choosing the option
503/// which results in a mantissa of 0 in the least significant digit.
504///
505/// Table of representable values in F4E2M1:
506///
507/// Note: x is sign bit
508/// | Binary | F4E2M1 | F32[23:32]
509/// | x000 | 0.0 | x000 0000 00
510/// | x001 | 0.5 | x011 1111 00
511/// | x010 | 1.0 | x011 1111 10
512/// | x011 | 1.5 | x011 1111 11
513/// | x100 | 2.0 | x010 0000 00
514/// | x101 | 3.0 | x010 0000 01
515/// | x110 | 4.0 | x010 0000 10
516/// | x111 | 6.0 | x010 0000 11
517///
518/// Conversion procedure:
519/// Step 1: Clamp to representable bounds.
520/// Step 2: Convert exponent by adjusting bias.
521/// Step 3: Set mantissa to first bit.
522/// Step 4: Special consideration for subnormal and zero exponent.
523/// Step 5: Round up if necessary, if mantissa[1:] greater than 1000000 or
524/// subnormal.
525struct F4E2M1TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
526 using Base::Base;
527 LogicalResult matchAndRewrite(arith::TruncFOp op,
528 PatternRewriter &rewriter) const final {
529 Location loc = op.getLoc();
530 ImplicitLocOpBuilder b(loc, rewriter);
531 Value operand = op.getOperand();
532 Type operandTy = operand.getType();
533 Type resultTy = op.getType();
534 Type operandETy = getElementTypeOrSelf(operandTy);
535 Type resultETy = getElementTypeOrSelf(resultTy);
536
537 Type i4Ty = cloneToShapedType(operandTy, b.getI4Type());
538 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
539 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
540 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
541
542 if (!isa<Float4E2M1FNType>(resultETy))
543 return rewriter.notifyMatchFailure(op, "not a trunc of F4E2M1FN");
544 if (!isa<Float32Type>(operandETy))
545 operand =
546 arith::ExtFOp::create(b, f32Ty, operand, arith::FastMathFlagsAttr{});
547
548 Value c0x1 = createConst(loc, i4Ty, 1, rewriter);
549 Value c0x3 = createConst(loc, i4Ty, 3, rewriter);
550 Value c0x00000016 = createConst(loc, i32Ty, 22, rewriter);
551 Value c0x00 = createConst(loc, i8Ty, 0x00, rewriter);
552 Value c0xff = createConst(loc, i8Ty, 0xff, rewriter);
553 Value zeroExpBits = createConst(loc, i32Ty, 0, rewriter);
554
555 // Step 0: Clamp to bounds.
556 Value cHigherBound = createFloatConst(loc, f32Ty, APFloat(6.0f), rewriter);
557 Value cLowerBound = createFloatConst(loc, f32Ty, APFloat(-6.0f), rewriter);
558 Value operandClamped = arith::MinNumFOp::create(b, cHigherBound, operand);
559 operandClamped = arith::MaxNumFOp::create(b, cLowerBound, operandClamped);
560 Value f32Bits = arith::BitcastOp::create(b, i32Ty, operandClamped);
561
562 // Step 1: Set sign bit.
563 Value cF32ExpManWidth = createConst(loc, i32Ty, 31, rewriter); // 23
564 Value f32Sign = arith::ShRUIOp::create(b, f32Bits, cF32ExpManWidth);
565 Value f4Sign = arith::TruncIOp::create(b, i4Ty, f32Sign);
566 Value f4Bits = arith::ShLIOp::create(b, f4Sign, c0x3);
567
568 // Step 2: Convert exponent by adjusting bias.
569 Value biasAdjustment = createConst(loc, i32Ty, 0x7e, rewriter);
570 Value cF4MantissaWidth = c0x1; // 1
571 Value cF32MantissaWidth = createConst(loc, i32Ty, 23, rewriter); // 23
572 Value f32SignExp = arith::ShRUIOp::create(b, f32Bits, cF32MantissaWidth);
573 Value biasAdjustedSignExp =
574 arith::SubIOp::create(b, f32SignExp, biasAdjustment);
575 Value f4Exp = arith::TruncIOp::create(b, i4Ty, biasAdjustedSignExp);
576 f4Exp = arith::ShLIOp::create(b, f4Exp, cF4MantissaWidth);
577 f4Bits = arith::AddIOp::create(b, f4Bits, f4Exp);
578
579 // Step 3: Set mantissa to first bit.
580 Value cF32FirstBitMask = createConst(loc, i32Ty, 0x400000, rewriter);
581 Value man1Bit = arith::AndIOp::create(b, f32Bits, cF32FirstBitMask);
582 man1Bit = arith::ShRUIOp::create(b, man1Bit, c0x00000016);
583 Value f4Man = arith::TruncIOp::create(b, i4Ty, man1Bit);
584 f4Bits = arith::AddIOp::create(b, f4Bits, f4Man);
585
586 // Step 4: Special consideration for conversion to 0.5.
587 Value cF32MantissaMask = createConst(loc, i32Ty, 0x7fffff, rewriter);
588 Value f8Exp = arith::TruncIOp::create(b, i8Ty, biasAdjustedSignExp);
589 Value isSubnormal =
590 arith::CmpIOp::create(b, arith::CmpIPredicate::sle, f8Exp, c0x00);
591 Value isNegOneExp =
592 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, f8Exp, c0xff);
593 Value man23Bits = arith::AndIOp::create(b, f32Bits, cF32MantissaMask);
594 Value isNonZeroMan = arith::CmpIOp::create(b, arith::CmpIPredicate::ugt,
595 man23Bits, zeroExpBits);
596 Value roundToHalf = arith::AndIOp::create(b, isNegOneExp, isNonZeroMan);
597 Value isZeroExp =
598 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, f8Exp, c0x00);
599 Value subnormalF4Bits = createConst(loc, i4Ty, 0xf, rewriter);
600 Value halfF4Bits = createConst(loc, i4Ty, 0x0, rewriter);
601 Value subResult =
602 arith::SelectOp::create(b, isSubnormal, subnormalF4Bits, f4Bits);
603 subResult = arith::SelectOp::create(b, roundToHalf, halfF4Bits, subResult);
604 f4Bits = arith::SelectOp::create(b, isZeroExp, f4Bits, subResult);
605
606 // Step 5: Round up if necessary.
607 Value cF32Last22BitMask = createConst(loc, i32Ty, 0x3fffff, rewriter);
608 Value cRound = createConst(loc, i32Ty, 0x200000, rewriter); // 010 0000...
609 Value man22Bits = arith::AndIOp::create(b, f32Bits, cF32Last22BitMask);
610 Value shouldRound =
611 arith::CmpIOp::create(b, arith::CmpIPredicate::uge, man22Bits, cRound);
612 shouldRound = arith::OrIOp::create(b, shouldRound, isSubnormal);
613 Value roundedF4Bits = arith::AddIOp::create(b, f4Bits, c0x1);
614 f4Bits = arith::SelectOp::create(b, shouldRound, roundedF4Bits, f4Bits);
615
616 Value result = arith::BitcastOp::create(b, resultTy, f4Bits);
617 rewriter.replaceOp(op, result);
618 return success();
619 }
620};
621
622/*
623TruncF to F8E8M0 is expected to extract exponent bits out of F32 type
624Since All kinds of Infs and NaNs are mapped to same exponent bits in F32 type,
625they all map to NaN in F8E8M0 Type.
626*/
627struct F8E8M0TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
628 using Base::Base;
629 LogicalResult matchAndRewrite(arith::TruncFOp op,
630 PatternRewriter &rewriter) const final {
631 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
632 Value operand = op.getOperand();
633 Type operandTy = operand.getType();
634 Type operandETy = getElementTypeOrSelf(operandTy);
635 Type resultTy = op.getType();
636 Type resultETy = getElementTypeOrSelf(resultTy);
637 if (!llvm::isa<Float8E8M0FNUType>(resultETy)) {
638 return rewriter.notifyMatchFailure(op, "not a truncf to f8E8M0FNU");
639 }
640
641 if (op.getRoundingmodeAttr()) {
642 return rewriter.notifyMatchFailure(
643 op, "only applicable to default rounding mode.");
644 }
645
646 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
647 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
648 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
649
650 if (operandETy.getIntOrFloatBitWidth() < 32) {
651 operand = arith::ExtFOp::create(b, f32Ty, operand, op.getFastmathAttr());
652 } else if (operandETy.getIntOrFloatBitWidth() > 32) {
653 operand = arith::TruncFOp::create(
654 b, f32Ty, operand, op.getRoundingmodeAttr(), op.getFastmathAttr());
655 }
656 Value f32Bits = arith::BitcastOp::create(b, i32Ty, operand);
657 Value cF32MantissaWidth = createConst(op->getLoc(), i32Ty, 23, rewriter);
658 Value f32SignExp = arith::ShRUIOp::create(b, f32Bits, cF32MantissaWidth);
659 Value exp8Bits = arith::TruncIOp::create(b, i8Ty, f32SignExp);
660 Value result = arith::BitcastOp::create(b, resultTy, exp8Bits);
661 rewriter.replaceOp(op, result);
662 return success();
663 }
664};
665
666/// Expand an ExtF from F8E5M2. F8E5M2 uses a 5-bit exponent with bias 15 and a
667/// 2-bit mantissa, i.e. it is bit-for-bit the high byte of an IEEE F16 value
668/// (same exponent field and bias), including infinities and NaNs. The exact
669/// F16 value is therefore obtained by placing the 8 F8E5M2 bits into the high
670/// byte of an i16. The F16 value is then converted to the requested result
671/// type with the native (LLVM-lowerable) extf/truncf.
672struct F8E5M2ExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
673 using Base::Base;
674 LogicalResult matchAndRewrite(arith::ExtFOp op,
675 PatternRewriter &rewriter) const final {
676 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
677 Value operand = op.getOperand();
678 Type operandTy = operand.getType();
679 Type resultTy = op.getType();
680 Type operandETy = getElementTypeOrSelf(operandTy);
681 Type resultETy = getElementTypeOrSelf(resultTy);
682
683 if (!llvm::isa<Float8E5M2Type>(operandETy))
684 return rewriter.notifyMatchFailure(op, "not a ext of F8E5M2");
685
686 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
687 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
688 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
689
690 Value bitcast = arith::BitcastOp::create(b, i8Ty, operand);
691 Value exti = arith::ExtUIOp::create(b, i16Ty, bitcast);
692 Value c8 = createConst(op.getLoc(), i16Ty, 8, rewriter);
693 Value f16Bits = arith::ShLIOp::create(b, exti, c8);
694 Value f16 = arith::BitcastOp::create(b, f16Ty, f16Bits);
695
696 Value result = f16;
697 if (!resultETy.isF16()) {
698 if (resultETy.getIntOrFloatBitWidth() < 16)
699 result = arith::TruncFOp::create(b, resultTy, f16, nullptr,
700 op.getFastmathAttr());
701 else
702 result = arith::ExtFOp::create(b, resultTy, f16, op.getFastmathAttr());
703 }
704 rewriter.replaceOp(op, result);
705 return success();
706 }
707};
708
709/// Expand a TruncF to F8E5M2. The input is first reduced to F16 (which shares
710/// the F8E5M2 exponent layout and bias) using the native truncf, then the low
711/// 8 mantissa bits of the F16 value are dropped with round-to-nearest-even.
712/// The rounding-bias trick is borrowed from the BF16 converter: adding the
713/// bias may carry into the exponent field, which is exactly the desired
714/// behavior since F16 and F8E5M2 share the same exponent bias. NaN is handled
715/// separately.
716struct F8E5M2TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
717 using Base::Base;
718 LogicalResult matchAndRewrite(arith::TruncFOp op,
719 PatternRewriter &rewriter) const final {
720 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
721 Value operand = op.getOperand();
722 Type operandTy = operand.getType();
723 Type resultTy = op.getType();
724 Type operandETy = getElementTypeOrSelf(operandTy);
725 Type resultETy = getElementTypeOrSelf(resultTy);
726
727 if (!llvm::isa<Float8E5M2Type>(resultETy))
728 return rewriter.notifyMatchFailure(op, "not a trunc to F8E5M2");
729 if (op.getRoundingmodeAttr())
730 return rewriter.notifyMatchFailure(
731 op, "only applicable to default rounding mode.");
732
733 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
734 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
735 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
736
737 Value h16 = operand;
738 if (!operandETy.isF16())
739 h16 = arith::TruncFOp::create(b, f16Ty, operand, nullptr,
740 op.getFastmathAttr());
741
742 Value isNan = arith::CmpFOp::create(b, arith::CmpFPredicate::UNE, h16, h16);
743 Value h16Bits = arith::BitcastOp::create(b, i16Ty, h16);
744 // Rounding bias constants for dropping the low 8 mantissa bits.
745 Value c7F = createConst(op.getLoc(), i16Ty, 0x7f, rewriter);
746 Value c8 = createConst(op.getLoc(), i16Ty, 8, rewriter);
747 Value c1 = createConst(op.getLoc(), i16Ty, 1, rewriter);
748 Value bit8 =
749 arith::AndIOp::create(b, arith::ShRUIOp::create(b, h16Bits, c8), c1);
750 Value roundingBias = arith::AddIOp::create(b, bit8, c7F);
751 Value biased = arith::AddIOp::create(b, h16Bits, roundingBias);
752 Value biasedAndShifted = arith::ShRUIOp::create(b, biased, c8);
753 Value normalCaseResult = arith::TruncIOp::create(b, i8Ty, biasedAndShifted);
754 // Quiet NaN for F8E5M2 (exponent all ones, mantissa MSB set).
755 Value cNan = createConst(op.getLoc(), i8Ty, 0x7e, rewriter);
756 Value select = arith::SelectOp::create(b, isNan, cNan, normalCaseResult);
757 Value result = arith::BitcastOp::create(b, resultTy, select);
758 rewriter.replaceOp(op, result);
759 return success();
760 }
761};
762
763/// Expand an ExtF from F8E4M3FN. F8E4M3FN uses a 4-bit exponent with bias 7, a
764/// 3-bit mantissa, no infinities and a single NaN encoding (S.1111.111). The
765/// 7 magnitude bits (EEEE.MMM) are placed into the high mantissa/exponent bits
766/// of an F16 by shifting left by 7, producing an F16 whose value equals the
767/// desired magnitude scaled by 2^-8 (the F16 bias is 15 while F8E4M3FN's is
768/// 7). Multiplying by 256 in F32 recovers the true magnitude for both normal
769/// and subnormal inputs. The sign bit and the NaN encoding are re-applied
770/// explicitly.
771struct F8E4M3FNExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
772 using Base::Base;
773 LogicalResult matchAndRewrite(arith::ExtFOp op,
774 PatternRewriter &rewriter) const final {
775 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
776 Value operand = op.getOperand();
777 Type operandTy = operand.getType();
778 Type resultTy = op.getType();
779 Type operandETy = getElementTypeOrSelf(operandTy);
780 Type resultETy = getElementTypeOrSelf(resultTy);
781
782 if (!llvm::isa<Float8E4M3FNType>(operandETy))
783 return rewriter.notifyMatchFailure(op, "not a ext of F8E4M3FN");
784
785 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
786 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
787 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
788 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
789 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
790
791 Value bits = arith::BitcastOp::create(b, i8Ty, operand);
792 Value c7F8 = createConst(op.getLoc(), i8Ty, 0x7f, rewriter);
793 Value mag8 = arith::AndIOp::create(b, bits, c7F8);
794 // Build an F16 equal to the magnitude times 2^-8.
795 Value mag16 = arith::ExtUIOp::create(b, i16Ty, mag8);
796 Value c7 = createConst(op.getLoc(), i16Ty, 7, rewriter);
797 Value g16Bits = arith::ShLIOp::create(b, mag16, c7);
798 Value g16 = arith::BitcastOp::create(b, f16Ty, g16Bits);
799 Value gF32 = arith::ExtFOp::create(b, f32Ty, g16, op.getFastmathAttr());
800 Value c256 =
801 createFloatConst(op.getLoc(), f32Ty, APFloat(256.0f), rewriter);
802 Value magF32 = arith::MulFOp::create(b, gF32, c256, op.getFastmathAttr());
803 // Re-apply the sign bit into the F32 result.
804 Value magI32 = arith::BitcastOp::create(b, i32Ty, magF32);
805 Value c80I8 = createConst(op.getLoc(), i8Ty, 0x80, rewriter);
806 Value sign8 = arith::AndIOp::create(b, bits, c80I8);
807 Value sign32 = arith::ExtUIOp::create(b, i32Ty, sign8);
808 Value c24 = createConst(op.getLoc(), i32Ty, 24, rewriter);
809 Value signBit = arith::ShLIOp::create(b, sign32, c24);
810 Value signedI32 = arith::OrIOp::create(b, magI32, signBit);
811 Value signedF32 = arith::BitcastOp::create(b, f32Ty, signedI32);
812 // NaN encoding is magnitude == 0x7f.
813 Value isNan =
814 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, mag8, c7F8);
815 Value cNan32 = createConst(op.getLoc(), i32Ty, 0x7fc00000, rewriter);
816 Value nanSigned = arith::OrIOp::create(b, cNan32, signBit);
817 Value nanF32 = arith::BitcastOp::create(b, f32Ty, nanSigned);
818 Value resultF32 = arith::SelectOp::create(b, isNan, nanF32, signedF32);
819
820 Value result = resultF32;
821 if (!resultETy.isF32()) {
822 if (resultETy.getIntOrFloatBitWidth() < 32)
823 result = arith::TruncFOp::create(b, resultTy, resultF32, nullptr,
824 op.getFastmathAttr());
825 else
826 result =
827 arith::ExtFOp::create(b, resultTy, resultF32, op.getFastmathAttr());
828 }
829 rewriter.replaceOp(op, result);
830 return success();
831 }
832};
833
834/// Expand a TruncF to F8E4M3FN. The magnitude is scaled by 2^-8 and reduced to
835/// F16 (undoing the bias difference so the F16 value equals the magnitude times
836/// 2^-8). The low 7 bits of the F16 encoding are then dropped with
837/// round-to-nearest-even to recover the 7 magnitude bits. F8E4M3FN has no
838/// infinity, so any input that overflows the maximum representable magnitude
839/// (448), as well as infinities and NaNs, maps to the F8E4M3FN NaN encoding to
840/// match the LLVM APFloat NanOnly overflow behavior and the OCP FP8 (E4M3)
841/// spec.
842struct F8E4M3FNTruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
843 using Base::Base;
844 LogicalResult matchAndRewrite(arith::TruncFOp op,
845 PatternRewriter &rewriter) const final {
846 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
847 Value operand = op.getOperand();
848 Type operandTy = operand.getType();
849 Type resultTy = op.getType();
850 Type operandETy = getElementTypeOrSelf(operandTy);
851 Type resultETy = getElementTypeOrSelf(resultTy);
852
853 if (!llvm::isa<Float8E4M3FNType>(resultETy))
854 return rewriter.notifyMatchFailure(op, "not a trunc to F8E4M3FN");
855 if (op.getRoundingmodeAttr())
856 return rewriter.notifyMatchFailure(
857 op, "only applicable to default rounding mode.");
858
859 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
860 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
861 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
862 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
863 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
864
865 Value f32 = operand;
866 if (!operandETy.isF32()) {
867 if (operandETy.getIntOrFloatBitWidth() < 32)
868 f32 = arith::ExtFOp::create(b, f32Ty, operand, op.getFastmathAttr());
869 else
870 f32 = arith::TruncFOp::create(b, f32Ty, operand, nullptr,
871 op.getFastmathAttr());
872 }
873
874 Value isNan = arith::CmpFOp::create(b, arith::CmpFPredicate::UNE, f32, f32);
875 // Split sign and magnitude.
876 Value f32Bits = arith::BitcastOp::create(b, i32Ty, f32);
877 Value cSignMask = createConst(op.getLoc(), i32Ty, 0x80000000, rewriter);
878 Value cAbsMask = createConst(op.getLoc(), i32Ty, 0x7fffffff, rewriter);
879 Value signBits = arith::AndIOp::create(b, f32Bits, cSignMask);
880 Value absBits = arith::AndIOp::create(b, f32Bits, cAbsMask);
881 Value absF32 = arith::BitcastOp::create(b, f32Ty, absBits);
882 // F8E4M3FN has no infinity: a magnitude above the round-to-nearest-even
883 // overflow boundary (464 = 448 + half an ulp) or an infinity maps to NaN.
884 Value cOverflow =
885 createFloatConst(op.getLoc(), f32Ty, APFloat(464.0f), rewriter);
886 Value isOverflow =
887 arith::CmpFOp::create(b, arith::CmpFPredicate::OGT, absF32, cOverflow);
888 // Clamp to the F8E4M3FN maximum magnitude (448) so the finite path stays
889 // well-defined; overflowing inputs are replaced by NaN below.
890 Value cMax =
891 createFloatConst(op.getLoc(), f32Ty, APFloat(448.0f), rewriter);
892 absF32 = arith::MinNumFOp::create(b, absF32, cMax);
893 Value cInv256 =
894 createFloatConst(op.getLoc(), f32Ty, APFloat(0.00390625f), rewriter);
895 Value scaled = arith::MulFOp::create(b, absF32, cInv256, nullptr);
896 Value h16 = arith::TruncFOp::create(b, f16Ty, scaled, nullptr,
897 op.getFastmathAttr());
898 Value h16Bits = arith::BitcastOp::create(b, i16Ty, h16);
899 // Drop the low 7 bits with round-to-nearest-even.
900 Value c3F = createConst(op.getLoc(), i16Ty, 0x3f, rewriter);
901 Value c7 = createConst(op.getLoc(), i16Ty, 7, rewriter);
902 Value c1 = createConst(op.getLoc(), i16Ty, 1, rewriter);
903 Value bit7 =
904 arith::AndIOp::create(b, arith::ShRUIOp::create(b, h16Bits, c7), c1);
905 Value roundingBias = arith::AddIOp::create(b, bit7, c3F);
906 Value biased = arith::AddIOp::create(b, h16Bits, roundingBias);
907 Value shifted = arith::ShRUIOp::create(b, biased, c7);
908 Value mag8 = arith::TruncIOp::create(b, i8Ty, shifted);
909 Value c7F8 = createConst(op.getLoc(), i8Ty, 0x7f, rewriter);
910 mag8 = arith::AndIOp::create(b, mag8, c7F8);
911 // Re-apply the sign.
912 Value c24 = createConst(op.getLoc(), i32Ty, 24, rewriter);
913 Value sign8 = arith::TruncIOp::create(
914 b, i8Ty, arith::ShRUIOp::create(b, signBits, c24));
915 Value res8 = arith::OrIOp::create(b, mag8, sign8);
916 // NaN input or an overflowing/infinite magnitude maps to the NaN encoding.
917 Value isNanOrOverflow = arith::OrIOp::create(b, isNan, isOverflow);
918 Value cNan8 = createConst(op.getLoc(), i8Ty, 0x7f, rewriter);
919 Value res = arith::SelectOp::create(b, isNanOrOverflow, cNan8, res8);
920 Value result = arith::BitcastOp::create(b, resultTy, res);
921 rewriter.replaceOp(op, result);
922 return success();
923 }
924};
925
926struct ScalingExtFOpConverter : public OpRewritePattern<arith::ScalingExtFOp> {
927 using Base::Base;
928 LogicalResult matchAndRewrite(arith::ScalingExtFOp op,
929 PatternRewriter &rewriter) const final {
930 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
931 Value inputOperand = op.getIn();
932 Value scaleOperand = op.getScale();
933 Type scaleTy = scaleOperand.getType();
934 Type scaleETy = getElementTypeOrSelf(scaleOperand);
935 // allow implicit exponent extraction from 16/32 bits floats
936 if (scaleETy.getIntOrFloatBitWidth() >= 16) {
937 scaleETy = b.getF8E8M0Type();
938 scaleTy = cloneToShapedType(scaleTy, scaleETy);
939 scaleOperand = arith::TruncFOp::create(b, scaleTy, scaleOperand, nullptr,
940 op.getFastmathAttr());
941 }
942 // Catch scale types like f8E5M2.
943 if (!llvm::isa<Float8E8M0FNUType>(scaleETy)) {
944 return rewriter.notifyMatchFailure(
945 op, "scaling_extf is using scales of type which can not be converted "
946 "to f8E8M0FNU");
947 }
948 Type resultTy = op.getType();
949 // extf on scale will essentially create floating point number
950 // of type resulTy that is 2^scale and will also propagate NaNs
951 Value scaleExt =
952 arith::ExtFOp::create(b, resultTy, scaleOperand, op.getFastmathAttr());
953 Value inputExt =
954 arith::ExtFOp::create(b, resultTy, inputOperand, op.getFastmathAttr());
955 Value result =
956 arith::MulFOp::create(b, inputExt, scaleExt, op.getFastmathAttr());
957 rewriter.replaceOp(op, result);
958 return success();
959 }
960};
961
962/*
963Expands arith.ScalingTruncFOp(in, scale) into
964 scale = arith.truncf(scale) : scaleTy -> f8E8M0FNU
965 result = arith.truncf(in / (2^scale))
966 */
967struct ScalingTruncFOpConverter
968 : public OpRewritePattern<arith::ScalingTruncFOp> {
969 using Base::Base;
970 LogicalResult matchAndRewrite(arith::ScalingTruncFOp op,
971 PatternRewriter &rewriter) const final {
972 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
973 Value inputOperand = op.getIn();
974 Value scaleOperand = op.getScale();
975 Type scaleTy = scaleOperand.getType();
976 Type scaleETy = getElementTypeOrSelf(scaleOperand);
977 // allow implicit exponent extraction from 16/32 bits floats
978 if (scaleETy.getIntOrFloatBitWidth() >= 16) {
979 scaleETy = b.getF8E8M0Type();
980 scaleTy = cloneToShapedType(scaleTy, scaleETy);
981 scaleOperand = arith::TruncFOp::create(b, scaleTy, scaleOperand, nullptr,
982 op.getFastmathAttr());
983 }
984 if (!llvm::isa<Float8E8M0FNUType>(scaleETy)) {
985 return rewriter.notifyMatchFailure(
986 op, "scaling_truncf is using scales type which can not be converted "
987 "to f8E8M0FNU");
988 }
989 Type resultTy = op.getType();
990 Type inputTy = inputOperand.getType();
991 // this will create a floating point number of type
992 // inputTy that is 2^scale and will also propagate NaNs
993 scaleOperand =
994 arith::ExtFOp::create(b, inputTy, scaleOperand, op.getFastmathAttr());
995 Value result = arith::DivFOp::create(b, inputOperand, scaleOperand,
996 op.getFastmathAttr());
997 Value resultCast = arith::TruncFOp::create(
998 b, resultTy, result, op.getRoundingmodeAttr(), op.getFastmathAttr());
999 rewriter.replaceOp(op, resultCast);
1000 return success();
1001 }
1002};
1003
1004/// Expands `arith.flush_denormals` into integer arithmetic.
1005///
1006/// For an IEEE-like floating-point value with a sign|exponent|mantissa bit
1007/// layout, a value is denormal iff its biased exponent field is zero and its
1008/// stored mantissa is non-zero. When the exponent field is zero, the value is
1009/// either pos/neg 0 (mantissa = 0) or a denormal (mantissa != 0); in both
1010/// cases, clearing the mantissa bits produces the desired sign-preserved zero
1011/// (a no-op for pos/neg 0, a flush for denormals). When the exponent field is
1012/// non-zero, the value passes through unchanged.
1013///
1014/// Pseudocode:
1015/// bits = bitcast(x, iN)
1016/// expIsZero = (bits & expMask) == 0
1017/// cleared = bits & ~manMask
1018/// resultBits = select(expIsZero, cleared, bits)
1019/// result = bitcast(resultBits, floatTy)
1020struct FlushDenormalsOpConverter
1021 : public OpRewritePattern<arith::FlushDenormalsOp> {
1022 using Base::Base;
1023 LogicalResult matchAndRewrite(arith::FlushDenormalsOp op,
1024 PatternRewriter &rewriter) const final {
1025 Location loc = op.getLoc();
1026 ImplicitLocOpBuilder b(loc, rewriter);
1027 Value operand = op.getOperand();
1028 Type operandTy = operand.getType();
1029 auto floatTy = dyn_cast<FloatType>(getElementTypeOrSelf(operandTy));
1030 if (!floatTy)
1031 return rewriter.notifyMatchFailure(op, "operand is not a float type");
1032
1033 const llvm::fltSemantics &sem = floatTy.getFloatSemantics();
1034 // Restrict to IEEE-like encodings, where the sign bit is the MSB and
1035 // denormals are exactly "biased exponent == 0 and non-zero mantissa".
1036 if (!llvm::APFloatBase::isIEEELikeFP(sem))
1037 return rewriter.notifyMatchFailure(
1038 op, "only IEEE-like floating-point types are supported");
1039
1040 unsigned totalBits = llvm::APFloatBase::semanticsSizeInBits(sem);
1041 unsigned precision = llvm::APFloatBase::semanticsPrecision(sem);
1042 // Stored mantissa bits = precision - 1 (implicit leading bit not stored).
1043 // Exponent field bits = totalBits - 1 (sign) - storedMantissa.
1044 if (precision < 1 || precision > totalBits)
1045 return rewriter.notifyMatchFailure(op, "unexpected float semantics");
1046 unsigned mantissaBits = precision - 1;
1047 unsigned expBits = totalBits - 1 - mantissaBits;
1048 if (expBits == 0 || mantissaBits == 0)
1049 return rewriter.notifyMatchFailure(
1050 op, "degenerate float encoding has no exponent or mantissa");
1051
1052 Type intTy =
1053 cloneToShapedType(operandTy, rewriter.getIntegerType(totalBits));
1054 Value bits = arith::BitcastOp::create(b, intTy, operand);
1055 APInt expMaskVal =
1056 APInt::getBitsSet(totalBits, mantissaBits, mantissaBits + expBits);
1057 APInt clearMantissaMaskVal = ~APInt::getLowBitsSet(totalBits, mantissaBits);
1058 APInt zeroVal = APInt::getZero(totalBits);
1059 Value expMask = createAPIntConst(loc, intTy, expMaskVal, rewriter);
1060 Value clearMantissaMask =
1061 createAPIntConst(loc, intTy, clearMantissaMaskVal, rewriter);
1062 Value zero = createAPIntConst(loc, intTy, zeroVal, rewriter);
1063
1064 // expField == 0
1065 Value expField = arith::AndIOp::create(b, bits, expMask);
1066 Value expIsZero =
1067 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, expField, zero);
1068
1069 // Clear mantissa bits: when exp == 0, this produces pos/neg 0.0.
1070 Value cleared = arith::AndIOp::create(b, bits, clearMantissaMask);
1071 Value resultBits = arith::SelectOp::create(b, expIsZero, cleared, bits);
1072 Value result = arith::BitcastOp::create(b, operandTy, resultBits);
1073
1074 rewriter.replaceOp(op, result);
1075 return success();
1076 }
1077};
1078
1079struct ArithExpandOpsPass
1080 : public arith::impl::ArithExpandOpsPassBase<ArithExpandOpsPass> {
1081 using ArithExpandOpsPassBase::ArithExpandOpsPassBase;
1082
1083 void runOnOperation() override {
1084 RewritePatternSet patterns(&getContext());
1085 ConversionTarget target(getContext());
1086
1087 arith::populateCeilFloorDivExpandOpsPatterns(patterns);
1088 arith::populateExpandScalingExtTruncPatterns(patterns);
1089
1090 target.addLegalDialect<arith::ArithDialect>();
1091 target.addLegalDialect<vector::VectorDialect>();
1092
1093 // clang-format off
1094 target.addIllegalOp<
1095 arith::CeilDivSIOp,
1096 arith::CeilDivUIOp,
1097 arith::FloorDivSIOp,
1098 arith::ScalingExtFOp,
1099 arith::ScalingTruncFOp
1100 >();
1101 // clang-format on
1102
1103 // The min/max ops also have a direct arith-to-llvm lowering to the
1104 // `llvm.intr.maximum`/`minimum`/... (and smax/umax/...) intrinsics, which
1105 // are a single hardware instruction on many targets. Only expand them into
1106 // cmpf/cmpi + select when requested, so pipelines that run arith-to-llvm
1107 // can keep the intrinsic lowering. The float and integer ops are gated
1108 // separately.
1109 if (includeMinMaxF) {
1110 arith::populateExpandMinMaxFPatterns(patterns);
1111 // clang-format off
1112 target.addIllegalOp<
1113 arith::MaximumFOp,
1114 arith::MinimumFOp,
1115 arith::MaxNumFOp,
1116 arith::MinNumFOp
1117 >();
1118 // clang-format on
1119 }
1120 if (includeMinMaxI) {
1121 arith::populateExpandMinMaxIPatterns(patterns);
1122 // clang-format off
1123 target.addIllegalOp<
1124 arith::MaxSIOp,
1125 arith::MaxUIOp,
1126 arith::MinSIOp,
1127 arith::MinUIOp
1128 >();
1129 // clang-format on
1130 }
1131
1132 if (includeBf16)
1133 arith::populateExpandBFloat16Patterns(patterns);
1134 if (includeF8E8M0)
1135 arith::populateExpandF8E8M0Patterns(patterns);
1136 if (includeF4E2M1)
1137 arith::populateExpandF4E2M1Patterns(patterns);
1138 if (includeF8E5M2)
1139 arith::populateExpandF8E5M2Patterns(patterns);
1140 if (includeF8E4M3FN)
1141 arith::populateExpandF8E4M3FNPatterns(patterns);
1142 if (includeFlushDenormals) {
1143 arith::populateExpandFlushDenormalsPatterns(patterns);
1144 // Only IEEE-like floating-point types are expanded by the pattern;
1145 // leave `arith.flush_denormals` on other types alone.
1146 target.addDynamicallyLegalOp<arith::FlushDenormalsOp>(
1147 [](arith::FlushDenormalsOp op) {
1148 auto floatTy =
1149 dyn_cast<FloatType>(getElementTypeOrSelf(op.getType()));
1150 if (!floatTy)
1151 return true;
1152 return !llvm::APFloatBase::isIEEELikeFP(
1153 floatTy.getFloatSemantics());
1154 });
1155 }
1156
1157 target.addDynamicallyLegalOp<arith::ExtFOp>([=](arith::ExtFOp op) {
1158 Type inETy = getElementTypeOrSelf(op.getOperand().getType());
1159 Type outETy = getElementTypeOrSelf(op.getType());
1160 bool legalTypes = true;
1161 if (includeBf16)
1162 legalTypes &= !(inETy.isBF16() && outETy.isF32());
1163 if (includeF8E8M0)
1164 legalTypes &= !llvm::isa<Float8E8M0FNUType>(inETy);
1165 if (includeF4E2M1)
1166 legalTypes &= !llvm::isa<Float4E2M1FNType>(inETy);
1167 if (includeF8E5M2)
1168 legalTypes &= !llvm::isa<Float8E5M2Type>(inETy);
1169 if (includeF8E4M3FN)
1170 legalTypes &= !llvm::isa<Float8E4M3FNType>(inETy);
1171 return legalTypes;
1172 });
1173
1174 target.addDynamicallyLegalOp<arith::TruncFOp>([=](arith::TruncFOp op) {
1175 Type inETy = getElementTypeOrSelf(op.getOperand().getType());
1176 Type outETy = getElementTypeOrSelf(op.getType());
1177 bool legalTypes = true;
1178 if (includeBf16)
1179 legalTypes &= !(inETy.isF32() && outETy.isBF16());
1180 if (includeF8E8M0)
1181 legalTypes &= !(llvm::isa<Float8E8M0FNUType>(outETy));
1182 if (includeF4E2M1)
1183 legalTypes &= !llvm::isa<Float4E2M1FNType>(outETy);
1184 if (includeF8E5M2)
1185 legalTypes &= !llvm::isa<Float8E5M2Type>(outETy);
1186 if (includeF8E4M3FN)
1187 legalTypes &= !llvm::isa<Float8E4M3FNType>(outETy);
1188 return legalTypes;
1189 });
1190
1191 // clang-format on
1192 if (failed(applyPartialConversion(getOperation(), target,
1193 std::move(patterns))))
1194 signalPassFailure();
1195 }
1196};
1197
1198} // namespace
1199
1201 RewritePatternSet &patterns) {
1202 patterns
1203 .add<CeilDivSIOpConverter, CeilDivUIOpConverter, FloorDivSIOpConverter>(
1204 patterns.getContext());
1205}
1206
1208 patterns.add<BFloat16ExtFOpConverter, BFloat16TruncFOpConverter>(
1209 patterns.getContext());
1210}
1211
1213 patterns.add<F4E2M1ExtFOpConverter, F4E2M1TruncFOpConverter>(
1214 patterns.getContext());
1215}
1216
1218 patterns.add<F8E5M2ExtFOpConverter, F8E5M2TruncFOpConverter>(
1219 patterns.getContext());
1220}
1221
1223 patterns.add<F8E4M3FNExtFOpConverter, F8E4M3FNTruncFOpConverter>(
1224 patterns.getContext());
1225}
1226
1228 patterns.add<F8E8M0ExtFOpConverter, F8E8M0TruncFOpConverter>(
1229 patterns.getContext());
1230}
1231
1233 RewritePatternSet &patterns) {
1234 patterns.add<ScalingExtFOpConverter, ScalingTruncFOpConverter>(
1235 patterns.getContext());
1236}
1237
1239 RewritePatternSet &patterns) {
1240 patterns.add<FlushDenormalsOpConverter>(patterns.getContext());
1241}
1242
1244 // clang-format off
1245 patterns.add<
1246 MaximumMinimumFOpConverter<MaximumFOp, arith::CmpFPredicate::UGT>,
1247 MaximumMinimumFOpConverter<MinimumFOp, arith::CmpFPredicate::ULT>,
1248 MaxNumMinNumFOpConverter<MaxNumFOp, arith::CmpFPredicate::UGT>,
1249 MaxNumMinNumFOpConverter<MinNumFOp, arith::CmpFPredicate::ULT>
1250 >(patterns.getContext());
1251 // clang-format on
1252}
1253
1255 // clang-format off
1256 patterns.add<
1257 MaxMinIOpConverter<MaxSIOp, arith::CmpIPredicate::sgt>,
1258 MaxMinIOpConverter<MaxUIOp, arith::CmpIPredicate::ugt>,
1259 MaxMinIOpConverter<MinSIOp, arith::CmpIPredicate::slt>,
1260 MaxMinIOpConverter<MinUIOp, arith::CmpIPredicate::ult>
1261 >(patterns.getContext());
1262 // clang-format on
1263}
1264
1269
return success()
static Value createConst(Location loc, Type type, int value, PatternRewriter &rewriter)
Create an integer or index constant.
Definition ExpandOps.cpp:27
static Value createAPIntConst(Location loc, Type type, const APInt &value, PatternRewriter &rewriter)
Create an integer constant from an APInt.
Definition ExpandOps.cpp:38
static Type cloneToShapedType(Type cloneFrom, Type cloneTo)
Creates shapedType using shape from cloneFrom and base type from cloneTo.
Definition ExpandOps.cpp:61
static Value createFloatConst(Location loc, Type type, const APFloat &value, PatternRewriter &rewriter)
Create a float constant.
Definition ExpandOps.cpp:49
static int64_t product(ArrayRef< int64_t > vals)
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
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.
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
bool isBF16() const
Definition Types.cpp:37
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
void populateExpandF8E4M3FNPatterns(RewritePatternSet &patterns)
Add patterns to expand Arith f8e4m3fn patterns to lower level bitcasts/shifts.
void populateExpandBFloat16Patterns(RewritePatternSet &patterns)
Add patterns to expand Arith bf16 patterns to lower level bitcasts/shifts.
void populateExpandScalingExtTruncPatterns(RewritePatternSet &patterns)
Add patterns to expand scaling ExtF/TruncF ops to equivalent arith ops.
void populateExpandF8E8M0Patterns(RewritePatternSet &patterns)
Add patterns to expand Arith f8e8m0 patterns to lower level bitcasts/shifts.
void populateCeilFloorDivExpandOpsPatterns(RewritePatternSet &patterns)
Add patterns to expand Arith ceil/floor division ops.
void populateExpandF4E2M1Patterns(RewritePatternSet &patterns)
Add patterns to expand Arith f4e2m1 patterns to lower level bitcasts/shifts.
void populateExpandFlushDenormalsPatterns(RewritePatternSet &patterns)
Add patterns to expand arith.flush_denormals into integer arithmetic (bitcast + bit masks + compare +...
void populateExpandMinMaxFPatterns(RewritePatternSet &patterns)
Add patterns to expand the floating-point min/max ops (arith.maximumf/ minimumf/maxnumf/minnumf) into...
void populateExpandMinMaxIPatterns(RewritePatternSet &patterns)
Add patterns to expand the signed/unsigned integer min/max ops (arith.maxsi/maxui/minsi/minui) into c...
void populateExpandMinMaxPatterns(RewritePatternSet &patterns)
Add patterns to expand both the floating-point and integer min/max ops into cmpf/cmpi + select sequen...
void populateArithExpandOpsPatterns(RewritePatternSet &patterns)
Add patterns to expand Arith ops.
void populateExpandF8E5M2Patterns(RewritePatternSet &patterns)
Add patterns to expand Arith f8e5m2 patterns to lower level bitcasts/shifts.
int compare(const Fraction &x, const Fraction &y)
Three-way comparison between two fractions.
Definition Fraction.h:68
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Include the generated interface declarations.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...