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