MLIR 23.0.0git
IntRangeOptimizations.cpp
Go to the documentation of this file.
1//===- IntRangeOptimizations.cpp - Optimizations based on integer ranges --===//
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 <utility>
10
11#include "llvm/ADT/TypeSwitch.h"
12
17
22#include "mlir/IR/IRMapping.h"
23#include "mlir/IR/Matchers.h"
30
31namespace mlir::arith {
32#define GEN_PASS_DEF_ARITHINTRANGEOPTS
33#include "mlir/Dialect/Arith/Transforms/Passes.h.inc"
34
35#define GEN_PASS_DEF_ARITHINTRANGENARROWING
36#include "mlir/Dialect/Arith/Transforms/Passes.h.inc"
37} // namespace mlir::arith
38
39using namespace mlir;
40using namespace mlir::arith;
41using namespace mlir::dataflow;
42
43static std::optional<APInt> getMaybeConstantValue(DataFlowSolver &solver,
44 Value value) {
45 auto *maybeInferredRange =
47 if (!maybeInferredRange || maybeInferredRange->getValue().isUninitialized())
48 return std::nullopt;
49 const ConstantIntRanges &inferredRange =
50 maybeInferredRange->getValue().getValue();
51 return inferredRange.getConstantValue();
52}
53
54static void copyIntegerRange(DataFlowSolver &solver, Value oldVal,
55 Value newVal) {
56 auto *oldState = solver.lookupState<IntegerValueRangeLattice>(oldVal);
57 if (!oldState)
58 return;
60 *oldState);
61}
62
63namespace mlir::dataflow {
64/// Patterned after SCCP
66 RewriterBase &rewriter, Value value) {
67 if (value.use_empty())
68 return failure();
69 std::optional<APInt> maybeConstValue = getMaybeConstantValue(solver, value);
70 if (!maybeConstValue.has_value())
71 return failure();
72
73 Type type = value.getType();
74 // If the type or element type is non-integral, the attribute constructor
75 // will crash, so eagerly check for an integer type to avoid this.
76 if (!getElementTypeOrSelf(type).isIntOrIndex())
77 return failure();
78
79 // Bail out if the inferred APInt bitwidth does not match the storage width
80 // of the IR type; IntegerAttr::get would assert otherwise.
81 unsigned storageWidth = ConstantIntRanges::getStorageBitwidth(type);
82 if (storageWidth != 0 && maybeConstValue->getBitWidth() != storageWidth)
83 return failure();
84
85 Location loc = value.getLoc();
86 Operation *maybeDefiningOp = value.getDefiningOp();
87 Dialect *valueDialect =
88 maybeDefiningOp ? maybeDefiningOp->getDialect()
90
91 Attribute constAttr;
92 if (auto shaped = dyn_cast<ShapedType>(type)) {
93 constAttr = mlir::DenseIntElementsAttr::get(shaped, *maybeConstValue);
94 } else {
95 constAttr = rewriter.getIntegerAttr(type, *maybeConstValue);
96 }
97 Operation *constOp =
98 valueDialect->materializeConstant(rewriter, constAttr, type, loc);
99 // Fall back to arith.constant if the dialect materializer doesn't know what
100 // to do with an integer constant.
101 if (!constOp)
102 constOp = rewriter.getContext()
103 ->getLoadedDialect<ArithDialect>()
104 ->materializeConstant(rewriter, constAttr, type, loc);
105 if (!constOp)
106 return failure();
107
108 OpResult res = constOp->getResult(0);
110 solver.eraseState(res);
111 copyIntegerRange(solver, value, res);
112 rewriter.replaceAllUsesWith(value, res);
113 return success();
114}
115} // namespace mlir::dataflow
116
117namespace {
118class DataFlowListener : public RewriterBase::Listener {
119public:
120 DataFlowListener(DataFlowSolver &s) : s(s) {}
121
122protected:
123 void notifyOperationErased(Operation *op) override {
124 s.eraseState(s.getProgramPointAfter(op));
125 for (Value res : op->getResults())
126 s.eraseState(res);
127 }
128
129 DataFlowSolver &s;
130};
131
132/// Rewrite any results of `op` that were inferred to be constant integers to
133/// and replace their uses with that constant. Return success() if all results
134/// where thus replaced and the operation is erased. Also replace any block
135/// arguments with their constant values.
136struct MaterializeKnownConstantValues : public RewritePattern {
137 MaterializeKnownConstantValues(MLIRContext *context, DataFlowSolver &s)
138 : RewritePattern::RewritePattern(Pattern::MatchAnyOpTypeTag(),
139 /*benefit=*/1, context),
140 solver(s) {}
141
142 LogicalResult matchAndRewrite(Operation *op,
143 PatternRewriter &rewriter) const override {
144 if (matchPattern(op, m_Constant()))
145 return failure();
146
147 // We need to check isIntOrIndex() and APInt bitwidth compatibility here
148 // as well to avoid infinite loops in the greedy pattern rewriter. If we
149 // only check in maybeReplaceWithConstant, this lambda might still return
150 // true for values that cannot be materialized, causing the pattern to
151 // match and claim success without making any changes, leading to
152 // non-convergence.
153 auto needsReplacing = [&](Value v) {
154 if (!getElementTypeOrSelf(v.getType()).isIntOrIndex())
155 return false;
156 std::optional<APInt> maybeConstValue = getMaybeConstantValue(solver, v);
157 if (!maybeConstValue.has_value() || v.use_empty())
158 return false;
159 unsigned storageWidth =
161 return storageWidth == 0 ||
162 maybeConstValue->getBitWidth() == storageWidth;
163 };
164 bool hasConstantResults = llvm::any_of(op->getResults(), needsReplacing);
165 if (op->getNumRegions() == 0)
166 if (!hasConstantResults)
167 return failure();
168 bool hasConstantRegionArgs = false;
169 for (Region &region : op->getRegions()) {
170 for (Block &block : region.getBlocks()) {
171 hasConstantRegionArgs |=
172 llvm::any_of(block.getArguments(), needsReplacing);
173 }
174 }
175 if (!hasConstantResults && !hasConstantRegionArgs)
176 return failure();
177
178 bool replacedAll = (op->getNumResults() != 0);
179 for (Value v : op->getResults())
180 replacedAll &=
181 (succeeded(maybeReplaceWithConstant(solver, rewriter, v)) ||
182 v.use_empty());
183 if (replacedAll && isOpTriviallyDead(op)) {
184 rewriter.eraseOp(op);
185 return success();
186 }
187
188 PatternRewriter::InsertionGuard guard(rewriter);
189 for (Region &region : op->getRegions()) {
190 for (Block &block : region.getBlocks()) {
191 rewriter.setInsertionPointToStart(&block);
192 for (BlockArgument &arg : block.getArguments()) {
193 (void)maybeReplaceWithConstant(solver, rewriter, arg);
194 }
195 }
196 }
197
198 return success();
199 }
200
201private:
202 DataFlowSolver &solver;
203};
204
205template <typename RemOp>
206struct DeleteTrivialRem : public OpRewritePattern<RemOp> {
207 DeleteTrivialRem(MLIRContext *context, DataFlowSolver &s)
208 : OpRewritePattern<RemOp>(context), solver(s) {}
209
210 LogicalResult matchAndRewrite(RemOp op,
211 PatternRewriter &rewriter) const override {
212 Value lhs = op.getOperand(0);
213 Value rhs = op.getOperand(1);
214 auto maybeModulus = getConstantIntValue(rhs);
215 if (!maybeModulus.has_value())
216 return failure();
217 int64_t modulus = *maybeModulus;
218 if (modulus <= 0)
219 return failure();
220 auto *maybeLhsRange = solver.lookupState<IntegerValueRangeLattice>(lhs);
221 if (!maybeLhsRange || maybeLhsRange->getValue().isUninitialized())
222 return failure();
223 const ConstantIntRanges &lhsRange = maybeLhsRange->getValue().getValue();
224 const APInt &min = isa<RemUIOp>(op) ? lhsRange.umin() : lhsRange.smin();
225 const APInt &max = isa<RemUIOp>(op) ? lhsRange.umax() : lhsRange.smax();
226 // The minima and maxima here are given as closed ranges, we must be
227 // strictly less than the modulus.
228 if (min.isNegative() || min.uge(modulus))
229 return failure();
230 if (max.isNegative() || max.uge(modulus))
231 return failure();
232 if (!min.ule(max))
233 return failure();
234
235 // With all those conditions out of the way, we know thas this invocation of
236 // a remainder is a noop because the input is strictly within the range
237 // [0, modulus), so get rid of it.
238 rewriter.replaceOp(op, ValueRange{lhs});
239 return success();
240 }
241
242private:
243 DataFlowSolver &solver;
244};
245
246/// Gather ranges for all the values in `values`. Appends to the existing
247/// vector.
248static LogicalResult collectRanges(DataFlowSolver &solver, ValueRange values,
250 for (Value val : values) {
251 auto *maybeInferredRange =
253 if (!maybeInferredRange || maybeInferredRange->getValue().isUninitialized())
254 return failure();
255
256 const ConstantIntRanges &inferredRange =
257 maybeInferredRange->getValue().getValue();
258 ranges.push_back(inferredRange);
259 }
260 return success();
261}
262
263/// Return int type truncated to `targetBitwidth`. If `srcType` is shaped,
264/// return shaped type as well.
265static Type getTargetType(Type srcType, unsigned targetBitwidth) {
266 auto dstType = IntegerType::get(srcType.getContext(), targetBitwidth);
267 if (auto shaped = dyn_cast<ShapedType>(srcType))
268 return shaped.clone(dstType);
269
270 assert(srcType.isIntOrIndex() && "Invalid src type");
271 return dstType;
272}
273
274namespace {
275// Enum for tracking which type of truncation should be performed
276// to narrow an operation, if any.
277enum class CastKind : uint8_t { None, Signed, Unsigned, Both };
278} // namespace
279
280/// If the values within `range` can be represented using only `width` bits,
281/// return the kind of truncation needed to preserve that property.
282///
283/// This check relies on the fact that the signed and unsigned ranges are both
284/// always correct, but that one might be an approximation of the other,
285/// so we want to use the correct truncation operation.
286static CastKind checkTruncatability(const ConstantIntRanges &range,
287 unsigned targetWidth) {
288 unsigned srcWidth = range.smin().getBitWidth();
289 if (srcWidth <= targetWidth)
290 return CastKind::None;
291 unsigned removedWidth = srcWidth - targetWidth;
292 // The sign bits need to extend into the sign bit of the target width. For
293 // example, if we're truncating 64 bits to 32, we need 64 - 32 + 1 = 33 sign
294 // bits.
295 bool canTruncateSigned =
296 range.smin().getNumSignBits() >= (removedWidth + 1) &&
297 range.smax().getNumSignBits() >= (removedWidth + 1);
298 bool canTruncateUnsigned = range.umin().countLeadingZeros() >= removedWidth &&
299 range.umax().countLeadingZeros() >= removedWidth;
300 if (canTruncateSigned && canTruncateUnsigned)
301 return CastKind::Both;
302 if (canTruncateSigned)
303 return CastKind::Signed;
304 if (canTruncateUnsigned)
305 return CastKind::Unsigned;
306 return CastKind::None;
307}
308
309static CastKind mergeCastKinds(CastKind lhs, CastKind rhs) {
310 if (lhs == CastKind::None || rhs == CastKind::None)
311 return CastKind::None;
312 if (lhs == CastKind::Both)
313 return rhs;
314 if (rhs == CastKind::Both)
315 return lhs;
316 if (lhs == rhs)
317 return lhs;
318 return CastKind::None;
319}
320
321static Value doCast(OpBuilder &builder, Location loc, Value src, Type dstType,
322 CastKind castKind) {
323 Type srcType = src.getType();
324 assert(isa<VectorType>(srcType) == isa<VectorType>(dstType) &&
325 "Mixing vector and non-vector types");
326 assert(castKind != CastKind::None && "Can't cast when casting isn't allowed");
327 Type srcElemType = getElementTypeOrSelf(srcType);
328 Type dstElemType = getElementTypeOrSelf(dstType);
329 assert(srcElemType.isIntOrIndex() && "Invalid src type");
330 assert(dstElemType.isIntOrIndex() && "Invalid dst type");
331 if (srcType == dstType)
332 return src;
333
334 if (isa<IndexType>(srcElemType) || isa<IndexType>(dstElemType)) {
335 if (castKind == CastKind::Signed)
336 return arith::IndexCastOp::create(builder, loc, dstType, src);
337 return arith::IndexCastUIOp::create(builder, loc, dstType, src);
338 }
340 auto srcInt = cast<IntegerType>(srcElemType);
341 auto dstInt = cast<IntegerType>(dstElemType);
342 if (dstInt.getWidth() < srcInt.getWidth())
343 return arith::TruncIOp::create(builder, loc, dstType, src);
345 if (castKind == CastKind::Signed)
346 return arith::ExtSIOp::create(builder, loc, dstType, src);
347 return arith::ExtUIOp::create(builder, loc, dstType, src);
348}
349
350struct NarrowElementwise final : OpTraitRewritePattern<OpTrait::Elementwise> {
351 NarrowElementwise(MLIRContext *context, DataFlowSolver &s,
353 : OpTraitRewritePattern(context), solver(s), targetBitwidths(target) {}
354
356 LogicalResult matchAndRewrite(Operation *op,
357 PatternRewriter &rewriter) const override {
358 if (op->getNumResults() == 0)
359 return rewriter.notifyMatchFailure(op, "can't narrow resultless op");
360
361 // Inline size chosen empirically based on compilation profiling.
362 // Profiled: 2.6M calls, avg=1.7+-1.3. N=4 covers >95% of cases inline.
364 if (failed(collectRanges(solver, op->getOperands(), ranges)))
365 return rewriter.notifyMatchFailure(op, "input without specified range");
366 if (failed(collectRanges(solver, op->getResults(), ranges)))
367 return rewriter.notifyMatchFailure(op, "output without specified range");
368
369 Type srcType = op->getResult(0).getType();
370 if (!llvm::all_equal(op->getResultTypes()))
371 return rewriter.notifyMatchFailure(op, "mismatched result types");
372 if (op->getNumOperands() == 0 ||
373 !llvm::all_of(op->getOperandTypes(),
374 [=](Type t) { return t == srcType; }))
375 return rewriter.notifyMatchFailure(
376 op, "no operands or operand types don't match result type");
377
378 for (unsigned targetBitwidth : targetBitwidths) {
379 CastKind castKind = CastKind::Both;
380 for (const ConstantIntRanges &range : ranges) {
381 castKind = mergeCastKinds(castKind,
382 checkTruncatability(range, targetBitwidth));
383 if (castKind == CastKind::None)
384 break;
385 }
386 // For operations that explicitly treat the values as signed, we should
387 // only do signed casts, if those are deemed possible as such based on the
388 // value range.
389 auto castKindForOp =
391 .Case<arith::DivSIOp, arith::CeilDivSIOp, arith::FloorDivSIOp,
392 arith::RemSIOp, arith::MaxSIOp, arith::MinSIOp,
393 arith::ShRSIOp>([](auto) { return CastKind::Signed; })
394 .Default(CastKind::Both);
395 castKind = mergeCastKinds(castKind, castKindForOp);
396 if (castKind == CastKind::None)
397 continue;
398 Type targetType = getTargetType(srcType, targetBitwidth);
399 if (targetType == srcType)
400 continue;
402 Location loc = op->getLoc();
403 IRMapping mapping;
404 for (auto [arg, argRange] : llvm::zip_first(op->getOperands(), ranges)) {
405 CastKind argCastKind = castKind;
406 // When dealing with `index` values, preserve non-negativity in the
407 // index_casts since we can't recover this in unsigned when equivalent.
408 if (argCastKind == CastKind::Signed && argRange.smin().isNonNegative())
409 argCastKind = CastKind::Both;
410 Value newArg = doCast(rewriter, loc, arg, targetType, argCastKind);
411 mapping.map(arg, newArg);
412 }
413
414 Operation *newOp = rewriter.clone(*op, mapping);
415 rewriter.modifyOpInPlace(newOp, [&]() {
416 for (OpResult res : newOp->getResults()) {
417 res.setType(targetType);
418 }
419 });
420 SmallVector<Value> newResults;
421 for (auto [newRes, oldRes] :
422 llvm::zip_equal(newOp->getResults(), op->getResults())) {
423 Value castBack = doCast(rewriter, loc, newRes, srcType, castKind);
424 copyIntegerRange(solver, oldRes, castBack);
425 newResults.push_back(castBack);
428 rewriter.replaceOp(op, newResults);
429 return success();
431 return failure();
432 }
433
434private:
435 DataFlowSolver &solver;
436 SmallVector<unsigned, 4> targetBitwidths;
438
439struct NarrowCmpI final : OpRewritePattern<arith::CmpIOp> {
440 NarrowCmpI(MLIRContext *context, DataFlowSolver &s, ArrayRef<unsigned> target)
441 : OpRewritePattern(context), solver(s), targetBitwidths(target) {}
443 LogicalResult matchAndRewrite(arith::CmpIOp op,
444 PatternRewriter &rewriter) const override {
445 Value lhs = op.getLhs();
446 Value rhs = op.getRhs();
447
449 if (failed(collectRanges(solver, op.getOperands(), ranges)))
450 return failure();
451 const ConstantIntRanges &lhsRange = ranges[0];
452 const ConstantIntRanges &rhsRange = ranges[1];
454 auto isSignedCmpPredicate = [](arith::CmpIPredicate pred) -> bool {
455 return pred == arith::CmpIPredicate::sge ||
456 pred == arith::CmpIPredicate::sgt ||
457 pred == arith::CmpIPredicate::sle ||
458 pred == arith::CmpIPredicate::slt;
459 };
460 // If we're to narrow the input values via a cast, we should preserve the
461 // sign.
462 CastKind predicateBasedCastRestriction =
463 isSignedCmpPredicate(op.getPredicate()) ? CastKind::Signed
464 : CastKind::Both;
465
466 Type srcType = lhs.getType();
467 for (unsigned targetBitwidth : targetBitwidths) {
468 CastKind lhsCastKind = checkTruncatability(lhsRange, targetBitwidth);
469 CastKind rhsCastKind = checkTruncatability(rhsRange, targetBitwidth);
470 CastKind castKind = mergeCastKinds(lhsCastKind, rhsCastKind);
471 castKind = mergeCastKinds(castKind, predicateBasedCastRestriction);
472 // Note: this includes target width > src width, as well as the unsigned
473 // truncatability & signed predicate scenario.
474 if (castKind == CastKind::None)
475 continue;
477 Type targetType = getTargetType(srcType, targetBitwidth);
478 if (targetType == srcType)
479 continue;
480
481 Location loc = op->getLoc();
482 IRMapping mapping;
483 Value lhsCast = doCast(rewriter, loc, lhs, targetType, lhsCastKind);
484 Value rhsCast = doCast(rewriter, loc, rhs, targetType, rhsCastKind);
485 mapping.map(lhs, lhsCast);
486 mapping.map(rhs, rhsCast);
487
488 Operation *newOp = rewriter.clone(*op, mapping);
489 copyIntegerRange(solver, op.getResult(), newOp->getResult(0));
490 rewriter.replaceOp(op, newOp->getResults());
491 return success();
492 }
493 return failure();
494 }
495
496private:
497 DataFlowSolver &solver;
498 SmallVector<unsigned, 4> targetBitwidths;
499};
500
501/// Fold index_cast(index_cast(%arg: i8, index), i8) -> %arg
502/// This pattern assumes all passed `targetBitwidths` are not wider than index
503/// type.
504template <typename CastOp>
505struct FoldIndexCastChain final : OpRewritePattern<CastOp> {
506 FoldIndexCastChain(MLIRContext *context, ArrayRef<unsigned> target)
507 : OpRewritePattern<CastOp>(context), targetBitwidths(target) {}
508
509 LogicalResult matchAndRewrite(CastOp op,
510 PatternRewriter &rewriter) const override {
511 auto srcOp = op.getIn().template getDefiningOp<CastOp>();
512 if (!srcOp)
513 return rewriter.notifyMatchFailure(op, "doesn't come from an index cast");
514
515 Value src = srcOp.getIn();
516 if (src.getType() != op.getType())
517 return rewriter.notifyMatchFailure(op, "outer types don't match");
518
519 if (!srcOp.getType().isIndex())
520 return rewriter.notifyMatchFailure(op, "intermediate type isn't index");
521
522 auto intType = dyn_cast<IntegerType>(op.getType());
523 if (!intType || !llvm::is_contained(targetBitwidths, intType.getWidth()))
524 return failure();
525
526 rewriter.replaceOp(op, src);
527 return success();
528 }
529
530private:
531 SmallVector<unsigned, 4> targetBitwidths;
532};
533
534struct NarrowLoopBounds final : OpInterfaceRewritePattern<LoopLikeOpInterface> {
535 NarrowLoopBounds(MLIRContext *context, DataFlowSolver &s,
536 ArrayRef<unsigned> target)
537 : OpInterfaceRewritePattern<LoopLikeOpInterface>(context), solver(s),
538 targetBitwidths(target),
539 boundsNarrowingFailedAttr(
540 StringAttr::get(context, "arith.bounds_narrowing_failed")) {}
541
542 LogicalResult matchAndRewrite(LoopLikeOpInterface loopLike,
543 PatternRewriter &rewriter) const override {
544 // Skip ops where bounds narrowing previously failed.
545 if (loopLike->hasAttr(boundsNarrowingFailedAttr))
546 return rewriter.notifyMatchFailure(loopLike,
547 "bounds narrowing previously failed");
548
549 std::optional<SmallVector<Value>> inductionVars =
550 loopLike.getLoopInductionVars();
551 if (!inductionVars.has_value() || inductionVars->empty())
552 return rewriter.notifyMatchFailure(loopLike, "no induction variables");
553
554 std::optional<SmallVector<OpFoldResult>> lowerBounds =
555 loopLike.getLoopLowerBounds();
556 std::optional<SmallVector<OpFoldResult>> upperBounds =
557 loopLike.getLoopUpperBounds();
558 std::optional<SmallVector<OpFoldResult>> steps = loopLike.getLoopSteps();
559
560 if (!lowerBounds.has_value() || !upperBounds.has_value() ||
561 !steps.has_value())
562 return rewriter.notifyMatchFailure(loopLike, "no loop bounds or steps");
563
564 if (lowerBounds->size() != inductionVars->size() ||
565 upperBounds->size() != inductionVars->size() ||
566 steps->size() != inductionVars->size())
567 return rewriter.notifyMatchFailure(loopLike,
568 "mismatched bounds/steps count");
569
570 Location loc = loopLike->getLoc();
571 SmallVector<OpFoldResult> newLowerBounds(*lowerBounds);
572 SmallVector<OpFoldResult> newUpperBounds(*upperBounds);
573 SmallVector<OpFoldResult> newSteps(*steps);
574 SmallVector<std::tuple<size_t, Type, CastKind>> narrowings;
575
576 // Check each (indVar, lb, ub, step) tuple.
577 for (auto [idx, indVar, lbOFR, ubOFR, stepOFR] :
578 llvm::enumerate(*inductionVars, *lowerBounds, *upperBounds, *steps)) {
579
580 // Only process value operands, skip attributes.
581 auto maybeLb = dyn_cast<Value>(lbOFR);
582 auto maybeUb = dyn_cast<Value>(ubOFR);
583 auto maybeStep = dyn_cast<Value>(stepOFR);
584
585 if (!maybeLb || !maybeUb || !maybeStep)
586 continue;
587
588 // Collect ranges for (lb, ub, step, indVar).
589 SmallVector<ConstantIntRanges> ranges;
590 if (failed(collectRanges(
591 solver, ValueRange{maybeLb, maybeUb, maybeStep, indVar}, ranges)))
592 continue;
593
594 const ConstantIntRanges &stepRange = ranges[2];
595 const ConstantIntRanges &indVarRange = ranges[3];
596
597 Type srcType = maybeLb.getType();
598
599 // Try each target bitwidth.
600 for (unsigned targetBitwidth : targetBitwidths) {
601 Type targetType = getTargetType(srcType, targetBitwidth);
602 if (targetType == srcType)
603 continue;
604
605 // Check if the target type is valid for this loop's induction
606 // variables.
607 if (!loopLike.isValidInductionVarType(targetType))
608 continue;
609
610 // Check if all values in this tuple can be truncated.
611 CastKind castKind = CastKind::Both;
612 for (const ConstantIntRanges &range : ranges) {
613 castKind = mergeCastKinds(castKind,
614 checkTruncatability(range, targetBitwidth));
615 if (castKind == CastKind::None)
616 break;
617 }
618
619 if (castKind == CastKind::None)
620 continue;
621
622 // Check if indVar + step fits in the narrowed type.
623 // This is critical for loop correctness: the loop computes
624 // iv_next = iv_current + step in the narrowed type, then compares
625 // iv_next < ub. If iv_current + step overflows, the comparison may
626 // produce incorrect results and break loop termination.
627 // Both signed and unsigned interpretations must fit because loop
628 // semantics are unknown (integer types are signless).
629 ConstantIntRanges indVarPlusStepRange(
630 indVarRange.smin().sadd_sat(stepRange.smin()),
631 indVarRange.smax().sadd_sat(stepRange.smax()),
632 indVarRange.umin().uadd_sat(stepRange.umin()),
633 indVarRange.umax().uadd_sat(stepRange.umax()));
634
635 if (checkTruncatability(indVarPlusStepRange, targetBitwidth) !=
636 CastKind::Both)
637 continue;
638
639 // Narrow the bounds and step values.
640 Value newLb = doCast(rewriter, loc, maybeLb, targetType, castKind);
641 Value newUb = doCast(rewriter, loc, maybeUb, targetType, castKind);
642 Value newStep = doCast(rewriter, loc, maybeStep, targetType, castKind);
643
644 newLowerBounds[idx] = newLb;
645 newUpperBounds[idx] = newUb;
646 newSteps[idx] = newStep;
647 narrowings.push_back({idx, targetType, castKind});
648 break;
649 }
650 }
651
652 if (narrowings.empty())
653 return rewriter.notifyMatchFailure(loopLike, "no narrowings found");
654
655 // Save original types before modifying.
656 SmallVector<Type> origTypes;
657 for (auto [idx, targetType, castKind] : narrowings) {
658 Value indVar = (*inductionVars)[idx];
659 origTypes.push_back(indVar.getType());
660 }
661
662 // Attempt to update bounds and induction variable types.
663 // If this fails, mark the op so we don't try again.
664 bool updateFailed = false;
665 rewriter.modifyOpInPlace(loopLike, [&]() {
666 // Update the loop bounds and steps.
667 if (failed(loopLike.setLoopLowerBounds(newLowerBounds)) ||
668 failed(loopLike.setLoopUpperBounds(newUpperBounds)) ||
669 failed(loopLike.setLoopSteps(newSteps))) {
670 // Mark op to prevent future attempts. IR was modified (attribute
671 // added), so we must return success() from the pattern.
672 loopLike->setAttr(boundsNarrowingFailedAttr, rewriter.getUnitAttr());
673 updateFailed = true;
674 return;
675 }
676
677 // Update induction variable types.
678 for (auto [idx, targetType, castKind] : narrowings) {
679 Value indVar = (*inductionVars)[idx];
680 auto blockArg = cast<BlockArgument>(indVar);
681
682 // Change the block argument type.
683 blockArg.setType(targetType);
684 }
685 });
686
687 if (updateFailed)
688 return success();
689
690 // Insert casts back to original type for uses.
691 for (auto [narrowingIdx, narrowingInfo] : llvm::enumerate(narrowings)) {
692 auto [idx, targetType, castKind] = narrowingInfo;
693 Value indVar = (*inductionVars)[idx];
694 auto blockArg = cast<BlockArgument>(indVar);
695 Type origType = origTypes[narrowingIdx];
696
697 OpBuilder::InsertionGuard guard(rewriter);
698 rewriter.setInsertionPointToStart(blockArg.getOwner());
699 Value casted = doCast(rewriter, loc, blockArg, origType, castKind);
700 copyIntegerRange(solver, blockArg, casted);
701
702 // Replace all uses of the narrowed indVar with the casted value.
703 rewriter.replaceAllUsesExcept(blockArg, casted, casted.getDefiningOp());
704 }
705
706 return success();
707 }
708
709private:
710 DataFlowSolver &solver;
711 SmallVector<unsigned, 4> targetBitwidths;
712 StringAttr boundsNarrowingFailedAttr;
713};
714
715struct IntRangeOptimizationsPass final
716 : arith::impl::ArithIntRangeOptsBase<IntRangeOptimizationsPass> {
717
718 void runOnOperation() override {
719 Operation *op = getOperation();
720 MLIRContext *ctx = op->getContext();
721 DataFlowSolver solver;
722 loadBaselineAnalyses(solver);
723 solver.load<IntegerRangeAnalysis>();
724 if (failed(solver.initializeAndRun(op)))
725 return signalPassFailure();
726
727 DataFlowListener listener(solver);
728
729 RewritePatternSet patterns(ctx);
731
732 // Disable folding and region simplification to avoid breaking the solver
733 // state. Both can remove block arguments (folding via control-flow
734 // simplification, region simplification via dead-arg elimination), which
735 // frees their underlying storage. A subsequent allocation may reuse the
736 // same address for a different block argument, causing stale solver state
737 // to be associated with the new argument and producing incorrect constants.
738 if (failed(
739 applyPatternsGreedily(op, std::move(patterns),
740 GreedyRewriteConfig()
741 .enableFolding(false)
742 .setRegionSimplificationLevel(
743 GreedySimplifyRegionLevel::Disabled)
744 .setListener(&listener))))
745 signalPassFailure();
746 }
747};
748
749struct IntRangeNarrowingPass final
750 : arith::impl::ArithIntRangeNarrowingBase<IntRangeNarrowingPass> {
751 using ArithIntRangeNarrowingBase::ArithIntRangeNarrowingBase;
752
753 void runOnOperation() override {
754 Operation *op = getOperation();
755 MLIRContext *ctx = op->getContext();
756 DataFlowSolver solver;
757 loadBaselineAnalyses(solver);
758 solver.load<IntegerRangeAnalysis>();
759 if (failed(solver.initializeAndRun(op)))
760 return signalPassFailure();
761
762 DataFlowListener listener(solver);
763
764 RewritePatternSet patterns(ctx);
765 populateIntRangeNarrowingPatterns(patterns, solver, bitwidthsSupported);
767 bitwidthsSupported);
768
769 // We specifically need bottom-up traversal as cmpi pattern needs range
770 // data, attached to its original argument values.
772 op, std::move(patterns),
773 GreedyRewriteConfig().setUseTopDownTraversal(false).setListener(
774 &listener))))
775 signalPassFailure();
776 }
777};
778} // namespace
779
781 RewritePatternSet &patterns, DataFlowSolver &solver) {
782 patterns.add<MaterializeKnownConstantValues, DeleteTrivialRem<RemSIOp>,
783 DeleteTrivialRem<RemUIOp>>(patterns.getContext(), solver);
784}
785
787 RewritePatternSet &patterns, DataFlowSolver &solver,
788 ArrayRef<unsigned> bitwidthsSupported) {
789 patterns.add<NarrowElementwise, NarrowCmpI>(patterns.getContext(), solver,
790 bitwidthsSupported);
791 patterns.add<FoldIndexCastChain<arith::IndexCastUIOp>,
792 FoldIndexCastChain<arith::IndexCastOp>>(patterns.getContext(),
793 bitwidthsSupported);
794}
795
797 RewritePatternSet &patterns, DataFlowSolver &solver,
798 ArrayRef<unsigned> bitwidthsSupported) {
799 patterns.add<NarrowLoopBounds>(patterns.getContext(), solver,
800 bitwidthsSupported);
801}
802
804 return std::make_unique<IntRangeOptimizationsPass>();
805}
return success()
static Operation * materializeConstant(Dialect *dialect, OpBuilder &builder, Attribute value, Type type, Location loc)
A utility function used to materialize a constant for a given attribute and type.
Definition FoldUtils.cpp:51
lhs
static void copyIntegerRange(DataFlowSolver &solver, Value oldVal, Value newVal)
static std::optional< APInt > getMaybeConstantValue(DataFlowSolver &solver, Value value)
@ None
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
Attributes are known-constant values of operations.
Definition Attributes.h:25
UnitAttr getUnitAttr()
Definition Builders.cpp:102
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:233
MLIRContext * getContext() const
Definition Builders.h:56
A set of arbitrary-precision integers representing bounds on a given integer value.
const APInt & smax() const
The maximum value of an integer when it is interpreted as signed.
const APInt & smin() const
The minimum value of an integer when it is interpreted as signed.
static unsigned getStorageBitwidth(Type type)
Return the bitwidth that should be used for integer ranges describing type.
std::optional< APInt > getConstantValue() const
If either the signed or unsigned interpretations of the range indicate that the value it bounds is a ...
const APInt & umax() const
The maximum value of an integer when it is interpreted as unsigned.
const APInt & umin() const
The minimum value of an integer when it is interpreted as unsigned.
The general data-flow analysis solver.
LogicalResult initializeAndRun(Operation *top, llvm::function_ref< bool(DataFlowAnalysis &)> analysisFilter=nullptr)
Initialize analyses starting from the provided top-level operation and run the analysis until fixpoin...
void eraseState(AnchorT anchor)
Erase any analysis state associated with the given lattice anchor.
const StateT * lookupState(AnchorT anchor) const
Lookup an analysis state for the given lattice anchor.
StateT * getOrCreateState(AnchorT anchor)
Get the state associated with the given lattice anchor.
AnalysisT * load(Args &&...args)
Load an analysis into the solver. Return the analysis instance.
static DenseIntElementsAttr get(const ShapedType &type, Arg &&arg)
Get an instance of a DenseIntElementsAttr with the given arguments.
Dialects are groups of MLIR operations, types and attributes, as well as behavior associated with the...
Definition Dialect.h:38
virtual Operation * materializeConstant(OpBuilder &builder, Attribute value, Type type, Location loc)
Registered hook to materialize a single constant operation from a given attribute value with the desi...
Definition Dialect.h:83
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
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:63
Dialect * getLoadedDialect(StringRef name)
Get a registered IR dialect with the given namespace.
This class helps build Operations.
Definition Builders.h:209
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:567
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:433
This is a value defined by a result of an operation.
Definition Value.h:454
OpTraitRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting again...
OpTraitRewritePattern(MLIRContext *context, PatternBenefit benefit=1)
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Definition Operation.h:237
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:699
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
unsigned getNumOperands()
Definition Operation.h:371
operand_type_range getOperandTypes()
Definition Operation.h:422
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:702
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:213
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
RewritePattern is the common base class for all DAG to DAG replacements.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void replaceAllUsesExcept(Value from, Value to, Operation *exceptedUser)
Find uses of from and replace them with to except if the user is exceptedUser.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
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 isIntOrIndex() const
Return true if this is an integer (of any signedness) or an index type.
Definition Types.cpp:114
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
This lattice element represents the integer value range of an SSA value.
ChangeResult join(const AbstractSparseLattice &rhs) override
Join the information contained in 'rhs' into this lattice.
std::unique_ptr< Pass > createIntRangeOptimizationsPass()
Create a pass which do optimizations based on integer range analysis.
void populateControlFlowValuesNarrowingPatterns(RewritePatternSet &patterns, DataFlowSolver &solver, ArrayRef< unsigned > bitwidthsSupported)
Add patterns for narrowing control flow values (loop bounds, steps, etc.) based on int range analysis...
void populateIntRangeOptimizationsPatterns(RewritePatternSet &patterns, DataFlowSolver &solver)
Add patterns for int range based optimizations.
void populateIntRangeNarrowingPatterns(RewritePatternSet &patterns, DataFlowSolver &solver, ArrayRef< unsigned > bitwidthsSupported)
Add patterns for int range based narrowing.
LogicalResult maybeReplaceWithConstant(DataFlowSolver &solver, RewriterBase &rewriter, Value value)
Patterned after SCCP.
void loadBaselineAnalyses(DataFlowSolver &solver)
Populates a DataFlowSolver with analyses that are required to ensure user-defined analyses are run pr...
Definition Utils.h:29
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
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
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
bool isOpTriviallyDead(Operation *op)
Return true if the given operation is unused, and has no side effects on memory that prevent erasing.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
OpInterfaceRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting a...
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...