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
38
45
52
59
61 if (auto bbArg = dyn_cast<BlockArgument>(value))
62 return bbArg.getOwner()->getParentOp();
63 return value.getDefiningOp();
64}
65
69 : mixedOffsets(offsets), mixedSizes(sizes), mixedStrides(strides) {
70 assert(offsets.size() == sizes.size() &&
71 "expected same number of offsets, sizes, strides");
72 assert(offsets.size() == strides.size() &&
73 "expected same number of offsets, sizes, strides");
74}
75
78 : mixedOffsets(offsets), mixedSizes(sizes) {
79 assert(offsets.size() == sizes.size() &&
80 "expected same number of offsets and sizes");
81 // Assume that all strides are 1.
82 if (offsets.empty())
83 return;
84 MLIRContext *ctx = offsets.front().getContext();
85 mixedStrides.append(offsets.size(), Builder(ctx).getIndexAttr(1));
86}
87
91
92/// If ofr is a constant integer or an IntegerAttr, return the integer.
93static std::optional<int64_t> getConstantIntValue(OpFoldResult ofr) {
94 // Case 1: Check for Constant integer.
95 if (auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
96 APSInt intVal;
97 if (matchPattern(val, m_ConstantInt(&intVal)))
98 return intVal.getSExtValue();
99 return std::nullopt;
100 }
101 // Case 2: Check for IntegerAttr.
102 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
103 if (auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
104 return intAttr.getValue().getSExtValue();
105 return std::nullopt;
106}
107
108[[maybe_unused]] static bool isIndexOrIntegerType(Type type) {
109 return type.isIndex() || type.isInteger();
110}
111
112[[maybe_unused]] static bool isIndexLikeType(Type type,
114 return type.isIndex() || (options.allowIntegerType && type.isInteger());
115}
116
119
121 : Variable(static_cast<OpFoldResult>(indexValue)) {}
122
124 : Variable(static_cast<OpFoldResult>(shapedValue), std::optional(dim)) {}
125
127 std::optional<int64_t> dim) {
128 Builder b(ofr.getContext());
129 if (auto constInt = ::getConstantIntValue(ofr)) {
130 assert(!dim && "expected no dim for index/integer-typed values");
131 map = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/0,
132 b.getAffineConstantExpr(*constInt));
133 return;
134 }
135 Value value = cast<Value>(ofr);
136#ifndef NDEBUG
137 if (dim) {
138 assert(isa<ShapedType>(value.getType()) && "expected shaped type");
139 } else {
140 assert(isIndexOrIntegerType(value.getType()) &&
141 "expected index or integer type");
142 }
143#endif // NDEBUG
144 map = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/1,
145 b.getAffineSymbolExpr(0));
146 mapOperands.emplace_back(value, dim);
147}
148
150 ArrayRef<Variable> mapOperands) {
151 assert(map.getNumResults() == 1 && "expected single result");
152
153 // Turn all dims into symbols.
154 Builder b(map.getContext());
155 // Inline size chosen empirically based on compilation profiling.
156 // Profiled: 490K calls, avg=1.5+-0.6. N=4 covers >99% of cases inline.
157 SmallVector<AffineExpr, 4> dimReplacements, symReplacements;
158 for (int64_t i = 0, e = map.getNumDims(); i < e; ++i)
159 dimReplacements.push_back(b.getAffineSymbolExpr(i));
160 for (int64_t i = 0, e = map.getNumSymbols(); i < e; ++i)
161 symReplacements.push_back(b.getAffineSymbolExpr(i + map.getNumDims()));
162 AffineMap tmpMap = map.replaceDimsAndSymbols(
163 dimReplacements, symReplacements, /*numResultDims=*/0,
164 /*numResultSyms=*/map.getNumSymbols() + map.getNumDims());
165
166 // Inline operands.
168 for (auto [index, var] : llvm::enumerate(mapOperands)) {
169 assert(var.map.getNumResults() == 1 && "expected single result");
170 assert(var.map.getNumDims() == 0 && "expected only symbols");
171 SmallVector<AffineExpr> symReplacements;
172 for (auto valueDim : var.mapOperands) {
173 auto *it = llvm::find(this->mapOperands, valueDim);
174 if (it != this->mapOperands.end()) {
175 // There is already a symbol for this operand.
176 symReplacements.push_back(b.getAffineSymbolExpr(
177 std::distance(this->mapOperands.begin(), it)));
178 } else {
179 // This is a new operand: add a new symbol.
180 symReplacements.push_back(
181 b.getAffineSymbolExpr(this->mapOperands.size()));
182 this->mapOperands.push_back(valueDim);
183 }
184 }
185 replacements[b.getAffineSymbolExpr(index)] =
186 var.map.getResult(0).replaceSymbols(symReplacements);
187 }
188 this->map = tmpMap.replace(replacements, /*numResultDims=*/0,
189 /*numResultSyms=*/this->mapOperands.size());
190}
191
193 ValueRange mapOperands)
194 : Variable(map, llvm::map_to_vector(mapOperands,
195 [](Value v) { return Variable(v); })) {}
196
204
206
207#ifndef NDEBUG
208static void assertValidValueDim(Value value, std::optional<int64_t> dim,
210 if (isIndexLikeType(value.getType(), options)) {
211 assert(!dim.has_value() && "invalid dim value");
212 } else if (auto shapedType = dyn_cast<ShapedType>(value.getType())) {
213 assert(*dim >= 0 && "invalid dim value");
214 if (shapedType.hasRank())
215 assert(*dim < shapedType.getRank() && "invalid dim value");
216 } else {
217 llvm_unreachable("unsupported type");
218 }
219}
220#endif // NDEBUG
221
223 AffineExpr expr) {
224 // Note: If `addConservativeSemiAffineBounds` is true then the bound
225 // computation function needs to handle the case that the constraints set
226 // could become empty. This is because the conservative bounds add assumptions
227 // (e.g. for `mod` it assumes `rhs > 0`). If these constraints are later found
228 // not to hold, then the bound is invalid.
229 LogicalResult status = cstr.addBound(
230 type, pos,
231 AffineMap::get(cstr.getNumDimVars(), cstr.getNumSymbolVars(), expr),
235 if (failed(status)) {
236 // Not all semi-affine expressions are not yet supported by
237 // FlatLinearConstraints. However, we can just ignore such failures here.
238 // Even without this bound, there may be enough information in the
239 // constraint system to compute the requested bound. In case this bound is
240 // actually needed, `computeBound` will return `failure`.
241 LDBG() << "Failed to add bound: " << expr << "\n";
242 }
243}
244
246 std::optional<int64_t> dim) {
247#ifndef NDEBUG
248 assertValidValueDim(value, dim, options);
249#endif // NDEBUG
250
251 // Check if the value/dim is statically known. In that case, an affine
252 // constant expression should be returned. This allows us to support
253 // multiplications with constants. (Multiplications of two columns in the
254 // constraint set is not supported.)
255 std::optional<int64_t> constSize = std::nullopt;
256 auto shapedType = dyn_cast<ShapedType>(value.getType());
257 if (shapedType) {
258 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
259 constSize = shapedType.getDimSize(*dim);
260 } else if (auto constInt = ::getConstantIntValue(value)) {
261 constSize = *constInt;
262 }
263
264 // If the value/dim is already mapped, return the corresponding expression
265 // directly.
266 ValueDim valueDim = std::make_pair(value, dim.value_or(kIndexValue));
267 if (valueDimToPosition.contains(valueDim)) {
268 // If it is a constant, return an affine constant expression. Otherwise,
269 // return an affine expression that represents the respective column in the
270 // constraint set.
271 if (constSize)
272 return builder.getAffineConstantExpr(*constSize);
273 return getPosExpr(getPos(value, dim));
274 }
275
276 if (constSize) {
277 // Constant index value/dim: add column to the constraint set, add EQ bound
278 // and return an affine constant expression without pushing the newly added
279 // column to the worklist.
280 (void)insert(value, dim, /*isSymbol=*/true, /*addToWorklist=*/false);
281 if (shapedType)
282 bound(value)[*dim] == *constSize;
283 else
284 bound(value) == *constSize;
285 return builder.getAffineConstantExpr(*constSize);
286 }
287
288 // Dynamic value/dim: insert column to the constraint set and put it on the
289 // worklist. Return an affine expression that represents the newly inserted
290 // column in the constraint set.
291 return getPosExpr(insert(value, dim, /*isSymbol=*/true));
292}
293
295 if (Value value = llvm::dyn_cast_if_present<Value>(ofr))
296 return getExpr(value, /*dim=*/std::nullopt);
297 auto constInt = ::getConstantIntValue(ofr);
298 assert(constInt.has_value() && "expected Integer constant");
299 return builder.getAffineConstantExpr(*constInt);
300}
301
303 return builder.getAffineConstantExpr(constant);
304}
305
307 std::optional<int64_t> dim,
308 bool isSymbol, bool addToWorklist) {
309#ifndef NDEBUG
310 assertValidValueDim(value, dim, options);
311#endif // NDEBUG
312
313 ValueDim valueDim = std::make_pair(value, dim.value_or(kIndexValue));
314 assert(!valueDimToPosition.contains(valueDim) && "already mapped");
315 int64_t pos = isSymbol ? cstr.appendVar(VarKind::Symbol)
316 : cstr.appendVar(VarKind::SetDim);
317 LDBG() << "Inserting constraint set column " << pos << " for: " << value
318 << " (dim: " << dim.value_or(kIndexValue)
319 << ", owner: " << getOwnerOfValue(value)->getName() << ")";
320 positionToValueDim.insert(positionToValueDim.begin() + pos, valueDim);
321 // Update reverse mapping.
322 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
323 if (positionToValueDim[i].has_value())
325
326 // Do not add block arguments from non-entry blocks to the worklist. The
327 // ValueBoundsOpInterface cannot derive any bounds for such values (they
328 // arise from unstructured control flow), so putting them on the worklist
329 // would be a no-op. More importantly, suppressing the worklist push ensures
330 // that processWorklist never calls getExpr on such a value a second time,
331 // which would otherwise cause the same value to be looked up as already
332 // mapped (triggering an unintended bug path).
333 if (addToWorklist &&
334 (!isa<BlockArgument>(value) ||
335 cast<BlockArgument>(value).getOwner()->isEntryBlock())) {
336 LDBG() << "Push to worklist: " << value
337 << " (dim: " << dim.value_or(kIndexValue) << ")";
338 worklist.push(pos);
339 }
340
341 return pos;
342}
343
345 int64_t pos = isSymbol ? cstr.appendVar(VarKind::Symbol)
346 : cstr.appendVar(VarKind::SetDim);
347 LDBG() << "Inserting anonymous constraint set column " << pos;
348 positionToValueDim.insert(positionToValueDim.begin() + pos, std::nullopt);
349 // Update reverse mapping.
350 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
351 if (positionToValueDim[i].has_value())
353 return pos;
354}
355
357 const ValueDimList &operands,
358 bool isSymbol) {
359 assert(map.getNumResults() == 1 && "expected affine map with one result");
360 int64_t pos = insert(isSymbol);
361
362 // Add map and operands to the constraint set. Dimensions are converted to
363 // symbols. All operands are added to the worklist (unless they were already
364 // processed).
365 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
366 return getExpr(v.first, v.second);
367 };
368 SmallVector<AffineExpr> dimReplacements = llvm::map_to_vector(
369 ArrayRef(operands).take_front(map.getNumDims()), mapper);
370 SmallVector<AffineExpr> symReplacements = llvm::map_to_vector(
371 ArrayRef(operands).drop_front(map.getNumDims()), mapper);
372 addBound(
374 map.getResult(0).replaceDimsAndSymbols(dimReplacements, symReplacements));
375
376 return pos;
377}
378
380 return insert(var.map, var.mapOperands, isSymbol);
381}
382
384 std::optional<int64_t> dim) const {
385#ifndef NDEBUG
386 assertValidValueDim(value, dim, options);
387#endif // NDEBUG
388 LDBG() << "Getting pos for: " << value
389 << " (dim: " << dim.value_or(kIndexValue)
390 << ", owner: " << getOwnerOfValue(value)->getName() << ")";
391 auto it =
392 valueDimToPosition.find(std::make_pair(value, dim.value_or(kIndexValue)));
393 assert(it != valueDimToPosition.end() && "expected mapped entry");
394 return it->second;
395}
396
398 assert(pos >= 0 && pos < cstr.getNumDimAndSymbolVars() && "invalid position");
399 return pos < cstr.getNumDimVars()
400 ? builder.getAffineDimExpr(pos)
401 : builder.getAffineSymbolExpr(pos - cstr.getNumDimVars());
402}
403
405 std::optional<int64_t> dim) const {
406 auto it =
407 valueDimToPosition.find(std::make_pair(value, dim.value_or(kIndexValue)));
408 return it != valueDimToPosition.end();
409}
410
412 LDBG() << "Processing value bounds worklist...";
413 while (!worklist.empty()) {
414 int64_t pos = worklist.front();
415 worklist.pop();
416 assert(positionToValueDim[pos].has_value() &&
417 "did not expect std::nullopt on worklist");
418 ValueDim valueDim = *positionToValueDim[pos];
419 Value value = valueDim.first;
420 int64_t dim = valueDim.second;
421
422 // Check for static dim size.
423 if (dim != kIndexValue) {
424 auto shapedType = cast<ShapedType>(value.getType());
425 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
426 bound(value)[dim] == getExpr(shapedType.getDimSize(dim));
427 continue;
428 }
429 }
430
431 // Do not process any further if the stop condition is met.
432 auto maybeDim = dim == kIndexValue ? std::nullopt : std::make_optional(dim);
433 if (stopCondition(value, maybeDim, *this)) {
434 LDBG() << "Stop condition met for: " << value << " (dim: " << maybeDim
435 << ")";
436 continue;
437 }
438
439 // Query `ValueBoundsOpInterface` for constraints. New items may be added to
440 // the worklist.
441 auto valueBoundsOp =
442 dyn_cast<ValueBoundsOpInterface>(getOwnerOfValue(value));
443 LDBG() << "Query value bounds for: " << value
444 << " (owner: " << getOwnerOfValue(value)->getName() << ")";
445 if (valueBoundsOp) {
446 if (dim == kIndexValue) {
447 valueBoundsOp.populateBoundsForIndexValue(value, *this);
448 } else {
449 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *this);
450 }
451 continue;
452 }
453 LDBG() << "--> ValueBoundsOpInterface not implemented";
454
455 // If the op does not implement `ValueBoundsOpInterface`, check if it
456 // implements the `DestinationStyleOpInterface`. OpResults of such ops are
457 // tied to OpOperands. Tied values have the same shape.
458 auto dstOp = value.getDefiningOp<DestinationStyleOpInterface>();
459 if (!dstOp || dim == kIndexValue)
460 continue;
461 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
462 bound(value)[dim] == getExpr(tiedOperand, dim);
463 }
464}
465
467 assert(pos >= 0 && pos < static_cast<int64_t>(positionToValueDim.size()) &&
468 "invalid position");
469 cstr.projectOut(pos);
470 if (positionToValueDim[pos].has_value()) {
471 bool erased = valueDimToPosition.erase(*positionToValueDim[pos]);
472 (void)erased;
473 assert(erased && "inconsistent reverse mapping");
474 }
475 positionToValueDim.erase(positionToValueDim.begin() + pos);
476 // Update reverse mapping.
477 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
478 if (positionToValueDim[i].has_value())
480}
481
483 function_ref<bool(ValueDim)> condition) {
484 int64_t nextPos = 0;
485 while (nextPos < static_cast<int64_t>(positionToValueDim.size())) {
486 if (positionToValueDim[nextPos].has_value() &&
487 condition(*positionToValueDim[nextPos])) {
488 projectOut(nextPos);
489 // The column was projected out so another column is now at that position.
490 // Do not increase the counter.
491 } else {
492 ++nextPos;
493 }
494 }
495}
496
498 std::optional<int64_t> except) {
499 int64_t nextPos = 0;
500 while (nextPos < static_cast<int64_t>(positionToValueDim.size())) {
501 if (positionToValueDim[nextPos].has_value() || except == nextPos) {
502 ++nextPos;
503 } else {
504 projectOut(nextPos);
505 // The column was projected out so another column is now at that position.
506 // Do not increase the counter.
507 }
508 }
509}
510
512 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
515 MLIRContext *ctx = var.getContext();
516 int64_t ubAdjustment = options.closedUB ? 0 : 1;
517 Builder b(ctx);
518 mapOperands.clear();
519
520 // Process the backward slice of `value` (i.e., reverse use-def chain) until
521 // `stopCondition` is met.
523 int64_t pos = cstr.insert(var, /*isSymbol=*/false);
524 assert(pos == 0 && "expected first column");
525 cstr.processWorklist();
526
527 // Project out all variables (apart from `valueDim`) that do not match the
528 // stop condition.
529 cstr.projectOut([&](ValueDim p) {
530 auto maybeDim =
531 p.second == kIndexValue ? std::nullopt : std::make_optional(p.second);
532 return !stopCondition(p.first, maybeDim, cstr);
533 });
534 cstr.projectOutAnonymous(/*except=*/pos);
535
536 // Compute lower and upper bounds for `valueDim`.
537 SmallVector<AffineMap> lb(1), ub(1);
538 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &ub,
539 /*closedUB=*/true);
540
541 // Note: There are TODOs in the implementation of `getSliceBounds`. In such a
542 // case, no lower/upper bound can be computed at the moment.
543 // EQ, UB bounds: upper bound is needed.
544 if ((type != BoundType::LB) &&
545 (ub.empty() || !ub[0] || ub[0].getNumResults() == 0))
546 return failure();
547 // EQ, LB bounds: lower bound is needed.
548 if ((type != BoundType::UB) &&
549 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
550 return failure();
551
552 // TODO: Generate an affine map with multiple results.
553 if (type != BoundType::LB)
554 assert(ub.size() == 1 && ub[0].getNumResults() == 1 &&
555 "multiple bounds not supported");
556 if (type != BoundType::UB)
557 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
558 "multiple bounds not supported");
559
560 // EQ bound: lower and upper bound must match.
561 if (type == BoundType::EQ && ub[0] != lb[0])
562 return failure();
563
565 if (type == BoundType::EQ || type == BoundType::LB) {
566 bound = lb[0];
567 } else {
568 // Computed UB is a closed bound.
569 bound = AffineMap::get(ub[0].getNumDims(), ub[0].getNumSymbols(),
570 ub[0].getResult(0) + ubAdjustment);
571 }
572
573 // Gather all SSA values that are used in the computed bound.
574 assert(cstr.cstr.getNumDimAndSymbolVars() == cstr.positionToValueDim.size() &&
575 "inconsistent mapping state");
576 SmallVector<AffineExpr> replacementDims, replacementSymbols;
577 int64_t numDims = 0, numSymbols = 0;
578 for (int64_t i = 0; i < cstr.cstr.getNumDimAndSymbolVars(); ++i) {
579 // Skip `value`.
580 if (i == pos)
581 continue;
582 // Check if the position `i` is used in the generated bound. If so, it must
583 // be included in the generated affine.apply op.
584 bool used = false;
585 bool isDim = i < cstr.cstr.getNumDimVars();
586 if (isDim) {
587 if (bound.isFunctionOfDim(i))
588 used = true;
589 } else {
590 if (bound.isFunctionOfSymbol(i - cstr.cstr.getNumDimVars()))
591 used = true;
592 }
593
594 if (!used) {
595 // Not used: Remove dim/symbol from the result.
596 if (isDim) {
597 replacementDims.push_back(b.getAffineConstantExpr(0));
598 } else {
599 replacementSymbols.push_back(b.getAffineConstantExpr(0));
600 }
601 continue;
602 }
603
604 if (isDim) {
605 replacementDims.push_back(b.getAffineDimExpr(numDims++));
606 } else {
607 replacementSymbols.push_back(b.getAffineSymbolExpr(numSymbols++));
608 }
609
610 assert(cstr.positionToValueDim[i].has_value() &&
611 "cannot build affine map in terms of anonymous column");
612 ValueBoundsConstraintSet::ValueDim valueDim = *cstr.positionToValueDim[i];
613 Value value = valueDim.first;
614 int64_t dim = valueDim.second;
616 // An index-typed/integer-typed value is used: it can be used directly in
617 // the computed bound.
618 assert(isIndexLikeType(value.getType(), options) &&
619 "expected index or integer type");
620 mapOperands.push_back(std::make_pair(value, std::nullopt));
621 continue;
622 }
623
624 assert(cast<ShapedType>(value.getType()).isDynamicDim(dim) &&
625 "expected dynamic dim");
626 mapOperands.push_back(std::make_pair(value, dim));
627 }
628
629 resultMap = bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
630 numDims, numSymbols);
631 return success();
632}
633
635 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
636 const Variable &var, ValueDimList dependencies,
638 return computeBound(
639 resultMap, mapOperands, type, var,
640 [&](Value v, std::optional<int64_t> d, ValueBoundsConstraintSet &cstr) {
641 return llvm::is_contained(dependencies, std::make_pair(v, d));
642 },
643 options);
644}
645
647 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
648 const Variable &var, ValueRange independencies,
650 // Return "true" if the given value is independent of all values in
651 // `independencies`. I.e., neither the value itself nor any value in the
652 // backward slice (reverse use-def chain) is contained in `independencies`.
653 auto isIndependent = [&](Value v) {
655 DenseSet<Value> visited;
656 worklist.push_back(v);
657 while (!worklist.empty()) {
658 Value next = worklist.pop_back_val();
659 if (!visited.insert(next).second)
660 continue;
661 if (llvm::is_contained(independencies, next))
662 return false;
663 // TODO: DominanceInfo could be used to stop the traversal early.
664 Operation *op = next.getDefiningOp();
665 if (!op)
666 continue;
667 worklist.append(op->getOperands().begin(), op->getOperands().end());
668 }
669 return true;
670 };
671
672 // Reify bounds in terms of any independent values.
673 return computeBound(
674 resultMap, mapOperands, type, var,
675 [&](Value v, std::optional<int64_t> d, ValueBoundsConstraintSet &cstr) {
676 return isIndependent(v);
677 },
678 options);
679}
680
682 presburger::BoundType type, const Variable &var,
684 // Default stop condition if none was specified: Keep adding constraints until
685 // a bound could be computed.
686 int64_t pos = 0;
687 auto defaultStopCondition = [&](Value v, std::optional<int64_t> dim,
689 return cstr.cstr.getConstantBound64(type, pos).has_value();
690 };
691
693 var.getContext(), stopCondition ? stopCondition : defaultStopCondition,
694 options);
695 pos = cstr.populateConstraints(var.map, var.mapOperands);
696 assert(pos == 0 && "expected `map` is the first column");
697
698 // Compute constant bound for `valueDim`.
699 int64_t ubAdjustment = options.closedUB ? 0 : 1;
700 if (auto bound = cstr.cstr.getConstantBound64(type, pos))
701 return type == BoundType::UB ? *bound + ubAdjustment : *bound;
702 return failure();
703}
704
706 std::optional<int64_t> dim) {
707#ifndef NDEBUG
708 assertValidValueDim(value, dim, options);
709#endif // NDEBUG
710
711 // `getExpr` pushes the value/dim onto the worklist (unless it was already
712 // analyzed).
713 (void)getExpr(value, dim);
714 // Process all values/dims on the worklist. This may traverse and analyze
715 // additional IR, depending the current stop function.
717}
718
720 ValueDimList operands) {
721 int64_t pos = insert(map, std::move(operands), /*isSymbol=*/false);
722 // Process the backward slice of `operands` (i.e., reverse use-def chain)
723 // until `stopCondition` is met.
725 return pos;
726}
727
728FailureOr<int64_t>
730 std::optional<int64_t> dim1,
731 std::optional<int64_t> dim2) {
732#ifndef NDEBUG
733 assertValidValueDim(value1, dim1, /*options=*/{});
734 assertValidValueDim(value2, dim2, /*options=*/{});
735#endif // NDEBUG
736
737 Builder b(value1.getContext());
738 AffineMap map = AffineMap::get(/*dimCount=*/2, /*symbolCount=*/0,
739 b.getAffineDimExpr(0) - b.getAffineDimExpr(1));
741 Variable(map, {{value1, dim1}, {value2, dim2}}));
742}
743
746 int64_t rhsPos) {
747 // This function returns "true" if "lhs CMP rhs" is proven to hold.
748 //
749 // Example for ComparisonOperator::LE and index-typed values: We would like to
750 // prove that lhs <= rhs. Proof by contradiction: add the inverse
751 // relation (lhs > rhs) to the constraint set and check if the resulting
752 // constraint set is "empty" (i.e. has no solution). In that case,
753 // lhs > rhs must be incorrect and we can deduce that lhs <= rhs holds.
754
755 // We cannot prove anything if the constraint set is already empty.
756 if (cstr.isEmpty()) {
757 LDBG() << "cannot compare value/dims: constraint system is already empty";
758 return false;
759 }
760
761 // EQ can be expressed as LE and GE.
762 if (cmp == EQ)
763 return comparePos(lhsPos, ComparisonOperator::LE, rhsPos) &&
764 comparePos(lhsPos, ComparisonOperator::GE, rhsPos);
765
766 // Construct inequality.
767 // Inline size chosen empirically based on compilation profiling.
768 // Profiled: 3.2M calls, avg=4.0+-2.3. N=8 covers ~95% of cases inline.
769 SmallVector<int64_t, 8> eq(cstr.getNumCols(), 0);
770 if (cmp == LT || cmp == LE) {
771 ++eq[lhsPos];
772 --eq[rhsPos];
773 } else if (cmp == GT || cmp == GE) {
774 --eq[lhsPos];
775 ++eq[rhsPos];
776 } else {
777 llvm_unreachable("unsupported comparison operator");
778 }
779 if (cmp == LE || cmp == GE)
780 eq[cstr.getNumCols() - 1] -= 1;
781
782 // Add inequality to the constraint set and check if it made the constraint
783 // set empty.
784 int64_t ineqPos = cstr.getNumInequalities();
785 cstr.addInequality(eq);
786 bool isEmpty = cstr.isEmpty();
787 cstr.removeInequality(ineqPos);
788 return isEmpty;
789}
790
792 int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos) {
793 auto strongCmp = [&](ComparisonOperator cmp,
794 ComparisonOperator negCmp) -> FailureOr<bool> {
795 if (comparePos(lhsPos, cmp, rhsPos))
796 return true;
797 if (comparePos(lhsPos, negCmp, rhsPos))
798 return false;
799 return failure();
800 };
801 switch (cmp) {
811 std::optional<bool> le =
813 if (!le)
814 return failure();
815 if (!*le)
816 return false;
817 std::optional<bool> ge =
819 if (!ge)
820 return failure();
821 if (!*ge)
822 return false;
823 return true;
824 }
825 }
826 llvm_unreachable("invalid comparison operator");
827}
828
831 const Variable &rhs) {
832 int64_t lhsPos = populateConstraints(lhs.map, lhs.mapOperands);
833 int64_t rhsPos = populateConstraints(rhs.map, rhs.mapOperands);
834 return comparePos(lhsPos, cmp, rhsPos);
835}
836
839 const Variable &rhs) {
840 int64_t lhsPos = -1, rhsPos = -1;
841 auto stopCondition = [&](Value v, std::optional<int64_t> dim,
843 // Keep processing as long as lhs/rhs were not processed.
844 if (size_t(lhsPos) >= cstr.positionToValueDim.size() ||
845 size_t(rhsPos) >= cstr.positionToValueDim.size())
846 return false;
847 // Keep processing as long as the relation cannot be proven.
848 return cstr.comparePos(lhsPos, cmp, rhsPos);
849 };
851 lhsPos = cstr.populateConstraints(lhs.map, lhs.mapOperands);
852 rhsPos = cstr.populateConstraints(rhs.map, rhs.mapOperands);
853 return cstr.comparePos(lhsPos, cmp, rhsPos);
854}
855
858 const Variable &rhs) {
859 int64_t lhsPos = -1, rhsPos = -1;
860 auto stopCondition = [&](Value v, std::optional<int64_t> dim,
862 // Keep processing as long as lhs/rhs were not processed.
863 if (size_t(lhsPos) >= cstr.positionToValueDim.size() ||
864 size_t(rhsPos) >= cstr.positionToValueDim.size())
865 return false;
866 // Keep processing as long as the strong relation cannot be proven.
867 FailureOr<bool> ordered = cstr.strongComparePos(lhsPos, cmp, rhsPos);
868 return failed(ordered);
869 };
871 lhsPos = cstr.populateConstraints(lhs.map, lhs.mapOperands);
872 rhsPos = cstr.populateConstraints(rhs.map, rhs.mapOperands);
873 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
874}
875
877 const Variable &var2) {
878 return strongCompare(var1, ComparisonOperator::EQ, var2);
879}
880
882 MLIRContext *ctx, const HyperrectangularSlice &slice1,
883 const HyperrectangularSlice &slice2) {
884 assert(slice1.getMixedOffsets().size() == slice2.getMixedOffsets().size() &&
885 "expected slices of same rank");
886 assert(slice1.getMixedSizes().size() == slice2.getMixedSizes().size() &&
887 "expected slices of same rank");
888 assert(slice1.getMixedStrides().size() == slice2.getMixedStrides().size() &&
889 "expected slices of same rank");
890
891 Builder b(ctx);
892 bool foundUnknownBound = false;
893 for (int64_t i = 0, e = slice1.getMixedOffsets().size(); i < e; ++i) {
894 AffineMap map =
895 AffineMap::get(/*dimCount=*/0, /*symbolCount=*/4,
896 b.getAffineSymbolExpr(0) +
897 b.getAffineSymbolExpr(1) * b.getAffineSymbolExpr(2) -
898 b.getAffineSymbolExpr(3));
899 {
900 // Case 1: Slices are guaranteed to be non-overlapping if
901 // offset1 + size1 * stride1 <= offset2 (for at least one dimension).
902 SmallVector<OpFoldResult> ofrOperands;
903 ofrOperands.push_back(slice1.getMixedOffsets()[i]);
904 ofrOperands.push_back(slice1.getMixedSizes()[i]);
905 ofrOperands.push_back(slice1.getMixedStrides()[i]);
906 ofrOperands.push_back(slice2.getMixedOffsets()[i]);
907 SmallVector<Value> valueOperands;
908 AffineMap foldedMap =
909 foldAttributesIntoMap(b, map, ofrOperands, valueOperands);
910 FailureOr<int64_t> constBound = computeConstantBound(
911 presburger::BoundType::EQ, Variable(foldedMap, valueOperands));
912 foundUnknownBound |= failed(constBound);
913 if (succeeded(constBound) && *constBound <= 0)
914 return false;
915 }
916 {
917 // Case 2: Slices are guaranteed to be non-overlapping if
918 // offset2 + size2 * stride2 <= offset1 (for at least one dimension).
919 SmallVector<OpFoldResult> ofrOperands;
920 ofrOperands.push_back(slice2.getMixedOffsets()[i]);
921 ofrOperands.push_back(slice2.getMixedSizes()[i]);
922 ofrOperands.push_back(slice2.getMixedStrides()[i]);
923 ofrOperands.push_back(slice1.getMixedOffsets()[i]);
924 SmallVector<Value> valueOperands;
925 AffineMap foldedMap =
926 foldAttributesIntoMap(b, map, ofrOperands, valueOperands);
927 FailureOr<int64_t> constBound = computeConstantBound(
928 presburger::BoundType::EQ, Variable(foldedMap, valueOperands));
929 foundUnknownBound |= failed(constBound);
930 if (succeeded(constBound) && *constBound <= 0)
931 return false;
932 }
933 }
934
935 // If at least one bound could not be computed, we cannot be certain that the
936 // slices are really overlapping.
937 if (foundUnknownBound)
938 return failure();
939
940 // All bounds could be computed and none of the above cases applied.
941 // Therefore, the slices are guaranteed to overlap.
942 return true;
943}
944
946 MLIRContext *ctx, const HyperrectangularSlice &slice1,
947 const HyperrectangularSlice &slice2) {
948 assert(slice1.getMixedOffsets().size() == slice2.getMixedOffsets().size() &&
949 "expected slices of same rank");
950 assert(slice1.getMixedSizes().size() == slice2.getMixedSizes().size() &&
951 "expected slices of same rank");
952 assert(slice1.getMixedStrides().size() == slice2.getMixedStrides().size() &&
953 "expected slices of same rank");
954
955 // The two slices are equivalent if all of their offsets, sizes and strides
956 // are equal. If equality cannot be determined for at least one of those
957 // values, equivalence cannot be determined and this function returns
958 // "failure".
959 for (auto [offset1, offset2] :
960 llvm::zip_equal(slice1.getMixedOffsets(), slice2.getMixedOffsets())) {
961 FailureOr<bool> equal = areEqual(offset1, offset2);
962 if (failed(equal))
963 return failure();
964 if (!equal.value())
965 return false;
966 }
967 for (auto [size1, size2] :
968 llvm::zip_equal(slice1.getMixedSizes(), slice2.getMixedSizes())) {
969 FailureOr<bool> equal = areEqual(size1, size2);
970 if (failed(equal))
971 return failure();
972 if (!equal.value())
973 return false;
974 }
975 for (auto [stride1, stride2] :
976 llvm::zip_equal(slice1.getMixedStrides(), slice2.getMixedStrides())) {
977 FailureOr<bool> equal = areEqual(stride1, stride2);
978 if (failed(equal))
979 return failure();
980 if (!equal.value())
981 return false;
982 }
983 return true;
984}
985
987 llvm::errs() << "==========\nColumns:\n";
988 llvm::errs() << "(column\tdim\tvalue)\n";
989 for (auto [index, valueDim] : llvm::enumerate(positionToValueDim)) {
990 llvm::errs() << " " << index << "\t";
991 if (valueDim) {
992 if (valueDim->second == kIndexValue) {
993 llvm::errs() << "n/a\t";
994 } else {
995 llvm::errs() << valueDim->second << "\t";
996 }
997 llvm::errs() << getOwnerOfValue(valueDim->first)->getName() << " ";
998 if (OpResult result = dyn_cast<OpResult>(valueDim->first)) {
999 llvm::errs() << "(result " << result.getResultNumber() << ")";
1000 } else {
1001 llvm::errs() << "(bbarg "
1002 << cast<BlockArgument>(valueDim->first).getArgNumber()
1003 << ")";
1004 }
1005 llvm::errs() << "\n";
1006 } else {
1007 llvm::errs() << "n/a\tn/a\n";
1008 }
1009 }
1010 llvm::errs() << "\nConstraint set:\n";
1011 cstr.dump();
1012 llvm::errs() << "==========\n";
1013}
1014
1017 assert(!this->dim.has_value() && "dim was already set");
1018 this->dim = dim;
1019#ifndef NDEBUG
1020 assertValidValueDim(value, this->dim, cstr.options);
1021#endif // NDEBUG
1022 return *this;
1023}
1024
1026#ifndef NDEBUG
1027 assertValidValueDim(value, this->dim, cstr.options);
1028#endif // NDEBUG
1029 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
1030}
1031
1035
1039
1041#ifndef NDEBUG
1042 assertValidValueDim(value, this->dim, cstr.options);
1043#endif // NDEBUG
1044 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
1045}
1046
1048#ifndef NDEBUG
1049 assertValidValueDim(value, this->dim, cstr.options);
1050#endif // NDEBUG
1051 cstr.addBound(BoundType::EQ, cstr.getPos(value, this->dim), expr);
1052}
1053
1057
1061
1065
1069
1073
1077
1081
1085
1089
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
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:112
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:87
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
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.
static bool isProvablyNegative(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably negative.
static bool isProvablyPositive(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably positive.
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.
static bool isProvablyNonNegative(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably non-negative.
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.
static bool isProvablyNonPositive(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably non-positive.
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:227
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.