MLIR 24.0.0git
StaticValueUtils.cpp
Go to the documentation of this file.
1//===- StaticValueUtils.cpp - Utilities for dealing with static values ----===//
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#include "mlir/IR/Attributes.h"
11#include "mlir/IR/Matchers.h"
12#include "mlir/Support/LLVM.h"
13#include "llvm/ADT/APSInt.h"
14#include "llvm/ADT/STLExtras.h"
15#include "llvm/ADT/SmallVectorExtras.h"
16#include "llvm/Support/DebugLog.h"
17#include "llvm/Support/MathExtras.h"
18
19namespace mlir {
20
22
24 if (auto attr = dyn_cast<Attribute>(v)) {
25 if (auto floatAttr = dyn_cast<FloatAttr>(attr))
26 return floatAttr.getValue().isZero();
27 return false;
28 }
29 return matchPattern(cast<Value>(v), m_AnyZeroFloat());
30}
31
35
37
38std::tuple<SmallVector<OpFoldResult>, SmallVector<OpFoldResult>,
39 SmallVector<OpFoldResult>>
41 SmallVector<OpFoldResult> offsets, sizes, strides;
42 offsets.reserve(ranges.size());
43 sizes.reserve(ranges.size());
44 strides.reserve(ranges.size());
45 for (const auto &[offset, size, stride] : ranges) {
46 offsets.push_back(offset);
47 sizes.push_back(size);
48 strides.push_back(stride);
49 }
50 return std::make_tuple(offsets, sizes, strides);
51}
52
53/// Helper function to dispatch an OpFoldResult into `staticVec` if:
54/// a) it is an IntegerAttr
55/// In other cases, the OpFoldResult is dispached to the `dynamicVec`.
56/// In such dynamic cases, a copy of the `sentinel` value is also pushed to
57/// `staticVec`. This is useful to extract mixed static and dynamic entries that
58/// come from an AttrSizedOperandSegments trait.
60 SmallVectorImpl<Value> &dynamicVec,
61 SmallVectorImpl<int64_t> &staticVec) {
62 auto v = llvm::dyn_cast_if_present<Value>(ofr);
63 if (!v) {
64 APInt apInt = cast<IntegerAttr>(cast<Attribute>(ofr)).getValue();
65 staticVec.push_back(apInt.getSExtValue());
66 return;
67 }
68 dynamicVec.push_back(v);
69 staticVec.push_back(ShapedType::kDynamic);
70}
71
72std::pair<int64_t, OpFoldResult>
74 int64_t tileSizeForShape =
75 getConstantIntValue(tileSizeOfr).value_or(ShapedType::kDynamic);
76
77 OpFoldResult tileSizeOfrSimplified =
78 (tileSizeForShape != ShapedType::kDynamic)
79 ? b.getIndexAttr(tileSizeForShape)
80 : tileSizeOfr;
81
82 return std::pair<int64_t, OpFoldResult>(tileSizeForShape,
83 tileSizeOfrSimplified);
84}
85
87 SmallVectorImpl<Value> &dynamicVec,
88 SmallVectorImpl<int64_t> &staticVec) {
89 for (OpFoldResult ofr : ofrs)
90 dispatchIndexOpFoldResult(ofr, dynamicVec, staticVec);
91}
92
93/// Given a value, try to extract a constant Attribute. If this fails, return
94/// the original value.
96 if (!val)
97 return OpFoldResult();
98 Attribute attr;
99 if (matchPattern(val, m_Constant(&attr)))
100 return attr;
101 return val;
102}
103
104/// Given an array of values, try to extract a constant Attribute from each
105/// value. If this fails, return the original value.
107 return llvm::map_to_vector(values,
108 [](Value v) { return getAsOpFoldResult(v); });
109}
110
111/// Convert `arrayAttr` to a vector of OpFoldResult.
114 res.reserve(arrayAttr.size());
115 for (Attribute a : arrayAttr)
116 res.push_back(a);
117 return res;
118}
119
121 return IntegerAttr::get(IndexType::get(ctx), val);
122}
123
125 ArrayRef<int64_t> values) {
126 return llvm::map_to_vector(
127 values, [ctx](int64_t v) { return getAsIndexOpFoldResult(ctx, v); });
128}
129
130/// If ofr is a constant integer or an IntegerAttr, return the integer.
131/// The boolean indicates whether the value is an index type.
132std::optional<std::pair<APInt, bool>> getConstantAPIntValue(OpFoldResult ofr) {
133 // Case 1: Check for Constant integer.
134 if (auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
135 APInt intVal;
136 if (matchPattern(val, m_ConstantInt(&intVal)))
137 return std::make_pair(intVal, val.getType().isIndex());
138 return std::nullopt;
139 }
140 // Case 2: Check for IntegerAttr.
141 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
142 if (auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
143 return std::make_pair(intAttr.getValue(), intAttr.getType().isIndex());
144 return std::nullopt;
145}
146
147/// If ofr is a constant integer or an IntegerAttr, return the integer.
148std::optional<int64_t> getConstantIntValue(OpFoldResult ofr) {
149 std::optional<std::pair<APInt, bool>> apInt = getConstantAPIntValue(ofr);
150 if (!apInt)
151 return std::nullopt;
152 return apInt->first.getSExtValue();
153}
154
155/// If ofr is a constant integer or an IntegerAttr, return the integer
156/// zero-extended to 64 bits.
157std::optional<uint64_t> getConstantUIntValue(OpFoldResult ofr) {
158 std::optional<std::pair<APInt, bool>> apInt = getConstantAPIntValue(ofr);
159 if (!apInt)
160 return std::nullopt;
161 return apInt->first.getZExtValue();
162}
163
164std::optional<SmallVector<int64_t>>
167 res.reserve(ofrs.size());
168 for (OpFoldResult ofr : ofrs) {
169 auto cv = getConstantIntValue(ofr);
170 if (!cv.has_value())
171 return std::nullopt;
172 res.push_back(cv.value());
173 }
174 return res;
175}
176
178 return getConstantIntValue(ofr) == value;
179}
180
182 return llvm::all_of(
183 ofrs, [&](OpFoldResult ofr) { return isConstantIntValue(ofr, value); });
184}
185
187 ArrayRef<int64_t> values) {
188 if (ofrs.size() != values.size())
189 return false;
190 std::optional<SmallVector<int64_t>> constOfrs = getConstantIntValues(ofrs);
191 return constOfrs && llvm::equal(constOfrs.value(), values);
192}
193
194/// Return true if ofr1 and ofr2 are the same integer constant attribute values
195/// or the same SSA value.
196/// Ignore integer bitwidth and type mismatch that come from the fact there is
197/// no IndexAttr and that IndexType has no bitwidth.
199 auto cst1 = getConstantIntValue(ofr1), cst2 = getConstantIntValue(ofr2);
200 if (cst1 && cst2 && *cst1 == *cst2)
201 return true;
202 auto v1 = llvm::dyn_cast_if_present<Value>(ofr1),
203 v2 = llvm::dyn_cast_if_present<Value>(ofr2);
204 return v1 && v1 == v2;
205}
206
209 if (ofrs1.size() != ofrs2.size())
210 return false;
211 for (auto [ofr1, ofr2] : llvm::zip_equal(ofrs1, ofrs2))
212 if (!isEqualConstantIntOrValue(ofr1, ofr2))
213 return false;
214 return true;
215}
216
217/// Return a vector of OpFoldResults with the same size as staticValues, but all
218/// elements for which ShapedType::isDynamic is true, will be replaced by
219/// dynamicValues.
221 ValueRange dynamicValues,
222 MLIRContext *context) {
223 assert(dynamicValues.size() == static_cast<size_t>(llvm::count_if(
224 staticValues, ShapedType::isDynamic)) &&
225 "expected the rank of dynamic values to match the number of "
226 "values known to be dynamic");
228 res.reserve(staticValues.size());
229 unsigned numDynamic = 0;
230 unsigned count = static_cast<unsigned>(staticValues.size());
231 for (unsigned idx = 0; idx < count; ++idx) {
232 int64_t value = staticValues[idx];
233 res.push_back(ShapedType::isDynamic(value)
234 ? OpFoldResult{dynamicValues[numDynamic++]}
235 : OpFoldResult{IntegerAttr::get(
236 IntegerType::get(context, 64), staticValues[idx])});
237 }
238 return res;
239}
241 ValueRange dynamicValues, Builder &b) {
242 return getMixedValues(staticValues, dynamicValues, b.getContext());
243}
244
245/// Decompose a vector of mixed static or dynamic values into the corresponding
246/// pair of arrays. This is the inverse function of `getMixedValues`.
247std::pair<SmallVector<int64_t>, SmallVector<Value>>
249 SmallVector<int64_t> staticValues;
250 SmallVector<Value> dynamicValues;
251 for (const auto &it : mixedValues) {
252 if (auto attr = dyn_cast<Attribute>(it)) {
253 staticValues.push_back(cast<IntegerAttr>(attr).getInt());
254 } else {
255 staticValues.push_back(ShapedType::kDynamic);
256 dynamicValues.push_back(cast<Value>(it));
257 }
258 }
259 return {staticValues, dynamicValues};
260}
261
262/// Helper to sort `values` according to matching `keys`.
263template <typename K, typename V>
264static SmallVector<V>
266 llvm::function_ref<bool(K, K)> compare) {
267 if (keys.empty())
268 return SmallVector<V>{values};
269 assert(keys.size() == values.size() && "unexpected mismatching sizes");
270 auto indices = llvm::to_vector(llvm::seq<int64_t>(0, values.size()));
271 llvm::sort(indices,
272 [&](int64_t i, int64_t j) { return compare(keys[i], keys[j]); });
273 SmallVector<V> res;
274 res.reserve(values.size());
275 for (int64_t i = 0, e = indices.size(); i < e; ++i)
276 res.push_back(values[indices[i]]);
277 return res;
278}
279
280SmallVector<Value>
282 llvm::function_ref<bool(Attribute, Attribute)> compare) {
283 return getValuesSortedByKeyImpl(keys, values, compare);
284}
285
286SmallVector<OpFoldResult>
288 llvm::function_ref<bool(Attribute, Attribute)> compare) {
289 return getValuesSortedByKeyImpl(keys, values, compare);
290}
291
292SmallVector<int64_t>
294 llvm::function_ref<bool(Attribute, Attribute)> compare) {
295 return getValuesSortedByKeyImpl(keys, values, compare);
296}
297
298/// Return the number of iterations for a loop with a lower bound `lb`, upper
299/// bound `ub` and step `step`.
300std::optional<APInt> constantTripCount(
301 OpFoldResult lb, OpFoldResult ub, OpFoldResult step, bool isSigned,
302 llvm::function_ref<std::optional<llvm::APSInt>(Value, Value, bool)>
303 computeUbMinusLb) {
304 // This is the bitwidth used to return 0 when loop does not execute.
305 // We infer it from the type of the bound if it isn't an index type.
306 auto getBitwidth = [&](OpFoldResult ofr) -> std::tuple<int, bool> {
307 if (auto intAttr =
308 dyn_cast_or_null<IntegerAttr>(dyn_cast<Attribute>(ofr))) {
309 if (auto intType = dyn_cast<IntegerType>(intAttr.getType()))
310 return std::make_tuple(intType.getWidth(), intType.isIndex());
311 } else {
312 auto val = cast<Value>(ofr);
313 if (auto intType = dyn_cast<IntegerType>(val.getType()))
314 return std::make_tuple(intType.getWidth(), intType.isIndex());
315 }
316 return std::make_tuple(IndexType::kInternalStorageBitWidth, true);
317 };
318 auto [bitwidth, isIndex] = getBitwidth(lb);
319 // This would better be an assert, but unfortunately it breaks scf.for_all
320 // which is missing attributes and SSA value optionally for its bounds, and
321 // uses Index type for the dynamic bounds but i64 for the static bounds. This
322 // is broken...
323 if (std::tie(bitwidth, isIndex) != getBitwidth(ub)) {
324 LDBG() << "mismatch between lb and ub bitwidth/type: " << ub << " vs "
325 << lb;
326 return std::nullopt;
327 }
328 if (lb == ub) {
329 // Fast path: LB == UB. The loop has zero iterations.
330 // Note: LB and UB could match at runtime, even though they are different
331 // SSA values. That case cannot be detected here.
332 return APInt(bitwidth, 0);
333 }
334 if (isZeroInteger(lb) && ub == step) {
335 // Fast path: LB == 0 && UB == step. The loop has a single iteration.
336 // Note: LB and UB could match at runtime, even though they are different
337 // SSA values. That case cannot be detected here.
338 return APInt(bitwidth, 1);
339 }
340
341 std::optional<std::pair<APInt, bool>> maybeStepCst =
343
344 if (maybeStepCst) {
345 auto &stepCst = maybeStepCst->first;
346 assert(static_cast<int>(stepCst.getBitWidth()) == bitwidth &&
347 "step must have the same bitwidth as lb and ub");
348 if (stepCst.isZero()) {
349 // Step is zero. If LB and UB match, we have zero iterations. Otherwise,
350 // we have an infinite number of iterations. We cannot tell for sure which
351 // case applies, so the static trip count is unknown.
352 return std::nullopt;
353 }
354 }
355
356 if (isIndex) {
357 LDBG()
358 << "Computing loop trip count for index type may break with overflow";
359 // TODO: we can't compute the trip count for index type. We should fix this
360 // but too many tests are failing right now.
361 // return {};
362 }
363
364 /// Compute the difference between the upper and lower bound: either from the
365 /// constant value or using the computeUbMinusLb callback.
366 llvm::APSInt diff;
367 std::optional<std::pair<APInt, bool>> maybeLbCst = getConstantAPIntValue(lb);
368 std::optional<std::pair<APInt, bool>> maybeUbCst = getConstantAPIntValue(ub);
369 if (maybeLbCst) {
370 // If one of the bounds is not a constant, we can't compute the trip count.
371 if (!maybeUbCst)
372 return std::nullopt;
373 APSInt lbCst(maybeLbCst->first, /*isUnsigned=*/!isSigned);
374 APSInt ubCst(maybeUbCst->first, /*isUnsigned=*/!isSigned);
375 if (ubCst <= lbCst) {
376 LDBG() << "constantTripCount is 0 because ub <= lb (" << lbCst << "("
377 << lbCst.getBitWidth() << ") <= " << ubCst << "("
378 << ubCst.getBitWidth() << "), "
379 << (isSigned ? "isSigned" : "isUnsigned") << ")";
380 return APInt(bitwidth, 0);
381 }
382 // Compute the difference. Since we've already checked that ub > lb, the
383 // result can be interpreted as an unsigned value without overflow concerns.
384 diff = ubCst - lbCst;
385 // Convert diff to unsigned. This handles cases like i8: ub=127, lb=-128
386 // where the subtraction yields 255, which wraps to -1 in signed i8 but is
387 // correctly represented as 255 when interpreted as unsigned.
388 diff.setIsUnsigned(true);
389 } else {
390 if (maybeUbCst)
391 return std::nullopt;
392
393 /// Non-constant bound, let's try to compute the difference between the
394 /// upper and lower bound
395 std::optional<llvm::APSInt> maybeDiff =
396 computeUbMinusLb(cast<Value>(lb), cast<Value>(ub), isSigned);
397 if (!maybeDiff)
398 return std::nullopt;
399 diff = *maybeDiff;
400 }
401 LDBG() << "constantTripCount: " << (isSigned ? "isSigned" : "isUnsigned")
402 << ", ub-lb: " << diff << "(" << diff.getBitWidth() << "b)";
403 if (diff.isNegative()) {
404 LDBG() << "constantTripCount is 0 because ub-lb diff is negative";
405 return APInt(bitwidth, 0);
406 }
407 if (!maybeStepCst) {
408 LDBG()
409 << "constantTripCount can't be computed because step is not a constant";
410 return std::nullopt;
411 }
412 auto &stepCst = maybeStepCst->first;
413 // For signed loops, a negative step size could indicate an infinite number of
414 // iterations.
415 if (isSigned && stepCst.isSignBitSet()) {
416 LDBG() << "constantTripCount is infinite because step is negative";
417 return std::nullopt;
418 }
419
420 // Both diff and step are non-negative at this point (negative steps are
421 // rejected earlier), so we use unsigned division regardless of the loop
422 // comparison signedness.
423 llvm::APInt tripCount = diff.udiv(stepCst);
424 llvm::APInt remainder = diff.urem(stepCst);
425 if (!remainder.isZero())
426 tripCount = tripCount + 1;
427
428 LDBG() << "constantTripCount found: " << tripCount;
429 return tripCount;
430}
431
433 return llvm::none_of(sizesOrOffsets, [](int64_t value) {
434 return ShapedType::isStatic(value) && value < 0;
435 });
436}
437
439 return llvm::none_of(strides, [](int64_t value) {
440 return ShapedType::isStatic(value) && value == 0;
441 });
442}
443
445 bool onlyNonNegative, bool onlyNonZero) {
446 bool valuesChanged = false;
447 for (OpFoldResult &ofr : ofrs) {
448 if (isa<Attribute>(ofr))
449 continue;
450 Attribute attr;
451 if (matchPattern(cast<Value>(ofr), m_Constant(&attr))) {
452 // Note: All ofrs have index type.
453 if (onlyNonNegative && *getConstantIntValue(attr) < 0)
454 continue;
455 if (onlyNonZero && *getConstantIntValue(attr) == 0)
456 continue;
457 ofr = attr;
458 valuesChanged = true;
459 }
460 }
461 return success(valuesChanged);
462}
463
464LogicalResult
466 return foldDynamicIndexList(offsetsOrSizes, /*onlyNonNegative=*/true,
467 /*onlyNonZero=*/false);
468}
469
471 return foldDynamicIndexList(strides, /*onlyNonNegative=*/false,
472 /*onlyNonZero=*/true);
473}
474
475} // namespace mlir
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class represents a single result from folding an operation.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
bool isConstantIntValue(OpFoldResult ofr, int64_t value)
Return true if ofr is constant integer equal to value.
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
bool areConstantIntValues(ArrayRef< OpFoldResult > ofrs, ArrayRef< int64_t > values)
Return true if all of ofrs are constant integers equal to the corresponding value in values.
OpFoldResult getAsIndexOpFoldResult(MLIRContext *ctx, int64_t val)
Convert int64_t to integer attributes of index type and return them as OpFoldResult.
std::tuple< SmallVector< OpFoldResult >, SmallVector< OpFoldResult >, SmallVector< OpFoldResult > > getOffsetsSizesAndStrides(ArrayRef< Range > ranges)
Given an array of Range values, return a tuple of (offset vector, sizes vector, and strides vector) f...
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult foldDynamicStrideList(SmallVectorImpl< OpFoldResult > &strides)
Returns "success" when any of the elements in strides is a constant value.
bool areAllConstantIntValue(ArrayRef< OpFoldResult > ofrs, int64_t value)
Return true if all of ofrs are constant integers equal to value.
bool isEqualConstantIntOrValue(OpFoldResult ofr1, OpFoldResult ofr2)
Return true if ofr1 and ofr2 are the same integer constant attribute values or the same SSA value.
static SmallVector< V > getValuesSortedByKeyImpl(ArrayRef< K > keys, ArrayRef< V > values, llvm::function_ref< bool(K, K)> compare)
Helper to sort values according to matching keys.
bool isZeroIntegerOrFloat(OpFoldResult v)
Return "true" if v is an integer/float value/attribute with constant value zero.
bool hasValidSizesOffsets(SmallVector< int64_t > sizesOrOffsets)
Helper function to check whether the passed in sizes or offsets are valid.
std::optional< std::pair< APInt, bool > > getConstantAPIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
bool hasValidStrides(SmallVector< int64_t > strides)
Helper function to check whether the passed in strides are valid.
detail::constant_float_predicate_matcher m_AnyZeroFloat()
Matches a constant scalar / vector splat / tensor splat float (both positive and negative) zero.
Definition Matchers.h:399
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
bool isEqualConstantIntOrValueArray(ArrayRef< OpFoldResult > ofrs1, ArrayRef< OpFoldResult > ofrs2)
void dispatchIndexOpFoldResult(OpFoldResult ofr, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch an OpFoldResult into staticVec if: a) it is an IntegerAttr In other cases...
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
std::optional< uint64_t > getConstantUIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer zero-extended to 64 bits.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
std::pair< int64_t, OpFoldResult > getSimplifiedOfrAndStaticSizePair(OpFoldResult ofr, Builder &b)
Given OpFoldResult representing dim size value (*), generates a pair of sizes:
std::optional< SmallVector< int64_t > > getConstantIntValues(ArrayRef< OpFoldResult > ofrs)
If all ofrs are constant integers or IntegerAttrs, return the integers.
SmallVector< Value > getValuesSortedByKey(ArrayRef< Attribute > keys, ArrayRef< Value > values, llvm::function_ref< bool(Attribute, Attribute)> compare)
Helper to sort values according to matching keys.
LogicalResult foldDynamicOffsetSizeList(SmallVectorImpl< OpFoldResult > &offsetsOrSizes)
Returns "success" when any of the elements in offsetsOrSizes is a constant value.
bool isOneInteger(OpFoldResult v)
Return true if v is an IntegerAttr with value 1.
std::optional< APInt > constantTripCount(OpFoldResult lb, OpFoldResult ub, OpFoldResult step, bool isSigned, llvm::function_ref< std::optional< llvm::APSInt >(Value, Value, bool)> computeUbMinusLb)
Return the number of iterations for a loop with a lower bound lb, upper bound ub and step step,...
bool isZeroFloat(OpFoldResult v)
Return "true" if v is a float value/attribute with constant value 0.0.
std::pair< SmallVector< int64_t >, SmallVector< Value > > decomposeMixedValues(ArrayRef< OpFoldResult > mixedValues)
Decompose a vector of mixed static or dynamic values into the corresponding pair of arrays.
LogicalResult foldDynamicIndexList(SmallVectorImpl< OpFoldResult > &ofrs, bool onlyNonNegative=false, bool onlyNonZero=false)
Returns "success" when any of the elements in ofrs is a constant value.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.