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