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 // F8E8M0 has no zero representation.
367 // All zeros encoding represents 2^-127 which is an f32 subnormal.
368 Value cF8Min = createConst(op.getLoc(), i8Ty, 0, rewriter);
369 Value cF32Min = createConst(op.getLoc(), i32Ty, 0x00400000, rewriter);
370 Value isMin =
371 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, bitcast, cF8Min);
372 f32Bits = arith::SelectOp::create(b, isMin, cF32Min, f32Bits);
373
374 // If FastMathFlag allows no NaN checks, skip it
375 auto fastMath = op.getFastmathAttr();
376 bool NoNaN = fastMath
377 ? (fastMath.getValue() & arith::FastMathFlags::nnan) ==
378 arith::FastMathFlags::nnan
379 : false;
380 if (!NoNaN) {
381 Value cF8NaN = createConst(op.getLoc(), i8Ty, 0xff, rewriter);
382 Value cF32NaN = createConst(op.getLoc(), i32Ty, 0xffffffff, rewriter);
383 Value isNan =
384 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, bitcast, cF8NaN);
385 // select for NaNs
386 f32Bits = arith::SelectOp::create(b, isNan, cF32NaN, f32Bits);
387 }
388
389 Value result = arith::BitcastOp::create(b, f32Ty, f32Bits);
390 if (resultETy.getIntOrFloatBitWidth() < 32) {
391 result = arith::TruncFOp::create(b, resultTy, result, nullptr,
392 op.getFastmathAttr());
393 } else if (resultETy.getIntOrFloatBitWidth() > 32) {
394 result = arith::ExtFOp::create(b, resultTy, result, op.getFastmathAttr());
395 }
396 rewriter.replaceOp(op, result);
397 return success();
398 }
399};
400
401/// Conversion from F32 to F4E2M1 according to the OCP Spec:
402/// www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
403///
404/// The spec requiers us to perform Round to Nearest, Ties to Even.
405///
406/// This means that after rounding, we should break ties by choosing the option
407/// which results in a mantissa of 0 in the least significant digit.
408///
409/// Table of representable values in F4E2M1:
410///
411/// Note: x is sign bit
412/// | Binary | F4E2M1 | F32[23:32]
413/// | x000 | 0.0 | x000 0000 00
414/// | x001 | 0.5 | x011 1111 00
415/// | x010 | 1.0 | x011 1111 10
416/// | x011 | 1.5 | x011 1111 11
417/// | x100 | 2.0 | x010 0000 00
418/// | x101 | 3.0 | x010 0000 01
419/// | x110 | 4.0 | x010 0000 10
420/// | x111 | 6.0 | x010 0000 11
421///
422/// Conversion procedure:
423/// Step 1: Clamp to representable bounds.
424/// Step 2: Convert exponent by adjusting bias.
425/// Step 3: Set mantissa to first bit.
426/// Step 4: Special consideration for subnormal and zero exponent.
427/// Step 5: Round up if necessary, if mantissa[1:] greater than 1000000 or
428/// subnormal.
429struct F4E2M1TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
430 using Base::Base;
431 LogicalResult matchAndRewrite(arith::TruncFOp op,
432 PatternRewriter &rewriter) const final {
433 Location loc = op.getLoc();
434 ImplicitLocOpBuilder b(loc, rewriter);
435 Value operand = op.getOperand();
436 Type operandTy = operand.getType();
437 Type resultTy = op.getType();
438 Type operandETy = getElementTypeOrSelf(operandTy);
439 Type resultETy = getElementTypeOrSelf(resultTy);
440
441 Type i4Ty = cloneToShapedType(operandTy, b.getI4Type());
442 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
443 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
444 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
445
446 if (!isa<Float4E2M1FNType>(resultETy))
447 return rewriter.notifyMatchFailure(op, "not a trunc of F4E2M1FN");
448 if (!isa<Float32Type>(operandETy))
449 operand =
450 arith::ExtFOp::create(b, f32Ty, operand, arith::FastMathFlagsAttr{});
451
452 Value c0x1 = createConst(loc, i4Ty, 1, rewriter);
453 Value c0x3 = createConst(loc, i4Ty, 3, rewriter);
454 Value c0x00000016 = createConst(loc, i32Ty, 22, rewriter);
455 Value c0x00 = createConst(loc, i8Ty, 0x00, rewriter);
456 Value c0xff = createConst(loc, i8Ty, 0xff, rewriter);
457 Value zeroExpBits = createConst(loc, i32Ty, 0, rewriter);
458
459 // Step 0: Clamp to bounds.
460 Value cHigherBound = createFloatConst(loc, f32Ty, APFloat(6.0f), rewriter);
461 Value cLowerBound = createFloatConst(loc, f32Ty, APFloat(-6.0f), rewriter);
462 Value operandClamped = arith::MinNumFOp::create(b, cHigherBound, operand);
463 operandClamped = arith::MaxNumFOp::create(b, cLowerBound, operandClamped);
464 Value f32Bits = arith::BitcastOp::create(b, i32Ty, operandClamped);
465
466 // Step 1: Set sign bit.
467 Value cF32ExpManWidth = createConst(loc, i32Ty, 31, rewriter); // 23
468 Value f32Sign = arith::ShRUIOp::create(b, f32Bits, cF32ExpManWidth);
469 Value f4Sign = arith::TruncIOp::create(b, i4Ty, f32Sign);
470 Value f4Bits = arith::ShLIOp::create(b, f4Sign, c0x3);
471
472 // Step 2: Convert exponent by adjusting bias.
473 Value biasAdjustment = createConst(loc, i32Ty, 0x7e, rewriter);
474 Value cF4MantissaWidth = c0x1; // 1
475 Value cF32MantissaWidth = createConst(loc, i32Ty, 23, rewriter); // 23
476 Value f32SignExp = arith::ShRUIOp::create(b, f32Bits, cF32MantissaWidth);
477 Value biasAdjustedSignExp =
478 arith::SubIOp::create(b, f32SignExp, biasAdjustment);
479 Value f4Exp = arith::TruncIOp::create(b, i4Ty, biasAdjustedSignExp);
480 f4Exp = arith::ShLIOp::create(b, f4Exp, cF4MantissaWidth);
481 f4Bits = arith::AddIOp::create(b, f4Bits, f4Exp);
482
483 // Step 3: Set mantissa to first bit.
484 Value cF32FirstBitMask = createConst(loc, i32Ty, 0x400000, rewriter);
485 Value man1Bit = arith::AndIOp::create(b, f32Bits, cF32FirstBitMask);
486 man1Bit = arith::ShRUIOp::create(b, man1Bit, c0x00000016);
487 Value f4Man = arith::TruncIOp::create(b, i4Ty, man1Bit);
488 f4Bits = arith::AddIOp::create(b, f4Bits, f4Man);
489
490 // Step 4: Special consideration for conversion to 0.5.
491 Value cF32MantissaMask = createConst(loc, i32Ty, 0x7fffff, rewriter);
492 Value f8Exp = arith::TruncIOp::create(b, i8Ty, biasAdjustedSignExp);
493 Value isSubnormal =
494 arith::CmpIOp::create(b, arith::CmpIPredicate::sle, f8Exp, c0x00);
495 Value isNegOneExp =
496 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, f8Exp, c0xff);
497 Value man23Bits = arith::AndIOp::create(b, f32Bits, cF32MantissaMask);
498 Value isNonZeroMan = arith::CmpIOp::create(b, arith::CmpIPredicate::ugt,
499 man23Bits, zeroExpBits);
500 Value roundToHalf = arith::AndIOp::create(b, isNegOneExp, isNonZeroMan);
501 Value isZeroExp =
502 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, f8Exp, c0x00);
503 Value subnormalF4Bits = createConst(loc, i4Ty, 0xf, rewriter);
504 Value halfF4Bits = createConst(loc, i4Ty, 0x0, rewriter);
505 Value subResult =
506 arith::SelectOp::create(b, isSubnormal, subnormalF4Bits, f4Bits);
507 subResult = arith::SelectOp::create(b, roundToHalf, halfF4Bits, subResult);
508 f4Bits = arith::SelectOp::create(b, isZeroExp, f4Bits, subResult);
509
510 // Step 5: Round up if necessary.
511 Value cF32Last22BitMask = createConst(loc, i32Ty, 0x3fffff, rewriter);
512 Value cRound = createConst(loc, i32Ty, 0x200000, rewriter); // 010 0000...
513 Value man22Bits = arith::AndIOp::create(b, f32Bits, cF32Last22BitMask);
514 Value shouldRound =
515 arith::CmpIOp::create(b, arith::CmpIPredicate::uge, man22Bits, cRound);
516 shouldRound = arith::OrIOp::create(b, shouldRound, isSubnormal);
517 Value roundedF4Bits = arith::AddIOp::create(b, f4Bits, c0x1);
518 f4Bits = arith::SelectOp::create(b, shouldRound, roundedF4Bits, f4Bits);
519
520 Value result = arith::BitcastOp::create(b, resultTy, f4Bits);
521 rewriter.replaceOp(op, result);
522 return success();
523 }
524};
525
526/*
527TruncF to F8E8M0 is expected to extract exponent bits out of F32 type
528Since All kinds of Infs and NaNs are mapped to same exponent bits in F32 type,
529they all map to NaN in F8E8M0 Type.
530*/
531struct F8E8M0TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
532 using Base::Base;
533 LogicalResult matchAndRewrite(arith::TruncFOp op,
534 PatternRewriter &rewriter) const final {
535 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
536 Value operand = op.getOperand();
537 Type operandTy = operand.getType();
538 Type operandETy = getElementTypeOrSelf(operandTy);
539 Type resultTy = op.getType();
540 Type resultETy = getElementTypeOrSelf(resultTy);
541 if (!llvm::isa<Float8E8M0FNUType>(resultETy)) {
542 return rewriter.notifyMatchFailure(op, "not a truncf to f8E8M0FNU");
543 }
544
545 if (op.getRoundingmodeAttr()) {
546 return rewriter.notifyMatchFailure(
547 op, "only applicable to default rounding mode.");
548 }
549
550 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
551 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
552 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
553
554 if (operandETy.getIntOrFloatBitWidth() < 32) {
555 operand = arith::ExtFOp::create(b, f32Ty, operand, op.getFastmathAttr());
556 } else if (operandETy.getIntOrFloatBitWidth() > 32) {
557 operand = arith::TruncFOp::create(
558 b, f32Ty, operand, op.getRoundingmodeAttr(), op.getFastmathAttr());
559 }
560 Value f32Bits = arith::BitcastOp::create(b, i32Ty, operand);
561 Value cF32MantissaWidth = createConst(op->getLoc(), i32Ty, 23, rewriter);
562 Value f32SignExp = arith::ShRUIOp::create(b, f32Bits, cF32MantissaWidth);
563 Value exp8Bits = arith::TruncIOp::create(b, i8Ty, f32SignExp);
564 Value result = arith::BitcastOp::create(b, resultTy, exp8Bits);
565 rewriter.replaceOp(op, result);
566 return success();
567 }
568};
569
570/// Expand an ExtF from F8E5M2. F8E5M2 uses a 5-bit exponent with bias 15 and a
571/// 2-bit mantissa, i.e. it is bit-for-bit the high byte of an IEEE F16 value
572/// (same exponent field and bias), including infinities and NaNs. The exact
573/// F16 value is therefore obtained by placing the 8 F8E5M2 bits into the high
574/// byte of an i16. The F16 value is then converted to the requested result
575/// type with the native (LLVM-lowerable) extf/truncf.
576struct F8E5M2ExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
577 using Base::Base;
578 LogicalResult matchAndRewrite(arith::ExtFOp op,
579 PatternRewriter &rewriter) const final {
580 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
581 Value operand = op.getOperand();
582 Type operandTy = operand.getType();
583 Type resultTy = op.getType();
584 Type operandETy = getElementTypeOrSelf(operandTy);
585 Type resultETy = getElementTypeOrSelf(resultTy);
586
587 if (!llvm::isa<Float8E5M2Type>(operandETy))
588 return rewriter.notifyMatchFailure(op, "not a ext of F8E5M2");
589
590 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
591 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
592 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
593
594 Value bitcast = arith::BitcastOp::create(b, i8Ty, operand);
595 Value exti = arith::ExtUIOp::create(b, i16Ty, bitcast);
596 Value c8 = createConst(op.getLoc(), i16Ty, 8, rewriter);
597 Value f16Bits = arith::ShLIOp::create(b, exti, c8);
598 Value f16 = arith::BitcastOp::create(b, f16Ty, f16Bits);
599
600 Value result = f16;
601 if (!resultETy.isF16()) {
602 if (resultETy.getIntOrFloatBitWidth() < 16)
603 result = arith::TruncFOp::create(b, resultTy, f16, nullptr,
604 op.getFastmathAttr());
605 else
606 result = arith::ExtFOp::create(b, resultTy, f16, op.getFastmathAttr());
607 }
608 rewriter.replaceOp(op, result);
609 return success();
610 }
611};
612
613/// Expand a TruncF to F8E5M2. The input is first reduced to F16 (which shares
614/// the F8E5M2 exponent layout and bias) using the native truncf, then the low
615/// 8 mantissa bits of the F16 value are dropped with round-to-nearest-even.
616/// The rounding-bias trick is borrowed from the BF16 converter: adding the
617/// bias may carry into the exponent field, which is exactly the desired
618/// behavior since F16 and F8E5M2 share the same exponent bias. NaN is handled
619/// separately.
620struct F8E5M2TruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
621 using Base::Base;
622 LogicalResult matchAndRewrite(arith::TruncFOp op,
623 PatternRewriter &rewriter) const final {
624 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
625 Value operand = op.getOperand();
626 Type operandTy = operand.getType();
627 Type resultTy = op.getType();
628 Type operandETy = getElementTypeOrSelf(operandTy);
629 Type resultETy = getElementTypeOrSelf(resultTy);
630
631 if (!llvm::isa<Float8E5M2Type>(resultETy))
632 return rewriter.notifyMatchFailure(op, "not a trunc to F8E5M2");
633 if (op.getRoundingmodeAttr())
634 return rewriter.notifyMatchFailure(
635 op, "only applicable to default rounding mode.");
636
637 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
638 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
639 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
640
641 Value h16 = operand;
642 if (!operandETy.isF16())
643 h16 = arith::TruncFOp::create(b, f16Ty, operand, nullptr,
644 op.getFastmathAttr());
645
646 Value isNan = arith::CmpFOp::create(b, arith::CmpFPredicate::UNE, h16, h16);
647 Value h16Bits = arith::BitcastOp::create(b, i16Ty, h16);
648 // Rounding bias constants for dropping the low 8 mantissa bits.
649 Value c7F = createConst(op.getLoc(), i16Ty, 0x7f, rewriter);
650 Value c8 = createConst(op.getLoc(), i16Ty, 8, rewriter);
651 Value c1 = createConst(op.getLoc(), i16Ty, 1, rewriter);
652 Value bit8 =
653 arith::AndIOp::create(b, arith::ShRUIOp::create(b, h16Bits, c8), c1);
654 Value roundingBias = arith::AddIOp::create(b, bit8, c7F);
655 Value biased = arith::AddIOp::create(b, h16Bits, roundingBias);
656 Value biasedAndShifted = arith::ShRUIOp::create(b, biased, c8);
657 Value normalCaseResult = arith::TruncIOp::create(b, i8Ty, biasedAndShifted);
658 // Quiet NaN for F8E5M2 (exponent all ones, mantissa MSB set).
659 Value cNan = createConst(op.getLoc(), i8Ty, 0x7e, rewriter);
660 Value select = arith::SelectOp::create(b, isNan, cNan, normalCaseResult);
661 Value result = arith::BitcastOp::create(b, resultTy, select);
662 rewriter.replaceOp(op, result);
663 return success();
664 }
665};
666
667/// Expand an ExtF from F8E4M3FN. F8E4M3FN uses a 4-bit exponent with bias 7, a
668/// 3-bit mantissa, no infinities and a single NaN encoding (S.1111.111). The
669/// 7 magnitude bits (EEEE.MMM) are placed into the high mantissa/exponent bits
670/// of an F16 by shifting left by 7, producing an F16 whose value equals the
671/// desired magnitude scaled by 2^-8 (the F16 bias is 15 while F8E4M3FN's is
672/// 7). Multiplying by 256 in F32 recovers the true magnitude for both normal
673/// and subnormal inputs. The sign bit and the NaN encoding are re-applied
674/// explicitly.
675struct F8E4M3FNExtFOpConverter : public OpRewritePattern<arith::ExtFOp> {
676 using Base::Base;
677 LogicalResult matchAndRewrite(arith::ExtFOp op,
678 PatternRewriter &rewriter) const final {
679 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
680 Value operand = op.getOperand();
681 Type operandTy = operand.getType();
682 Type resultTy = op.getType();
683 Type operandETy = getElementTypeOrSelf(operandTy);
684 Type resultETy = getElementTypeOrSelf(resultTy);
685
686 if (!llvm::isa<Float8E4M3FNType>(operandETy))
687 return rewriter.notifyMatchFailure(op, "not a ext of F8E4M3FN");
688
689 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
690 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
691 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
692 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
693 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
694
695 Value bits = arith::BitcastOp::create(b, i8Ty, operand);
696 Value c7F8 = createConst(op.getLoc(), i8Ty, 0x7f, rewriter);
697 Value mag8 = arith::AndIOp::create(b, bits, c7F8);
698 // Build an F16 equal to the magnitude times 2^-8.
699 Value mag16 = arith::ExtUIOp::create(b, i16Ty, mag8);
700 Value c7 = createConst(op.getLoc(), i16Ty, 7, rewriter);
701 Value g16Bits = arith::ShLIOp::create(b, mag16, c7);
702 Value g16 = arith::BitcastOp::create(b, f16Ty, g16Bits);
703 Value gF32 = arith::ExtFOp::create(b, f32Ty, g16, op.getFastmathAttr());
704 Value c256 =
705 createFloatConst(op.getLoc(), f32Ty, APFloat(256.0f), rewriter);
706 Value magF32 = arith::MulFOp::create(b, gF32, c256, op.getFastmathAttr());
707 // Re-apply the sign bit into the F32 result.
708 Value magI32 = arith::BitcastOp::create(b, i32Ty, magF32);
709 Value c80I8 = createConst(op.getLoc(), i8Ty, 0x80, rewriter);
710 Value sign8 = arith::AndIOp::create(b, bits, c80I8);
711 Value sign32 = arith::ExtUIOp::create(b, i32Ty, sign8);
712 Value c24 = createConst(op.getLoc(), i32Ty, 24, rewriter);
713 Value signBit = arith::ShLIOp::create(b, sign32, c24);
714 Value signedI32 = arith::OrIOp::create(b, magI32, signBit);
715 Value signedF32 = arith::BitcastOp::create(b, f32Ty, signedI32);
716 // NaN encoding is magnitude == 0x7f.
717 Value isNan =
718 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, mag8, c7F8);
719 Value cNan32 = createConst(op.getLoc(), i32Ty, 0x7fc00000, rewriter);
720 Value nanSigned = arith::OrIOp::create(b, cNan32, signBit);
721 Value nanF32 = arith::BitcastOp::create(b, f32Ty, nanSigned);
722 Value resultF32 = arith::SelectOp::create(b, isNan, nanF32, signedF32);
723
724 Value result = resultF32;
725 if (!resultETy.isF32()) {
726 if (resultETy.getIntOrFloatBitWidth() < 32)
727 result = arith::TruncFOp::create(b, resultTy, resultF32, nullptr,
728 op.getFastmathAttr());
729 else
730 result =
731 arith::ExtFOp::create(b, resultTy, resultF32, op.getFastmathAttr());
732 }
733 rewriter.replaceOp(op, result);
734 return success();
735 }
736};
737
738/// Expand a TruncF to F8E4M3FN. The magnitude is scaled by 2^-8 and reduced to
739/// F16 (undoing the bias difference so the F16 value equals the magnitude times
740/// 2^-8). The low 7 bits of the F16 encoding are then dropped with
741/// round-to-nearest-even to recover the 7 magnitude bits. F8E4M3FN has no
742/// infinity, so any input that overflows the maximum representable magnitude
743/// (448), as well as infinities and NaNs, maps to the F8E4M3FN NaN encoding to
744/// match the LLVM APFloat NanOnly overflow behavior and the OCP FP8 (E4M3)
745/// spec.
746struct F8E4M3FNTruncFOpConverter : public OpRewritePattern<arith::TruncFOp> {
747 using Base::Base;
748 LogicalResult matchAndRewrite(arith::TruncFOp op,
749 PatternRewriter &rewriter) const final {
750 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
751 Value operand = op.getOperand();
752 Type operandTy = operand.getType();
753 Type resultTy = op.getType();
754 Type operandETy = getElementTypeOrSelf(operandTy);
755 Type resultETy = getElementTypeOrSelf(resultTy);
756
757 if (!llvm::isa<Float8E4M3FNType>(resultETy))
758 return rewriter.notifyMatchFailure(op, "not a trunc to F8E4M3FN");
759 if (op.getRoundingmodeAttr())
760 return rewriter.notifyMatchFailure(
761 op, "only applicable to default rounding mode.");
762
763 Type i8Ty = cloneToShapedType(operandTy, b.getI8Type());
764 Type i16Ty = cloneToShapedType(operandTy, b.getI16Type());
765 Type i32Ty = cloneToShapedType(operandTy, b.getI32Type());
766 Type f16Ty = cloneToShapedType(operandTy, b.getF16Type());
767 Type f32Ty = cloneToShapedType(operandTy, b.getF32Type());
768
769 Value f32 = operand;
770 if (!operandETy.isF32()) {
771 if (operandETy.getIntOrFloatBitWidth() < 32)
772 f32 = arith::ExtFOp::create(b, f32Ty, operand, op.getFastmathAttr());
773 else
774 f32 = arith::TruncFOp::create(b, f32Ty, operand, nullptr,
775 op.getFastmathAttr());
776 }
777
778 Value isNan = arith::CmpFOp::create(b, arith::CmpFPredicate::UNE, f32, f32);
779 // Split sign and magnitude.
780 Value f32Bits = arith::BitcastOp::create(b, i32Ty, f32);
781 Value cSignMask = createConst(op.getLoc(), i32Ty, 0x80000000, rewriter);
782 Value cAbsMask = createConst(op.getLoc(), i32Ty, 0x7fffffff, rewriter);
783 Value signBits = arith::AndIOp::create(b, f32Bits, cSignMask);
784 Value absBits = arith::AndIOp::create(b, f32Bits, cAbsMask);
785 Value absF32 = arith::BitcastOp::create(b, f32Ty, absBits);
786 // F8E4M3FN has no infinity: a magnitude above the round-to-nearest-even
787 // overflow boundary (464 = 448 + half an ulp) or an infinity maps to NaN.
788 Value cOverflow =
789 createFloatConst(op.getLoc(), f32Ty, APFloat(464.0f), rewriter);
790 Value isOverflow =
791 arith::CmpFOp::create(b, arith::CmpFPredicate::OGT, absF32, cOverflow);
792 // Clamp to the F8E4M3FN maximum magnitude (448) so the finite path stays
793 // well-defined; overflowing inputs are replaced by NaN below.
794 Value cMax =
795 createFloatConst(op.getLoc(), f32Ty, APFloat(448.0f), rewriter);
796 absF32 = arith::MinNumFOp::create(b, absF32, cMax);
797 Value cInv256 =
798 createFloatConst(op.getLoc(), f32Ty, APFloat(0.00390625f), rewriter);
799 Value scaled = arith::MulFOp::create(b, absF32, cInv256, nullptr);
800 Value h16 = arith::TruncFOp::create(b, f16Ty, scaled, nullptr,
801 op.getFastmathAttr());
802 Value h16Bits = arith::BitcastOp::create(b, i16Ty, h16);
803 // Drop the low 7 bits with round-to-nearest-even.
804 Value c3F = createConst(op.getLoc(), i16Ty, 0x3f, rewriter);
805 Value c7 = createConst(op.getLoc(), i16Ty, 7, rewriter);
806 Value c1 = createConst(op.getLoc(), i16Ty, 1, rewriter);
807 Value bit7 =
808 arith::AndIOp::create(b, arith::ShRUIOp::create(b, h16Bits, c7), c1);
809 Value roundingBias = arith::AddIOp::create(b, bit7, c3F);
810 Value biased = arith::AddIOp::create(b, h16Bits, roundingBias);
811 Value shifted = arith::ShRUIOp::create(b, biased, c7);
812 Value mag8 = arith::TruncIOp::create(b, i8Ty, shifted);
813 Value c7F8 = createConst(op.getLoc(), i8Ty, 0x7f, rewriter);
814 mag8 = arith::AndIOp::create(b, mag8, c7F8);
815 // Re-apply the sign.
816 Value c24 = createConst(op.getLoc(), i32Ty, 24, rewriter);
817 Value sign8 = arith::TruncIOp::create(
818 b, i8Ty, arith::ShRUIOp::create(b, signBits, c24));
819 Value res8 = arith::OrIOp::create(b, mag8, sign8);
820 // NaN input or an overflowing/infinite magnitude maps to the NaN encoding.
821 Value isNanOrOverflow = arith::OrIOp::create(b, isNan, isOverflow);
822 Value cNan8 = createConst(op.getLoc(), i8Ty, 0x7f, rewriter);
823 Value res = arith::SelectOp::create(b, isNanOrOverflow, cNan8, res8);
824 Value result = arith::BitcastOp::create(b, resultTy, res);
825 rewriter.replaceOp(op, result);
826 return success();
827 }
828};
829
830struct ScalingExtFOpConverter : public OpRewritePattern<arith::ScalingExtFOp> {
831 using Base::Base;
832 LogicalResult matchAndRewrite(arith::ScalingExtFOp op,
833 PatternRewriter &rewriter) const final {
834 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
835 Value inputOperand = op.getIn();
836 Value scaleOperand = op.getScale();
837 Type scaleTy = scaleOperand.getType();
838 Type scaleETy = getElementTypeOrSelf(scaleOperand);
839 // allow implicit exponent extraction from 16/32 bits floats
840 if (scaleETy.getIntOrFloatBitWidth() >= 16) {
841 scaleETy = b.getF8E8M0Type();
842 scaleTy = cloneToShapedType(scaleTy, scaleETy);
843 scaleOperand = arith::TruncFOp::create(b, scaleTy, scaleOperand, nullptr,
844 op.getFastmathAttr());
845 }
846 // Catch scale types like f8E5M2.
847 if (!llvm::isa<Float8E8M0FNUType>(scaleETy)) {
848 return rewriter.notifyMatchFailure(
849 op, "scaling_extf is using scales of type which can not be converted "
850 "to f8E8M0FNU");
851 }
852 Type resultTy = op.getType();
853 // extf on scale will essentially create floating point number
854 // of type resulTy that is 2^scale and will also propagate NaNs
855 Value scaleExt =
856 arith::ExtFOp::create(b, resultTy, scaleOperand, op.getFastmathAttr());
857 Value inputExt =
858 arith::ExtFOp::create(b, resultTy, inputOperand, op.getFastmathAttr());
859 Value result =
860 arith::MulFOp::create(b, inputExt, scaleExt, op.getFastmathAttr());
861 rewriter.replaceOp(op, result);
862 return success();
863 }
864};
865
866/*
867Expands arith.ScalingTruncFOp(in, scale) into
868 scale = arith.truncf(scale) : scaleTy -> f8E8M0FNU
869 result = arith.truncf(in / (2^scale))
870 */
871struct ScalingTruncFOpConverter
872 : public OpRewritePattern<arith::ScalingTruncFOp> {
873 using Base::Base;
874 LogicalResult matchAndRewrite(arith::ScalingTruncFOp op,
875 PatternRewriter &rewriter) const final {
876 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
877 Value inputOperand = op.getIn();
878 Value scaleOperand = op.getScale();
879 Type scaleTy = scaleOperand.getType();
880 Type scaleETy = getElementTypeOrSelf(scaleOperand);
881 // allow implicit exponent extraction from 16/32 bits floats
882 if (scaleETy.getIntOrFloatBitWidth() >= 16) {
883 scaleETy = b.getF8E8M0Type();
884 scaleTy = cloneToShapedType(scaleTy, scaleETy);
885 scaleOperand = arith::TruncFOp::create(b, scaleTy, scaleOperand, nullptr,
886 op.getFastmathAttr());
887 }
888 if (!llvm::isa<Float8E8M0FNUType>(scaleETy)) {
889 return rewriter.notifyMatchFailure(
890 op, "scaling_truncf is using scales type which can not be converted "
891 "to f8E8M0FNU");
892 }
893 Type resultTy = op.getType();
894 Type inputTy = inputOperand.getType();
895 // this will create a floating point number of type
896 // inputTy that is 2^scale and will also propagate NaNs
897 scaleOperand =
898 arith::ExtFOp::create(b, inputTy, scaleOperand, op.getFastmathAttr());
899 Value result = arith::DivFOp::create(b, inputOperand, scaleOperand,
900 op.getFastmathAttr());
901 Value resultCast = arith::TruncFOp::create(
902 b, resultTy, result, op.getRoundingmodeAttr(), op.getFastmathAttr());
903 rewriter.replaceOp(op, resultCast);
904 return success();
905 }
906};
907
908/// Expands `arith.flush_denormals` into integer arithmetic.
909///
910/// For an IEEE-like floating-point value with a sign|exponent|mantissa bit
911/// layout, a value is denormal iff its biased exponent field is zero and its
912/// stored mantissa is non-zero. When the exponent field is zero, the value is
913/// either pos/neg 0 (mantissa = 0) or a denormal (mantissa != 0); in both
914/// cases, clearing the mantissa bits produces the desired sign-preserved zero
915/// (a no-op for pos/neg 0, a flush for denormals). When the exponent field is
916/// non-zero, the value passes through unchanged.
917///
918/// Pseudocode:
919/// bits = bitcast(x, iN)
920/// expIsZero = (bits & expMask) == 0
921/// cleared = bits & ~manMask
922/// resultBits = select(expIsZero, cleared, bits)
923/// result = bitcast(resultBits, floatTy)
924struct FlushDenormalsOpConverter
925 : public OpRewritePattern<arith::FlushDenormalsOp> {
926 using Base::Base;
927 LogicalResult matchAndRewrite(arith::FlushDenormalsOp op,
928 PatternRewriter &rewriter) const final {
929 Location loc = op.getLoc();
930 ImplicitLocOpBuilder b(loc, rewriter);
931 Value operand = op.getOperand();
932 Type operandTy = operand.getType();
933 auto floatTy = dyn_cast<FloatType>(getElementTypeOrSelf(operandTy));
934 if (!floatTy)
935 return rewriter.notifyMatchFailure(op, "operand is not a float type");
936
937 const llvm::fltSemantics &sem = floatTy.getFloatSemantics();
938 // Restrict to IEEE-like encodings, where the sign bit is the MSB and
939 // denormals are exactly "biased exponent == 0 and non-zero mantissa".
940 if (!llvm::APFloatBase::isIEEELikeFP(sem))
941 return rewriter.notifyMatchFailure(
942 op, "only IEEE-like floating-point types are supported");
943
944 unsigned totalBits = llvm::APFloatBase::semanticsSizeInBits(sem);
945 unsigned precision = llvm::APFloatBase::semanticsPrecision(sem);
946 // Stored mantissa bits = precision - 1 (implicit leading bit not stored).
947 // Exponent field bits = totalBits - 1 (sign) - storedMantissa.
948 if (precision < 1 || precision > totalBits)
949 return rewriter.notifyMatchFailure(op, "unexpected float semantics");
950 unsigned mantissaBits = precision - 1;
951 unsigned expBits = totalBits - 1 - mantissaBits;
952 if (expBits == 0 || mantissaBits == 0)
953 return rewriter.notifyMatchFailure(
954 op, "degenerate float encoding has no exponent or mantissa");
955
956 Type intTy =
957 cloneToShapedType(operandTy, rewriter.getIntegerType(totalBits));
958 Value bits = arith::BitcastOp::create(b, intTy, operand);
959 APInt expMaskVal =
960 APInt::getBitsSet(totalBits, mantissaBits, mantissaBits + expBits);
961 APInt clearMantissaMaskVal = ~APInt::getLowBitsSet(totalBits, mantissaBits);
962 APInt zeroVal = APInt::getZero(totalBits);
963 Value expMask = createAPIntConst(loc, intTy, expMaskVal, rewriter);
964 Value clearMantissaMask =
965 createAPIntConst(loc, intTy, clearMantissaMaskVal, rewriter);
966 Value zero = createAPIntConst(loc, intTy, zeroVal, rewriter);
967
968 // expField == 0
969 Value expField = arith::AndIOp::create(b, bits, expMask);
970 Value expIsZero =
971 arith::CmpIOp::create(b, arith::CmpIPredicate::eq, expField, zero);
972
973 // Clear mantissa bits: when exp == 0, this produces pos/neg 0.0.
974 Value cleared = arith::AndIOp::create(b, bits, clearMantissaMask);
975 Value resultBits = arith::SelectOp::create(b, expIsZero, cleared, bits);
976 Value result = arith::BitcastOp::create(b, operandTy, resultBits);
977
978 rewriter.replaceOp(op, result);
979 return success();
980 }
981};
982
983struct ArithExpandOpsPass
984 : public arith::impl::ArithExpandOpsPassBase<ArithExpandOpsPass> {
985 using ArithExpandOpsPassBase::ArithExpandOpsPassBase;
986
987 void runOnOperation() override {
988 RewritePatternSet patterns(&getContext());
989 ConversionTarget target(getContext());
990
991 arith::populateCeilFloorDivExpandOpsPatterns(patterns);
992 arith::populateExpandScalingExtTruncPatterns(patterns);
993
994 target.addLegalDialect<arith::ArithDialect>();
995 target.addLegalDialect<vector::VectorDialect>();
996
997 // clang-format off
998 target.addIllegalOp<
999 arith::CeilDivSIOp,
1000 arith::CeilDivUIOp,
1001 arith::FloorDivSIOp,
1002 arith::ScalingExtFOp,
1003 arith::ScalingTruncFOp
1004 >();
1005 // clang-format on
1006
1007 // The min/max ops also have a direct arith-to-llvm lowering to the
1008 // `llvm.intr.maximum`/`minimum`/... (and smax/umax/...) intrinsics, which
1009 // are a single hardware instruction on many targets. Only expand them into
1010 // cmpf/cmpi + select when requested, so pipelines that run arith-to-llvm
1011 // can keep the intrinsic lowering. The float and integer ops are gated
1012 // separately.
1013 if (includeMinMaxF) {
1014 arith::populateExpandMinMaxFPatterns(patterns);
1015 // clang-format off
1016 target.addIllegalOp<
1017 arith::MaximumFOp,
1018 arith::MinimumFOp,
1019 arith::MaxNumFOp,
1020 arith::MinNumFOp
1021 >();
1022 // clang-format on
1023 }
1024 if (includeMinMaxI) {
1025 arith::populateExpandMinMaxIPatterns(patterns);
1026 // clang-format off
1027 target.addIllegalOp<
1028 arith::MaxSIOp,
1029 arith::MaxUIOp,
1030 arith::MinSIOp,
1031 arith::MinUIOp
1032 >();
1033 // clang-format on
1034 }
1035
1036 if (includeBf16)
1037 arith::populateExpandBFloat16Patterns(patterns);
1038 if (includeF8E8M0)
1039 arith::populateExpandF8E8M0Patterns(patterns);
1040 if (includeF4E2M1)
1041 arith::populateExpandF4E2M1Patterns(patterns);
1042 if (includeF8E5M2)
1043 arith::populateExpandF8E5M2Patterns(patterns);
1044 if (includeF8E4M3FN)
1045 arith::populateExpandF8E4M3FNPatterns(patterns);
1046 if (includeFlushDenormals) {
1047 arith::populateExpandFlushDenormalsPatterns(patterns);
1048 // Only IEEE-like floating-point types are expanded by the pattern;
1049 // leave `arith.flush_denormals` on other types alone.
1050 target.addDynamicallyLegalOp<arith::FlushDenormalsOp>(
1051 [](arith::FlushDenormalsOp op) {
1052 auto floatTy =
1053 dyn_cast<FloatType>(getElementTypeOrSelf(op.getType()));
1054 if (!floatTy)
1055 return true;
1056 return !llvm::APFloatBase::isIEEELikeFP(
1057 floatTy.getFloatSemantics());
1058 });
1059 }
1060
1061 target.addDynamicallyLegalOp<arith::ExtFOp>([=](arith::ExtFOp op) {
1062 Type inETy = getElementTypeOrSelf(op.getOperand().getType());
1063 Type outETy = getElementTypeOrSelf(op.getType());
1064 bool legalTypes = true;
1065 if (includeBf16)
1066 legalTypes &= !(inETy.isBF16() && outETy.isF32());
1067 if (includeF8E8M0)
1068 legalTypes &= !llvm::isa<Float8E8M0FNUType>(inETy);
1069 if (includeF4E2M1)
1070 legalTypes &= !llvm::isa<Float4E2M1FNType>(inETy);
1071 if (includeF8E5M2)
1072 legalTypes &= !llvm::isa<Float8E5M2Type>(inETy);
1073 if (includeF8E4M3FN)
1074 legalTypes &= !llvm::isa<Float8E4M3FNType>(inETy);
1075 return legalTypes;
1076 });
1077
1078 target.addDynamicallyLegalOp<arith::TruncFOp>([=](arith::TruncFOp op) {
1079 Type inETy = getElementTypeOrSelf(op.getOperand().getType());
1080 Type outETy = getElementTypeOrSelf(op.getType());
1081 bool legalTypes = true;
1082 if (includeBf16)
1083 legalTypes &= !(inETy.isF32() && outETy.isBF16());
1084 if (includeF8E8M0)
1085 legalTypes &= !(llvm::isa<Float8E8M0FNUType>(outETy));
1086 if (includeF4E2M1)
1087 legalTypes &= !llvm::isa<Float4E2M1FNType>(outETy);
1088 if (includeF8E5M2)
1089 legalTypes &= !llvm::isa<Float8E5M2Type>(outETy);
1090 if (includeF8E4M3FN)
1091 legalTypes &= !llvm::isa<Float8E4M3FNType>(outETy);
1092 return legalTypes;
1093 });
1094
1095 // clang-format on
1096 if (failed(applyPartialConversion(getOperation(), target,
1097 std::move(patterns))))
1098 signalPassFailure();
1099 }
1100};
1101
1102} // namespace
1103
1105 patterns.add<BFloat16ExtFOpConverter, BFloat16TruncFOpConverter>(
1106 patterns.getContext());
1107}
1108
1110 patterns.add<F4E2M1ExtFOpConverter, F4E2M1TruncFOpConverter>(
1111 patterns.getContext());
1112}
1113
1115 patterns.add<F8E5M2ExtFOpConverter, F8E5M2TruncFOpConverter>(
1116 patterns.getContext());
1117}
1118
1120 patterns.add<F8E4M3FNExtFOpConverter, F8E4M3FNTruncFOpConverter>(
1121 patterns.getContext());
1122}
1123
1125 patterns.add<F8E8M0ExtFOpConverter, F8E8M0TruncFOpConverter>(
1126 patterns.getContext());
1127}
1128
1130 RewritePatternSet &patterns) {
1131 patterns.add<ScalingExtFOpConverter, ScalingTruncFOpConverter>(
1132 patterns.getContext());
1133}
1134
1136 RewritePatternSet &patterns) {
1137 patterns.add<FlushDenormalsOpConverter>(patterns.getContext());
1138}
1139
1141 // clang-format off
1142 patterns.add<
1143 MaximumMinimumFOpConverter<MaximumFOp, arith::CmpFPredicate::UGT>,
1144 MaximumMinimumFOpConverter<MinimumFOp, arith::CmpFPredicate::ULT>,
1145 MaxNumMinNumFOpConverter<MaxNumFOp, arith::CmpFPredicate::UGT>,
1146 MaxNumMinNumFOpConverter<MinNumFOp, arith::CmpFPredicate::ULT>
1147 >(patterns.getContext());
1148 // clang-format on
1149}
1150
1152 // clang-format off
1153 patterns.add<
1154 MaxMinIOpConverter<MaxSIOp, arith::CmpIPredicate::sgt>,
1155 MaxMinIOpConverter<MaxUIOp, arith::CmpIPredicate::ugt>,
1156 MaxMinIOpConverter<MinSIOp, arith::CmpIPredicate::slt>,
1157 MaxMinIOpConverter<MinUIOp, arith::CmpIPredicate::ult>
1158 >(patterns.getContext());
1159 // clang-format on
1160}
1161
1166
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:853
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...