MLIR 24.0.0git
ValueBoundsOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- ValueBoundsOpInterfaceImpl.cpp - Impl. of ValueBoundsOpInterface ---===//
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
13
14using namespace mlir;
15
16namespace mlir {
17namespace scf {
18namespace {
19
20static AffineExpr getTripCountExpr(OpFoldResult lb, OpFoldResult ub,
21 OpFoldResult step,
22 ValueBoundsConstraintSet &cstr) {
23 AffineExpr lbExpr = cstr.getExpr(lb);
24 AffineExpr ubExpr = cstr.getExpr(ub);
25 AffineExpr stepExpr = cstr.getExpr(step);
26 AffineExpr tripCountExpr =
27 AffineExpr(ubExpr - lbExpr).ceilDiv(stepExpr); // (ub - lb) / step
28 return tripCountExpr;
29}
30
31static void populateIVBounds(OpFoldResult lb, OpFoldResult ub,
32 OpFoldResult step, Value iv,
33 ValueBoundsConstraintSet &cstr) {
34 cstr.bound(iv) >= cstr.getExpr(lb);
35 cstr.bound(iv) < cstr.getExpr(ub);
36 // iv <= lb + ((ub-lb)/step - 1) * step
37 // This bound does not replace the `iv < ub` constraint mentioned above,
38 // since constraints involving the multiplication of two constraint set
39 // dimensions are not supported.
40 AffineExpr tripCountMinusOne =
41 getTripCountExpr(lb, ub, step, cstr) - cstr.getExpr(1);
42 AffineExpr computedUpperBound =
43 cstr.getExpr(lb) + AffineExpr(tripCountMinusOne * cstr.getExpr(step));
44 cstr.bound(iv) <= computedUpperBound;
45}
46
47struct ForOpInterface
48 : public ValueBoundsOpInterface::ExternalModel<ForOpInterface, ForOp> {
49
50 /// Populate bounds of values/dimensions for iter_args/OpResults. If the
51 /// value/dimension size does not change in an iteration, we can deduce that
52 /// it the same as the initial value/dimension.
53 ///
54 /// Example 1:
55 /// %0 = scf.for ... iter_args(%arg0 = %t) -> tensor<?xf32> {
56 /// ...
57 /// %1 = tensor.insert %f into %arg0[...] : tensor<?xf32>
58 /// scf.yield %1 : tensor<?xf32>
59 /// }
60 /// --> bound(%0)[0] == bound(%t)[0]
61 /// --> bound(%arg0)[0] == bound(%t)[0]
62 ///
63 /// Example 2:
64 /// %0 = scf.for ... iter_args(%arg0 = %t) -> tensor<?xf32> {
65 /// %sz = tensor.dim %arg0 : tensor<?xf32>
66 /// %incr = arith.addi %sz, %c1 : index
67 /// %1 = tensor.empty(%incr) : tensor<?xf32>
68 /// scf.yield %1 : tensor<?xf32>
69 /// }
70 /// --> The yielded tensor dimension size changes with each iteration. Such
71 /// loops are not supported and no constraints are added.
72 static void populateIterArgBounds(scf::ForOp forOp, Value value,
73 std::optional<int64_t> dim,
74 ValueBoundsConstraintSet &cstr) {
75 // `value` is an iter_arg or an OpResult.
76 int64_t iterArgIdx;
77 if (auto iterArg = llvm::dyn_cast<BlockArgument>(value)) {
78 iterArgIdx = iterArg.getArgNumber() - forOp.getNumInductionVars();
79 } else {
80 iterArgIdx = llvm::cast<OpResult>(value).getResultNumber();
81 }
82
83 Value yieldedValue = cast<scf::YieldOp>(forOp.getBody()->getTerminator())
84 .getOperand(iterArgIdx);
85 Value iterArg = forOp.getRegionIterArg(iterArgIdx);
86 Value initArg = forOp.getInitArgs()[iterArgIdx];
87
88 // An EQ constraint can be added if the yielded value (dimension size)
89 // equals the corresponding block argument (dimension size).
90 if (cstr.populateAndCompare(
91 /*lhs=*/{yieldedValue, dim},
93 /*rhs=*/{iterArg, dim})) {
94 if (dim.has_value()) {
95 cstr.bound(value)[*dim] == cstr.getExpr(initArg, dim);
96 } else {
97 cstr.bound(value) == cstr.getExpr(initArg);
98 }
99 }
100
101 if (dim.has_value() || isa<BlockArgument>(value))
102 return;
103
104 // The closed form below holds only if the loop runs at least once (the
105 // trip count is not clamped at 0) and the bounds are compared as signed
106 // integers.
107 if (forOp.getUnsignedCmp() ||
108 !cstr.populateAndCompare(
109 forOp.getUpperBound(),
111 forOp.getLowerBound()))
112 return;
113
114 // `value` is result of `forOp`, we can prove that:
115 // %result == %init_arg + trip_count * (%yielded_value - %iter_arg).
116 // Where trip_count is (ub - lb) / step.
117 AffineExpr tripCountExpr = getTripCountExpr(
118 forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep(), cstr);
119 AffineExpr oneIterAdvanceExpr =
120 cstr.getExpr(yieldedValue) - cstr.getExpr(iterArg);
121 cstr.bound(value) ==
122 cstr.getExpr(initArg) + AffineExpr(tripCountExpr * oneIterAdvanceExpr);
123 }
124
125 void populateBoundsForIndexValue(Operation *op, Value value,
126 ValueBoundsConstraintSet &cstr) const {
127 auto forOp = cast<ForOp>(op);
128
129 if (value == forOp.getInductionVar()) {
130 // The IV bounds are signed; unsigned loops are not modeled.
131 if (forOp.getUnsignedCmp())
132 return;
133 return populateIVBounds(forOp.getLowerBound(), forOp.getUpperBound(),
134 forOp.getStep(), value, cstr);
135 }
136
137 // Handle iter_args and OpResults.
138 populateIterArgBounds(forOp, value, std::nullopt, cstr);
139 }
140
141 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
142 ValueBoundsConstraintSet &cstr) const {
143 auto forOp = cast<ForOp>(op);
144 // Handle iter_args and OpResults.
145 populateIterArgBounds(forOp, value, dim, cstr);
146 }
147};
148
149struct ForallOpInterface
150 : public ValueBoundsOpInterface::ExternalModel<ForallOpInterface,
151 ForallOp> {
152
153 void populateBoundsForIndexValue(Operation *op, Value value,
154 ValueBoundsConstraintSet &cstr) const {
155 auto forallOp = cast<ForallOp>(op);
156
157 // Index values should be induction variables, since the semantics of
158 // tensor::ParallelInsertSliceOp requires forall outputs to be ranked
159 // tensors.
160 auto blockArg = cast<BlockArgument>(value);
161 assert(blockArg.getArgNumber() < forallOp.getInductionVars().size() &&
162 "expected index value to be an induction var");
163 int64_t idx = blockArg.getArgNumber();
164 return populateIVBounds(forallOp.getMixedLowerBound()[idx],
165 forallOp.getMixedUpperBound()[idx],
166 forallOp.getMixedStep()[idx], value, cstr);
167 }
168
169 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
170 ValueBoundsConstraintSet &cstr) const {
171 auto forallOp = cast<ForallOp>(op);
172
173 // `value` is an iter_arg or an OpResult.
174 int64_t iterArgIdx;
175 if (auto iterArg = llvm::dyn_cast<BlockArgument>(value)) {
176 iterArgIdx = iterArg.getArgNumber() - forallOp.getInductionVars().size();
177 } else {
178 iterArgIdx = llvm::cast<OpResult>(value).getResultNumber();
179 }
180
181 // The forall results and output arguments have the same sizes as the output
182 // operands.
183 Value outputOperand = forallOp.getOutputs()[iterArgIdx];
184 cstr.bound(value)[dim] == cstr.getExpr(outputOperand, dim);
185 }
186};
187
188struct IfOpInterface
189 : public ValueBoundsOpInterface::ExternalModel<IfOpInterface, IfOp> {
190
191 static void populateBounds(scf::IfOp ifOp, Value value,
192 std::optional<int64_t> dim,
193 ValueBoundsConstraintSet &cstr) {
194 unsigned int resultNum = cast<OpResult>(value).getResultNumber();
195 Value thenValue = ifOp.thenYield().getResults()[resultNum];
196 Value elseValue = ifOp.elseYield().getResults()[resultNum];
197
198 auto boundsBuilder = cstr.bound(value);
199 if (dim)
200 boundsBuilder[*dim];
201
202 // Compare yielded values.
203 // If thenValue <= elseValue:
204 // * result <= elseValue
205 // * result >= thenValue
206 if (cstr.populateAndCompare(
207 /*lhs=*/{thenValue, dim},
209 /*rhs=*/{elseValue, dim})) {
210 if (dim) {
211 cstr.bound(value)[*dim] >= cstr.getExpr(thenValue, dim);
212 cstr.bound(value)[*dim] <= cstr.getExpr(elseValue, dim);
213 } else {
214 cstr.bound(value) >= thenValue;
215 cstr.bound(value) <= elseValue;
216 }
217 }
218 // If elseValue <= thenValue:
219 // * result <= thenValue
220 // * result >= elseValue
221 if (cstr.populateAndCompare(
222 /*lhs=*/{elseValue, dim},
224 /*rhs=*/{thenValue, dim})) {
225 if (dim) {
226 cstr.bound(value)[*dim] >= cstr.getExpr(elseValue, dim);
227 cstr.bound(value)[*dim] <= cstr.getExpr(thenValue, dim);
228 } else {
229 cstr.bound(value) >= elseValue;
230 cstr.bound(value) <= thenValue;
231 }
232 }
233 }
234
235 void populateBoundsForIndexValue(Operation *op, Value value,
236 ValueBoundsConstraintSet &cstr) const {
237 populateBounds(cast<IfOp>(op), value, /*dim=*/std::nullopt, cstr);
238 }
239
240 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
241 ValueBoundsConstraintSet &cstr) const {
242 populateBounds(cast<IfOp>(op), value, dim, cstr);
243 }
244};
245
246} // namespace
247} // namespace scf
248} // namespace mlir
249
251 DialectRegistry &registry) {
252 registry.addExtension(+[](MLIRContext *ctx, scf::SCFDialect *dialect) {
253 scf::ForOp::attachInterface<scf::ForOpInterface>(*ctx);
254 scf::ForallOp::attachInterface<scf::ForallOpInterface>(*ctx);
255 scf::IfOp::attachInterface<scf::IfOpInterface>(*ctx);
256 });
257}
AffineExpr ceilDiv(uint64_t v) const
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
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.
BoundBuilder bound(Value value)
Add a bound for the given index-typed value or shaped value.
bool populateAndCompare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Populate constraints for lhs/rhs (until the stop condition is met).
void registerValueBoundsOpInterfaceExternalModels(DialectRegistry &registry)
Include the generated interface declarations.