//===- StaticValueUtils.h - Utilities for static values ---------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This header file defines utilities for dealing with static values, e.g., // converting back and forth between Value and OpFoldResult. Such functionality // is used in multiple dialects. // //===----------------------------------------------------------------------===// #ifndef MLIR_DIALECT_UTILS_STATICVALUEUTILS_H #define MLIR_DIALECT_UTILS_STATICVALUEUTILS_H #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/OpDefinition.h" #include "mlir/Support/LLVM.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/SmallVectorExtras.h" namespace mlir { /// Return "true" if `v` is an integer value/attribute with constant value `0`. bool isZeroInteger(OpFoldResult v); /// Return "true" if `v` is a float value/attribute with constant value `0.0`. bool isZeroFloat(OpFoldResult v); /// Return "true" if `v` is an integer/float value/attribute with constant /// value zero. bool isZeroIntegerOrFloat(OpFoldResult v); /// Return true if `v` is an IntegerAttr with value `1`. bool isOneInteger(OpFoldResult v); /// Represents a range (offset, size, and stride) where each element of the /// triple may be dynamic or static. struct Range { OpFoldResult offset; OpFoldResult size; OpFoldResult stride; }; /// Given an array of Range values, return a tuple of (offset vector, sizes /// vector, and strides vector) formed by separating out the individual /// elements of each range. std::tuple, SmallVector, SmallVector> getOffsetsSizesAndStrides(ArrayRef ranges); /// Helper function to dispatch an OpFoldResult into `staticVec` if: /// a) it is an IntegerAttr /// In other cases, the OpFoldResult is dispached to the `dynamicVec`. /// In such dynamic cases, ShapedType::kDynamic is also pushed to /// `staticVec`. This is useful to extract mixed static and dynamic entries /// that come from an AttrSizedOperandSegments trait. void dispatchIndexOpFoldResult(OpFoldResult ofr, SmallVectorImpl &dynamicVec, SmallVectorImpl &staticVec); /// Helper function to dispatch multiple OpFoldResults according to the /// behavior of `dispatchIndexOpFoldResult(OpFoldResult ofr` for a single /// OpFoldResult. void dispatchIndexOpFoldResults(ArrayRef ofrs, SmallVectorImpl &dynamicVec, SmallVectorImpl &staticVec); /// Given OpFoldResult representing dim size value (*), generates a pair of /// sizes: /// * 1st result, static value, contains an int64_t dim size that can be used /// to build ShapedType (ShapedType::kDynamic is used for truly dynamic dims), /// * 2nd result, dynamic value, contains OpFoldResult encapsulating the /// actual dim size (either original or updated input value). /// For input sizes for which it is possible to extract a constant Attribute, /// replaces the original size value with an integer attribute (unless it's /// already a constant Attribute). The 1st return value also becomes the actual /// integer size (as opposed ShapedType::kDynamic). /// /// (*) This hook is usually used when, given input sizes as OpFoldResult, /// it's required to generate two vectors: /// * sizes as int64_t to generate a shape, /// * sizes as OpFoldResult for sizes-like attribute. /// Please update this comment if you identify other use cases. std::pair getSimplifiedOfrAndStaticSizePair(OpFoldResult ofr, Builder &b); /// Extract integer values from the assumed ArrayAttr of IntegerAttr. template SmallVector extractFromIntegerArrayAttr(Attribute attr) { return llvm::map_to_vector(cast(attr), [](Attribute a) -> IntTy { return cast(a).getInt(); }); } /// Given a value, try to extract a constant Attribute. If this fails, return /// the original value. OpFoldResult getAsOpFoldResult(Value val); /// Given an array of values, try to extract a constant Attribute from each /// value. If this fails, return the original value. SmallVector getAsOpFoldResult(ValueRange values); /// Convert `arrayAttr` to a vector of OpFoldResult. SmallVector getAsOpFoldResult(ArrayAttr arrayAttr); /// Convert int64_t to integer attributes of index type and return them as /// OpFoldResult. OpFoldResult getAsIndexOpFoldResult(MLIRContext *ctx, int64_t val); SmallVector getAsIndexOpFoldResult(MLIRContext *ctx, ArrayRef values); /// If ofr is a constant integer or an IntegerAttr, return the integer. /// The second return value indicates whether the value is an index type /// and thus the bitwidth is not defined (the APInt will be set with 64bits). std::optional> getConstantAPIntValue(OpFoldResult ofr); /// If ofr is a constant integer or an IntegerAttr, return the integer. std::optional getConstantIntValue(OpFoldResult ofr); /// If all ofrs are constant integers or IntegerAttrs, return the integers. std::optional> getConstantIntValues(ArrayRef ofrs); /// Return true if `ofr` is constant integer equal to `value`. bool isConstantIntValue(OpFoldResult ofr, int64_t value); /// Return true if all of `ofrs` are constant integers equal to `value`. bool areAllConstantIntValue(ArrayRef ofrs, int64_t value); /// Return true if all of `ofrs` are constant integers equal to the /// corresponding value in `values`. bool areConstantIntValues(ArrayRef ofrs, ArrayRef values); /// Return true if ofr1 and ofr2 are the same integer constant attribute /// values or the same SSA value. Ignore integer bitwitdh and type mismatch /// that come from the fact there is no IndexAttr and that IndexType have no /// bitwidth. bool isEqualConstantIntOrValue(OpFoldResult ofr1, OpFoldResult ofr2); bool isEqualConstantIntOrValueArray(ArrayRef ofrs1, ArrayRef ofrs2); // To convert an OpFoldResult to a Value of index type, see: // mlir/include/mlir/Dialect/Arith/Utils/Utils.h // TODO: find a better common landing place. // // Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, // OpFoldResult ofr); // To convert an OpFoldResult to a Value of index type, see: // mlir/include/mlir/Dialect/Arith/Utils/Utils.h // TODO: find a better common landing place. // // SmallVector // getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, // ArrayRef valueOrAttrVec); /// Return a vector of OpFoldResults with the same size a staticValues, but /// all elements for which ShapedType::isDynamic is true, will be replaced by /// dynamicValues. SmallVector getMixedValues(ArrayRef staticValues, ValueRange dynamicValues, MLIRContext *context); SmallVector getMixedValues(ArrayRef staticValues, ValueRange dynamicValues, Builder &b); /// Decompose a vector of mixed static or dynamic values into the /// corresponding pair of arrays. This is the inverse function of /// `getMixedValues`. std::pair, SmallVector> decomposeMixedValues(ArrayRef mixedValues); /// Helper to sort `values` according to matching `keys`. SmallVector getValuesSortedByKey(ArrayRef keys, ArrayRef values, llvm::function_ref compare); SmallVector getValuesSortedByKey(ArrayRef keys, ArrayRef values, llvm::function_ref compare); SmallVector getValuesSortedByKey(ArrayRef keys, ArrayRef values, llvm::function_ref compare); /// Helper function to check whether the passed in `sizes` or `offsets` are /// valid. This can be used to re-check whether dimensions are still valid /// after constant folding the dynamic dimensions. bool hasValidSizesOffsets(SmallVector sizesOrOffsets); /// Helper function to check whether the passed in `strides` are valid. This /// can be used to re-check whether dimensions are still valid after constant /// folding the dynamic dimensions. bool hasValidStrides(SmallVector strides); /// Returns "success" when any of the elements in `ofrs` is a constant value. In /// that case the value is replaced by an attribute. Returns "failure" when no /// folding happened. If `onlyNonNegative` and `onlyNonZero` are set, only /// non-negative and non-zero constant values are folded respectively. LogicalResult foldDynamicIndexList(SmallVectorImpl &ofrs, bool onlyNonNegative = false, bool onlyNonZero = false); /// Returns "success" when any of the elements in `offsetsOrSizes` is a /// constant value. In that case the value is replaced by an attribute. Returns /// "failure" when no folding happened. Invalid values are not folded to avoid /// canonicalization crashes. LogicalResult foldDynamicOffsetSizeList(SmallVectorImpl &offsetsOrSizes); /// Returns "success" when any of the elements in `strides` is a constant /// value. In that case the value is replaced by an attribute. Returns /// "failure" when no folding happened. Invalid values are not folded to avoid /// canonicalization crashes. LogicalResult foldDynamicStrideList(SmallVectorImpl &strides); /// Return the number of iterations for a loop with a lower bound `lb`, upper /// bound `ub` and step `step`, as an unsigned integer. The `isSigned` flag /// indicates whether the loop comparison between lb and ub is signed or /// unsigned. (The result of this function must be interpreted as an unsigned /// integer.) A lower bound greater than the upper bound is considered invalid /// and will yield a zero trip count. /// /// Note: The loops modeled here use a less-than comparison (`<`), meaning the /// loop continues while `iv < ub`. This is different from arbitrary C++ loops /// which can use various comparison operators. /// The `computeUbMinusLb` callback is invoked to compute the difference between /// the upper and lower bound when not constant. It can be used by the client /// to compute a static difference when the bounds are not constant. /// /// For example, the following code: /// /// %ub = arith.addi nsw %lb, %c16_i32 : i32 /// %1 = scf.for %arg0 = %lb to %ub ... /// /// where %ub is computed as a static offset from %lb. /// Note: the matched addition should be nsw/nuw (matching the loop comparison) /// to avoid overflow, otherwise an overflow would imply a zero trip count. std::optional constantTripCount( OpFoldResult lb, OpFoldResult ub, OpFoldResult step, bool isSigned, llvm::function_ref(Value, Value, bool)> computeUbMinusLb); /// Idiomatic saturated operations on values like offsets, sizes, and strides. struct SaturatedInteger { static SaturatedInteger wrap(int64_t v) { return (ShapedType::isDynamic(v)) ? SaturatedInteger{true, 0} : SaturatedInteger{false, v}; } int64_t asInteger() { return saturated ? ShapedType::kDynamic : v; } bool isSaturated() const { return saturated; } FailureOr desaturate(SaturatedInteger other) { if (saturated && !other.saturated) return other; if (!saturated && !other.saturated && v != other.v) return failure(); return *this; } bool operator==(SaturatedInteger other) { return (saturated && other.saturated) || (!saturated && !other.saturated && v == other.v); } bool operator!=(SaturatedInteger other) { return !(*this == other); } SaturatedInteger operator+(SaturatedInteger other) { if (saturated || other.saturated) return SaturatedInteger{true, 0}; return SaturatedInteger{false, other.v + v}; } SaturatedInteger operator*(SaturatedInteger other) { // Multiplication with 0 is always 0. if (!other.saturated && other.v == 0) return SaturatedInteger{false, 0}; if (!saturated && v == 0) return SaturatedInteger{false, 0}; // Otherwise, if this or the other integer is dynamic, so is the result. if (saturated || other.saturated) return SaturatedInteger{true, 0}; return SaturatedInteger{false, other.v * v}; } SaturatedInteger smax(SaturatedInteger other) { if (saturated || other.saturated) return SaturatedInteger{true, 0}; return SaturatedInteger{false, std::max(other.v, v)}; } bool saturated = true; int64_t v = 0; }; } // namespace mlir #endif // MLIR_DIALECT_UTILS_STATICVALUEUTILS_H