MLIR 23.0.0git
ValueBoundsOpInterface.cpp
Go to the documentation of this file.
1//===- ValueBoundsOpInterface.cpp - Value Bounds -------------------------===//
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
10
12#include "mlir/IR/Matchers.h"
15#include "llvm/ADT/APSInt.h"
16#include "llvm/ADT/SmallVectorExtras.h"
17#include "llvm/Support/Debug.h"
18#include "llvm/Support/DebugLog.h"
19
20#include <utility>
21
22#define DEBUG_TYPE "value-bounds-op-interface"
23
24using namespace mlir;
27
28namespace mlir {
29#include "mlir/Interfaces/ValueBoundsOpInterface.cpp.inc"
30} // namespace mlir
31
33 if (auto bbArg = dyn_cast<BlockArgument>(value))
34 return bbArg.getOwner()->getParentOp();
35 return value.getDefiningOp();
36}
37
41 : mixedOffsets(offsets), mixedSizes(sizes), mixedStrides(strides) {
42 assert(offsets.size() == sizes.size() &&
43 "expected same number of offsets, sizes, strides");
44 assert(offsets.size() == strides.size() &&
45 "expected same number of offsets, sizes, strides");
46}
47
50 : mixedOffsets(offsets), mixedSizes(sizes) {
51 assert(offsets.size() == sizes.size() &&
52 "expected same number of offsets and sizes");
53 // Assume that all strides are 1.
54 if (offsets.empty())
55 return;
56 MLIRContext *ctx = offsets.front().getContext();
57 mixedStrides.append(offsets.size(), Builder(ctx).getIndexAttr(1));
58}
59
63
64/// If ofr is a constant integer or an IntegerAttr, return the integer.
65static std::optional<int64_t> getConstantIntValue(OpFoldResult ofr) {
66 // Case 1: Check for Constant integer.
67 if (auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
68 APSInt intVal;
69 if (matchPattern(val, m_ConstantInt(&intVal)))
70 return intVal.getSExtValue();
71 return std::nullopt;
72 }
73 // Case 2: Check for IntegerAttr.
74 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
75 if (auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
76 return intAttr.getValue().getSExtValue();
77 return std::nullopt;
78}
79
80[[maybe_unused]] static bool isIndexOrIntegerType(Type type) {
81 return type.isIndex() || type.isInteger();
82}
83
84[[maybe_unused]] static bool isIndexLikeType(Type type,
86 return type.isIndex() || (options.allowIntegerType && type.isInteger());
87}
88
91
93 : Variable(static_cast<OpFoldResult>(indexValue)) {}
94
96 : Variable(static_cast<OpFoldResult>(shapedValue), std::optional(dim)) {}
97
99 std::optional<int64_t> dim) {
100 Builder b(ofr.getContext());
101 if (auto constInt = ::getConstantIntValue(ofr)) {
102 assert(!dim && "expected no dim for index/integer-typed values");
103 map = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/0,
104 b.getAffineConstantExpr(*constInt));
105 return;
106 }
107 Value value = cast<Value>(ofr);
108#ifndef NDEBUG
109 if (dim) {
110 assert(isa<ShapedType>(value.getType()) && "expected shaped type");
111 } else {
112 assert(isIndexOrIntegerType(value.getType()) &&
113 "expected index or integer type");
114 }
115#endif // NDEBUG
116 map = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/1,
117 b.getAffineSymbolExpr(0));
118 mapOperands.emplace_back(value, dim);
119}
120
122 ArrayRef<Variable> mapOperands) {
123 assert(map.getNumResults() == 1 && "expected single result");
124
125 // Turn all dims into symbols.
126 Builder b(map.getContext());
127 // Inline size chosen empirically based on compilation profiling.
128 // Profiled: 490K calls, avg=1.5+-0.6. N=4 covers >99% of cases inline.
129 SmallVector<AffineExpr, 4> dimReplacements, symReplacements;
130 for (int64_t i = 0, e = map.getNumDims(); i < e; ++i)
131 dimReplacements.push_back(b.getAffineSymbolExpr(i));
132 for (int64_t i = 0, e = map.getNumSymbols(); i < e; ++i)
133 symReplacements.push_back(b.getAffineSymbolExpr(i + map.getNumDims()));
134 AffineMap tmpMap = map.replaceDimsAndSymbols(
135 dimReplacements, symReplacements, /*numResultDims=*/0,
136 /*numResultSyms=*/map.getNumSymbols() + map.getNumDims());
137
138 // Inline operands.
140 for (auto [index, var] : llvm::enumerate(mapOperands)) {
141 assert(var.map.getNumResults() == 1 && "expected single result");
142 assert(var.map.getNumDims() == 0 && "expected only symbols");
143 SmallVector<AffineExpr> symReplacements;
144 for (auto valueDim : var.mapOperands) {
145 auto *it = llvm::find(this->mapOperands, valueDim);
146 if (it != this->mapOperands.end()) {
147 // There is already a symbol for this operand.
148 symReplacements.push_back(b.getAffineSymbolExpr(
149 std::distance(this->mapOperands.begin(), it)));
150 } else {
151 // This is a new operand: add a new symbol.
152 symReplacements.push_back(
153 b.getAffineSymbolExpr(this->mapOperands.size()));
154 this->mapOperands.push_back(valueDim);
155 }
156 }
157 replacements[b.getAffineSymbolExpr(index)] =
158 var.map.getResult(0).replaceSymbols(symReplacements);
159 }
160 this->map = tmpMap.replace(replacements, /*numResultDims=*/0,
161 /*numResultSyms=*/this->mapOperands.size());
162}
163
165 ValueRange mapOperands)
166 : Variable(map, llvm::map_to_vector(mapOperands,
167 [](Value v) { return Variable(v); })) {}
168
176
178
179#ifndef NDEBUG
180static void assertValidValueDim(Value value, std::optional<int64_t> dim,
182 if (isIndexLikeType(value.getType(), options)) {
183 assert(!dim.has_value() && "invalid dim value");
184 } else if (auto shapedType = dyn_cast<ShapedType>(value.getType())) {
185 assert(*dim >= 0 && "invalid dim value");
186 if (shapedType.hasRank())
187 assert(*dim < shapedType.getRank() && "invalid dim value");
188 } else {
189 llvm_unreachable("unsupported type");
190 }
191}
192#endif // NDEBUG
193
195 AffineExpr expr) {
196 // Note: If `addConservativeSemiAffineBounds` is true then the bound
197 // computation function needs to handle the case that the constraints set
198 // could become empty. This is because the conservative bounds add assumptions
199 // (e.g. for `mod` it assumes `rhs > 0`). If these constraints are later found
200 // not to hold, then the bound is invalid.
201 LogicalResult status = cstr.addBound(
202 type, pos,
203 AffineMap::get(cstr.getNumDimVars(), cstr.getNumSymbolVars(), expr),
207 if (failed(status)) {
208 // Not all semi-affine expressions are not yet supported by
209 // FlatLinearConstraints. However, we can just ignore such failures here.
210 // Even without this bound, there may be enough information in the
211 // constraint system to compute the requested bound. In case this bound is
212 // actually needed, `computeBound` will return `failure`.
213 LDBG() << "Failed to add bound: " << expr << "\n";
214 }
215}
216
218 std::optional<int64_t> dim) {
219#ifndef NDEBUG
220 assertValidValueDim(value, dim, options);
221#endif // NDEBUG
222
223 // Check if the value/dim is statically known. In that case, an affine
224 // constant expression should be returned. This allows us to support
225 // multiplications with constants. (Multiplications of two columns in the
226 // constraint set is not supported.)
227 std::optional<int64_t> constSize = std::nullopt;
228 auto shapedType = dyn_cast<ShapedType>(value.getType());
229 if (shapedType) {
230 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
231 constSize = shapedType.getDimSize(*dim);
232 } else if (auto constInt = ::getConstantIntValue(value)) {
233 constSize = *constInt;
234 }
235
236 // If the value/dim is already mapped, return the corresponding expression
237 // directly.
238 ValueDim valueDim = std::make_pair(value, dim.value_or(kIndexValue));
239 if (valueDimToPosition.contains(valueDim)) {
240 // If it is a constant, return an affine constant expression. Otherwise,
241 // return an affine expression that represents the respective column in the
242 // constraint set.
243 if (constSize)
244 return builder.getAffineConstantExpr(*constSize);
245 return getPosExpr(getPos(value, dim));
246 }
247
248 if (constSize) {
249 // Constant index value/dim: add column to the constraint set, add EQ bound
250 // and return an affine constant expression without pushing the newly added
251 // column to the worklist.
252 (void)insert(value, dim, /*isSymbol=*/true, /*addToWorklist=*/false);
253 if (shapedType)
254 bound(value)[*dim] == *constSize;
255 else
256 bound(value) == *constSize;
257 return builder.getAffineConstantExpr(*constSize);
258 }
259
260 // Dynamic value/dim: insert column to the constraint set and put it on the
261 // worklist. Return an affine expression that represents the newly inserted
262 // column in the constraint set.
263 return getPosExpr(insert(value, dim, /*isSymbol=*/true));
264}
265
267 if (Value value = llvm::dyn_cast_if_present<Value>(ofr))
268 return getExpr(value, /*dim=*/std::nullopt);
269 auto constInt = ::getConstantIntValue(ofr);
270 assert(constInt.has_value() && "expected Integer constant");
271 return builder.getAffineConstantExpr(*constInt);
272}
273
275 return builder.getAffineConstantExpr(constant);
276}
277
279 std::optional<int64_t> dim,
280 bool isSymbol, bool addToWorklist) {
281#ifndef NDEBUG
282 assertValidValueDim(value, dim, options);
283#endif // NDEBUG
284
285 ValueDim valueDim = std::make_pair(value, dim.value_or(kIndexValue));
286 assert(!valueDimToPosition.contains(valueDim) && "already mapped");
287 int64_t pos = isSymbol ? cstr.appendVar(VarKind::Symbol)
288 : cstr.appendVar(VarKind::SetDim);
289 LDBG() << "Inserting constraint set column " << pos << " for: " << value
290 << " (dim: " << dim.value_or(kIndexValue)
291 << ", owner: " << getOwnerOfValue(value)->getName() << ")";
292 positionToValueDim.insert(positionToValueDim.begin() + pos, valueDim);
293 // Update reverse mapping.
294 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
295 if (positionToValueDim[i].has_value())
297
298 // Do not add block arguments from non-entry blocks to the worklist. The
299 // ValueBoundsOpInterface cannot derive any bounds for such values (they
300 // arise from unstructured control flow), so putting them on the worklist
301 // would be a no-op. More importantly, suppressing the worklist push ensures
302 // that processWorklist never calls getExpr on such a value a second time,
303 // which would otherwise cause the same value to be looked up as already
304 // mapped (triggering an unintended bug path).
305 if (addToWorklist &&
306 (!isa<BlockArgument>(value) ||
307 cast<BlockArgument>(value).getOwner()->isEntryBlock())) {
308 LDBG() << "Push to worklist: " << value
309 << " (dim: " << dim.value_or(kIndexValue) << ")";
310 worklist.push(pos);
311 }
312
313 return pos;
314}
315
317 int64_t pos = isSymbol ? cstr.appendVar(VarKind::Symbol)
318 : cstr.appendVar(VarKind::SetDim);
319 LDBG() << "Inserting anonymous constraint set column " << pos;
320 positionToValueDim.insert(positionToValueDim.begin() + pos, std::nullopt);
321 // Update reverse mapping.
322 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
323 if (positionToValueDim[i].has_value())
325 return pos;
326}
327
329 const ValueDimList &operands,
330 bool isSymbol) {
331 assert(map.getNumResults() == 1 && "expected affine map with one result");
332 int64_t pos = insert(isSymbol);
333
334 // Add map and operands to the constraint set. Dimensions are converted to
335 // symbols. All operands are added to the worklist (unless they were already
336 // processed).
337 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
338 return getExpr(v.first, v.second);
339 };
340 SmallVector<AffineExpr> dimReplacements = llvm::map_to_vector(
341 ArrayRef(operands).take_front(map.getNumDims()), mapper);
342 SmallVector<AffineExpr> symReplacements = llvm::map_to_vector(
343 ArrayRef(operands).drop_front(map.getNumDims()), mapper);
344 addBound(
346 map.getResult(0).replaceDimsAndSymbols(dimReplacements, symReplacements));
347
348 return pos;
349}
350
352 return insert(var.map, var.mapOperands, isSymbol);
353}
354
356 std::optional<int64_t> dim) const {
357#ifndef NDEBUG
358 assertValidValueDim(value, dim, options);
359#endif // NDEBUG
360 LDBG() << "Getting pos for: " << value
361 << " (dim: " << dim.value_or(kIndexValue)
362 << ", owner: " << getOwnerOfValue(value)->getName() << ")";
363 auto it =
364 valueDimToPosition.find(std::make_pair(value, dim.value_or(kIndexValue)));
365 assert(it != valueDimToPosition.end() && "expected mapped entry");
366 return it->second;
367}
368
370 assert(pos >= 0 && pos < cstr.getNumDimAndSymbolVars() && "invalid position");
371 return pos < cstr.getNumDimVars()
372 ? builder.getAffineDimExpr(pos)
373 : builder.getAffineSymbolExpr(pos - cstr.getNumDimVars());
374}
375
377 std::optional<int64_t> dim) const {
378 auto it =
379 valueDimToPosition.find(std::make_pair(value, dim.value_or(kIndexValue)));
380 return it != valueDimToPosition.end();
381}
382
384 LDBG() << "Processing value bounds worklist...";
385 while (!worklist.empty()) {
386 int64_t pos = worklist.front();
387 worklist.pop();
388 assert(positionToValueDim[pos].has_value() &&
389 "did not expect std::nullopt on worklist");
390 ValueDim valueDim = *positionToValueDim[pos];
391 Value value = valueDim.first;
392 int64_t dim = valueDim.second;
393
394 // Check for static dim size.
395 if (dim != kIndexValue) {
396 auto shapedType = cast<ShapedType>(value.getType());
397 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
398 bound(value)[dim] == getExpr(shapedType.getDimSize(dim));
399 continue;
400 }
401 }
402
403 // Do not process any further if the stop condition is met.
404 auto maybeDim = dim == kIndexValue ? std::nullopt : std::make_optional(dim);
405 if (stopCondition(value, maybeDim, *this)) {
406 LDBG() << "Stop condition met for: " << value << " (dim: " << maybeDim
407 << ")";
408 continue;
409 }
410
411 // Query `ValueBoundsOpInterface` for constraints. New items may be added to
412 // the worklist.
413 auto valueBoundsOp =
414 dyn_cast<ValueBoundsOpInterface>(getOwnerOfValue(value));
415 LDBG() << "Query value bounds for: " << value
416 << " (owner: " << getOwnerOfValue(value)->getName() << ")";
417 if (valueBoundsOp) {
418 if (dim == kIndexValue) {
419 valueBoundsOp.populateBoundsForIndexValue(value, *this);
420 } else {
421 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *this);
422 }
423 continue;
424 }
425 LDBG() << "--> ValueBoundsOpInterface not implemented";
426
427 // If the op does not implement `ValueBoundsOpInterface`, check if it
428 // implements the `DestinationStyleOpInterface`. OpResults of such ops are
429 // tied to OpOperands. Tied values have the same shape.
430 auto dstOp = value.getDefiningOp<DestinationStyleOpInterface>();
431 if (!dstOp || dim == kIndexValue)
432 continue;
433 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
434 bound(value)[dim] == getExpr(tiedOperand, dim);
435 }
436}
437
439 assert(pos >= 0 && pos < static_cast<int64_t>(positionToValueDim.size()) &&
440 "invalid position");
441 cstr.projectOut(pos);
442 if (positionToValueDim[pos].has_value()) {
443 bool erased = valueDimToPosition.erase(*positionToValueDim[pos]);
444 (void)erased;
445 assert(erased && "inconsistent reverse mapping");
446 }
447 positionToValueDim.erase(positionToValueDim.begin() + pos);
448 // Update reverse mapping.
449 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
450 if (positionToValueDim[i].has_value())
452}
453
455 function_ref<bool(ValueDim)> condition) {
456 int64_t nextPos = 0;
457 while (nextPos < static_cast<int64_t>(positionToValueDim.size())) {
458 if (positionToValueDim[nextPos].has_value() &&
459 condition(*positionToValueDim[nextPos])) {
460 projectOut(nextPos);
461 // The column was projected out so another column is now at that position.
462 // Do not increase the counter.
463 } else {
464 ++nextPos;
465 }
466 }
467}
468
470 std::optional<int64_t> except) {
471 int64_t nextPos = 0;
472 while (nextPos < static_cast<int64_t>(positionToValueDim.size())) {
473 if (positionToValueDim[nextPos].has_value() || except == nextPos) {
474 ++nextPos;
475 } else {
476 projectOut(nextPos);
477 // The column was projected out so another column is now at that position.
478 // Do not increase the counter.
479 }
480 }
481}
482
484 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
487 MLIRContext *ctx = var.getContext();
488 int64_t ubAdjustment = options.closedUB ? 0 : 1;
489 Builder b(ctx);
490 mapOperands.clear();
491
492 // Process the backward slice of `value` (i.e., reverse use-def chain) until
493 // `stopCondition` is met.
495 int64_t pos = cstr.insert(var, /*isSymbol=*/false);
496 assert(pos == 0 && "expected first column");
497 cstr.processWorklist();
498
499 // Project out all variables (apart from `valueDim`) that do not match the
500 // stop condition.
501 cstr.projectOut([&](ValueDim p) {
502 auto maybeDim =
503 p.second == kIndexValue ? std::nullopt : std::make_optional(p.second);
504 return !stopCondition(p.first, maybeDim, cstr);
505 });
506 cstr.projectOutAnonymous(/*except=*/pos);
507
508 // Compute lower and upper bounds for `valueDim`.
509 SmallVector<AffineMap> lb(1), ub(1);
510 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &ub,
511 /*closedUB=*/true);
512
513 // Note: There are TODOs in the implementation of `getSliceBounds`. In such a
514 // case, no lower/upper bound can be computed at the moment.
515 // EQ, UB bounds: upper bound is needed.
516 if ((type != BoundType::LB) &&
517 (ub.empty() || !ub[0] || ub[0].getNumResults() == 0))
518 return failure();
519 // EQ, LB bounds: lower bound is needed.
520 if ((type != BoundType::UB) &&
521 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
522 return failure();
523
524 // TODO: Generate an affine map with multiple results.
525 if (type != BoundType::LB)
526 assert(ub.size() == 1 && ub[0].getNumResults() == 1 &&
527 "multiple bounds not supported");
528 if (type != BoundType::UB)
529 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
530 "multiple bounds not supported");
531
532 // EQ bound: lower and upper bound must match.
533 if (type == BoundType::EQ && ub[0] != lb[0])
534 return failure();
535
537 if (type == BoundType::EQ || type == BoundType::LB) {
538 bound = lb[0];
539 } else {
540 // Computed UB is a closed bound.
541 bound = AffineMap::get(ub[0].getNumDims(), ub[0].getNumSymbols(),
542 ub[0].getResult(0) + ubAdjustment);
543 }
544
545 // Gather all SSA values that are used in the computed bound.
546 assert(cstr.cstr.getNumDimAndSymbolVars() == cstr.positionToValueDim.size() &&
547 "inconsistent mapping state");
548 SmallVector<AffineExpr> replacementDims, replacementSymbols;
549 int64_t numDims = 0, numSymbols = 0;
550 for (int64_t i = 0; i < cstr.cstr.getNumDimAndSymbolVars(); ++i) {
551 // Skip `value`.
552 if (i == pos)
553 continue;
554 // Check if the position `i` is used in the generated bound. If so, it must
555 // be included in the generated affine.apply op.
556 bool used = false;
557 bool isDim = i < cstr.cstr.getNumDimVars();
558 if (isDim) {
559 if (bound.isFunctionOfDim(i))
560 used = true;
561 } else {
562 if (bound.isFunctionOfSymbol(i - cstr.cstr.getNumDimVars()))
563 used = true;
564 }
565
566 if (!used) {
567 // Not used: Remove dim/symbol from the result.
568 if (isDim) {
569 replacementDims.push_back(b.getAffineConstantExpr(0));
570 } else {
571 replacementSymbols.push_back(b.getAffineConstantExpr(0));
572 }
573 continue;
574 }
575
576 if (isDim) {
577 replacementDims.push_back(b.getAffineDimExpr(numDims++));
578 } else {
579 replacementSymbols.push_back(b.getAffineSymbolExpr(numSymbols++));
580 }
581
582 assert(cstr.positionToValueDim[i].has_value() &&
583 "cannot build affine map in terms of anonymous column");
584 ValueBoundsConstraintSet::ValueDim valueDim = *cstr.positionToValueDim[i];
585 Value value = valueDim.first;
586 int64_t dim = valueDim.second;
588 // An index-typed/integer-typed value is used: it can be used directly in
589 // the computed bound.
590 assert(isIndexLikeType(value.getType(), options) &&
591 "expected index or integer type");
592 mapOperands.push_back(std::make_pair(value, std::nullopt));
593 continue;
594 }
595
596 assert(cast<ShapedType>(value.getType()).isDynamicDim(dim) &&
597 "expected dynamic dim");
598 mapOperands.push_back(std::make_pair(value, dim));
599 }
600
601 resultMap = bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
602 numDims, numSymbols);
603 return success();
604}
605
607 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
608 const Variable &var, ValueDimList dependencies,
610 return computeBound(
611 resultMap, mapOperands, type, var,
612 [&](Value v, std::optional<int64_t> d, ValueBoundsConstraintSet &cstr) {
613 return llvm::is_contained(dependencies, std::make_pair(v, d));
614 },
615 options);
616}
617
619 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
620 const Variable &var, ValueRange independencies,
622 // Return "true" if the given value is independent of all values in
623 // `independencies`. I.e., neither the value itself nor any value in the
624 // backward slice (reverse use-def chain) is contained in `independencies`.
625 auto isIndependent = [&](Value v) {
627 DenseSet<Value> visited;
628 worklist.push_back(v);
629 while (!worklist.empty()) {
630 Value next = worklist.pop_back_val();
631 if (!visited.insert(next).second)
632 continue;
633 if (llvm::is_contained(independencies, next))
634 return false;
635 // TODO: DominanceInfo could be used to stop the traversal early.
636 Operation *op = next.getDefiningOp();
637 if (!op)
638 continue;
639 worklist.append(op->getOperands().begin(), op->getOperands().end());
640 }
641 return true;
642 };
643
644 // Reify bounds in terms of any independent values.
645 return computeBound(
646 resultMap, mapOperands, type, var,
647 [&](Value v, std::optional<int64_t> d, ValueBoundsConstraintSet &cstr) {
648 return isIndependent(v);
649 },
650 options);
651}
652
654 presburger::BoundType type, const Variable &var,
656 // Default stop condition if none was specified: Keep adding constraints until
657 // a bound could be computed.
658 int64_t pos = 0;
659 auto defaultStopCondition = [&](Value v, std::optional<int64_t> dim,
661 return cstr.cstr.getConstantBound64(type, pos).has_value();
662 };
663
665 var.getContext(), stopCondition ? stopCondition : defaultStopCondition,
666 options);
667 pos = cstr.populateConstraints(var.map, var.mapOperands);
668 assert(pos == 0 && "expected `map` is the first column");
669
670 // Compute constant bound for `valueDim`.
671 int64_t ubAdjustment = options.closedUB ? 0 : 1;
672 if (auto bound = cstr.cstr.getConstantBound64(type, pos))
673 return type == BoundType::UB ? *bound + ubAdjustment : *bound;
674 return failure();
675}
676
678 std::optional<int64_t> dim) {
679#ifndef NDEBUG
680 assertValidValueDim(value, dim, options);
681#endif // NDEBUG
682
683 // `getExpr` pushes the value/dim onto the worklist (unless it was already
684 // analyzed).
685 (void)getExpr(value, dim);
686 // Process all values/dims on the worklist. This may traverse and analyze
687 // additional IR, depending the current stop function.
689}
690
692 ValueDimList operands) {
693 int64_t pos = insert(map, std::move(operands), /*isSymbol=*/false);
694 // Process the backward slice of `operands` (i.e., reverse use-def chain)
695 // until `stopCondition` is met.
697 return pos;
698}
699
700FailureOr<int64_t>
702 std::optional<int64_t> dim1,
703 std::optional<int64_t> dim2) {
704#ifndef NDEBUG
705 assertValidValueDim(value1, dim1, /*options=*/{});
706 assertValidValueDim(value2, dim2, /*options=*/{});
707#endif // NDEBUG
708
709 Builder b(value1.getContext());
710 AffineMap map = AffineMap::get(/*dimCount=*/2, /*symbolCount=*/0,
711 b.getAffineDimExpr(0) - b.getAffineDimExpr(1));
713 Variable(map, {{value1, dim1}, {value2, dim2}}));
714}
715
718 int64_t rhsPos) {
719 // This function returns "true" if "lhs CMP rhs" is proven to hold.
720 //
721 // Example for ComparisonOperator::LE and index-typed values: We would like to
722 // prove that lhs <= rhs. Proof by contradiction: add the inverse
723 // relation (lhs > rhs) to the constraint set and check if the resulting
724 // constraint set is "empty" (i.e. has no solution). In that case,
725 // lhs > rhs must be incorrect and we can deduce that lhs <= rhs holds.
726
727 // We cannot prove anything if the constraint set is already empty.
728 if (cstr.isEmpty()) {
729 LDBG() << "cannot compare value/dims: constraint system is already empty";
730 return false;
731 }
732
733 // EQ can be expressed as LE and GE.
734 if (cmp == EQ)
735 return comparePos(lhsPos, ComparisonOperator::LE, rhsPos) &&
736 comparePos(lhsPos, ComparisonOperator::GE, rhsPos);
737
738 // Construct inequality.
739 // Inline size chosen empirically based on compilation profiling.
740 // Profiled: 3.2M calls, avg=4.0+-2.3. N=8 covers ~95% of cases inline.
741 SmallVector<int64_t, 8> eq(cstr.getNumCols(), 0);
742 if (cmp == LT || cmp == LE) {
743 ++eq[lhsPos];
744 --eq[rhsPos];
745 } else if (cmp == GT || cmp == GE) {
746 --eq[lhsPos];
747 ++eq[rhsPos];
748 } else {
749 llvm_unreachable("unsupported comparison operator");
750 }
751 if (cmp == LE || cmp == GE)
752 eq[cstr.getNumCols() - 1] -= 1;
753
754 // Add inequality to the constraint set and check if it made the constraint
755 // set empty.
756 int64_t ineqPos = cstr.getNumInequalities();
757 cstr.addInequality(eq);
758 bool isEmpty = cstr.isEmpty();
759 cstr.removeInequality(ineqPos);
760 return isEmpty;
761}
762
764 int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos) {
765 auto strongCmp = [&](ComparisonOperator cmp,
766 ComparisonOperator negCmp) -> FailureOr<bool> {
767 if (comparePos(lhsPos, cmp, rhsPos))
768 return true;
769 if (comparePos(lhsPos, negCmp, rhsPos))
770 return false;
771 return failure();
772 };
773 switch (cmp) {
783 std::optional<bool> le =
785 if (!le)
786 return failure();
787 if (!*le)
788 return false;
789 std::optional<bool> ge =
791 if (!ge)
792 return failure();
793 if (!*ge)
794 return false;
795 return true;
796 }
797 }
798 llvm_unreachable("invalid comparison operator");
799}
800
803 const Variable &rhs) {
804 int64_t lhsPos = populateConstraints(lhs.map, lhs.mapOperands);
805 int64_t rhsPos = populateConstraints(rhs.map, rhs.mapOperands);
806 return comparePos(lhsPos, cmp, rhsPos);
807}
808
811 const Variable &rhs) {
812 int64_t lhsPos = -1, rhsPos = -1;
813 auto stopCondition = [&](Value v, std::optional<int64_t> dim,
815 // Keep processing as long as lhs/rhs were not processed.
816 if (size_t(lhsPos) >= cstr.positionToValueDim.size() ||
817 size_t(rhsPos) >= cstr.positionToValueDim.size())
818 return false;
819 // Keep processing as long as the relation cannot be proven.
820 return cstr.comparePos(lhsPos, cmp, rhsPos);
821 };
823 lhsPos = cstr.populateConstraints(lhs.map, lhs.mapOperands);
824 rhsPos = cstr.populateConstraints(rhs.map, rhs.mapOperands);
825 return cstr.comparePos(lhsPos, cmp, rhsPos);
826}
827
830 const Variable &rhs) {
831 int64_t lhsPos = -1, rhsPos = -1;
832 auto stopCondition = [&](Value v, std::optional<int64_t> dim,
834 // Keep processing as long as lhs/rhs were not processed.
835 if (size_t(lhsPos) >= cstr.positionToValueDim.size() ||
836 size_t(rhsPos) >= cstr.positionToValueDim.size())
837 return false;
838 // Keep processing as long as the strong relation cannot be proven.
839 FailureOr<bool> ordered = cstr.strongComparePos(lhsPos, cmp, rhsPos);
840 return failed(ordered);
841 };
843 lhsPos = cstr.populateConstraints(lhs.map, lhs.mapOperands);
844 rhsPos = cstr.populateConstraints(rhs.map, rhs.mapOperands);
845 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
846}
847
849 const Variable &var2) {
850 return strongCompare(var1, ComparisonOperator::EQ, var2);
851}
852
854 MLIRContext *ctx, const HyperrectangularSlice &slice1,
855 const HyperrectangularSlice &slice2) {
856 assert(slice1.getMixedOffsets().size() == slice2.getMixedOffsets().size() &&
857 "expected slices of same rank");
858 assert(slice1.getMixedSizes().size() == slice2.getMixedSizes().size() &&
859 "expected slices of same rank");
860 assert(slice1.getMixedStrides().size() == slice2.getMixedStrides().size() &&
861 "expected slices of same rank");
862
863 Builder b(ctx);
864 bool foundUnknownBound = false;
865 for (int64_t i = 0, e = slice1.getMixedOffsets().size(); i < e; ++i) {
866 AffineMap map =
867 AffineMap::get(/*dimCount=*/0, /*symbolCount=*/4,
868 b.getAffineSymbolExpr(0) +
869 b.getAffineSymbolExpr(1) * b.getAffineSymbolExpr(2) -
870 b.getAffineSymbolExpr(3));
871 {
872 // Case 1: Slices are guaranteed to be non-overlapping if
873 // offset1 + size1 * stride1 <= offset2 (for at least one dimension).
874 SmallVector<OpFoldResult> ofrOperands;
875 ofrOperands.push_back(slice1.getMixedOffsets()[i]);
876 ofrOperands.push_back(slice1.getMixedSizes()[i]);
877 ofrOperands.push_back(slice1.getMixedStrides()[i]);
878 ofrOperands.push_back(slice2.getMixedOffsets()[i]);
879 SmallVector<Value> valueOperands;
880 AffineMap foldedMap =
881 foldAttributesIntoMap(b, map, ofrOperands, valueOperands);
882 FailureOr<int64_t> constBound = computeConstantBound(
883 presburger::BoundType::EQ, Variable(foldedMap, valueOperands));
884 foundUnknownBound |= failed(constBound);
885 if (succeeded(constBound) && *constBound <= 0)
886 return false;
887 }
888 {
889 // Case 2: Slices are guaranteed to be non-overlapping if
890 // offset2 + size2 * stride2 <= offset1 (for at least one dimension).
891 SmallVector<OpFoldResult> ofrOperands;
892 ofrOperands.push_back(slice2.getMixedOffsets()[i]);
893 ofrOperands.push_back(slice2.getMixedSizes()[i]);
894 ofrOperands.push_back(slice2.getMixedStrides()[i]);
895 ofrOperands.push_back(slice1.getMixedOffsets()[i]);
896 SmallVector<Value> valueOperands;
897 AffineMap foldedMap =
898 foldAttributesIntoMap(b, map, ofrOperands, valueOperands);
899 FailureOr<int64_t> constBound = computeConstantBound(
900 presburger::BoundType::EQ, Variable(foldedMap, valueOperands));
901 foundUnknownBound |= failed(constBound);
902 if (succeeded(constBound) && *constBound <= 0)
903 return false;
904 }
905 }
906
907 // If at least one bound could not be computed, we cannot be certain that the
908 // slices are really overlapping.
909 if (foundUnknownBound)
910 return failure();
911
912 // All bounds could be computed and none of the above cases applied.
913 // Therefore, the slices are guaranteed to overlap.
914 return true;
915}
916
918 MLIRContext *ctx, const HyperrectangularSlice &slice1,
919 const HyperrectangularSlice &slice2) {
920 assert(slice1.getMixedOffsets().size() == slice2.getMixedOffsets().size() &&
921 "expected slices of same rank");
922 assert(slice1.getMixedSizes().size() == slice2.getMixedSizes().size() &&
923 "expected slices of same rank");
924 assert(slice1.getMixedStrides().size() == slice2.getMixedStrides().size() &&
925 "expected slices of same rank");
926
927 // The two slices are equivalent if all of their offsets, sizes and strides
928 // are equal. If equality cannot be determined for at least one of those
929 // values, equivalence cannot be determined and this function returns
930 // "failure".
931 for (auto [offset1, offset2] :
932 llvm::zip_equal(slice1.getMixedOffsets(), slice2.getMixedOffsets())) {
933 FailureOr<bool> equal = areEqual(offset1, offset2);
934 if (failed(equal))
935 return failure();
936 if (!equal.value())
937 return false;
938 }
939 for (auto [size1, size2] :
940 llvm::zip_equal(slice1.getMixedSizes(), slice2.getMixedSizes())) {
941 FailureOr<bool> equal = areEqual(size1, size2);
942 if (failed(equal))
943 return failure();
944 if (!equal.value())
945 return false;
946 }
947 for (auto [stride1, stride2] :
948 llvm::zip_equal(slice1.getMixedStrides(), slice2.getMixedStrides())) {
949 FailureOr<bool> equal = areEqual(stride1, stride2);
950 if (failed(equal))
951 return failure();
952 if (!equal.value())
953 return false;
954 }
955 return true;
956}
957
959 llvm::errs() << "==========\nColumns:\n";
960 llvm::errs() << "(column\tdim\tvalue)\n";
961 for (auto [index, valueDim] : llvm::enumerate(positionToValueDim)) {
962 llvm::errs() << " " << index << "\t";
963 if (valueDim) {
964 if (valueDim->second == kIndexValue) {
965 llvm::errs() << "n/a\t";
966 } else {
967 llvm::errs() << valueDim->second << "\t";
968 }
969 llvm::errs() << getOwnerOfValue(valueDim->first)->getName() << " ";
970 if (OpResult result = dyn_cast<OpResult>(valueDim->first)) {
971 llvm::errs() << "(result " << result.getResultNumber() << ")";
972 } else {
973 llvm::errs() << "(bbarg "
974 << cast<BlockArgument>(valueDim->first).getArgNumber()
975 << ")";
976 }
977 llvm::errs() << "\n";
978 } else {
979 llvm::errs() << "n/a\tn/a\n";
980 }
981 }
982 llvm::errs() << "\nConstraint set:\n";
983 cstr.dump();
984 llvm::errs() << "==========\n";
985}
986
989 assert(!this->dim.has_value() && "dim was already set");
990 this->dim = dim;
991#ifndef NDEBUG
992 assertValidValueDim(value, this->dim, cstr.options);
993#endif // NDEBUG
994 return *this;
995}
996
998#ifndef NDEBUG
999 assertValidValueDim(value, this->dim, cstr.options);
1000#endif // NDEBUG
1001 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
1002}
1003
1007
1011
1013#ifndef NDEBUG
1014 assertValidValueDim(value, this->dim, cstr.options);
1015#endif // NDEBUG
1016 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
1017}
1018
1020#ifndef NDEBUG
1021 assertValidValueDim(value, this->dim, cstr.options);
1022#endif // NDEBUG
1023 cstr.addBound(BoundType::EQ, cstr.getPos(value, this->dim), expr);
1024}
1025
1029
1033
1037
1041
1045
1049
1053
1057
1061
return success()
static bool isIndexLikeType(Type type, ValueBoundsOptions options)
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static llvm::ManagedStatic< PassManagerOptions > options
static bool isIndexOrIntegerType(Type type)
static Operation * getOwnerOfValue(Value value)
static void assertValidValueDim(Value value, std::optional< int64_t > dim, ValueBoundsOptions options)
Base type for affine expression.
Definition AffineExpr.h:68
AffineExpr replaceDimsAndSymbols(ArrayRef< AffineExpr > dimReplacements, ArrayRef< AffineExpr > symReplacements) const
This method substitutes any uses of dimensions and symbols (e.g.
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumDims() const
unsigned getNumResults() const
AffineMap replaceDimsAndSymbols(ArrayRef< AffineExpr > dimReplacements, ArrayRef< AffineExpr > symReplacements, unsigned numResultDims, unsigned numResultSyms) const
This method substitutes any uses of dimensions and symbols (e.g.
AffineExpr getResult(unsigned idx) const
AffineMap replace(AffineExpr expr, AffineExpr replacement, unsigned numResultDims, unsigned numResultSyms) const
Sparse replace method.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
A hyperrectangular slice, represented as a list of offsets, sizes and strides.
HyperrectangularSlice(ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ArrayRef< OpFoldResult > strides)
ArrayRef< OpFoldResult > getMixedStrides() const
ArrayRef< OpFoldResult > getMixedSizes() const
ArrayRef< OpFoldResult > getMixedOffsets() const
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class represents a single result from folding an operation.
MLIRContext * getContext() const
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:252
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:116
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:404
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIndex() const
Definition Types.cpp:56
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
Helper class that builds a bound for a shaped value dimension or index-typed value.
BoundBuilder & operator[](int64_t dim)
Specify a dimension, assuming that the underlying value is a shaped value.
A variable that can be added to the constraint set as a "column".
Variable(OpFoldResult ofr)
Construct a variable for an index-typed attribute or SSA value.
static bool compare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Return "true" if "lhs cmp rhs" was proven to hold.
static FailureOr< bool > areEqual(const Variable &var1, const Variable &var2)
Compute whether the given variables are equal.
static LogicalResult computeBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, StopConditionFn stopCondition, ValueBoundsOptions options={})
Compute a bound for the given variable.
DenseMap< ValueDim, int64_t > valueDimToPosition
Reverse mapping of values/shape dimensions to columns.
void processWorklist()
Iteratively process all elements on the worklist until an index-typed value or shaped value meets sto...
bool addConservativeSemiAffineBounds
Should conservative bounds be added for semi-affine expressions.
AffineExpr getExpr(Value value, std::optional< int64_t > dim=std::nullopt)
Return an expression that represents the given index-typed value or shaped value dimension.
static FailureOr< bool > areEquivalentSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1, const HyperrectangularSlice &slice2)
Return "true" if the given slices are guaranteed to be equivalent.
ValueBoundsConstraintSet(MLIRContext *ctx, const StopConditionFn &stopCondition, ValueBoundsOptions options={}, bool addConservativeSemiAffineBounds=false)
void projectOut(int64_t pos)
Project out the given column in the constraint set.
std::function< bool( Value, std::optional< int64_t >, ValueBoundsConstraintSet &cstr)> StopConditionFn
The stop condition when traversing the backward slice of a shaped value/ index-type value.
static FailureOr< int64_t > computeConstantDelta(Value value1, Value value2, std::optional< int64_t > dim1=std::nullopt, std::optional< int64_t > dim2=std::nullopt)
Compute a constant delta between the given two values.
static llvm::FailureOr< bool > strongCompare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
This function is similar to ValueBoundsConstraintSet::compare, except that it returns false if !...
static FailureOr< int64_t > computeConstantBound(presburger::BoundType type, const Variable &var, const StopConditionFn &stopCondition=nullptr, ValueBoundsOptions options={})
Compute a constant bound for the given variable.
void addBound(presburger::BoundType type, int64_t pos, AffineExpr expr)
Bound the given column in the underlying constraint set by the given expression.
StopConditionFn stopCondition
The current stop condition function.
ComparisonOperator
Comparison operator for ValueBoundsConstraintSet::compare.
BoundBuilder bound(Value value)
Add a bound for the given index-typed value or shaped value.
int64_t getPos(Value value, std::optional< int64_t > dim=std::nullopt) const
Return the column position of the given value/dimension.
int64_t insert(Value value, std::optional< int64_t > dim, bool isSymbol=true, bool addToWorklist=true)
Insert a value/dimension into the constraint set.
bool comparePos(int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos)
Return "true" if, based on the current state of the constraint system, "lhs cmp rhs" was proven to ho...
ValueBoundsOptions options
Options that control value bound computation.
SmallVector< std::optional< ValueDim >, 4 > positionToValueDim
Mapping of columns to values/shape dimensions.
void dump() const
Debugging only: Dump the constraint set and the column-to-value/dim mapping to llvm::errs.
std::queue< int64_t > worklist
Worklist of values/shape dimensions that have not been processed yet.
FlatLinearConstraints cstr
Constraint system of equalities and inequalities.
static LogicalResult computeIndependentBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, ValueRange independencies, ValueBoundsOptions options={})
Compute a bound in that is independent of all values in independencies.
bool isMapped(Value value, std::optional< int64_t > dim=std::nullopt) const
Return "true" if the given value/dim is mapped (i.e., has a corresponding column in the constraint sy...
llvm::FailureOr< bool > strongComparePos(int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos)
Return "true" if, based on the current state of the constraint system, "lhs cmp rhs" was proven to ho...
AffineExpr getPosExpr(int64_t pos)
Return an affine expression that represents column pos in the constraint set.
void projectOutAnonymous(std::optional< int64_t > except=std::nullopt)
static FailureOr< bool > areOverlappingSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1, const HyperrectangularSlice &slice2)
Return "true" if the given slices are guaranteed to be overlapping.
std::pair< Value, int64_t > ValueDim
An index-typed value or the dimension of a shaped-type value.
void populateConstraints(Value value, std::optional< int64_t > dim)
Traverse the IR starting from the given value/dim and populate constraints as long as the stop condit...
Builder builder
Builder for constructing affine expressions.
bool populateAndCompare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Populate constraints for lhs/rhs (until the stop condition is met).
static constexpr int64_t kIndexValue
Dimension identifier to indicate a value is index-typed.
static LogicalResult computeDependentBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, ValueDimList dependencies, ValueBoundsOptions options={})
Compute a bound in terms of the values/dimensions in dependencies.
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
MLIRContext * getContext() const
Utility to get the associated MLIRContext that this value is defined in.
Definition Value.h:108
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition CallGraph.h:229
BoundType
The type of bound: equal, lower bound or upper bound.
VarKind
Kind of variable.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
bool operator==(StringAttr lhs, std::nullptr_t)
Define comparisons for StringAttr against nullptr and itself to avoid the StringRef overloads from be...
SmallVector< std::pair< Value, std::optional< int64_t > >, 2 > ValueDimList
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
AffineMap foldAttributesIntoMap(Builder &b, AffineMap map, ArrayRef< OpFoldResult > operands, SmallVector< Value > &remainingValues)
Fold all attributes among the given operands into the affine map.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
Options that control value bound computation.