MLIR 24.0.0git
InferIntRangeCommon.cpp
Go to the documentation of this file.
1//===- InferIntRangeCommon.cpp - Inference for common ops ------------===//
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// This file contains implementations of range inference for operations that are
10// common to both the `arith` and `index` dialects to facilitate reuse.
11//
12//===----------------------------------------------------------------------===//
13
15
16#include "mlir/IR/AffineExpr.h"
19
20#include "llvm/ADT/ArrayRef.h"
21#include "llvm/ADT/STLExtras.h"
22
23#include "llvm/Support/Debug.h"
24
25#include <array>
26#include <iterator>
27#include <optional>
28#include <utility>
29
30using namespace mlir;
31
32#define DEBUG_TYPE "int-range-analysis"
33
34//===----------------------------------------------------------------------===//
35// General utilities
36//===----------------------------------------------------------------------===//
37
38/// Function that evaluates the result of doing something on arithmetic
39/// constants and returns std::nullopt on overflow.
41 function_ref<std::optional<APInt>(const APInt &, const APInt &)>;
43 std::function<std::optional<APInt>(const APInt &, const APInt &)>;
44
45/// Compute op(minLeft, minRight) and op(maxLeft, maxRight) if possible,
46/// If either computation overflows, make the result unbounded.
47static ConstantIntRanges computeBoundsBy(ConstArithFn op, const APInt &minLeft,
48 const APInt &minRight,
49 const APInt &maxLeft,
50 const APInt &maxRight, bool isSigned) {
51 std::optional<APInt> maybeMin = op(minLeft, minRight);
52 std::optional<APInt> maybeMax = op(maxLeft, maxRight);
53 if (maybeMin && maybeMax)
54 return ConstantIntRanges::range(*maybeMin, *maybeMax, isSigned);
55 return ConstantIntRanges::maxRange(minLeft.getBitWidth());
56}
57
58/// Compute the minimum and maximum of `(op(l, r) for l in lhs for r in rhs)`,
59/// ignoring unbounded values. Returns the maximal range if `op` overflows.
61 ArrayRef<APInt> rhs, bool isSigned) {
62 unsigned width = lhs[0].getBitWidth();
63 APInt min =
64 isSigned ? APInt::getSignedMaxValue(width) : APInt::getMaxValue(width);
65 APInt max =
66 isSigned ? APInt::getSignedMinValue(width) : APInt::getZero(width);
67 for (const APInt &left : lhs) {
68 for (const APInt &right : rhs) {
69 std::optional<APInt> maybeThisResult = op(left, right);
70 if (!maybeThisResult)
71 return ConstantIntRanges::maxRange(width);
72 APInt result = std::move(*maybeThisResult);
73 min = (isSigned ? result.slt(min) : result.ult(min)) ? result : min;
74 max = (isSigned ? result.sgt(max) : result.ugt(max)) ? result : max;
75 }
76 }
77 return ConstantIntRanges::range(min, max, isSigned);
78}
79
80//===----------------------------------------------------------------------===//
81// Ext, trunc, index op handling
82//===----------------------------------------------------------------------===//
83
87 intrange::CmpMode mode) {
88 ConstantIntRanges sixtyFour = inferFn(argRanges);
90 llvm::transform(argRanges, std::back_inserter(truncated),
91 [](const ConstantIntRanges &range) {
92 return truncRange(range, /*destWidth=*/indexMinWidth);
93 });
94 ConstantIntRanges thirtyTwo = inferFn(truncated);
95 ConstantIntRanges thirtyTwoAsSixtyFour =
96 extRange(thirtyTwo, /*destWidth=*/indexMaxWidth);
97 ConstantIntRanges sixtyFourAsThirtyTwo =
98 truncRange(sixtyFour, /*destWidth=*/indexMinWidth);
99
100 LLVM_DEBUG(llvm::dbgs() << "Index handling: 64-bit result = " << sixtyFour
101 << " 32-bit = " << thirtyTwo << "\n");
102 bool truncEqual = false;
103 switch (mode) {
105 truncEqual = (thirtyTwo == sixtyFourAsThirtyTwo);
106 break;
108 truncEqual = (thirtyTwo.smin() == sixtyFourAsThirtyTwo.smin() &&
109 thirtyTwo.smax() == sixtyFourAsThirtyTwo.smax());
110 break;
112 truncEqual = (thirtyTwo.umin() == sixtyFourAsThirtyTwo.umin() &&
113 thirtyTwo.umax() == sixtyFourAsThirtyTwo.umax());
114 break;
115 }
116 if (truncEqual)
117 // Returing the 64-bit result preserves more information.
118 return sixtyFour;
119 ConstantIntRanges merged = sixtyFour.rangeUnion(thirtyTwoAsSixtyFour);
120 return merged;
121}
122
124 unsigned int destWidth) {
125 APInt umin = range.umin().zext(destWidth);
126 APInt umax = range.umax().zext(destWidth);
127 APInt smin = range.smin().sext(destWidth);
128 APInt smax = range.smax().sext(destWidth);
129 return {umin, umax, smin, smax};
130}
131
133 unsigned destWidth) {
134 APInt umin = range.umin().zext(destWidth);
135 APInt umax = range.umax().zext(destWidth);
136 return ConstantIntRanges::fromUnsigned(umin, umax);
137}
138
140 unsigned destWidth) {
141 APInt smin = range.smin().sext(destWidth);
142 APInt smax = range.smax().sext(destWidth);
143 return ConstantIntRanges::fromSigned(smin, smax);
144}
145
147 unsigned int destWidth) {
148 // If you truncate the first four bytes in [0xaaaabbbb, 0xccccbbbb],
149 // the range of the resulting value is not contiguous ind includes 0.
150 // Ex. If you truncate [256, 258] from i16 to i8, you validly get [0, 2],
151 // but you can't truncate [255, 257] similarly.
152 bool hasUnsignedRollover =
153 range.umin().lshr(destWidth) != range.umax().lshr(destWidth);
154 APInt umin = hasUnsignedRollover ? APInt::getZero(destWidth)
155 : range.umin().trunc(destWidth);
156 APInt umax = hasUnsignedRollover ? APInt::getMaxValue(destWidth)
157 : range.umax().trunc(destWidth);
158
159 // Signed post-truncation rollover will not occur when either:
160 // - The high parts of the min and max, plus the sign bit, are the same
161 // - The high halves + sign bit of the min and max are either all 1s or all 0s
162 // and you won't create a [positive, negative] range by truncating.
163 // For example, you can truncate the ranges [256, 258]_i16 to [0, 2]_i8
164 // but not [255, 257]_i16 to a range of i8s. You can also truncate
165 // [-256, -256]_i16 to [-2, 0]_i8, but not [-257, -255]_i16.
166 // You can also truncate [-130, 0]_i16 to i8 because -130_i16 (0xff7e)
167 // will truncate to 0x7e, which is greater than 0
168 APInt sminHighPart = range.smin().ashr(destWidth - 1);
169 APInt smaxHighPart = range.smax().ashr(destWidth - 1);
170 bool hasSignedOverflow =
171 (sminHighPart != smaxHighPart) &&
172 !(sminHighPart.isAllOnes() &&
173 (smaxHighPart.isAllOnes() || smaxHighPart.isZero())) &&
174 !(sminHighPart.isZero() && smaxHighPart.isZero());
175 APInt smin = hasSignedOverflow ? APInt::getSignedMinValue(destWidth)
176 : range.smin().trunc(destWidth);
177 APInt smax = hasSignedOverflow ? APInt::getSignedMaxValue(destWidth)
178 : range.smax().trunc(destWidth);
179 return {umin, umax, smin, smax};
180}
181
182//===----------------------------------------------------------------------===//
183// Addition
184//===----------------------------------------------------------------------===//
185
188 OverflowFlags ovfFlags) {
189 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
190
191 ConstArithStdFn uadd = [=](const APInt &a,
192 const APInt &b) -> std::optional<APInt> {
193 bool overflowed = false;
194 APInt result = any(ovfFlags & OverflowFlags::Nuw)
195 ? a.uadd_sat(b)
196 : a.uadd_ov(b, overflowed);
197 return overflowed ? std::optional<APInt>() : result;
198 };
199 ConstArithStdFn sadd = [=](const APInt &a,
200 const APInt &b) -> std::optional<APInt> {
201 bool overflowed = false;
202 APInt result = any(ovfFlags & OverflowFlags::Nsw)
203 ? a.sadd_sat(b)
204 : a.sadd_ov(b, overflowed);
205 return overflowed ? std::optional<APInt>() : result;
206 };
207
209 uadd, lhs.umin(), rhs.umin(), lhs.umax(), rhs.umax(), /*isSigned=*/false);
211 sadd, lhs.smin(), rhs.smin(), lhs.smax(), rhs.smax(), /*isSigned=*/true);
212 return urange.intersection(srange);
213}
214
215//===----------------------------------------------------------------------===//
216// Subtraction
217//===----------------------------------------------------------------------===//
218
221 OverflowFlags ovfFlags) {
222 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
223
224 ConstArithStdFn usub = [=](const APInt &a,
225 const APInt &b) -> std::optional<APInt> {
226 bool overflowed = false;
227 APInt result = any(ovfFlags & OverflowFlags::Nuw)
228 ? a.usub_sat(b)
229 : a.usub_ov(b, overflowed);
230 return overflowed ? std::optional<APInt>() : result;
231 };
232 ConstArithStdFn ssub = [=](const APInt &a,
233 const APInt &b) -> std::optional<APInt> {
234 bool overflowed = false;
235 APInt result = any(ovfFlags & OverflowFlags::Nsw)
236 ? a.ssub_sat(b)
237 : a.ssub_ov(b, overflowed);
238 return overflowed ? std::optional<APInt>() : result;
239 };
241 usub, lhs.umin(), rhs.umax(), lhs.umax(), rhs.umin(), /*isSigned=*/false);
243 ssub, lhs.smin(), rhs.smax(), lhs.smax(), rhs.smin(), /*isSigned=*/true);
244 return urange.intersection(srange);
245}
246
247//===----------------------------------------------------------------------===//
248// Multiplication
249//===----------------------------------------------------------------------===//
250
253 OverflowFlags ovfFlags) {
254 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
255
256 ConstArithStdFn umul = [=](const APInt &a,
257 const APInt &b) -> std::optional<APInt> {
258 bool overflowed = false;
259 APInt result = any(ovfFlags & OverflowFlags::Nuw)
260 ? a.umul_sat(b)
261 : a.umul_ov(b, overflowed);
262 return overflowed ? std::optional<APInt>() : result;
263 };
264 ConstArithStdFn smul = [=](const APInt &a,
265 const APInt &b) -> std::optional<APInt> {
266 bool overflowed = false;
267 APInt result = any(ovfFlags & OverflowFlags::Nsw)
268 ? a.smul_sat(b)
269 : a.smul_ov(b, overflowed);
270 return overflowed ? std::optional<APInt>() : result;
271 };
272
273 ConstantIntRanges urange =
274 minMaxBy(umul, {lhs.umin(), lhs.umax()}, {rhs.umin(), rhs.umax()},
275 /*isSigned=*/false);
276 ConstantIntRanges srange =
277 minMaxBy(smul, {lhs.smin(), lhs.smax()}, {rhs.smin(), rhs.smax()},
278 /*isSigned=*/true);
279 return urange.intersection(srange);
280}
281
282//===----------------------------------------------------------------------===//
283// DivU, CeilDivU (Unsigned division)
284//===----------------------------------------------------------------------===//
285
286/// Fix up division results (ex. for ceiling and floor), returning an APInt
287/// if there has been no overflow
289 const APInt &lhs, const APInt &rhs, const APInt &result)>;
290
292 const ConstantIntRanges &rhs,
293 DivisionFixupFn fixup) {
294 const APInt &lhsMin = lhs.umin(), &lhsMax = lhs.umax(), &rhsMin = rhs.umin(),
295 &rhsMax = rhs.umax();
296 if (!rhsMin.isZero() && !rhsMax.isZero()) {
297 auto udiv = [&fixup](const APInt &a,
298 const APInt &b) -> std::optional<APInt> {
299 return fixup(a, b, a.udiv(b));
300 };
301 return minMaxBy(udiv, {lhsMin, lhsMax}, {rhsMin, rhsMax},
302 /*isSigned=*/false);
303 }
304
305 APInt umin = APInt::getZero(rhsMin.getBitWidth());
306 if (lhsMin.uge(rhsMax) && !rhsMax.isZero())
307 umin = lhsMin.udiv(rhsMax);
308
309 // X u/ Y u<= X.
310 const APInt &umax = lhsMax;
311 return ConstantIntRanges::fromUnsigned(umin, umax);
312}
313
316 return inferDivURange(argRanges[0], argRanges[1],
317 [](const APInt &lhs, const APInt &rhs,
318 const APInt &result) { return result; });
319}
320
323 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
324
325 auto ceilDivUIFix = [](const APInt &lhs, const APInt &rhs,
326 const APInt &result) -> std::optional<APInt> {
327 if (!lhs.urem(rhs).isZero()) {
328 bool overflowed = false;
329 APInt corrected =
330 result.uadd_ov(APInt(result.getBitWidth(), 1), overflowed);
331 return overflowed ? std::optional<APInt>() : corrected;
332 }
333 return result;
334 };
335 return inferDivURange(lhs, rhs, ceilDivUIFix);
336}
337
338//===----------------------------------------------------------------------===//
339// DivS, CeilDivS, FloorDivS (Signed division)
340//===----------------------------------------------------------------------===//
341
343 const ConstantIntRanges &rhs,
344 DivisionFixupFn fixup) {
345 const APInt &lhsMin = lhs.smin(), &lhsMax = lhs.smax(), &rhsMin = rhs.smin(),
346 &rhsMax = rhs.smax();
347 // An empty (min > max) divisor range can arise from poison, e.g. a
348 // saturated `nsw`/`nuw` subtraction. Don't trust its bounds.
349 unsigned width = rhsMin.getBitWidth();
350 if (rhsMin.sgt(rhsMax))
351 return ConstantIntRanges::maxRange(width);
352 bool canDivide = rhsMin.isStrictlyPositive() || rhsMax.isNegative();
353
354 if (canDivide) {
355 auto sdiv = [&fixup](const APInt &a,
356 const APInt &b) -> std::optional<APInt> {
357 bool overflowed = false;
358 APInt result = a.sdiv_ov(b, overflowed);
359 return overflowed ? std::optional<APInt>() : fixup(a, b, result);
360 };
361 return minMaxBy(sdiv, {lhsMin, lhsMax}, {rhsMin, rhsMax},
362 /*isSigned=*/true);
363 }
364 // Zero divisors produce poison. Bound each nonzero interval separately.
365 std::optional<ConstantIntRanges> result;
366 if (rhsMin.isNegative())
368 lhs, ConstantIntRanges::fromSigned(rhsMin, APInt::getAllOnes(width)),
369 fixup);
370 if (rhsMax.isStrictlyPositive()) {
372 lhs, ConstantIntRanges::fromSigned(APInt(width, 1), rhsMax), fixup);
373 result = result ? result->rangeUnion(positive) : positive;
374 }
375 return result.value_or(ConstantIntRanges::maxRange(width));
376}
377
380 return inferDivSRange(argRanges[0], argRanges[1],
381 [](const APInt &lhs, const APInt &rhs,
382 const APInt &result) { return result; });
383}
384
387 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
388
389 auto ceilDivSIFix = [](const APInt &lhs, const APInt &rhs,
390 const APInt &result) -> std::optional<APInt> {
391 if (!lhs.srem(rhs).isZero() && lhs.isNonNegative() == rhs.isNonNegative()) {
392 bool overflowed = false;
393 APInt corrected =
394 result.sadd_ov(APInt(result.getBitWidth(), 1), overflowed);
395 return overflowed ? std::optional<APInt>() : corrected;
396 }
397 // Special case where the usual implementation of ceilDiv causes
398 // INT_MIN / [positive number] to be positive. This doesn't match the
399 // definition of signed ceiling division mathematically, but it prevents
400 // inconsistent constant-folding results. This arises because (-int_min) is
401 // still negative, so -(-int_min / b) is -(int_min / b), which is
402 // positive See #115293.
403 if (lhs.isMinSignedValue() && rhs.sgt(1)) {
404 return -result;
405 }
406 return result;
407 };
408 ConstantIntRanges result = inferDivSRange(lhs, rhs, ceilDivSIFix);
409 if (lhs.smin().isMinSignedValue() && lhs.smax().sgt(lhs.smin())) {
410 // If lhs range includes INT_MIN and lhs is not a single value, we can
411 // suddenly wrap to positive val, skipping entire negative range, add
412 // [INT_MIN + 1, smax()] range to the result to handle this.
413 auto newLhs = ConstantIntRanges::fromSigned(lhs.smin() + 1, lhs.smax());
414 result = result.rangeUnion(inferDivSRange(newLhs, rhs, ceilDivSIFix));
415 }
416 if (lhs.smin().isMinSignedValue() && rhs.smax().sgt(1) && rhs.smin().sle(2)) {
417 // The sign changes between divisors 1 and 2. Include the positive peak.
418 APInt two(lhs.smin().getBitWidth(), 2);
419 result =
420 result.rangeUnion(ConstantIntRanges::constant(-lhs.smin().sdiv(two)));
421 }
422 return result;
423}
424
427 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
428
429 auto floorDivSIFix = [](const APInt &lhs, const APInt &rhs,
430 const APInt &result) -> std::optional<APInt> {
431 if (!lhs.srem(rhs).isZero() && lhs.isNonNegative() != rhs.isNonNegative()) {
432 bool overflowed = false;
433 APInt corrected =
434 result.ssub_ov(APInt(result.getBitWidth(), 1), overflowed);
435 return overflowed ? std::optional<APInt>() : corrected;
436 }
437 return result;
438 };
439 return inferDivSRange(lhs, rhs, floorDivSIFix);
440}
441
442//===----------------------------------------------------------------------===//
443// Signed remainder (RemS)
444//===----------------------------------------------------------------------===//
445
448 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
449 const APInt &lhsMin = lhs.smin(), &lhsMax = lhs.smax(), &rhsMin = rhs.smin(),
450 &rhsMax = rhs.smax();
451
452 unsigned width = rhsMax.getBitWidth();
453 if (rhsMin.sgt(rhsMax) || (rhsMin.isZero() && rhsMax.isZero()))
454 return ConstantIntRanges::maxRange(width);
455
456 // Magnitudes are unsigned: abs(INT_MIN) is not a positive signed value.
457 APInt maxDivisor = llvm::APIntOps::umax(rhsMin.abs(), rhsMax.abs());
458 APInt zero = APInt::getZero(width);
459 APInt maxPositiveResult = maxDivisor - 1;
460 // Remainder has the dividend's sign and cannot exceed its magnitude.
461 APInt smin = llvm::APIntOps::smax(llvm::APIntOps::smin(lhsMin, zero),
462 -maxPositiveResult);
463 APInt smax = llvm::APIntOps::smin(llvm::APIntOps::smax(lhsMax, zero),
464 maxPositiveResult);
465 // Special case: sweeping out a contiguous range in N/[modulus].
466 if (rhsMin == rhsMax) {
467 if ((lhsMax - lhsMin).ult(maxDivisor)) {
468 APInt minRem = lhsMin.srem(maxDivisor);
469 APInt maxRem = lhsMax.srem(maxDivisor);
470 if (minRem.sle(maxRem)) {
471 smin = std::move(minRem);
472 smax = std::move(maxRem);
473 }
474 }
475 }
476 return ConstantIntRanges::fromSigned(smin, smax);
477}
478
479//===----------------------------------------------------------------------===//
480// Unsigned remainder (RemU)
481//===----------------------------------------------------------------------===//
482
485 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
486 const APInt &rhsMin = rhs.umin(), &rhsMax = rhs.umax();
487
488 unsigned width = rhsMin.getBitWidth();
489 APInt umin = APInt::getZero(width);
490 // Remainder can't be larger than either of its arguments.
491 APInt umax = llvm::APIntOps::umin((rhsMax - 1), lhs.umax());
492
493 if (!rhsMin.isZero()) {
494 // Special case: sweeping out a contiguous range in N/[modulus]
495 if (rhsMin == rhsMax) {
496 const APInt &lhsMin = lhs.umin(), &lhsMax = lhs.umax();
497 if ((lhsMax - lhsMin).ult(rhsMax)) {
498 APInt minRem = lhsMin.urem(rhsMax);
499 APInt maxRem = lhsMax.urem(rhsMax);
500 if (minRem.ule(maxRem)) {
501 umin = std::move(minRem);
502 umax = std::move(maxRem);
503 }
504 }
505 }
506 }
507 return ConstantIntRanges::fromUnsigned(umin, umax);
508}
509
510//===----------------------------------------------------------------------===//
511// Max and min (MaxS, MaxU, MinS, MinU)
512//===----------------------------------------------------------------------===//
513
516 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
517
518 const APInt &smin = lhs.smin().sgt(rhs.smin()) ? lhs.smin() : rhs.smin();
519 const APInt &smax = lhs.smax().sgt(rhs.smax()) ? lhs.smax() : rhs.smax();
520 return ConstantIntRanges::fromSigned(smin, smax);
521}
522
525 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
526
527 const APInt &umin = lhs.umin().ugt(rhs.umin()) ? lhs.umin() : rhs.umin();
528 const APInt &umax = lhs.umax().ugt(rhs.umax()) ? lhs.umax() : rhs.umax();
529 return ConstantIntRanges::fromUnsigned(umin, umax);
530}
531
534 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
535
536 const APInt &smin = lhs.smin().slt(rhs.smin()) ? lhs.smin() : rhs.smin();
537 const APInt &smax = lhs.smax().slt(rhs.smax()) ? lhs.smax() : rhs.smax();
538 return ConstantIntRanges::fromSigned(smin, smax);
539}
540
543 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
544
545 const APInt &umin = lhs.umin().ult(rhs.umin()) ? lhs.umin() : rhs.umin();
546 const APInt &umax = lhs.umax().ult(rhs.umax()) ? lhs.umax() : rhs.umax();
547 return ConstantIntRanges::fromUnsigned(umin, umax);
548}
549
550//===----------------------------------------------------------------------===//
551// Bitwise operators (And, Or, Xor)
552//===----------------------------------------------------------------------===//
553
554/// "Widen" bounds - if 0bvvvvv??? <= a <= 0bvvvvv???,
555/// relax the bounds to 0bvvvvv000 <= a <= 0bvvvvv111, where vvvvv are the bits
556/// that both bonuds have in common. This gives us a consertive approximation
557/// for what values can be passed to bitwise operations.
558static std::tuple<APInt, APInt>
560 APInt leftVal = bound.umin(), rightVal = bound.umax();
561 unsigned bitwidth = leftVal.getBitWidth();
562 unsigned differingBits = bitwidth - (leftVal ^ rightVal).countl_zero();
563 leftVal.clearLowBits(differingBits);
564 rightVal.setLowBits(differingBits);
565 return std::make_tuple(std::move(leftVal), std::move(rightVal));
566}
567
570 auto [lhsZeros, lhsOnes] = widenBitwiseBounds(argRanges[0]);
571 auto [rhsZeros, rhsOnes] = widenBitwiseBounds(argRanges[1]);
572 auto andi = [](const APInt &a, const APInt &b) -> std::optional<APInt> {
573 return a & b;
574 };
575 std::array<APInt, 2> lhsBounds = {std::move(lhsZeros), std::move(lhsOnes)};
576 std::array<APInt, 2> rhsBounds = {std::move(rhsZeros), std::move(rhsOnes)};
577 return minMaxBy(andi, lhsBounds, rhsBounds, /*isSigned=*/false);
578}
579
582 auto [lhsZeros, lhsOnes] = widenBitwiseBounds(argRanges[0]);
583 auto [rhsZeros, rhsOnes] = widenBitwiseBounds(argRanges[1]);
584 auto ori = [](const APInt &a, const APInt &b) -> std::optional<APInt> {
585 return a | b;
586 };
587 std::array<APInt, 2> lhsBounds = {std::move(lhsZeros), std::move(lhsOnes)};
588 std::array<APInt, 2> rhsBounds = {std::move(rhsZeros), std::move(rhsOnes)};
589 return minMaxBy(ori, lhsBounds, rhsBounds, /*isSigned=*/false);
590}
591
592/// Get bitmask of all bits which can change while iterating in
593/// [bound.umin(), bound.umax()].
594static APInt getVaryingBitsMask(const ConstantIntRanges &bound) {
595 APInt leftVal = bound.umin(), rightVal = bound.umax();
596 unsigned bitwidth = leftVal.getBitWidth();
597 unsigned differingBits =
598 bitwidth - (std::move(leftVal) ^ rightVal).countl_zero();
599 return APInt::getLowBitsSet(bitwidth, differingBits);
600}
601
604 // Construct mask of varying bits for both ranges, xor values and then replace
605 // masked bits with 0s and 1s to get min and max values respectively.
606 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
607 APInt mask = getVaryingBitsMask(lhs) | getVaryingBitsMask(rhs);
608 APInt res = lhs.umin() ^ rhs.umin();
609 APInt min = res & ~mask;
610 APInt max = std::move(res) | mask;
612}
613
614//===----------------------------------------------------------------------===//
615// Shifts (Shl, ShrS, ShrU)
616//===----------------------------------------------------------------------===//
617
620 OverflowFlags ovfFlags) {
621 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
622 const APInt &rhsUMin = rhs.umin(), &rhsUMax = rhs.umax();
623
624 // The signed/unsigned overflow behavior of shl by `rhs` matches a mul with
625 // 2^rhs.
626 ConstArithStdFn ushl = [=](const APInt &l,
627 const APInt &r) -> std::optional<APInt> {
628 bool overflowed = false;
629 APInt result = any(ovfFlags & OverflowFlags::Nuw)
630 ? l.ushl_sat(r)
631 : l.ushl_ov(r, overflowed);
632 return overflowed ? std::optional<APInt>() : result;
633 };
634 ConstArithStdFn sshl = [=](const APInt &l,
635 const APInt &r) -> std::optional<APInt> {
636 bool overflowed = false;
637 APInt result = any(ovfFlags & OverflowFlags::Nsw)
638 ? l.sshl_sat(r)
639 : l.sshl_ov(r, overflowed);
640 return overflowed ? std::optional<APInt>() : result;
641 };
642
643 ConstantIntRanges urange =
644 minMaxBy(ushl, {lhs.umin(), lhs.umax()}, {rhsUMin, rhsUMax},
645 /*isSigned=*/false);
646 ConstantIntRanges srange =
647 minMaxBy(sshl, {lhs.smin(), lhs.smax()}, {rhsUMin, rhsUMax},
648 /*isSigned=*/true);
649 return urange.intersection(srange);
650}
651
654 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
655
656 auto ashr = [](const APInt &l, const APInt &r) -> std::optional<APInt> {
657 return r.uge(r.getBitWidth()) ? std::optional<APInt>() : l.ashr(r);
658 };
659
660 return minMaxBy(ashr, {lhs.smin(), lhs.smax()}, {rhs.umin(), rhs.umax()},
661 /*isSigned=*/true);
662}
663
666 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
667
668 auto lshr = [](const APInt &l, const APInt &r) -> std::optional<APInt> {
669 return r.uge(r.getBitWidth()) ? std::optional<APInt>() : l.lshr(r);
670 };
671 return minMaxBy(lshr, {lhs.umin(), lhs.umax()}, {rhs.umin(), rhs.umax()},
672 /*isSigned=*/false);
673}
674
675//===----------------------------------------------------------------------===//
676// Comparisons (Cmp)
677//===----------------------------------------------------------------------===//
678
704
706 const ConstantIntRanges &lhs,
707 const ConstantIntRanges &rhs) {
708 switch (pred) {
710 return lhs.smax().sle(rhs.smin());
712 return lhs.smax().slt(rhs.smin());
714 return lhs.umax().ule(rhs.umin());
716 return lhs.umax().ult(rhs.umin());
718 return lhs.smin().sge(rhs.smax());
720 return lhs.smin().sgt(rhs.smax());
722 return lhs.umin().uge(rhs.umax());
724 return lhs.umin().ugt(rhs.umax());
726 std::optional<APInt> lhsConst = lhs.getConstantValue();
727 std::optional<APInt> rhsConst = rhs.getConstantValue();
728 return lhsConst && rhsConst && lhsConst == rhsConst;
729 }
731 // While equality requires that there is an interpration of the preceeding
732 // computations that produces equal constants, whether that be signed or
733 // unsigned, statically determining inequality requires that neither
734 // interpretation produce potentially overlapping ranges.
735 bool sne = isStaticallyTrue(intrange::CmpPredicate::slt, lhs, rhs) ||
737 bool une = isStaticallyTrue(intrange::CmpPredicate::ult, lhs, rhs) ||
739 return sne && une;
740 }
741 }
742 return false;
743}
744
746 const ConstantIntRanges &lhs,
747 const ConstantIntRanges &rhs) {
748 if (isStaticallyTrue(pred, lhs, rhs))
749 return true;
750 if (isStaticallyTrue(invertPredicate(pred), lhs, rhs))
751 return false;
752 return std::nullopt;
753}
754
755//===----------------------------------------------------------------------===//
756// Shaped type dimension accessors / ShapedDimOpInterface
757//===----------------------------------------------------------------------===//
758
761 const IntegerValueRange &maybeDim) {
762 unsigned width =
763 ConstantIntRanges::getStorageBitwidth(op->getResult(0).getType());
764 APInt zero = APInt::getZero(width);
765 APInt typeMax = APInt::getSignedMaxValue(width);
766
767 auto shapedTy = cast<ShapedType>(op.getShapedValue().getType());
768 if (!shapedTy.hasRank())
769 return ConstantIntRanges::fromSigned(zero, typeMax);
770
771 int64_t rank = shapedTy.getRank();
772 int64_t minDim = 0;
773 int64_t maxDim = rank - 1;
774 if (!maybeDim.isUninitialized()) {
775 const ConstantIntRanges &dim = maybeDim.getValue();
776 minDim = std::max(minDim, dim.smin().getSExtValue());
777 maxDim = std::min(maxDim, dim.smax().getSExtValue());
778 }
779
780 std::optional<ConstantIntRanges> result;
781 auto joinResult = [&](const ConstantIntRanges &thisResult) {
782 if (!result.has_value())
783 result = thisResult;
784 else
785 result = result->rangeUnion(thisResult);
786 };
787 for (int64_t i = minDim; i <= maxDim; ++i) {
788 int64_t length = shapedTy.getDimSize(i);
789
790 if (ShapedType::isDynamic(length))
791 joinResult(ConstantIntRanges::fromSigned(zero, typeMax));
792 else
793 joinResult(ConstantIntRanges::constant(APInt(width, length)));
794 }
795 return result.value_or(ConstantIntRanges::fromSigned(zero, typeMax));
796}
797
798//===----------------------------------------------------------------------===//
799// Affine expression inference
800//===----------------------------------------------------------------------===//
801
803 unsigned width = val.smin().getBitWidth();
804 APInt one(width, 1);
805 APInt clampedUMin = val.umin().ult(one) ? one : val.umin();
806 APInt clampedSMin = val.smin().slt(one) ? one : val.smin();
807 return ConstantIntRanges::fromUnsigned(clampedUMin, val.umax())
808 .intersection(ConstantIntRanges::fromSigned(clampedSMin, val.smax()));
809}
810
814 ArrayRef<ConstantIntRanges> symbolRanges) {
815 switch (expr.getKind()) {
817 auto constExpr = cast<AffineConstantExpr>(expr);
818 APInt value(indexMaxWidth, constExpr.getValue(), /*isSigned=*/true);
819 return ConstantIntRanges::constant(value);
820 }
822 auto dimExpr = cast<AffineDimExpr>(expr);
823 unsigned pos = dimExpr.getPosition();
824 assert(pos < dimRanges.size() && "Dimension index out of bounds");
825 return dimRanges[pos];
826 }
828 auto symbolExpr = cast<AffineSymbolExpr>(expr);
829 unsigned pos = symbolExpr.getPosition();
830 assert(pos < symbolRanges.size() && "Symbol index out of bounds");
831 return symbolRanges[pos];
832 }
833 case AffineExprKind::Add: {
834 auto binExpr = cast<AffineBinaryOpExpr>(expr);
836 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
838 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
839 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
840 std::move(rhs)};
841 return inferAdd(operands, OverflowFlags::Nsw);
842 }
843 case AffineExprKind::Mul: {
844 auto binExpr = cast<AffineBinaryOpExpr>(expr);
846 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
848 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
849 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
850 std::move(rhs)};
851 return inferMul(operands, OverflowFlags::Nsw);
852 }
853 case AffineExprKind::Mod: {
854 auto binExpr = cast<AffineBinaryOpExpr>(expr);
856 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
858 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
859 // Affine mod is Euclidean modulo: result is always in [0, rhs-1].
860 // This assumes RHS is positive (enforced by affine expr semantics).
861 const APInt &lhsMin = lhs.smin();
862 const APInt &lhsMax = lhs.smax();
863 const APInt &rhsMin = rhs.smin();
864 const APInt &rhsMax = rhs.smax();
865 unsigned width = rhsMin.getBitWidth();
866
867 // Guard against division by zero.
868 if (rhsMax.isZero())
869 return ConstantIntRanges::maxRange(width);
870
871 // For Euclidean mod, result is in [0, max(rhs)-1].
872 APInt umin = APInt::getZero(width);
873 APInt umax = rhsMax - 1;
874
875 // Special case: if dividend is already in [0, min(rhs)), result equals
876 // dividend. We use rhsMin to ensure this is safe for all possible divisor
877 // values.
878 if (rhsMin.isStrictlyPositive() && lhsMin.isNonNegative() &&
879 lhsMax.ult(rhsMin)) {
880 umin = lhsMin;
881 umax = lhsMax;
882 }
883 // Special case: sweeping out a contiguous range with constant divisor.
884 // Only applies when dividend is non-negative and the range does not
885 // cross a modulus boundary (same quotient), ensuring contiguity.
886 else if (rhsMin == rhsMax && lhsMin.isNonNegative() &&
887 (lhsMax - lhsMin).ult(rhsMax) &&
888 lhsMin.udiv(rhsMax) == lhsMax.udiv(rhsMax)) {
889 // For non-negative dividends within the same modular period,
890 // Euclidean mod is same as unsigned remainder and the result is
891 // contiguous.
892 umin = lhsMin.urem(rhsMax);
893 umax = lhsMax.urem(rhsMax);
894 // Result should be contiguous since we're not wrapping around.
895 assert(umin.ule(umax) &&
896 "Range should be contiguous for non-negative dividend");
897 }
898
899 return ConstantIntRanges::fromUnsigned(umin, umax);
900 }
902 auto binExpr = cast<AffineBinaryOpExpr>(expr);
904 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
906 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
907 // Affine floordiv requires strictly positive divisor (> 0).
908 // Clamp divisor lower bound to 1 for tighter range inference.
909 ConstantIntRanges clampedRhs = clampToPositive(rhs);
910 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
911 std::move(clampedRhs)};
912 return inferFloorDivS(operands);
913 }
915 auto binExpr = cast<AffineBinaryOpExpr>(expr);
917 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
919 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
920 // Affine ceildiv requires strictly positive divisor (> 0).
921 // Clamp divisor lower bound to 1 for tighter range inference.
922 ConstantIntRanges clampedRhs = clampToPositive(rhs);
923 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
924 std::move(clampedRhs)};
925 return inferCeilDivS(operands);
926 }
927 }
928 llvm_unreachable("unknown affine expression kind");
929}
static ConstantIntRanges inferDivSRange(const ConstantIntRanges &lhs, const ConstantIntRanges &rhs, DivisionFixupFn fixup)
static bool isStaticallyTrue(intrange::CmpPredicate pred, const ConstantIntRanges &lhs, const ConstantIntRanges &rhs)
static intrange::CmpPredicate invertPredicate(intrange::CmpPredicate pred)
static ConstantIntRanges minMaxBy(ConstArithFn op, ArrayRef< APInt > lhs, ArrayRef< APInt > rhs, bool isSigned)
Compute the minimum and maximum of (op(l, r) for l in lhs for r in rhs), ignoring unbounded values.
std::function< std::optional< APInt >(const APInt &, const APInt &)> ConstArithStdFn
static std::tuple< APInt, APInt > widenBitwiseBounds(const ConstantIntRanges &bound)
"Widen" bounds - if 0bvvvvv?
static ConstantIntRanges inferDivURange(const ConstantIntRanges &lhs, const ConstantIntRanges &rhs, DivisionFixupFn fixup)
function_ref< std::optional< APInt >(const APInt &, const APInt &)> ConstArithFn
Function that evaluates the result of doing something on arithmetic constants and returns std::nullop...
static ConstantIntRanges clampToPositive(const ConstantIntRanges &val)
static ConstantIntRanges computeBoundsBy(ConstArithFn op, const APInt &minLeft, const APInt &minRight, const APInt &maxLeft, const APInt &maxRight, bool isSigned)
Compute op(minLeft, minRight) and op(maxLeft, maxRight) if possible, If either computation overflows,...
static APInt getVaryingBitsMask(const ConstantIntRanges &bound)
Get bitmask of all bits which can change while iterating in [bound.umin(), bound.umax()].
function_ref< std::optional< APInt >( const APInt &lhs, const APInt &rhs, const APInt &result)> DivisionFixupFn
Fix up division results (ex.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
Base type for affine expression.
Definition AffineExpr.h:68
AffineExprKind getKind() const
Return the classification for this type.
A set of arbitrary-precision integers representing bounds on a given integer value.
static ConstantIntRanges maxRange(unsigned bitwidth)
Create a ConstantIntRanges with the maximum bounds for the width bitwidth, that is - [0,...
const APInt & smax() const
The maximum value of an integer when it is interpreted as signed.
static ConstantIntRanges constant(const APInt &value)
Create a ConstantIntRanges with a constant value - that is, with the bounds [value,...
static ConstantIntRanges fromUnsigned(const APInt &umin, const APInt &umax)
Create an ConstantIntRanges with the unsigned minimum and maximum equal to umin and umax and the sign...
const APInt & smin() const
The minimum value of an integer when it is interpreted as signed.
static ConstantIntRanges range(const APInt &min, const APInt &max, bool isSigned)
Create a ConstantIntRanges whose minimum is min and maximum is max with isSigned specifying if the mi...
ConstantIntRanges intersection(const ConstantIntRanges &other) const
Returns the intersection (computed separately for signed and unsigned bounds) of this range and other...
static ConstantIntRanges fromSigned(const APInt &smin, const APInt &smax)
Create an ConstantIntRanges with the signed minimum and maximum equal to smin and smax,...
static unsigned getStorageBitwidth(Type type)
Return the bitwidth that should be used for integer ranges describing type.
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.
ConstantIntRanges rangeUnion(const ConstantIntRanges &other) const
Returns the union (computed separately for signed and unsigned bounds) of this range and other.
This lattice value represents the integer range of an SSA value.
const ConstantIntRanges & getValue() const
Get the known integer value range.
bool isUninitialized() const
Whether the range is uninitialized.
ConstantIntRanges inferAnd(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferShl(ArrayRef< ConstantIntRanges > argRanges, OverflowFlags ovfFlags=OverflowFlags::None)
ConstantIntRanges inferIndexOp(const InferRangeFn &inferFn, ArrayRef< ConstantIntRanges > argRanges, CmpMode mode)
Compute inferFn on ranges, whose size should be the index storage bitwidth.
ConstantIntRanges inferShrS(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges extSIRange(const ConstantIntRanges &range, unsigned destWidth)
Use the signed values in range to sign-extend it to destWidth.
std::optional< bool > evaluatePred(CmpPredicate pred, const ConstantIntRanges &lhs, const ConstantIntRanges &rhs)
Returns a boolean value if pred is statically true or false for anypossible inputs falling within lhs...
ConstantIntRanges inferMinS(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferMaxU(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferRemS(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferAffineExpr(AffineExpr expr, ArrayRef< ConstantIntRanges > dimRanges, ArrayRef< ConstantIntRanges > symbolRanges)
Infer the integer range for an affine expression given ranges for its dimensions and symbols.
CmpPredicate
Copy of the enum from arith and index to allow the common integer range infrastructure to not depend ...
ConstantIntRanges inferOr(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges extRange(const ConstantIntRanges &range, unsigned destWidth)
Independently zero-extend the unsigned values and sign-extend the signed values in range to destWidth...
ConstantIntRanges inferSub(ArrayRef< ConstantIntRanges > argRanges, OverflowFlags ovfFlags=OverflowFlags::None)
std::function< ConstantIntRanges(ArrayRef< ConstantIntRanges >)> InferRangeFn
Function that performs inference on an array of ConstantIntRanges, abstracted away here to permit wri...
ConstantIntRanges inferAdd(ArrayRef< ConstantIntRanges > argRanges, OverflowFlags ovfFlags=OverflowFlags::None)
ConstantIntRanges truncRange(const ConstantIntRanges &range, unsigned destWidth)
Truncate range to destWidth bits, taking care to handle cases such as the truncation of [255,...
ConstantIntRanges inferDivU(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferCeilDivS(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferMinU(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferMul(ArrayRef< ConstantIntRanges > argRanges, OverflowFlags ovfFlags=OverflowFlags::None)
ConstantIntRanges inferXor(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferDivS(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferShrU(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges inferRemU(ArrayRef< ConstantIntRanges > argRanges)
static constexpr unsigned indexMinWidth
ConstantIntRanges inferFloorDivS(ArrayRef< ConstantIntRanges > argRanges)
static constexpr unsigned indexMaxWidth
ConstantIntRanges inferCeilDivU(ArrayRef< ConstantIntRanges > argRanges)
ConstantIntRanges extUIRange(const ConstantIntRanges &range, unsigned destWidth)
Use the unsigned values in range to zero-extend it to destWidth.
ConstantIntRanges inferShapedDimOpInterface(ShapedDimOpInterface op, const IntegerValueRange &maybeDim)
Returns the integer range for the result of a ShapedDimOpInterface given the optional inferred ranges...
ConstantIntRanges inferMaxS(ArrayRef< ConstantIntRanges > argRanges)
Include the generated interface declarations.
@ CeilDiv
RHS of ceildiv is always a constant or a symbolic expression.
Definition AffineExpr.h:50
@ Mul
RHS of mul is always a constant or a symbolic expression.
Definition AffineExpr.h:43
@ Mod
RHS of mod is always a constant or a symbolic expression with a positive value.
Definition AffineExpr.h:46
@ DimId
Dimensional identifier.
Definition AffineExpr.h:59
@ FloorDiv
RHS of floordiv is always a constant or a symbolic expression.
Definition AffineExpr.h:48
@ Constant
Constant integer.
Definition AffineExpr.h:57
@ SymbolId
Symbolic identifier.
Definition AffineExpr.h:61
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155