Sketch out affine analysis structures: AffineValueMap, IntegerValueSet,

FlatAffineConstraints, and MutableAffineMap.

All four classes introduced reside in lib/Analysis and are not meant to be
used in the IR (from lib/IR or lib/Parser/). They are all mutable, alloc'ed,
dealloc'ed - although with their fields pointing to immutable affine
expressions (AffineExpr *).

While on this, update simplifyMod to fold mod to a zero when possible.

PiperOrigin-RevId: 209618437
This commit is contained in:
Uday Bondhugula 2018-08-21 10:32:24 -07:00 committed by jpienaar
parent d9290db5fe
commit 6911c24e97
5 changed files with 364 additions and 12 deletions

View File

@ -0,0 +1,268 @@
//===- 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/Support/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
namespace mlir {
class AffineExpr;
class AffineApplyOp;
class AffineBound;
class AffineCondition;
class AffineMap;
class IntegerSet;
class MLIRContext;
class MLValue;
/// A mutable affine map. Its affine expressions are however unique.
struct MutableAffineMap {
public:
explicit MutableAffineMap(AffineMap *map);
AffineExpr *getResult(unsigned idx) const { return results[idx]; }
unsigned getNumResults() const { return results.size(); }
private:
SmallVector<AffineExpr *, 8> results;
SmallVector<AffineExpr *, 8> rangeSizes;
};
/// A mutable integer set. Its affine expressions are however unique.
struct MutableIntegerSet {
public:
explicit MutableIntegerSet(IntegerSet *set);
unsigned getNumDims() const { return numDims; }
unsigned getNumSymbols() const { return numSymbols; }
unsigned getNumConstraints() const { return constraints.size(); }
private:
unsigned numDims;
unsigned numSymbols;
SmallVector<AffineExpr *, 8> constraints;
SmallVector<bool, 8> eqFlags;
};
/// 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.
// TODO(bondhugula): Some of these classes could go into separate files.
class AffineValueMap {
public:
explicit AffineValueMap(const AffineApplyOp &op);
explicit AffineValueMap(const AffineBound &bound);
explicit AffineValueMap(AffineMap *map);
~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 fwdSubstitute(const AffineValueMap &inputMap);
void fwdSubstitute(const AffineApplyOp &inputOp);
/// Return true if the idx^th result can be proved to be a multiple of
/// 'factor', false otherwise.
bool isMultipleOf(unsigned idx, int64_t factor) 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;
private:
// 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;
/// A pointer to the IR's context to store all newly created AffineExpr's.
MLIRContext *context;
};
/// 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 pointer to the IR's context to store all newly created AffineExpr's.
MLIRContext *context;
};
/// 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
///
/// The coefficients are stored. 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 and
/// are needed to increase representational power. Local identifiers / are
/// typically obtained when eliminating % and div constraints.
// Usage scenario:
// For a register tiling or unroll-jam, for example, if we need to check loop
// bounds are a multiple of say the tile size say 4:
//
// %lb = affine_apply #map1 (%s, %i0)
// %ub = affine_apply #map2 (%N, %i0)
// for %i1 = %lb to %ub
// ...
//
// Create AffineValueMap's that have result %lb, %ub (successively fwd
// substituting all affine_apply that lead to the %lb, %ub). Create another
// AffineValueMap: %trip_count = (%ub - %lb + 1). Create a
// FlatAffineConstraints set using all these. Add %trip_count % 4 = 0 to this,
// and check for feasibility.
class FlatAffineConstraints {
public:
enum IdKind { Dimension, Symbol, Local };
/// Construct a constraint system reserving memory for the specified number of
/// constraints and identifiers..
FlatAffineConstraints(unsigned numReservedInequalities,
unsigned numReservedEqualities, unsigned numReservedIds)
: numEqualities(0), numInequalities(0),
numReservedEqualities(numReservedEqualities),
numReservedInequalities(numReservedInequalities),
numReservedIds(numReservedIds) {
equalities.reserve(numReservedIds * numReservedEqualities);
inequalities.reserve(numReservedIds * numReservedInequalities);
}
/// 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);
/// Create an affine constraint system from an IntegerValueSet.
// TODO(bondhugula)
FlatAffineConstraints(const IntegerValueSet &set);
FlatAffineConstraints(ArrayRef<const AffineValueMap *> avmRef,
const IntegerSet &set);
~FlatAffineConstraints() {}
inline int64_t atEq(unsigned i, unsigned j) {
return equalities[i * numIds + j];
}
inline int64_t atIneq(unsigned i, unsigned j) {
return inequalities[i * numIds + j];
}
unsigned getNumEqualities() const { return equalities.size(); }
unsigned getNumInequalities() const { return inequalities.size(); }
void addInequality(ArrayRef<int64_t> inEq);
void addEquality(ArrayRef<int64_t> eq);
void addId(IdKind idKind, unsigned pos);
void addDimId(unsigned pos);
void addSymbolId(unsigned pos);
void addLocalId(unsigned pos);
void removeId(IdKind idKind, unsigned pos);
void removeEquality(unsigned pos);
void removeInequality(unsigned pos);
unsigned getNumConstraints() const { return numEqualities + numInequalities; }
unsigned getNumIds() const { return numIds; }
unsigned getNumDimIds() const { return numDims; }
unsigned getNumSymbolIds() const { return numSymbols; }
unsigned getNumLocalIds() const { return numDims - numSymbols - numDims; }
private:
/// 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 equalities in this system.
unsigned numEqualities;
/// Number of inequalities in this system.
unsigned numInequalities;
// Pre-allocated space.
unsigned numReservedEqualities;
unsigned numReservedInequalities;
unsigned numReservedIds;
/// 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;
};
} // end namespace mlir.
#endif // MLIR_ANALYSIS_AFFINE_STRUCTURES_H

View File

@ -238,6 +238,8 @@ public:
}
private:
// TODO(shpeisman): please name the ForStmt's bounds encapsulating
// an affinemap and its operands as AffineBound.
AffineConstantExpr *lowerBound;
AffineConstantExpr *upperBound;
AffineConstantExpr *step;
@ -292,6 +294,8 @@ public:
private:
IfClause *thenClause;
IfClause *elseClause;
// TODO(shpeisman): please name the ifStmt's conditional encapsulating
// IntegerSet + its operands as AffineCondition.
// The integer set capturing the conditional guard.
IntegerSet *condition;
// TODO: arguments to integer set

View File

@ -0,0 +1,61 @@
//===- AffineStructures.cpp - 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 MLIR functions.
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/AffineStructures.h"
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/AffineMap.h"
#include "mlir/IR/IntegerSet.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/StandardOps.h"
namespace mlir {
MutableAffineMap::MutableAffineMap(AffineMap *map) {
for (auto *result : map->getResults())
results.push_back(result);
for (auto *rangeSize : map->getRangeSizes())
results.push_back(rangeSize);
}
MutableIntegerSet::MutableIntegerSet(IntegerSet *set)
: numDims(set->getNumDims()), numSymbols(set->getNumSymbols()) {
// TODO(bondhugula)
}
AffineValueMap::AffineValueMap(const AffineApplyOp &op)
: map(op.getAffineMap()) {
// TODO: pull operands and results in.
}
bool AffineValueMap::isMultipleOf(unsigned idx, int64_t factor) const {
/* Check if the (first result expr) % factor becomes 0. */
if (auto *expr = dyn_cast<AffineConstantExpr>(AffineBinaryOpExpr::get(
AffineExpr::Kind::Mod, map.getResult(idx),
AffineConstantExpr::get(factor, context), context)))
return expr->getValue() == 0;
// TODO(bondhugula): use FlatAffineConstraints to complete this.
assert(0 && "isMultipleOf implementation incomplete");
}
AffineValueMap::~AffineValueMap() {}
} // end namespace mlir

View File

@ -205,20 +205,33 @@ AffineExpr *AffineBinaryOpExpr::simplifyCeilDiv(AffineExpr *lhs,
}
return nullptr;
// TODO(someone): implement more simplification along the lines described in
// simplifyMod TODO. For eg: 128*N ceildiv 128 is N.
}
AffineExpr *AffineBinaryOpExpr::simplifyMod(AffineExpr *lhs, AffineExpr *rhs,
MLIRContext *context) {
if (auto *l = dyn_cast<AffineConstantExpr>(lhs))
if (auto *r = dyn_cast<AffineConstantExpr>(rhs))
return AffineConstantExpr::get(l->getValue() % r->getValue(), context);
auto *lhsConst = dyn_cast<AffineConstantExpr>(lhs);
auto *rhsConst = dyn_cast<AffineConstantExpr>(rhs);
if (lhsConst && rhsConst)
return AffineConstantExpr::get(lhsConst->getValue() % rhsConst->getValue(),
context);
// Fold modulo of a multiply with a constant that is a multiple of the
// modulo factor to zero. Eg: (i * 128) mod 64 = 0.
if (rhsConst) {
auto *lBin = dyn_cast<AffineBinaryOpExpr>(lhs);
if (lBin && lBin->getKind() == Kind::Mul) {
if (auto *lrhs = dyn_cast<AffineConstantExpr>(lBin->getRHS())) {
// rhsConst is known to be positive if a constant.
if (lrhs->getValue() % rhsConst->getValue() == 0)
return AffineConstantExpr::get(0, context);
}
}
}
return nullptr;
// TODO(someone): implement more simplification; for eg: 2*x mod 2 is 0; (2*x
// + 1) mod 2 is 1. In general, this can be simplified by using the GCD test
// iteratively if the RHS of the mod is a small number, or in general using
// quantifier elimination (add two new variables q and r, and eliminate all
// variables from the linear system other than r.
// TODO(bondhugula): In general, this can be simplified more by using the GCD
// test, or in general using quantifier elimination (add two new variables q
// and r, and eliminate all variables from the linear system other than r. All
// of this can be done through mlir/Analysis/'s FlatAffineConstraints.
}

View File

@ -146,8 +146,11 @@
// CHECK: #map{{[0-9]+}} = (d0, d1, d2)[s0] -> (d0 + d1 + d2 + 1, d2 + d1, (d0 * s0) * 8)
#map45 = (i, j, k) [N] -> (1 + i + 3 + j - 3 + k, k + 5 + j - 5, 2*i*4*N)
// CHECK: #map{{[0-9]+}} = (d0, d1, d2) -> (0, d0 * 2, 0, d0, d0 * 4)
#map46 = (i, j, k) -> (i*0, i * 128 floordiv 64, j * 0 floordiv 64, i * 64 ceildiv 64, i * 512 ceildiv 128)
// CHECK: #map{{[0-9]+}} = (d0, d1, d2) -> (0, d1, d0 * 2, 0)
#map46 = (i, j, k) -> (i*0, 1*j, i * 128 floordiv 64, j * 0 floordiv 64)
// CHECK: #map{{[0-9]+}} = (d0, d1, d2) -> (d0, d0 * 4, 0, 0)
#map47 = (i, j, k) -> (i * 64 ceildiv 64, i * 512 ceildiv 128, 4 * j mod 4, 4*j*4 mod 8)
// CHECK: extfunc @f0(memref<2x4xi8, #map{{[0-9]+}}, 1>)
extfunc @f0(memref<2x4xi8, #map0, 1>)
@ -302,3 +305,6 @@ extfunc @f45(memref<100x100x100xi8, #map45>)
// CHECK: extfunc @f46(memref<100x100x100xi8, #map{{[0-9]+}}>)
extfunc @f46(memref<100x100x100xi8, #map46>)
// CHECK: extfunc @f47(memref<100x100x100xi8, #map{{[0-9]+}}>)
extfunc @f47(memref<100x100x100xi8, #map47>)