blob: c6a08567f6687032005d9cc26113adebe529b54b [file] [log] [blame]
//===- AffineStructures.h - MLIR Affine Structures Class --------*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// Structures for affine/polyhedral analysis of ML functions.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_ANALYSIS_AFFINE_STRUCTURES_H
#define MLIR_ANALYSIS_AFFINE_STRUCTURES_H
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/Operation.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
namespace mlir {
class AffineApplyOp;
class AffineBound;
class AffineCondition;
class AffineMap;
class ForStmt;
class IntegerSet;
class MLIRContext;
class MLValue;
class HyperRectangularSet;
/// A mutable affine map. Its affine expressions are however unique.
struct MutableAffineMap {
public:
MutableAffineMap() {}
MutableAffineMap(AffineMap map);
AffineExpr getResult(unsigned idx) const { return results[idx]; }
void setResult(unsigned idx, AffineExpr result) { results[idx] = result; }
unsigned getNumResults() const { return results.size(); }
unsigned getNumDims() const { return numDims; }
void setNumDims(unsigned d) { numDims = d; }
unsigned getNumSymbols() const { return numSymbols; }
void setNumSymbols(unsigned d) { numSymbols = d; }
MLIRContext *getContext() const { return context; }
/// Returns true if the idx'th result expression is a multiple of factor.
bool isMultipleOf(unsigned idx, int64_t factor) const;
/// Resets this MutableAffineMap with 'map'.
void reset(AffineMap map);
/// Simplify the (result) expressions in this map using analysis (used by
//-simplify-affine-expr pass).
void simplify();
/// Get the AffineMap corresponding to this MutableAffineMap. Note that an
/// AffineMap will be uniqued and stored in context, while a mutable one
/// isn't.
AffineMap getAffineMap() const;
private:
// Same meaning as AffineMap's fields.
SmallVector<AffineExpr, 8> results;
SmallVector<AffineExpr, 8> rangeSizes;
unsigned numDims;
unsigned numSymbols;
/// A pointer to the IR's context to store all newly created
/// AffineExprStorage's.
MLIRContext *context;
};
/// A mutable integer set. Its affine expressions are however unique.
struct MutableIntegerSet {
public:
MutableIntegerSet(IntegerSet set, MLIRContext *context);
/// Create a universal set (no constraints).
MutableIntegerSet(unsigned numDims, unsigned numSymbols,
MLIRContext *context);
unsigned getNumDims() const { return numDims; }
unsigned getNumSymbols() const { return numSymbols; }
unsigned getNumConstraints() const { return constraints.size(); }
void clear() {
constraints.clear();
eqFlags.clear();
}
private:
unsigned numDims;
unsigned numSymbols;
SmallVector<AffineExpr, 8> constraints;
SmallVector<bool, 8> eqFlags;
/// A pointer to the IR's context to store all newly created
/// AffineExprStorage's.
MLIRContext *context;
};
/// An AffineValueMap is an affine map plus its ML value operands and
/// results for analysis purposes. The structure is still a tree form that is
/// same as that of an affine map or an AffineApplyOp. However, its operands,
/// results, and its map can themselves change as a result of
/// substitutions, simplifications, and other analysis.
// An affine value map can readily be constructed from an AffineApplyOp, or an
// AffineBound of a ForStmt. It can be further transformed, substituted into,
// or simplified. Unlike AffineMap's, AffineValueMap's are created and destroyed
// during analysis. Only the AffineMap expressions that are pointed by them are
// unique'd.
// An affine value map, and the operations on it, maintain the invariant that
// operands are always positionally aligned with the AffineDimExpr and
// AffineSymbolExpr in the underlying AffineMap.
// TODO(bondhugula): Some of these classes could go into separate files.
class AffineValueMap {
public:
// Creates an empty AffineValueMap (users should call 'reset' to reset map
// and operands).
AffineValueMap() {}
AffineValueMap(const AffineApplyOp &op);
AffineValueMap(const AffineBound &bound);
AffineValueMap(AffineMap map);
AffineValueMap(AffineMap map, ArrayRef<MLValue *> operands);
~AffineValueMap();
/// Substitute the results of inputMap into the operands of this map.
// The new list of operands will be a union of this map's and that of the map
// we are substituting from.
// Example usage scenario: a subscript operand for a 'load' is forward
// substituted into the memref's access map. The subscript operand itself is
// then substituted by its defining affine_apply op instructions and
// successively by a loop IV remap expression, eventually resulting in an
// affine value map that has only the loop IVs and symbols as its operands.
// Hence, the access pattern can then be analyzed for example.
// TODO(bondhugula)
void forwardSubstitute(const AffineValueMap &inputMap);
void forwardSubstitute(const AffineApplyOp &inputOp);
void forwardSubstituteSingle(const AffineApplyOp &inputOp,
unsigned inputResultIndex);
// TODO(andydavis, bondhugula) Expose an affine map simplify function, which
// can be used to amortize the cost of simplification over multiple fwd
// substitutions).
// Resets this AffineValueMap with 'map' and 'operands'.
void reset(AffineMap map, ArrayRef<MLValue *> operands);
/// Return true if the idx^th result can be proved to be a multiple of
/// 'factor', false otherwise.
inline bool isMultipleOf(unsigned idx, int64_t factor) const;
/// Return true if the idx^th result depends on 'value', false otherwise.
bool isFunctionOf(unsigned idx, MLValue *value) const;
/// Return true if the result at 'idx' is a constant, false
/// otherwise.
bool isConstant(unsigned idx) const;
/// Return true if this is an identity map.
bool isIdentity() const;
inline unsigned getNumOperands() const { return operands.size(); }
inline unsigned getNumDims() const { return map.getNumDims(); }
inline unsigned getNumSymbols() const { return map.getNumSymbols(); }
inline unsigned getNumResults() const { return map.getNumResults(); }
SSAValue *getOperand(unsigned i) const;
ArrayRef<MLValue *> getOperands() const;
AffineMap getAffineMap() const;
private:
void forwardSubstitute(const AffineApplyOp &inputOp,
ArrayRef<bool> inputResultsToSubstitute);
// A mutable affine map.
MutableAffineMap map;
// TODO: make these trailing objects?
/// The SSA operands binding to the dim's and symbols of 'map'.
SmallVector<MLValue *, 4> operands;
/// The SSA results binding to the results of 'map'.
SmallVector<MLValue *, 4> results;
};
/// An IntegerValueSet is an integer set plus its operands.
// Both, the integer set being pointed to and the operands can change during
// analysis, simplification, and transformation.
class IntegerValueSet {
// Constructs an integer value set map from an IntegerSet and operands.
explicit IntegerValueSet(const AffineCondition &cond);
/// Constructs an integer value set from an affine value map.
// This will lead to a single equality in 'set'.
explicit IntegerValueSet(const AffineValueMap &avm);
/// Returns true if this integer set is empty.
bool isEmpty() const;
bool getNumDims() const { return set.getNumDims(); }
bool getNumSymbols() const { return set.getNumSymbols(); }
private:
// The set pointed to may itself change unlike in IR structures like
// 'AffineCondition'.
MutableIntegerSet set;
/// The SSA operands binding to the dim's and symbols of 'set'.
SmallVector<MLValue *, 4> operands;
};
/// A flat list of affine equalities and inequalities in the form.
/// Inequality: c_0*x_0 + c_1*x_1 + .... + c_{n-1}*x_{n-1} == 0
/// Equality: c_0*x_0 + c_1*x_1 + .... + c_{n-1}*x_{n-1} >= 0
///
/// FlatAffineConstraints stores coefficients in a contiguous buffer (one buffer
/// for equalities and one for inequalities). The size of each buffer is
/// numReservedCols * number of inequalities (or equalities). The reserved size
/// is numReservedCols * numReservedInequalities (or numReservedEqualities). A
/// coefficient (r, c) lives at the location numReservedCols * r + c in the
/// buffer. The extra space between getNumCols() and numReservedCols exists to
/// prevent frequent movement of data when adding columns, especially at the
/// end.
///
/// The identifiers x_0, x_1, ... appear in the order: dimensional identifiers,
/// symbolic identifiers, and local identifiers. The local identifiers
/// correspond to local/internal variables created temporarily when converting
/// from tree AffineExpr's that have mod's and div's and are thus needed
/// to increase representational power.
//
class FlatAffineConstraints {
public:
enum IdKind { Dimension, Symbol, Local };
/// Constructs a constraint system reserving memory for the specified number
/// of constraints and identifiers..
FlatAffineConstraints(unsigned numReservedInequalities,
unsigned numReservedEqualities,
unsigned numReservedCols, unsigned numDims = 0,
unsigned numSymbols = 0, unsigned numLocals = 0,
ArrayRef<Optional<MLValue *>> idArgs = {})
: numReservedCols(numReservedCols), numDims(numDims),
numSymbols(numSymbols) {
assert(numReservedCols >= numDims + numSymbols + 1);
equalities.reserve(numReservedCols * numReservedEqualities);
inequalities.reserve(numReservedCols * numReservedInequalities);
numIds = numDims + numSymbols + numLocals;
ids.reserve(numReservedCols);
if (idArgs.empty())
ids.resize(numIds, None);
else
ids.insert(ids.end(), idArgs.begin(), idArgs.end());
}
/// Constructs a constraint system with the specified number of
/// dimensions and symbols.
FlatAffineConstraints(unsigned numDims = 0, unsigned numSymbols = 0,
unsigned numLocals = 0)
: numReservedCols(numDims + numSymbols + numLocals + 1), numDims(numDims),
numSymbols(numSymbols) {
assert(numReservedCols >= numDims + numSymbols + 1);
numIds = numDims + numSymbols + numLocals;
ids.resize(numIds, None);
}
explicit FlatAffineConstraints(const HyperRectangularSet &set);
/// Create a flat affine constraint system from an AffineValueMap or a list of
/// these. The constructed system will only include equalities.
// TODO(bondhugula)
explicit FlatAffineConstraints(const AffineValueMap &avm);
explicit FlatAffineConstraints(ArrayRef<const AffineValueMap *> avmRef);
/// Creates an affine constraint system from an IntegerSet.
explicit FlatAffineConstraints(IntegerSet set);
/// Create an affine constraint system from an IntegerValueSet.
// TODO(bondhugula)
explicit FlatAffineConstraints(const IntegerValueSet &set);
FlatAffineConstraints(const FlatAffineConstraints &other);
FlatAffineConstraints(ArrayRef<const AffineValueMap *> avmRef,
IntegerSet set);
FlatAffineConstraints(const MutableAffineMap &map);
~FlatAffineConstraints() {}
// Clears any existing data and reserves memory for the specified constraints.
void reset(unsigned numReservedInequalities, unsigned numReservedEqualities,
unsigned numReservedCols, unsigned numDims, unsigned numSymbols,
unsigned numLocals = 0, ArrayRef<MLValue *> idArgs = {});
void reset(unsigned numDims = 0, unsigned numSymbols = 0,
unsigned numLocals = 0, ArrayRef<MLValue *> idArgs = {});
/// Appends constraints from 'other' into this. This is equivalent to an
/// intersection with no simplification of any sort attempted.
void append(const FlatAffineConstraints &other);
// Checks for emptiness by performing variable elimination on all identifiers,
// running the GCD test on each equality constraint, and checking for invalid
// constraints.
// Returns true if the GCD test fails for any equality, or if any invalid
// constraints are discovered on any row. Returns false otherwise.
bool isEmpty() const;
// Runs the GCD test on all equality constraints. Returns 'true' if this test
// fails on any equality. Returns 'false' otherwise.
// This test can be used to disprove the existence of a solution. If it
// returns true, no integer solution to the equality constraints can exist.
bool isEmptyByGCDTest() const;
/// Tightens inequalities given that we are dealing with integer spaces. This
/// is similar to the GCD test but applied to inequalities. The constant term
/// can be reduced to the preceding multiple of the GCD of the coefficients,
/// i.e.,
/// 64*i - 100 >= 0 => 64*i - 128 >= 0 (since 'i' is an integer). This is a
/// fast method (linear in the number of coefficients).
void GCDTightenInequalities();
// Eliminates a single identifier at 'position' from equality and inequality
// constraints. Returns 'true' if the identifier was eliminated.
// Returns 'false' otherwise.
bool gaussianEliminateId(unsigned position);
// Eliminates identifiers from equality and inequality constraints
// in column range [posStart, posLimit).
// Returns the number of variables eliminated.
unsigned gaussianEliminateIds(unsigned posStart, unsigned posLimit);
// Clones this object.
std::unique_ptr<FlatAffineConstraints> clone() const;
/// Returns the value at the specified equality row and column.
inline int64_t atEq(unsigned i, unsigned j) const {
return equalities[i * numReservedCols + j];
}
inline int64_t &atEq(unsigned i, unsigned j) {
return equalities[i * numReservedCols + j];
}
inline int64_t atIneq(unsigned i, unsigned j) const {
return inequalities[i * numReservedCols + j];
}
inline int64_t &atIneq(unsigned i, unsigned j) {
return inequalities[i * numReservedCols + j];
}
/// Returns the number of columns in the constraint system.
inline unsigned getNumCols() const { return numIds + 1; }
inline unsigned getNumEqualities() const {
assert(equalities.size() % numReservedCols == 0 &&
"inconsistent equality buffer size");
return equalities.size() / numReservedCols;
}
inline unsigned getNumInequalities() const {
assert(inequalities.size() % numReservedCols == 0 &&
"inconsistent inequality buffer size");
return inequalities.size() / numReservedCols;
}
inline unsigned getNumReservedEqualities() const {
return equalities.capacity() / numReservedCols;
}
inline unsigned getNumReservedInequalities() const {
return inequalities.capacity() / numReservedCols;
}
inline ArrayRef<int64_t> getEquality(unsigned idx) const {
return ArrayRef<int64_t>(&equalities[idx * numReservedCols], getNumCols());
}
inline ArrayRef<int64_t> getInequality(unsigned idx) const {
return ArrayRef<int64_t>(&inequalities[idx * numReservedCols],
getNumCols());
}
AffineExpr toAffineExpr(unsigned idx, MLIRContext *context);
// Adds an inequality (>= 0) from the coefficients specified in inEq.
void addInequality(ArrayRef<int64_t> inEq);
// Adds an equality from the coefficients specified in eq.
void addEquality(ArrayRef<int64_t> eq);
/// Adds a constant lower bound constraint for the specified identifier.
void addConstantLowerBound(unsigned pos, int64_t lb);
/// Adds a constant upper bound constraint for the specified identifier.
void addConstantUpperBound(unsigned pos, int64_t ub);
/// Adds a lower bound expression for the specified expression.
void addLowerBound(ArrayRef<int64_t> expr, ArrayRef<int64_t> lb);
/// Adds constraints (lower and upper bounds) from the ForStmt into the
/// FlatAffineConstraints. 'forStmt's' MLValue is used to look up the right
/// identifier, and if it doesn't exist, a new one is added. Returns false for
/// the yet unimplemented/unsupported cases.
bool addBoundsFromForStmt(unsigned pos, ForStmt *forStmt);
/// Adds an upper bound expression for the specified expression.
void addUpperBound(ArrayRef<int64_t> expr, ArrayRef<int64_t> ub);
/// Adds a constant lower bound constraint for the specified expression.
void addConstantLowerBound(ArrayRef<int64_t> expr, int64_t lb);
/// Adds a constant upper bound constraint for the specified expression.
void addConstantUpperBound(ArrayRef<int64_t> expr, int64_t ub);
/// Sets the identifier at the specified position to a constant.
void setIdToConstant(unsigned pos, int64_t val);
/// Looks up the identifier with the specified MLValue. Returns false if not
/// found.
bool findId(const MLValue &operand, unsigned *pos);
// Add identifiers of the specified kind - specified positions are relative to
// the kind of identifier. 'id' is the MLValue corresponding to the
// identifier that can optionally be provided.
void addDimId(unsigned pos, MLValue *id = nullptr);
void addSymbolId(unsigned pos);
void addLocalId(unsigned pos);
void addId(IdKind kind, unsigned pos, MLValue *id = nullptr);
/// Composes the affine value map with this FlatAffineConstrains, adding the
/// results of the map as dimensions at the specified position and with the
/// dimensions set to the equalities specified by the value map.
void composeMap(AffineValueMap *vMap, unsigned pos = 0);
/// Eliminates identifier at the specified position using Fourier-Motzkin
/// variable elimination. If the result of the elimination is integer exact,
/// *isResultIntegerExact is set to true. If 'darkShadow' is set to true, a
/// potential under approximation (subset) of the rational shadow / exact
/// integer shadow is computed.
// See implementation comments for more details.
void FourierMotzkinEliminate(unsigned pos, bool darkShadow = false,
bool *isResultIntegerExact = nullptr);
/// Projects out (aka eliminates) 'num' identifiers starting at position
/// 'pos'. The resulting constraint system is the shadow along the dimensions
/// that still exist. This method may not always be integer exact.
// TODO(bondhugula): deal with integer exactness when necessary - can return a
// value to mark exactness for example.
void projectOut(unsigned pos, unsigned num);
/// Projects out the identifier that is associate with MLValue *.
void projectOut(MLValue *id);
void removeId(IdKind idKind, unsigned pos);
void removeId(unsigned pos);
void removeDim(unsigned pos);
void removeEquality(unsigned pos);
void removeInequality(unsigned pos);
unsigned getNumConstraints() const {
return getNumInequalities() + getNumEqualities();
}
inline unsigned getNumIds() const { return numIds; }
inline unsigned getNumDimIds() const { return numDims; }
inline unsigned getNumSymbolIds() const { return numSymbols; }
inline unsigned getNumLocalIds() const {
return numIds - numDims - numSymbols;
}
inline ArrayRef<Optional<MLValue *>> getIds() const {
return {ids.data(), ids.size()};
}
/// Clears this list of constraints and copies other into it.
void clearAndCopyFrom(const FlatAffineConstraints &other);
/// Returns the constant lower bound of the specified identifier (through a
/// scan through the constraints); returns None if the bound isn't trivially a
/// constant.
Optional<int64_t> getConstantLowerBound(unsigned pos) const;
/// Returns the constant upper bound of the specified identifier (through a
/// scan through the constraints); returns None if the bound isn't trivially a
/// constant. Note that the upper bound for FlatAffineConstraints is
/// inclusive.
Optional<int64_t> getConstantUpperBound(unsigned pos) const;
/// Returns the extent (upper bound - lower bound) of the specified
/// identifier if it is found to be a constant; returns None if it's not a
/// constant. 'lbPosition' is set to the row position of the corresponding
/// lower bound.
Optional<int64_t> getConstantBoundDifference(unsigned pos,
unsigned *lbPosition) const;
// Returns the lower and upper bounds of the specified dimensions as
// AffineMap's. Returns false for the unimplemented cases for the moment.
bool getDimensionBounds(unsigned pos, unsigned num,
SmallVectorImpl<AffineMap> *lbs,
SmallVectorImpl<AffineMap> *ubs,
MLIRContext *context);
/// Returns true if the set is hyper-rectangular on the specified contiguous
/// set of identifiers.
bool isHyperRectangular(unsigned pos, unsigned num) const;
// More expensive ones.
void removeDuplicates();
void print(raw_ostream &os) const;
void dump() const;
private:
// Removes coefficients in column range [colStart, colLimit),and copies any
// remaining valid data into place, updates member variables, and resizes
// arrays as needed.
void removeColumnRange(unsigned colStart, unsigned colLimit);
/// Coefficients of affine equalities (in == 0 form).
SmallVector<int64_t, 64> equalities;
/// Coefficients of affine inequalities (in >= 0 form).
SmallVector<int64_t, 64> inequalities;
/// Number of columns reserved. Actual ones in used are returned by
/// getNumCols().
unsigned numReservedCols;
/// Total number of identifiers.
unsigned numIds;
/// Number of identifiers corresponding to real dimensions.
unsigned numDims;
/// Number of identifiers corresponding to symbols (unknown but constant for
/// analysis).
unsigned numSymbols;
/// MLValues corresponding to the (column) identifiers of this constraint
/// system appearing in the order the identifiers correspond to columns.
/// Temporary ones or those that aren't associated to any MLValue are to be
/// set to None.
SmallVector<Optional<MLValue *>, 8> ids;
};
} // end namespace mlir.
#endif // MLIR_ANALYSIS_AFFINE_STRUCTURES_H