MLIR 22.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 // `value` is result of `forOp`, we can prove that:
105 // %result == %init_arg + trip_count * (%yielded_value - %iter_arg).
106 // Where trip_count is (ub - lb) / step.
107 AffineExpr tripCountExpr = getTripCountExpr(
108 forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep(), cstr);
109 AffineExpr oneIterAdvanceExpr =
110 cstr.getExpr(yieldedValue) - cstr.getExpr(iterArg);
111 cstr.bound(value) ==
112 cstr.getExpr(initArg) + AffineExpr(tripCountExpr * oneIterAdvanceExpr);
113 }
114
115 void populateBoundsForIndexValue(Operation *op, Value value,
116 ValueBoundsConstraintSet &cstr) const {
117 auto forOp = cast<ForOp>(op);
118
119 if (value == forOp.getInductionVar()) {
120 return populateIVBounds(forOp.getLowerBound(), forOp.getUpperBound(),
121 forOp.getStep(), value, cstr);
122 }
123
124 // Handle iter_args and OpResults.
125 populateIterArgBounds(forOp, value, std::nullopt, cstr);
126 }
127
128 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
129 ValueBoundsConstraintSet &cstr) const {
130 auto forOp = cast<ForOp>(op);
131 // Handle iter_args and OpResults.
132 populateIterArgBounds(forOp, value, dim, cstr);
133 }
134};
135
136struct ForallOpInterface
137 : public ValueBoundsOpInterface::ExternalModel<ForallOpInterface,
138 ForallOp> {
139
140 void populateBoundsForIndexValue(Operation *op, Value value,
141 ValueBoundsConstraintSet &cstr) const {
142 auto forallOp = cast<ForallOp>(op);
143
144 // Index values should be induction variables, since the semantics of
145 // tensor::ParallelInsertSliceOp requires forall outputs to be ranked
146 // tensors.
147 auto blockArg = cast<BlockArgument>(value);
148 assert(blockArg.getArgNumber() < forallOp.getInductionVars().size() &&
149 "expected index value to be an induction var");
150 int64_t idx = blockArg.getArgNumber();
151 return populateIVBounds(forallOp.getMixedLowerBound()[idx],
152 forallOp.getMixedUpperBound()[idx],
153 forallOp.getMixedStep()[idx], value, cstr);
154 }
155
156 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
157 ValueBoundsConstraintSet &cstr) const {
158 auto forallOp = cast<ForallOp>(op);
159
160 // `value` is an iter_arg or an OpResult.
161 int64_t iterArgIdx;
162 if (auto iterArg = llvm::dyn_cast<BlockArgument>(value)) {
163 iterArgIdx = iterArg.getArgNumber() - forallOp.getInductionVars().size();
164 } else {
165 iterArgIdx = llvm::cast<OpResult>(value).getResultNumber();
166 }
167
168 // The forall results and output arguments have the same sizes as the output
169 // operands.
170 Value outputOperand = forallOp.getOutputs()[iterArgIdx];
171 cstr.bound(value)[dim] == cstr.getExpr(outputOperand, dim);
172 }
173};
174
175struct IfOpInterface
176 : public ValueBoundsOpInterface::ExternalModel<IfOpInterface, IfOp> {
177
178 static void populateBounds(scf::IfOp ifOp, Value value,
179 std::optional<int64_t> dim,
180 ValueBoundsConstraintSet &cstr) {
181 unsigned int resultNum = cast<OpResult>(value).getResultNumber();
182 Value thenValue = ifOp.thenYield().getResults()[resultNum];
183 Value elseValue = ifOp.elseYield().getResults()[resultNum];
184
185 auto boundsBuilder = cstr.bound(value);
186 if (dim)
187 boundsBuilder[*dim];
188
189 // Compare yielded values.
190 // If thenValue <= elseValue:
191 // * result <= elseValue
192 // * result >= thenValue
193 if (cstr.populateAndCompare(
194 /*lhs=*/{thenValue, dim},
196 /*rhs=*/{elseValue, dim})) {
197 if (dim) {
198 cstr.bound(value)[*dim] >= cstr.getExpr(thenValue, dim);
199 cstr.bound(value)[*dim] <= cstr.getExpr(elseValue, dim);
200 } else {
201 cstr.bound(value) >= thenValue;
202 cstr.bound(value) <= elseValue;
203 }
204 }
205 // If elseValue <= thenValue:
206 // * result <= thenValue
207 // * result >= elseValue
208 if (cstr.populateAndCompare(
209 /*lhs=*/{elseValue, dim},
211 /*rhs=*/{thenValue, dim})) {
212 if (dim) {
213 cstr.bound(value)[*dim] >= cstr.getExpr(elseValue, dim);
214 cstr.bound(value)[*dim] <= cstr.getExpr(thenValue, dim);
215 } else {
216 cstr.bound(value) >= elseValue;
217 cstr.bound(value) <= thenValue;
218 }
219 }
220 }
221
222 void populateBoundsForIndexValue(Operation *op, Value value,
223 ValueBoundsConstraintSet &cstr) const {
224 populateBounds(cast<IfOp>(op), value, /*dim=*/std::nullopt, cstr);
225 }
226
227 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
228 ValueBoundsConstraintSet &cstr) const {
229 populateBounds(cast<IfOp>(op), value, dim, cstr);
230 }
231};
232
233} // namespace
234} // namespace scf
235} // namespace mlir
236
238 DialectRegistry &registry) {
239 registry.addExtension(+[](MLIRContext *ctx, scf::SCFDialect *dialect) {
240 scf::ForOp::attachInterface<scf::ForOpInterface>(*ctx);
241 scf::ForallOp::attachInterface<scf::ForallOpInterface>(*ctx);
242 scf::IfOp::attachInterface<scf::IfOpInterface>(*ctx);
243 });
244}
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.