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
1090Speculation::Speculatability FloorDivSIOp::getSpeculatability() {
1091 // Division by zero is UB.
1092 // Signed division overflow (INT_MIN / -1) is also UB.
1093 Value divisor = getRhs();
1094 if (matchPattern(divisor, m_IntRangeWithoutZeroS()) &&
1097
1099}
1100
1101//===----------------------------------------------------------------------===//
1102// RemUIOp
1103//===----------------------------------------------------------------------===//
1104
1105OpFoldResult arith::RemUIOp::fold(FoldAdaptor adaptor) {
1106 // TODO: remui (x, 0) -> poison. Remainder by zero is undefined behaviour and
1107 // could fold to poison, but that would make the arith dialect depend on the
1108 // ub dialect to materialize ub.poison; left out for now.
1109
1110 // remui (x, 1) -> 0.
1111 if (matchPattern(adaptor.getRhs(), m_One()))
1112 return getIntegerAttrOfType(getType(), 0);
1113
1114 // remui (0, x) -> 0 and remui (x, x) -> 0. Division by zero is UB, so
1115 // refining to 0 is valid.
1116 if (matchPattern(adaptor.getLhs(), m_Zero()) || getLhs() == getRhs())
1117 return getIntegerAttrOfType(getType(), 0);
1118
1119 // Don't fold if it would require a division by zero.
1120 bool div0 = false;
1121 auto result = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1122 [&](APInt a, const APInt &b) {
1123 if (div0 || b.isZero()) {
1124 div0 = true;
1125 return a;
1126 }
1127 return a.urem(b);
1128 });
1129
1130 return div0 ? Attribute() : result;
1131}
1132
1133Speculation::Speculatability arith::RemUIOp::getSpeculatability() {
1134 return getDivUISpeculatability(getRhs());
1135}
1136
1137//===----------------------------------------------------------------------===//
1138// RemSIOp
1139//===----------------------------------------------------------------------===//
1140
1141OpFoldResult arith::RemSIOp::fold(FoldAdaptor adaptor) {
1142 // TODO: remsi (x, 0) -> poison. Remainder by zero is undefined behaviour and
1143 // could fold to poison, but that would make the arith dialect depend on the
1144 // ub dialect to materialize ub.poison; left out for now.
1145
1146 // remsi (x, 1) -> 0.
1147 if (matchPattern(adaptor.getRhs(), m_One()))
1148 return getIntegerAttrOfType(getType(), 0);
1149
1150 // remsi (0, x) -> 0 and remsi (x, x) -> 0. Division by zero is UB, so
1151 // refining to 0 is valid.
1152 if (matchPattern(adaptor.getLhs(), m_Zero()) || getLhs() == getRhs())
1153 return getIntegerAttrOfType(getType(), 0);
1154
1155 // Don't fold if it would require a division by zero.
1156 bool div0 = false;
1157 auto result = constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1158 [&](APInt a, const APInt &b) {
1159 if (div0 || b.isZero()) {
1160 div0 = true;
1161 return a;
1162 }
1163 return a.srem(b);
1164 });
1165
1166 return div0 ? Attribute() : result;
1167}
1168
1169Speculation::Speculatability arith::RemSIOp::getSpeculatability() {
1170 // X % 0 => UB
1171 // X % -1 is well-defined (always 0), unlike X / -1 which can overflow.
1172 if (matchPattern(getRhs(), m_IntRangeWithoutZeroS()))
1174
1176}
1177
1178//===----------------------------------------------------------------------===//
1179// AndIOp
1180//===----------------------------------------------------------------------===//
1181
1182/// Fold `op(a, op(a, b))` to `op(a, b)` for an associative, commutative and
1183/// idempotent `op` (e.g. `and`, `or`).
1184template <typename OpTy>
1186 for (bool reversePrev : {false, true}) {
1187 auto prev = (reversePrev ? op.getRhs() : op.getLhs())
1188 .template getDefiningOp<OpTy>();
1189 if (!prev)
1190 continue;
1191
1192 Value other = (reversePrev ? op.getLhs() : op.getRhs());
1193 if (other != prev.getLhs() && other != prev.getRhs())
1194 continue;
1195
1196 return prev.getResult();
1197 }
1198 return {};
1199}
1200
1201OpFoldResult arith::AndIOp::fold(FoldAdaptor adaptor) {
1202 /// and(x, 0) -> 0
1203 if (matchPattern(adaptor.getRhs(), m_Zero()))
1204 return getRhs();
1205 /// and(x, allOnes) -> x
1206 APInt intValue;
1207 if (matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue)) &&
1208 intValue.isAllOnes())
1209 return getLhs();
1210 /// and(x, not(x)) -> 0
1211 if (matchPattern(getRhs(), m_Op<XOrIOp>(matchers::m_Val(getLhs()),
1212 m_ConstantInt(&intValue))) &&
1213 intValue.isAllOnes())
1214 return Builder(getContext()).getZeroAttr(getType());
1215 /// and(not(x), x) -> 0
1216 if (matchPattern(getLhs(), m_Op<XOrIOp>(matchers::m_Val(getRhs()),
1217 m_ConstantInt(&intValue))) &&
1218 intValue.isAllOnes())
1219 return Builder(getContext()).getZeroAttr(getType());
1220
1221 /// and(a, and(a, b)) -> and(a, b)
1222 if (Value result = foldIdempotentOfSameOp(*this))
1223 return result;
1224
1226 adaptor.getOperands(),
1227 [](APInt a, const APInt &b) { return std::move(a) & b; });
1228}
1229
1230//===----------------------------------------------------------------------===//
1231// OrIOp
1232//===----------------------------------------------------------------------===//
1233
1234OpFoldResult arith::OrIOp::fold(FoldAdaptor adaptor) {
1235 if (APInt rhsVal; matchPattern(adaptor.getRhs(), m_ConstantInt(&rhsVal))) {
1236 /// or(x, 0) -> x
1237 if (rhsVal.isZero())
1238 return getLhs();
1239 /// or(x, <all ones>) -> <all ones>
1240 if (rhsVal.isAllOnes())
1241 return adaptor.getRhs();
1242 }
1243
1244 APInt intValue;
1245 /// or(x, xor(x, 1)) -> 1
1246 if (matchPattern(getRhs(), m_Op<XOrIOp>(matchers::m_Val(getLhs()),
1247 m_ConstantInt(&intValue))) &&
1248 intValue.isAllOnes())
1249 return getRhs().getDefiningOp<XOrIOp>().getRhs();
1250 /// or(xor(x, 1), x) -> 1
1251 if (matchPattern(getLhs(), m_Op<XOrIOp>(matchers::m_Val(getRhs()),
1252 m_ConstantInt(&intValue))) &&
1253 intValue.isAllOnes())
1254 return getLhs().getDefiningOp<XOrIOp>().getRhs();
1255
1256 /// or(a, or(a, b)) -> or(a, b)
1257 if (Value result = foldIdempotentOfSameOp(*this))
1258 return result;
1259
1261 adaptor.getOperands(),
1262 [](APInt a, const APInt &b) { return std::move(a) | b; });
1263}
1264
1265//===----------------------------------------------------------------------===//
1266// XOrIOp
1267//===----------------------------------------------------------------------===//
1268
1269OpFoldResult arith::XOrIOp::fold(FoldAdaptor adaptor) {
1270 /// xor(x, 0) -> x
1271 if (matchPattern(adaptor.getRhs(), m_Zero()))
1272 return getLhs();
1273 /// xor(x, x) -> 0
1274 if (getLhs() == getRhs()) {
1275 // A dynamically-shaped result cannot be a constant; bail before
1276 // getZeroAttr, which would assert on a non-static shape.
1277 auto shapedType = dyn_cast<ShapedType>(getType());
1278 if (!shapedType || shapedType.hasStaticShape())
1279 return Builder(getContext()).getZeroAttr(getType());
1280 }
1281 /// xor(xor(x, a), a) -> x
1282 /// xor(xor(a, x), a) -> x
1283 if (arith::XOrIOp prev = getLhs().getDefiningOp<arith::XOrIOp>()) {
1284 if (prev.getRhs() == getRhs())
1285 return prev.getLhs();
1286 if (prev.getLhs() == getRhs())
1287 return prev.getRhs();
1288 }
1289 /// xor(a, xor(x, a)) -> x
1290 /// xor(a, xor(a, x)) -> x
1291 if (arith::XOrIOp prev = getRhs().getDefiningOp<arith::XOrIOp>()) {
1292 if (prev.getRhs() == getLhs())
1293 return prev.getLhs();
1294 if (prev.getLhs() == getLhs())
1295 return prev.getRhs();
1296 }
1297
1299 adaptor.getOperands(),
1300 [](APInt a, const APInt &b) { return std::move(a) ^ b; });
1301}
1302
1303void arith::XOrIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1304 MLIRContext *context) {
1305 patterns.add<XOrIXOrIConstant, XOrINotCmpI, XOrIOfExtUI, XOrIOfExtSI>(
1306 context);
1307}
1308
1309//===----------------------------------------------------------------------===//
1310// NegFOp
1311//===----------------------------------------------------------------------===//
1312
1313OpFoldResult arith::NegFOp::fold(FoldAdaptor adaptor) {
1314 /// negf(negf(x)) -> x
1315 if (auto op = this->getOperand().getDefiningOp<arith::NegFOp>())
1316 return op.getOperand();
1318 adaptor.getOperands(), [](const APFloat &a) -> std::optional<APFloat> {
1319 if (!APFloat::semanticsHasSignedRepr(a.getSemantics()))
1320 return std::nullopt;
1321 return -a;
1322 });
1323}
1324
1325//===----------------------------------------------------------------------===//
1326// FlushDenormalsOp
1327//===----------------------------------------------------------------------===//
1328
1329OpFoldResult arith::FlushDenormalsOp::fold(FoldAdaptor adaptor) {
1330 // TODO: Fold flush_denormals if the floating-point type does not support
1331 // denormals. There is currently no API to query this information from
1332 // APFloat.
1333
1334 // flush_denormals(flush_denormals(x)) -> flush_denormals(x)
1335 if (auto op = this->getOperand().getDefiningOp<arith::FlushDenormalsOp>())
1336 return op.getResult();
1337
1338 // Constant-fold flush_denormals if the operand is a constant.
1340 adaptor.getOperands(), [](const APFloat &a) {
1341 if (a.isDenormal())
1342 return APFloat::getZero(a.getSemantics(), a.isNegative());
1343 return a;
1344 });
1345}
1346
1347//===----------------------------------------------------------------------===//
1348// AddFOp
1349//===----------------------------------------------------------------------===//
1350
1351OpFoldResult arith::AddFOp::fold(FoldAdaptor adaptor) {
1352 // addf(x, -0) -> x
1353 if (matchPattern(adaptor.getRhs(), m_NegZeroFloat()))
1354 return getLhs();
1355 if (matchPattern(adaptor.getRhs(), m_PosZeroFloat()) &&
1356 bitEnumContainsAll(adaptor.getFastmath(), FastMathFlags::nsz))
1357 return getLhs();
1358
1359 auto rm = getRoundingmode();
1361 adaptor.getOperands(), [rm](const APFloat &a, const APFloat &b) {
1362 APFloat result(a);
1363 result.add(b, convertArithRoundingModeToLLVMIR(rm));
1364 return result;
1365 });
1366}
1367
1368void arith::AddFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1369 MLIRContext *context) {
1370 patterns.add<AddFOfNegFLhs, AddFOfNegFRhs>(context);
1371}
1372
1373//===----------------------------------------------------------------------===//
1374// SubFOp
1375//===----------------------------------------------------------------------===//
1376
1377OpFoldResult arith::SubFOp::fold(FoldAdaptor adaptor) {
1378 // subf(x, +0) -> x
1379 if (matchPattern(adaptor.getRhs(), m_PosZeroFloat()))
1380 return getLhs();
1381 if (matchPattern(adaptor.getRhs(), m_NegZeroFloat()) &&
1382 bitEnumContainsAll(adaptor.getFastmath(), FastMathFlags::nsz))
1383 return getLhs();
1384
1385 auto rm = getRoundingmode();
1387 adaptor.getOperands(),
1388 [rm](const APFloat &a, const APFloat &b) -> std::optional<APFloat> {
1389 // A negative difference is not representable in types such as
1390 // f8E8M0FNU, and APFloat::subtract does not support computing it.
1391 if (!APFloat::semanticsHasSignedRepr(a.getSemantics()) &&
1392 a.compare(b) == APFloat::cmpLessThan)
1393 return std::nullopt;
1394 APFloat result(a);
1395 result.subtract(b, convertArithRoundingModeToLLVMIR(rm));
1396 return result;
1397 });
1398}
1399
1400void arith::SubFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1401 MLIRContext *context) {
1402 patterns.add<SubFOfNegZero>(context);
1403}
1404
1405namespace {
1406
1407/// Narrow an extremum whose operands were extended from the result type:
1408///
1409/// trunc(extremum(ext(lhs), ext(rhs))) -> extremum(lhs, rhs)
1410///
1411/// The concrete extension is part of the pattern so each extremum is only
1412/// registered with extensions that preserve its ordering.
1413/// For floating-point types, also require the extension to preserve every
1414/// source value relevant under the extremum's fast-math flags.
1415template <typename TruncOp, typename ExtOp, typename ExtremumOp>
1416struct NarrowExtremum final : OpRewritePattern<TruncOp> {
1417 using OpRewritePattern<TruncOp>::OpRewritePattern;
1418
1419 LogicalResult matchAndRewrite(TruncOp truncOp,
1420 PatternRewriter &rewriter) const override {
1421 auto extremumOp = truncOp.getIn().template getDefiningOp<ExtremumOp>();
1422 if (!extremumOp || !extremumOp->hasOneUse())
1423 return failure();
1424
1425 auto lhsExt = extremumOp.getLhs().template getDefiningOp<ExtOp>();
1426 auto rhsExt = extremumOp.getRhs().template getDefiningOp<ExtOp>();
1427 if (!lhsExt || !rhsExt)
1428 return failure();
1429
1430 Value lhs = lhsExt.getIn();
1431 Value rhs = rhsExt.getIn();
1432 Type narrowType = truncOp.getType();
1433 if (lhs.getType() != narrowType || rhs.getType() != narrowType)
1434 return failure();
1435
1436 // A floating-point extension is not necessarily lossless between arbitrary
1437 // floating-point semantics, even when the destination has a larger bit
1438 // width. In particular, it may lose the sign of zero or quiet a signaling
1439 // NaN, either of which can change an extremum's result. `nnan` lets us
1440 // disregard NaN representation differences, but all other relevant source
1441 // values must be preserved.
1442 if (auto narrowFloatType =
1443 dyn_cast<FloatType>(getElementTypeOrSelf(narrowType))) {
1444 auto wideFloatType =
1445 dyn_cast<FloatType>(getElementTypeOrSelf(extremumOp.getType()));
1446 if (!wideFloatType)
1447 return failure();
1448
1449 const llvm::fltSemantics &narrowSemantics =
1450 narrowFloatType.getFloatSemantics();
1451 const llvm::fltSemantics &wideSemantics =
1452 wideFloatType.getFloatSemantics();
1453 bool ignoreNaNs = false;
1454 if constexpr (std::is_same_v<TruncOp, TruncFOp>)
1455 ignoreNaNs =
1456 bitEnumContainsAll(extremumOp.getFastmath(), FastMathFlags::nnan);
1457 if (!llvm::APFloatBase::isLosslesslyConvertibleTo(
1458 narrowSemantics, wideSemantics, ignoreNaNs))
1459 return failure();
1460 }
1461
1462 rewriter.replaceOpWithNewOp<ExtremumOp>(
1463 truncOp, TypeRange{narrowType}, ValueRange{lhs, rhs},
1464 extremumOp.getProperties(),
1465 extremumOp->getDiscardableAttrDictionary().getValue());
1466 return success();
1467 }
1468};
1469
1470} // namespace
1471
1472//===----------------------------------------------------------------------===//
1473// MaximumFOp
1474//===----------------------------------------------------------------------===//
1475
1476OpFoldResult arith::MaximumFOp::fold(FoldAdaptor adaptor) {
1477 // maximumf(x,x) -> x
1478 if (getLhs() == getRhs())
1479 return getRhs();
1480
1481 // maximumf(x, -inf) -> x
1482 if (matchPattern(adaptor.getRhs(), m_NegInfFloat()))
1483 return getLhs();
1484
1485 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::maximum);
1486}
1487
1488//===----------------------------------------------------------------------===//
1489// MaxNumFOp
1490//===----------------------------------------------------------------------===//
1491
1492OpFoldResult arith::MaxNumFOp::fold(FoldAdaptor adaptor) {
1493 // maxnumf(x,x) -> x
1494 if (getLhs() == getRhs())
1495 return getRhs();
1496
1497 // maxnumf(x, NaN) -> x
1498 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1499 return getLhs();
1500
1501 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::maxnum);
1502}
1503
1504//===----------------------------------------------------------------------===//
1505// MaximumNumFOp
1506//===----------------------------------------------------------------------===//
1507
1508OpFoldResult arith::MaximumNumFOp::fold(FoldAdaptor adaptor) {
1509 // maximumnumf(x,x) -> x
1510 if (getLhs() == getRhs())
1511 return getRhs();
1512
1513 // maximumnumf(x, NaN) -> x
1514 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1515 return getLhs();
1516
1517 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::maximumnum);
1518}
1519
1520//===----------------------------------------------------------------------===//
1521// MaxSIOp
1522//===----------------------------------------------------------------------===//
1523
1524OpFoldResult MaxSIOp::fold(FoldAdaptor adaptor) {
1525 // maxsi(x,x) -> x
1526 if (getLhs() == getRhs())
1527 return getRhs();
1528
1529 if (APInt intValue;
1530 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1531 // maxsi(x,MAX_INT) -> MAX_INT
1532 if (intValue.isMaxSignedValue())
1533 return getRhs();
1534 // maxsi(x, MIN_INT) -> x
1535 if (intValue.isMinSignedValue())
1536 return getLhs();
1537 }
1538
1539 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1540 llvm::APIntOps::smax);
1541}
1542
1543//===----------------------------------------------------------------------===//
1544// MaxUIOp
1545//===----------------------------------------------------------------------===//
1546
1547OpFoldResult MaxUIOp::fold(FoldAdaptor adaptor) {
1548 // maxui(x,x) -> x
1549 if (getLhs() == getRhs())
1550 return getRhs();
1551
1552 if (APInt intValue;
1553 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1554 // maxui(x,MAX_INT) -> MAX_INT
1555 if (intValue.isMaxValue())
1556 return getRhs();
1557 // maxui(x, MIN_INT) -> x
1558 if (intValue.isMinValue())
1559 return getLhs();
1560 }
1561
1562 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1563 llvm::APIntOps::umax);
1564}
1565
1566//===----------------------------------------------------------------------===//
1567// MinimumFOp
1568//===----------------------------------------------------------------------===//
1569
1570OpFoldResult arith::MinimumFOp::fold(FoldAdaptor adaptor) {
1571 // minimumf(x,x) -> x
1572 if (getLhs() == getRhs())
1573 return getRhs();
1574
1575 // minimumf(x, +inf) -> x
1576 if (matchPattern(adaptor.getRhs(), m_PosInfFloat()))
1577 return getLhs();
1578
1579 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::minimum);
1580}
1581
1582//===----------------------------------------------------------------------===//
1583// MinNumFOp
1584//===----------------------------------------------------------------------===//
1585
1586OpFoldResult arith::MinNumFOp::fold(FoldAdaptor adaptor) {
1587 // minnumf(x,x) -> x
1588 if (getLhs() == getRhs())
1589 return getRhs();
1590
1591 // minnumf(x, NaN) -> x
1592 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1593 return getLhs();
1594
1595 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::minnum);
1596}
1597
1598//===----------------------------------------------------------------------===//
1599// MinimumNumFOp
1600//===----------------------------------------------------------------------===//
1601
1602OpFoldResult arith::MinimumNumFOp::fold(FoldAdaptor adaptor) {
1603 // minimumnumf(x,x) -> x
1604 if (getLhs() == getRhs())
1605 return getRhs();
1606
1607 // minimumnumf(x, NaN) -> x
1608 if (matchPattern(adaptor.getRhs(), m_NaNFloat()))
1609 return getLhs();
1610
1611 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(), llvm::minimumnum);
1612}
1613
1614//===----------------------------------------------------------------------===//
1615// MinSIOp
1616//===----------------------------------------------------------------------===//
1617
1618OpFoldResult MinSIOp::fold(FoldAdaptor adaptor) {
1619 // minsi(x,x) -> x
1620 if (getLhs() == getRhs())
1621 return getRhs();
1622
1623 if (APInt intValue;
1624 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1625 // minsi(x,MIN_INT) -> MIN_INT
1626 if (intValue.isMinSignedValue())
1627 return getRhs();
1628 // minsi(x, MAX_INT) -> x
1629 if (intValue.isMaxSignedValue())
1630 return getLhs();
1631 }
1632
1633 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1634 llvm::APIntOps::smin);
1635}
1636
1637//===----------------------------------------------------------------------===//
1638// MinUIOp
1639//===----------------------------------------------------------------------===//
1640
1641OpFoldResult MinUIOp::fold(FoldAdaptor adaptor) {
1642 // minui(x,x) -> x
1643 if (getLhs() == getRhs())
1644 return getRhs();
1645
1646 if (APInt intValue;
1647 matchPattern(adaptor.getRhs(), m_ConstantInt(&intValue))) {
1648 // minui(x,MIN_INT) -> MIN_INT
1649 if (intValue.isMinValue())
1650 return getRhs();
1651 // minui(x, MAX_INT) -> x
1652 if (intValue.isMaxValue())
1653 return getLhs();
1654 }
1655
1656 return constFoldBinaryOp<IntegerAttr>(adaptor.getOperands(),
1657 llvm::APIntOps::umin);
1658}
1659
1660//===----------------------------------------------------------------------===//
1661// MulFOp
1662//===----------------------------------------------------------------------===//
1663
1664OpFoldResult arith::MulFOp::fold(FoldAdaptor adaptor) {
1665 // mulf(x, 1) -> x
1666 if (matchPattern(adaptor.getRhs(), m_OneFloat()))
1667 return getLhs();
1668
1669 // Match LLVM InstSimplify: with nnan+nsz, X * 0 -> 0 for a non-constant X.
1670 // When both operands are constants, fall through to APFloat so IEEE signed
1671 // zeros are preserved (e.g. (-c) * +0.0 == -0.0).
1672 if (arith::bitEnumContainsAll(getFastmath(), arith::FastMathFlags::nnan |
1673 arith::FastMathFlags::nsz) &&
1674 !(adaptor.getLhs() && adaptor.getRhs())) {
1675 // mulf(x, 0) -> 0
1676 if (matchPattern(adaptor.getRhs(), m_AnyZeroFloat()))
1677 return getRhs();
1678 if (matchPattern(adaptor.getLhs(), m_AnyZeroFloat()))
1679 return getLhs();
1680 }
1681
1682 auto rm = getRoundingmode();
1684 adaptor.getOperands(), [rm](const APFloat &a, const APFloat &b) {
1685 APFloat result(a);
1686 result.multiply(b, convertArithRoundingModeToLLVMIR(rm));
1687 return result;
1688 });
1689}
1690
1691void arith::MulFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1692 MLIRContext *context) {
1693 patterns.add<MulFOfNegF>(context);
1694}
1695
1696//===----------------------------------------------------------------------===//
1697// DivFOp
1698//===----------------------------------------------------------------------===//
1699
1700OpFoldResult arith::DivFOp::fold(FoldAdaptor adaptor) {
1701 // divf(x, 1) -> x
1702 if (matchPattern(adaptor.getRhs(), m_OneFloat()))
1703 return getLhs();
1704
1705 auto rm = getRoundingmode();
1707 adaptor.getOperands(), [rm](const APFloat &a, const APFloat &b) {
1708 APFloat result(a);
1709 result.divide(b, convertArithRoundingModeToLLVMIR(rm));
1710 return result;
1711 });
1712}
1713
1714void arith::DivFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1715 MLIRContext *context) {
1716 patterns.add<DivFOfNegF>(context);
1717}
1718
1719//===----------------------------------------------------------------------===//
1720// RemFOp
1721//===----------------------------------------------------------------------===//
1722
1723OpFoldResult arith::RemFOp::fold(FoldAdaptor adaptor) {
1724 return constFoldBinaryOp<FloatAttr>(adaptor.getOperands(),
1725 [](const APFloat &a, const APFloat &b) {
1726 APFloat result(a);
1727 // APFloat::mod() offers the remainder
1728 // behavior we want, i.e. the result has
1729 // the sign of LHS operand.
1730 (void)result.mod(b);
1731 return result;
1732 });
1733}
1734
1735//===----------------------------------------------------------------------===//
1736// Utility functions for verifying cast ops
1737//===----------------------------------------------------------------------===//
1738
1739template <typename... Types>
1740using type_list = std::tuple<Types...> *;
1741
1742/// Returns a non-null type only if the provided type is one of the allowed
1743/// types or one of the allowed shaped types of the allowed types. Returns the
1744/// element type if a valid shaped type is provided.
1745template <typename... ShapedTypes, typename... ElementTypes>
1748 if (llvm::isa<ShapedType>(type) && !llvm::isa<ShapedTypes...>(type))
1749 return {};
1750
1751 auto underlyingType = getElementTypeOrSelf(type);
1752 if (!llvm::isa<ElementTypes...>(underlyingType))
1753 return {};
1754
1755 return underlyingType;
1756}
1757
1758/// Get allowed underlying types for vectors and tensors.
1759template <typename... ElementTypes>
1764
1765/// Get allowed underlying types for vectors, tensors, and memrefs.
1766template <typename... ElementTypes>
1772
1773/// Return false if both types are ranked tensor with mismatching encoding.
1774static bool hasSameEncoding(Type typeA, Type typeB) {
1775 auto rankedTensorA = dyn_cast<RankedTensorType>(typeA);
1776 auto rankedTensorB = dyn_cast<RankedTensorType>(typeB);
1777 if (!rankedTensorA || !rankedTensorB)
1778 return true;
1779 return rankedTensorA.getEncoding() == rankedTensorB.getEncoding();
1780}
1781
1783 if (inputs.size() != 1 || outputs.size() != 1)
1784 return false;
1785 if (!hasSameEncoding(inputs.front(), outputs.front()))
1786 return false;
1787 return succeeded(verifyCompatibleShapes(inputs.front(), outputs.front()));
1788}
1789
1790//===----------------------------------------------------------------------===//
1791// Verifiers for integer and floating point extension/truncation ops
1792//===----------------------------------------------------------------------===//
1793
1794// Extend ops can only extend to a wider type.
1795template <typename ValType, typename Op>
1796static LogicalResult verifyExtOp(Op op) {
1797 Type srcType = getElementTypeOrSelf(op.getIn().getType());
1798 Type dstType = getElementTypeOrSelf(op.getType());
1799
1800 if (llvm::cast<ValType>(srcType).getWidth() >=
1801 llvm::cast<ValType>(dstType).getWidth())
1802 return op.emitError("result type ")
1803 << dstType << " must be wider than operand type " << srcType;
1804
1805 return success();
1806}
1807
1808// Truncate ops can only truncate to a shorter type.
1809template <typename ValType, typename Op>
1810static LogicalResult verifyTruncateOp(Op op) {
1811 Type srcType = getElementTypeOrSelf(op.getIn().getType());
1812 Type dstType = getElementTypeOrSelf(op.getType());
1813
1814 if (llvm::cast<ValType>(srcType).getWidth() <=
1815 llvm::cast<ValType>(dstType).getWidth())
1816 return op.emitError("result type ")
1817 << dstType << " must be shorter than operand type " << srcType;
1818
1819 return success();
1820}
1821
1822/// Validate a cast that changes the width of a type.
1823template <template <typename> class WidthComparator, typename... ElementTypes>
1824static bool checkWidthChangeCast(TypeRange inputs, TypeRange outputs) {
1825 if (!areValidCastInputsAndOutputs(inputs, outputs))
1826 return false;
1827
1828 auto srcType = getTypeIfLike<ElementTypes...>(inputs.front());
1829 auto dstType = getTypeIfLike<ElementTypes...>(outputs.front());
1830 if (!srcType || !dstType)
1831 return false;
1832
1833 return WidthComparator<unsigned>()(dstType.getIntOrFloatBitWidth(),
1834 srcType.getIntOrFloatBitWidth());
1835}
1836
1837/// Attempts to convert `sourceValue` to an APFloat value with
1838/// `targetSemantics` and `roundingMode`, without any information loss.
1839static FailureOr<APFloat>
1840convertFloatValue(APFloat sourceValue,
1841 const llvm::fltSemantics &targetSemantics,
1842 llvm::RoundingMode roundingMode = kDefaultRoundingMode) {
1843 // Reject special values that are not representable in the target type before
1844 // calling APFloat::convert, which would llvm_unreachable on them.
1845 using fltNonfiniteBehavior = llvm::fltNonfiniteBehavior;
1846 if (sourceValue.isInfinity() &&
1847 (targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::NanOnly ||
1848 targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::FiniteOnly))
1849 return failure();
1850 if (sourceValue.isNaN() &&
1851 targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::FiniteOnly)
1852 return failure();
1853
1854 bool losesInfo = false;
1855 auto status = sourceValue.convert(targetSemantics, roundingMode, &losesInfo);
1856 if (losesInfo || status != APFloat::opOK)
1857 return failure();
1858
1859 return sourceValue;
1860}
1861
1862//===----------------------------------------------------------------------===//
1863// ExtUIOp
1864//===----------------------------------------------------------------------===//
1865
1866OpFoldResult arith::ExtUIOp::fold(FoldAdaptor adaptor) {
1867 if (auto lhs = getIn().getDefiningOp<ExtUIOp>()) {
1868 // Only the inner extension's nneg speaks about the surviving source; the
1869 // outer flag described the already-extended value.
1870 setNonNeg(lhs.getNonNeg());
1871 getInMutable().assign(lhs.getIn());
1872 return getResult();
1873 }
1874
1875 Type resType = getElementTypeOrSelf(getType());
1876 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
1878 adaptor.getOperands(), getType(),
1879 [bitWidth](const APInt &a, bool &castStatus) {
1880 return a.zext(bitWidth);
1881 });
1882}
1883
1884bool arith::ExtUIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1886}
1887
1888LogicalResult arith::ExtUIOp::verify() {
1889 return verifyExtOp<IntegerType>(*this);
1890}
1891
1892//===----------------------------------------------------------------------===//
1893// ExtSIOp
1894//===----------------------------------------------------------------------===//
1895
1896OpFoldResult arith::ExtSIOp::fold(FoldAdaptor adaptor) {
1897 if (auto lhs = getIn().getDefiningOp<ExtSIOp>()) {
1898 getInMutable().assign(lhs.getIn());
1899 return getResult();
1900 }
1901
1902 Type resType = getElementTypeOrSelf(getType());
1903 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
1905 adaptor.getOperands(), getType(),
1906 [bitWidth](const APInt &a, bool &castStatus) {
1907 return a.sext(bitWidth);
1908 });
1909}
1910
1911bool arith::ExtSIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1913}
1914
1915void arith::ExtSIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1916 MLIRContext *context) {
1917 patterns.add<ExtSIOfExtUI>(context);
1918}
1919
1920LogicalResult arith::ExtSIOp::verify() {
1921 return verifyExtOp<IntegerType>(*this);
1922}
1923
1924//===----------------------------------------------------------------------===//
1925// ExtFOp
1926//===----------------------------------------------------------------------===//
1927
1928/// Fold extension of float constants when there is no information loss due the
1929/// difference in fp semantics.
1930OpFoldResult arith::ExtFOp::fold(FoldAdaptor adaptor) {
1931 if (auto truncFOp = getOperand().getDefiningOp<TruncFOp>()) {
1932 if (truncFOp.getOperand().getType() == getType()) {
1933 arith::FastMathFlags truncFMF =
1934 truncFOp.getFastmath().value_or(arith::FastMathFlags::none);
1935 bool isTruncContract =
1936 bitEnumContainsAll(truncFMF, arith::FastMathFlags::contract);
1937 arith::FastMathFlags extFMF =
1938 getFastmath().value_or(arith::FastMathFlags::none);
1939 bool isExtContract =
1940 bitEnumContainsAll(extFMF, arith::FastMathFlags::contract);
1941 if (isTruncContract && isExtContract) {
1942 return truncFOp.getOperand();
1943 }
1944 }
1945 }
1946
1947 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
1948 const llvm::fltSemantics &targetSemantics = resElemType.getFloatSemantics();
1950 adaptor.getOperands(), getType(),
1951 [&targetSemantics](const APFloat &a, bool &castStatus) {
1952 FailureOr<APFloat> result = convertFloatValue(a, targetSemantics);
1953 if (failed(result)) {
1954 castStatus = false;
1955 return a;
1956 }
1957 return *result;
1958 });
1959}
1960
1961bool arith::ExtFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1962 return checkWidthChangeCast<std::greater, FloatType>(inputs, outputs);
1963}
1964
1965LogicalResult arith::ExtFOp::verify() { return verifyExtOp<FloatType>(*this); }
1966
1967//===----------------------------------------------------------------------===//
1968// ScalingExtFOp
1969//===----------------------------------------------------------------------===//
1970
1971/// Fold `calculate` element-wise over the operands of a scaling cast op. The
1972/// `constFoldBinaryOp` helpers cannot be used: they bail out unless both
1973/// operands have the same type, and `in` and `scale` never do.
1975 Attribute inAttr, Attribute scaleAttr, Type resultType,
1976 function_ref<std::optional<APFloat>(const APFloat &, const APFloat &)>
1977 calculate) {
1978 // Poison propagates, as it does in the generic constant folders.
1979 if (isa_and_nonnull<ub::PoisonAttr>(inAttr))
1980 return inAttr;
1981 if (isa_and_nonnull<ub::PoisonAttr>(scaleAttr))
1982 return scaleAttr;
1983
1984 if (!inAttr || !scaleAttr || !resultType)
1985 return {};
1986
1987 if (auto inFloat = dyn_cast<FloatAttr>(inAttr)) {
1988 auto scaleFloat = dyn_cast<FloatAttr>(scaleAttr);
1989 if (!scaleFloat)
1990 return {};
1991 std::optional<APFloat> result =
1992 calculate(inFloat.getValue(), scaleFloat.getValue());
1993 if (!result)
1994 return {};
1995 return FloatAttr::get(resultType, *result);
1996 }
1997
1998 auto inElements = dyn_cast<DenseFPElementsAttr>(inAttr);
1999 auto scaleElements = dyn_cast<DenseFPElementsAttr>(scaleAttr);
2000 auto shapedResultType = dyn_cast<ShapedType>(resultType);
2001 if (!inElements || !scaleElements || !shapedResultType ||
2002 !shapedResultType.hasStaticShape() ||
2003 inElements.getNumElements() != scaleElements.getNumElements())
2004 return {};
2005
2006 // Both operands are splats, so avoid expanding the elements out.
2007 if (inElements.isSplat() && scaleElements.isSplat()) {
2008 std::optional<APFloat> result =
2009 calculate(inElements.getSplatValue<APFloat>(),
2010 scaleElements.getSplatValue<APFloat>());
2011 if (!result)
2012 return {};
2013 return DenseElementsAttr::get(shapedResultType, *result);
2014 }
2015
2016 SmallVector<APFloat> results;
2017 results.reserve(inElements.getNumElements());
2018 for (const auto &[in, scale] : llvm::zip_equal(inElements, scaleElements)) {
2019 std::optional<APFloat> result = calculate(in, scale);
2020 if (!result)
2021 return {};
2022 results.push_back(*result);
2023 }
2024 return DenseElementsAttr::get(shapedResultType, results);
2025}
2026
2027/// Only scales that already are f8E8M0FNU fold. What a wider scale means is
2028/// unsettled -- the tree does not say whether truncating one to f8E8M0FNU
2029/// rounds or takes its exponent -- so a folder should not settle it, see
2030/// https://github.com/llvm/llvm-project/issues/215295.
2031static bool isFoldableScalingScale(Value scale) {
2032 return isa<Float8E8M0FNUType>(getElementTypeOrSelf(scale.getType()));
2033}
2034
2035OpFoldResult arith::ScalingExtFOp::fold(FoldAdaptor adaptor) {
2036 // scaling_extf(in, scale) -> mulf(extf(in), extf(scale)), matching the
2037 // expansion in ExpandOps.cpp. As in arith.extf, the widening steps only fold
2038 // when they are lossless.
2039 if (!isFoldableScalingScale(getScale()))
2040 return {};
2041
2042 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2043 const llvm::fltSemantics &resSemantics = resElemType.getFloatSemantics();
2044 return foldScalingCastOp(
2045 adaptor.getIn(), adaptor.getScale(), getType(),
2046 [&resSemantics](const APFloat &in,
2047 const APFloat &scale) -> std::optional<APFloat> {
2048 FailureOr<APFloat> inExt = convertFloatValue(in, resSemantics);
2049 FailureOr<APFloat> scaleExt = convertFloatValue(scale, resSemantics);
2050 if (failed(inExt) || failed(scaleExt))
2051 return std::nullopt;
2052 APFloat result(*inExt);
2053 result.multiply(*scaleExt, kDefaultRoundingMode);
2054 return result;
2055 });
2056}
2057
2058bool arith::ScalingExtFOp::areCastCompatible(TypeRange inputs,
2059 TypeRange outputs) {
2060 return checkWidthChangeCast<std::greater, FloatType>(inputs.front(), outputs);
2061}
2062
2063LogicalResult arith::ScalingExtFOp::verify() {
2064 return verifyExtOp<FloatType>(*this);
2065}
2066
2067//===----------------------------------------------------------------------===//
2068// TruncIOp
2069//===----------------------------------------------------------------------===//
2070
2071OpFoldResult arith::TruncIOp::fold(FoldAdaptor adaptor) {
2072 if (matchPattern(getOperand(), m_Op<arith::ExtUIOp>()) ||
2073 matchPattern(getOperand(), m_Op<arith::ExtSIOp>())) {
2074 Value src = getOperand().getDefiningOp()->getOperand(0);
2075 Type srcType = getElementTypeOrSelf(src.getType());
2076 Type dstType = getElementTypeOrSelf(getType());
2077 // trunci(zexti(a)) -> trunci(a)
2078 // trunci(sexti(a)) -> trunci(a)
2079 if (llvm::cast<IntegerType>(srcType).getWidth() >
2080 llvm::cast<IntegerType>(dstType).getWidth()) {
2081 setOperand(src);
2082 return getResult();
2083 }
2084
2085 // trunci(zexti(a)) -> a
2086 // trunci(sexti(a)) -> a
2087 if (srcType == dstType)
2088 return src;
2089 }
2090
2091 // trunci(trunci(a)) -> trunci(a)
2092 if (auto truncOp = getOperand().getDefiningOp<arith::TruncIOp>()) {
2093 // Preserve a flag only if both truncations are lossless in that signedness.
2094 setOverflowFlags(getOverflowFlags() & truncOp.getOverflowFlags());
2095 setOperand(truncOp.getIn());
2096 return getResult();
2097 }
2098
2099 Type resType = getElementTypeOrSelf(getType());
2100 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
2102 adaptor.getOperands(), getType(),
2103 [bitWidth](const APInt &a, bool &castStatus) {
2104 return a.trunc(bitWidth);
2105 });
2106}
2107
2108bool arith::TruncIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2109 return checkWidthChangeCast<std::less, IntegerType>(inputs, outputs);
2110}
2111
2112void arith::TruncIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2113 MLIRContext *context) {
2114 patterns.add<NarrowExtremum<TruncIOp, ExtSIOp, MaxSIOp>,
2115 NarrowExtremum<TruncIOp, ExtSIOp, MinSIOp>,
2116 NarrowExtremum<TruncIOp, ExtUIOp, MaxUIOp>,
2117 NarrowExtremum<TruncIOp, ExtUIOp, MinUIOp>, TruncIExtSIToExtSI,
2118 TruncIExtUIToExtUI, TruncIShrSIToTrunciShrUI>(context);
2119}
2120
2121LogicalResult arith::TruncIOp::verify() {
2122 return verifyTruncateOp<IntegerType>(*this);
2123}
2124
2125//===----------------------------------------------------------------------===//
2126// TruncFOp
2127//===----------------------------------------------------------------------===//
2128
2129/// Perform safe const propagation for truncf, i.e., only propagate if FP value
2130/// can be represented without precision loss.
2131OpFoldResult arith::TruncFOp::fold(FoldAdaptor adaptor) {
2132 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2133 if (auto extOp = getOperand().getDefiningOp<arith::ExtFOp>()) {
2134 Value src = extOp.getIn();
2135 auto srcType = cast<FloatType>(getElementTypeOrSelf(src.getType()));
2136 auto intermediateType =
2137 cast<FloatType>(getElementTypeOrSelf(extOp.getType()));
2138 // Check whether every source value round-trips through the intermediate
2139 // type, including signaling NaNs and signed zero.
2140 if (llvm::APFloatBase::isLosslesslyConvertibleTo(
2141 srcType.getFloatSemantics(),
2142 intermediateType.getFloatSemantics())) {
2143 // truncf(extf(a)) -> truncf(a)
2144 if (srcType.getWidth() > resElemType.getWidth()) {
2145 setOperand(src);
2146 return getResult();
2147 }
2148
2149 // truncf(extf(a)) -> a
2150 if (srcType == resElemType)
2151 return src;
2152 }
2153 }
2154
2155 const llvm::fltSemantics &targetSemantics = resElemType.getFloatSemantics();
2157 adaptor.getOperands(), getType(),
2158 [this, &targetSemantics](const APFloat &a, bool &castStatus) {
2159 llvm::RoundingMode llvmRoundingMode =
2160 convertArithRoundingModeToLLVMIR(getRoundingmode());
2161 FailureOr<APFloat> result =
2162 convertFloatValue(a, targetSemantics, llvmRoundingMode);
2163 if (failed(result)) {
2164 castStatus = false;
2165 return a;
2166 }
2167 return *result;
2168 });
2169}
2170
2171void arith::TruncFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2172 MLIRContext *context) {
2173 patterns.add<NarrowExtremum<TruncFOp, ExtFOp, MaximumFOp>,
2174 NarrowExtremum<TruncFOp, ExtFOp, MaxNumFOp>,
2175 NarrowExtremum<TruncFOp, ExtFOp, MaximumNumFOp>,
2176 NarrowExtremum<TruncFOp, ExtFOp, MinimumFOp>,
2177 NarrowExtremum<TruncFOp, ExtFOp, MinNumFOp>,
2178 NarrowExtremum<TruncFOp, ExtFOp, MinimumNumFOp>,
2179 TruncFSIToFPToSIToFP, TruncFUIToFPToUIToFP>(context);
2180}
2181
2182bool arith::TruncFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2183 return checkWidthChangeCast<std::less, FloatType>(inputs, outputs);
2184}
2185
2186LogicalResult arith::TruncFOp::verify() {
2187 return verifyTruncateOp<FloatType>(*this);
2188}
2189
2190//===----------------------------------------------------------------------===//
2191// ConvertFOp
2192//===----------------------------------------------------------------------===//
2193
2194OpFoldResult arith::ConvertFOp::fold(FoldAdaptor adaptor) {
2195 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2196 const llvm::fltSemantics &targetSemantics = resElemType.getFloatSemantics();
2198 adaptor.getOperands(), getType(),
2199 [this, &targetSemantics](const APFloat &a, bool &castStatus) {
2200 llvm::RoundingMode llvmRoundingMode =
2201 convertArithRoundingModeToLLVMIR(getRoundingmode());
2202 FailureOr<APFloat> result =
2203 convertFloatValue(a, targetSemantics, llvmRoundingMode);
2204 if (failed(result)) {
2205 castStatus = false;
2206 return a;
2207 }
2208 return *result;
2209 });
2210}
2211
2212bool arith::ConvertFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2213 if (!areValidCastInputsAndOutputs(inputs, outputs))
2214 return false;
2215 auto srcType = getTypeIfLike<FloatType>(inputs.front());
2216 auto dstType = getTypeIfLike<FloatType>(outputs.front());
2217 if (!srcType || !dstType)
2218 return false;
2219 return srcType != dstType &&
2220 srcType.getIntOrFloatBitWidth() == dstType.getIntOrFloatBitWidth();
2221}
2222
2223LogicalResult arith::ConvertFOp::verify() {
2224 auto srcType = cast<FloatType>(getElementTypeOrSelf(getIn().getType()));
2225 auto dstType = cast<FloatType>(getElementTypeOrSelf(getType()));
2226 if (srcType == dstType)
2227 return emitError("result element type ")
2228 << dstType << " must be different from operand element type "
2229 << srcType;
2230 if (srcType.getWidth() != dstType.getWidth())
2231 return emitError("result element type ")
2232 << dstType << " must have the same bitwidth as operand element type "
2233 << srcType;
2234 return success();
2235}
2236
2237//===----------------------------------------------------------------------===//
2238// ScalingTruncFOp
2239//===----------------------------------------------------------------------===//
2240
2241OpFoldResult arith::ScalingTruncFOp::fold(FoldAdaptor adaptor) {
2242 // scaling_truncf(in, scale) -> truncf(in / extf(scale)), matching the
2243 // expansion in ExpandOps.cpp. Unlike scaling_extf, the scale is widened to
2244 // the type of `in` rather than to the result type.
2245 if (!isFoldableScalingScale(getScale()))
2246 return {};
2247
2248 auto inElemType = cast<FloatType>(getElementTypeOrSelf(getIn().getType()));
2249 auto resElemType = cast<FloatType>(getElementTypeOrSelf(getType()));
2250 const llvm::fltSemantics &inSemantics = inElemType.getFloatSemantics();
2251 const llvm::fltSemantics &resSemantics = resElemType.getFloatSemantics();
2252 llvm::RoundingMode roundingMode =
2253 convertArithRoundingModeToLLVMIR(getRoundingmode());
2254 return foldScalingCastOp(
2255 adaptor.getIn(), adaptor.getScale(), getType(),
2256 [&](const APFloat &in, const APFloat &scale) -> std::optional<APFloat> {
2257 FailureOr<APFloat> scaleExt = convertFloatValue(scale, inSemantics);
2258 if (failed(scaleExt))
2259 return std::nullopt;
2260 APFloat quotient(in);
2261 quotient.divide(*scaleExt, kDefaultRoundingMode);
2262 FailureOr<APFloat> result =
2263 convertFloatValue(quotient, resSemantics, roundingMode);
2264 if (failed(result))
2265 return std::nullopt;
2266 return *result;
2267 });
2268}
2269
2270bool arith::ScalingTruncFOp::areCastCompatible(TypeRange inputs,
2271 TypeRange outputs) {
2272 return checkWidthChangeCast<std::less, FloatType>(inputs.front(), outputs);
2273}
2274
2275LogicalResult arith::ScalingTruncFOp::verify() {
2276 return verifyTruncateOp<FloatType>(*this);
2277}
2278
2279//===----------------------------------------------------------------------===//
2280// AndIOp
2281//===----------------------------------------------------------------------===//
2282
2283void arith::AndIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2284 MLIRContext *context) {
2285 patterns.add<AndIAndIConstant, AndOfExtUI, AndOfExtSI>(context);
2286}
2287
2288//===----------------------------------------------------------------------===//
2289// OrIOp
2290//===----------------------------------------------------------------------===//
2291
2292void arith::OrIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2293 MLIRContext *context) {
2294 patterns.add<OrIOrIConstant, OrOfExtUI, OrOfExtSI>(context);
2295}
2296
2297//===----------------------------------------------------------------------===//
2298// Verifiers for casts between integers and floats.
2299//===----------------------------------------------------------------------===//
2300
2301template <typename From, typename To>
2302static bool checkIntFloatCast(TypeRange inputs, TypeRange outputs) {
2303 if (!areValidCastInputsAndOutputs(inputs, outputs))
2304 return false;
2305
2306 auto srcType = getTypeIfLike<From>(inputs.front());
2307 auto dstType = getTypeIfLike<To>(outputs.back());
2308
2309 return srcType && dstType;
2310}
2311
2312//===----------------------------------------------------------------------===//
2313// UIToFPOp
2314//===----------------------------------------------------------------------===//
2315
2316bool arith::UIToFPOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2317 return checkIntFloatCast<IntegerType, FloatType>(inputs, outputs);
2318}
2319
2320OpFoldResult arith::UIToFPOp::fold(FoldAdaptor adaptor) {
2321 Type resEleType = getElementTypeOrSelf(getType());
2323 adaptor.getOperands(), getType(),
2324 [&resEleType](const APInt &a, bool &castStatus) {
2325 FloatType floatTy = llvm::cast<FloatType>(resEleType);
2326 APFloat apf(floatTy.getFloatSemantics(),
2327 APInt::getZero(floatTy.getWidth()));
2328 apf.convertFromAPInt(a, /*IsSigned=*/false,
2329 APFloat::rmNearestTiesToEven);
2330 return apf;
2331 });
2332}
2333
2334void arith::UIToFPOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2335 MLIRContext *context) {
2336 patterns.add<UIToFPOfExtUI>(context);
2337}
2338
2339//===----------------------------------------------------------------------===//
2340// SIToFPOp
2341//===----------------------------------------------------------------------===//
2342
2343bool arith::SIToFPOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2344 return checkIntFloatCast<IntegerType, FloatType>(inputs, outputs);
2345}
2346
2347OpFoldResult arith::SIToFPOp::fold(FoldAdaptor adaptor) {
2348 Type resEleType = getElementTypeOrSelf(getType());
2350 adaptor.getOperands(), getType(),
2351 [&resEleType](const APInt &a, bool &castStatus) {
2352 FloatType floatTy = llvm::cast<FloatType>(resEleType);
2353 APFloat apf(floatTy.getFloatSemantics(),
2354 APInt::getZero(floatTy.getWidth()));
2355 if (a.isNegative() &&
2356 !APFloat::semanticsHasSignedRepr(floatTy.getFloatSemantics())) {
2357 castStatus = false;
2358 return apf;
2359 }
2360 apf.convertFromAPInt(a, /*IsSigned=*/true,
2361 APFloat::rmNearestTiesToEven);
2362 return apf;
2363 });
2364}
2365
2366void arith::SIToFPOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2367 MLIRContext *context) {
2368 patterns.add<SIToFPOfExtSI, SIToFPOfExtUI>(context);
2369}
2370
2371//===----------------------------------------------------------------------===//
2372// FPToUIOp
2373//===----------------------------------------------------------------------===//
2374
2375bool arith::FPToUIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2376 return checkIntFloatCast<FloatType, IntegerType>(inputs, outputs);
2377}
2378
2379OpFoldResult arith::FPToUIOp::fold(FoldAdaptor adaptor) {
2380 Type resType = getElementTypeOrSelf(getType());
2381 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
2383 adaptor.getOperands(), getType(),
2384 [&bitWidth](const APFloat &a, bool &castStatus) {
2385 bool ignored;
2386 APSInt api(bitWidth, /*isUnsigned=*/true);
2387 castStatus = APFloat::opInvalidOp !=
2388 a.convertToInteger(api, APFloat::rmTowardZero, &ignored);
2389 return api;
2390 });
2391}
2392
2393//===----------------------------------------------------------------------===//
2394// FPToSIOp
2395//===----------------------------------------------------------------------===//
2396
2397bool arith::FPToSIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2398 return checkIntFloatCast<FloatType, IntegerType>(inputs, outputs);
2399}
2400
2401OpFoldResult arith::FPToSIOp::fold(FoldAdaptor adaptor) {
2402 Type resType = getElementTypeOrSelf(getType());
2403 unsigned bitWidth = llvm::cast<IntegerType>(resType).getWidth();
2405 adaptor.getOperands(), getType(),
2406 [&bitWidth](const APFloat &a, bool &castStatus) {
2407 bool ignored;
2408 APSInt api(bitWidth, /*isUnsigned=*/false);
2409 castStatus = APFloat::opInvalidOp !=
2410 a.convertToInteger(api, APFloat::rmTowardZero, &ignored);
2411 return api;
2412 });
2413}
2414
2415//===----------------------------------------------------------------------===//
2416// IndexCastOp
2417//===----------------------------------------------------------------------===//
2418
2419/// Return the bit-width of \p t for the purpose of index_cast width checks.
2420/// For vector types use the element type; index maps to its internal storage
2421/// width (64 on all current targets).
2422static unsigned getIndexCastWidth(Type t) {
2423 if (auto intTy = dyn_cast<IntegerType>(getElementTypeOrSelf(t)))
2424 return intTy.getWidth();
2425 return IndexType::kInternalStorageBitWidth;
2426}
2427
2428static bool areIndexCastCompatible(TypeRange inputs, TypeRange outputs) {
2429 if (!areValidCastInputsAndOutputs(inputs, outputs))
2430 return false;
2431
2432 auto srcType = getTypeIfLikeOrMemRef<IntegerType, IndexType>(inputs.front());
2433 auto dstType = getTypeIfLikeOrMemRef<IntegerType, IndexType>(outputs.front());
2434 if (!srcType || !dstType)
2435 return false;
2436
2437 return (srcType.isIndex() && dstType.isSignlessInteger()) ||
2438 (srcType.isSignlessInteger() && dstType.isIndex());
2439}
2440
2441bool arith::IndexCastOp::areCastCompatible(TypeRange inputs,
2442 TypeRange outputs) {
2443 return areIndexCastCompatible(inputs, outputs);
2444}
2445
2446OpFoldResult arith::IndexCastOp::fold(FoldAdaptor adaptor) {
2447 // index_cast(constant) -> constant
2448 unsigned resultBitwidth = 64; // Default for index integer attributes.
2449 if (auto intTy = dyn_cast<IntegerType>(getElementTypeOrSelf(getType())))
2450 resultBitwidth = intTy.getWidth();
2451
2452 if (auto foldResult = constFoldCastOp<IntegerAttr, IntegerAttr>(
2453 adaptor.getOperands(), getType(),
2454 [resultBitwidth](const APInt &a, bool & /*castStatus*/) {
2455 return a.sextOrTrunc(resultBitwidth);
2456 }))
2457 return foldResult;
2458
2459 // index_cast(index_cast(x : A) : B) : A -> x, but only when B is at least
2460 // as wide as A. If B is narrower, the inner cast truncates and the outer
2461 // cast sign-extends, so the round-trip is lossy.
2462 if (auto inner = getOperand().getDefiningOp<arith::IndexCastOp>()) {
2463 Value x = inner.getOperand();
2464 if (x.getType() == getType()) {
2465 if (getIndexCastWidth(inner.getType()) >= getIndexCastWidth(x.getType()))
2466 return x;
2467 }
2468 }
2469 return {};
2470}
2471
2472void arith::IndexCastOp::getCanonicalizationPatterns(
2473 RewritePatternSet &patterns, MLIRContext *context) {
2474 patterns.add<IndexCastOfExtSI>(context);
2475}
2476
2477//===----------------------------------------------------------------------===//
2478// IndexCastUIOp
2479//===----------------------------------------------------------------------===//
2480
2481bool arith::IndexCastUIOp::areCastCompatible(TypeRange inputs,
2482 TypeRange outputs) {
2483 return areIndexCastCompatible(inputs, outputs);
2484}
2485
2486OpFoldResult arith::IndexCastUIOp::fold(FoldAdaptor adaptor) {
2487 // index_castui(constant) -> constant
2488 unsigned resultBitwidth = 64; // Default for index integer attributes.
2489 if (auto intTy = dyn_cast<IntegerType>(getElementTypeOrSelf(getType())))
2490 resultBitwidth = intTy.getWidth();
2491
2492 if (auto foldResult = constFoldCastOp<IntegerAttr, IntegerAttr>(
2493 adaptor.getOperands(), getType(),
2494 [resultBitwidth](const APInt &a, bool & /*castStatus*/) {
2495 return a.zextOrTrunc(resultBitwidth);
2496 }))
2497 return foldResult;
2498
2499 // index_castui(index_castui(x : A) : B) : A -> x, but only when B is at
2500 // least as wide as A. If B is narrower, the inner cast truncates and the
2501 // outer cast zero-extends, so the round-trip is lossy.
2502 if (auto inner = getOperand().getDefiningOp<arith::IndexCastUIOp>()) {
2503 Value x = inner.getOperand();
2504 if (x.getType() == getType()) {
2505 if (getIndexCastWidth(inner.getType()) >= getIndexCastWidth(x.getType()))
2506 return x;
2507 }
2508 }
2509 return {};
2510}
2511
2512void arith::IndexCastUIOp::getCanonicalizationPatterns(
2513 RewritePatternSet &patterns, MLIRContext *context) {
2514 patterns.add<IndexCastUIOfExtUI>(context);
2515}
2516
2517//===----------------------------------------------------------------------===//
2518// BitcastOp
2519//===----------------------------------------------------------------------===//
2520
2521bool arith::BitcastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
2522 if (!areValidCastInputsAndOutputs(inputs, outputs))
2523 return false;
2524
2525 auto srcType = getTypeIfLikeOrMemRef<IntegerType, FloatType>(inputs.front());
2526 auto dstType = getTypeIfLikeOrMemRef<IntegerType, FloatType>(outputs.front());
2527 if (!srcType || !dstType)
2528 return false;
2529
2530 return srcType.getIntOrFloatBitWidth() == dstType.getIntOrFloatBitWidth();
2531}
2532
2533OpFoldResult arith::BitcastOp::fold(FoldAdaptor adaptor) {
2534 auto resType = getType();
2535 auto operand = adaptor.getIn();
2536 if (!operand)
2537 return {};
2538
2539 /// Bitcast dense elements.
2540 if (auto denseAttr = dyn_cast_or_null<DenseElementsAttr>(operand))
2541 return denseAttr.bitcast(llvm::cast<ShapedType>(resType).getElementType());
2542 /// Other shaped types unhandled.
2543 if (llvm::isa<ShapedType>(resType))
2544 return {};
2545
2546 /// Bitcast poison.
2547 if (matchPattern(operand, ub::m_Poison()))
2548 return ub::PoisonAttr::get(getContext());
2549
2550 /// Bitcast integer or float to integer or float.
2551 if (!llvm::isa<FloatAttr, IntegerAttr>(operand))
2552 return {};
2553
2554 APInt bits = llvm::isa<FloatAttr>(operand)
2555 ? llvm::cast<FloatAttr>(operand).getValue().bitcastToAPInt()
2556 : llvm::cast<IntegerAttr>(operand).getValue();
2557 assert(resType.getIntOrFloatBitWidth() == bits.getBitWidth() &&
2558 "trying to fold on broken IR: operands have incompatible types");
2559
2560 if (auto resFloatType = dyn_cast<FloatType>(resType))
2561 return FloatAttr::get(resType,
2562 APFloat(resFloatType.getFloatSemantics(), bits));
2563 return IntegerAttr::get(resType, bits);
2564}
2565
2566void arith::BitcastOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2567 MLIRContext *context) {
2568 patterns.add<BitcastOfBitcast>(context);
2569}
2570
2571//===----------------------------------------------------------------------===//
2572// CmpIOp
2573//===----------------------------------------------------------------------===//
2574
2575/// Compute `lhs` `pred` `rhs`, where `pred` is one of the known integer
2576/// comparison predicates.
2577bool mlir::arith::applyCmpPredicate(arith::CmpIPredicate predicate,
2578 const APInt &lhs, const APInt &rhs) {
2579 switch (predicate) {
2580 case arith::CmpIPredicate::eq:
2581 return lhs.eq(rhs);
2582 case arith::CmpIPredicate::ne:
2583 return lhs.ne(rhs);
2584 case arith::CmpIPredicate::slt:
2585 return lhs.slt(rhs);
2586 case arith::CmpIPredicate::sle:
2587 return lhs.sle(rhs);
2588 case arith::CmpIPredicate::sgt:
2589 return lhs.sgt(rhs);
2590 case arith::CmpIPredicate::sge:
2591 return lhs.sge(rhs);
2592 case arith::CmpIPredicate::ult:
2593 return lhs.ult(rhs);
2594 case arith::CmpIPredicate::ule:
2595 return lhs.ule(rhs);
2596 case arith::CmpIPredicate::ugt:
2597 return lhs.ugt(rhs);
2598 case arith::CmpIPredicate::uge:
2599 return lhs.uge(rhs);
2600 }
2601 llvm_unreachable("unknown cmpi predicate kind");
2602}
2603
2604/// Returns true if the predicate is true for two equal operands.
2605static bool applyCmpPredicateToEqualOperands(arith::CmpIPredicate predicate) {
2606 switch (predicate) {
2607 case arith::CmpIPredicate::eq:
2608 case arith::CmpIPredicate::sle:
2609 case arith::CmpIPredicate::sge:
2610 case arith::CmpIPredicate::ule:
2611 case arith::CmpIPredicate::uge:
2612 return true;
2613 case arith::CmpIPredicate::ne:
2614 case arith::CmpIPredicate::slt:
2615 case arith::CmpIPredicate::sgt:
2616 case arith::CmpIPredicate::ult:
2617 case arith::CmpIPredicate::ugt:
2618 return false;
2619 }
2620 llvm_unreachable("unknown cmpi predicate kind");
2621}
2622
2623static std::optional<int64_t> getIntegerWidth(Type t) {
2624 if (auto intType = dyn_cast<IntegerType>(t)) {
2625 return intType.getWidth();
2626 }
2627 if (auto vectorIntType = dyn_cast<VectorType>(t)) {
2628 return llvm::cast<IntegerType>(vectorIntType.getElementType()).getWidth();
2629 }
2630 return std::nullopt;
2631}
2632
2633OpFoldResult arith::CmpIOp::fold(FoldAdaptor adaptor) {
2634 // cmpi(pred, x, x)
2635 if (getLhs() == getRhs()) {
2636 auto val = applyCmpPredicateToEqualOperands(getPredicate());
2637 return getBoolAttribute(getType(), val);
2638 }
2639
2640 if (matchPattern(adaptor.getRhs(), m_Zero())) {
2641 if (auto extOp = getLhs().getDefiningOp<ExtSIOp>()) {
2642 // extsi(%x : i1 -> iN) != 0 -> %x
2643 std::optional<int64_t> integerWidth =
2644 getIntegerWidth(extOp.getOperand().getType());
2645 if (integerWidth && integerWidth.value() == 1 &&
2646 getPredicate() == arith::CmpIPredicate::ne)
2647 return extOp.getOperand();
2648 }
2649 if (auto extOp = getLhs().getDefiningOp<ExtUIOp>()) {
2650 // extui(%x : i1 -> iN) != 0 -> %x
2651 std::optional<int64_t> integerWidth =
2652 getIntegerWidth(extOp.getOperand().getType());
2653 if (integerWidth && integerWidth.value() == 1 &&
2654 getPredicate() == arith::CmpIPredicate::ne)
2655 return extOp.getOperand();
2656 }
2657
2658 // arith.cmpi ne, %val, %zero : i1 -> %val
2659 if (getElementTypeOrSelf(getLhs().getType()).isInteger(1) &&
2660 getPredicate() == arith::CmpIPredicate::ne)
2661 return getLhs();
2662 }
2663
2664 if (matchPattern(adaptor.getRhs(), m_One())) {
2665 // arith.cmpi eq, %val, %one : i1 -> %val
2666 if (getElementTypeOrSelf(getLhs().getType()).isInteger(1) &&
2667 getPredicate() == arith::CmpIPredicate::eq)
2668 return getLhs();
2669 }
2670
2671 // Move constant to the right side.
2672 if (adaptor.getLhs() && !adaptor.getRhs()) {
2673 // Do not use invertPredicate, as it will change eq to ne and vice versa.
2674 using Pred = CmpIPredicate;
2675 const std::pair<Pred, Pred> invPreds[] = {
2676 {Pred::slt, Pred::sgt}, {Pred::sgt, Pred::slt}, {Pred::sle, Pred::sge},
2677 {Pred::sge, Pred::sle}, {Pred::ult, Pred::ugt}, {Pred::ugt, Pred::ult},
2678 {Pred::ule, Pred::uge}, {Pred::uge, Pred::ule}, {Pred::eq, Pred::eq},
2679 {Pred::ne, Pred::ne},
2680 };
2681 Pred origPred = getPredicate();
2682 for (auto pred : invPreds) {
2683 if (origPred == pred.first) {
2684 setPredicate(pred.second);
2685 Value lhs = getLhs();
2686 Value rhs = getRhs();
2687 getLhsMutable().assign(rhs);
2688 getRhsMutable().assign(lhs);
2689 return getResult();
2690 }
2691 }
2692 llvm_unreachable("unknown cmpi predicate kind");
2693 }
2694
2695 // We are moving constants to the right side; So if lhs is constant rhs is
2696 // guaranteed to be a constant.
2697 if (auto lhs = dyn_cast_if_present<TypedAttr>(adaptor.getLhs())) {
2699 adaptor.getOperands(), getI1SameShape(lhs.getType()),
2700 [pred = getPredicate()](const APInt &lhs, const APInt &rhs) {
2701 return APInt(1,
2702 static_cast<int64_t>(applyCmpPredicate(pred, lhs, rhs)));
2703 });
2704 }
2705
2706 return {};
2707}
2708
2709void arith::CmpIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2710 MLIRContext *context) {
2711 patterns.insert<CmpIExtSI, CmpIExtUI>(context);
2712}
2713
2714//===----------------------------------------------------------------------===//
2715// CmpFOp
2716//===----------------------------------------------------------------------===//
2717
2718/// Compute `lhs` `pred` `rhs`, where `pred` is one of the known floating point
2719/// comparison predicates.
2720bool mlir::arith::applyCmpPredicate(arith::CmpFPredicate predicate,
2721 const APFloat &lhs, const APFloat &rhs) {
2722 auto cmpResult = lhs.compare(rhs);
2723 switch (predicate) {
2724 case arith::CmpFPredicate::AlwaysFalse:
2725 return false;
2726 case arith::CmpFPredicate::OEQ:
2727 return cmpResult == APFloat::cmpEqual;
2728 case arith::CmpFPredicate::OGT:
2729 return cmpResult == APFloat::cmpGreaterThan;
2730 case arith::CmpFPredicate::OGE:
2731 return cmpResult == APFloat::cmpGreaterThan ||
2732 cmpResult == APFloat::cmpEqual;
2733 case arith::CmpFPredicate::OLT:
2734 return cmpResult == APFloat::cmpLessThan;
2735 case arith::CmpFPredicate::OLE:
2736 return cmpResult == APFloat::cmpLessThan || cmpResult == APFloat::cmpEqual;
2737 case arith::CmpFPredicate::ONE:
2738 return cmpResult != APFloat::cmpUnordered && cmpResult != APFloat::cmpEqual;
2739 case arith::CmpFPredicate::ORD:
2740 return cmpResult != APFloat::cmpUnordered;
2741 case arith::CmpFPredicate::UEQ:
2742 return cmpResult == APFloat::cmpUnordered || cmpResult == APFloat::cmpEqual;
2743 case arith::CmpFPredicate::UGT:
2744 return cmpResult == APFloat::cmpUnordered ||
2745 cmpResult == APFloat::cmpGreaterThan;
2746 case arith::CmpFPredicate::UGE:
2747 return cmpResult == APFloat::cmpUnordered ||
2748 cmpResult == APFloat::cmpGreaterThan ||
2749 cmpResult == APFloat::cmpEqual;
2750 case arith::CmpFPredicate::ULT:
2751 return cmpResult == APFloat::cmpUnordered ||
2752 cmpResult == APFloat::cmpLessThan;
2753 case arith::CmpFPredicate::ULE:
2754 return cmpResult == APFloat::cmpUnordered ||
2755 cmpResult == APFloat::cmpLessThan || cmpResult == APFloat::cmpEqual;
2756 case arith::CmpFPredicate::UNE:
2757 return cmpResult != APFloat::cmpEqual;
2758 case arith::CmpFPredicate::UNO:
2759 return cmpResult == APFloat::cmpUnordered;
2760 case arith::CmpFPredicate::AlwaysTrue:
2761 return true;
2762 }
2763 llvm_unreachable("unknown cmpf predicate kind");
2764}
2765
2766OpFoldResult arith::CmpFOp::fold(FoldAdaptor adaptor) {
2767 auto lhs = dyn_cast_if_present<FloatAttr>(adaptor.getLhs());
2768 auto rhs = dyn_cast_if_present<FloatAttr>(adaptor.getRhs());
2769
2770 // If one operand is NaN, making them both NaN does not change the result.
2771 if (lhs && lhs.getValue().isNaN())
2772 rhs = lhs;
2773 if (rhs && rhs.getValue().isNaN())
2774 lhs = rhs;
2775
2776 if (!lhs || !rhs)
2777 return {};
2778
2779 auto val = applyCmpPredicate(getPredicate(), lhs.getValue(), rhs.getValue());
2780 return BoolAttr::get(getContext(), val);
2781}
2782
2783namespace {
2784class CmpFIntToFPConst final : public OpRewritePattern<CmpFOp> {
2785public:
2786 using Base::Base;
2787
2788 static CmpIPredicate convertToIntegerPredicate(CmpFPredicate pred,
2789 bool isUnsigned) {
2790 using namespace arith;
2791 switch (pred) {
2792 case CmpFPredicate::UEQ:
2793 case CmpFPredicate::OEQ:
2794 return CmpIPredicate::eq;
2795 case CmpFPredicate::UGT:
2796 case CmpFPredicate::OGT:
2797 return isUnsigned ? CmpIPredicate::ugt : CmpIPredicate::sgt;
2798 case CmpFPredicate::UGE:
2799 case CmpFPredicate::OGE:
2800 return isUnsigned ? CmpIPredicate::uge : CmpIPredicate::sge;
2801 case CmpFPredicate::ULT:
2802 case CmpFPredicate::OLT:
2803 return isUnsigned ? CmpIPredicate::ult : CmpIPredicate::slt;
2804 case CmpFPredicate::ULE:
2805 case CmpFPredicate::OLE:
2806 return isUnsigned ? CmpIPredicate::ule : CmpIPredicate::sle;
2807 case CmpFPredicate::UNE:
2808 case CmpFPredicate::ONE:
2809 return CmpIPredicate::ne;
2810 default:
2811 llvm_unreachable("Unexpected predicate!");
2812 }
2813 }
2814
2815 LogicalResult matchAndRewrite(CmpFOp op,
2816 PatternRewriter &rewriter) const override {
2817 FloatAttr flt;
2818 if (!matchPattern(op.getRhs(), m_Constant(&flt)))
2819 return failure();
2820
2821 const APFloat &rhs = flt.getValue();
2822
2823 // Don't attempt to fold a nan.
2824 if (rhs.isNaN())
2825 return failure();
2826
2827 // Get the width of the mantissa. We don't want to hack on conversions that
2828 // might lose information from the integer, e.g. "i64 -> float"
2829 FloatType floatTy = llvm::cast<FloatType>(op.getRhs().getType());
2830 int mantissaWidth = floatTy.getFPMantissaWidth();
2831 if (mantissaWidth <= 0)
2832 return failure();
2833
2834 bool isUnsigned;
2835 Value intVal;
2836
2837 if (auto si = op.getLhs().getDefiningOp<SIToFPOp>()) {
2838 isUnsigned = false;
2839 intVal = si.getIn();
2840 } else if (auto ui = op.getLhs().getDefiningOp<UIToFPOp>()) {
2841 isUnsigned = true;
2842 intVal = ui.getIn();
2843 } else {
2844 return failure();
2845 }
2846
2847 // Check to see that the input is converted from an integer type that is
2848 // small enough that preserves all bits.
2849 auto intTy = llvm::cast<IntegerType>(intVal.getType());
2850 auto intWidth = intTy.getWidth();
2851
2852 // Number of bits representing values, as opposed to the sign
2853 auto valueBits = isUnsigned ? intWidth : (intWidth - 1);
2854
2855 const llvm::fltSemantics &semantics = floatTy.getFloatSemantics();
2856
2857 // Conservatively require an exact representation of integer zero. For
2858 // example, f8E8M0FNU converts zero to its smallest positive value.
2859 if (!APFloat::semanticsHasZero(semantics))
2860 return failure();
2861
2862 // Signed inputs may be negative, which e.g. f8E5M3FNU cannot represent.
2863 // Do not try to convert the signed minimum below for such formats.
2864 if (!isUnsigned && !APFloat::semanticsHasSignedRepr(semantics))
2865 return failure();
2866
2867 if (!APFloat::semanticsHasInf(semantics)) {
2868 // Overflow can produce NaN or saturate to a finite value:
2869 // - sitofp(1000 : i32) to f8E4M3FN is NaN, so cmpf ogt against 0.0
2870 // is false, whereas cmpi sgt against integer zero would be true.
2871 // - uitofp(8 : i4) to f6E2M3FN saturates to 7.5. Equality against 7.5
2872 // is true, but the fractional-constant logic below would fold it to
2873 // false. Its four bits of precision do not prevent overflow.
2874 //
2875 // Conservatively require the enclosing 2^valueBits (and its negative
2876 // for signed inputs) to be representable, so rounding stays within
2877 // finite bounds. Equality at the exponent bound is safe.
2878 if (APFloat::semanticsMaxExponent(semantics) <
2879 static_cast<int>(valueBits))
2880 return failure();
2881 }
2882
2883 // Following test does NOT adjust intWidth downwards for signed inputs,
2884 // because the most negative value still requires all the mantissa bits
2885 // to distinguish it from one less than that value.
2886 if ((int)intWidth > mantissaWidth) {
2887 // Conversion would lose accuracy. Check if loss can impact comparison.
2888 int exponent = ilogb(rhs);
2889 if (exponent == APFloat::IEK_Inf) {
2890 int maxExponent = ilogb(APFloat::getLargest(rhs.getSemantics()));
2891 if (maxExponent < static_cast<int>(valueBits)) {
2892 // Conversion could create infinity. For example, sitofp(65536 : i32)
2893 // to f16 is +inf, so equality against +inf can be true.
2894 return failure();
2895 }
2896 } else {
2897 // Note that if rhs is zero or NaN, then Exp is negative
2898 // and first condition is trivially false.
2899 if (mantissaWidth <= exponent && exponent <= (int)valueBits) {
2900 // Conversion could affect comparison.
2901 return failure();
2902 }
2903 }
2904 }
2905
2906 // Convert to equivalent cmpi predicate
2907 CmpIPredicate pred;
2908 switch (op.getPredicate()) {
2909 case CmpFPredicate::AlwaysTrue:
2910 case CmpFPredicate::ORD:
2911 // Int to fp conversion doesn't create a nan (ord checks neither is a nan)
2912 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2913 /*width=*/1);
2914 return success();
2915 case CmpFPredicate::AlwaysFalse:
2916 case CmpFPredicate::UNO:
2917 // Int to fp conversion doesn't create a nan (uno checks either is a nan)
2918 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2919 /*width=*/1);
2920 return success();
2921 default:
2922 pred = convertToIntegerPredicate(op.getPredicate(), isUnsigned);
2923 break;
2924 }
2925
2926 if (!isUnsigned) {
2927 // If the rhs value is > SignedMax, fold the comparison. This handles
2928 // +INF and large values.
2929 APFloat signedMax(rhs.getSemantics());
2930 signedMax.convertFromAPInt(APInt::getSignedMaxValue(intWidth), true,
2931 APFloat::rmNearestTiesToEven);
2932 if (signedMax < rhs) { // smax < 13123.0
2933 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::slt ||
2934 pred == CmpIPredicate::sle)
2935 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2936 /*width=*/1);
2937 else
2938 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2939 /*width=*/1);
2940 return success();
2941 }
2942 } else {
2943 // If the rhs value is > UnsignedMax, fold the comparison. This handles
2944 // +INF and large values.
2945 APFloat unsignedMax(rhs.getSemantics());
2946 unsignedMax.convertFromAPInt(APInt::getMaxValue(intWidth), false,
2947 APFloat::rmNearestTiesToEven);
2948 if (unsignedMax < rhs) { // umax < 13123.0
2949 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::ult ||
2950 pred == CmpIPredicate::ule)
2951 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2952 /*width=*/1);
2953 else
2954 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2955 /*width=*/1);
2956 return success();
2957 }
2958 }
2959
2960 if (!isUnsigned) {
2961 // See if the rhs value is < SignedMin.
2962 APFloat signedMin(rhs.getSemantics());
2963 signedMin.convertFromAPInt(APInt::getSignedMinValue(intWidth), true,
2964 APFloat::rmNearestTiesToEven);
2965 if (signedMin > rhs) { // smin > 12312.0
2966 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::sgt ||
2967 pred == CmpIPredicate::sge)
2968 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2969 /*width=*/1);
2970 else
2971 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2972 /*width=*/1);
2973 return success();
2974 }
2975 } else {
2976 // See if the rhs value is < UnsignedMin.
2977 APFloat unsignedMin(rhs.getSemantics());
2978 unsignedMin.convertFromAPInt(APInt::getMinValue(intWidth), false,
2979 APFloat::rmNearestTiesToEven);
2980 if (unsignedMin > rhs) { // umin > 12312.0
2981 if (pred == CmpIPredicate::ne || pred == CmpIPredicate::ugt ||
2982 pred == CmpIPredicate::uge)
2983 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
2984 /*width=*/1);
2985 else
2986 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
2987 /*width=*/1);
2988 return success();
2989 }
2990 }
2991
2992 // Okay, now we know that the FP constant fits in the range [SMIN, SMAX] or
2993 // [0, UMAX], but it may still be fractional. See if it is fractional by
2994 // casting the FP value to the integer value and back, checking for
2995 // equality. Don't do this for zero, because -0.0 is not fractional.
2996 bool ignored;
2997 APSInt rhsInt(intWidth, isUnsigned);
2998 if (APFloat::opInvalidOp ==
2999 rhs.convertToInteger(rhsInt, APFloat::rmTowardZero, &ignored)) {
3000 // Undefined behavior invoked - the destination type can't represent
3001 // the input constant.
3002 return failure();
3003 }
3004
3005 if (!rhs.isZero()) {
3006 APFloat apf(floatTy.getFloatSemantics(),
3007 APInt::getZero(floatTy.getWidth()));
3008 apf.convertFromAPInt(rhsInt, !isUnsigned, APFloat::rmNearestTiesToEven);
3009
3010 bool equal = apf == rhs;
3011 if (!equal) {
3012 // If we had a comparison against a fractional value, we have to adjust
3013 // the compare predicate and sometimes the value. rhsInt is rounded
3014 // towards zero at this point.
3015 switch (pred) {
3016 case CmpIPredicate::ne: // (float)int != 4.4 --> true
3017 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
3018 /*width=*/1);
3019 return success();
3020 case CmpIPredicate::eq: // (float)int == 4.4 --> false
3021 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
3022 /*width=*/1);
3023 return success();
3024 case CmpIPredicate::ule:
3025 // (float)int <= 4.4 --> int <= 4
3026 // (float)int <= -4.4 --> false
3027 if (rhs.isNegative()) {
3028 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
3029 /*width=*/1);
3030 return success();
3031 }
3032 break;
3033 case CmpIPredicate::sle:
3034 // (float)int <= 4.4 --> int <= 4
3035 // (float)int <= -4.4 --> int < -4
3036 if (rhs.isNegative())
3037 pred = CmpIPredicate::slt;
3038 break;
3039 case CmpIPredicate::ult:
3040 // (float)int < -4.4 --> false
3041 // (float)int < 4.4 --> int <= 4
3042 if (rhs.isNegative()) {
3043 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/false,
3044 /*width=*/1);
3045 return success();
3046 }
3047 pred = CmpIPredicate::ule;
3048 break;
3049 case CmpIPredicate::slt:
3050 // (float)int < -4.4 --> int < -4
3051 // (float)int < 4.4 --> int <= 4
3052 if (!rhs.isNegative())
3053 pred = CmpIPredicate::sle;
3054 break;
3055 case CmpIPredicate::ugt:
3056 // (float)int > 4.4 --> int > 4
3057 // (float)int > -4.4 --> true
3058 if (rhs.isNegative()) {
3059 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
3060 /*width=*/1);
3061 return success();
3062 }
3063 break;
3064 case CmpIPredicate::sgt:
3065 // (float)int > 4.4 --> int > 4
3066 // (float)int > -4.4 --> int >= -4
3067 if (rhs.isNegative())
3068 pred = CmpIPredicate::sge;
3069 break;
3070 case CmpIPredicate::uge:
3071 // (float)int >= -4.4 --> true
3072 // (float)int >= 4.4 --> int > 4
3073 if (rhs.isNegative()) {
3074 rewriter.replaceOpWithNewOp<ConstantIntOp>(op, /*value=*/true,
3075 /*width=*/1);
3076 return success();
3077 }
3078 pred = CmpIPredicate::ugt;
3079 break;
3080 case CmpIPredicate::sge:
3081 // (float)int >= -4.4 --> int >= -4
3082 // (float)int >= 4.4 --> int > 4
3083 if (!rhs.isNegative())
3084 pred = CmpIPredicate::sgt;
3085 break;
3086 }
3087 }
3088 }
3089
3090 // Lower this FP comparison into an appropriate integer version of the
3091 // comparison.
3092 rewriter.replaceOpWithNewOp<CmpIOp>(
3093 op, pred, intVal,
3094 ConstantOp::create(rewriter, op.getLoc(), intVal.getType(),
3095 rewriter.getIntegerAttr(intVal.getType(), rhsInt)));
3096 return success();
3097 }
3098};
3099} // namespace
3100
3101void arith::CmpFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
3102 MLIRContext *context) {
3103 patterns.insert<CmpFIntToFPConst>(context);
3104}
3105
3106//===----------------------------------------------------------------------===//
3107// SelectOp
3108//===----------------------------------------------------------------------===//
3109
3110namespace {
3111// select %arg, %c1, %c0 => extui %arg
3112struct SelectToExtUI : public OpRewritePattern<arith::SelectOp> {
3113 using Base::Base;
3114
3115 LogicalResult matchAndRewrite(arith::SelectOp op,
3116 PatternRewriter &rewriter) const override {
3117 // Cannot extui i1 to i1, or i1 to f32
3118 if (!llvm::isa<IntegerType>(op.getType()) || op.getType().isInteger(1))
3119 return failure();
3120
3121 // select %x, c1, %c0 => extui %arg
3122 if (matchPattern(op.getTrueValue(), m_One()) &&
3123 matchPattern(op.getFalseValue(), m_Zero())) {
3124 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(op, op.getType(),
3125 op.getCondition());
3126 return success();
3127 }
3128
3129 // select %x, c0, %c1 => extui (xor %arg, true)
3130 if (matchPattern(op.getTrueValue(), m_Zero()) &&
3131 matchPattern(op.getFalseValue(), m_One())) {
3132 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(
3133 op, op.getType(),
3134 arith::XOrIOp::create(
3135 rewriter, op.getLoc(), op.getCondition(),
3136 arith::ConstantIntOp::create(rewriter, op.getLoc(),
3137 op.getCondition().getType(), 1)));
3138 return success();
3139 }
3140
3141 return failure();
3142 }
3143};
3144} // namespace
3145
3146void arith::SelectOp::getCanonicalizationPatterns(RewritePatternSet &results,
3147 MLIRContext *context) {
3148 results.add<RedundantSelectFalse, RedundantSelectTrue, SelectNotCond,
3149 SelectI1ToNot, SelectCmpISgeToMaxSI, SelectCmpISgeToMinSI,
3150 SelectCmpISgtToMaxSI, SelectCmpISgtToMinSI, SelectCmpISleToMaxSI,
3151 SelectCmpISleToMinSI, SelectCmpISltToMaxSI, SelectCmpISltToMinSI,
3152 SelectCmpIUgeToMaxUI, SelectCmpIUgeToMinUI, SelectCmpIUgtToMaxUI,
3153 SelectCmpIUgtToMinUI, SelectCmpIUleToMaxUI, SelectCmpIUleToMinUI,
3154 SelectCmpIUltToMaxUI, SelectCmpIUltToMinUI, SelectToExtUI>(
3155 context);
3156}
3157
3158OpFoldResult arith::SelectOp::fold(FoldAdaptor adaptor) {
3159 Value trueVal = getTrueValue();
3160 Value falseVal = getFalseValue();
3161 if (trueVal == falseVal)
3162 return trueVal;
3163
3164 Value condition = getCondition();
3165
3166 // select true, %0, %1 => %0
3167 if (matchPattern(adaptor.getCondition(), m_One()))
3168 return trueVal;
3169
3170 // select false, %0, %1 => %1
3171 if (matchPattern(adaptor.getCondition(), m_Zero()))
3172 return falseVal;
3173
3174 // If either operand is fully poisoned, return the other.
3175 if (matchPattern(adaptor.getTrueValue(), ub::m_Poison()))
3176 return falseVal;
3177
3178 if (matchPattern(adaptor.getFalseValue(), ub::m_Poison()))
3179 return trueVal;
3180
3181 // select %x, true, false => %x
3182 if (getType().isSignlessInteger(1) &&
3183 matchPattern(adaptor.getTrueValue(), m_One()) &&
3184 matchPattern(adaptor.getFalseValue(), m_Zero()))
3185 return condition;
3186
3187 if (auto cmp = condition.getDefiningOp<arith::CmpIOp>()) {
3188 auto pred = cmp.getPredicate();
3189 if (pred == arith::CmpIPredicate::eq || pred == arith::CmpIPredicate::ne) {
3190 auto cmpLhs = cmp.getLhs();
3191 auto cmpRhs = cmp.getRhs();
3192
3193 // %0 = arith.cmpi eq, %arg0, %arg1
3194 // %1 = arith.select %0, %arg0, %arg1 => %arg1
3195
3196 // %0 = arith.cmpi ne, %arg0, %arg1
3197 // %1 = arith.select %0, %arg0, %arg1 => %arg0
3198
3199 if ((cmpLhs == trueVal && cmpRhs == falseVal) ||
3200 (cmpRhs == trueVal && cmpLhs == falseVal))
3201 return pred == arith::CmpIPredicate::ne ? trueVal : falseVal;
3202 }
3203 }
3204
3205 // Constant-fold constant operands over non-splat constant condition.
3206 // select %cst_vec, %cst0, %cst1 => %cst2
3207 if (auto cond =
3208 dyn_cast_if_present<DenseElementsAttr>(adaptor.getCondition())) {
3209 // DenseElementsAttr by construction always has a static shape.
3210 assert(cond.getType().hasStaticShape() &&
3211 "DenseElementsAttr must have static shape");
3212 if (auto lhs =
3213 dyn_cast_if_present<DenseElementsAttr>(adaptor.getTrueValue())) {
3214 if (auto rhs =
3215 dyn_cast_if_present<DenseElementsAttr>(adaptor.getFalseValue())) {
3216 SmallVector<Attribute> results;
3217 results.reserve(static_cast<size_t>(cond.getNumElements()));
3218 auto condVals = llvm::make_range(cond.value_begin<BoolAttr>(),
3219 cond.value_end<BoolAttr>());
3220 auto lhsVals = llvm::make_range(lhs.value_begin<Attribute>(),
3221 lhs.value_end<Attribute>());
3222 auto rhsVals = llvm::make_range(rhs.value_begin<Attribute>(),
3223 rhs.value_end<Attribute>());
3224
3225 for (auto [condVal, lhsVal, rhsVal] :
3226 llvm::zip_equal(condVals, lhsVals, rhsVals))
3227 results.push_back(condVal.getValue() ? lhsVal : rhsVal);
3228
3229 return DenseElementsAttr::get(lhs.getType(), results);
3230 }
3231 }
3232 }
3233
3234 return nullptr;
3235}
3236
3237ParseResult SelectOp::parse(OpAsmParser &parser, OperationState &result) {
3238 Type conditionType, resultType;
3239 SmallVector<OpAsmParser::UnresolvedOperand, 3> operands;
3240 if (parser.parseOperandList(operands, /*requiredOperandCount=*/3) ||
3241 parser.parseOptionalAttrDict(result.attributes) ||
3242 parser.parseColonType(resultType))
3243 return failure();
3244
3245 // Check for the explicit condition type if this is a masked tensor or vector.
3246 if (succeeded(parser.parseOptionalComma())) {
3247 conditionType = resultType;
3248 if (parser.parseType(resultType))
3249 return failure();
3250 } else {
3251 conditionType = parser.getBuilder().getI1Type();
3252 }
3253
3254 result.addTypes(resultType);
3255 return parser.resolveOperands(operands,
3256 {conditionType, resultType, resultType},
3257 parser.getNameLoc(), result.operands);
3258}
3259
3260void arith::SelectOp::print(OpAsmPrinter &p) {
3261 p << " " << getOperands();
3262 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
3263 p << " : ";
3264 if (ShapedType condType = dyn_cast<ShapedType>(getCondition().getType()))
3265 p << condType << ", ";
3266 p << getType();
3267}
3268
3269LogicalResult arith::SelectOp::verify() {
3270 Type conditionType = getCondition().getType();
3271 if (conditionType.isSignlessInteger(1))
3272 return success();
3273
3274 // If the result type is a vector or tensor, the type can be a mask with the
3275 // same elements.
3276 Type resultType = getType();
3277 if (!llvm::isa<TensorType, VectorType>(resultType))
3278 return emitOpError() << "expected condition to be a signless i1, but got "
3279 << conditionType;
3280 Type shapedConditionType = getI1SameShape(resultType);
3281 if (conditionType != shapedConditionType) {
3282 return emitOpError() << "expected condition type to have the same shape "
3283 "as the result type, expected "
3284 << shapedConditionType << ", but got "
3285 << conditionType;
3286 }
3287 return success();
3288}
3289//===----------------------------------------------------------------------===//
3290// ShLIOp
3291//===----------------------------------------------------------------------===//
3292
3293OpFoldResult arith::ShLIOp::fold(FoldAdaptor adaptor) {
3294 // TODO: shli(x, c) -> poison when c is out of range (c >= bit width). An
3295 // out-of-range shift amount is undefined behaviour and could fold to poison,
3296 // but that would make the arith dialect depend on the ub dialect to
3297 // materialize ub.poison; left out for now.
3298
3299 // shli(x, 0) -> x
3300 if (matchPattern(adaptor.getRhs(), m_Zero()))
3301 return getLhs();
3302 // shli(0, x) -> 0. An out-of-range shift amount yields poison, so refining
3303 // it to 0 is valid.
3304 if (matchPattern(adaptor.getLhs(), m_Zero()))
3305 return getLhs();
3306 // Don't fold if shifting more or equal than the bit width.
3307 bool bounded = false;
3309 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
3310 bounded = b.ult(b.getBitWidth());
3311 return a.shl(b);
3312 });
3313 return bounded ? result : Attribute();
3314}
3315
3316//===----------------------------------------------------------------------===//
3317// ShRUIOp
3318//===----------------------------------------------------------------------===//
3319
3320OpFoldResult arith::ShRUIOp::fold(FoldAdaptor adaptor) {
3321 // TODO: shrui(x, c) -> poison when c is out of range (c >= bit width). An
3322 // out-of-range shift amount is undefined behaviour and could fold to poison,
3323 // but that would make the arith dialect depend on the ub dialect to
3324 // materialize ub.poison; left out for now.
3325
3326 // shrui(x, 0) -> x
3327 if (matchPattern(adaptor.getRhs(), m_Zero()))
3328 return getLhs();
3329 // shrui(0, x) -> 0. An out-of-range shift amount yields poison, so refining
3330 // it to 0 is valid.
3331 if (matchPattern(adaptor.getLhs(), m_Zero()))
3332 return getLhs();
3333 // shrui(x, x) -> 0. For any in-range shift amount v < bitwidth, v >> v == 0
3334 // (v < 2^v); out-of-range amounts yield poison, so 0 is a valid refinement.
3335 if (getLhs() == getRhs())
3336 return getIntegerAttrOfType(getType(), 0);
3337 // Don't fold if shifting more or equal than the bit width.
3338 bool bounded = false;
3340 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
3341 bounded = b.ult(b.getBitWidth());
3342 return a.lshr(b);
3343 });
3344 return bounded ? result : Attribute();
3345}
3346
3347//===----------------------------------------------------------------------===//
3348// ShRSIOp
3349//===----------------------------------------------------------------------===//
3350
3351OpFoldResult arith::ShRSIOp::fold(FoldAdaptor adaptor) {
3352 // TODO: shrsi(x, c) -> poison when c is out of range (c >= bit width). An
3353 // out-of-range shift amount is undefined behaviour and could fold to poison,
3354 // but that would make the arith dialect depend on the ub dialect to
3355 // materialize ub.poison; left out for now.
3356
3357 // shrsi(x, 0) -> x
3358 if (matchPattern(adaptor.getRhs(), m_Zero()))
3359 return getLhs();
3360 // shrsi(0, x) -> 0. An out-of-range shift amount yields poison, so refining
3361 // it to 0 is valid.
3362 if (matchPattern(adaptor.getLhs(), m_Zero()))
3363 return getLhs();
3364 // shrsi(x, x) -> 0. For any in-range shift amount v < bitwidth, v is a small
3365 // non-negative value and v >> v == 0; out-of-range amounts yield poison.
3366 if (getLhs() == getRhs())
3367 return getIntegerAttrOfType(getType(), 0);
3368 // shrsi(-1, x) -> -1. Arithmetic shift of all-ones is all-ones for any
3369 // in-range amount; out-of-range amounts yield poison.
3370 if (APInt val;
3371 matchPattern(adaptor.getLhs(), m_ConstantInt(&val)) && val.isAllOnes())
3372 return getLhs();
3373 // Don't fold if shifting more or equal than the bit width.
3374 bool bounded = false;
3376 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
3377 bounded = b.ult(b.getBitWidth());
3378 return a.ashr(b);
3379 });
3380 return bounded ? result : Attribute();
3381}
3382
3383//===----------------------------------------------------------------------===//
3384// Atomic Enum
3385//===----------------------------------------------------------------------===//
3386
3387/// Returns the identity value attribute associated with an AtomicRMWKind op.
3388TypedAttr mlir::arith::getIdentityValueAttr(AtomicRMWKind kind, Type resultType,
3389 OpBuilder &builder, Location loc,
3390 bool useOnlyFiniteValue) {
3391 switch (kind) {
3392 case AtomicRMWKind::maximumf: {
3393 const llvm::fltSemantics &semantic =
3394 llvm::cast<FloatType>(resultType).getFloatSemantics();
3395 APFloat identity = useOnlyFiniteValue
3396 ? APFloat::getLargest(semantic, /*Negative=*/true)
3397 : APFloat::getInf(semantic, /*Negative=*/true);
3398 return builder.getFloatAttr(resultType, identity);
3399 }
3400 case AtomicRMWKind::maxnumf: {
3401 const llvm::fltSemantics &semantic =
3402 llvm::cast<FloatType>(resultType).getFloatSemantics();
3403 APFloat identity = useOnlyFiniteValue
3404 ? APFloat::getLargest(semantic, /*Negative=*/true)
3405 : APFloat::getNaN(semantic, /*Negative=*/true);
3406 return builder.getFloatAttr(resultType, identity);
3407 }
3408 case AtomicRMWKind::addf:
3409 case AtomicRMWKind::addi:
3410 case AtomicRMWKind::maxu:
3411 case AtomicRMWKind::ori:
3412 case AtomicRMWKind::xori:
3413 return builder.getZeroAttr(resultType);
3414 case AtomicRMWKind::andi:
3415 return builder.getIntegerAttr(
3416 resultType,
3417 APInt::getAllOnes(llvm::cast<IntegerType>(resultType).getWidth()));
3418 case AtomicRMWKind::maxs:
3419 return builder.getIntegerAttr(
3420 resultType, APInt::getSignedMinValue(
3421 llvm::cast<IntegerType>(resultType).getWidth()));
3422 case AtomicRMWKind::minimumf: {
3423 const llvm::fltSemantics &semantic =
3424 llvm::cast<FloatType>(resultType).getFloatSemantics();
3425 APFloat identity = useOnlyFiniteValue
3426 ? APFloat::getLargest(semantic, /*Negative=*/false)
3427 : APFloat::getInf(semantic, /*Negative=*/false);
3428
3429 return builder.getFloatAttr(resultType, identity);
3430 }
3431 case AtomicRMWKind::minnumf: {
3432 const llvm::fltSemantics &semantic =
3433 llvm::cast<FloatType>(resultType).getFloatSemantics();
3434 APFloat identity = useOnlyFiniteValue
3435 ? APFloat::getLargest(semantic, /*Negative=*/false)
3436 : APFloat::getNaN(semantic, /*Negative=*/false);
3437 return builder.getFloatAttr(resultType, identity);
3438 }
3439 case AtomicRMWKind::mins:
3440 return builder.getIntegerAttr(
3441 resultType, APInt::getSignedMaxValue(
3442 llvm::cast<IntegerType>(resultType).getWidth()));
3443 case AtomicRMWKind::minu:
3444 return builder.getIntegerAttr(
3445 resultType,
3446 APInt::getMaxValue(llvm::cast<IntegerType>(resultType).getWidth()));
3447 case AtomicRMWKind::muli:
3448 return builder.getIntegerAttr(resultType, 1);
3449 case AtomicRMWKind::mulf:
3450 return builder.getFloatAttr(resultType, 1);
3451 // `assign` is not a reduction and has no identity element.
3452 case AtomicRMWKind::assign:
3453 break;
3454 }
3455 (void)emitOptionalError(loc, "Reduction operation type not supported");
3456 return nullptr;
3457}
3458
3459/// Returns the identity numeric value of the given op.
3460std::optional<TypedAttr> mlir::arith::getNeutralElement(Operation *op) {
3461 std::optional<AtomicRMWKind> maybeKind =
3463 // Floating-point operations.
3464 .Case([](arith::AddFOp op) { return AtomicRMWKind::addf; })
3465 .Case([](arith::MulFOp op) { return AtomicRMWKind::mulf; })
3466 .Case([](arith::MaximumFOp op) { return AtomicRMWKind::maximumf; })
3467 .Case([](arith::MinimumFOp op) { return AtomicRMWKind::minimumf; })
3468 .Case([](arith::MaxNumFOp op) { return AtomicRMWKind::maxnumf; })
3469 .Case([](arith::MinNumFOp op) { return AtomicRMWKind::minnumf; })
3470 // Integer operations.
3471 .Case([](arith::AddIOp op) { return AtomicRMWKind::addi; })
3472 .Case([](arith::OrIOp op) { return AtomicRMWKind::ori; })
3473 .Case([](arith::XOrIOp op) { return AtomicRMWKind::xori; })
3474 .Case([](arith::AndIOp op) { return AtomicRMWKind::andi; })
3475 .Case([](arith::MaxUIOp op) { return AtomicRMWKind::maxu; })
3476 .Case([](arith::MinUIOp op) { return AtomicRMWKind::minu; })
3477 .Case([](arith::MaxSIOp op) { return AtomicRMWKind::maxs; })
3478 .Case([](arith::MinSIOp op) { return AtomicRMWKind::mins; })
3479 .Case([](arith::MulIOp op) { return AtomicRMWKind::muli; })
3480 .Default(std::nullopt);
3481 if (!maybeKind) {
3482 return std::nullopt;
3483 }
3484
3485 bool useOnlyFiniteValue = false;
3486 auto fmfOpInterface = dyn_cast<ArithFastMathInterface>(op);
3487 if (fmfOpInterface) {
3488 arith::FastMathFlagsAttr fmfAttr = fmfOpInterface.getFastMathFlagsAttr();
3489 useOnlyFiniteValue =
3490 bitEnumContainsAny(fmfAttr.getValue(), arith::FastMathFlags::ninf);
3491 if (*maybeKind == AtomicRMWKind::maxnumf ||
3492 *maybeKind == AtomicRMWKind::minnumf) {
3493 if (bitEnumContainsAny(fmfAttr.getValue(), arith::FastMathFlags::nnan)) {
3494 *maybeKind = *maybeKind == AtomicRMWKind::maxnumf
3495 ? AtomicRMWKind::maximumf
3496 : AtomicRMWKind::minimumf;
3497 } else {
3498 useOnlyFiniteValue = false;
3499 }
3500 }
3501 }
3502
3503 // Builder only used as helper for attribute creation.
3504 OpBuilder b(op->getContext());
3505 Type resultType = op->getResult(0).getType();
3506
3507 return getIdentityValueAttr(*maybeKind, resultType, b, op->getLoc(),
3508 useOnlyFiniteValue);
3509}
3510
3511/// Returns the identity value associated with an AtomicRMWKind op.
3512Value mlir::arith::getIdentityValue(AtomicRMWKind op, Type resultType,
3513 OpBuilder &builder, Location loc,
3514 bool useOnlyFiniteValue) {
3515 if (auto attr = getIdentityValueAttr(op, resultType, builder, loc,
3516 useOnlyFiniteValue))
3517 return arith::ConstantOp::create(builder, loc, attr);
3518 return {};
3519}
3520
3521/// Return the value obtained by applying the reduction operation kind
3522/// associated with a binary AtomicRMWKind op to `lhs` and `rhs`.
3524 Location loc, Value lhs, Value rhs) {
3525 switch (op) {
3526 case AtomicRMWKind::addf:
3527 return arith::AddFOp::create(builder, loc, lhs, rhs);
3528 case AtomicRMWKind::addi:
3529 return arith::AddIOp::create(builder, loc, lhs, rhs);
3530 case AtomicRMWKind::mulf:
3531 return arith::MulFOp::create(builder, loc, lhs, rhs);
3532 case AtomicRMWKind::muli:
3533 return arith::MulIOp::create(builder, loc, lhs, rhs);
3534 case AtomicRMWKind::maximumf:
3535 return arith::MaximumFOp::create(builder, loc, lhs, rhs);
3536 case AtomicRMWKind::minimumf:
3537 return arith::MinimumFOp::create(builder, loc, lhs, rhs);
3538 case AtomicRMWKind::maxnumf:
3539 return arith::MaxNumFOp::create(builder, loc, lhs, rhs);
3540 case AtomicRMWKind::minnumf:
3541 return arith::MinNumFOp::create(builder, loc, lhs, rhs);
3542 case AtomicRMWKind::maxs:
3543 return arith::MaxSIOp::create(builder, loc, lhs, rhs);
3544 case AtomicRMWKind::mins:
3545 return arith::MinSIOp::create(builder, loc, lhs, rhs);
3546 case AtomicRMWKind::maxu:
3547 return arith::MaxUIOp::create(builder, loc, lhs, rhs);
3548 case AtomicRMWKind::minu:
3549 return arith::MinUIOp::create(builder, loc, lhs, rhs);
3550 case AtomicRMWKind::ori:
3551 return arith::OrIOp::create(builder, loc, lhs, rhs);
3552 case AtomicRMWKind::andi:
3553 return arith::AndIOp::create(builder, loc, lhs, rhs);
3554 case AtomicRMWKind::xori:
3555 return arith::XOrIOp::create(builder, loc, lhs, rhs);
3556 // `assign` is not a reduction and has no corresponding binary operation.
3557 case AtomicRMWKind::assign:
3558 break;
3559 }
3560 (void)emitOptionalError(loc, "Reduction operation type not supported");
3561 return nullptr;
3562}
3563
3564//===----------------------------------------------------------------------===//
3565// TableGen'd op method definitions
3566//===----------------------------------------------------------------------===//
3567
3568#define GET_OP_CLASSES
3569#include "mlir/Dialect/Arith/IR/ArithOps.cpp.inc"
3570
3571//===----------------------------------------------------------------------===//
3572// TableGen'd enum attribute definitions
3573//===----------------------------------------------------------------------===//
3574
3575#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)
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
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:71
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:434
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:64
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:85
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:26
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:853
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
This represents an operation in an abstracted form, suitable for use with the builder APIs.