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