Skip to content

Commit e896a3e

Browse files
committed
Merge remote-tracking branch 'origin/feature/fused-ops' into jrickert.bump_integration
Required various changes to tosa.tile canonicalization/folding
2 parents c11d5c0 + bb0d9a5 commit e896a3e

25 files changed

+1424
-132
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===- AffineExprBounds.h - Compute bounds of affine expressions *- C++ -*-===//
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+
//
9+
// This header file defines an analysis of affine expressions to compute their
10+
// ranges (lower/upper bounds) in a given context.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
#ifndef MLIR_ANALYSIS_AFFINEEXPRBOUNDS_H
14+
#define MLIR_ANALYSIS_AFFINEEXPRBOUNDS_H
15+
16+
#include "mlir/IR/AffineExprVisitor.h"
17+
#include "mlir/IR/Attributes.h"
18+
#include "mlir/IR/BuiltinAttributes.h"
19+
#include "mlir/Interfaces/InferIntRangeInterface.h"
20+
21+
#include "mlir/IR/AffineExpr.h"
22+
#include "mlir/IR/AffineMap.h"
23+
#include "mlir/Support/LogicalResult.h"
24+
25+
using namespace mlir;
26+
27+
/// This visitor computes the bounds of affine expressions, using as context the
28+
/// bounds of the dimensions of the expression.
29+
///
30+
/// Example:
31+
/// Given bounds 0 <= d0 <= 99 and 0 <= d1 <= 199, we can compute the bounds
32+
/// of the following expression:
33+
/// lb(2 * d0 + 3 * d1) = 0
34+
/// ub(2 * d0 + 3 * d1) = 795
35+
///
36+
/// * The bounds given in the context are inclusive, and the bounds returned
37+
/// are also inclusive.
38+
/// * If bounds are not available for a dimension, std::nullopt can be used
39+
/// instead. The bounds of an expression that involves it will be std::nullopt.
40+
/// * Limitations:
41+
/// - Parametric expressions (using symbols) are not supported.
42+
/// - Unsigned FloorDiv is currently not supported.
43+
class AffineExprBoundsVisitor
44+
: public AffineExprVisitor<AffineExprBoundsVisitor, LogicalResult> {
45+
public:
46+
/// Initialize the context (bounds) with APInt. All bounds must have the same
47+
/// signedness and bit width.
48+
AffineExprBoundsVisitor(ArrayRef<std::optional<APInt>> constLowerBounds,
49+
ArrayRef<std::optional<APInt>> constUpperBounds,
50+
bool boundsSigned, uint64_t bitWidth,
51+
MLIRContext *context);
52+
53+
/// Initialize the context (bounds) with 64-bit signed integers. This allows
54+
/// to directly map index-type values such as Linalg op bounds, which are
55+
/// represented as int64_t.
56+
AffineExprBoundsVisitor(ArrayRef<std::optional<int64_t>> constLowerBounds,
57+
ArrayRef<std::optional<int64_t>> constUpperBounds,
58+
MLIRContext *context);
59+
60+
/// Get the upper bound of \p expr using the context bounds.
61+
std::optional<APInt> getUpperBound(AffineExpr expr);
62+
std::optional<int64_t> getIndexUpperBound(AffineExpr expr);
63+
64+
/// Get the lower bound of \p expr using the context bounds.
65+
std::optional<APInt> getLowerBound(AffineExpr expr);
66+
std::optional<int64_t> getIndexLowerBound(AffineExpr expr);
67+
68+
// These methods are directly called by the AffineExprVisitor base class.
69+
LogicalResult visitMulExpr(AffineBinaryOpExpr expr);
70+
LogicalResult visitAddExpr(AffineBinaryOpExpr expr);
71+
LogicalResult visitDimExpr(AffineDimExpr expr);
72+
LogicalResult visitSymbolExpr(AffineSymbolExpr expr);
73+
LogicalResult visitConstantExpr(AffineConstantExpr expr);
74+
LogicalResult visitCeilDivExpr(AffineBinaryOpExpr expr);
75+
LogicalResult visitFloorDivExpr(AffineBinaryOpExpr expr);
76+
LogicalResult visitModExpr(AffineBinaryOpExpr expr);
77+
78+
private:
79+
bool boundsSigned;
80+
uint64_t bitWidth;
81+
void inferBinOpRange(
82+
AffineBinaryOpExpr expr,
83+
const std::function<ConstantIntRanges(ArrayRef<ConstantIntRanges>)>
84+
&opInference);
85+
86+
/// Bounds that have been computed for subexpressions are memoized and reused.
87+
llvm::DenseMap<AffineExpr, APInt> lb;
88+
llvm::DenseMap<AffineExpr, APInt> ub;
89+
};
90+
91+
#endif // MLIR_ANALYSIS_AFFINEEXPRBOUNDS_H

mlir/include/mlir/Dialect/Linalg/IR/LinalgInterfaces.td

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -860,11 +860,6 @@ def LinalgStructuredInterface
860860
/// `createFlatListOfOperandDims`.
861861
SmallVector<Range, 4> createLoopRanges(OpBuilder &b, Location loc);
862862

863-
/// Compute the static loop sizes necessary to vectorize the computation.
864-
/// This is done by applying `getShapesToLoopsMap` to
865-
/// `createFlatListOfOperandStaticDims`.
866-
SmallVector<int64_t, 4> computeStaticLoopSizes();
867-
868863
/// Returns the value that expresses the shape of the output in terms of
869864
/// shape of the input operands where possible
870865
LogicalResult reifyResultShapes(OpBuilder &b,

mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,7 @@ def Tosa_TileOp : Tosa_InferShapedTypeOp<"tile"> {
17241724
}];
17251725

17261726
let hasFolder = 1;
1727+
let hasCanonicalizer = 1;
17271728
let hasVerifier = 1;
17281729
}
17291730

@@ -1877,6 +1878,15 @@ def Tosa_CastOp: Tosa_Op<"cast", [Pure,
18771878
| signed 16 to float | int16 | float |
18781879
| float 32 to float 64 | float32 | float64 |
18791880
| float 64 to float 32 | float64 | float32 |
1881+
1882+
AMD extensions:
1883+
| signed to unsigned | signed | unsigned|
1884+
| unsigned to signed | unsigned| signed |
1885+
| unsigned to float | unsigned| float |
1886+
- unsigned to signed integer and signed to unsigned integer:
1887+
wrap on overflow
1888+
- unsigned to float:
1889+
uses llvm's float to int conversion with TOSA rounding mode
18801890
}];
18811891

18821892
let arguments = (ins

mlir/include/mlir/IR/AffineExpr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ class AffineExpr {
110110
/// floordiv, ceildiv, and mod is only allowed w.r.t constants.
111111
bool isPureAffine() const;
112112

113+
/// Returns true if this expression is monotonicically increasing with respect
114+
/// to the AffineDimExprs, i.e. increasing the value of any AffineDimExpr will
115+
/// never decrease the value of the result.
116+
bool isMonotonicallyIncreasing() const;
117+
113118
/// Returns the greatest known integral divisor of this affine expression. The
114119
/// result is always positive.
115120
int64_t getLargestKnownDivisor() const;
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
//===- AffineExprBounds.h - Compute bounds of affine expressions *- C++ -*-===//
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+
//
9+
// This file implements an analysis of affine expressions to compute their
10+
// ranges (lower/upper bounds) in a given context.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
#include "mlir/Analysis/AffineExprBounds.h"
14+
15+
#include "mlir/IR/AffineExpr.h"
16+
#include "mlir/IR/AffineExprVisitor.h"
17+
#include "mlir/IR/AffineMap.h"
18+
#include "mlir/IR/BuiltinAttributes.h"
19+
#include "mlir/Interfaces/InferIntRangeInterface.h"
20+
#include "mlir/Interfaces/Utils/InferIntRangeCommon.h"
21+
#include "llvm/ADT/APInt.h"
22+
23+
#include <cstdint>
24+
25+
using namespace mlir;
26+
27+
AffineExprBoundsVisitor::AffineExprBoundsVisitor(
28+
ArrayRef<std::optional<APInt>> constLowerBounds,
29+
ArrayRef<std::optional<APInt>> constUpperBounds, bool boundsSigned,
30+
uint64_t bitWidth, MLIRContext *context)
31+
: boundsSigned(boundsSigned), bitWidth(bitWidth) {
32+
assert(constLowerBounds.size() == constUpperBounds.size());
33+
for (unsigned i = 0; i < constLowerBounds.size(); i++) {
34+
if (constLowerBounds[i].has_value()) {
35+
lb[getAffineDimExpr(i, context)] = constLowerBounds[i].value();
36+
}
37+
if (constUpperBounds[i].has_value()) {
38+
ub[getAffineDimExpr(i, context)] = constUpperBounds[i].value();
39+
}
40+
}
41+
}
42+
43+
AffineExprBoundsVisitor::AffineExprBoundsVisitor(
44+
ArrayRef<std::optional<int64_t>> constLowerBounds,
45+
ArrayRef<std::optional<int64_t>> constUpperBounds, MLIRContext *context)
46+
: boundsSigned(true), bitWidth(64) {
47+
assert(constLowerBounds.size() == constUpperBounds.size());
48+
// Convert int64_ts to APInts.
49+
for (unsigned i = 0; i < constLowerBounds.size(); i++) {
50+
if (constLowerBounds[i].has_value()) {
51+
lb[getAffineDimExpr(i, context)] =
52+
APInt(64, constLowerBounds[i].value(), /*isSigned=*/true);
53+
}
54+
if (constUpperBounds[i].has_value()) {
55+
ub[getAffineDimExpr(i, context)] =
56+
APInt(64, constUpperBounds[i].value(), /*isSigned=*/true);
57+
}
58+
}
59+
}
60+
61+
std::optional<APInt> AffineExprBoundsVisitor::getUpperBound(AffineExpr expr) {
62+
// Use memoized bound if available.
63+
auto i = ub.find(expr);
64+
if (i != ub.end()) {
65+
return i->second;
66+
}
67+
// Compute the bound otherwise.
68+
if (failed(walkPostOrder(expr))) {
69+
return std::nullopt;
70+
}
71+
return ub[expr];
72+
}
73+
74+
std::optional<APInt> AffineExprBoundsVisitor::getLowerBound(AffineExpr expr) {
75+
// Use memoized bound if available.
76+
auto i = lb.find(expr);
77+
if (i != lb.end()) {
78+
return i->second;
79+
}
80+
// Compute the bound otherwise.
81+
if (failed(walkPostOrder(expr))) {
82+
return std::nullopt;
83+
}
84+
return lb[expr];
85+
}
86+
87+
std::optional<int64_t>
88+
AffineExprBoundsVisitor::getIndexUpperBound(AffineExpr expr) {
89+
std::optional<APInt> apIntResult = getUpperBound(expr);
90+
if (!apIntResult)
91+
return std::nullopt;
92+
93+
return apIntResult->getSExtValue();
94+
}
95+
96+
std::optional<int64_t>
97+
AffineExprBoundsVisitor::getIndexLowerBound(AffineExpr expr) {
98+
std::optional<APInt> apIntResult = getLowerBound(expr);
99+
if (!apIntResult)
100+
return std::nullopt;
101+
102+
return apIntResult->getSExtValue();
103+
}
104+
105+
ConstantIntRanges getRange(APInt lb, APInt ub, bool boundsSigned) {
106+
return ConstantIntRanges::range(lb, ub, boundsSigned);
107+
}
108+
109+
/// Wrapper around the intrange::infer* functions that infers the range of
110+
/// binary operations on two ranges.
111+
void AffineExprBoundsVisitor::inferBinOpRange(
112+
AffineBinaryOpExpr expr,
113+
const std::function<ConstantIntRanges(ArrayRef<ConstantIntRanges>)>
114+
&opInference) {
115+
ConstantIntRanges lhsRange =
116+
getRange(lb[expr.getLHS()], ub[expr.getLHS()], boundsSigned);
117+
ConstantIntRanges rhsRange =
118+
getRange(lb[expr.getRHS()], ub[expr.getRHS()], boundsSigned);
119+
ConstantIntRanges result = opInference({lhsRange, rhsRange});
120+
121+
lb[expr] = (boundsSigned) ? result.smin() : result.umin();
122+
ub[expr] = (boundsSigned) ? result.smax() : result.umax();
123+
}
124+
125+
// Visitor method overrides.
126+
LogicalResult AffineExprBoundsVisitor::visitMulExpr(AffineBinaryOpExpr expr) {
127+
inferBinOpRange(expr, [](ArrayRef<ConstantIntRanges> ranges) {
128+
return intrange::inferMul(ranges);
129+
});
130+
return success();
131+
}
132+
LogicalResult AffineExprBoundsVisitor::visitAddExpr(AffineBinaryOpExpr expr) {
133+
inferBinOpRange(expr, [](ArrayRef<ConstantIntRanges> ranges) {
134+
return intrange::inferAdd(ranges);
135+
});
136+
return success();
137+
}
138+
LogicalResult
139+
AffineExprBoundsVisitor::visitCeilDivExpr(AffineBinaryOpExpr expr) {
140+
inferBinOpRange(
141+
expr, [boundsSigned = boundsSigned](ArrayRef<ConstantIntRanges> ranges) {
142+
if (boundsSigned) {
143+
return intrange::inferCeilDivS(ranges);
144+
}
145+
return intrange::inferCeilDivU(ranges);
146+
});
147+
return success();
148+
}
149+
LogicalResult
150+
AffineExprBoundsVisitor::visitFloorDivExpr(AffineBinaryOpExpr expr) {
151+
// There is no inferFloorDivU in the intrange library. We only offer
152+
// computation of bounds for signed floordiv operations.
153+
if (boundsSigned) {
154+
inferBinOpRange(expr, [](ArrayRef<ConstantIntRanges> ranges) {
155+
return intrange::inferFloorDivS(ranges);
156+
});
157+
return success();
158+
}
159+
return failure();
160+
}
161+
LogicalResult AffineExprBoundsVisitor::visitModExpr(AffineBinaryOpExpr expr) {
162+
// Only support integers >= 1 as RHS.
163+
auto rhsConst = dyn_cast<AffineConstantExpr>(expr.getRHS());
164+
if (!rhsConst || rhsConst.getValue() < 1)
165+
return failure();
166+
167+
inferBinOpRange(expr, [boundsSigned =
168+
boundsSigned](ArrayRef<ConstantIntRanges> ranges) {
169+
// Mod must return a value between 0 and N-1.
170+
// Computing (N + (expr mod N)) mod N is guaranteed to yield a result in
171+
// this range.
172+
if (boundsSigned) {
173+
auto rhs = ranges[1];
174+
auto lhs = ranges[0];
175+
return intrange::inferRemS(
176+
{intrange::inferAdd({intrange::inferRemS({lhs, rhs}), rhs}), rhs});
177+
}
178+
return intrange::inferRemU(ranges);
179+
});
180+
return success();
181+
}
182+
LogicalResult AffineExprBoundsVisitor::visitDimExpr(AffineDimExpr expr) {
183+
if (lb.find(expr) == lb.end() || ub.find(expr) == ub.end()) {
184+
return failure();
185+
}
186+
return success();
187+
}
188+
LogicalResult AffineExprBoundsVisitor::visitSymbolExpr(AffineSymbolExpr expr) {
189+
return failure();
190+
}
191+
LogicalResult
192+
AffineExprBoundsVisitor::visitConstantExpr(AffineConstantExpr expr) {
193+
APInt apIntVal =
194+
APInt(bitWidth, static_cast<uint64_t>(expr.getValue()), boundsSigned);
195+
lb[expr] = apIntVal;
196+
ub[expr] = apIntVal;
197+
return success();
198+
}

mlir/lib/Analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ set(LLVM_OPTIONAL_SOURCES
2121
add_subdirectory(Presburger)
2222

2323
add_mlir_library(MLIRAnalysis
24+
AffineExprBounds.cpp
2425
AliasAnalysis.cpp
2526
CallGraph.cpp
2627
DataFlowFramework.cpp

mlir/lib/Conversion/PDLToPDLInterp/PredicateTree.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -768,17 +768,25 @@ struct OrderedPredicate {
768768
/// model.
769769
bool operator<(const OrderedPredicate &rhs) const {
770770
// Sort by:
771+
// * not being a constraint. Rational: When writing constraints, it is
772+
// sometimes assumed that checks for null or operation names are executed
773+
// before the constraint. As there is no dependency between this
774+
// operation, this is not always guaranteed, which can lead to bugs if the
775+
// constraints is not checking inputs for null itself. By ordering
776+
// constraints to the end, it is assured that implicit checks are nun
777+
// before them
771778
// * higher first and secondary order sums
772779
// * lower depth
773780
// * lower position dependency
774781
// * lower predicate dependency
775782
// * lower tie breaking ID
776783
auto *rhsPos = rhs.position;
777-
return std::make_tuple(primary, secondary, rhsPos->getOperationDepth(),
784+
return std::make_tuple(!isa<ConstraintQuestion>(question), primary,
785+
secondary, rhsPos->getOperationDepth(),
778786
rhsPos->getKind(), rhs.question->getKind(), rhs.id) >
779-
std::make_tuple(rhs.primary, rhs.secondary,
780-
position->getOperationDepth(), position->getKind(),
781-
question->getKind(), id);
787+
std::make_tuple(!isa<ConstraintQuestion>(rhs.question), rhs.primary,
788+
rhs.secondary, position->getOperationDepth(),
789+
position->getKind(), question->getKind(), id);
782790
}
783791
};
784792

mlir/lib/Dialect/Linalg/IR/LinalgInterfaces.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,19 +1094,6 @@ SmallVector<Range, 4> LinalgOp::createLoopRanges(OpBuilder &b, Location loc) {
10941094
return res;
10951095
}
10961096

1097-
SmallVector<int64_t, 4> LinalgOp::computeStaticLoopSizes() {
1098-
AffineMap map = getLoopsToShapesMap();
1099-
unsigned numDims = map.getNumDims(), numRes = map.getNumResults();
1100-
SmallVector<int64_t, 4> allShapeSizes = createFlatListOfOperandStaticDims();
1101-
SmallVector<int64_t, 4> res(numDims, 0);
1102-
for (unsigned idx = 0; idx < numRes; ++idx) {
1103-
auto result = map.getResult(idx);
1104-
if (auto d = dyn_cast<AffineDimExpr>(result))
1105-
res[d.getPosition()] = allShapeSizes[idx];
1106-
}
1107-
return res;
1108-
}
1109-
11101097
/// Visitor to check if any of the given set of positions from AffineDimExprs
11111098
/// are used within an AffineExpr.
11121099
struct HasAffineDimExprVisitor

0 commit comments

Comments
 (0)