[Mlir-commits] [mlir] [mlir][affine] Update getSliceBounds to allow multi-result upper bound maps (PR #219369)
Uday Bondhugula
llvmlistbot at llvm.org
Thu Aug 27 22:19:46 PDT 2026
https://github.com/bondhugula updated https://github.com/llvm/llvm-project/pull/219369
>From 51b0f3135425e4ce50f25a2d3c539db0e2552a3e Mon Sep 17 00:00:00 2001
From: Uday Bondhugula <uday at polymagelabs.com>
Date: Wed, 26 Aug 2026 16:41:24 +0530
Subject: [PATCH] [mlir][affine] Update getSliceBounds to allow multi-result
upper bound maps
Resolve a long-standing TODO on supporting multi-result upper bound maps
on affine analysis utility getSliceBounds. This makes affine fusion more
powerful.
`getSliceBounds` threw away any upper bound that came out of more than
one inequality and put the constant bound in its place, under a TODO
saying it was conservative until `getConstDifference` in LoopFusion could
handle multiple bounds (b/126426796). Several inequalities is what a
destination loop clamped at the end of the data produces -- of a 1000
long dim tiled by 64, the region a tile reads is bounded by
`min(%i * 64 + 64, 1000)` -- and dropping that for the constant leaves
`1000`, which says only that the slice ends somewhere before the end of
the data. A slice of one 64-wide tile then costs as a slice of everything
from the tile onwards, and fusion refuses it as redundant computation
that isn't there.
Keep such a bound. It is already the min of its results, which is the
form an affine.for upper bound takes, so materializing the slice needs
nothing new. `getConstDifference` takes each result against the lower
bound and keeps the smallest difference that comes out constant: every
result bounds the count from above, so the smallest constant among them
is the tightest constant bound there is. On the example above the
tile-relative result gives 64 and the extent gives nothing constant.
Only a caller that can consume such a bound should be handed one, so this
is behind `allowMultiResultUb`, which the two slice-computation callers
pass and the value-bounds one does not -- it can't express a bound of
several results and would lose the bounds it computes today.
Assisted-by: Claude.
---
.../Analysis/FlatLinearValueConstraints.h | 9 ++++-
.../Analysis/FlatLinearValueConstraints.cpp | 17 +++++---
mlir/lib/Dialect/Affine/Analysis/Utils.cpp | 40 +++++++++++++------
.../Affine/loop-fusion-slice-computation.mlir | 29 ++++++++++++++
4 files changed, 75 insertions(+), 20 deletions(-)
diff --git a/mlir/include/mlir/Analysis/FlatLinearValueConstraints.h b/mlir/include/mlir/Analysis/FlatLinearValueConstraints.h
index 79fb201935f0d..818a05f827ea6 100644
--- a/mlir/include/mlir/Analysis/FlatLinearValueConstraints.h
+++ b/mlir/include/mlir/Analysis/FlatLinearValueConstraints.h
@@ -158,10 +158,15 @@ class FlatLinearConstraints : public presburger::IntegerPolyhedron {
///
/// By default the returned lower bounds are closed and upper bounds are open.
/// If `closedUb` is true, the upper bound is closed.
+ ///
+ /// An upper bound built from more than one inequality is the min of them.
+ /// Only a caller that can consume such a bound should ask for it, via
+ /// `allowMultiResultUb`; the rest are given the constant upper bound
+ /// instead, which is weaker but always a single result.
void getSliceBounds(unsigned offset, unsigned num, MLIRContext *context,
SmallVectorImpl<AffineMap> *lbMaps,
- SmallVectorImpl<AffineMap> *ubMaps,
- bool closedUB = false);
+ SmallVectorImpl<AffineMap> *ubMaps, bool closedUB = false,
+ bool allowMultiResultUb = false);
/// Composes an affine map whose dimensions and symbols match one to one with
/// the dimensions and symbols of this FlatLinearConstraints. The results of
diff --git a/mlir/lib/Analysis/FlatLinearValueConstraints.cpp b/mlir/lib/Analysis/FlatLinearValueConstraints.cpp
index 6d28387b0e5c8..8acdf0a7329a9 100644
--- a/mlir/lib/Analysis/FlatLinearValueConstraints.cpp
+++ b/mlir/lib/Analysis/FlatLinearValueConstraints.cpp
@@ -8,8 +8,8 @@
#include "mlir/Analysis//FlatLinearValueConstraints.h"
+#include "mlir/Analysis/Presburger/PWMAFunction.h"
#include "mlir/Analysis/Presburger/PresburgerSpace.h"
-#include "mlir/Analysis/Presburger/Simplex.h"
#include "mlir/Analysis/Presburger/Utils.h"
#include "mlir/IR/AffineExprVisitor.h"
#include "mlir/IR/Builders.h"
@@ -701,7 +701,8 @@ void FlatLinearConstraints::getSliceBounds(unsigned offset, unsigned num,
MLIRContext *context,
SmallVectorImpl<AffineMap> *lbMaps,
SmallVectorImpl<AffineMap> *ubMaps,
- bool closedUB) {
+ bool closedUB,
+ bool allowMultiResultUb) {
assert(offset + num <= getNumDimVars() && "invalid range");
// Basic simplification.
@@ -753,9 +754,6 @@ void FlatLinearConstraints::getSliceBounds(unsigned offset, unsigned num,
// If the above fails, we'll just use the constant lower bound and the
// constant upper bound (if they exist) as the slice bounds.
- // TODO: being conservative for the moment in cases that
- // lead to multiple bounds - until getConstDifference in LoopFusion.cpp is
- // fixed (b/126426796).
if (!lbMap || lbMap.getNumResults() != 1) {
LLVM_DEBUG(llvm::dbgs()
<< "WARNING: Potentially over-approximating slice lb\n");
@@ -765,7 +763,14 @@ void FlatLinearConstraints::getSliceBounds(unsigned offset, unsigned num,
getAffineConstantExpr(*lbConst, context));
}
}
- if (!ubMap || ubMap.getNumResults() != 1) {
+ // An upper bound of several results is the min of them, for e.g.
+ // the bound of a tiled loop clamped at the end of the data looks like:
+ // `min(%i * 64 + 64, 1000)`. Falling back to the constant bound
+ // the way the lower one does would drop the tile-relative result and
+ // leave nothing but the extent of the data, turning a slice of a single
+ // tile into a slice of everything from the tile onwards.
+ if (!ubMap || ubMap.getNumResults() == 0 ||
+ (!allowMultiResultUb && ubMap.getNumResults() != 1)) {
LLVM_DEBUG(llvm::dbgs()
<< "WARNING: Potentially over-approximating slice ub\n");
auto ubConst = getConstantBound64(BoundType::UB, pos + offset);
diff --git a/mlir/lib/Dialect/Affine/Analysis/Utils.cpp b/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
index 321c8e34d907c..df965cdc4083b 100644
--- a/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
@@ -1754,7 +1754,8 @@ mlir::affine::computeSliceUnion(ArrayRef<Operation *> opsA,
// Get slice bounds from slice union constraints 'sliceUnionCst'.
sliceUnionCst.getSliceBounds(/*offset=*/0, numSliceLoopIVs,
opsA[0]->getContext(), &sliceUnion->lbs,
- &sliceUnion->ubs);
+ &sliceUnion->ubs, /*closedUb=*/false,
+ /*allowMultiResultUb=*/true);
// Add slice bound operands of union.
SmallVector<Value, 4> sliceBoundOperands;
@@ -1791,21 +1792,35 @@ mlir::affine::computeSliceUnion(ArrayRef<Operation *> opsA,
return SliceComputationResult::Success;
}
-// TODO: extend this to handle multiple result maps.
+/// Returns the number of iterations the slice bounded below by `lbMap` and
+/// above by `ubMap` runs for, where that is a constant.
+///
+/// An upper bound of several results is the min of them, so each result taken
+/// against the lower bound bounds the count from above and the smallest of
+/// those that comes out constant is the tightest constant bound there is. A
+/// tiled loop clamped at the end of the data has exactly this shape --
+/// `min(%i * 64 + 64, 1000)` over `%i * 64` -- where the tile-relative result
+/// gives the 64 and the extent gives nothing constant at all.
static std::optional<uint64_t> getConstDifference(AffineMap lbMap,
AffineMap ubMap) {
- assert(lbMap.getNumResults() == 1 && "expected single result bound map");
- assert(ubMap.getNumResults() == 1 && "expected single result bound map");
+ assert(lbMap.getNumResults() == 1 && "expected single result lower bound");
+ assert(ubMap.getNumResults() >= 1 && "expected at least one upper bound");
assert(lbMap.getNumDims() == ubMap.getNumDims());
assert(lbMap.getNumSymbols() == ubMap.getNumSymbols());
AffineExpr lbExpr(lbMap.getResult(0));
- AffineExpr ubExpr(ubMap.getResult(0));
- auto loopSpanExpr = simplifyAffineExpr(ubExpr - lbExpr, lbMap.getNumDims(),
- lbMap.getNumSymbols());
- auto cExpr = dyn_cast<AffineConstantExpr>(loopSpanExpr);
- if (!cExpr)
- return std::nullopt;
- return cExpr.getValue();
+ std::optional<uint64_t> tripCount;
+ for (AffineExpr ubExpr : ubMap.getResults()) {
+ AffineExpr loopSpanExpr = simplifyAffineExpr(
+ ubExpr - lbExpr, lbMap.getNumDims(), lbMap.getNumSymbols());
+ auto cExpr = dyn_cast<AffineConstantExpr>(loopSpanExpr);
+ if (!cExpr)
+ continue;
+ if (cExpr.getValue() < 0)
+ return 0;
+ tripCount =
+ std::min(tripCount.value_or(UINT64_MAX), (uint64_t)cExpr.getValue());
+ }
+ return tripCount;
}
// Builds a map 'tripCountMap' from AffineForOp to constant trip count for loop
@@ -1899,7 +1914,8 @@ void mlir::affine::getComputationSliceState(
// Get bounds for slice IVs in terms of other IVs, symbols, and constants.
sliceCst.getSliceBounds(offset, numSliceLoopIVs, depSourceOp->getContext(),
- &sliceState->lbs, &sliceState->ubs);
+ &sliceState->lbs, &sliceState->ubs,
+ /*closedUb=*/false, /*allowMultiResultUb=*/true);
// Set up bound operands for the slice's lower and upper bounds.
SmallVector<Value, 4> sliceBoundOperands;
diff --git a/mlir/test/Dialect/Affine/loop-fusion-slice-computation.mlir b/mlir/test/Dialect/Affine/loop-fusion-slice-computation.mlir
index aa79ee26928e2..05bf3d415e1d6 100644
--- a/mlir/test/Dialect/Affine/loop-fusion-slice-computation.mlir
+++ b/mlir/test/Dialect/Affine/loop-fusion-slice-computation.mlir
@@ -160,3 +160,32 @@ func.func @slice_loop_nest_with_smaller_outer_trip_count() {
}
return
}
+
+// -----
+
+// The destination loop of a tiled dim is clamped at the end of the data by a
+// min upper bound, so the region of the source the tile reads is bounded by a
+// min too: `min(%i * 64 + 64, 1000)`. Such a bound is kept as the several
+// results it takes rather than being dropped for the constant bound, which
+// would say only that the slice ends somewhere before the end of the data and
+// make a slice of one tile look like a slice of everything from the tile on.
+
+#ub = affine_map<(d0) -> (64, d0 * -64 + 1000)>
+
+// CHECK-LABEL: func @slice_ub_from_min_bounded_dst() {
+func.func @slice_ub_from_min_bounded_dst() {
+ %0 = memref.alloc() : memref<1000xf32>
+ %cst = arith.constant 7.000000e+00 : f32
+ affine.for %i0 = 0 to 1000 {
+ // expected-remark at -1 {{Incorrect slice ( src loop: 1, dst loop: 0, depth: 1 : insert point: (1, 1) loop bounds: [(d0) -> (d0 floordiv 64), (d0) -> (d0 floordiv 64 + 1)] [(d0) -> (d0 mod 64), (d0) -> (d0 mod 64 + 1)] )}}
+ affine.store %cst, %0[%i0] : memref<1000xf32>
+ }
+ affine.for %i1 = 0 to 16 {
+ // expected-remark at -1 {{slice ( src loop: 0, dst loop: 1, depth: 1 : insert point: (1, 0) loop bounds: [(d0) -> (d0 * 64), (d0) -> (d0 * 64 + 64, 1000)] )}}
+ // expected-remark at -2 {{slice ( src loop: 0, dst loop: 1, depth: 2 : insert point: (2, 0) loop bounds: [(d0, d1) -> (d0 * 64 + d1), (d0, d1) -> (d0 * 64 + d1 + 1)] )}}
+ affine.for %i2 = 0 to min #ub(%i1) {
+ %1 = affine.load %0[%i1 * 64 + %i2] : memref<1000xf32>
+ }
+ }
+ return
+}
More information about the Mlir-commits
mailing list