MLIR 23.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 arith {
18namespace {
19
20struct ConstantOpInterface
21 : public ValueBoundsOpInterface::ExternalModel<ConstantOpInterface,
22 ConstantOp> {
23 void populateBoundsForIndexValue(Operation *op, Value value,
24 ValueBoundsConstraintSet &cstr) const {
25 auto constantOp = cast<ConstantOp>(op);
26 assert(value == constantOp.getResult() && "invalid value");
27
28 if (auto attr = llvm::dyn_cast<IntegerAttr>(constantOp.getValue()))
29 cstr.bound(value) == attr.getInt();
30 }
31};
32
33struct ExtSIOpInterface
34 : public ValueBoundsOpInterface::ExternalModel<ExtSIOpInterface, ExtSIOp> {
35 void populateBoundsForIndexValue(Operation *op, Value value,
36 ValueBoundsConstraintSet &cstr) const {
37 auto extSIOp = cast<ExtSIOp>(op);
38 assert(value == extSIOp.getOut() && "invalid value");
39
40 // Sign extension preserves the signed value (unlike zero extension where
41 // the result may be negative), so the bound is an exact equality.
42 cstr.bound(value) == cstr.getExpr(extSIOp.getIn());
43 }
44};
45
46struct AddIOpInterface
47 : public ValueBoundsOpInterface::ExternalModel<AddIOpInterface, AddIOp> {
48 void populateBoundsForIndexValue(Operation *op, Value value,
49 ValueBoundsConstraintSet &cstr) const {
50 auto addIOp = cast<AddIOp>(op);
51 assert(value == addIOp.getResult() && "invalid value");
52
53 // Note: `getExpr` has a side effect: it may add a new column to the
54 // constraint system. The evaluation order of addition operands is
55 // unspecified in C++. To make sure that all compilers produce the exact
56 // same results (that can be FileCheck'd), it is important that `getExpr`
57 // is called first and assigned to temporary variables, and the addition
58 // is performed afterwards.
59 AffineExpr lhs = cstr.getExpr(addIOp.getLhs());
60 AffineExpr rhs = cstr.getExpr(addIOp.getRhs());
61 cstr.bound(value) == lhs + rhs;
62 }
63};
64
65struct DivUIOpInterface
66 : public ValueBoundsOpInterface::ExternalModel<DivUIOpInterface,
67 arith::DivUIOp> {
68 void populateBoundsForIndexValue(Operation *op, Value value,
69 ValueBoundsConstraintSet &cstr) const {
70 auto divOp = cast<arith::DivUIOp>(op);
71 assert(value == divOp.getResult() && "invalid value");
72
73 bool lhsNonNegative =
75 bool rhsPositive =
77 if (!lhsNonNegative || !rhsPositive)
78 return;
79
80 AffineExpr lhs = cstr.getExpr(divOp.getLhs());
81 AffineExpr rhs = cstr.getExpr(divOp.getRhs());
82 cstr.bound(value) >= 0;
83 cstr.bound(value) == lhs.floorDiv(rhs);
84 }
85};
86
87struct DivSIOpInterface
88 : public ValueBoundsOpInterface::ExternalModel<DivSIOpInterface,
89 arith::DivSIOp> {
90 void populateBoundsForIndexValue(Operation *op, Value value,
91 ValueBoundsConstraintSet &cstr) const {
92 auto divOp = cast<arith::DivSIOp>(op);
93 assert(value == divOp.getResult() && "invalid value");
94
95 Value lhsValue = divOp.getLhs();
96 Value rhsValue = divOp.getRhs();
97
98 bool lhsNonNegative =
100 bool lhsNonPositive =
102 bool rhsPositive =
104 bool rhsNegative =
106
107 AffineExpr lhs = cstr.getExpr(lhsValue);
108 AffineExpr rhs = cstr.getExpr(rhsValue);
109
110 // divsi rounds toward zero, unlike floorDiv/ceilDiv which round toward
111 // negative/positive infinity respectively. When the result is non-negative,
112 // divsi equals floorDiv(lhs, rhs); when negative, it equals ceilDiv(lhs,
113 // rhs). Without knowing the sign, bound the result between those two
114 // expressions, which is always correct.
115 cstr.bound(value) >= lhs.floorDiv(rhs);
116 cstr.bound(value) <= lhs.ceilDiv(rhs);
117
118 // If the sign of the result is known, we can use the exact expression.
119 if ((lhsNonNegative && rhsPositive) || (lhsNonPositive && rhsNegative)) {
120 cstr.bound(value) == lhs.floorDiv(rhs);
121 cstr.bound(value) >= 0;
122 } else if ((lhsNonPositive && rhsPositive) ||
123 (lhsNonNegative && rhsNegative)) {
124 cstr.bound(value) == lhs.ceilDiv(rhs);
125 cstr.bound(value) <= 0;
126 }
127 }
128};
129
130struct SubIOpInterface
131 : public ValueBoundsOpInterface::ExternalModel<SubIOpInterface, SubIOp> {
132 void populateBoundsForIndexValue(Operation *op, Value value,
133 ValueBoundsConstraintSet &cstr) const {
134 auto subIOp = cast<SubIOp>(op);
135 assert(value == subIOp.getResult() && "invalid value");
136
137 AffineExpr lhs = cstr.getExpr(subIOp.getLhs());
138 AffineExpr rhs = cstr.getExpr(subIOp.getRhs());
139 cstr.bound(value) == lhs - rhs;
140 }
141};
142
143struct MulIOpInterface
144 : public ValueBoundsOpInterface::ExternalModel<MulIOpInterface, MulIOp> {
145 void populateBoundsForIndexValue(Operation *op, Value value,
146 ValueBoundsConstraintSet &cstr) const {
147 auto mulIOp = cast<MulIOp>(op);
148 assert(value == mulIOp.getResult() && "invalid value");
149
150 AffineExpr lhs = cstr.getExpr(mulIOp.getLhs());
151 AffineExpr rhs = cstr.getExpr(mulIOp.getRhs());
152 cstr.bound(value) == (lhs * rhs);
153 }
154};
155
156struct FloorDivSIOpInterface
157 : public ValueBoundsOpInterface::ExternalModel<FloorDivSIOpInterface,
158 FloorDivSIOp> {
159 void populateBoundsForIndexValue(Operation *op, Value value,
160 ValueBoundsConstraintSet &cstr) const {
161 auto divSIOp = cast<FloorDivSIOp>(op);
162 assert(value == divSIOp.getResult() && "invalid value");
163
164 AffineExpr lhs = cstr.getExpr(divSIOp.getLhs());
165 AffineExpr rhs = cstr.getExpr(divSIOp.getRhs());
166 cstr.bound(value) == lhs.floorDiv(rhs);
167 }
168};
169
170struct CeilDivSIOpInterface
171 : public ValueBoundsOpInterface::ExternalModel<CeilDivSIOpInterface,
172 CeilDivSIOp> {
173 void populateBoundsForIndexValue(Operation *op, Value value,
174 ValueBoundsConstraintSet &cstr) const {
175 auto divSIOp = cast<CeilDivSIOp>(op);
176 assert(value == divSIOp.getResult() && "invalid value");
177
178 AffineExpr lhs = cstr.getExpr(divSIOp.getLhs());
179 AffineExpr rhs = cstr.getExpr(divSIOp.getRhs());
180 cstr.bound(value) == lhs.ceilDiv(rhs);
181 }
182};
183
184struct RemSIOpInterface
185 : public ValueBoundsOpInterface::ExternalModel<RemSIOpInterface, RemSIOp> {
186 void populateBoundsForIndexValue(Operation *op, Value value,
187 ValueBoundsConstraintSet &cstr) const {
188 auto remSIOp = cast<RemSIOp>(op);
189 assert(value == remSIOp.getResult() && "invalid value");
190
191 Value lhsValue = remSIOp.getLhs();
192 Value rhsValue = remSIOp.getRhs();
193 AffineExpr rhs = cstr.getExpr(rhsValue);
194 bool rhsPositive =
196 bool rhsNegative =
198
199 // The result of remsi has the same sign as the dividend (lhs) and also
200 // fulfills |result| < |rhs|. The sign of lhs does not need to be a
201 // compile-time constant: it is sufficient if the constraint set can prove
202 // it. For lhs == 0 both branches may fire, which is consistent since the
203 // result is then 0. f.e:
204 // lhs rhs result bounds
205 // ---- ---- ------ --------------------------------------------------
206 // 7 3 1 0 <= val && val <= rhs-1 = 2 -> [0, 2]
207 // 7 -3 1 0 <= val && val <= -rhs-1 = 2 -> [0, 2]
208 // -7 3 -1 val <= 0 && val >= 1-rhs = -2 -> [-2, 0]
209 // -7 -3 -1 val <= 0 && val >= rhs+1 = -2 -> [-2, 0]
210 // 0 3 0 both lhs branches fire (0<=val and val<=0) -> val
211 // == 0
213 cstr.bound(value) <= 0;
214 if (rhsPositive)
215 cstr.bound(value) >= 1 - rhs;
216 if (rhsNegative)
217 cstr.bound(value) >= rhs + 1;
218 }
220 cstr.bound(value) >= 0;
221 if (rhsPositive)
222 cstr.bound(value) <= rhs - 1;
223 if (rhsNegative)
224 cstr.bound(value) <= -rhs - 1;
225 }
226 }
227};
228
229struct RemUIOpInterface
230 : public ValueBoundsOpInterface::ExternalModel<RemUIOpInterface, RemUIOp> {
231 void populateBoundsForIndexValue(Operation *op, Value value,
232 ValueBoundsConstraintSet &cstr) const {
233 auto remUIOp = cast<RemUIOp>(op);
234 assert(value == remUIOp.getResult() && "invalid value");
235
236 Value rhsValue = remUIOp.getRhs();
237 AffineExpr rhs = cstr.getExpr(rhsValue);
238
239 // remui computes an unsigned remainder, so for a provably positive divisor
240 // the result is always in [0, rhs - 1].
242 cstr.bound(value) >= 0;
243 cstr.bound(value) <= rhs - 1;
244 }
245 }
246};
247
248struct SelectOpInterface
249 : public ValueBoundsOpInterface::ExternalModel<SelectOpInterface,
250 SelectOp> {
251
252 static void populateBounds(SelectOp selectOp, std::optional<int64_t> dim,
253 ValueBoundsConstraintSet &cstr) {
254 Value value = selectOp.getResult();
255 Value condition = selectOp.getCondition();
256 Value trueValue = selectOp.getTrueValue();
257 Value falseValue = selectOp.getFalseValue();
258
259 if (isa<ShapedType>(condition.getType())) {
260 // If the condition is a shaped type, the condition is applied
261 // element-wise. All three operands must have the same shape.
262 cstr.bound(value)[*dim] == cstr.getExpr(trueValue, dim);
263 cstr.bound(value)[*dim] == cstr.getExpr(falseValue, dim);
264 cstr.bound(value)[*dim] == cstr.getExpr(condition, dim);
265 return;
266 }
267
268 // Populate constraints for the true/false values (and all values on the
269 // backward slice, as long as the current stop condition is not satisfied).
270 cstr.populateConstraints(trueValue, dim);
271 cstr.populateConstraints(falseValue, dim);
272 auto boundsBuilder = cstr.bound(value);
273 if (dim)
274 boundsBuilder[*dim];
275
276 // Compare yielded values.
277 // If trueValue <= falseValue:
278 // * result <= falseValue
279 // * result >= trueValue
280 if (cstr.populateAndCompare(
281 /*lhs=*/{trueValue, dim},
283 /*rhs=*/{falseValue, dim})) {
284 if (dim) {
285 cstr.bound(value)[*dim] >= cstr.getExpr(trueValue, dim);
286 cstr.bound(value)[*dim] <= cstr.getExpr(falseValue, dim);
287 } else {
288 cstr.bound(value) >= trueValue;
289 cstr.bound(value) <= falseValue;
290 }
291 }
292 // If falseValue <= trueValue:
293 // * result <= trueValue
294 // * result >= falseValue
295 if (cstr.populateAndCompare(
296 /*lhs=*/{falseValue, dim},
298 /*rhs=*/{trueValue, dim})) {
299 if (dim) {
300 cstr.bound(value)[*dim] >= cstr.getExpr(falseValue, dim);
301 cstr.bound(value)[*dim] <= cstr.getExpr(trueValue, dim);
302 } else {
303 cstr.bound(value) >= falseValue;
304 cstr.bound(value) <= trueValue;
305 }
306 }
307 }
308
309 void populateBoundsForIndexValue(Operation *op, Value value,
310 ValueBoundsConstraintSet &cstr) const {
311 populateBounds(cast<SelectOp>(op), /*dim=*/std::nullopt, cstr);
312 }
313
314 void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
315 ValueBoundsConstraintSet &cstr) const {
316 populateBounds(cast<SelectOp>(op), dim, cstr);
317 }
318};
319
320struct MinSIOpInterface
321 : public ValueBoundsOpInterface::ExternalModel<MinSIOpInterface, MinSIOp> {
322 void populateBoundsForIndexValue(Operation *op, Value value,
323 ValueBoundsConstraintSet &cstr) const {
324 auto minOp = cast<MinSIOp>(op);
325 assert(value == minOp.getResult() && "invalid value");
326
327 AffineExpr lhs = cstr.getExpr(minOp.getLhs());
328 AffineExpr rhs = cstr.getExpr(minOp.getRhs());
329 cstr.bound(value) <= lhs;
330 cstr.bound(value) <= rhs;
331 }
332};
333
334struct MaxSIOpInterface
335 : public ValueBoundsOpInterface::ExternalModel<MaxSIOpInterface, MaxSIOp> {
336 void populateBoundsForIndexValue(Operation *op, Value value,
337 ValueBoundsConstraintSet &cstr) const {
338 auto maxOp = cast<MaxSIOp>(op);
339 assert(value == maxOp.getResult() && "invalid value");
340
341 AffineExpr lhs = cstr.getExpr(maxOp.getLhs());
342 AffineExpr rhs = cstr.getExpr(maxOp.getRhs());
343 cstr.bound(value) >= lhs;
344 cstr.bound(value) >= rhs;
345 }
346};
347
348struct MinUIOpInterface
349 : public ValueBoundsOpInterface::ExternalModel<MinUIOpInterface,
350 arith::MinUIOp> {
351 void populateBoundsForIndexValue(Operation *op, Value value,
352 ValueBoundsConstraintSet &cstr) const {
353 auto minOp = cast<arith::MinUIOp>(op);
354 assert(value == minOp.getResult() && "invalid value");
355
356 // ValueBoundsConstraintSet models values as signed integers (e.g. an i8
357 // 0xff is treated as -1, not 255). For an unsigned minimum it is enough
358 // that a single operand is provably non-negative: minui(x, y) is in
359 // [0, y] whenever y >= 0 (and symmetrically for x).
360 bool lhsNonNegative =
362 bool rhsNonNegative =
364 if (!lhsNonNegative && !rhsNonNegative)
365 return;
366
367 // A negative signed integer bit pattern reinterpreted as an
368 // unsigned integer is greater than SIGNED_INT_MAX. If
369 // one of the operands is signed non-negative, it is smaller than
370 // or equal to SIGNED_INT_MAX in unsigned interpretation,
371 // and `minui` will choose that operand over a negative signed
372 // integer operand.
373 cstr.bound(value) >= 0;
374 // If an operand is provably non-negative, its signed and unsigned value
375 // interpretations agree, so `minsi` and `minui` impose the same upper
376 // bound: `result <= operand`.
377 if (lhsNonNegative) {
378 AffineExpr lhs = cstr.getExpr(minOp.getLhs());
379 cstr.bound(value) <= lhs;
380 }
381 if (rhsNonNegative) {
382 AffineExpr rhs = cstr.getExpr(minOp.getRhs());
383 cstr.bound(value) <= rhs;
384 }
385 }
386};
387
388struct MaxUIOpInterface
389 : public ValueBoundsOpInterface::ExternalModel<MaxUIOpInterface,
390 arith::MaxUIOp> {
391 void populateBoundsForIndexValue(Operation *op, Value value,
392 ValueBoundsConstraintSet &cstr) const {
393 auto maxOp = cast<arith::MaxUIOp>(op);
394 assert(value == maxOp.getResult() && "invalid value");
395
396 // See MinUIOpInterface comment
397 bool lhsNonNegative =
399 bool rhsNonNegative =
401
402 if (lhsNonNegative) {
403 AffineExpr lhs = cstr.getExpr(maxOp.getLhs());
404 cstr.bound(value) >= lhs;
405 }
406 if (rhsNonNegative) {
407 AffineExpr rhs = cstr.getExpr(maxOp.getRhs());
408 cstr.bound(value) >= rhs;
409 }
410 }
411};
412} // namespace
413} // namespace arith
414} // namespace mlir
415
417 DialectRegistry &registry) {
418 registry.addExtension(+[](MLIRContext *ctx, arith::ArithDialect *dialect) {
419 arith::ConstantOp::attachInterface<arith::ConstantOpInterface>(*ctx);
420 arith::ExtSIOp::attachInterface<arith::ExtSIOpInterface>(*ctx);
421 arith::AddIOp::attachInterface<arith::AddIOpInterface>(*ctx);
422 arith::DivUIOp::attachInterface<arith::DivUIOpInterface>(*ctx);
423 arith::DivSIOp::attachInterface<arith::DivSIOpInterface>(*ctx);
424 arith::SubIOp::attachInterface<arith::SubIOpInterface>(*ctx);
425 arith::MulIOp::attachInterface<arith::MulIOpInterface>(*ctx);
426 arith::FloorDivSIOp::attachInterface<arith::FloorDivSIOpInterface>(*ctx);
427 arith::CeilDivSIOp::attachInterface<arith::CeilDivSIOpInterface>(*ctx);
428 arith::RemSIOp::attachInterface<arith::RemSIOpInterface>(*ctx);
429 arith::RemUIOp::attachInterface<arith::RemUIOpInterface>(*ctx);
430 arith::SelectOp::attachInterface<arith::SelectOpInterface>(*ctx);
431 arith::MinSIOp::attachInterface<arith::MinSIOpInterface>(*ctx);
432 arith::MaxSIOp::attachInterface<arith::MaxSIOpInterface>(*ctx);
433 arith::MinUIOp::attachInterface<arith::MinUIOpInterface>(*ctx);
434 arith::MaxUIOp::attachInterface<arith::MaxUIOpInterface>(*ctx);
435 });
436}
lhs
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
static bool isProvablyNegative(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably negative.
static bool isProvablyPositive(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably positive.
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.
static bool isProvablyNonNegative(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably non-negative.
static bool isProvablyNonPositive(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably non-positive.
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...
bool populateAndCompare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Populate constraints for lhs/rhs (until the stop condition is met).
Type getType() const
Return the type of this value.
Definition Value.h:105
void registerValueBoundsOpInterfaceExternalModels(DialectRegistry &registry)
Include the generated interface declarations.