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 bool canDivide = rhsMin.isStrictlyPositive() || rhsMax.isNegative();
348
349 if (canDivide) {
350 auto sdiv = [&fixup](const APInt &a,
351 const APInt &b) -> std::optional<APInt> {
352 bool overflowed = false;
353 APInt result = a.sdiv_ov(b, overflowed);
354 return overflowed ? std::optional<APInt>() : fixup(a, b, result);
355 };
356 return minMaxBy(sdiv, {lhsMin, lhsMax}, {rhsMin, rhsMax},
357 /*isSigned=*/true);
358 }
359 return ConstantIntRanges::maxRange(rhsMin.getBitWidth());
360}
361
364 return inferDivSRange(argRanges[0], argRanges[1],
365 [](const APInt &lhs, const APInt &rhs,
366 const APInt &result) { return result; });
367}
368
371 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
372
373 auto ceilDivSIFix = [](const APInt &lhs, const APInt &rhs,
374 const APInt &result) -> std::optional<APInt> {
375 if (!lhs.srem(rhs).isZero() && lhs.isNonNegative() == rhs.isNonNegative()) {
376 bool overflowed = false;
377 APInt corrected =
378 result.sadd_ov(APInt(result.getBitWidth(), 1), overflowed);
379 return overflowed ? std::optional<APInt>() : corrected;
380 }
381 // Special case where the usual implementation of ceilDiv causes
382 // INT_MIN / [positive number] to be positive. This doesn't match the
383 // definition of signed ceiling division mathematically, but it prevents
384 // inconsistent constant-folding results. This arises because (-int_min) is
385 // still negative, so -(-int_min / b) is -(int_min / b), which is
386 // positive See #115293.
387 if (lhs.isMinSignedValue() && rhs.sgt(1)) {
388 return -result;
389 }
390 return result;
391 };
392 ConstantIntRanges result = inferDivSRange(lhs, rhs, ceilDivSIFix);
393 if (lhs.smin().isMinSignedValue() && lhs.smax().sgt(lhs.smin())) {
394 // If lhs range includes INT_MIN and lhs is not a single value, we can
395 // suddenly wrap to positive val, skipping entire negative range, add
396 // [INT_MIN + 1, smax()] range to the result to handle this.
397 auto newLhs = ConstantIntRanges::fromSigned(lhs.smin() + 1, lhs.smax());
398 result = result.rangeUnion(inferDivSRange(newLhs, rhs, ceilDivSIFix));
399 }
400 return result;
401}
402
405 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
406
407 auto floorDivSIFix = [](const APInt &lhs, const APInt &rhs,
408 const APInt &result) -> std::optional<APInt> {
409 if (!lhs.srem(rhs).isZero() && lhs.isNonNegative() != rhs.isNonNegative()) {
410 bool overflowed = false;
411 APInt corrected =
412 result.ssub_ov(APInt(result.getBitWidth(), 1), overflowed);
413 return overflowed ? std::optional<APInt>() : corrected;
414 }
415 return result;
416 };
417 return inferDivSRange(lhs, rhs, floorDivSIFix);
418}
419
420//===----------------------------------------------------------------------===//
421// Signed remainder (RemS)
422//===----------------------------------------------------------------------===//
423
426 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
427 const APInt &lhsMin = lhs.smin(), &lhsMax = lhs.smax(), &rhsMin = rhs.smin(),
428 &rhsMax = rhs.smax();
429
430 unsigned width = rhsMax.getBitWidth();
431 APInt smin = APInt::getSignedMinValue(width);
432 APInt smax = APInt::getSignedMaxValue(width);
433 // No bounds if zero could be a divisor.
434 bool canBound = (rhsMin.isStrictlyPositive() || rhsMax.isNegative());
435 if (canBound) {
436 APInt maxDivisor = rhsMin.isStrictlyPositive() ? rhsMax : rhsMin.abs();
437 bool canNegativeDividend = lhsMin.isNegative();
438 bool canPositiveDividend = lhsMax.isStrictlyPositive();
439 APInt zero = APInt::getZero(maxDivisor.getBitWidth());
440 APInt maxPositiveResult = maxDivisor - 1;
441 APInt minNegativeResult = -maxPositiveResult;
442 smin = canNegativeDividend ? minNegativeResult : zero;
443 smax = canPositiveDividend ? maxPositiveResult : zero;
444 // Special case: sweeping out a contiguous range in N/[modulus].
445 if (rhsMin == rhsMax) {
446 if ((lhsMax - lhsMin).ult(maxDivisor)) {
447 APInt minRem = lhsMin.srem(maxDivisor);
448 APInt maxRem = lhsMax.srem(maxDivisor);
449 if (minRem.sle(maxRem)) {
450 smin = std::move(minRem);
451 smax = std::move(maxRem);
452 }
453 }
454 }
455 }
456 return ConstantIntRanges::fromSigned(smin, smax);
457}
458
459//===----------------------------------------------------------------------===//
460// Unsigned remainder (RemU)
461//===----------------------------------------------------------------------===//
462
465 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
466 const APInt &rhsMin = rhs.umin(), &rhsMax = rhs.umax();
467
468 unsigned width = rhsMin.getBitWidth();
469 APInt umin = APInt::getZero(width);
470 // Remainder can't be larger than either of its arguments.
471 APInt umax = llvm::APIntOps::umin((rhsMax - 1), lhs.umax());
472
473 if (!rhsMin.isZero()) {
474 // Special case: sweeping out a contiguous range in N/[modulus]
475 if (rhsMin == rhsMax) {
476 const APInt &lhsMin = lhs.umin(), &lhsMax = lhs.umax();
477 if ((lhsMax - lhsMin).ult(rhsMax)) {
478 APInt minRem = lhsMin.urem(rhsMax);
479 APInt maxRem = lhsMax.urem(rhsMax);
480 if (minRem.ule(maxRem)) {
481 umin = std::move(minRem);
482 umax = std::move(maxRem);
483 }
484 }
485 }
486 }
487 return ConstantIntRanges::fromUnsigned(umin, umax);
488}
489
490//===----------------------------------------------------------------------===//
491// Max and min (MaxS, MaxU, MinS, MinU)
492//===----------------------------------------------------------------------===//
493
496 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
497
498 const APInt &smin = lhs.smin().sgt(rhs.smin()) ? lhs.smin() : rhs.smin();
499 const APInt &smax = lhs.smax().sgt(rhs.smax()) ? lhs.smax() : rhs.smax();
500 return ConstantIntRanges::fromSigned(smin, smax);
501}
502
505 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
506
507 const APInt &umin = lhs.umin().ugt(rhs.umin()) ? lhs.umin() : rhs.umin();
508 const APInt &umax = lhs.umax().ugt(rhs.umax()) ? lhs.umax() : rhs.umax();
509 return ConstantIntRanges::fromUnsigned(umin, umax);
510}
511
514 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
515
516 const APInt &smin = lhs.smin().slt(rhs.smin()) ? lhs.smin() : rhs.smin();
517 const APInt &smax = lhs.smax().slt(rhs.smax()) ? lhs.smax() : rhs.smax();
518 return ConstantIntRanges::fromSigned(smin, smax);
519}
520
523 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
524
525 const APInt &umin = lhs.umin().ult(rhs.umin()) ? lhs.umin() : rhs.umin();
526 const APInt &umax = lhs.umax().ult(rhs.umax()) ? lhs.umax() : rhs.umax();
527 return ConstantIntRanges::fromUnsigned(umin, umax);
528}
529
530//===----------------------------------------------------------------------===//
531// Bitwise operators (And, Or, Xor)
532//===----------------------------------------------------------------------===//
533
534/// "Widen" bounds - if 0bvvvvv??? <= a <= 0bvvvvv???,
535/// relax the bounds to 0bvvvvv000 <= a <= 0bvvvvv111, where vvvvv are the bits
536/// that both bonuds have in common. This gives us a consertive approximation
537/// for what values can be passed to bitwise operations.
538static std::tuple<APInt, APInt>
540 APInt leftVal = bound.umin(), rightVal = bound.umax();
541 unsigned bitwidth = leftVal.getBitWidth();
542 unsigned differingBits = bitwidth - (leftVal ^ rightVal).countl_zero();
543 leftVal.clearLowBits(differingBits);
544 rightVal.setLowBits(differingBits);
545 return std::make_tuple(std::move(leftVal), std::move(rightVal));
546}
547
550 auto [lhsZeros, lhsOnes] = widenBitwiseBounds(argRanges[0]);
551 auto [rhsZeros, rhsOnes] = widenBitwiseBounds(argRanges[1]);
552 auto andi = [](const APInt &a, const APInt &b) -> std::optional<APInt> {
553 return a & b;
554 };
555 std::array<APInt, 2> lhsBounds = {std::move(lhsZeros), std::move(lhsOnes)};
556 std::array<APInt, 2> rhsBounds = {std::move(rhsZeros), std::move(rhsOnes)};
557 return minMaxBy(andi, lhsBounds, rhsBounds, /*isSigned=*/false);
558}
559
562 auto [lhsZeros, lhsOnes] = widenBitwiseBounds(argRanges[0]);
563 auto [rhsZeros, rhsOnes] = widenBitwiseBounds(argRanges[1]);
564 auto ori = [](const APInt &a, const APInt &b) -> std::optional<APInt> {
565 return a | b;
566 };
567 std::array<APInt, 2> lhsBounds = {std::move(lhsZeros), std::move(lhsOnes)};
568 std::array<APInt, 2> rhsBounds = {std::move(rhsZeros), std::move(rhsOnes)};
569 return minMaxBy(ori, lhsBounds, rhsBounds, /*isSigned=*/false);
570}
571
572/// Get bitmask of all bits which can change while iterating in
573/// [bound.umin(), bound.umax()].
574static APInt getVaryingBitsMask(const ConstantIntRanges &bound) {
575 APInt leftVal = bound.umin(), rightVal = bound.umax();
576 unsigned bitwidth = leftVal.getBitWidth();
577 unsigned differingBits =
578 bitwidth - (std::move(leftVal) ^ rightVal).countl_zero();
579 return APInt::getLowBitsSet(bitwidth, differingBits);
580}
581
584 // Construct mask of varying bits for both ranges, xor values and then replace
585 // masked bits with 0s and 1s to get min and max values respectively.
586 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
587 APInt mask = getVaryingBitsMask(lhs) | getVaryingBitsMask(rhs);
588 APInt res = lhs.umin() ^ rhs.umin();
589 APInt min = res & ~mask;
590 APInt max = std::move(res) | mask;
592}
593
594//===----------------------------------------------------------------------===//
595// Shifts (Shl, ShrS, ShrU)
596//===----------------------------------------------------------------------===//
597
600 OverflowFlags ovfFlags) {
601 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
602 const APInt &rhsUMin = rhs.umin(), &rhsUMax = rhs.umax();
603
604 // The signed/unsigned overflow behavior of shl by `rhs` matches a mul with
605 // 2^rhs.
606 ConstArithStdFn ushl = [=](const APInt &l,
607 const APInt &r) -> std::optional<APInt> {
608 bool overflowed = false;
609 APInt result = any(ovfFlags & OverflowFlags::Nuw)
610 ? l.ushl_sat(r)
611 : l.ushl_ov(r, overflowed);
612 return overflowed ? std::optional<APInt>() : result;
613 };
614 ConstArithStdFn sshl = [=](const APInt &l,
615 const APInt &r) -> std::optional<APInt> {
616 bool overflowed = false;
617 APInt result = any(ovfFlags & OverflowFlags::Nsw)
618 ? l.sshl_sat(r)
619 : l.sshl_ov(r, overflowed);
620 return overflowed ? std::optional<APInt>() : result;
621 };
622
623 ConstantIntRanges urange =
624 minMaxBy(ushl, {lhs.umin(), lhs.umax()}, {rhsUMin, rhsUMax},
625 /*isSigned=*/false);
626 ConstantIntRanges srange =
627 minMaxBy(sshl, {lhs.smin(), lhs.smax()}, {rhsUMin, rhsUMax},
628 /*isSigned=*/true);
629 return urange.intersection(srange);
630}
631
634 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
635
636 auto ashr = [](const APInt &l, const APInt &r) -> std::optional<APInt> {
637 return r.uge(r.getBitWidth()) ? std::optional<APInt>() : l.ashr(r);
638 };
639
640 return minMaxBy(ashr, {lhs.smin(), lhs.smax()}, {rhs.umin(), rhs.umax()},
641 /*isSigned=*/true);
642}
643
646 const ConstantIntRanges &lhs = argRanges[0], &rhs = argRanges[1];
647
648 auto lshr = [](const APInt &l, const APInt &r) -> std::optional<APInt> {
649 return r.uge(r.getBitWidth()) ? std::optional<APInt>() : l.lshr(r);
650 };
651 return minMaxBy(lshr, {lhs.umin(), lhs.umax()}, {rhs.umin(), rhs.umax()},
652 /*isSigned=*/false);
653}
654
655//===----------------------------------------------------------------------===//
656// Comparisons (Cmp)
657//===----------------------------------------------------------------------===//
658
684
686 const ConstantIntRanges &lhs,
687 const ConstantIntRanges &rhs) {
688 switch (pred) {
690 return lhs.smax().sle(rhs.smin());
692 return lhs.smax().slt(rhs.smin());
694 return lhs.umax().ule(rhs.umin());
696 return lhs.umax().ult(rhs.umin());
698 return lhs.smin().sge(rhs.smax());
700 return lhs.smin().sgt(rhs.smax());
702 return lhs.umin().uge(rhs.umax());
704 return lhs.umin().ugt(rhs.umax());
706 std::optional<APInt> lhsConst = lhs.getConstantValue();
707 std::optional<APInt> rhsConst = rhs.getConstantValue();
708 return lhsConst && rhsConst && lhsConst == rhsConst;
709 }
711 // While equality requires that there is an interpration of the preceeding
712 // computations that produces equal constants, whether that be signed or
713 // unsigned, statically determining inequality requires that neither
714 // interpretation produce potentially overlapping ranges.
715 bool sne = isStaticallyTrue(intrange::CmpPredicate::slt, lhs, rhs) ||
717 bool une = isStaticallyTrue(intrange::CmpPredicate::ult, lhs, rhs) ||
719 return sne && une;
720 }
721 }
722 return false;
723}
724
726 const ConstantIntRanges &lhs,
727 const ConstantIntRanges &rhs) {
728 if (isStaticallyTrue(pred, lhs, rhs))
729 return true;
730 if (isStaticallyTrue(invertPredicate(pred), lhs, rhs))
731 return false;
732 return std::nullopt;
733}
734
735//===----------------------------------------------------------------------===//
736// Shaped type dimension accessors / ShapedDimOpInterface
737//===----------------------------------------------------------------------===//
738
741 const IntegerValueRange &maybeDim) {
742 unsigned width =
743 ConstantIntRanges::getStorageBitwidth(op->getResult(0).getType());
744 APInt zero = APInt::getZero(width);
745 APInt typeMax = APInt::getSignedMaxValue(width);
746
747 auto shapedTy = cast<ShapedType>(op.getShapedValue().getType());
748 if (!shapedTy.hasRank())
749 return ConstantIntRanges::fromSigned(zero, typeMax);
750
751 int64_t rank = shapedTy.getRank();
752 int64_t minDim = 0;
753 int64_t maxDim = rank - 1;
754 if (!maybeDim.isUninitialized()) {
755 const ConstantIntRanges &dim = maybeDim.getValue();
756 minDim = std::max(minDim, dim.smin().getSExtValue());
757 maxDim = std::min(maxDim, dim.smax().getSExtValue());
758 }
759
760 std::optional<ConstantIntRanges> result;
761 auto joinResult = [&](const ConstantIntRanges &thisResult) {
762 if (!result.has_value())
763 result = thisResult;
764 else
765 result = result->rangeUnion(thisResult);
766 };
767 for (int64_t i = minDim; i <= maxDim; ++i) {
768 int64_t length = shapedTy.getDimSize(i);
769
770 if (ShapedType::isDynamic(length))
771 joinResult(ConstantIntRanges::fromSigned(zero, typeMax));
772 else
773 joinResult(ConstantIntRanges::constant(APInt(width, length)));
774 }
775 return result.value_or(ConstantIntRanges::fromSigned(zero, typeMax));
776}
777
778//===----------------------------------------------------------------------===//
779// Affine expression inference
780//===----------------------------------------------------------------------===//
781
783 unsigned width = val.smin().getBitWidth();
784 APInt one(width, 1);
785 APInt clampedUMin = val.umin().ult(one) ? one : val.umin();
786 APInt clampedSMin = val.smin().slt(one) ? one : val.smin();
787 return ConstantIntRanges::fromUnsigned(clampedUMin, val.umax())
788 .intersection(ConstantIntRanges::fromSigned(clampedSMin, val.smax()));
789}
790
794 ArrayRef<ConstantIntRanges> symbolRanges) {
795 switch (expr.getKind()) {
797 auto constExpr = cast<AffineConstantExpr>(expr);
798 APInt value(indexMaxWidth, constExpr.getValue(), /*isSigned=*/true);
799 return ConstantIntRanges::constant(value);
800 }
802 auto dimExpr = cast<AffineDimExpr>(expr);
803 unsigned pos = dimExpr.getPosition();
804 assert(pos < dimRanges.size() && "Dimension index out of bounds");
805 return dimRanges[pos];
806 }
808 auto symbolExpr = cast<AffineSymbolExpr>(expr);
809 unsigned pos = symbolExpr.getPosition();
810 assert(pos < symbolRanges.size() && "Symbol index out of bounds");
811 return symbolRanges[pos];
812 }
813 case AffineExprKind::Add: {
814 auto binExpr = cast<AffineBinaryOpExpr>(expr);
816 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
818 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
819 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
820 std::move(rhs)};
821 return inferAdd(operands, OverflowFlags::Nsw);
822 }
823 case AffineExprKind::Mul: {
824 auto binExpr = cast<AffineBinaryOpExpr>(expr);
826 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
828 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
829 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
830 std::move(rhs)};
831 return inferMul(operands, OverflowFlags::Nsw);
832 }
833 case AffineExprKind::Mod: {
834 auto binExpr = cast<AffineBinaryOpExpr>(expr);
836 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
838 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
839 // Affine mod is Euclidean modulo: result is always in [0, rhs-1].
840 // This assumes RHS is positive (enforced by affine expr semantics).
841 const APInt &lhsMin = lhs.smin();
842 const APInt &lhsMax = lhs.smax();
843 const APInt &rhsMin = rhs.smin();
844 const APInt &rhsMax = rhs.smax();
845 unsigned width = rhsMin.getBitWidth();
846
847 // Guard against division by zero.
848 if (rhsMax.isZero())
849 return ConstantIntRanges::maxRange(width);
850
851 // For Euclidean mod, result is in [0, max(rhs)-1].
852 APInt umin = APInt::getZero(width);
853 APInt umax = rhsMax - 1;
854
855 // Special case: if dividend is already in [0, min(rhs)), result equals
856 // dividend. We use rhsMin to ensure this is safe for all possible divisor
857 // values.
858 if (rhsMin.isStrictlyPositive() && lhsMin.isNonNegative() &&
859 lhsMax.ult(rhsMin)) {
860 umin = lhsMin;
861 umax = lhsMax;
862 }
863 // Special case: sweeping out a contiguous range with constant divisor.
864 // Only applies when dividend is non-negative and the range does not
865 // cross a modulus boundary (same quotient), ensuring contiguity.
866 else if (rhsMin == rhsMax && lhsMin.isNonNegative() &&
867 (lhsMax - lhsMin).ult(rhsMax) &&
868 lhsMin.udiv(rhsMax) == lhsMax.udiv(rhsMax)) {
869 // For non-negative dividends within the same modular period,
870 // Euclidean mod is same as unsigned remainder and the result is
871 // contiguous.
872 umin = lhsMin.urem(rhsMax);
873 umax = lhsMax.urem(rhsMax);
874 // Result should be contiguous since we're not wrapping around.
875 assert(umin.ule(umax) &&
876 "Range should be contiguous for non-negative dividend");
877 }
878
879 return ConstantIntRanges::fromUnsigned(umin, umax);
880 }
882 auto binExpr = cast<AffineBinaryOpExpr>(expr);
884 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
886 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
887 // Affine floordiv requires strictly positive divisor (> 0).
888 // Clamp divisor lower bound to 1 for tighter range inference.
889 ConstantIntRanges clampedRhs = clampToPositive(rhs);
890 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
891 std::move(clampedRhs)};
892 return inferFloorDivS(operands);
893 }
895 auto binExpr = cast<AffineBinaryOpExpr>(expr);
897 inferAffineExpr(binExpr.getLHS(), dimRanges, symbolRanges);
899 inferAffineExpr(binExpr.getRHS(), dimRanges, symbolRanges);
900 // Affine ceildiv requires strictly positive divisor (> 0).
901 // Clamp divisor lower bound to 1 for tighter range inference.
902 ConstantIntRanges clampedRhs = clampToPositive(rhs);
903 std::array<ConstantIntRanges, 2> operands = {std::move(lhs),
904 std::move(clampedRhs)};
905 return inferCeilDivS(operands);
906 }
907 }
908 llvm_unreachable("unknown affine expression kind");
909}
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:147