MLIR 24.0.0git
ArithOps.cpp
Go to the documentation of this file.
1//===- ArithOps.cpp - MLIR Arith dialect ops implementation -----===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include <cassert>
10#include <cstdint>
11#include <functional>
12#include <type_traits>
13#include <utility>
14
18#include "mlir/IR/Builders.h"
21#include "mlir/IR/Matchers.h"
26
27#include "llvm/ADT/APFloat.h"
28#include "llvm/ADT/APInt.h"
29#include "llvm/ADT/APSInt.h"
30#include "llvm/ADT/FloatingPointMode.h"
31#include "llvm/ADT/STLExtras.h"
32#include "llvm/ADT/SmallVector.h"
33#include "llvm/ADT/TypeSwitch.h"
34
35using namespace mlir;
36using namespace mlir::arith;
37
38/// Default rounding mode according to default LLVM floating-point environment.
39static constexpr llvm::RoundingMode kDefaultRoundingMode =
40 llvm::RoundingMode::NearestTiesToEven;
41
42//===----------------------------------------------------------------------===//
43// Pattern helpers
44//===----------------------------------------------------------------------===//
45
46static IntegerAttr
48 Attribute rhs,
49 function_ref<APInt(const APInt &, const APInt &)> binFn) {
50 const APInt &lhsVal = llvm::cast<IntegerAttr>(lhs).getValue();
51 const APInt &rhsVal = llvm::cast<IntegerAttr>(rhs).getValue();
52 APInt value = binFn(lhsVal, rhsVal);
53 return IntegerAttr::get(res.getType(), value);
54}
55
56static IntegerAttr addIntegerAttrs(PatternRewriter &builder, Value res,
57 Attribute lhs, Attribute rhs) {
58 return applyToIntegerAttrs(builder, res, lhs, rhs, std::plus<APInt>());
59}
60
61static IntegerAttr subIntegerAttrs(PatternRewriter &builder, Value res,
62 Attribute lhs, Attribute rhs) {
63 return applyToIntegerAttrs(builder, res, lhs, rhs, std::minus<APInt>());
64}
65
66static IntegerAttr mulIntegerAttrs(PatternRewriter &builder, Value res,
67 Attribute lhs, Attribute rhs) {
68 return applyToIntegerAttrs(builder, res, lhs, rhs, std::multiplies<APInt>());
69}
70
71static IntegerAttr andIntegerAttrs(PatternRewriter &builder, Value res,
72 Attribute lhs, Attribute rhs) {
73 return applyToIntegerAttrs(builder, res, lhs, rhs, std::bit_and<APInt>());
74}
75
76static IntegerAttr orIntegerAttrs(PatternRewriter &builder, Value res,
77 Attribute lhs, Attribute rhs) {
78 return applyToIntegerAttrs(builder, res, lhs, rhs, std::bit_or<APInt>());
79}
80
81static IntegerAttr xorIntegerAttrs(PatternRewriter &builder, Value res,
82 Attribute lhs, Attribute rhs) {
83 return applyToIntegerAttrs(builder, res, lhs, rhs, std::bit_xor<APInt>());
84}
85
86// Intersect overflow flags. Callers must ensure the common flags remain valid
87// after rewriting.
88static IntegerOverflowFlagsAttr
89mergeOverflowFlags(IntegerOverflowFlagsAttr val1,
90 IntegerOverflowFlagsAttr val2) {
91 return IntegerOverflowFlagsAttr::get(val1.getContext(),
92 val1.getValue() & val2.getValue());
93}
94
95using OverflowCheckedBinaryOp = APInt (APInt::*)(const APInt &, bool &) const;
96
97// For fixed-width integers, if combining the constants does not overflow, the
98// reassociated expression has the same mathematical result as the original
99// expression. Common source flags then rule out overflow in the new operation.
100static IntegerOverflowFlagsAttr mergeOverflowFlagsForReassociation(
101 IntegerOverflowFlagsAttr val1, IntegerOverflowFlagsAttr val2, Attribute lhs,
103 OverflowCheckedBinaryOp unsignedOp) {
104 IntegerOverflowFlags flags = val1.getValue() & val2.getValue();
105 if (flags == IntegerOverflowFlags::none)
106 return IntegerOverflowFlagsAttr::get(val1.getContext(), flags);
107
108 auto lhsAttr = llvm::cast<IntegerAttr>(lhs);
109 auto rhsAttr = llvm::cast<IntegerAttr>(rhs);
110 // TODO: Handle index separately; its storage width is not its target width.
111 if (isa<IndexType>(lhsAttr.getType()))
112 return IntegerOverflowFlagsAttr::get(val1.getContext(), flags);
113
114 const APInt &lhsValue = lhsAttr.getValue();
115 const APInt &rhsValue = rhsAttr.getValue();
116 bool overflow;
117 if (bitEnumContainsAny(flags, IntegerOverflowFlags::nsw)) {
118 (void)(lhsValue.*signedOp)(rhsValue, overflow);
119 if (overflow)
120 flags = bitEnumClear(flags, IntegerOverflowFlags::nsw);
121 }
122 if (bitEnumContainsAny(flags, IntegerOverflowFlags::nuw)) {
123 (void)(lhsValue.*unsignedOp)(rhsValue, overflow);
124 if (overflow)
125 flags = bitEnumClear(flags, IntegerOverflowFlags::nuw);
126 }
127 return IntegerOverflowFlagsAttr::get(val1.getContext(), flags);
128}
129
130/// Invert an integer comparison predicate.
131arith::CmpIPredicate arith::invertPredicate(arith::CmpIPredicate pred) {
132 switch (pred) {
133 case arith::CmpIPredicate::eq:
134 return arith::CmpIPredicate::ne;
135 case arith::CmpIPredicate::ne:
136 return arith::CmpIPredicate::eq;
137 case arith::CmpIPredicate::slt:
138 return arith::CmpIPredicate::sge;
139 case arith::CmpIPredicate::sle:
140 return arith::CmpIPredicate::sgt;
141 case arith::CmpIPredicate::sgt:
142 return arith::CmpIPredicate::sle;
143 case arith::CmpIPredicate::sge:
144 return arith::CmpIPredicate::slt;
145 case arith::CmpIPredicate::ult:
146 return arith::CmpIPredicate::uge;
147 case arith::CmpIPredicate::ule:
148 return arith::CmpIPredicate::ugt;
149 case arith::CmpIPredicate::ugt:
150 return arith::CmpIPredicate::ule;
151 case arith::CmpIPredicate::uge:
152 return arith::CmpIPredicate::ult;
153 }
154 llvm_unreachable("unknown cmpi predicate kind");
155}
156
157/// Equivalent to
158/// convertRoundingModeToLLVM(convertArithRoundingModeToLLVM(roundingMode)).
159///
160/// Not possible to implement as chain of calls as this would introduce a
161/// circular dependency with MLIRArithAttrToLLVMConversion and make arith depend
162/// on the LLVM dialect and on translation to LLVM.
163static llvm::RoundingMode
164convertArithRoundingModeToLLVMIR(std::optional<RoundingMode> roundingMode) {
165 if (!roundingMode)
167 switch (*roundingMode) {
168 case RoundingMode::downward:
169 return llvm::RoundingMode::TowardNegative;
170 case RoundingMode::to_nearest_away:
171 return llvm::RoundingMode::NearestTiesToAway;
172 case RoundingMode::to_nearest_even:
173 return llvm::RoundingMode::NearestTiesToEven;
174 case RoundingMode::toward_zero:
175 return llvm::RoundingMode::TowardZero;
176 case RoundingMode::upward:
177 return llvm::RoundingMode::TowardPositive;
178 }
179 llvm_unreachable("Unhandled rounding mode");
180}
181
182static arith::CmpIPredicateAttr invertPredicate(arith::CmpIPredicateAttr pred) {
183 return arith::CmpIPredicateAttr::get(pred.getContext(),
184 invertPredicate(pred.getValue()));
185}
186
188 Type elemTy = getElementTypeOrSelf(type);
189 if (elemTy.isIntOrFloat())
190 return elemTy.getIntOrFloatBitWidth();
191
192 return -1;
193}
194
196 return getScalarOrElementWidth(value.getType());
197}
198
199static FailureOr<APInt> getIntOrSplatIntValue(Attribute attr) {
200 APInt value;
201 if (matchPattern(attr, m_ConstantInt(&value)))
202 return value;
203
204 return failure();
205}
206
207static Attribute getBoolAttribute(Type type, bool value) {
208 auto boolAttr = BoolAttr::get(type.getContext(), value);
209 ShapedType shapedType = dyn_cast_or_null<ShapedType>(type);
210 if (!shapedType)
211 return boolAttr;
212 // DenseElementsAttr requires a static shape.
213 if (!shapedType.hasStaticShape())
214 return {};
215 return DenseElementsAttr::get(shapedType, boolAttr);
216}
217
218/// Return a scalar or splat integer attribute of `type` (an integer/index type
219/// or a shaped type thereof) holding `value`. Returns a null attribute for
220/// shaped types with a dynamic shape, so callers can bail out of folding.
222 auto scalarAttr = IntegerAttr::get(getElementTypeOrSelf(type), value);
223 ShapedType shapedType = dyn_cast<ShapedType>(type);
224 if (!shapedType)
225 return scalarAttr;
226 if (!shapedType.hasStaticShape())
227 return {};
228 return DenseElementsAttr::get(shapedType, scalarAttr);
229}
230
231//===----------------------------------------------------------------------===//
232// TableGen'd canonicalization patterns
233//===----------------------------------------------------------------------===//
234
235namespace {
236#include "ArithCanonicalization.inc"
237} // namespace
238
239//===----------------------------------------------------------------------===//
240// Common helpers
241//===----------------------------------------------------------------------===//
242
243/// Return the type of the same shape (scalar, vector or tensor) containing i1.
245 auto i1Type = IntegerType::get(type.getContext(), 1);
246 if (auto shapedType = dyn_cast<ShapedType>(type))
247 return shapedType.cloneWith(std::nullopt, i1Type);
248 if (llvm::isa<UnrankedTensorType>(type))
249 return UnrankedTensorType::get(i1Type);
250 return i1Type;
251}
252
253//===----------------------------------------------------------------------===//
254// ConstantOp
255//===----------------------------------------------------------------------===//
256
257void arith::ConstantOp::getAsmResultNames(
258 function_ref<void(Value, StringRef)> setNameFn) {
259 auto type = getType();
260 if (auto intCst = dyn_cast<IntegerAttr>(getValue())) {
261 auto intType = dyn_cast<IntegerType>(type);
262
263 // Sugar i1 constants with 'true' and 'false'.
264 if (intType && intType.getWidth() == 1)
265 return setNameFn(getResult(), (intCst.getInt() ? "true" : "false"));
266
267 // Otherwise, build a complex name with the value and type.
268 SmallString<32> specialNameBuffer;
269 llvm::raw_svector_ostream specialName(specialNameBuffer);
270 specialName << 'c' << intCst.getValue();
271 if (intType)
272 specialName << '_' << type;
273 setNameFn(getResult(), specialName.str());
274 } else {
275 setNameFn(getResult(), "cst");
276 }
277}
278
279/// TODO: disallow arith.constant to return anything other than signless integer
280/// or float like.
281LogicalResult arith::ConstantOp::verify() {
282 auto type = getType();
283 // Integer values must be signless.
284 if (auto intType = dyn_cast<IntegerType>(getElementTypeOrSelf(type));
285 intType && !intType.isSignless())
286 return emitOpError("integer return type must be signless");
287 // Any float or elements attribute are acceptable.
288 if (!llvm::isa<IntegerAttr, FloatAttr, ElementsAttr>(getValue())) {
289 return emitOpError(
290 "value must be an integer, float, or elements attribute");
291 }
292
293 // Note, we could relax this for vectors with 1 scalable dim, e.g.:
294 // * arith.constant dense<[[3, 3], [1, 1]]> : vector<2 x [2] x i32>
295 // However, this would most likely require updating the lowerings to LLVM.
296 if (isa<ScalableVectorType>(type) && !isa<SplatElementsAttr>(getValue()))
297 return emitOpError(
298 "initializing scalable vectors with elements attribute is not supported"
299 " unless it's a vector splat");
300 return success();
301}
302
303bool arith::ConstantOp::isBuildableWith(Attribute value, Type type) {
304 // The value's type must be the same as the provided type.
305 auto typedAttr = dyn_cast<TypedAttr>(value);
306 if (!typedAttr || typedAttr.getType() != type)
307 return false;
308 // Integer values must be signless.
309 if (auto intType = dyn_cast<IntegerType>(getElementTypeOrSelf(type))) {
310 if (!intType.isSignless())
311 return false;
312 }
313 // Integer, float, and element attributes are buildable.
314 return llvm::isa<IntegerAttr, FloatAttr, ElementsAttr>(value);
315}
316
317ConstantOp arith::ConstantOp::materialize(OpBuilder &builder, Attribute value,
318 Type type, Location loc) {
319 if (isBuildableWith(value, type))
320 return arith::ConstantOp::create(builder, loc, cast<TypedAttr>(value));
321 return nullptr;
322}
323
324OpFoldResult arith::ConstantOp::fold(FoldAdaptor adaptor) { return getValue(); }
325
327 int64_t value, unsigned width) {
328 auto type = builder.getIntegerType(width);
329 arith::ConstantOp::build(builder, result, type,
330 builder.getIntegerAttr(type, value));
331}
332
334 Location location,
336 unsigned width) {
337 mlir::OperationState state(location, getOperationName());
338 build(builder, state, value, width);
339 auto result = dyn_cast<ConstantIntOp>(builder.create(state));
340 assert(result && "builder didn't return the right type");
341 return result;
342}
343
346 unsigned width) {
347 return create(builder, builder.getLoc(), value, width);
348}
349
351 Type type, int64_t value) {
352 arith::ConstantOp::build(builder, result, type,
353 builder.getIntegerAttr(type, value));
354}
355
357 Location location, Type type,
358 int64_t value) {
359 mlir::OperationState state(location, getOperationName());
360 build(builder, state, type, value);
361 auto result = dyn_cast<ConstantIntOp>(builder.create(state));
362 assert(result && "builder didn't return the right type");
363 return result;
364}
365
367 Type type, int64_t value) {
368 return create(builder, builder.getLoc(), type, value);
369}
370
372 Type type, const APInt &value) {
373 arith::ConstantOp::build(builder, result, type,
374 builder.getIntegerAttr(type, value));
375}
376
378 Location location, Type type,
379 const APInt &value) {
380 mlir::OperationState state(location, getOperationName());
381 build(builder, state, type, value);
382 auto result = dyn_cast<ConstantIntOp>(builder.create(state));
383 assert(result && "builder didn't return the right type");
384 return result;
385}
386
388 Type type,
389 const APInt &value) {
390 return create(builder, builder.getLoc(), type, value);
391}
392
394 if (auto constOp = dyn_cast_or_null<arith::ConstantOp>(op))
395 return constOp.getType().isSignlessInteger();
396 return false;
397}
398
400 FloatType type, const APFloat &value) {
401 arith::ConstantOp::build(builder, result, type,
402 builder.getFloatAttr(type, value));
403}
404
406 Location location,
407 FloatType type,
408 const APFloat &value) {
409 mlir::OperationState state(location, getOperationName());
410 build(builder, state, type, value);
411 auto result = dyn_cast<ConstantFloatOp>(builder.create(state));
412 assert(result && "builder didn't return the right type");
413 return result;
414}
415
418 const APFloat &value) {
419 return create(builder, builder.getLoc(), type, value);
420}
421
423 if (auto constOp = dyn_cast_or_null<arith::ConstantOp>(op))
424 return llvm::isa<FloatType>(constOp.getType());
425 return false;
426}
427
429 int64_t value) {
430 arith::ConstantOp::build(builder, result, builder.getIndexType(),
431 builder.getIndexAttr(value));
432}
433
435 Location location,
436 int64_t value) {
437 mlir::OperationState state(location, getOperationName());
438 build(builder, state, value);
439 auto result = dyn_cast<ConstantIndexOp>(builder.create(state));
440 assert(result && "builder didn't return the right type");
441 return result;
442}
443
446 return create(builder, builder.getLoc(), value);
447}
448
450 if (auto constOp = dyn_cast_or_null<arith::ConstantOp>(op))
451 return constOp.getType().isIndex();
452 return false;
453}
454
456 Type type) {
457 // TODO: Incorporate this check to `FloatAttr::get*`.
458 assert(!isa<Float8E8M0FNUType>(getElementTypeOrSelf(type)) &&
459 "type doesn't have a zero representation");
460 TypedAttr zeroAttr = builder.getZeroAttr(type);
461 assert(zeroAttr && "unsupported type for zero attribute");
462 return arith::ConstantOp::create(builder, loc, zeroAttr);
463}
464
465//===----------------------------------------------------------------------===//
466// AddIOp
467//===----------------------------------------------------------------------===//
468
469OpFoldResult arith::AddIOp::fold(FoldAdaptor adaptor) {
470 // addi(x, 0) -> x
471 if (matchPattern(adaptor.getRhs(), m_Zero()))
472 return getLhs();
473
474 // addi(subi(a, b), b) -> a
475 if (auto sub = getLhs().getDefiningOp<SubIOp>())
476 if (getRhs() == sub.getRhs())
477 return sub.getLhs();
478
479 // addi(b, subi(a, b)) -> a
480 if (auto sub = getRhs().getDefiningOp<SubIOp>())
481 if (getLhs() == sub.getRhs())
482 return sub.getLhs();
483
485 adaptor.getOperands(),
486 [](APInt a, const APInt &b) { return std::move(a) + b; });
487}
488
489void arith::AddIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
490 MLIRContext *context) {
491 patterns.add<AddIAddConstant, AddISubConstantRHS, AddISubConstantLHS,
492 AddIMulNegativeOneRhs, AddIMulNegativeOneLhs>(context);
493}
494
495//===----------------------------------------------------------------------===//
496// AddUIExtendedOp
497//===----------------------------------------------------------------------===//
498
499std::optional<SmallVector<int64_t, 4>>
500arith::AddUIExtendedOp::getIterationShapeToUnroll() {
501 if (auto vt = dyn_cast<VectorType>(getType(0)))
502 return llvm::to_vector<4>(vt.getShape());
503 return std::nullopt;
504}
505
506// Returns the overflow bit, assuming that `sum` is the result of unsigned
507// addition of `operand` and another number.
508static APInt calculateUnsignedOverflow(const APInt &sum, const APInt &operand) {
509 return sum.ult(operand) ? APInt::getAllOnes(1) : APInt::getZero(1);
510}
511
512LogicalResult
513arith::AddUIExtendedOp::fold(FoldAdaptor adaptor,
514 SmallVectorImpl<OpFoldResult> &results) {
515 Type overflowTy = getOverflow().getType();
516 // addui_extended(x, 0) -> x, false
517 if (matchPattern(getRhs(), m_Zero())) {
518 Builder builder(getContext());
519 auto falseValue = builder.getZeroAttr(overflowTy);
520
521 results.push_back(getLhs());
522 results.push_back(falseValue);
523 return success();
524 }
525
526 // addui_extended(constant_a, constant_b) -> constant_sum, constant_carry
527 // Let the `constFoldBinaryOp` utility attempt to fold the sum of both
528 // operands. If that succeeds, calculate the overflow bit based on the sum
529 // and the first (constant) operand, `lhs`.
530 if (Attribute sumAttr = constFoldBinaryOp<IntegerAttr>(
531 adaptor.getOperands(),
532 [](APInt a, const APInt &b) { return std::move(a) + b; })) {
533 // If any operand is poison, propagate poison to both results.
534 if (matchPattern(sumAttr, ub::m_Poison())) {
535 results.push_back(sumAttr);
536 results.push_back(sumAttr);
537 return success();
538 }
539 Attribute overflowAttr = constFoldBinaryOp<IntegerAttr>(
540 ArrayRef({sumAttr, adaptor.getLhs()}),
541 getI1SameShape(llvm::cast<TypedAttr>(sumAttr).getType()),
543 if (!overflowAttr)
544 return failure();
545
546 results.push_back(sumAttr);
547 results.push_back(overflowAttr);
548 return success();
549 }
550
551 return failure();
552}
553
554void arith::AddUIExtendedOp::getCanonicalizationPatterns(
555 RewritePatternSet &patterns, MLIRContext *context) {
556 patterns.add<AddUIExtendedToAddI>(context);
557}
558
559//===----------------------------------------------------------------------===//
560// SubUIExtendedOp
561//===----------------------------------------------------------------------===//
562
563std::optional<SmallVector<int64_t, 4>>
564arith::SubUIExtendedOp::getIterationShapeToUnroll() {
565 if (auto vt = dyn_cast<VectorType>(getType(0)))
566 return llvm::to_vector<4>(vt.getShape());
567 return std::nullopt;
568}
569
570// Returns the borrow bit, assuming `lhs` and `rhs` are operands of an unsigned
571// subtraction whose mathematical result underflows iff `lhs < rhs`.
572static APInt calculateUnsignedBorrow(const APInt &lhs, const APInt &rhs) {
573 return lhs.ult(rhs) ? APInt::getAllOnes(1) : APInt::getZero(1);
574}
575
576LogicalResult
577arith::SubUIExtendedOp::fold(FoldAdaptor adaptor,
578 SmallVectorImpl<OpFoldResult> &results) {
579 Type borrowTy = getBorrow().getType();
580 // subui_extended(x, 0) -> x, false
581 if (matchPattern(getRhs(), m_Zero())) {
582 Builder builder(getContext());
583 auto falseValue = builder.getZeroAttr(borrowTy);
584
585 results.push_back(getLhs());
586 results.push_back(falseValue);
587 return success();
588 }
589
590 // subui_extended(x, x) -> 0, false
591 if (getLhs() == getRhs()) {
592 // A dynamically-shaped result cannot be a constant; bail before
593 // getZeroAttr, which would assert on a non-static shape.
594 auto shapedType = dyn_cast<ShapedType>(getDiff().getType());
595 if (shapedType && !shapedType.hasStaticShape())
596 return failure();
597 Builder builder(getContext());
598 auto zeroDiff = builder.getZeroAttr(getDiff().getType());
599 auto falseValue = builder.getZeroAttr(borrowTy);
600 if (!zeroDiff)
601 return failure();
602
603 results.push_back(zeroDiff);
604 results.push_back(falseValue);
605 return success();
606 }
607
608 // subui_extended(constant_a, constant_b) -> constant_diff, constant_borrow
609 if (Attribute diffAttr = constFoldBinaryOp<IntegerAttr>(
610 adaptor.getOperands(),
611 [](APInt a, const APInt &b) { return std::move(a) - b; })) {
612 // If any operand is poison, propagate poison to both results.
613 if (matchPattern(diffAttr, ub::m_Poison())) {
614 results.push_back(diffAttr);
615 results.push_back(diffAttr);
616 return success();
617 }
618 Attribute borrowAttr = constFoldBinaryOp<IntegerAttr>(
619 adaptor.getOperands(),
620 getI1SameShape(llvm::cast<TypedAttr>(diffAttr).getType()),
622 if (!borrowAttr)
623 return failure();
624
625 results.push_back(diffAttr);
626 results.push_back(borrowAttr);
627 return success();
628 }
629
630 return failure();
631}
632
633void arith::SubUIExtendedOp::getCanonicalizationPatterns(
634 RewritePatternSet &patterns, MLIRContext *context) {
635 patterns.add<SubUIExtendedToSubI>(context);
636}
637
638//===----------------------------------------------------------------------===//
639// SubIOp
640//===----------------------------------------------------------------------===//
641
642OpFoldResult arith::SubIOp::fold(FoldAdaptor adaptor) {
643 // subi(x,x) -> 0
644 if (getOperand(0) == getOperand(1)) {
645 auto shapedType = dyn_cast<ShapedType>(getType());
646 // We can't generate a constant with a dynamic shaped tensor.
647 if (!shapedType || shapedType.hasStaticShape())
648 return Builder(getContext()).getZeroAttr(getType());
649 }
650 // subi(x,0) -> x
651 if (matchPattern(adaptor.getRhs(), m_Zero()))
652 return getLhs();
653
654 if (auto add = getLhs().getDefiningOp<AddIOp>()) {
655 // subi(addi(a, b), b) -> a
656 if (getRhs() == add.getRhs())
657 return add.getLhs();
658 // subi(addi(a, b), a) -> b
659 if (getRhs() == add.getLhs())
660 return add.getRhs();
661 }
662
663 // subi(a, subi(a, b)) -> b
664 if (auto sub = getRhs().getDefiningOp<SubIOp>())
665 if (getLhs() == sub.getLhs())
666 return sub.getRhs();
667
669 adaptor.getOperands(),
670 [](APInt a, const APInt &b) { return std::move(a) - b; });
671}
672
673void arith::SubIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
674 MLIRContext *context) {
675 patterns.add<SubIRHSAddConstant, SubILHSAddConstant, SubIRHSSubConstantRHS,
676 SubIRHSSubConstantLHS, SubILHSSubConstantRHS,
677 SubILHSSubConstantLHS, SubISubILHSRHSLHS>(context);
678}
679
680//===----------------------------------------------------------------------===//
681// MulIOp
682//===----------------------------------------------------------------------===//
683
684OpFoldResult arith::MulIOp::fold(FoldAdaptor adaptor) {
685 // muli(x, 0) -> 0
686 if (matchPattern(adaptor.getRhs(), m_Zero()))
687 return getRhs();
688 // muli(x, 1) -> x
689 if (matchPattern(adaptor.getRhs(), m_One()))
690 return getLhs();
691 // TODO: Handle the overflow case.
692
693 // default folder
695 adaptor.getOperands(),
696 [](const APInt &a, const APInt &b) { return a * b; });
697}
698
699void arith::MulIOp::getAsmResultNames(
700 function_ref<void(Value, StringRef)> setNameFn) {
701 if (!isa<IndexType>(getType()))
702 return;
703
704 // Match vector.vscale by name to avoid depending on the vector dialect (which
705 // is a circular dependency).
706 auto isVscale = [](Operation *op) {
707 return op && op->getName().getStringRef() == "vector.vscale";
708 };
709
710 IntegerAttr baseValue;
711 auto isVscaleExpr = [&](Value a, Value b) {
712 return matchPattern(a, m_Constant(&baseValue)) &&
713 isVscale(b.getDefiningOp());
714 };
715
716 if (!isVscaleExpr(getLhs(), getRhs()) && !isVscaleExpr(getRhs(), getLhs()))
717 return;
718
719 // Name `base * vscale` or `vscale * base` as `c<base_value>_vscale`.
720 SmallString<32> specialNameBuffer;
721 llvm::raw_svector_ostream specialName(specialNameBuffer);
722 specialName << 'c' << baseValue.getInt() << "_vscale";
723 setNameFn(getResult(), specialName.str());
724}
725
726void arith::MulIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
727 MLIRContext *context) {
728 patterns.add<MulIMulIConstant>(context);
729}
730
731//===----------------------------------------------------------------------===//
732// MulSIExtendedOp
733//===----------------------------------------------------------------------===//
734
735std::optional<SmallVector<int64_t, 4>>
736arith::MulSIExtendedOp::getIterationShapeToUnroll() {
737 if (auto vt = dyn_cast<VectorType>(getType(0)))
738 return llvm::to_vector<4>(vt.getShape());
739 return std::nullopt;
740}
741
742LogicalResult
743arith::MulSIExtendedOp::fold(FoldAdaptor adaptor,
744 SmallVectorImpl<OpFoldResult> &results) {
745 // mulsi_extended(x, 0) -> 0, 0
746 if (matchPattern(adaptor.getRhs(), m_Zero())) {
747 Attribute zero = adaptor.getRhs();
748 results.push_back(zero);
749 results.push_back(zero);
750 return success();
751 }
752
753 // mulsi_extended(cst_a, cst_b) -> cst_low, cst_high
754 if (Attribute lowAttr = constFoldBinaryOp<IntegerAttr>(
755 adaptor.getOperands(),
756 [](const APInt &a, const APInt &b) { return a * b; })) {
757 // Invoke the constant fold helper again to calculate the 'high' result.
758 Attribute highAttr = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
759 llvm::APIntOps::mulhs);
760 assert(highAttr && "Unexpected constant-folding failure");
761
762 results.push_back(lowAttr);
763 results.push_back(highAttr);
764 return success();
765 }
766
767 return failure();
768}
769
770void arith::MulSIExtendedOp::getCanonicalizationPatterns(
771 RewritePatternSet &patterns, MLIRContext *context) {
772 patterns.add<MulSIExtendedToMulI, MulSIExtendedRHSOne>(context);
773}
774
775//===----------------------------------------------------------------------===//
776// MulUIExtendedOp
777//===----------------------------------------------------------------------===//
778
779std::optional<SmallVector<int64_t, 4>>
780arith::MulUIExtendedOp::getIterationShapeToUnroll() {
781 if (auto vt = dyn_cast<VectorType>(getType(0)))
782 return llvm::to_vector<4>(vt.getShape());
783 return std::nullopt;
784}
785
786LogicalResult
787arith::MulUIExtendedOp::fold(FoldAdaptor adaptor,
788 SmallVectorImpl<OpFoldResult> &results) {
789 // mului_extended(x, 0) -> 0, 0
790 if (matchPattern(adaptor.getRhs(), m_Zero())) {
791 Attribute zero = adaptor.getRhs();
792 results.push_back(zero);
793 results.push_back(zero);
794 return success();
795 }
796
797 // mului_extended(x, 1) -> x, 0
798 if (matchPattern(adaptor.getRhs(), m_One())) {
799 Builder builder(getContext());
800 Attribute zero = builder.getZeroAttr(getLhs().getType());
801 results.push_back(getLhs());
802 results.push_back(zero);
803 return success();
804 }
805
806 // mului_extended(cst_a, cst_b) -> cst_low, cst_high
807 if (Attribute lowAttr = constFoldBinaryOp<IntegerAttr>(
808 adaptor.getOperands(),
809 [](const APInt &a, const APInt &b) { return a * b; })) {
810 // Invoke the constant fold helper again to calculate the 'high' result.
811 Attribute highAttr = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
812 llvm::APIntOps::mulhu);
813 assert(highAttr && "Unexpected constant-folding failure");
814
815 results.push_back(lowAttr);
816 results.push_back(highAttr);
817 return success();
818 }
819
820 return failure();
821}
822
823void arith::MulUIExtendedOp::getCanonicalizationPatterns(
824 RewritePatternSet &patterns, MLIRContext *context) {
825 patterns.add<MulUIExtendedToMulI>(context);
826}
827
828//===----------------------------------------------------------------------===//
829// DivUIOp
830//===----------------------------------------------------------------------===//
831
832/// Fold `(a * b) / b -> a`
833static Value foldDivMul(Value lhs, Value rhs,
834 arith::IntegerOverflowFlags ovfFlags) {
835 auto mul = lhs.getDefiningOp<mlir::arith::MulIOp>();
836 if (!mul || !bitEnumContainsAll(mul.getOverflowFlags(), ovfFlags))
837 return {};
838
839 if (mul.getLhs() == rhs)
840 return mul.getRhs();
841
842 if (mul.getRhs() == rhs)
843 return mul.getLhs();
844
845 return {};
846}
847
848OpFoldResult arith::DivUIOp::fold(FoldAdaptor adaptor) {
849 // TODO: divui (x, 0) -> poison. Division by zero is undefined behaviour and
850 // could fold to poison, but that would make the arith dialect depend on the
851 // ub dialect to materialize ub.poison; left out for now.
852
853 // divui (x, 1) -> x.
854 if (matchPattern(adaptor.getRhs(), m_One()))
855 return getLhs();
856
857 // divui (0, x) -> 0. Division by zero is UB, so refining to 0 is valid.
858 if (matchPattern(adaptor.getLhs(), m_Zero()))
859 return getLhs();
860
861 // divui (x, x) -> 1.
862 if (getLhs() == getRhs())
863 return getIntegerAttrOfType(getType(), 1);
864
865 // (a * b) / b -> a
866 if (Value val = foldDivMul(getLhs(), getRhs(), IntegerOverflowFlags::nuw))
867 return val;
868
869 // Don't fold if it would require a division by zero.
870 bool div0 = false;
871 auto result = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
872 [&](APInt a, const APInt &b) {
873 if (div0 || !b) {
874 div0 = true;
875 return a;
876 }
877 return a.udiv(b);
878 });
879
880 return div0 ? Attribute() : result;
881}
882
883/// Returns whether an unsigned division by `divisor` is speculatable.
885 // X / 0 => UB
886 if (matchPattern(divisor, m_IntRangeWithoutZeroU()))
888
890}
891
892Speculation::Speculatability arith::DivUIOp::getSpeculatability() {
893 return getDivUISpeculatability(getRhs());
894}
895
896//===----------------------------------------------------------------------===//
897// DivSIOp
898//===----------------------------------------------------------------------===//
899
900OpFoldResult arith::DivSIOp::fold(FoldAdaptor adaptor) {
901 // TODO: divsi (x, 0) -> poison. Division by zero is undefined behaviour and
902 // could fold to poison, but that would make the arith dialect depend on the
903 // ub dialect to materialize ub.poison; left out for now.
904
905 // divsi (x, 1) -> x.
906 if (matchPattern(adaptor.getRhs(), m_One()))
907 return getLhs();
908
909 // divsi (0, x) -> 0. Division by zero is UB, so refining to 0 is valid.
910 if (matchPattern(adaptor.getLhs(), m_Zero()))
911 return getLhs();
912
913 // divsi (x, x) -> 1.
914 if (getLhs() == getRhs())
915 return getIntegerAttrOfType(getType(), 1);
916
917 // (a * b) / b -> a
918 if (Value val = foldDivMul(getLhs(), getRhs(), IntegerOverflowFlags::nsw))
919 return val;
920
921 // Don't fold if it would overflow or if it requires a division by zero.
922 bool overflowOrDiv0 = false;
924 adaptor.getOperands(), [&](APInt a, const APInt &b) {
925 if (overflowOrDiv0 || !b) {
926 overflowOrDiv0 = true;
927 return a;
928 }
929 return a.sdiv_ov(b, overflowOrDiv0);
930 });
931
932 return overflowOrDiv0 ? Attribute() : result;
933}
934
935/// Returns whether a signed division by `divisor` is speculatable. This
936/// function conservatively assumes that all signed division by -1 are not
937/// speculatable.
939 // X / 0 => UB
940 // INT_MIN / -1 => UB
941 if (matchPattern(divisor, m_IntRangeWithoutZeroS()) &&
944
946}
947
948Speculation::Speculatability arith::DivSIOp::getSpeculatability() {
949 return getDivSISpeculatability(getRhs());
950}
951
952//===----------------------------------------------------------------------===//
953// CeilDivUIOp
954//===----------------------------------------------------------------------===//
955
956OpFoldResult arith::CeilDivUIOp::fold(FoldAdaptor adaptor) {
957 // TODO: ceildivui (x, 0) -> poison. Division by zero is undefined behaviour
958 // and could fold to poison, but that would make the arith dialect depend on
959 // the ub dialect to materialize ub.poison; left out for now.
960
961 // ceildivui (x, 1) -> x.
962 if (matchPattern(adaptor.getRhs(), m_One()))
963 return getLhs();
964
965 // ceildivui (0, x) -> 0. Division by zero is UB, so refining to 0 is valid.
966 if (matchPattern(adaptor.getLhs(), m_Zero()))
967 return getLhs();
968
969 // ceildivui (x, x) -> 1.
970 if (getLhs() == getRhs())
971 return getIntegerAttrOfType(getType(), 1);
972
973 bool overflowOrDiv0 = false;
975 adaptor.getOperands(), [&](APInt a, const APInt &b) {
976 if (overflowOrDiv0 || !b) {
977 overflowOrDiv0 = true;
978 return a;
979 }
980 APInt quotient = a.udiv(b);
981 if (!a.urem(b))
982 return quotient;
983 APInt one(a.getBitWidth(), 1, true);
984 return quotient.uadd_ov(one, overflowOrDiv0);
985 });
986
987 return overflowOrDiv0 ? Attribute() : result;
988}
989
990Speculation::Speculatability arith::CeilDivUIOp::getSpeculatability() {
991 return getDivUISpeculatability(getRhs());
992}
993
994//===----------------------------------------------------------------------===//
995// CeilDivSIOp
996//===----------------------------------------------------------------------===//
997
998OpFoldResult arith::CeilDivSIOp::fold(FoldAdaptor adaptor) {
999 // TODO: ceildivsi (x, 0) -> poison. Division by zero is undefined behaviour
1000 // and could fold to poison, but that would make the arith dialect depend on
1001 // the ub dialect to materialize ub.poison; left out for now.
1002
1003 // ceildivsi (x, 1) -> x.
1004 if (matchPattern(adaptor.getRhs(), m_One()))
1005 return getLhs();
1006
1007 // ceildivsi (0, x) -> 0. Division by zero is UB, so refining to 0 is valid.
1008 if (matchPattern(adaptor.getLhs(), m_Zero()))
1009 return getLhs();
1010
1011 // ceildivsi (x, x) -> 1.
1012 if (getLhs() == getRhs())
1013 return getIntegerAttrOfType(getType(), 1);
1014
1015 // Don't fold if it would overflow or if it requires a division by zero.
1016 bool overflowOrDiv0 = false;
1018 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1019 if (overflowOrDiv0 || !b) {
1020 overflowOrDiv0 = true;
1021 return a;
1022 }
1023 // Compute the ceiling without negating either operand, so that MININT
1024 // operands still fold whenever the result is representable.
1025 //
1026 // sdiv truncates towards zero, so it already rounds up whenever the
1027 // exact quotient is negative. When the exact quotient is positive, i.e.
1028 // when the operands have the same sign, an inexact division has to be
1029 // corrected by one. This mirrors the expansion in ExpandOps.cpp.
1030 bool overflowDiv = false;
1031 APInt quotient = a.sdiv_ov(b, overflowDiv);
1032 if (overflowDiv) {
1033 // MININT / -1. The exact result is -MININT, which is not
1034 // representable.
1035 overflowOrDiv0 = true;
1036 return a;
1037 }
1038 if (a.isNegative() != b.isNegative() || quotient * b == a)
1039 return quotient;
1040
1041 // The correction cannot overflow: it only applies when the exact
1042 // quotient is positive and the division is inexact, which bounds the
1043 // quotient well below the maximum. Check anyway, at no cost.
1044 APInt one(a.getBitWidth(), 1, /*isSigned=*/true);
1045 return quotient.sadd_ov(one, overflowOrDiv0);
1046 });
1047
1048 return overflowOrDiv0 ? Attribute() : result;
1049}
1050
1051Speculation::Speculatability arith::CeilDivSIOp::getSpeculatability() {
1052 return getDivSISpeculatability(getRhs());
1053}
1054
1055//===----------------------------------------------------------------------===//
1056// FloorDivSIOp
1057//===----------------------------------------------------------------------===//
1058
1059OpFoldResult arith::FloorDivSIOp::fold(FoldAdaptor adaptor) {
1060 // TODO: floordivsi (x, 0) -> poison. Division by zero is undefined behaviour
1061 // and could fold to poison, but that would make the arith dialect depend on
1062 // the ub dialect to materialize ub.poison; left out for now.
1063
1064 // floordivsi (x, 1) -> x.
1065 if (matchPattern(adaptor.getRhs(), m_One()))
1066 return getLhs();
1067
1068 // floordivsi (0, x) -> 0. Division by zero is UB, so refining to 0 is valid.
1069 if (matchPattern(adaptor.getLhs(), m_Zero()))
1070 return getLhs();
1071
1072 // floordivsi (x, x) -> 1.
1073 if (getLhs() == getRhs())
1074 return getIntegerAttrOfType(getType(), 1);
1075
1076 // Don't fold if it would overflow or if it requires a division by zero.
1077 bool overflowOrDiv = false;
1079 adaptor.getOperands(), [&](APInt a, const APInt &b) {
1080 if (b.isZero()) {
1081 overflowOrDiv = true;
1082 return a;
1083 }
1084 return a.sfloordiv_ov(b, overflowOrDiv);
1085 });
1086
1087 return overflowOrDiv ? Attribute() : result;
1088}
1089
1090//===----------------------------------------------------------------------===//
1091// RemUIOp
1092//===----------------------------------------------------------------------===//
1093
1094OpFoldResult arith::RemUIOp::fold(FoldAdaptor adaptor) {
1095 // TODO: remui (x, 0) -> poison. Remainder by zero is undefined behaviour and
1096 // could fold to poison, but that would make the arith dialect depend on the
1097 // ub dialect to materialize ub.poison; left out for now.
1098
1099 // remui (x, 1) -> 0.
1100 if (matchPattern(adaptor.getRhs(), m_One()))
1101 return getIntegerAttrOfType(getType(), 0);
1102
1103 // remui (0, x) -> 0 and remui (x, x) -> 0. Division by zero is UB, so
1104 // refining to 0 is valid.
1105 if (matchPattern(adaptor.getLhs(), m_Zero()) || getLhs() == getRhs())
1106 return getIntegerAttrOfType(getType(), 0);
1107
1108 // Don't fold if it would require a division by zero.
1109 bool div0 = false;
1110 auto result = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1111 [&](APInt a, const APInt &b) {
1112 if (div0 || b.isZero()) {
1113 div0 = true;
1114 return a;
1115 }
1116 return a.urem(b);
1117 });
1118
1119 return div0 ? Attribute() : result;
1120}
1121
1122Speculation::Speculatability arith::RemUIOp::getSpeculatability() {
1123 return getDivUISpeculatability(getRhs());
1124}
1125
1126//===----------------------------------------------------------------------===//
1127// RemSIOp
1128//===----------------------------------------------------------------------===//
1129
1130OpFoldResult arith::RemSIOp::fold(FoldAdaptor adaptor) {
1131 // TODO: remsi (x, 0) -> poison. Remainder by zero is undefined behaviour and
1132 // could fold to poison, but that would make the arith dialect depend on the
1133 // ub dialect to materialize ub.poison; left out for now.
1134
1135 // remsi (x, 1) -> 0.
1136 if (matchPattern(adaptor.getRhs(), m_One()))
1137 return getIntegerAttrOfType(getType(), 0);
1138
1139 // remsi (0, x) -> 0 and remsi (x, x) -> 0. Division by zero is UB, so
1140 // refining to 0 is valid.
1141 if (matchPattern(adaptor.getLhs(), m_Zero()) || getLhs() == getRhs())
1142 return getIntegerAttrOfType(getType(), 0);
1143
1144 // Don't fold if it would require a division by zero.
1145 bool div0 = false;
1146 auto result = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1147 [&](APInt a, const APInt &b) {
1148 if (div0 || b.isZero()) {
1149 div0 = true;
1150 return a;
1151 }
1152 return a.srem(b);
1153 });
1154
1155 return div0 ? Attribute() : result;
1156}
1157
1158Speculation::Speculatability arith::RemSIOp::getSpeculatability() {
1159 // X % 0 => UB
1160 // X % -1 is well-defined (always 0), unlike X / -1 which can overflow.
1161 if (matchPattern(getRhs(), m_IntRangeWithoutZeroS()))
1163
1165}
1166
1167//===----------------------------------------------------------------------===//
1168// AndIOp
1169//===----------------------------------------------------------------------===//
1170
1171/// Fold `op(a, op(a, b))` to `op(a, b)` for an associative, commutative and
1172/// idempotent `op` (e.g. `and`, `or`).
1173template <typename OpTy>
1175 for (bool reversePrev : {false, true}) {
1176 auto prev = (reversePrev ? op.getRhs() : op.getLhs())
1177 .template getDefiningOp<OpTy>();
1178 if (!prev)
1179 continue;
1180
1181 Value other = (reversePrev ? op.getLhs() : op.getRhs());
1182 if (other != prev.getLhs() && other != prev.getRhs())
1183 continue;
1184
1185 return prev.getResult();
1186 }
1187 return {};
1188}
1189
1190OpFoldResult arith::AndIOp::fold(FoldAdaptor adaptor) {
1191 /// and(x, 0) -> 0
1192 if (matchPattern(adaptor.getRhs(), m_Zero()))
1193 return getRhs();
1194 /// and(x, allOnes) -> x
1195 APInt intValue;
1196 if (matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue)) &&
1197 intValue.isAllOnes())
1198 return getLhs();
1199 /// and(x, not(x)) -> 0
1200 if (matchPattern(getRhs(), m_Op<XOrIOp>(matchers::m_Val(getLhs()),
1201 m_ConstantInt(&intValue))) &&
1202 intValue.isAllOnes())
1203 return Builder(getContext()).getZeroAttr(getType());
1204 /// and(not(x), x) -> 0
1205 if (matchPattern(getLhs(), m_Op<XOrIOp>(matchers::m_Val(getRhs()),
1206 m_ConstantInt(&intValue))) &&
1207 intValue.isAllOnes())
1208 return Builder(getContext()).getZeroAttr(getType());
1209
1210 /// and(a, and(a, b)) -> and(a, b)
1211 if (Value result = foldIdempotentOfSameOp(*this))
1212 return result;
1213
1215 adaptor.getOperands(),
1216 [](APInt a, const APInt &b) { return std::move(a) & b; });
1217}
1218
1219//===----------------------------------------------------------------------===//
1220// OrIOp
1221//===----------------------------------------------------------------------===//
1222
1223OpFoldResult arith::OrIOp::fold(FoldAdaptor adaptor) {
1224 if (APInt rhsVal; matchPattern(adaptor.getRhs(), m_ConstantInt(&rhsVal))) {
1225 /// or(x, 0) -> x
1226 if (rhsVal.isZero())
1227 return getLhs();
1228 /// or(x, <all ones>) -> <all ones>
1229 if (rhsVal.isAllOnes())
1230 return adaptor.getRhs();
1231 }
1232
1233 APInt intValue;
1234 /// or(x, xor(x, 1)) -> 1
1235 if (matchPattern(getRhs(), m_Op<XOrIOp>(matchers::m_Val(getLhs()),
1236 m_ConstantInt(&intValue))) &&
1237 intValue.isAllOnes())
1238 return getRhs().getDefiningOp<XOrIOp>().getRhs();
1239 /// or(xor(x, 1), x) -> 1
1240 if (matchPattern(getLhs(), m_Op<XOrIOp>(matchers::m_Val(getRhs()),
1241 m_ConstantInt(&intValue))) &&
1242 intValue.isAllOnes())
1243 return getLhs().getDefiningOp<XOrIOp>().getRhs();
1244
1245 /// or(a, or(a, b)) -> or(a, b)
1246 if (Value result = foldIdempotentOfSameOp(*this))
1247 return result;
1248
1250 adaptor.getOperands(),
1251 [](APInt a, const APInt &b) { return std::move(a) | b; });
1252}
1253
1254//===----------------------------------------------------------------------===//
1255// XOrIOp
1256//===----------------------------------------------------------------------===//
1257
1258OpFoldResult arith::XOrIOp::fold(FoldAdaptor adaptor) {
1259 /// xor(x, 0) -> x
1260 if (matchPattern(adaptor.getRhs(), m_Zero()))
1261 return getLhs();
1262 /// xor(x, x) -> 0
1263 if (getLhs() == getRhs()) {
1264 // A dynamically-shaped result cannot be a constant; bail before
1265 // getZeroAttr, which would assert on a non-static shape.
1266 auto shapedType = dyn_cast<ShapedType>(getType());
1267 if (!shapedType || shapedType.hasStaticShape())
1268 return Builder(getContext()).getZeroAttr(getType());
1269 }
1270 /// xor(xor(x, a), a) -> x
1271 /// xor(xor(a, x), a) -> x
1272 if (arith::XOrIOp prev = getLhs().getDefiningOp<arith::XOrIOp>()) {
1273 if (prev.getRhs() == getRhs())
1274 return prev.getLhs();
1275 if (prev.getLhs() == getRhs())
1276 return prev.getRhs();
1277 }
1278 /// xor(a, xor(x, a)) -> x
1279 /// xor(a, xor(a, x)) -> x
1280 if (arith::XOrIOp prev = getRhs().getDefiningOp<arith::XOrIOp>()) {
1281 if (prev.getRhs() == getLhs())
1282 return prev.getLhs();
1283 if (prev.getLhs() == getLhs())
1284 return prev.getRhs();
1285 }
1286
1288 adaptor.getOperands(),
1289 [](APInt a, const APInt &b) { return std::move(a) ^ b; });
1290}
1291
1292void arith::XOrIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1293 MLIRContext *context) {
1294 patterns.add<XOrIXOrIConstant, XOrINotCmpI, XOrIOfExtUI, XOrIOfExtSI>(
1295 context);
1296}
1297
1298//===----------------------------------------------------------------------===//
1299// NegFOp
1300//===----------------------------------------------------------------------===//
1301
1302OpFoldResult arith::NegFOp::fold(FoldAdaptor adaptor) {
1303 /// negf(negf(x)) -> x
1304 if (auto op = this->getOperand().getDefiningOp<arith::NegFOp>())
1305 return op.getOperand();
1307 adaptor.getOperands(), [](const APFloat &a) -> std::optional<APFloat> {
1308 if (!APFloat::semanticsHasSignedRepr(a.getSemantics()))
1309 return std::nullopt;
1310 return -a;
1311 });
1312}
1313
1314//===----------------------------------------------------------------------===//
1315// FlushDenormalsOp
1316//===----------------------------------------------------------------------===//
1317
1318OpFoldResult arith::FlushDenormalsOp::fold(FoldAdaptor adaptor) {
1319 // TODO: Fold flush_denormals if the floating-point type does not support
1320 // denormals. There is currently no API to query this information from
1321 // APFloat.
1322
1323 // flush_denormals(flush_denormals(x)) -> flush_denormals(x)
1324 if (auto op = this->getOperand().getDefiningOp<arith::FlushDenormalsOp>())
1325 return op.getResult();
1326
1327 // Constant-fold flush_denormals if the operand is a constant.
1329 adaptor.getOperands(), [](const APFloat &a) {
1330 if (a.isDenormal())
1331 return APFloat::getZero(a.getSemantics(), a.isNegative());
1332 return a;
1333 });
1334}
1335
1336//===----------------------------------------------------------------------===//
1337// AddFOp
1338//===----------------------------------------------------------------------===//
1339
1340OpFoldResult arith::AddFOp::fold(FoldAdaptor adaptor) {
1341 // addf(x, -0) -> x
1342 if (matchPattern(adaptor.getRhs(), m_NegZeroFloat()))
1343 return getLhs();
1344 if (matchPattern(adaptor.getRhs(), m_PosZeroFloat()) &&
1345 bitEnumContainsAll(adaptor.getFastmath(), FastMathFlags::nsz))
1346 return getLhs();
1347
1348 auto rm = getRoundingmode();
1350 adaptor.getOperands(), [rm](const APFloat &a, const APFloat &b) {
1351 APFloat result(a);
1352 result.add(b, convertArithRoundingModeToLLVMIR(rm));
1353 return result;
1354 });
1355}
1356
1357void arith::AddFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1358 MLIRContext *context) {
1359 patterns.add<AddFOfNegFLhs, AddFOfNegFRhs>(context);
1360}
1361
1362//===----------------------------------------------------------------------===//
1363// SubFOp
1364//===----------------------------------------------------------------------===//
1365
1366OpFoldResult arith::SubFOp::fold(FoldAdaptor adaptor) {
1367 // subf(x, +0) -> x
1368 if (matchPattern(adaptor.getRhs(), m_PosZeroFloat()))
1369 return getLhs();
1370 if (matchPattern(adaptor.getRhs(), m_NegZeroFloat()) &&
1371 bitEnumContainsAll(adaptor.getFastmath(), FastMathFlags::nsz))
1372 return getLhs();
1373
1374 auto rm = getRoundingmode();
1376 adaptor.getOperands(),
1377 [rm](const APFloat &a, const APFloat &b) -> std::optional<APFloat> {
1378 // A negative difference is not representable in types such as
1379 // f8E8M0FNU, and APFloat::subtract does not support computing it.
1380 if (!APFloat::semanticsHasSignedRepr(a.getSemantics()) &&
1381 a.compare(b) == APFloat::cmpLessThan)
1382 return std::nullopt;
1383 APFloat result(a);
1384 result.subtract(b, convertArithRoundingModeToLLVMIR(rm));
1385 return result;
1386 });
1387}
1388
1389void arith::SubFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1390 MLIRContext *context) {
1391 patterns.add<SubFOfNegZero>(context);
1392}
1393
1394namespace {
1395
1396/// Narrow an extremum whose operands were extended from the result type:
1397///
1398/// trunc(extremum(ext(lhs), ext(rhs))) -> extremum(lhs, rhs)
1399///
1400/// The concrete extension is part of the pattern so each extremum is only
1401/// registered with extensions that preserve its ordering.
1402/// For floating-point types, also require the extension to preserve every
1403/// source value relevant under the extremum's fast-math flags.
1404template <typename TruncOp, typename ExtOp, typename ExtremumOp>
1405struct NarrowExtremum final : OpRewritePattern<TruncOp> {
1406 using OpRewritePattern<TruncOp>::OpRewritePattern;
1407
1408 LogicalResult matchAndRewrite(TruncOp truncOp,
1409 PatternRewriter &rewriter) const override {
1410 auto extremumOp = truncOp.getIn().template getDefiningOp<ExtremumOp>();
1411 if (!extremumOp || !extremumOp->hasOneUse())
1412 return failure();
1413
1414 auto lhsExt = extremumOp.getLhs().template getDefiningOp<ExtOp>();
1415 auto rhsExt = extremumOp.getRhs().template getDefiningOp<ExtOp>();
1416 if (!lhsExt || !rhsExt)
1417 return failure();
1418
1419 Value lhs = lhsExt.getIn();
1420 Value rhs = rhsExt.getIn();
1421 Type narrowType = truncOp.getType();
1422 if (lhs.getType() != narrowType || rhs.getType() != narrowType)
1423 return failure();
1424
1425 // A floating-point extension is not necessarily lossless between arbitrary
1426 // floating-point semantics, even when the destination has a larger bit
1427 // width. In particular, it may lose the sign of zero or quiet a signaling
1428 // NaN, either of which can change an extremum's result. `nnan` lets us
1429 // disregard NaN representation differences, but all other relevant source
1430 // values must be preserved.
1431 if (auto narrowFloatType =
1432 dyn_cast<FloatType>(getElementTypeOrSelf(narrowType))) {
1433 auto wideFloatType =
1434 dyn_cast<FloatType>(getElementTypeOrSelf(extremumOp.getType()));
1435 if (!wideFloatType)
1436 return failure();
1437
1438 const llvm::fltSemantics &narrowSemantics =
1439 narrowFloatType.getFloatSemantics();
1440 const llvm::fltSemantics &wideSemantics =
1441 wideFloatType.getFloatSemantics();
1442 bool ignoreNaNs = false;
1443 if constexpr (std::is_same_v<TruncOp, TruncFOp>)
1444 ignoreNaNs =
1445 bitEnumContainsAll(extremumOp.getFastmath(), FastMathFlags::nnan);
1446 if (!llvm::APFloatBase::isLosslesslyConvertibleTo(
1447 narrowSemantics, wideSemantics, ignoreNaNs))
1448 return failure();
1449 }
1450
1451 rewriter.replaceOpWithNewOp<ExtremumOp>(
1452 truncOp, TypeRange{narrowType}, ValueRange{lhs, rhs},
1453 extremumOp.getProperties(),
1454 extremumOp->getDiscardableAttrDictionary().getValue());
1455 return success();
1456 }
1457};
1458
1459} // namespace
1460
1461//===----------------------------------------------------------------------===//
1462// MaximumFOp
1463//===----------------------------------------------------------------------===//
1464
1465OpFoldResult arith::MaximumFOp::fold(FoldAdaptor adaptor) {
1466 // maximumf(x,x) -> x
1467 if (getLhs() == getRhs())
1468 return getRhs();
1469
1470 // maximumf(x, -inf) -> x
1471 if (matchPattern(adaptor.getRhs(), m_NegInfFloat()))
1472 return getLhs();
1473
1474 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::maximum);
1475}
1476
1477//===----------------------------------------------------------------------===//
1478// MaxNumFOp
1479//===----------------------------------------------------------------------===//
1480
1481OpFoldResult arith::MaxNumFOp::fold(FoldAdaptor adaptor) {
1482 // maxnumf(x,x) -> x
1483 if (getLhs() == getRhs())
1484 return getRhs();
1485
1486 // maxnumf(x, NaN) -> x
1487 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1488 return getLhs();
1489
1490 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::maxnum);
1491}
1492
1493//===----------------------------------------------------------------------===//
1494// MaximumNumFOp
1495//===----------------------------------------------------------------------===//
1496
1497OpFoldResult arith::MaximumNumFOp::fold(FoldAdaptor adaptor) {
1498 // maximumnumf(x,x) -> x
1499 if (getLhs() == getRhs())
1500 return getRhs();
1501
1502 // maximumnumf(x, NaN) -> x
1503 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1504 return getLhs();
1505
1506 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::maximumnum);
1507}
1508
1509//===----------------------------------------------------------------------===//
1510// MaxSIOp
1511//===----------------------------------------------------------------------===//
1512
1513OpFoldResult MaxSIOp::fold(FoldAdaptor adaptor) {
1514 // maxsi(x,x) -> x
1515 if (getLhs() == getRhs())
1516 return getRhs();
1517
1518 if (APInt intValue;
1519 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1520 // maxsi(x,MAX_INT) -> MAX_INT
1521 if (intValue.isMaxSignedValue())
1522 return getRhs();
1523 // maxsi(x, MIN_INT) -> x
1524 if (intValue.isMinSignedValue())
1525 return getLhs();
1526 }
1527
1528 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1529 llvm::APIntOps::smax);
1530}
1531
1532//===----------------------------------------------------------------------===//
1533// MaxUIOp
1534//===----------------------------------------------------------------------===//
1535
1536OpFoldResult MaxUIOp::fold(FoldAdaptor adaptor) {
1537 // maxui(x,x) -> x
1538 if (getLhs() == getRhs())
1539 return getRhs();
1540
1541 if (APInt intValue;
1542 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1543 // maxui(x,MAX_INT) -> MAX_INT
1544 if (intValue.isMaxValue())
1545 return getRhs();
1546 // maxui(x, MIN_INT) -> x
1547 if (intValue.isMinValue())
1548 return getLhs();
1549 }
1550
1551 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1552 llvm::APIntOps::umax);
1553}
1554
1555//===----------------------------------------------------------------------===//
1556// MinimumFOp
1557//===----------------------------------------------------------------------===//
1558
1559OpFoldResult arith::MinimumFOp::fold(FoldAdaptor adaptor) {
1560 // minimumf(x,x) -> x
1561 if (getLhs() == getRhs())
1562 return getRhs();
1563
1564 // minimumf(x, +inf) -> x
1565 if (matchPattern(adaptor.getRhs(), m_PosInfFloat()))
1566 return getLhs();
1567
1568 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::minimum);
1569}
1570
1571//===----------------------------------------------------------------------===//
1572// MinNumFOp
1573//===----------------------------------------------------------------------===//
1574
1575OpFoldResult arith::MinNumFOp::fold(FoldAdaptor adaptor) {
1576 // minnumf(x,x) -> x
1577 if (getLhs() == getRhs())
1578 return getRhs();
1579
1580 // minnumf(x, NaN) -> x
1581 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1582 return getLhs();
1583
1584 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::minnum);
1585}
1586
1587//===----------------------------------------------------------------------===//
1588// MinimumNumFOp
1589//===----------------------------------------------------------------------===//
1590
1591OpFoldResult arith::MinimumNumFOp::fold(FoldAdaptor adaptor) {
1592 // minimumnumf(x,x) -> x
1593 if (getLhs() == getRhs())
1594 return getRhs();
1595
1596 // minimumnumf(x, NaN) -> x
1597 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1598 return getLhs();
1599
1600 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::minimumnum);
1601}
1602
1603//===----------------------------------------------------------------------===//
1604// MinSIOp
1605//===----------------------------------------------------------------------===//
1606
1607OpFoldResult MinSIOp::fold(FoldAdaptor adaptor) {
1608 // minsi(x,x) -> x
1609 if (getLhs() == getRhs())
1610 return getRhs();
1611
1612 if (APInt intValue;
1613 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1614 // minsi(x,MIN_INT) -> MIN_INT
1615 if (intValue.isMinSignedValue())
1616 return getRhs();
1617 // minsi(x, MAX_INT) -> x
1618 if (intValue.isMaxSignedValue())
1619 return getLhs();
1620 }
1621
1622 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1623 llvm::APIntOps::smin);
1624}
1625
1626//===----------------------------------------------------------------------===//
1627// MinUIOp
1628//===----------------------------------------------------------------------===//
1629
1630OpFoldResult MinUIOp::fold(FoldAdaptor adaptor) {
1631 // minui(x,x) -> x
1632 if (getLhs() == getRhs())
1633 return getRhs();
1634
1635 if (APInt intValue;
1636 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1637 // minui(x,MIN_INT) -> MIN_INT
1638 if (intValue.isMinValue())
1639 return getRhs();
1640 // minui(x, MAX_INT) -> x
1641 if (intValue.isMaxValue())
1642 return getLhs();
1643 }
1644
1645 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1646 llvm::APIntOps::umin);
1647}
1648
1649//===----------------------------------------------------------------------===//
1650// MulFOp
1651//===----------------------------------------------------------------------===//
1652
1653OpFoldResult arith::MulFOp::fold(FoldAdaptor adaptor) {
1654 // mulf(x, 1) -> x
1655 if (matchPattern(adaptor.getRhs(), m_OneFloat()))
1656 return getLhs();
1657
1658 // Match LLVM InstSimplify: with nnan+nsz, X * 0 -> 0 for a non-constant X.
1659 // When both operands are constants, fall through to APFloat so IEEE signed
1660 // zeros are preserved (e.g. (-c) * +0.0 == -0.0).
1661 if (arith::bitEnumContainsAll(getFastmath(), arith::FastMathFlags::nnan |
1662 arith::FastMathFlags::nsz) &&
1663 !(adaptor.getLhs() && adaptor.getRhs())) {
1664 // mulf(x, 0) -> 0
1665 if (matchPattern(adaptor.getRhs(), m_AnyZeroFloat()))
1666 return getRhs();
1667 if (matchPattern(adaptor.getLhs(), m_AnyZeroFloat()))
1668 return getLhs();
1669 }
1670
1671 auto rm = getRoundingmode();
1673 adaptor.getOperands(), [rm](const APFloat &a, const APFloat &b) {
1674 APFloat result(a);
1675 result.multiply(b, convertArithRoundingModeToLLVMIR(rm));
1676 return result;
1677 });
1678}
1679
1680void arith::MulFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1681 MLIRContext *context) {
1682 patterns.add<MulFOfNegF>(context);
1683}
1684
1685//===----------------------------------------------------------------------===//
1686// DivFOp
1687//===----------------------------------------------------------------------===//
1688
1689OpFoldResult arith::DivFOp::fold(FoldAdaptor adaptor) {
1690 // divf(x, 1) -> x
1691 if (matchPattern(adaptor.getRhs(), m_OneFloat()))
1692 return getLhs();
1693
1694 auto rm = getRoundingmode();
1696 adaptor.getOperands(), [rm](const APFloat &a, const APFloat &b) {
1697 APFloat result(a);
1698 result.divide(b, convertArithRoundingModeToLLVMIR(rm));
1699 return result;
1700 });
1701}
1702
1703void arith::DivFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1704 MLIRContext *context) {
1705 patterns.add<DivFOfNegF>(context);
1706}
1707
1708//===----------------------------------------------------------------------===//
1709// RemFOp
1710//===----------------------------------------------------------------------===//
1711
1712OpFoldResult arith::RemFOp::fold(FoldAdaptor adaptor) {
1713 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(),
1714 [](const APFloat &a, const APFloat &b) {
1715 APFloat result(a);
1716 // APFloat::mod() offers the remainder
1717 // behavior we want, i.e. the result has
1718 // the sign of LHS operand.
1719 (void)result.mod(b);
1720 return result;
1721 });
1722}
1723
1724//===----------------------------------------------------------------------===//
1725// Utility functions for verifying cast ops
1726//===----------------------------------------------------------------------===//
1727
1728template <typename... Types>
1729using type_list = std::tuple<Types...> *;
1730
1731/// Returns a non-null type only if the provided type is one of the allowed
1732/// types or one of the allowed shaped types of the allowed types. Returns the
1733/// element type if a valid shaped type is provided.
1734template <typename... ShapedTypes, typename... ElementTypes>
1737 if (llvm::isa<ShapedType>(type) && !llvm::isa<ShapedTypes...>(type))
1738 return {};
1739
1740 auto underlyingType = getElementTypeOrSelf(type);
1741 if (!llvm::isa<ElementTypes...>(underlyingType))
1742 return {};
1743
1744 return underlyingType;
1745}
1746
1747/// Get allowed underlying types for vectors and tensors.
1748template <typename... ElementTypes>
1753
1754/// Get allowed underlying types for vectors, tensors, and memrefs.
1755template <typename... ElementTypes>
1761
1762/// Return false if both types are ranked tensor with mismatching encoding.
1763static bool hasSameEncoding(Type typeA, Type typeB) {
1764 auto rankedTensorA = dyn_cast<RankedTensorType>(typeA);
1765 auto rankedTensorB = dyn_cast<RankedTensorType>(typeB);
1766 if (!rankedTensorA || !rankedTensorB)
1767 return true;
1768 return rankedTensorA.getEncoding() == rankedTensorB.getEncoding();
1769}
1770
1772 if (inputs.size() != 1 || outputs.size() != 1)
1773 return false;
1774 if (!hasSameEncoding(inputs.front(), outputs.front()))
1775 return false;
1776 return succeeded(verifyCompatibleShapes(inputs.front(), outputs.front()));
1777}
1778
1779//===----------------------------------------------------------------------===//
1780// Verifiers for integer and floating point extension/truncation ops
1781//===----------------------------------------------------------------------===//
1782
1783// Extend ops can only extend to a wider type.
1784template <typename ValType, typename Op>
1785static LogicalResult verifyExtOp(Op op) {
1786 Type srcType = getElementTypeOrSelf(op.getIn().getType());
1787 Type dstType = getElementTypeOrSelf(op.getType());
1788
1789 if (llvm::cast<ValType>(srcType).getWidth() >=
1790 llvm::cast<ValType>(dstType).getWidth())
1791 return op.emitError("result type ")
1792 << dstType << " must be wider than operand type " << srcType;
1793
1794 return success();
1795}
1796
1797// Truncate ops can only truncate to a shorter type.
1798template <typename ValType, typename Op>
1799static LogicalResult verifyTruncateOp(Op op) {
1800 Type srcType = getElementTypeOrSelf(op.getIn().getType());
1801 Type dstType = getElementTypeOrSelf(op.getType());
1802
1803 if (llvm::cast<ValType>(srcType).getWidth() <=
1804 llvm::cast<ValType>(dstType).getWidth())
1805 return op.emitError("result type ")
1806 << dstType << " must be shorter than operand type " << srcType;
1807
1808 return success();
1809}
1810
1811/// Validate a cast that changes the width of a type.
1812template <template <typename> class WidthComparator, typename... ElementTypes>
1813static bool checkWidthChangeCast(TypeRange inputs, TypeRange outputs) {
1814 if (!areValidCastInputsAndOutputs(inputs, outputs))
1815 return false;
1816
1817 auto srcType = getTypeIfLike<ElementTypes...>(inputs.front());
1818 auto dstType = getTypeIfLike<ElementTypes...>(outputs.front());
1819 if (!srcType || !dstType)
1820 return false;
1821
1822 return WidthComparator<unsigned>()(dstType.getIntOrFloatBitWidth(),
1823 srcType.getIntOrFloatBitWidth());
1824}
1825
1826/// Attempts to convert `sourceValue` to an APFloat value with
1827/// `targetSemantics` and `roundingMode`, without any information loss.
1828static FailureOr<APFloat>
1829convertFloatValue(APFloat sourceValue,
1830 const llvm::fltSemantics &targetSemantics,
1831 llvm::RoundingMode roundingMode = kDefaultRoundingMode) {
1832 // Reject special values that are not representable in the target type before
1833 // calling APFloat::convert, which would llvm_unreachable on them.
1834 using fltNonfiniteBehavior = llvm::fltNonfiniteBehavior;
1835 if (sourceValue.isInfinity() &&
1836 (targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::NanOnly ||
1837 targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::FiniteOnly))
1838 return failure();
1839 if (sourceValue.isNaN() &&
1840 targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::FiniteOnly)
1841 return failure();
1842
1843 bool losesInfo = false;
1844 auto status = sourceValue.convert(targetSemantics, roundingMode, &losesInfo);
1845 if (losesInfo || status != APFloat::opOK)
1846 return failure();
1847
1848 return sourceValue;
1849}
1850
1851//===----------------------------------------------------------------------===//
1852// ExtUIOp
1853//===----------------------------------------------------------------------===//
1854
1855OpFoldResult arith::ExtUIOp::fold(FoldAdaptor adaptor) {
1856 if (auto lhs = getIn().getDefiningOp<ExtUIOp>()) {
1857 // Only the inner extension's nneg speaks about the surviving source; the
1858 // outer flag described the already-extended value.
1859 setNonNeg(lhs.getNonNeg());
1860 getInMutable().assign(lhs.getIn());
1861 return getResult();
1862 }
1863
1864 Type resType = getElementTypeOrSelf(getType());
1865 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
1867 adaptor.getOperands(), getType(),
1868 [bitWidth](const APInt &a, bool &castStatus) {
1869 return a.zext(bitWidth);
1870 });
1871}
1872
1873bool arith::ExtUIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1875}
1876
1877LogicalResult arith::ExtUIOp::verify() {
1878 return verifyExtOp<IntegerType>(*this);
1879}
1880
1881//===----------------------------------------------------------------------===//
1882// ExtSIOp
1883//===----------------------------------------------------------------------===//
1884
1885OpFoldResult arith::ExtSIOp::fold(FoldAdaptor adaptor) {
1886 if (auto lhs = getIn().getDefiningOp<ExtSIOp>()) {
1887 getInMutable().assign(lhs.getIn());
1888 return getResult();
1889 }
1890
1891 Type resType = getElementTypeOrSelf(getType());
1892 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
1894 adaptor.getOperands(), getType(),
1895 [bitWidth](const APInt &a, bool &castStatus) {
1896 return a.sext(bitWidth);
1897 });
1898}
1899
1900bool arith::ExtSIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1902}
1903
1904void arith::ExtSIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1905 MLIRContext *context) {
1906 patterns.add<ExtSIOfExtUI>(context);
1907}
1908
1909LogicalResult arith::ExtSIOp::verify() {
1910 return verifyExtOp<IntegerType>(*this);
1911}
1912
1913//===----------------------------------------------------------------------===//
1914// ExtFOp
1915//===----------------------------------------------------------------------===//
1916
1917/// Fold extension of float constants when there is no information loss due the
1918/// difference in fp semantics.
1919OpFoldResult arith::ExtFOp::fold(FoldAdaptor adaptor) {
1920 if (auto truncFOp = getOperand().getDefiningOp<TruncFOp>()) {
1921 if (truncFOp.getOperand().getType() == getType()) {
1922 arith::FastMathFlags truncFMF =
1923 truncFOp.getFastmath().value_or(arith::FastMathFlags::none);
1924 bool isTruncContract =
1925 bitEnumContainsAll(truncFMF, arith::FastMathFlags::contract);
1926 arith::FastMathFlags extFMF =
1927 getFastmath().value_or(arith::FastMathFlags::none);
1928 bool isExtContract =
1929 bitEnumContainsAll(extFMF, arith::FastMathFlags::contract);
1930 if (isTruncContract && isExtContract) {
1931 return truncFOp.getOperand();
1932 }
1933 }
1934 }
1935
1936 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
1937 const llvm::fltSemantics &targetSemantics = resElemType.getFloatSemantics();
1939 adaptor.getOperands(), getType(),
1940 [&targetSemantics](const APFloat &a, bool &castStatus) {
1941 FailureOr<APFloat> result = convertFloatValue(a, targetSemantics);
1942 if (failed(result)) {
1943 castStatus = false;
1944 return a;
1945 }
1946 return *result;
1947 });
1948}
1949
1950bool arith::ExtFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1951 return checkWidthChangeCast<std::greater, FloatType>(inputs, outputs);
1952}
1953
1954LogicalResult arith::ExtFOp::verify() { return verifyExtOp<FloatType>(*this); }
1955
1956//===----------------------------------------------------------------------===//
1957// ScalingExtFOp
1958//===----------------------------------------------------------------------===//
1959
1960/// Fold `calculate` element-wise over the operands of a scaling cast op. The
1961/// `constFoldBinaryOp` helpers cannot be used: they bail out unless both
1962/// operands have the same type, and `in` and `scale` never do.
1964 Attribute inAttr, Attribute scaleAttr, Type resultType,
1965 function_ref<std::optional<APFloat>(const APFloat &, const APFloat &)>
1966 calculate) {
1967 // Poison propagates, as it does in the generic constant folders.
1968 if (isa_and_nonnull<ub::PoisonAttr>(inAttr))
1969 return inAttr;
1970 if (isa_and_nonnull<ub::PoisonAttr>(scaleAttr))
1971 return scaleAttr;
1972
1973 if (!inAttr || !scaleAttr || !resultType)
1974 return {};
1975
1976 if (auto inFloat = dyn_cast<FloatAttr>(inAttr)) {
1977 auto scaleFloat = dyn_cast<FloatAttr>(scaleAttr);
1978 if (!scaleFloat)
1979 return {};
1980 std::optional<APFloat> result =
1981 calculate(inFloat.getValue(), scaleFloat.getValue());
1982 if (!result)
1983 return {};
1984 return FloatAttr::get(resultType, *result);
1985 }
1986
1987 auto inElements = dyn_cast<DenseFPElementsAttr>(inAttr);
1988 auto scaleElements = dyn_cast<DenseFPElementsAttr>(scaleAttr);
1989 auto shapedResultType = dyn_cast<ShapedType>(resultType);
1990 if (!inElements || !scaleElements || !shapedResultType ||
1991 !shapedResultType.hasStaticShape() ||
1992 inElements.getNumElements() != scaleElements.getNumElements())
1993 return {};
1994
1995 // Both operands are splats, so avoid expanding the elements out.
1996 if (inElements.isSplat() && scaleElements.isSplat()) {
1997 std::optional<APFloat> result =
1998 calculate(inElements.getSplatValue<APFloat>(),
1999 scaleElements.getSplatValue<APFloat>());
2000 if (!result)
2001 return {};
2002 return DenseElementsAttr::get(shapedResultType, *result);
2003 }
2004
2005 SmallVector<APFloat> results;
2006 results.reserve(inElements.getNumElements());
2007 for (const auto &[in, scale] : llvm::zip_equal(inElements, scaleElements)) {
2008 std::optional<APFloat> result = calculate(in, scale);
2009 if (!result)
2010 return {};
2011 results.push_back(*result);
2012 }
2013 return DenseElementsAttr::get(shapedResultType, results);
2014}
2015
2016/// Only scales that already are f8E8M0FNU fold. What a wider scale means is
2017/// unsettled -- the tree does not say whether truncating one to f8E8M0FNU
2018/// rounds or takes its exponent -- so a folder should not settle it, see
2019/// https://github.com/llvm/llvm-project/issues/215295.
2020static bool isFoldableScalingScale(Value scale) {
2021 return isa<Float8E8M0FNUType>(getElementTypeOrSelf(scale.getType()));
2022}
2023
2024OpFoldResult arith::ScalingExtFOp::fold(FoldAdaptor adaptor) {
2025 // scaling_extf(in, scale) -> mulf(extf(in), extf(scale)), matching the
2026 // expansion in ExpandOps.cpp. As in arith.extf, the widening steps only fold
2027 // when they are lossless.
2028 if (!isFoldableScalingScale(getScale()))
2029 return {};
2030
2031 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2032 const llvm::fltSemantics &resSemantics = resElemType.getFloatSemantics();
2033 return foldScalingCastOp(
2034 adaptor.getIn(), adaptor.getScale(), getType(),
2035 [&resSemantics](const APFloat &in,
2036 const APFloat &scale) -> std::optional<APFloat> {
2037 FailureOr<APFloat> inExt = convertFloatValue(in, resSemantics);
2038 FailureOr<APFloat> scaleExt = convertFloatValue(scale, resSemantics);
2039 if (failed(inExt) || failed(scaleExt))
2040 return std::nullopt;
2041 APFloat result(*inExt);
2042 result.multiply(*scaleExt, kDefaultRoundingMode);
2043 return result;
2044 });
2045}
2046
2047bool arith::ScalingExtFOp::areCastCompatible(TypeRange inputs,
2048 TypeRange outputs) {
2049 return checkWidthChangeCast<std::greater, FloatType>(inputs.front(), outputs);
2050}
2051
2052LogicalResult arith::ScalingExtFOp::verify() {
2053 return verifyExtOp<FloatType>(*this);
2054}
2055
2056//===----------------------------------------------------------------------===//
2057// TruncIOp
2058//===----------------------------------------------------------------------===//
2059
2060OpFoldResult arith::TruncIOp::fold(FoldAdaptor adaptor) {
2061 if (matchPattern(getOperand(), m_Op<arith::ExtUIOp>()) ||
2062 matchPattern(getOperand(), m_Op<arith::ExtSIOp>())) {
2063 Value src = getOperand().getDefiningOp()->getOperand(0);
2064 Type srcType = getElementTypeOrSelf(src.getType());
2065 Type dstType = getElementTypeOrSelf(getType());
2066 // trunci(zexti(a)) -> trunci(a)
2067 // trunci(sexti(a)) -> trunci(a)
2068 if (llvm::cast<IntegerType>(srcType).getWidth() >
2069 llvm::cast<IntegerType>(dstType).getWidth()) {
2070 setOperand(src);
2071 return getResult();
2072 }
2073
2074 // trunci(zexti(a)) -> a
2075 // trunci(sexti(a)) -> a
2076 if (srcType == dstType)
2077 return src;
2078 }
2079
2080 // trunci(trunci(a)) -> trunci(a)
2081 if (auto truncOp = getOperand().getDefiningOp<arith::TruncIOp>()) {
2082 // Preserve a flag only if both truncations are lossless in that signedness.
2083 setOverflowFlags(getOverflowFlags() & truncOp.getOverflowFlags());
2084 setOperand(truncOp.getIn());
2085 return getResult();
2086 }
2087
2088 Type resType = getElementTypeOrSelf(getType());
2089 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
2091 adaptor.getOperands(), getType(),
2092 [bitWidth](const APInt &a, bool &castStatus) {
2093 return a.trunc(bitWidth);
2094 });
2095}
2096
2097bool arith::TruncIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2098 return checkWidthChangeCast<std::less, IntegerType>(inputs, outputs);
2099}
2100
2101void arith::TruncIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2102 MLIRContext *context) {
2103 patterns.add<NarrowExtremum<TruncIOp, ExtSIOp, MaxSIOp>,
2104 NarrowExtremum<TruncIOp, ExtSIOp, MinSIOp>,
2105 NarrowExtremum<TruncIOp, ExtUIOp, MaxUIOp>,
2106 NarrowExtremum<TruncIOp, ExtUIOp, MinUIOp>, TruncIExtSIToExtSI,
2107 TruncIExtUIToExtUI, TruncIShrSIToTrunciShrUI>(context);
2108}
2109
2110LogicalResult arith::TruncIOp::verify() {
2111 return verifyTruncateOp<IntegerType>(*this);
2112}
2113
2114//===----------------------------------------------------------------------===//
2115// TruncFOp
2116//===----------------------------------------------------------------------===//
2117
2118/// Perform safe const propagation for truncf, i.e., only propagate if FP value
2119/// can be represented without precision loss.
2120OpFoldResult arith::TruncFOp::fold(FoldAdaptor adaptor) {
2121 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2122 if (auto extOp = getOperand().getDefiningOp<arith::ExtFOp>()) {
2123 Value src = extOp.getIn();
2124 auto srcType = cast<FloatType>(getElementTypeOrSelf(src.getType()));
2125 auto intermediateType =
2126 cast<FloatType>(getElementTypeOrSelf(extOp.getType()));
2127 // Check whether every source value round-trips through the intermediate
2128 // type, including signaling NaNs and signed zero.
2129 if (llvm::APFloatBase::isLosslesslyConvertibleTo(
2130 srcType.getFloatSemantics(),
2131 intermediateType.getFloatSemantics())) {
2132 // truncf(extf(a)) -> truncf(a)
2133 if (srcType.getWidth() > resElemType.getWidth()) {
2134 setOperand(src);
2135 return getResult();
2136 }
2137
2138 // truncf(extf(a)) -> a
2139 if (srcType == resElemType)
2140 return src;
2141 }
2142 }
2143
2144 const llvm::fltSemantics &targetSemantics = resElemType.getFloatSemantics();
2146 adaptor.getOperands(), getType(),
2147 [this, &targetSemantics](const APFloat &a, bool &castStatus) {
2148 llvm::RoundingMode llvmRoundingMode =
2149 convertArithRoundingModeToLLVMIR(getRoundingmode());
2150 FailureOr<APFloat> result =
2151 convertFloatValue(a, targetSemantics, llvmRoundingMode);
2152 if (failed(result)) {
2153 castStatus = false;
2154 return a;
2155 }
2156 return *result;
2157 });
2158}
2159
2160void arith::TruncFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2161 MLIRContext *context) {
2162 patterns.add<NarrowExtremum<TruncFOp, ExtFOp, MaximumFOp>,
2163 NarrowExtremum<TruncFOp, ExtFOp, MaxNumFOp>,
2164 NarrowExtremum<TruncFOp, ExtFOp, MaximumNumFOp>,
2165 NarrowExtremum<TruncFOp, ExtFOp, MinimumFOp>,
2166 NarrowExtremum<TruncFOp, ExtFOp, MinNumFOp>,
2167 NarrowExtremum<TruncFOp, ExtFOp, MinimumNumFOp>,
2168 TruncFSIToFPToSIToFP, TruncFUIToFPToUIToFP>(context);
2169}
2170
2171bool arith::TruncFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2172 return checkWidthChangeCast<std::less, FloatType>(inputs, outputs);
2173}
2174
2175LogicalResult arith::TruncFOp::verify() {
2176 return verifyTruncateOp<FloatType>(*this);
2177}
2178
2179//===----------------------------------------------------------------------===//
2180// ConvertFOp
2181//===----------------------------------------------------------------------===//
2182
2183OpFoldResult arith::ConvertFOp::fold(FoldAdaptor adaptor) {
2184 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2185 const llvm::fltSemantics &targetSemantics = resElemType.getFloatSemantics();
2187 adaptor.getOperands(), getType(),
2188 [this, &targetSemantics](const APFloat &a, bool &castStatus) {
2189 llvm::RoundingMode llvmRoundingMode =
2190 convertArithRoundingModeToLLVMIR(getRoundingmode());
2191 FailureOr<APFloat> result =
2192 convertFloatValue(a, targetSemantics, llvmRoundingMode);
2193 if (failed(result)) {
2194 castStatus = false;
2195 return a;
2196 }
2197 return *result;
2198 });
2199}
2200
2201bool arith::ConvertFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2202 if (!areValidCastInputsAndOutputs(inputs, outputs))
2203 return false;
2204 auto srcType = getTypeIfLike<FloatType>(inputs.front());
2205 auto dstType = getTypeIfLike<FloatType>(outputs.front());
2206 if (!srcType || !dstType)
2207 return false;
2208 return srcType != dstType &&
2209 srcType.getIntOrFloatBitWidth() == dstType.getIntOrFloatBitWidth();
2210}
2211
2212LogicalResult arith::ConvertFOp::verify() {
2213 auto srcType = cast<FloatType>(getElementTypeOrSelf(getIn().getType()));
2214 auto dstType = cast<FloatType>(getElementTypeOrSelf(getType()));
2215 if (srcType == dstType)
2216 return emitError("result element type ")
2217 << dstType << " must be different from operand element type "
2218 << srcType;
2219 if (srcType.getWidth() != dstType.getWidth())
2220 return emitError("result element type ")
2221 << dstType << " must have the same bitwidth as operand element type "
2222 << srcType;
2223 return success();
2224}
2225
2226//===----------------------------------------------------------------------===//
2227// ScalingTruncFOp
2228//===----------------------------------------------------------------------===//
2229
2230OpFoldResult arith::ScalingTruncFOp::fold(FoldAdaptor adaptor) {
2231 // scaling_truncf(in, scale) -> truncf(in / extf(scale)), matching the
2232 // expansion in ExpandOps.cpp. Unlike scaling_extf, the scale is widened to
2233 // the type of `in` rather than to the result type.
2234 if (!isFoldableScalingScale(getScale()))
2235 return {};
2236
2237 auto inElemType = cast<FloatType>(getElementTypeOrSelf(getIn().getType()));
2238 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2239 const llvm::fltSemantics &inSemantics = inElemType.getFloatSemantics();
2240 const llvm::fltSemantics &resSemantics = resElemType.getFloatSemantics();
2241 llvm::RoundingMode roundingMode =
2242 convertArithRoundingModeToLLVMIR(getRoundingmode());
2243 return foldScalingCastOp(
2244 adaptor.getIn(), adaptor.getScale(), getType(),
2245 [&](const APFloat &in, const APFloat &scale) -> std::optional<APFloat> {
2246 FailureOr<APFloat> scaleExt = convertFloatValue(scale, inSemantics);
2247 if (failed(scaleExt))
2248 return std::nullopt;
2249 APFloat quotient(in);
2250 quotient.divide(*scaleExt, kDefaultRoundingMode);
2251 FailureOr<APFloat> result =
2252 convertFloatValue(quotient, resSemantics, roundingMode);
2253 if (failed(result))
2254 return std::nullopt;
2255 return *result;
2256 });
2257}
2258
2259bool arith::ScalingTruncFOp::areCastCompatible(TypeRange inputs,
2260 TypeRange outputs) {
2261 return checkWidthChangeCast<std::less, FloatType>(inputs.front(), outputs);
2262}
2263
2264LogicalResult arith::ScalingTruncFOp::verify() {
2265 return verifyTruncateOp<FloatType>(*this);
2266}
2267
2268//===----------------------------------------------------------------------===//
2269// AndIOp
2270//===----------------------------------------------------------------------===//
2271
2272void arith::AndIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2273 MLIRContext *context) {
2274 patterns.add<AndIAndIConstant, AndOfExtUI, AndOfExtSI>(context);
2275}
2276
2277//===----------------------------------------------------------------------===//
2278// OrIOp
2279//===----------------------------------------------------------------------===//
2280
2281void arith::OrIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2282 MLIRContext *context) {
2283 patterns.add<OrIOrIConstant, OrOfExtUI, OrOfExtSI>(context);
2284}
2285
2286//===----------------------------------------------------------------------===//
2287// Verifiers for casts between integers and floats.
2288//===----------------------------------------------------------------------===//
2289
2290template <typename From, typename To>
2291static bool checkIntFloatCast(TypeRange inputs, TypeRange outputs) {
2292 if (!areValidCastInputsAndOutputs(inputs, outputs))
2293 return false;
2294
2295 auto srcType = getTypeIfLike<From>(inputs.front());
2296 auto dstType = getTypeIfLike<To>(outputs.back());
2297
2298 return srcType && dstType;
2299}
2300
2301//===----------------------------------------------------------------------===//
2302// UIToFPOp
2303//===----------------------------------------------------------------------===//
2304
2305bool arith::UIToFPOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2306 return checkIntFloatCast<IntegerType, FloatType>(inputs, outputs);
2307}
2308
2309OpFoldResult arith::UIToFPOp::fold(FoldAdaptor adaptor) {
2310 Type resEleType = getElementTypeOrSelf(getType());
2312 adaptor.getOperands(), getType(),
2313 [&resEleType](const APInt &a, bool &castStatus) {
2314 FloatType floatTy = llvm::cast<FloatType>(resEleType);
2315 APFloat apf(floatTy.getFloatSemantics(),
2316 APInt::getZero(floatTy.getWidth()));
2317 apf.convertFromAPInt(a, /*IsSigned=*/false,
2318 APFloat::rmNearestTiesToEven);
2319 return apf;
2320 });
2321}
2322
2323void arith::UIToFPOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2324 MLIRContext *context) {
2325 patterns.add<UIToFPOfExtUI>(context);
2326}
2327
2328//===----------------------------------------------------------------------===//
2329// SIToFPOp
2330//===----------------------------------------------------------------------===//
2331
2332bool arith::SIToFPOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2333 return checkIntFloatCast<IntegerType, FloatType>(inputs, outputs);
2334}
2335
2336OpFoldResult arith::SIToFPOp::fold(FoldAdaptor adaptor) {
2337 Type resEleType = getElementTypeOrSelf(getType());
2339 adaptor.getOperands(), getType(),
2340 [&resEleType](const APInt &a, bool &castStatus) {
2341 FloatType floatTy = llvm::cast<FloatType>(resEleType);
2342 APFloat apf(floatTy.getFloatSemantics(),
2343 APInt::getZero(floatTy.getWidth()));
2344 if (a.isNegative() &&
2345 !APFloat::semanticsHasSignedRepr(floatTy.getFloatSemantics())) {
2346 castStatus = false;
2347 return apf;
2348 }
2349 apf.convertFromAPInt(a, /*IsSigned=*/true,
2350 APFloat::rmNearestTiesToEven);
2351 return apf;
2352 });
2353}
2354
2355void arith::SIToFPOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2356 MLIRContext *context) {
2357 patterns.add<SIToFPOfExtSI, SIToFPOfExtUI>(context);
2358}
2359
2360//===----------------------------------------------------------------------===//
2361// FPToUIOp
2362//===----------------------------------------------------------------------===//
2363
2364bool arith::FPToUIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2365 return checkIntFloatCast<FloatType, IntegerType>(inputs, outputs);
2366}
2367
2368OpFoldResult arith::FPToUIOp::fold(FoldAdaptor adaptor) {
2369 Type resType = getElementTypeOrSelf(getType());
2370 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
2372 adaptor.getOperands(), getType(),
2373 [&bitWidth](const APFloat &a, bool &castStatus) {
2374 bool ignored;
2375 APSInt api(bitWidth, /*isUnsigned=*/true);
2376 castStatus = APFloat::opInvalidOp !=
2377 a.convertToInteger(api, APFloat::rmTowardZero, &ignored);
2378 return api;
2379 });
2380}
2381
2382//===----------------------------------------------------------------------===//
2383// FPToSIOp
2384//===----------------------------------------------------------------------===//
2385
2386bool arith::FPToSIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2387 return checkIntFloatCast<FloatType, IntegerType>(inputs, outputs);
2388}
2389
2390OpFoldResult arith::FPToSIOp::fold(FoldAdaptor adaptor) {
2391 Type resType = getElementTypeOrSelf(getType());
2392 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
2394 adaptor.getOperands(), getType(),
2395 [&bitWidth](const APFloat &a, bool &castStatus) {
2396 bool ignored;
2397 APSInt api(bitWidth, /*isUnsigned=*/false);
2398 castStatus = APFloat::opInvalidOp !=
2399 a.convertToInteger(api, APFloat::rmTowardZero, &ignored);
2400 return api;
2401 });
2402}
2403
2404//===----------------------------------------------------------------------===//
2405// IndexCastOp
2406//===----------------------------------------------------------------------===//
2407
2408/// Return the bit-width of \p t for the purpose of index_cast width checks.
2409/// For vector types use the element type; index maps to its internal storage
2410/// width (64 on all current targets).
2411static unsigned getIndexCastWidth(Type t) {
2412 if (auto intTy = dyn_cast<IntegerType>(getElementTypeOrSelf(t)))
2413 return intTy.getWidth();
2414 return IndexType::kInternalStorageBitWidth;
2415}
2416
2417static bool areIndexCastCompatible(TypeRange inputs, TypeRange outputs) {
2418 if (!areValidCastInputsAndOutputs(inputs, outputs))
2419 return false;
2420
2421 auto srcType = getTypeIfLikeOrMemRef<IntegerType, IndexType>(inputs.front());
2422 auto dstType = getTypeIfLikeOrMemRef<IntegerType, IndexType>(outputs.front());
2423 if (!srcType || !dstType)
2424 return false;
2425
2426 return (srcType.isIndex() && dstType.isSignlessInteger()) ||
2427 (srcType.isSignlessInteger() && dstType.isIndex());
2428}
2429
2430bool arith::IndexCastOp::areCastCompatible(TypeRange inputs,
2431 TypeRange outputs) {
2432 return areIndexCastCompatible(inputs, outputs);
2433}
2434
2435OpFoldResult arith::IndexCastOp::fold(FoldAdaptor adaptor) {
2436 // index_cast(constant) -> constant
2437 unsigned resultBitwidth = 64; // Default for index integer attributes.
2438 if (auto intTy = dyn_cast<IntegerType>(getElementTypeOrSelf(getType())))
2439 resultBitwidth = intTy.getWidth();
2440
2441 if (auto foldResult = constFoldCastOp<IntegerAttr, IntegerAttr>(
2442 adaptor.getOperands(), getType(),
2443 [resultBitwidth](const APInt &a, bool & /*castStatus*/) {
2444 return a.sextOrTrunc(resultBitwidth);
2445 }))
2446 return foldResult;
2447
2448 // index_cast(index_cast(x : A) : B) : A -> x, but only when B is at least
2449 // as wide as A. If B is narrower, the inner cast truncates and the outer
2450 // cast sign-extends, so the round-trip is lossy.
2451 if (auto inner = getOperand().getDefiningOp<arith::IndexCastOp>()) {
2452 Value x = inner.getOperand();
2453 if (x.getType() == getType()) {
2454 if (getIndexCastWidth(inner.getType()) >= getIndexCastWidth(x.getType()))
2455 return x;
2456 }
2457 }
2458 return {};
2459}
2460
2461void arith::IndexCastOp::getCanonicalizationPatterns(
2462 RewritePatternSet &patterns, MLIRContext *context) {
2463 patterns.add<IndexCastOfExtSI>(context);
2464}
2465
2466//===----------------------------------------------------------------------===//
2467// IndexCastUIOp
2468//===----------------------------------------------------------------------===//
2469
2470bool arith::IndexCastUIOp::areCastCompatible(TypeRange inputs,
2471 TypeRange outputs) {
2472 return areIndexCastCompatible(inputs, outputs);
2473}
2474
2475OpFoldResult arith::IndexCastUIOp::fold(FoldAdaptor adaptor) {
2476 // index_castui(constant) -> constant
2477 unsigned resultBitwidth = 64; // Default for index integer attributes.
2478 if (auto intTy = dyn_cast<IntegerType>(getElementTypeOrSelf(getType())))
2479 resultBitwidth = intTy.getWidth();
2480
2481 if (auto foldResult = constFoldCastOp<IntegerAttr, IntegerAttr>(
2482 adaptor.getOperands(), getType(),
2483 [resultBitwidth](const APInt &a, bool & /*castStatus*/) {
2484 return a.zextOrTrunc(resultBitwidth);
2485 }))
2486 return foldResult;
2487
2488 // index_castui(index_castui(x : A) : B) : A -> x, but only when B is at
2489 // least as wide as A. If B is narrower, the inner cast truncates and the
2490 // outer cast zero-extends, so the round-trip is lossy.
2491 if (auto inner = getOperand().getDefiningOp<arith::IndexCastUIOp>()) {
2492 Value x = inner.getOperand();
2493 if (x.getType() == getType()) {
2494 if (getIndexCastWidth(inner.getType()) >= getIndexCastWidth(x.getType()))
2495 return x;
2496 }
2497 }
2498 return {};
2499}
2500
2501void arith::IndexCastUIOp::getCanonicalizationPatterns(
2502 RewritePatternSet &patterns, MLIRContext *context) {
2503 patterns.add<IndexCastUIOfExtUI>(context);
2504}
2505
2506//===----------------------------------------------------------------------===//
2507// BitcastOp
2508//===----------------------------------------------------------------------===//
2509
2510bool arith::BitcastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2511 if (!areValidCastInputsAndOutputs(inputs, outputs))
2512 return false;
2513
2514 auto srcType = getTypeIfLikeOrMemRef<IntegerType, FloatType>(inputs.front());
2515 auto dstType = getTypeIfLikeOrMemRef<IntegerType, FloatType>(outputs.front());
2516 if (!srcType || !dstType)
2517 return false;
2518
2519 return srcType.getIntOrFloatBitWidth() == dstType.getIntOrFloatBitWidth();
2520}
2521
2522OpFoldResult arith::BitcastOp::fold(FoldAdaptor adaptor) {
2523 auto resType = getType();
2524 auto operand = adaptor.getIn();
2525 if (!operand)
2526 return {};
2527
2528 /// Bitcast dense elements.
2529 if (auto denseAttr = dyn_cast_or_null<DenseElementsAttr>(operand))
2530 return denseAttr.bitcast(llvm::cast<ShapedType>(resType).getElementType());
2531 /// Other shaped types unhandled.
2532 if (llvm::isa<ShapedType>(resType))
2533 return {};
2534
2535 /// Bitcast poison.
2536 if (matchPattern(operand, ub::m_Poison()))
2537 return ub::PoisonAttr::get(getContext());
2538
2539 /// Bitcast integer or float to integer or float.
2540 if (!llvm::isa<FloatAttr, IntegerAttr>(operand))
2541 return {};
2542
2543 APInt bits = llvm::isa<FloatAttr>(operand)
2544 ? llvm::cast<FloatAttr>(operand).getValue().bitcastToAPInt()
2545 : llvm::cast<IntegerAttr>(operand).getValue();
2546 assert(resType.getIntOrFloatBitWidth() == bits.getBitWidth() &&
2547 "trying to fold on broken IR: operands have incompatible types");
2548
2549 if (auto resFloatType = dyn_cast<FloatType>(resType))
2550 return FloatAttr::get(resType,
2551 APFloat(resFloatType.getFloatSemantics(), bits));
2552 return IntegerAttr::get(resType, bits);
2553}
2554
2555void arith::BitcastOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2556 MLIRContext *context) {
2557 patterns.add<BitcastOfBitcast>(context);
2558}
2559
2560//===----------------------------------------------------------------------===//
2561// CmpIOp
2562//===----------------------------------------------------------------------===//
2563
2564/// Compute `lhs` `pred` `rhs`, where `pred` is one of the known integer
2565/// comparison predicates.
2566bool mlir::arith::applyCmpPredicate(arith::CmpIPredicate predicate,
2567 const APInt &lhs, const APInt &rhs) {
2568 switch (predicate) {
2569 case arith::CmpIPredicate::eq:
2570 return lhs.eq(rhs);
2571 case arith::CmpIPredicate::ne:
2572 return lhs.ne(rhs);
2573 case arith::CmpIPredicate::slt:
2574 return lhs.slt(rhs);
2575 case arith::CmpIPredicate::sle:
2576 return lhs.sle(rhs);
2577 case arith::CmpIPredicate::sgt:
2578 return lhs.sgt(rhs);
2579 case arith::CmpIPredicate::sge:
2580 return lhs.sge(rhs);
2581 case arith::CmpIPredicate::ult:
2582 return lhs.ult(rhs);
2583 case arith::CmpIPredicate::ule:
2584 return lhs.ule(rhs);
2585 case arith::CmpIPredicate::ugt:
2586 return lhs.ugt(rhs);
2587 case arith::CmpIPredicate::uge:
2588 return lhs.uge(rhs);
2589 }
2590 llvm_unreachable("unknown cmpi predicate kind");
2591}
2592
2593/// Returns true if the predicate is true for two equal operands.
2594static bool applyCmpPredicateToEqualOperands(arith::CmpIPredicate predicate) {
2595 switch (predicate) {
2596 case arith::CmpIPredicate::eq:
2597 case arith::CmpIPredicate::sle:
2598 case arith::CmpIPredicate::sge:
2599 case arith::CmpIPredicate::ule:
2600 case arith::CmpIPredicate::uge:
2601 return true;
2602 case arith::CmpIPredicate::ne:
2603 case arith::CmpIPredicate::slt:
2604 case arith::CmpIPredicate::sgt:
2605 case arith::CmpIPredicate::ult:
2606 case arith::CmpIPredicate::ugt:
2607 return false;
2608 }
2609 llvm_unreachable("unknown cmpi predicate kind");
2610}
2611
2612static std::optional<int64_t> getIntegerWidth(Type t) {
2613 if (auto intType = dyn_cast<IntegerType>(t)) {
2614 return intType.getWidth();
2615 }
2616 if (auto vectorIntType = dyn_cast<VectorType>(t)) {
2617 return llvm::cast<IntegerType>(vectorIntType.getElementType()).getWidth();
2618 }
2619 return std::nullopt;
2620}
2621
2622OpFoldResult arith::CmpIOp::fold(FoldAdaptor adaptor) {
2623 // cmpi(pred, x, x)
2624 if (getLhs() == getRhs()) {
2625 auto val = applyCmpPredicateToEqualOperands(getPredicate());
2626 return getBoolAttribute(getType(), val);
2627 }
2628
2629 if (matchPattern(adaptor.getRhs(), m_Zero())) {
2630 if (auto extOp = getLhs().getDefiningOp<ExtSIOp>()) {
2631 // extsi(%x : i1 -> iN) != 0 -> %x
2632 std::optional<int64_t> integerWidth =
2633 getIntegerWidth(extOp.getOperand().getType());
2634 if (integerWidth && integerWidth.value() == 1 &&
2635 getPredicate() == arith::CmpIPredicate::ne)
2636 return extOp.getOperand();
2637 }
2638 if (auto extOp = getLhs().getDefiningOp<ExtUIOp>()) {
2639 // extui(%x : i1 -> iN) != 0 -> %x
2640 std::optional<int64_t> integerWidth =
2641 getIntegerWidth(extOp.getOperand().getType());
2642 if (integerWidth && integerWidth.value() == 1 &&
2643 getPredicate() == arith::CmpIPredicate::ne)
2644 return extOp.getOperand();
2645 }
2646
2647 // arith.cmpi ne, %val, %zero : i1 -> %val
2648 if (getElementTypeOrSelf(getLhs().getType()).isInteger(1) &&
2649 getPredicate() == arith::CmpIPredicate::ne)
2650 return getLhs();
2651 }
2652
2653 if (matchPattern(adaptor.getRhs(), m_One())) {
2654 // arith.cmpi eq, %val, %one : i1 -> %val
2655 if (getElementTypeOrSelf(getLhs().getType()).isInteger(1) &&
2656 getPredicate() == arith::CmpIPredicate::eq)
2657 return getLhs();
2658 }
2659
2660 // Move constant to the right side.
2661 if (adaptor.getLhs() && !adaptor.getRhs()) {
2662 // Do not use invertPredicate, as it will change eq to ne and vice versa.
2663 using Pred = CmpIPredicate;
2664 const std::pair<Pred, Pred> invPreds[] = {
2665 {Pred::slt, Pred::sgt}, {Pred::sgt, Pred::slt}, {Pred::sle, Pred::sge},
2666 {Pred::sge, Pred::sle}, {Pred::ult, Pred::ugt}, {Pred::ugt, Pred::ult},
2667 {Pred::ule, Pred::uge}, {Pred::uge, Pred::ule}, {Pred::eq, Pred::eq},
2668 {Pred::ne, Pred::ne},
2669 };
2670 Pred origPred = getPredicate();
2671 for (auto pred : invPreds) {
2672 if (origPred == pred.first) {
2673 setPredicate(pred.second);
2674 Value lhs = getLhs();
2675 Value rhs = getRhs();
2676 getLhsMutable().assign(rhs);
2677 getRhsMutable().assign(lhs);
2678 return getResult();
2679 }
2680 }
2681 llvm_unreachable("unknown cmpi predicate kind");
2682 }
2683
2684 // We are moving constants to the right side; So if lhs is constant rhs is
2685 // guaranteed to be a constant.
2686 if (auto lhs = dyn_cast_if_present<TypedAttr>(adaptor.getLhs())) {
2688 adaptor.getOperands(), getI1SameShape(lhs.getType()),
2689 [pred = getPredicate()](const APInt &lhs, const APInt &rhs) {
2690 return APInt(1,
2691 static_cast<int64_t>(applyCmpPredicate(pred, lhs, rhs)));
2692 });
2693 }
2694
2695 return {};
2696}
2697
2698void arith::CmpIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2699 MLIRContext *context) {
2700 patterns.insert<CmpIExtSI, CmpIExtUI>(context);
2701}
2702
2703//===----------------------------------------------------------------------===//
2704// CmpFOp
2705//===----------------------------------------------------------------------===//
2706
2707/// Compute `lhs` `pred` `rhs`, where `pred` is one of the known floating point
2708/// comparison predicates.
2709bool mlir::arith::applyCmpPredicate(arith::CmpFPredicate predicate,
2710 const APFloat &lhs, const APFloat &rhs) {
2711 auto cmpResult = lhs.compare(rhs);
2712 switch (predicate) {
2713 case arith::CmpFPredicate::AlwaysFalse:
2714 return false;
2715 case arith::CmpFPredicate::OEQ:
2716 return cmpResult == APFloat::cmpEqual;
2717 case arith::CmpFPredicate::OGT:
2718 return cmpResult == APFloat::cmpGreaterThan;
2719 case arith::CmpFPredicate::OGE:
2720 return cmpResult == APFloat::cmpGreaterThan ||
2721 cmpResult == APFloat::cmpEqual;
2722 case arith::CmpFPredicate::OLT:
2723 return cmpResult == APFloat::cmpLessThan;
2724 case arith::CmpFPredicate::OLE:
2725 return cmpResult == APFloat::cmpLessThan || cmpResult == APFloat::cmpEqual;
2726 case arith::CmpFPredicate::ONE:
2727 return cmpResult != APFloat::cmpUnordered && cmpResult != APFloat::cmpEqual;
2728 case arith::CmpFPredicate::ORD:
2729 return cmpResult != APFloat::cmpUnordered;
2730 case arith::CmpFPredicate::UEQ:
2731 return cmpResult == APFloat::cmpUnordered || cmpResult == APFloat::cmpEqual;
2732 case arith::CmpFPredicate::UGT:
2733 return cmpResult == APFloat::cmpUnordered ||
2734 cmpResult == APFloat::cmpGreaterThan;
2735 case arith::CmpFPredicate::UGE:
2736 return cmpResult == APFloat::cmpUnordered ||
2737 cmpResult == APFloat::cmpGreaterThan ||
2738 cmpResult == APFloat::cmpEqual;
2739 case arith::CmpFPredicate::ULT:
2740 return cmpResult == APFloat::cmpUnordered ||
2741 cmpResult == APFloat::cmpLessThan;
2742 case arith::CmpFPredicate::ULE:
2743 return cmpResult == APFloat::cmpUnordered ||
2744 cmpResult == APFloat::cmpLessThan || cmpResult == APFloat::cmpEqual;
2745 case arith::CmpFPredicate::UNE:
2746 return cmpResult != APFloat::cmpEqual;
2747 case arith::CmpFPredicate::UNO:
2748 return cmpResult == APFloat::cmpUnordered;
2749 case arith::CmpFPredicate::AlwaysTrue:
2750 return true;
2751 }
2752 llvm_unreachable("unknown cmpf predicate kind");
2753}
2754
2755OpFoldResult arith::CmpFOp::fold(FoldAdaptor adaptor) {
2756 auto lhs = dyn_cast_if_present<FloatAttr>(adaptor.getLhs());
2757 auto rhs = dyn_cast_if_present<FloatAttr>(adaptor.getRhs());
2758
2759 // If one operand is NaN, making them both NaN does not change the result.
2760 if (lhs && lhs.getValue().isNaN())
2761 rhs = lhs;
2762 if (rhs && rhs.getValue().isNaN())
2763 lhs = rhs;
2764
2765 if (!lhs || !rhs)
2766 return {};
2767
2768 auto val = applyCmpPredicate(getPredicate(), lhs.getValue(), rhs.getValue());
2769 return BoolAttr::get(getContext(), val);
2770}
2771
2772class CmpFIntToFPConst final : public OpRewritePattern<CmpFOp> {
2773public:
2774 using Base::Base;
2775
2776 static CmpIPredicate convertToIntegerPredicate(CmpFPredicate pred,
2777 bool isUnsigned) {
2778 using namespace arith;
2779 switch (pred) {
2780 case CmpFPredicate::UEQ:
2781 case CmpFPredicate::OEQ:
2782 return CmpIPredicate::eq;
2783 case CmpFPredicate::UGT:
2784 case CmpFPredicate::OGT:
2785 return isUnsigned ? CmpIPredicate::ugt : CmpIPredicate::sgt;
2786 case CmpFPredicate::UGE:
2787 case CmpFPredicate::OGE:
2788 return isUnsigned ? CmpIPredicate::uge : CmpIPredicate::sge;
2789 case CmpFPredicate::ULT:
2790 case CmpFPredicate::OLT:
2791 return isUnsigned ? CmpIPredicate::ult : CmpIPredicate::slt;
2792 case CmpFPredicate::ULE:
2793 case CmpFPredicate::OLE:
2794 return isUnsigned ? CmpIPredicate::ule : CmpIPredicate::sle;
2795 case CmpFPredicate::UNE:
2796 case CmpFPredicate::ONE:
2797 return CmpIPredicate::ne;
2798 default:
2799 llvm_unreachable("Unexpected predicate!");
2800 }
2801 }
2802
2803 LogicalResult matchAndRewrite(CmpFOp op,
2804 PatternRewriter &rewriter) const override {
2805 FloatAttr flt;
2806 if (!matchPattern(op.getRhs(), m_Constant(&flt)))
2807 return failure();
2808
2809 const APFloat &rhs = flt.getValue();
2810
2811 // Don't attempt to fold a nan.
2812 if (rhs.isNaN())
2813 return failure();
2814
2815 // Get the width of the mantissa. We don't want to hack on conversions that
2816 // might lose information from the integer, e.g. "i64 -> float"
2817 FloatType floatTy = llvm::cast<FloatType>(op.getRhs().getType());
2818 int mantissaWidth = floatTy.getFPMantissaWidth();
2819 if (mantissaWidth <= 0)
2820 return failure();
2821
2822 bool isUnsigned;
2823 Value intVal;
2824
2825 if (auto si = op.getLhs().getDefiningOp<SIToFPOp>()) {
2826 isUnsigned = false;
2827 intVal = si.getIn();
2828 } else if (auto ui = op.getLhs().getDefiningOp<UIToFPOp>()) {
2829 isUnsigned = true;
2830 intVal = ui.getIn();
2831 } else {
2832 return failure();
2833 }
2834
2835 // Check to see that the input is converted from an integer type that is
2836 // small enough that preserves all bits.
2837 auto intTy = llvm::cast<IntegerType>(intVal.getType());
2838 auto intWidth = intTy.getWidth();
2839
2840 // Number of bits representing values, as opposed to the sign
2841 auto valueBits = isUnsigned ? intWidth : (intWidth - 1);
2842
2843 const llvm::fltSemantics &semantics = floatTy.getFloatSemantics();
2844
2845 // Conservatively require an exact representation of integer zero. For
2846 // example, f8E8M0FNU converts zero to its smallest positive value.
2847 if (!APFloat::semanticsHasZero(semantics))
2848 return failure();
2849
2850 // Signed inputs may be negative, which e.g. f8E5M3FNU cannot represent.
2851 // Do not try to convert the signed minimum below for such formats.
2852 if (!isUnsigned && !APFloat::semanticsHasSignedRepr(semantics))
2853 return failure();
2854
2855 if (!APFloat::semanticsHasInf(semantics)) {
2856 // Overflow can produce NaN or saturate to a finite value:
2857 // - sitofp(1000 : i32) to f8E4M3FN is NaN, so cmpf ogt against 0.0
2858 // is false, whereas cmpi sgt against integer zero would be true.
2859 // - uitofp(8 : i4) to f6E2M3FN saturates to 7.5. Equality against 7.5
2860 // is true, but the fractional-constant logic below would fold it to
2861 // false. Its four bits of precision do not prevent overflow.
2862 //
2863 // Conservatively require the enclosing 2^valueBits (and its negative
2864 // for signed inputs) to be representable, so rounding stays within
2865 // finite bounds. Equality at the exponent bound is safe.
2866 if (APFloat::semanticsMaxExponent(semantics) <
2867 static_cast<int>(valueBits))
2868 return failure();
2869 }
2870
2871 // Following test does NOT adjust intWidth downwards for signed inputs,
2872 // because the most negative value still requires all the mantissa bits
2873 // to distinguish it from one less than that value.
2874 if ((int)intWidth > mantissaWidth) {
2875 // Conversion would lose accuracy. Check if loss can impact comparison.
2876 int exponent = ilogb(rhs);
2877 if (exponent == APFloat::IEK_Inf) {
2878 int maxExponent = ilogb(APFloat::getLargest(rhs.getSemantics()));
2879 if (maxExponent < static_cast<int>(valueBits)) {
2880 // Conversion could create infinity. For example, sitofp(65536 : i32)
2881 // to f16 is +inf, so equality against +inf can be true.
2882 return failure();
2883 }
2884 } else {
2885 // Note that if rhs is zero or NaN, then Exp is negative
2886 // and first condition is trivially false.
2887 if (mantissaWidth <= exponent && exponent <= (int)valueBits) {
2888 // Conversion could affect comparison.
2889 return failure();
2890 }
2891 }
2892 }
2893
2894 // Convert to equivalent cmpi predicate
2895 CmpIPredicate pred;
2896 switch (op.getPredicate()) {
2897 case CmpFPredicate::AlwaysTrue:
2898 case CmpFPredicate::ORD:
2899 // Int to fp conversion doesn't create a nan (ord checks neither is a nan)
2900 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2901 /*width=*/1);
2902 return success();
2903 case CmpFPredicate::AlwaysFalse:
2904 case CmpFPredicate::UNO:
2905 // Int to fp conversion doesn't create a nan (uno checks either is a nan)
2906 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2907 /*width=*/1);
2908 return success();
2909 default:
2910 pred = convertToIntegerPredicate(op.getPredicate(), isUnsigned);
2911 break;
2912 }
2913
2914 if (!isUnsigned) {
2915 // If the rhs value is > SignedMax, fold the comparison. This handles
2916 // +INF and large values.
2917 APFloat signedMax(rhs.getSemantics());
2918 signedMax.convertFromAPInt(APInt::getSignedMaxValue(intWidth), true,
2919 APFloat::rmNearestTiesToEven);
2920 if (signedMax < rhs) { // smax < 13123.0
2921 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::slt ||
2922 pred == CmpIPredicate::sle)
2923 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2924 /*width=*/1);
2925 else
2926 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2927 /*width=*/1);
2928 return success();
2929 }
2930 } else {
2931 // If the rhs value is > UnsignedMax, fold the comparison. This handles
2932 // +INF and large values.
2933 APFloat unsignedMax(rhs.getSemantics());
2934 unsignedMax.convertFromAPInt(APInt::getMaxValue(intWidth), false,
2935 APFloat::rmNearestTiesToEven);
2936 if (unsignedMax < rhs) { // umax < 13123.0
2937 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::ult ||
2938 pred == CmpIPredicate::ule)
2939 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2940 /*width=*/1);
2941 else
2942 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2943 /*width=*/1);
2944 return success();
2945 }
2946 }
2947
2948 if (!isUnsigned) {
2949 // See if the rhs value is < SignedMin.
2950 APFloat signedMin(rhs.getSemantics());
2951 signedMin.convertFromAPInt(APInt::getSignedMinValue(intWidth), true,
2952 APFloat::rmNearestTiesToEven);
2953 if (signedMin > rhs) { // smin > 12312.0
2954 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::sgt ||
2955 pred == CmpIPredicate::sge)
2956 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2957 /*width=*/1);
2958 else
2959 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2960 /*width=*/1);
2961 return success();
2962 }
2963 } else {
2964 // See if the rhs value is < UnsignedMin.
2965 APFloat unsignedMin(rhs.getSemantics());
2966 unsignedMin.convertFromAPInt(APInt::getMinValue(intWidth), false,
2967 APFloat::rmNearestTiesToEven);
2968 if (unsignedMin > rhs) { // umin > 12312.0
2969 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::ugt ||
2970 pred == CmpIPredicate::uge)
2971 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2972 /*width=*/1);
2973 else
2974 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2975 /*width=*/1);
2976 return success();
2977 }
2978 }
2979
2980 // Okay, now we know that the FP constant fits in the range [SMIN, SMAX] or
2981 // [0, UMAX], but it may still be fractional. See if it is fractional by
2982 // casting the FP value to the integer value and back, checking for
2983 // equality. Don't do this for zero, because -0.0 is not fractional.
2984 bool ignored;
2985 APSInt rhsInt(intWidth, isUnsigned);
2986 if (APFloat::opInvalidOp ==
2987 rhs.convertToInteger(rhsInt, APFloat::rmTowardZero, &ignored)) {
2988 // Undefined behavior invoked - the destination type can't represent
2989 // the input constant.
2990 return failure();
2991 }
2992
2993 if (!rhs.isZero()) {
2994 APFloat apf(floatTy.getFloatSemantics(),
2995 APInt::getZero(floatTy.getWidth()));
2996 apf.convertFromAPInt(rhsInt, !isUnsigned, APFloat::rmNearestTiesToEven);
2997
2998 bool equal = apf == rhs;
2999 if (!equal) {
3000 // If we had a comparison against a fractional value, we have to adjust
3001 // the compare predicate and sometimes the value. rhsInt is rounded
3002 // towards zero at this point.
3003 switch (pred) {
3004 case CmpIPredicate::ne: // (float)int != 4.4 --> true
3005 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
3006 /*width=*/1);
3007 return success();
3008 case CmpIPredicate::eq: // (float)int == 4.4 --> false
3009 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
3010 /*width=*/1);
3011 return success();
3012 case CmpIPredicate::ule:
3013 // (float)int <= 4.4 --> int <= 4
3014 // (float)int <= -4.4 --> false
3015 if (rhs.isNegative()) {
3016 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
3017 /*width=*/1);
3018 return success();
3019 }
3020 break;
3021 case CmpIPredicate::sle:
3022 // (float)int <= 4.4 --> int <= 4
3023 // (float)int <= -4.4 --> int < -4
3024 if (rhs.isNegative())
3025 pred = CmpIPredicate::slt;
3026 break;
3027 case CmpIPredicate::ult:
3028 // (float)int < -4.4 --> false
3029 // (float)int < 4.4 --> int <= 4
3030 if (rhs.isNegative()) {
3031 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
3032 /*width=*/1);
3033 return success();
3034 }
3035 pred = CmpIPredicate::ule;
3036 break;
3037 case CmpIPredicate::slt:
3038 // (float)int < -4.4 --> int < -4
3039 // (float)int < 4.4 --> int <= 4
3040 if (!rhs.isNegative())
3041 pred = CmpIPredicate::sle;
3042 break;
3043 case CmpIPredicate::ugt:
3044 // (float)int > 4.4 --> int > 4
3045 // (float)int > -4.4 --> true
3046 if (rhs.isNegative()) {
3047 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
3048 /*width=*/1);
3049 return success();
3050 }
3051 break;
3052 case CmpIPredicate::sgt:
3053 // (float)int > 4.4 --> int > 4
3054 // (float)int > -4.4 --> int >= -4
3055 if (rhs.isNegative())
3056 pred = CmpIPredicate::sge;
3057 break;
3058 case CmpIPredicate::uge:
3059 // (float)int >= -4.4 --> true
3060 // (float)int >= 4.4 --> int > 4
3061 if (rhs.isNegative()) {
3062 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
3063 /*width=*/1);
3064 return success();
3065 }
3066 pred = CmpIPredicate::ugt;
3067 break;
3068 case CmpIPredicate::sge:
3069 // (float)int >= -4.4 --> int >= -4
3070 // (float)int >= 4.4 --> int > 4
3071 if (!rhs.isNegative())
3072 pred = CmpIPredicate::sgt;
3073 break;
3074 }
3075 }
3076 }
3077
3078 // Lower this FP comparison into an appropriate integer version of the
3079 // comparison.
3080 rewriter.replaceOpWithNewOp<CmpIOp>(
3081 op, pred, intVal,
3082 ConstantOp::create(rewriter, op.getLoc(), intVal.getType(),
3083 rewriter.getIntegerAttr(intVal.getType(), rhsInt)));
3084 return success();
3085 }
3086};
3087
3088void arith::CmpFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
3089 MLIRContext *context) {
3090 patterns.insert<CmpFIntToFPConst>(context);
3091}
3092
3093//===----------------------------------------------------------------------===//
3094// SelectOp
3095//===----------------------------------------------------------------------===//
3096
3097// select %arg, %c1, %c0 => extui %arg
3098struct SelectToExtUI : public OpRewritePattern<arith::SelectOp> {
3099 using Base::Base;
3100
3101 LogicalResult matchAndRewrite(arith::SelectOp op,
3102 PatternRewriter &rewriter) const override {
3103 // Cannot extui i1 to i1, or i1 to f32
3104 if (!llvm::isa<IntegerType>(op.getType()) || op.getType().isInteger(1))
3105 return failure();
3106
3107 // select %x, c1, %c0 => extui %arg
3108 if (matchPattern(op.getTrueValue(), m_One()) &&
3109 matchPattern(op.getFalseValue(), m_Zero())) {
3110 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(op, op.getType(),
3111 op.getCondition());
3112 return success();
3113 }
3114
3115 // select %x, c0, %c1 => extui (xor %arg, true)
3116 if (matchPattern(op.getTrueValue(), m_Zero()) &&
3117 matchPattern(op.getFalseValue(), m_One())) {
3118 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(
3119 op, op.getType(),
3120 arith::XOrIOp::create(
3121 rewriter, op.getLoc(), op.getCondition(),
3122 arith::ConstantIntOp::create(rewriter, op.getLoc(),
3123 op.getCondition().getType(), 1)));
3124 return success();
3125 }
3126
3127 return failure();
3128 }
3129};
3130
3131void arith::SelectOp::getCanonicalizationPatterns(RewritePatternSet &results,
3132 MLIRContext *context) {
3133 results.add<RedundantSelectFalse, RedundantSelectTrue, SelectNotCond,
3134 SelectI1ToNot, SelectCmpISgeToMaxSI, SelectCmpISgeToMinSI,
3135 SelectCmpISgtToMaxSI, SelectCmpISgtToMinSI, SelectCmpISleToMaxSI,
3136 SelectCmpISleToMinSI, SelectCmpISltToMaxSI, SelectCmpISltToMinSI,
3137 SelectCmpIUgeToMaxUI, SelectCmpIUgeToMinUI, SelectCmpIUgtToMaxUI,
3138 SelectCmpIUgtToMinUI, SelectCmpIUleToMaxUI, SelectCmpIUleToMinUI,
3139 SelectCmpIUltToMaxUI, SelectCmpIUltToMinUI, SelectToExtUI>(
3140 context);
3141}
3142
3143OpFoldResult arith::SelectOp::fold(FoldAdaptor adaptor) {
3144 Value trueVal = getTrueValue();
3145 Value falseVal = getFalseValue();
3146 if (trueVal == falseVal)
3147 return trueVal;
3148
3149 Value condition = getCondition();
3150
3151 // select true, %0, %1 => %0
3152 if (matchPattern(adaptor.getCondition(), m_One()))
3153 return trueVal;
3154
3155 // select false, %0, %1 => %1
3156 if (matchPattern(adaptor.getCondition(), m_Zero()))
3157 return falseVal;
3158
3159 // If either operand is fully poisoned, return the other.
3160 if (matchPattern(adaptor.getTrueValue(), ub::m_Poison()))
3161 return falseVal;
3162
3163 if (matchPattern(adaptor.getFalseValue(), ub::m_Poison()))
3164 return trueVal;
3165
3166 // select %x, true, false => %x
3167 if (getType().isSignlessInteger(1) &&
3168 matchPattern(adaptor.getTrueValue(), m_One()) &&
3169 matchPattern(adaptor.getFalseValue(), m_Zero()))
3170 return condition;
3171
3172 if (auto cmp = condition.getDefiningOp<arith::CmpIOp>()) {
3173 auto pred = cmp.getPredicate();
3174 if (pred == arith::CmpIPredicate::eq || pred == arith::CmpIPredicate::ne) {
3175 auto cmpLhs = cmp.getLhs();
3176 auto cmpRhs = cmp.getRhs();
3177
3178 // %0 = arith.cmpi eq, %arg0, %arg1
3179 // %1 = arith.select %0, %arg0, %arg1 => %arg1
3180
3181 // %0 = arith.cmpi ne, %arg0, %arg1
3182 // %1 = arith.select %0, %arg0, %arg1 => %arg0
3183
3184 if ((cmpLhs == trueVal && cmpRhs == falseVal) ||
3185 (cmpRhs == trueVal && cmpLhs == falseVal))
3186 return pred == arith::CmpIPredicate::ne ? trueVal : falseVal;
3187 }
3188 }
3189
3190 // Constant-fold constant operands over non-splat constant condition.
3191 // select %cst_vec, %cst0, %cst1 => %cst2
3192 if (auto cond =
3193 dyn_cast_if_present<DenseElementsAttr>(adaptor.getCondition())) {
3194 // DenseElementsAttr by construction always has a static shape.
3195 assert(cond.getType().hasStaticShape() &&
3196 "DenseElementsAttr must have static shape");
3197 if (auto lhs =
3198 dyn_cast_if_present<DenseElementsAttr>(adaptor.getTrueValue())) {
3199 if (auto rhs =
3200 dyn_cast_if_present<DenseElementsAttr>(adaptor.getFalseValue())) {
3201 SmallVector<Attribute> results;
3202 results.reserve(static_cast<size_t>(cond.getNumElements()));
3203 auto condVals = llvm::make_range(cond.value_begin<BoolAttr>(),
3204 cond.value_end<BoolAttr>());
3205 auto lhsVals = llvm::make_range(lhs.value_begin<Attribute>(),
3206 lhs.value_end<Attribute>());
3207 auto rhsVals = llvm::make_range(rhs.value_begin<Attribute>(),
3208 rhs.value_end<Attribute>());
3209
3210 for (auto [condVal, lhsVal, rhsVal] :
3211 llvm::zip_equal(condVals, lhsVals, rhsVals))
3212 results.push_back(condVal.getValue() ? lhsVal : rhsVal);
3213
3214 return DenseElementsAttr::get(lhs.getType(), results);
3215 }
3216 }
3217 }
3218
3219 return nullptr;
3220}
3221
3222ParseResult SelectOp::parse(OpAsmParser &parser, OperationState &result) {
3223 Type conditionType, resultType;
3224 SmallVector<OpAsmParser::UnresolvedOperand, 3> operands;
3225 if (parser.parseOperandList(operands, /*requiredOperandCount=*/3) ||
3226 parser.parseOptionalAttrDict(result.attributes) ||
3227 parser.parseColonType(resultType))
3228 return failure();
3229
3230 // Check for the explicit condition type if this is a masked tensor or vector.
3231 if (succeeded(parser.parseOptionalComma())) {
3232 conditionType = resultType;
3233 if (parser.parseType(resultType))
3234 return failure();
3235 } else {
3236 conditionType = parser.getBuilder().getI1Type();
3237 }
3238
3239 result.addTypes(resultType);
3240 return parser.resolveOperands(operands,
3241 {conditionType, resultType, resultType},
3242 parser.getNameLoc(), result.operands);
3243}
3244
3245void arith::SelectOp::print(OpAsmPrinter &p) {
3246 p << " " << getOperands();
3247 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
3248 p << " : ";
3249 if (ShapedType condType = dyn_cast<ShapedType>(getCondition().getType()))
3250 p << condType << ", ";
3251 p << getType();
3252}
3253
3254LogicalResult arith::SelectOp::verify() {
3255 Type conditionType = getCondition().getType();
3256 if (conditionType.isSignlessInteger(1))
3257 return success();
3258
3259 // If the result type is a vector or tensor, the type can be a mask with the
3260 // same elements.
3261 Type resultType = getType();
3262 if (!llvm::isa<TensorType, VectorType>(resultType))
3263 return emitOpError() << "expected condition to be a signless i1, but got "
3264 << conditionType;
3265 Type shapedConditionType = getI1SameShape(resultType);
3266 if (conditionType != shapedConditionType) {
3267 return emitOpError() << "expected condition type to have the same shape "
3268 "as the result type, expected "
3269 << shapedConditionType << ", but got "
3270 << conditionType;
3271 }
3272 return success();
3273}
3274//===----------------------------------------------------------------------===//
3275// ShLIOp
3276//===----------------------------------------------------------------------===//
3277
3278OpFoldResult arith::ShLIOp::fold(FoldAdaptor adaptor) {
3279 // TODO: shli(x, c) -> poison when c is out of range (c >= bit width). An
3280 // out-of-range shift amount is undefined behaviour and could fold to poison,
3281 // but that would make the arith dialect depend on the ub dialect to
3282 // materialize ub.poison; left out for now.
3283
3284 // shli(x, 0) -> x
3285 if (matchPattern(adaptor.getRhs(), m_Zero()))
3286 return getLhs();
3287 // shli(0, x) -> 0. An out-of-range shift amount yields poison, so refining
3288 // it to 0 is valid.
3289 if (matchPattern(adaptor.getLhs(), m_Zero()))
3290 return getLhs();
3291 // Don't fold if shifting more or equal than the bit width.
3292 bool bounded = false;
3294 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
3295 bounded = b.ult(b.getBitWidth());
3296 return a.shl(b);
3297 });
3298 return bounded ? result : Attribute();
3299}
3300
3301//===----------------------------------------------------------------------===//
3302// ShRUIOp
3303//===----------------------------------------------------------------------===//
3304
3305OpFoldResult arith::ShRUIOp::fold(FoldAdaptor adaptor) {
3306 // TODO: shrui(x, c) -> poison when c is out of range (c >= bit width). An
3307 // out-of-range shift amount is undefined behaviour and could fold to poison,
3308 // but that would make the arith dialect depend on the ub dialect to
3309 // materialize ub.poison; left out for now.
3310
3311 // shrui(x, 0) -> x
3312 if (matchPattern(adaptor.getRhs(), m_Zero()))
3313 return getLhs();
3314 // shrui(0, x) -> 0. An out-of-range shift amount yields poison, so refining
3315 // it to 0 is valid.
3316 if (matchPattern(adaptor.getLhs(), m_Zero()))
3317 return getLhs();
3318 // shrui(x, x) -> 0. For any in-range shift amount v < bitwidth, v >> v == 0
3319 // (v < 2^v); out-of-range amounts yield poison, so 0 is a valid refinement.
3320 if (getLhs() == getRhs())
3321 return getIntegerAttrOfType(getType(), 0);
3322 // Don't fold if shifting more or equal than the bit width.
3323 bool bounded = false;
3325 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
3326 bounded = b.ult(b.getBitWidth());
3327 return a.lshr(b);
3328 });
3329 return bounded ? result : Attribute();
3330}
3331
3332//===----------------------------------------------------------------------===//
3333// ShRSIOp
3334//===----------------------------------------------------------------------===//
3335
3336OpFoldResult arith::ShRSIOp::fold(FoldAdaptor adaptor) {
3337 // TODO: shrsi(x, c) -> poison when c is out of range (c >= bit width). An
3338 // out-of-range shift amount is undefined behaviour and could fold to poison,
3339 // but that would make the arith dialect depend on the ub dialect to
3340 // materialize ub.poison; left out for now.
3341
3342 // shrsi(x, 0) -> x
3343 if (matchPattern(adaptor.getRhs(), m_Zero()))
3344 return getLhs();
3345 // shrsi(0, x) -> 0. An out-of-range shift amount yields poison, so refining
3346 // it to 0 is valid.
3347 if (matchPattern(adaptor.getLhs(), m_Zero()))
3348 return getLhs();
3349 // shrsi(x, x) -> 0. For any in-range shift amount v < bitwidth, v is a small
3350 // non-negative value and v >> v == 0; out-of-range amounts yield poison.
3351 if (getLhs() == getRhs())
3352 return getIntegerAttrOfType(getType(), 0);
3353 // shrsi(-1, x) -> -1. Arithmetic shift of all-ones is all-ones for any
3354 // in-range amount; out-of-range amounts yield poison.
3355 if (APInt val;
3356 matchPattern(adaptor.getLhs(), m_ConstantInt(&val)) && val.isAllOnes())
3357 return getLhs();
3358 // Don't fold if shifting more or equal than the bit width.
3359 bool bounded = false;
3361 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
3362 bounded = b.ult(b.getBitWidth());
3363 return a.ashr(b);
3364 });
3365 return bounded ? result : Attribute();
3366}
3367
3368//===----------------------------------------------------------------------===//
3369// Atomic Enum
3370//===----------------------------------------------------------------------===//
3371
3372/// Returns the identity value attribute associated with an AtomicRMWKind op.
3373TypedAttr mlir::arith::getIdentityValueAttr(AtomicRMWKind kind, Type resultType,
3374 OpBuilder &builder, Location loc,
3375 bool useOnlyFiniteValue) {
3376 switch (kind) {
3377 case AtomicRMWKind::maximumf: {
3378 const llvm::fltSemantics &semantic =
3379 llvm::cast<FloatType>(resultType).getFloatSemantics();
3380 APFloat identity = useOnlyFiniteValue
3381 ? APFloat::getLargest(semantic, /*Negative=*/true)
3382 : APFloat::getInf(semantic, /*Negative=*/true);
3383 return builder.getFloatAttr(resultType, identity);
3384 }
3385 case AtomicRMWKind::maxnumf: {
3386 const llvm::fltSemantics &semantic =
3387 llvm::cast<FloatType>(resultType).getFloatSemantics();
3388 APFloat identity = useOnlyFiniteValue
3389 ? APFloat::getLargest(semantic, /*Negative=*/true)
3390 : APFloat::getNaN(semantic, /*Negative=*/true);
3391 return builder.getFloatAttr(resultType, identity);
3392 }
3393 case AtomicRMWKind::addf:
3394 case AtomicRMWKind::addi:
3395 case AtomicRMWKind::maxu:
3396 case AtomicRMWKind::ori:
3397 case AtomicRMWKind::xori:
3398 return builder.getZeroAttr(resultType);
3399 case AtomicRMWKind::andi:
3400 return builder.getIntegerAttr(
3401 resultType,
3402 APInt::getAllOnes(llvm::cast<IntegerType>(resultType).getWidth()));
3403 case AtomicRMWKind::maxs:
3404 return builder.getIntegerAttr(
3405 resultType, APInt::getSignedMinValue(
3406 llvm::cast<IntegerType>(resultType).getWidth()));
3407 case AtomicRMWKind::minimumf: {
3408 const llvm::fltSemantics &semantic =
3409 llvm::cast<FloatType>(resultType).getFloatSemantics();
3410 APFloat identity = useOnlyFiniteValue
3411 ? APFloat::getLargest(semantic, /*Negative=*/false)
3412 : APFloat::getInf(semantic, /*Negative=*/false);
3413
3414 return builder.getFloatAttr(resultType, identity);
3415 }
3416 case AtomicRMWKind::minnumf: {
3417 const llvm::fltSemantics &semantic =
3418 llvm::cast<FloatType>(resultType).getFloatSemantics();
3419 APFloat identity = useOnlyFiniteValue
3420 ? APFloat::getLargest(semantic, /*Negative=*/false)
3421 : APFloat::getNaN(semantic, /*Negative=*/false);
3422 return builder.getFloatAttr(resultType, identity);
3423 }
3424 case AtomicRMWKind::mins:
3425 return builder.getIntegerAttr(
3426 resultType, APInt::getSignedMaxValue(
3427 llvm::cast<IntegerType>(resultType).getWidth()));
3428 case AtomicRMWKind::minu:
3429 return builder.getIntegerAttr(
3430 resultType,
3431 APInt::getMaxValue(llvm::cast<IntegerType>(resultType).getWidth()));
3432 case AtomicRMWKind::muli:
3433 return builder.getIntegerAttr(resultType, 1);
3434 case AtomicRMWKind::mulf:
3435 return builder.getFloatAttr(resultType, 1);
3436 // `assign` is not a reduction and has no identity element.
3437 case AtomicRMWKind::assign:
3438 break;
3439 }
3440 (void)emitOptionalError(loc, "Reduction operation type not supported");
3441 return nullptr;
3442}
3443
3444/// Returns the identity numeric value of the given op.
3445std::optional<TypedAttr> mlir::arith::getNeutralElement(Operation *op) {
3446 std::optional<AtomicRMWKind> maybeKind =
3448 // Floating-point operations.
3449 .Case([](arith::AddFOp op) { return AtomicRMWKind::addf; })
3450 .Case([](arith::MulFOp op) { return AtomicRMWKind::mulf; })
3451 .Case([](arith::MaximumFOp op) { return AtomicRMWKind::maximumf; })
3452 .Case([](arith::MinimumFOp op) { return AtomicRMWKind::minimumf; })
3453 .Case([](arith::MaxNumFOp op) { return AtomicRMWKind::maxnumf; })
3454 .Case([](arith::MinNumFOp op) { return AtomicRMWKind::minnumf; })
3455 // Integer operations.
3456 .Case([](arith::AddIOp op) { return AtomicRMWKind::addi; })
3457 .Case([](arith::OrIOp op) { return AtomicRMWKind::ori; })
3458 .Case([](arith::XOrIOp op) { return AtomicRMWKind::xori; })
3459 .Case([](arith::AndIOp op) { return AtomicRMWKind::andi; })
3460 .Case([](arith::MaxUIOp op) { return AtomicRMWKind::maxu; })
3461 .Case([](arith::MinUIOp op) { return AtomicRMWKind::minu; })
3462 .Case([](arith::MaxSIOp op) { return AtomicRMWKind::maxs; })
3463 .Case([](arith::MinSIOp op) { return AtomicRMWKind::mins; })
3464 .Case([](arith::MulIOp op) { return AtomicRMWKind::muli; })
3465 .Default(std::nullopt);
3466 if (!maybeKind) {
3467 return std::nullopt;
3468 }
3469
3470 bool useOnlyFiniteValue = false;
3471 auto fmfOpInterface = dyn_cast<ArithFastMathInterface>(op);
3472 if (fmfOpInterface) {
3473 arith::FastMathFlagsAttr fmfAttr = fmfOpInterface.getFastMathFlagsAttr();
3474 useOnlyFiniteValue =
3475 bitEnumContainsAny(fmfAttr.getValue(), arith::FastMathFlags::ninf);
3476 if (*maybeKind == AtomicRMWKind::maxnumf ||
3477 *maybeKind == AtomicRMWKind::minnumf) {
3478 if (bitEnumContainsAny(fmfAttr.getValue(), arith::FastMathFlags::nnan)) {
3479 *maybeKind = *maybeKind == AtomicRMWKind::maxnumf
3480 ? AtomicRMWKind::maximumf
3481 : AtomicRMWKind::minimumf;
3482 } else {
3483 useOnlyFiniteValue = false;
3484 }
3485 }
3486 }
3487
3488 // Builder only used as helper for attribute creation.
3489 OpBuilder b(op->getContext());
3490 Type resultType = op->getResult(0).getType();
3491
3492 return getIdentityValueAttr(*maybeKind, resultType, b, op->getLoc(),
3493 useOnlyFiniteValue);
3494}
3495
3496/// Returns the identity value associated with an AtomicRMWKind op.
3497Value mlir::arith::getIdentityValue(AtomicRMWKind op, Type resultType,
3498 OpBuilder &builder, Location loc,
3499 bool useOnlyFiniteValue) {
3500 if (auto attr = getIdentityValueAttr(op, resultType, builder, loc,
3501 useOnlyFiniteValue))
3502 return arith::ConstantOp::create(builder, loc, attr);
3503 return {};
3504}
3505
3506/// Return the value obtained by applying the reduction operation kind
3507/// associated with a binary AtomicRMWKind op to `lhs` and `rhs`.
3509 Location loc, Value lhs, Value rhs) {
3510 switch (op) {
3511 case AtomicRMWKind::addf:
3512 return arith::AddFOp::create(builder, loc, lhs, rhs);
3513 case AtomicRMWKind::addi:
3514 return arith::AddIOp::create(builder, loc, lhs, rhs);
3515 case AtomicRMWKind::mulf:
3516 return arith::MulFOp::create(builder, loc, lhs, rhs);
3517 case AtomicRMWKind::muli:
3518 return arith::MulIOp::create(builder, loc, lhs, rhs);
3519 case AtomicRMWKind::maximumf:
3520 return arith::MaximumFOp::create(builder, loc, lhs, rhs);
3521 case AtomicRMWKind::minimumf:
3522 return arith::MinimumFOp::create(builder, loc, lhs, rhs);
3523 case AtomicRMWKind::maxnumf:
3524 return arith::MaxNumFOp::create(builder, loc, lhs, rhs);
3525 case AtomicRMWKind::minnumf:
3526 return arith::MinNumFOp::create(builder, loc, lhs, rhs);
3527 case AtomicRMWKind::maxs:
3528 return arith::MaxSIOp::create(builder, loc, lhs, rhs);
3529 case AtomicRMWKind::mins:
3530 return arith::MinSIOp::create(builder, loc, lhs, rhs);
3531 case AtomicRMWKind::maxu:
3532 return arith::MaxUIOp::create(builder, loc, lhs, rhs);
3533 case AtomicRMWKind::minu:
3534 return arith::MinUIOp::create(builder, loc, lhs, rhs);
3535 case AtomicRMWKind::ori:
3536 return arith::OrIOp::create(builder, loc, lhs, rhs);
3537 case AtomicRMWKind::andi:
3538 return arith::AndIOp::create(builder, loc, lhs, rhs);
3539 case AtomicRMWKind::xori:
3540 return arith::XOrIOp::create(builder, loc, lhs, rhs);
3541 // `assign` is not a reduction and has no corresponding binary operation.
3542 case AtomicRMWKind::assign:
3543 break;
3544 }
3545 (void)emitOptionalError(loc, "Reduction operation type not supported");
3546 return nullptr;
3547}
3548
3549//===----------------------------------------------------------------------===//
3550// TableGen'd op method definitions
3551//===----------------------------------------------------------------------===//
3552
3553#define GET_OP_CLASSES
3554#include "mlir/Dialect/Arith/IR/ArithOps.cpp.inc"
3555
3556//===----------------------------------------------------------------------===//
3557// TableGen'd enum attribute definitions
3558//===----------------------------------------------------------------------===//
3559
3560#include "mlir/Dialect/Arith/IR/ArithOpsEnums.cpp.inc"
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
static Speculation::Speculatability getDivUISpeculatability(Value divisor)
Returns whether an unsigned division by divisor is speculatable.
Definition ArithOps.cpp:884
static bool checkWidthChangeCast(TypeRange inputs, TypeRange outputs)
Validate a cast that changes the width of a type.
static IntegerAttr mulIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs)
Definition ArithOps.cpp:66
static IntegerOverflowFlagsAttr mergeOverflowFlags(IntegerOverflowFlagsAttr val1, IntegerOverflowFlagsAttr val2)
Definition ArithOps.cpp:89
static Value foldIdempotentOfSameOp(OpTy op)
Fold op(a, op(a, b)) to op(a, b) for an associative, commutative and idempotent op (e....
static constexpr llvm::RoundingMode kDefaultRoundingMode
Default rounding mode according to default LLVM floating-point environment.
Definition ArithOps.cpp:39
static Type getTypeIfLike(Type type)
Get allowed underlying types for vectors and tensors.
static bool applyCmpPredicateToEqualOperands(arith::CmpIPredicate predicate)
Returns true if the predicate is true for two equal operands.
static FailureOr< APFloat > convertFloatValue(APFloat sourceValue, const llvm::fltSemantics &targetSemantics, llvm::RoundingMode roundingMode=kDefaultRoundingMode)
Attempts to convert sourceValue to an APFloat value with targetSemantics and roundingMode,...
static Attribute foldScalingCastOp(Attribute inAttr, Attribute scaleAttr, Type resultType, function_ref< std::optional< APFloat >(const APFloat &, const APFloat &)> calculate)
Fold calculate element-wise over the operands of a scaling cast op.
static Value foldDivMul(Value lhs, Value rhs, arith::IntegerOverflowFlags ovfFlags)
Fold (a * b) / b -> a
Definition ArithOps.cpp:833
static bool hasSameEncoding(Type typeA, Type typeB)
Return false if both types are ranked tensor with mismatching encoding.
static llvm::RoundingMode convertArithRoundingModeToLLVMIR(std::optional< RoundingMode > roundingMode)
Equivalent to convertRoundingModeToLLVM(convertArithRoundingModeToLLVM(roundingMode)).
Definition ArithOps.cpp:164
static Type getUnderlyingType(Type type, type_list< ShapedTypes... >, type_list< ElementTypes... >)
Returns a non-null type only if the provided type is one of the allowed types or one of the allowed s...
static std::optional< int64_t > getIntegerWidth(Type t)
APInt(APInt::*)(const APInt &, bool &) const OverflowCheckedBinaryOp
Definition ArithOps.cpp:95
static Speculation::Speculatability getDivSISpeculatability(Value divisor)
Returns whether a signed division by divisor is speculatable.
Definition ArithOps.cpp:938
static IntegerAttr orIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs)
Definition ArithOps.cpp:76
static IntegerAttr addIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs)
Definition ArithOps.cpp:56
static Attribute getBoolAttribute(Type type, bool value)
Definition ArithOps.cpp:207
static bool areIndexCastCompatible(TypeRange inputs, TypeRange outputs)
static bool isFoldableScalingScale(Value scale)
Only scales that already are f8E8M0FNU fold.
static bool checkIntFloatCast(TypeRange inputs, TypeRange outputs)
static LogicalResult verifyExtOp(Op op)
static IntegerAttr subIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs)
Definition ArithOps.cpp:61
static Attribute getIntegerAttrOfType(Type type, int64_t value)
Return a scalar or splat integer attribute of type (an integer/index type or a shaped type thereof) h...
Definition ArithOps.cpp:221
static IntegerAttr andIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs)
Definition ArithOps.cpp:71
static int64_t getScalarOrElementWidth(Type type)
Definition ArithOps.cpp:187
static Type getTypeIfLikeOrMemRef(Type type)
Get allowed underlying types for vectors, tensors, and memrefs.
static Type getI1SameShape(Type type)
Return the type of the same shape (scalar, vector or tensor) containing i1.
Definition ArithOps.cpp:244
static bool areValidCastInputsAndOutputs(TypeRange inputs, TypeRange outputs)
static IntegerOverflowFlagsAttr mergeOverflowFlagsForReassociation(IntegerOverflowFlagsAttr val1, IntegerOverflowFlagsAttr val2, Attribute lhs, Attribute rhs, OverflowCheckedBinaryOp signedOp, OverflowCheckedBinaryOp unsignedOp)
Definition ArithOps.cpp:100
static IntegerAttr xorIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs)
Definition ArithOps.cpp:81
static APInt calculateUnsignedBorrow(const APInt &lhs, const APInt &rhs)
Definition ArithOps.cpp:572
std::tuple< Types... > * type_list
static IntegerAttr applyToIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs, function_ref< APInt(const APInt &, const APInt &)> binFn)
Definition ArithOps.cpp:47
static APInt calculateUnsignedOverflow(const APInt &sum, const APInt &operand)
Definition ArithOps.cpp:508
static FailureOr< APInt > getIntOrSplatIntValue(Attribute attr)
Definition ArithOps.cpp:199
static unsigned getIndexCastWidth(Type t)
Return the bit-width of t for the purpose of index_cast width checks.
static LogicalResult verifyTruncateOp(Op op)
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static Type getElementType(Type type, ArrayRef< int32_t > indices, function_ref< InFlightDiagnostic(StringRef)> emitErrorFn)
Walks the given type hierarchy with the given indices, potentially down to component granularity,...
Definition SPIRVOps.cpp:229
#define mul(a, b)
#define add(a, b)
LogicalResult matchAndRewrite(CmpFOp op, PatternRewriter &rewriter) const override
static CmpIPredicate convertToIntegerPredicate(CmpFPredicate pred, bool isUnsigned)
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual ParseResult parseOptionalComma()=0
Parse a , token if present.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
Attributes are known-constant values of operations.
Definition Attributes.h:25
static BoolAttr get(MLIRContext *context, bool value)
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
Ty getType(Args &&...args)
Get or construct an instance of the type Ty with provided arguments.
Definition Builders.h:94
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
IntegerType getI1Type()
Definition Builders.cpp:61
IndexType getIndexType()
Definition Builders.cpp:59
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
Location getLoc() const
Accessors for the implied location.
Definition Builders.h:665
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:64
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
This class helps build Operations.
Definition Builders.h:210
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:466
This class represents a single result from folding an operation.
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
This provides public APIs that all operations should have.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
RewritePatternSet & insert(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
bool isSignlessInteger() const
Return true if this is a signless integer type (with the specified width).
Definition Types.cpp:66
bool isIndex() const
Definition Types.cpp:56
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class 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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Specialization of arith.constant op that returns a floating point value.
Definition Arith.h:72
static ConstantFloatOp create(OpBuilder &builder, Location location, FloatType type, const APFloat &value)
Definition ArithOps.cpp:405
static bool classof(Operation *op)
Definition ArithOps.cpp:422
static void build(OpBuilder &builder, OperationState &result, FloatType type, const APFloat &value)
Build a constant float op that produces a float of the specified type.
Definition ArithOps.cpp:399
Specialization of arith.constant op that returns an integer of index type.
Definition Arith.h:93
static void build(OpBuilder &builder, OperationState &result, int64_t value)
Build a constant int op that produces an index.
Definition ArithOps.cpp:428
static bool classof(Operation *op)
Definition ArithOps.cpp:449
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
Specialization of arith.constant op that returns an integer value.
Definition Arith.h:34
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:333
static void build(OpBuilder &builder, OperationState &result, int64_t value, unsigned width)
Build a constant int op that produces an integer of the specified width.
Definition ArithOps.cpp:326
static bool classof(Operation *op)
Definition ArithOps.cpp:393
Speculatability
This enum is returned from the getSpeculatability method in the ConditionallySpeculatable op interfac...
constexpr auto Speculatable
constexpr auto NotSpeculatable
std::optional< TypedAttr > getNeutralElement(Operation *op)
Return the identity numeric value associated to the give op.
bool applyCmpPredicate(arith::CmpIPredicate predicate, const APInt &lhs, const APInt &rhs)
Compute lhs pred rhs, where pred is one of the known integer comparison predicates.
TypedAttr getIdentityValueAttr(AtomicRMWKind kind, Type resultType, OpBuilder &builder, Location loc, bool useOnlyFiniteValue=false)
Returns the identity value attribute associated with an AtomicRMWKind op.
Value getReductionOp(AtomicRMWKind op, OpBuilder &builder, Location loc, Value lhs, Value rhs)
Returns the value obtained by applying the reduction operation kind associated with a binary AtomicRM...
Value getIdentityValue(AtomicRMWKind op, Type resultType, OpBuilder &builder, Location loc, bool useOnlyFiniteValue=false)
Returns the identity value associated with an AtomicRMWKind op.
arith::CmpIPredicate invertPredicate(arith::CmpIPredicate pred)
Invert an integer comparison predicate.
Definition ArithOps.cpp:131
Value getZeroConstant(OpBuilder &builder, Location loc, Type type)
Creates an arith.constant operation with a zero value of type type.
Definition ArithOps.cpp:455
auto m_Val(Value v)
Definition Matchers.h:539
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
detail::poison_attr_matcher m_Poison()
Matches a poison constant (any attribute implementing PoisonAttrInterface).
Definition UBMatchers.h:46
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
Attribute constFoldBinaryOpConditional(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
Performs constant folding calculate with element-wise behavior on the two attributes in operands and ...
detail::constant_float_predicate_matcher m_NaNFloat()
Matches a constant scalar / vector splat / tensor splat float ones.
Definition Matchers.h:421
LogicalResult verifyCompatibleShapes(TypeRange types1, TypeRange types2)
Returns success if the given two arrays have the same number of elements and each pair wise entries h...
Attribute constFoldCastOp(ArrayRef< Attribute > operands, Type resType, CalculationT &&calculate)
Attribute constFoldBinaryOp(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
detail::constant_int_range_predicate_matcher m_IntRangeWithoutNegOneS()
Matches a constant scalar / vector splat / tensor splat integer or a signed integer range that does n...
Definition Matchers.h:471
LogicalResult emitOptionalError(std::optional< Location > loc, Args &&...args)
Overloads of the above emission functions that take an optionally null location.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
detail::constant_float_predicate_matcher m_PosZeroFloat()
Matches a constant scalar / vector splat / tensor splat float positive zero.
Definition Matchers.h:404
detail::constant_int_predicate_matcher m_Zero()
Matches a constant scalar / vector splat / tensor splat integer zero.
Definition Matchers.h:442
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
Attribute constFoldUnaryOpConditional(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
Performs constant folding calculate with element-wise behavior on the one attributes in operands and ...
detail::constant_float_predicate_matcher m_AnyZeroFloat()
Matches a constant scalar / vector splat / tensor splat float (both positive and negative) zero.
Definition Matchers.h:399
detail::constant_int_predicate_matcher m_One()
Matches a constant scalar / vector splat / tensor splat integer one.
Definition Matchers.h:478
detail::constant_float_predicate_matcher m_NegInfFloat()
Matches a constant scalar / vector splat / tensor splat float negative infinity.
Definition Matchers.h:435
detail::constant_float_predicate_matcher m_NegZeroFloat()
Matches a constant scalar / vector splat / tensor splat float negative zero.
Definition Matchers.h:409
detail::constant_int_range_predicate_matcher m_IntRangeWithoutZeroS()
Matches a constant scalar / vector splat / tensor splat integer or a signed integer range that does n...
Definition Matchers.h:462
detail::op_matcher< OpClass > m_Op()
Matches the given OpClass.
Definition Matchers.h:484
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
Attribute constFoldUnaryOp(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
detail::constant_float_predicate_matcher m_PosInfFloat()
Matches a constant scalar / vector splat / tensor splat float positive infinity.
Definition Matchers.h:427
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
detail::constant_float_predicate_matcher m_OneFloat()
Matches a constant scalar / vector splat / tensor splat float ones.
Definition Matchers.h:414
detail::constant_int_range_predicate_matcher m_IntRangeWithoutZeroU()
Matches a constant scalar / vector splat / tensor splat integer or a unsigned integer range that does...
Definition Matchers.h:455
LogicalResult matchAndRewrite(arith::SelectOp op, PatternRewriter &rewriter) const override
OpRewritePattern Base
Type alias to allow derived classes to inherit constructors with using Base::Base;.
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
This represents an operation in an abstracted form, suitable for use with the builder APIs.