MLIR 24.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(valueDim);
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 ValueDim valueDim = worklist.front();
415 worklist.pop();
416 assert(valueDimToPosition.contains(valueDim) &&
417 "expected mapped worklist entry");
418 Value value = valueDim.first;
419 int64_t dim = valueDim.second;
420
421 // Check for static dim size.
422 if (dim != kIndexValue) {
423 auto shapedType = cast<ShapedType>(value.getType());
424 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
425 bound(value)[dim] == getExpr(shapedType.getDimSize(dim));
426 continue;
427 }
428 }
429
430 // Do not process any further if the stop condition is met.
431 auto maybeDim = dim == kIndexValue ? std::nullopt : std::make_optional(dim);
432 if (stopCondition(value, maybeDim, *this)) {
433 LDBG() << "Stop condition met for: " << value << " (dim: " << maybeDim
434 << ")";
435 continue;
436 }
437
438 // Query `ValueBoundsOpInterface` for constraints. New items may be added to
439 // the worklist.
440 auto valueBoundsOp =
441 dyn_cast<ValueBoundsOpInterface>(getOwnerOfValue(value));
442 LDBG() << "Query value bounds for: " << value
443 << " (owner: " << getOwnerOfValue(value)->getName() << ")";
444 if (valueBoundsOp) {
445 if (dim == kIndexValue) {
446 valueBoundsOp.populateBoundsForIndexValue(value, *this);
447 } else {
448 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *this);
449 }
450 continue;
451 }
452 LDBG() << "--> ValueBoundsOpInterface not implemented";
453
454 // If the op does not implement `ValueBoundsOpInterface`, check if it
455 // implements the `DestinationStyleOpInterface`. OpResults of such ops are
456 // tied to OpOperands. Tied values have the same shape.
457 auto dstOp = value.getDefiningOp<DestinationStyleOpInterface>();
458 if (!dstOp || dim == kIndexValue)
459 continue;
460 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
461 bound(value)[dim] == getExpr(tiedOperand, dim);
462 }
463}
464
466 assert(pos >= 0 && pos < static_cast<int64_t>(positionToValueDim.size()) &&
467 "invalid position");
468 cstr.projectOut(pos);
469 if (positionToValueDim[pos].has_value()) {
470 bool erased = valueDimToPosition.erase(*positionToValueDim[pos]);
471 (void)erased;
472 assert(erased && "inconsistent reverse mapping");
473 }
474 positionToValueDim.erase(positionToValueDim.begin() + pos);
475 // Update reverse mapping.
476 for (int64_t i = pos, e = positionToValueDim.size(); i < e; ++i)
477 if (positionToValueDim[i].has_value())
479}
480
482 function_ref<bool(ValueDim)> condition) {
483 int64_t nextPos = 0;
484 while (nextPos < static_cast<int64_t>(positionToValueDim.size())) {
485 if (positionToValueDim[nextPos].has_value() &&
486 condition(*positionToValueDim[nextPos])) {
487 projectOut(nextPos);
488 // The column was projected out so another column is now at that position.
489 // Do not increase the counter.
490 } else {
491 ++nextPos;
492 }
493 }
494}
495
497 std::optional<int64_t> except) {
498 int64_t nextPos = 0;
499 while (nextPos < static_cast<int64_t>(positionToValueDim.size())) {
500 if (positionToValueDim[nextPos].has_value() || except == nextPos) {
501 ++nextPos;
502 } else {
503 projectOut(nextPos);
504 // The column was projected out so another column is now at that position.
505 // Do not increase the counter.
506 }
507 }
508}
509
511 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
514 MLIRContext *ctx = var.getContext();
515 int64_t ubAdjustment = options.closedUB ? 0 : 1;
516 Builder b(ctx);
517 mapOperands.clear();
518
519 // Process the backward slice of `value` (i.e., reverse use-def chain) until
520 // `stopCondition` is met.
522 int64_t pos = cstr.insert(var, /*isSymbol=*/false);
523 assert(pos == 0 && "expected first column");
524 cstr.processWorklist();
525
526 // Project out all variables (apart from `valueDim`) that do not match the
527 // stop condition.
528 cstr.projectOut([&](ValueDim p) {
529 auto maybeDim =
530 p.second == kIndexValue ? std::nullopt : std::make_optional(p.second);
531 return !stopCondition(p.first, maybeDim, cstr);
532 });
533 cstr.projectOutAnonymous(/*except=*/pos);
534
535 // Compute lower and upper bounds for `valueDim`.
536 SmallVector<AffineMap> lb(1), ub(1);
537 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &ub,
538 /*closedUB=*/true);
539
540 // Note: There are TODOs in the implementation of `getSliceBounds`. In such a
541 // case, no lower/upper bound can be computed at the moment.
542 // EQ, UB bounds: upper bound is needed.
543 if ((type != BoundType::LB) &&
544 (ub.empty() || !ub[0] || ub[0].getNumResults() == 0))
545 return failure();
546 // EQ, LB bounds: lower bound is needed.
547 if ((type != BoundType::UB) &&
548 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
549 return failure();
550
551 // TODO: Generate an affine map with multiple results.
552 if (type != BoundType::LB)
553 assert(ub.size() == 1 && ub[0].getNumResults() == 1 &&
554 "multiple bounds not supported");
555 if (type != BoundType::UB)
556 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
557 "multiple bounds not supported");
558
559 // EQ bound: lower and upper bound must match.
560 if (type == BoundType::EQ && ub[0] != lb[0])
561 return failure();
562
564 if (type == BoundType::EQ || type == BoundType::LB) {
565 bound = lb[0];
566 } else {
567 // Computed UB is a closed bound.
568 bound = AffineMap::get(ub[0].getNumDims(), ub[0].getNumSymbols(),
569 ub[0].getResult(0) + ubAdjustment);
570 }
571
572 // Gather all SSA values that are used in the computed bound.
573 assert(cstr.cstr.getNumDimAndSymbolVars() == cstr.positionToValueDim.size() &&
574 "inconsistent mapping state");
575 SmallVector<AffineExpr> replacementDims, replacementSymbols;
576 int64_t numDims = 0, numSymbols = 0;
577 for (int64_t i = 0; i < cstr.cstr.getNumDimAndSymbolVars(); ++i) {
578 // Skip `value`.
579 if (i == pos)
580 continue;
581 // Check if the position `i` is used in the generated bound. If so, it must
582 // be included in the generated affine.apply op.
583 bool used = false;
584 bool isDim = i < cstr.cstr.getNumDimVars();
585 if (isDim) {
586 if (bound.isFunctionOfDim(i))
587 used = true;
588 } else {
589 if (bound.isFunctionOfSymbol(i - cstr.cstr.getNumDimVars()))
590 used = true;
591 }
592
593 if (!used) {
594 // Not used: Remove dim/symbol from the result.
595 if (isDim) {
596 replacementDims.push_back(b.getAffineConstantExpr(0));
597 } else {
598 replacementSymbols.push_back(b.getAffineConstantExpr(0));
599 }
600 continue;
601 }
602
603 if (isDim) {
604 replacementDims.push_back(b.getAffineDimExpr(numDims++));
605 } else {
606 replacementSymbols.push_back(b.getAffineSymbolExpr(numSymbols++));
607 }
608
609 assert(cstr.positionToValueDim[i].has_value() &&
610 "cannot build affine map in terms of anonymous column");
611 ValueBoundsConstraintSet::ValueDim valueDim = *cstr.positionToValueDim[i];
612 Value value = valueDim.first;
613 int64_t dim = valueDim.second;
615 // An index-typed/integer-typed value is used: it can be used directly in
616 // the computed bound.
617 assert(isIndexLikeType(value.getType(), options) &&
618 "expected index or integer type");
619 mapOperands.push_back(std::make_pair(value, std::nullopt));
620 continue;
621 }
622
623 assert(cast<ShapedType>(value.getType()).isDynamicDim(dim) &&
624 "expected dynamic dim");
625 mapOperands.push_back(std::make_pair(value, dim));
626 }
627
628 resultMap = bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
629 numDims, numSymbols);
630 return success();
631}
632
634 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
635 const Variable &var, ValueDimList dependencies,
637 return computeBound(
638 resultMap, mapOperands, type, var,
639 [&](Value v, std::optional<int64_t> d, ValueBoundsConstraintSet &cstr) {
640 return llvm::is_contained(dependencies, std::make_pair(v, d));
641 },
642 options);
643}
644
646 AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type,
647 const Variable &var, ValueRange independencies,
649 // Return "true" if the given value is independent of all values in
650 // `independencies`. I.e., neither the value itself nor any value in the
651 // backward slice (reverse use-def chain) is contained in `independencies`.
652 auto isIndependent = [&](Value v) {
654 DenseSet<Value> visited;
655 worklist.push_back(v);
656 while (!worklist.empty()) {
657 Value next = worklist.pop_back_val();
658 if (!visited.insert(next).second)
659 continue;
660 if (llvm::is_contained(independencies, next))
661 return false;
662 // TODO: DominanceInfo could be used to stop the traversal early.
663 Operation *op = next.getDefiningOp();
664 if (!op)
665 continue;
666 worklist.append(op->getOperands().begin(), op->getOperands().end());
667 }
668 return true;
669 };
670
671 // Reify bounds in terms of any independent values.
672 return computeBound(
673 resultMap, mapOperands, type, var,
674 [&](Value v, std::optional<int64_t> d, ValueBoundsConstraintSet &cstr) {
675 return isIndependent(v);
676 },
677 options);
678}
679
681 presburger::BoundType type, const Variable &var,
683 // Default stop condition if none was specified: Keep adding constraints until
684 // a bound could be computed.
685 int64_t pos = 0;
686 auto defaultStopCondition = [&](Value v, std::optional<int64_t> dim,
688 return cstr.cstr.getConstantBound64(type, pos).has_value();
689 };
690
692 var.getContext(), stopCondition ? stopCondition : defaultStopCondition,
693 options);
694 pos = cstr.populateConstraints(var.map, var.mapOperands);
695 assert(pos == 0 && "expected `map` is the first column");
696
697 // Compute constant bound for `valueDim`.
698 int64_t ubAdjustment = options.closedUB ? 0 : 1;
699 if (auto bound = cstr.cstr.getConstantBound64(type, pos))
700 return type == BoundType::UB ? *bound + ubAdjustment : *bound;
701 return failure();
702}
703
705 std::optional<int64_t> dim) {
706#ifndef NDEBUG
707 assertValidValueDim(value, dim, options);
708#endif // NDEBUG
709
710 // `getExpr` pushes the value/dim onto the worklist (unless it was already
711 // analyzed).
712 (void)getExpr(value, dim);
713 // Process all values/dims on the worklist. This may traverse and analyze
714 // additional IR, depending the current stop function.
716}
717
719 ValueDimList operands) {
720 int64_t pos = insert(map, std::move(operands), /*isSymbol=*/false);
721 // Process the backward slice of `operands` (i.e., reverse use-def chain)
722 // until `stopCondition` is met.
724 return pos;
725}
726
727FailureOr<int64_t>
729 std::optional<int64_t> dim1,
730 std::optional<int64_t> dim2) {
731#ifndef NDEBUG
732 assertValidValueDim(value1, dim1, /*options=*/{});
733 assertValidValueDim(value2, dim2, /*options=*/{});
734#endif // NDEBUG
735
736 Builder b(value1.getContext());
737 AffineMap map = AffineMap::get(/*dimCount=*/2, /*symbolCount=*/0,
738 b.getAffineDimExpr(0) - b.getAffineDimExpr(1));
740 Variable(map, {{value1, dim1}, {value2, dim2}}));
741}
742
745 int64_t rhsPos) {
746 // This function returns "true" if "lhs CMP rhs" is proven to hold.
747 //
748 // Example for ComparisonOperator::LE and index-typed values: We would like to
749 // prove that lhs <= rhs. Proof by contradiction: add the inverse
750 // relation (lhs > rhs) to the constraint set and check if the resulting
751 // constraint set is "empty" (i.e. has no solution). In that case,
752 // lhs > rhs must be incorrect and we can deduce that lhs <= rhs holds.
753
754 // We cannot prove anything if the constraint set is already empty.
755 if (cstr.isEmpty()) {
756 LDBG() << "cannot compare value/dims: constraint system is already empty";
757 return false;
758 }
759
760 // EQ can be expressed as LE and GE.
761 if (cmp == EQ)
762 return comparePos(lhsPos, ComparisonOperator::LE, rhsPos) &&
763 comparePos(lhsPos, ComparisonOperator::GE, rhsPos);
764
765 // Construct inequality.
766 // Inline size chosen empirically based on compilation profiling.
767 // Profiled: 3.2M calls, avg=4.0+-2.3. N=8 covers ~95% of cases inline.
768 SmallVector<int64_t, 8> eq(cstr.getNumCols(), 0);
769 if (cmp == LT || cmp == LE) {
770 ++eq[lhsPos];
771 --eq[rhsPos];
772 } else if (cmp == GT || cmp == GE) {
773 --eq[lhsPos];
774 ++eq[rhsPos];
775 } else {
776 llvm_unreachable("unsupported comparison operator");
777 }
778 if (cmp == LE || cmp == GE)
779 eq[cstr.getNumCols() - 1] -= 1;
780
781 // Add inequality to the constraint set and check if it made the constraint
782 // set empty.
783 int64_t ineqPos = cstr.getNumInequalities();
784 cstr.addInequality(eq);
785 bool isEmpty = cstr.isEmpty();
786 cstr.removeInequality(ineqPos);
787 return isEmpty;
788}
789
791 int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos) {
792 auto strongCmp = [&](ComparisonOperator cmp,
793 ComparisonOperator negCmp) -> FailureOr<bool> {
794 if (comparePos(lhsPos, cmp, rhsPos))
795 return true;
796 if (comparePos(lhsPos, negCmp, rhsPos))
797 return false;
798 return failure();
799 };
800 switch (cmp) {
810 std::optional<bool> le =
812 if (!le)
813 return failure();
814 if (!*le)
815 return false;
816 std::optional<bool> ge =
818 if (!ge)
819 return failure();
820 if (!*ge)
821 return false;
822 return true;
823 }
824 }
825 llvm_unreachable("invalid comparison operator");
826}
827
830 const Variable &rhs) {
831 int64_t lhsPos = populateConstraints(lhs.map, lhs.mapOperands);
832 int64_t rhsPos = populateConstraints(rhs.map, rhs.mapOperands);
833 return comparePos(lhsPos, cmp, rhsPos);
834}
835
838 const Variable &rhs) {
839 int64_t lhsPos = -1, rhsPos = -1;
840 auto stopCondition = [&](Value v, std::optional<int64_t> dim,
842 // Keep processing as long as lhs/rhs were not processed.
843 if (size_t(lhsPos) >= cstr.positionToValueDim.size() ||
844 size_t(rhsPos) >= cstr.positionToValueDim.size())
845 return false;
846 // Keep processing as long as the relation cannot be proven.
847 return cstr.comparePos(lhsPos, cmp, rhsPos);
848 };
850 return cstr.populateAndCompare(lhs, cmp, rhs);
851}
852
855 const Variable &rhs) {
856 int64_t lhsPos = -1, rhsPos = -1;
857 auto stopCondition = [&](Value v, std::optional<int64_t> dim,
859 // Keep processing as long as lhs/rhs were not processed.
860 if (size_t(lhsPos) >= cstr.positionToValueDim.size() ||
861 size_t(rhsPos) >= cstr.positionToValueDim.size())
862 return false;
863 // Keep processing as long as the strong relation cannot be proven.
864 FailureOr<bool> ordered = cstr.strongComparePos(lhsPos, cmp, rhsPos);
865 return failed(ordered);
866 };
868 lhsPos = cstr.populateConstraints(lhs.map, lhs.mapOperands);
869 rhsPos = cstr.populateConstraints(rhs.map, rhs.mapOperands);
870 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
871}
872
874 const Variable &var2) {
875 return strongCompare(var1, ComparisonOperator::EQ, var2);
876}
877
879 MLIRContext *ctx, const HyperrectangularSlice &slice1,
880 const HyperrectangularSlice &slice2) {
881 assert(slice1.getMixedOffsets().size() == slice2.getMixedOffsets().size() &&
882 "expected slices of same rank");
883 assert(slice1.getMixedSizes().size() == slice2.getMixedSizes().size() &&
884 "expected slices of same rank");
885 assert(slice1.getMixedStrides().size() == slice2.getMixedStrides().size() &&
886 "expected slices of same rank");
887
888 Builder b(ctx);
889 bool foundUnknownBound = false;
890 for (int64_t i = 0, e = slice1.getMixedOffsets().size(); i < e; ++i) {
891 AffineMap map =
892 AffineMap::get(/*dimCount=*/0, /*symbolCount=*/4,
893 b.getAffineSymbolExpr(0) +
894 b.getAffineSymbolExpr(1) * b.getAffineSymbolExpr(2) -
895 b.getAffineSymbolExpr(3));
896 {
897 // Case 1: Slices are guaranteed to be non-overlapping if
898 // offset1 + size1 * stride1 <= offset2 (for at least one dimension).
899 SmallVector<OpFoldResult> ofrOperands;
900 ofrOperands.push_back(slice1.getMixedOffsets()[i]);
901 ofrOperands.push_back(slice1.getMixedSizes()[i]);
902 ofrOperands.push_back(slice1.getMixedStrides()[i]);
903 ofrOperands.push_back(slice2.getMixedOffsets()[i]);
904 SmallVector<Value> valueOperands;
905 AffineMap foldedMap =
906 foldAttributesIntoMap(b, map, ofrOperands, valueOperands);
907 FailureOr<int64_t> constBound = computeConstantBound(
908 presburger::BoundType::EQ, Variable(foldedMap, valueOperands));
909 foundUnknownBound |= failed(constBound);
910 if (succeeded(constBound) && *constBound <= 0)
911 return false;
912 }
913 {
914 // Case 2: Slices are guaranteed to be non-overlapping if
915 // offset2 + size2 * stride2 <= offset1 (for at least one dimension).
916 SmallVector<OpFoldResult> ofrOperands;
917 ofrOperands.push_back(slice2.getMixedOffsets()[i]);
918 ofrOperands.push_back(slice2.getMixedSizes()[i]);
919 ofrOperands.push_back(slice2.getMixedStrides()[i]);
920 ofrOperands.push_back(slice1.getMixedOffsets()[i]);
921 SmallVector<Value> valueOperands;
922 AffineMap foldedMap =
923 foldAttributesIntoMap(b, map, ofrOperands, valueOperands);
924 FailureOr<int64_t> constBound = computeConstantBound(
925 presburger::BoundType::EQ, Variable(foldedMap, valueOperands));
926 foundUnknownBound |= failed(constBound);
927 if (succeeded(constBound) && *constBound <= 0)
928 return false;
929 }
930 }
931
932 // If at least one bound could not be computed, we cannot be certain that the
933 // slices are really overlapping.
934 if (foundUnknownBound)
935 return failure();
936
937 // All bounds could be computed and none of the above cases applied.
938 // Therefore, the slices are guaranteed to overlap.
939 return true;
940}
941
943 MLIRContext *ctx, const HyperrectangularSlice &slice1,
944 const HyperrectangularSlice &slice2) {
945 assert(slice1.getMixedOffsets().size() == slice2.getMixedOffsets().size() &&
946 "expected slices of same rank");
947 assert(slice1.getMixedSizes().size() == slice2.getMixedSizes().size() &&
948 "expected slices of same rank");
949 assert(slice1.getMixedStrides().size() == slice2.getMixedStrides().size() &&
950 "expected slices of same rank");
951
952 // The two slices are equivalent if all of their offsets, sizes and strides
953 // are equal. If equality cannot be determined for at least one of those
954 // values, equivalence cannot be determined and this function returns
955 // "failure".
956 for (auto [offset1, offset2] :
957 llvm::zip_equal(slice1.getMixedOffsets(), slice2.getMixedOffsets())) {
958 FailureOr<bool> equal = areEqual(offset1, offset2);
959 if (failed(equal))
960 return failure();
961 if (!equal.value())
962 return false;
963 }
964 for (auto [size1, size2] :
965 llvm::zip_equal(slice1.getMixedSizes(), slice2.getMixedSizes())) {
966 FailureOr<bool> equal = areEqual(size1, size2);
967 if (failed(equal))
968 return failure();
969 if (!equal.value())
970 return false;
971 }
972 for (auto [stride1, stride2] :
973 llvm::zip_equal(slice1.getMixedStrides(), slice2.getMixedStrides())) {
974 FailureOr<bool> equal = areEqual(stride1, stride2);
975 if (failed(equal))
976 return failure();
977 if (!equal.value())
978 return false;
979 }
980 return true;
981}
982
984 llvm::errs() << "==========\nColumns:\n";
985 llvm::errs() << "(column\tdim\tvalue)\n";
986 for (auto [index, valueDim] : llvm::enumerate(positionToValueDim)) {
987 llvm::errs() << " " << index << "\t";
988 if (valueDim) {
989 if (valueDim->second == kIndexValue) {
990 llvm::errs() << "n/a\t";
991 } else {
992 llvm::errs() << valueDim->second << "\t";
993 }
994 llvm::errs() << getOwnerOfValue(valueDim->first)->getName() << " ";
995 if (OpResult result = dyn_cast<OpResult>(valueDim->first)) {
996 llvm::errs() << "(result " << result.getResultNumber() << ")";
997 } else {
998 llvm::errs() << "(bbarg "
999 << cast<BlockArgument>(valueDim->first).getArgNumber()
1000 << ")";
1001 }
1002 llvm::errs() << "\n";
1003 } else {
1004 llvm::errs() << "n/a\tn/a\n";
1005 }
1006 }
1007 llvm::errs() << "\nConstraint set:\n";
1008 cstr.dump();
1009 llvm::errs() << "==========\n";
1010}
1011
1014 assert(!this->dim.has_value() && "dim was already set");
1015 this->dim = dim;
1016#ifndef NDEBUG
1017 assertValidValueDim(value, this->dim, cstr.options);
1018#endif // NDEBUG
1019 return *this;
1020}
1021
1023#ifndef NDEBUG
1024 assertValidValueDim(value, this->dim, cstr.options);
1025#endif // NDEBUG
1026 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
1027}
1028
1032
1036
1038#ifndef NDEBUG
1039 assertValidValueDim(value, this->dim, cstr.options);
1040#endif // NDEBUG
1041 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
1042}
1043
1045#ifndef NDEBUG
1046 assertValidValueDim(value, this->dim, cstr.options);
1047#endif // NDEBUG
1048 cstr.addBound(BoundType::EQ, cstr.getPos(value, this->dim), expr);
1049}
1050
1054
1058
1062
1066
1070
1074
1078
1082
1086
return success()
static bool isIndexLikeType(Type type, ValueBoundsOptions options)
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:116
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< ValueDim > 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.