Complete affine expr parsing support

- check for non-affine expressions
- handle negative numbers and negation of id's, expressions
- functions to check if a map is pure affine or semi-affine
- simplify/clean up affine map parsing code
- report more errors messages, more accurate error messages

PiperOrigin-RevId: 203773633
This commit is contained in:
Uday Bondhugula 2018-07-09 09:00:25 -07:00 committed by jpienaar
parent a5a6c77e91
commit fc46bcf51d
8 changed files with 540 additions and 326 deletions

View File

@ -45,9 +45,6 @@ class AffineExpr {
/// op's is expected to be this element and earlier.
LAST_AFFINE_BINARY_OP = CeilDiv,
// Unary op negation
Neg,
// Constant integer.
Constant,
// Dimensional identifier.
@ -62,6 +59,14 @@ class AffineExpr {
void print(raw_ostream &os) const;
void dump() const;
/// Returns true if this expression is made out of only symbols and
/// constants (no dimensional identifiers).
bool isSymbolic() const;
/// Returns true if this is a pure affine expression, i.e., multiplication,
/// floordiv, ceildiv, and mod is only allowed w.r.t constants.
bool isPureAffine() const;
protected:
explicit AffineExpr(Kind kind) : kind(kind) {}
@ -81,6 +86,10 @@ class AffineBinaryOpExpr : public AffineExpr {
AffineExpr *getLeftOperand() const { return lhsOperand; }
AffineExpr *getRightOperand() const { return rhsOperand; }
bool isSymbolic() const {
return lhsOperand->isSymbolic() && rhsOperand->isSymbolic();
}
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() <= Kind::LAST_AFFINE_BINARY_OP;
@ -101,8 +110,12 @@ class AffineBinaryOpExpr : public AffineExpr {
/// Binary affine add expression.
class AffineAddExpr : public AffineBinaryOpExpr {
public:
static AffineAddExpr *get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context);
static AffineExpr *get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context);
bool isPureAffine() const {
return lhsOperand->isPureAffine() && rhsOperand->isPureAffine();
}
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
@ -111,16 +124,24 @@ class AffineAddExpr : public AffineBinaryOpExpr {
void print(raw_ostream &os) const;
private:
/// Simplify the addition of two affine expressions.
static AffineExpr *simplify(AffineExpr *lhs, AffineExpr *rhs,
MLIRContext *context);
explicit AffineAddExpr(AffineExpr *lhsOperand, AffineExpr *rhsOperand)
: AffineBinaryOpExpr(Kind::Add, lhsOperand, rhsOperand) {}
};
/// Binary affine sub expression.
/// Binary affine subtract expression.
class AffineSubExpr : public AffineBinaryOpExpr {
public:
static AffineSubExpr *get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context);
bool isPureAffine() const {
return lhsOperand->isPureAffine() && rhsOperand->isPureAffine();
}
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::Sub;
@ -132,12 +153,14 @@ private:
: AffineBinaryOpExpr(Kind::Sub, lhsOperand, rhsOperand) {}
};
/// Binary affine mul expression.
/// Binary affine multiplication expression.
class AffineMulExpr : public AffineBinaryOpExpr {
public:
static AffineMulExpr *get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context);
bool isPureAffine() const;
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::Mul;
@ -149,12 +172,14 @@ private:
: AffineBinaryOpExpr(Kind::Mul, lhsOperand, rhsOperand) {}
};
/// Binary affine mod expression.
/// Binary affine modulo operation expression.
class AffineModExpr : public AffineBinaryOpExpr {
public:
static AffineModExpr *get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context);
bool isPureAffine() const;
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::Mod;
@ -172,6 +197,8 @@ class AffineFloorDivExpr : public AffineBinaryOpExpr {
static AffineFloorDivExpr *get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand, MLIRContext *context);
bool isPureAffine() const;
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::FloorDiv;
@ -189,6 +216,8 @@ public:
static AffineCeilDivExpr *get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context);
bool isPureAffine() const;
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::CeilDiv;
@ -200,28 +229,6 @@ private:
: AffineBinaryOpExpr(Kind::CeilDiv, lhsOperand, rhsOperand) {}
};
/// Unary affine expression.
class AffineUnaryOpExpr : public AffineExpr {
public:
static AffineUnaryOpExpr *get(const AffineExpr &operand,
MLIRContext *context);
static AffineUnaryOpExpr *get(const AffineExpr &operand);
AffineExpr *getOperand() const { return operand; }
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::Neg;
}
void print(raw_ostream &os) const;
private:
explicit AffineUnaryOpExpr(Kind kind, AffineExpr *operand)
: AffineExpr(kind), operand(operand) {}
AffineExpr *operand;
};
/// A dimensional identifier appearing in an affine expression.
///
/// This is a POD type of int size; so it should be passed around by
@ -233,6 +240,8 @@ public:
unsigned getPosition() const { return position; }
bool isPureAffine() const { return true; }
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::DimId;
@ -258,6 +267,8 @@ class AffineSymbolExpr : public AffineExpr {
unsigned getPosition() const { return position; }
bool isPureAffine() const { return true; }
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::SymbolId;
@ -279,6 +290,8 @@ class AffineConstantExpr : public AffineExpr {
int64_t getValue() const { return constant; }
bool isPureAffine() const { return true; }
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static bool classof(const AffineExpr *expr) {
return expr->getKind() == Kind::Constant;

View File

@ -16,5 +16,66 @@
// =============================================================================
#include "mlir/IR/AffineExpr.h"
#include "mlir/Support/STLExtras.h"
using namespace mlir;
bool AffineExpr::isSymbolic() const {
switch (getKind()) {
case Kind::Constant:
return true;
case Kind::DimId:
return false;
case Kind::SymbolId:
return true;
case Kind::Add:
case Kind::Sub:
case Kind::Mul:
case Kind::FloorDiv:
case Kind::CeilDiv:
case Kind::Mod:
return cast<AffineBinaryOpExpr>(this)->isSymbolic();
}
}
bool AffineExpr::isPureAffine() const {
switch (getKind()) {
case Kind::SymbolId:
return cast<AffineSymbolExpr>(this)->isPureAffine();
case Kind::DimId:
return cast<AffineDimExpr>(this)->isPureAffine();
case Kind::Constant:
return cast<AffineConstantExpr>(this)->isPureAffine();
case Kind::Add:
return cast<AffineAddExpr>(this)->isPureAffine();
case Kind::Sub:
return cast<AffineSubExpr>(this)->isPureAffine();
case Kind::Mul:
return cast<AffineMulExpr>(this)->isPureAffine();
case Kind::FloorDiv:
return cast<AffineFloorDivExpr>(this)->isPureAffine();
case Kind::CeilDiv:
return cast<AffineCeilDivExpr>(this)->isPureAffine();
case Kind::Mod:
return cast<AffineModExpr>(this)->isPureAffine();
}
}
bool AffineMulExpr::isPureAffine() const {
return lhsOperand->isPureAffine() && rhsOperand->isPureAffine() &&
(isa<AffineConstantExpr>(lhsOperand) ||
isa<AffineConstantExpr>(rhsOperand));
}
bool AffineFloorDivExpr::isPureAffine() const {
return lhsOperand->isPureAffine() && isa<AffineConstantExpr>(rhsOperand);
}
bool AffineCeilDivExpr::isPureAffine() const {
return lhsOperand->isPureAffine() && isa<AffineConstantExpr>(rhsOperand);
}
bool AffineModExpr::isPureAffine() const {
return lhsOperand->isPureAffine() && isa<AffineConstantExpr>(rhsOperand);
}

View File

@ -16,6 +16,7 @@
// =============================================================================
#include "mlir/IR/AffineMap.h"
#include "mlir/IR/AffineExpr.h"
#include "llvm/ADT/StringRef.h"
using namespace mlir;
@ -24,3 +25,15 @@ AffineMap::AffineMap(unsigned numDims, unsigned numSymbols, unsigned numResults,
AffineExpr *const *results)
: numDims(numDims), numSymbols(numSymbols), numResults(numResults),
results(results) {}
AffineExpr *AffineAddExpr::simplify(AffineExpr *lhs, AffineExpr *rhs,
MLIRContext *context) {
AffineConstantExpr *l, *r;
if ((l = dyn_cast<AffineConstantExpr>(lhs)) &&
(r = dyn_cast<AffineConstantExpr>(rhs)))
return AffineConstantExpr::get(l->getValue() + r->getValue(), context);
return nullptr;
// TODO(someone): implement more simplification.
}
// TODO(bondhugula): implement simplify for remaining affine binary op expr's

View File

@ -282,6 +282,8 @@ void Instruction::dump() const {
print(llvm::errs());
}
void AffineMap::dump() const { print(llvm::errs()); }
void AffineExpr::dump() const {
print(llvm::errs());
llvm::errs() << "\n";
@ -339,9 +341,6 @@ void AffineExpr::print(raw_ostream &os) const {
return cast<AffineCeilDivExpr>(this)->print(os);
case Kind::Mod:
return cast<AffineModExpr>(this)->print(os);
default:
os << "<unimplemented expr>";
return;
}
}

View File

@ -171,8 +171,7 @@ public:
// Affine binary op expression uniquing. Figure out uniquing of dimensional
// or symbolic identifiers.
DenseMap<std::tuple<unsigned, AffineExpr *, AffineExpr *>,
AffineBinaryOpExpr *>
DenseMap<std::tuple<unsigned, AffineExpr *, AffineExpr *>, AffineExpr *>
affineExprs;
/// Integer type uniquing.
@ -583,68 +582,132 @@ AffineMap *AffineMap::get(unsigned dimCount, unsigned symbolCount,
return *existing.first = res;
}
AffineBinaryOpExpr *AffineBinaryOpExpr::get(AffineExpr::Kind kind,
AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
AffineExpr *AffineAddExpr::get(AffineExpr *lhsOperand, AffineExpr *rhsOperand,
MLIRContext *context) {
auto &impl = context->getImpl();
// Check if we already have this affine expression.
auto keyValue = std::make_tuple((unsigned)kind, lhsOperand, rhsOperand);
auto keyValue = std::make_tuple((unsigned)Kind::Add, lhsOperand, rhsOperand);
auto *&result = impl.affineExprs[keyValue];
// If we already have it, return that value.
if (result)
return result;
// Use the simplified expression if it can be simplified.
result = AffineAddExpr::simplify(lhsOperand, rhsOperand, context);
if (!result) {
// On the first use, we allocate them into the bump pointer.
result = impl.allocator.Allocate<AffineBinaryOpExpr>();
result = impl.allocator.Allocate<AffineAddExpr>();
// Initialize the memory using placement new.
new (result) AffineBinaryOpExpr(kind, lhsOperand, rhsOperand);
new (result) AffineAddExpr(lhsOperand, rhsOperand);
}
return result;
}
// TODO(bondhugula): complete uniquing of remaining AffineExpr sub-classes.
AffineAddExpr *AffineAddExpr::get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
return cast<AffineAddExpr>(
AffineBinaryOpExpr::get(Kind::Add, lhsOperand, rhsOperand, context));
}
AffineSubExpr *AffineSubExpr::get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
return cast<AffineSubExpr>(
AffineBinaryOpExpr::get(Kind::Sub, lhsOperand, rhsOperand, context));
auto &impl = context->getImpl();
// Check if we already have this affine expression.
auto keyValue = std::make_tuple((unsigned)Kind::Sub, lhsOperand, rhsOperand);
auto *&result = impl.affineExprs[keyValue];
// If we already have it, return that value.
if (!result) {
// On the first use, we allocate them into the bump pointer.
result = impl.allocator.Allocate<AffineSubExpr>();
// Initialize the memory using placement new.
new (result) AffineSubExpr(lhsOperand, rhsOperand);
}
return cast<AffineSubExpr>(result);
}
AffineMulExpr *AffineMulExpr::get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
return cast<AffineMulExpr>(
AffineBinaryOpExpr::get(Kind::Mul, lhsOperand, rhsOperand, context));
auto &impl = context->getImpl();
// Check if we already have this affine expression.
const auto keyValue =
std::make_tuple((unsigned)Kind::Mul, lhsOperand, rhsOperand);
auto *&result = impl.affineExprs[keyValue];
// If we already have it, return that value.
if (!result) {
// On the first use, we allocate them into the bump pointer.
result = impl.allocator.Allocate<AffineMulExpr>();
// Initialize the memory using placement new.
new (result) AffineMulExpr(lhsOperand, rhsOperand);
}
return cast<AffineMulExpr>(result);
}
AffineFloorDivExpr *AffineFloorDivExpr::get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
return cast<AffineFloorDivExpr>(
AffineBinaryOpExpr::get(Kind::FloorDiv, lhsOperand, rhsOperand, context));
auto &impl = context->getImpl();
// Check if we already have this affine expression.
auto keyValue =
std::make_tuple((unsigned)Kind::FloorDiv, lhsOperand, rhsOperand);
auto *&result = impl.affineExprs[keyValue];
// If we already have it, return that value.
if (!result) {
// On the first use, we allocate them into the bump pointer.
result = impl.allocator.Allocate<AffineFloorDivExpr>();
// Initialize the memory using placement new.
new (result) AffineFloorDivExpr(lhsOperand, rhsOperand);
}
return cast<AffineFloorDivExpr>(result);
}
AffineCeilDivExpr *AffineCeilDivExpr::get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
return cast<AffineCeilDivExpr>(
AffineBinaryOpExpr::get(Kind::CeilDiv, lhsOperand, rhsOperand, context));
auto &impl = context->getImpl();
// Check if we already have this affine expression.
auto keyValue =
std::make_tuple((unsigned)Kind::CeilDiv, lhsOperand, rhsOperand);
auto *&result = impl.affineExprs[keyValue];
// If we already have it, return that value.
if (!result) {
// On the first use, we allocate them into the bump pointer.
result = impl.allocator.Allocate<AffineCeilDivExpr>();
// Initialize the memory using placement new.
new (result) AffineCeilDivExpr(lhsOperand, rhsOperand);
}
return cast<AffineCeilDivExpr>(result);
}
AffineModExpr *AffineModExpr::get(AffineExpr *lhsOperand,
AffineExpr *rhsOperand,
MLIRContext *context) {
return cast<AffineModExpr>(
AffineBinaryOpExpr::get(Kind::Mod, lhsOperand, rhsOperand, context));
auto &impl = context->getImpl();
// Check if we already have this affine expression.
auto keyValue = std::make_tuple((unsigned)Kind::Mod, lhsOperand, rhsOperand);
auto *&result = impl.affineExprs[keyValue];
// If we already have it, return that value.
if (!result) {
// On the first use, we allocate them into the bump pointer.
result = impl.allocator.Allocate<AffineModExpr>();
// Initialize the memory using placement new.
new (result) AffineModExpr(lhsOperand, rhsOperand);
}
return cast<AffineModExpr>(result);
}
AffineDimExpr *AffineDimExpr::get(unsigned position, MLIRContext *context) {

View File

@ -165,24 +165,23 @@ private:
ParseResult parseAffineMapDef();
AffineMap *parseAffineMapInline(StringRef mapId);
AffineExpr *parseAffineExpr(const AffineMapParserState &state);
AffineExpr *parseParentheticalExpr(const AffineMapParserState &state);
AffineExpr *parseNegateExpression(AffineExpr *lhs,
const AffineMapParserState &state);
AffineExpr *parseIntegerExpr(const AffineMapParserState &state);
AffineExpr *parseBareIdExpr(const AffineMapParserState &state);
AffineBinaryOpExpr *getBinaryAffineOpExpr(AffineHighPrecOp op,
AffineExpr *lhs, AffineExpr *rhs);
AffineBinaryOpExpr *getBinaryAffineOpExpr(AffineLowPrecOp op, AffineExpr *lhs,
AffineExpr *rhs);
ParseResult parseAffineOperandExpr(const AffineMapParserState &state,
AffineExpr *&result);
ParseResult parseAffineLowPrecOpExpr(AffineExpr *llhs, AffineLowPrecOp llhsOp,
const AffineMapParserState &state,
AffineExpr *&result);
ParseResult parseAffineHighPrecOpExpr(AffineExpr *llhs,
AffineExpr *getBinaryAffineOpExpr(AffineHighPrecOp op, AffineExpr *lhs,
AffineExpr *rhs);
AffineExpr *getBinaryAffineOpExpr(AffineLowPrecOp op, AffineExpr *lhs,
AffineExpr *rhs);
AffineExpr *parseAffineOperandExpr(AffineExpr *lhs,
const AffineMapParserState &state);
AffineExpr *parseAffineLowPrecOpExpr(AffineExpr *llhs, AffineLowPrecOp llhsOp,
const AffineMapParserState &state);
AffineExpr *parseAffineHighPrecOpExpr(AffineExpr *llhs,
AffineHighPrecOp llhsOp,
const AffineMapParserState &state,
AffineExpr *&result);
const AffineMapParserState &state);
// SSA
ParseResult parseSSAUse();
@ -616,12 +615,11 @@ Attribute *Parser::parseAttribute() {
}
}
/// Attribute dictionary.
///
/// attribute-dict ::= `{` `}`
/// | `{` attribute-entry (`,` attribute-entry)* `}`
/// attribute-entry ::= bare-id `:` attribute-value
/// attribute-dict ::= `{` `}`
/// | `{` attribute-entry (`,` attribute-entry)* `}`
/// attribute-entry ::= bare-id `:` attribute-value
///
ParseResult Parser::parseAttributeDict(
SmallVectorImpl<NamedAttribute> &attributes) {
@ -657,7 +655,7 @@ ParseResult Parser::parseAttributeDict(
/// Affine map declaration.
///
/// affine-map-def ::= affine-map-id `=` affine-map-inline
/// affine-map-def ::= affine-map-id `=` affine-map-inline
///
ParseResult Parser::parseAffineMapDef() {
assert(curToken.is(Token::hash_identifier));
@ -683,18 +681,37 @@ ParseResult Parser::parseAffineMapDef() {
return ParseSuccess;
}
/// Create an affine op expression
AffineBinaryOpExpr *Parser::getBinaryAffineOpExpr(AffineHighPrecOp op,
AffineExpr *lhs,
AffineExpr *rhs) {
/// Create an affine binary high precedence op expression (mul's, div's, mod)
AffineExpr *Parser::getBinaryAffineOpExpr(AffineHighPrecOp op, AffineExpr *lhs,
AffineExpr *rhs) {
switch (op) {
case Mul:
if (!lhs->isSymbolic() && !rhs->isSymbolic()) {
emitError("non-affine expression: at least one of the multiply "
"operands has to be either a constant or symbolic");
return nullptr;
}
return AffineMulExpr::get(lhs, rhs, builder.getContext());
case FloorDiv:
if (!rhs->isSymbolic()) {
emitError("non-affine expression: right operand of floordiv "
"has to be either a constant or symbolic");
return nullptr;
}
return AffineFloorDivExpr::get(lhs, rhs, builder.getContext());
case CeilDiv:
if (!rhs->isSymbolic()) {
emitError("non-affine expression: right operand of ceildiv "
"has to be either a constant or symbolic");
return nullptr;
}
return AffineCeilDivExpr::get(lhs, rhs, builder.getContext());
case Mod:
if (!rhs->isSymbolic()) {
emitError("non-affine expression: right operand of mod "
"has to be either a constant or symbolic");
return nullptr;
}
return AffineModExpr::get(lhs, rhs, builder.getContext());
case HNoOp:
llvm_unreachable("can't create affine expression for null high prec op");
@ -702,9 +719,9 @@ AffineBinaryOpExpr *Parser::getBinaryAffineOpExpr(AffineHighPrecOp op,
}
}
AffineBinaryOpExpr *Parser::getBinaryAffineOpExpr(AffineLowPrecOp op,
AffineExpr *lhs,
AffineExpr *rhs) {
/// Create an affine binary low precedence op expression (add, sub).
AffineExpr *Parser::getBinaryAffineOpExpr(AffineLowPrecOp op, AffineExpr *lhs,
AffineExpr *rhs) {
switch (op) {
case AffineLowPrecOp::Add:
return AffineAddExpr::get(lhs, rhs, builder.getContext());
@ -716,88 +733,8 @@ AffineBinaryOpExpr *Parser::getBinaryAffineOpExpr(AffineLowPrecOp op,
}
}
/// Parses an expression that can be a valid operand of an affine expression
/// (where associativity may not have been specified through parentheses).
// Eg: for an expression without parentheses (like i + j + k + l), each
// of the four identifiers is an operand. For: i + j*k + l, j*k is not an
// operand expression, it's an op expression and will be parsed via
// parseAffineLowPrecOpExpression().
ParseResult Parser::parseAffineOperandExpr(const AffineMapParserState &state,
AffineExpr *&result) {
result = parseParentheticalExpr(state);
if (!result)
result = parseBareIdExpr(state);
if (!result)
result = parseIntegerExpr(state);
return result ? ParseSuccess : ParseFailure;
}
/// Parse a high precedence op expression list: mul, div, and mod are high
/// precedence binary ops, i.e., parse a
/// expr_1 op_1 expr_2 op_2 ... expr_n
/// where op_1, op_2 are all a AffineHighPrecOp (mul, div, mod).
/// All affine binary ops are left associative.
/// Given llhs, returns (llhs * lhs) * rhs, or (lhs * rhs) if llhs is null. If
/// no rhs can be found, returns (llhs * lhs) or lhs if llhs is null.
// TODO(bondhugula): check whether mul is w.r.t. a constant - otherwise, the
/// map is semi-affine.
ParseResult Parser::parseAffineHighPrecOpExpr(AffineExpr *llhs,
AffineHighPrecOp llhsOp,
const AffineMapParserState &state,
AffineExpr *&result) {
// FIXME: Assume for now that llhsOp is mul.
AffineExpr *lhs = nullptr;
if (parseAffineOperandExpr(state, lhs)) {
return ParseFailure;
}
AffineHighPrecOp op = HNoOp;
// Found an LHS. Parse the remaining expression.
if ((op = consumeIfHighPrecOp())) {
if (llhs) {
// TODO(bondhugula): check whether 'lhs' here is a constant (for affine
// maps); semi-affine maps allow symbols.
AffineExpr *expr = getBinaryAffineOpExpr(llhsOp, llhs, lhs);
AffineExpr *subRes = nullptr;
if (parseAffineHighPrecOpExpr(expr, op, state, subRes)) {
if (!subRes)
emitError("missing right operand of multiply op");
// In spite of the error, setting result to prevent duplicate errors
// messages as the call stack unwinds. All of this due to left
// associativity.
result = expr;
return ParseFailure;
}
result = subRes ? subRes : expr;
return ParseSuccess;
}
// No LLHS, get RHS
AffineExpr *subRes = nullptr;
if (parseAffineHighPrecOpExpr(lhs, op, state, subRes)) {
// 'product' needs to be checked to prevent duplicate errors messages as
// the call stack unwinds. All of this due to left associativity.
if (!subRes)
emitError("missing right operand of multiply op");
return ParseFailure;
}
result = subRes;
return ParseSuccess;
}
// This is the last operand in this expression.
if (llhs) {
// TODO(bondhugula): check whether lhs here is a constant (for affine
// maps); semi-affine maps allow symbols.
result = getBinaryAffineOpExpr(llhsOp, llhs, lhs);
return ParseSuccess;
}
// No llhs, 'lhs' itself is the expression.
result = lhs;
return ParseSuccess;
}
/// Consume this token if it is a lower precedence affine op (there are only two
/// precedence levels)
/// precedence levels).
AffineLowPrecOp Parser::consumeIfLowPrecOp() {
switch (curToken.getKind()) {
case Token::plus:
@ -832,6 +769,148 @@ AffineHighPrecOp Parser::consumeIfHighPrecOp() {
}
}
/// Parse a high precedence op expression list: mul, div, and mod are high
/// precedence binary ops, i.e., parse a
/// expr_1 op_1 expr_2 op_2 ... expr_n
/// where op_1, op_2 are all a AffineHighPrecOp (mul, div, mod).
/// All affine binary ops are left associative.
/// Given llhs, returns (llhs llhsOp lhs) op rhs, or (lhs op rhs) if llhs is
/// null. If no rhs can be found, returns (llhs llhsOp lhs) or lhs if llhs is
/// null.
AffineExpr *
Parser::parseAffineHighPrecOpExpr(AffineExpr *llhs, AffineHighPrecOp llhsOp,
const AffineMapParserState &state) {
AffineExpr *lhs = parseAffineOperandExpr(llhs, state);
if (!lhs)
return nullptr;
AffineHighPrecOp op = HNoOp;
// Found an LHS. Parse the remaining expression.
if ((op = consumeIfHighPrecOp())) {
if (llhs) {
AffineExpr *expr = getBinaryAffineOpExpr(llhsOp, llhs, lhs);
if (!expr)
return nullptr;
return parseAffineHighPrecOpExpr(expr, op, state);
}
// No LLHS, get RHS
return parseAffineHighPrecOpExpr(lhs, op, state);
}
// This is the last operand in this expression.
if (llhs)
return getBinaryAffineOpExpr(llhsOp, llhs, lhs);
// No llhs, 'lhs' itself is the expression.
return lhs;
}
/// Parse an affine expression inside parentheses.
///
/// affine-expr ::= `(` affine-expr `)`
AffineExpr *Parser::parseParentheticalExpr(const AffineMapParserState &state) {
if (!consumeIf(Token::l_paren))
return (emitError("expected '('"), nullptr);
if (curToken.is(Token::r_paren))
return (emitError("no expression inside parentheses"), nullptr);
auto *expr = parseAffineExpr(state);
if (!expr)
// Error would have been emitted by parseAffineExpr.
return nullptr;
if (!consumeIf(Token::r_paren))
return (emitError("expected ')'"), nullptr);
return expr;
}
/// Parse the negation expression.
///
/// affine-expr ::= `-` affine-expr
AffineExpr *Parser::parseNegateExpression(AffineExpr *lhs,
const AffineMapParserState &state) {
if (!consumeIf(Token::minus))
return (emitError("expected '-'"), nullptr);
AffineExpr *operand = parseAffineOperandExpr(lhs, state);
// Since negation has the highest precedence of all ops (including high
// precedence ops) but lower than parentheses, we are only going to use
// parseAffineOperandExpr instead of parseAffineExpr here.
if (!operand)
// Extra error message although parseAffineOperandExpr would have
// complained. Leads to a better diagnostic.
return (emitError("missing operand of negation"), nullptr);
AffineConstantExpr *minusOne =
AffineConstantExpr::get(-1, builder.getContext());
return AffineMulExpr::get(minusOne, operand, builder.getContext());
}
/// Parse a bare id that may appear in an affine expression.
///
/// affine-expr ::= bare-id
AffineExpr *Parser::parseBareIdExpr(const AffineMapParserState &state) {
if (curToken.isNot(Token::bare_identifier))
return (emitError("expected bare identifier"), nullptr);
StringRef sRef = curToken.getSpelling();
const auto &dims = state.getDims();
const auto &symbols = state.getSymbols();
if (dims.count(sRef)) {
consumeToken(Token::bare_identifier);
return AffineDimExpr::get(dims.lookup(sRef), builder.getContext());
}
if (symbols.count(sRef)) {
consumeToken(Token::bare_identifier);
return AffineSymbolExpr::get(symbols.lookup(sRef), builder.getContext());
}
return (emitError("identifier is neither dimensional nor symbolic"), nullptr);
}
/// Parse a positive integral constant appearing in an affine expression.
///
/// affine-expr ::= integer-literal
AffineExpr *Parser::parseIntegerExpr(const AffineMapParserState &state) {
// No need to handle negative numbers separately here. They are naturally
// handled via the unary negation operator, although (FIXME) MININT_64 still
// not correctly handled.
if (curToken.isNot(Token::integer))
return (emitError("expected integer"), nullptr);
auto val = curToken.getUInt64IntegerValue();
if (!val.hasValue() || (int64_t)val.getValue() < 0) {
return (emitError("constant too large for affineint"), nullptr);
}
consumeToken(Token::integer);
return AffineConstantExpr::get((int64_t)val.getValue(), builder.getContext());
}
/// Parses an expression that can be a valid operand of an affine expression.
/// lhs: if non-null, an affine expression that is the lhs of a binary operator,
/// the rhs of which is being parsed. This is used to determine whether an error
/// should be emitted for a missing right operand.
// Eg: for an expression without parentheses (like i + j + k + l), each
// of the four identifiers is an operand. For i + j*k + l, j*k is not an
// operand expression, it's an op expression and will be parsed via
// parseAffineHighPrecOpExpression(). However, for i + (j*k) + -l, (j*k) and -l
// are valid operands that will be parsed by this function.
AffineExpr *Parser::parseAffineOperandExpr(AffineExpr *lhs,
const AffineMapParserState &state) {
switch (curToken.getKind()) {
case Token::bare_identifier:
return parseBareIdExpr(state);
case Token::integer:
return parseIntegerExpr(state);
case Token::l_paren:
return parseParentheticalExpr(state);
case Token::minus:
return parseNegateExpression(lhs, state);
default:
if (lhs)
emitError("missing right operand of binary op");
else
emitError("expected affine expression");
return nullptr;
}
}
/// Parse affine expressions that are bare-id's, integer constants,
/// parenthetical affine expressions, and affine op expressions that are a
/// composition of those.
@ -840,164 +919,94 @@ AffineHighPrecOp Parser::consumeIfHighPrecOp() {
///
/// {add, sub} have lower precedence than {mul, div, and mod}.
///
/// Add, sub'are themselves at the same precedence level. mul, div, and mod are
/// Add, sub'are themselves at the same precedence level, mul, div, and mod are
/// at the same higher precedence level.
///
/// llhs: the affine expression appearing on the left of the one being parsed.
/// This function will return ((llhs + lhs) + rhs) if llhs is non null, and
/// lhs + rhs otherwise; if there is no rhs, llhs + lhs is returned if llhs is
/// non-null; otherwise lhs is returned. This is to deal with left
/// This function will return ((llhs llhsOp lhs) op rhs) if llhs is non null,
/// and lhs op rhs otherwise; if there is no rhs, llhs llhsOp lhs is returned if
/// llhs is non-null; otherwise lhs is returned. This is to deal with left
/// associativity.
///
/// Eg: when the expression is e1 + e2*e3 + e4, with e1 as llhs, this function
/// will return the affine expr equivalent of (e1 + (e2*e3)) + e4.
///
// TODO(bondhugula): add support for unary op negation. Assuming for now that
// the op to associate with llhs is add.
ParseResult Parser::parseAffineLowPrecOpExpr(AffineExpr *llhs,
AffineLowPrecOp llhsOp,
const AffineMapParserState &state,
AffineExpr *&result) {
AffineExpr *lhs = nullptr;
if (parseAffineOperandExpr(state, lhs))
return ParseFailure;
/// will return the affine expr equivalent of (e1 + (e2*e3)) + e4, where (e2*e3)
/// will be parsed using parseAffineHighPrecOpExpr().
AffineExpr *
Parser::parseAffineLowPrecOpExpr(AffineExpr *llhs, AffineLowPrecOp llhsOp,
const AffineMapParserState &state) {
AffineExpr *lhs = parseAffineOperandExpr(llhs, state);
if (!lhs)
return nullptr;
// Found an LHS. Deal with the ops.
AffineLowPrecOp lOp;
AffineHighPrecOp rOp;
AffineHighPrecOp hOp;
if ((lOp = consumeIfLowPrecOp())) {
if (llhs) {
AffineExpr *sum = getBinaryAffineOpExpr(llhsOp, llhs, lhs);
AffineExpr *recSum = nullptr;
parseAffineLowPrecOpExpr(sum, lOp, state, recSum);
result = recSum ? recSum : sum;
return ParseSuccess;
return parseAffineLowPrecOpExpr(sum, lOp, state);
}
// No LLHS, get RHS and form the expression.
if (parseAffineLowPrecOpExpr(lhs, lOp, state, result)) {
if (!result)
emitError("missing right operand of add op");
return ParseFailure;
}
return ParseSuccess;
} else if ((rOp = consumeIfHighPrecOp())) {
return parseAffineLowPrecOpExpr(lhs, lOp, state);
}
if ((hOp = consumeIfHighPrecOp())) {
// We have a higher precedence op here. Get the rhs operand for the llhs
// through parseAffineHighPrecOpExpr.
AffineExpr *highRes = nullptr;
if (parseAffineHighPrecOpExpr(lhs, rOp, state, highRes)) {
// 'product' needs to be checked to prevent duplicate errors messages as
// the call stack unwinds. All of this due to left associativity.
if (!highRes)
emitError("missing right operand of binary op");
return ParseFailure;
}
AffineExpr *highRes = parseAffineHighPrecOpExpr(lhs, hOp, state);
if (!highRes)
return nullptr;
// If llhs is null, the product forms the first operand of the yet to be
// found expression. If non-null, assume for now that the op to associate
// with llhs is add.
// found expression. If non-null, the op to associate with llhs is llhsOp.
AffineExpr *expr =
llhs ? getBinaryAffineOpExpr(llhsOp, llhs, highRes) : highRes;
// Recurse for subsequent add's after the affine mul expression
AffineLowPrecOp nextOp = consumeIfLowPrecOp();
if (nextOp) {
AffineExpr *sumProd = nullptr;
parseAffineLowPrecOpExpr(expr, nextOp, state, sumProd);
result = sumProd ? sumProd : expr;
} else {
result = expr;
}
return ParseSuccess;
} else {
// Last operand in the expression list.
if (llhs) {
result = getBinaryAffineOpExpr(llhsOp, llhs, lhs);
return ParseSuccess;
}
// No llhs, 'lhs' itself is the expression.
result = lhs;
return ParseSuccess;
}
}
/// Parse an affine expression inside parentheses.
/// affine-expr ::= `(` affine-expr `)`
AffineExpr *Parser::parseParentheticalExpr(const AffineMapParserState &state) {
if (!consumeIf(Token::l_paren)) {
return nullptr;
}
auto *expr = parseAffineExpr(state);
if (!consumeIf(Token::r_paren)) {
emitError("expected ')'");
return nullptr;
}
if (!expr)
emitError("no expression inside parentheses");
return expr;
}
/// Parse a bare id that may appear in an affine expression.
/// affine-expr ::= bare-id
AffineExpr *Parser::parseBareIdExpr(const AffineMapParserState &state) {
if (curToken.is(Token::bare_identifier)) {
StringRef sRef = curToken.getSpelling();
const auto &dims = state.getDims();
const auto &symbols = state.getSymbols();
if (dims.count(sRef)) {
consumeToken(Token::bare_identifier);
return AffineDimExpr::get(dims.lookup(sRef), builder.getContext());
}
if (symbols.count(sRef)) {
consumeToken(Token::bare_identifier);
return AffineSymbolExpr::get(symbols.lookup(sRef), builder.getContext());
}
return emitError("identifier is neither dimensional nor symbolic"), nullptr;
}
return nullptr;
}
/// Parse an integral constant appearing in an affine expression.
/// affine-expr ::= `-`? integer-literal
/// TODO(bondhugula): handle negative numbers.
AffineExpr *Parser::parseIntegerExpr(const AffineMapParserState &state) {
if (curToken.is(Token::integer)) {
auto *expr = AffineConstantExpr::get(
curToken.getUnsignedIntegerValue().getValue(), builder.getContext());
consumeToken(Token::integer);
// Recurse for subsequent low prec op's after the affine high prec op
// expression.
AffineLowPrecOp nextOp;
if ((nextOp = consumeIfLowPrecOp()))
return parseAffineLowPrecOpExpr(expr, nextOp, state);
return expr;
}
return nullptr;
// Last operand in the expression list.
if (llhs)
return getBinaryAffineOpExpr(llhsOp, llhs, lhs);
// No llhs, 'lhs' itself is the expression.
return lhs;
}
/// Parse an affine expression.
/// affine-expr ::= `(` affine-expr `)`
/// | affine-expr `+` affine-expr
/// | affine-expr `-` affine-expr
/// | `-`? integer-literal `*` affine-expr
/// | `ceildiv` `(` affine-expr `,` integer-literal `)`
/// | `floordiv` `(` affine-expr `,` integer-literal `)`
/// | affine-expr `mod` integer-literal
/// | bare-id
/// | `-`? integer-literal
/// Use 'state' to check if valid identifiers appear.
// TODO(bondhugula): check if mul, mod, div take integral constants
/// affine-expr ::= `(` affine-expr `)`
/// | `-` affine-expr
/// | affine-expr `+` affine-expr
/// | affine-expr `-` affine-expr
/// | affine-expr `*` affine-expr
/// | affine-expr `floordiv` affine-expr
/// | affine-expr `ceildiv` affine-expr
/// | affine-expr `mod` affine-expr
/// | bare-id
/// | integer-literal
///
/// Additional conditions are checked depending on the production. For eg., one
/// of the operands for `*` has to be either constant/symbolic; the second
/// operand for floordiv, ceildiv, and mod has to be a positive integer.
/// Use 'state' to check if valid identifiers appear in the expressoins.
AffineExpr *Parser::parseAffineExpr(const AffineMapParserState &state) {
switch (curToken.getKind()) {
case Token::l_paren:
case Token::bare_identifier:
case Token::minus:
case Token::integer:
return parseAffineLowPrecOpExpr(nullptr, AffineLowPrecOp::LNoOp, state);
case Token::kw_ceildiv:
case Token::kw_floordiv:
case Token::bare_identifier:
case Token::integer: {
AffineExpr *result = nullptr;
parseAffineLowPrecOpExpr(nullptr, AffineLowPrecOp::LNoOp, state, result);
return result;
}
case Token::kw_mod:
case Token::plus:
case Token::minus:
case Token::star:
emitError("left operand of binary op missing");
return nullptr;
default:
emitError("expected affine expression");
return nullptr;
}
}
@ -1045,11 +1054,12 @@ ParseResult Parser::parseDimIdList(AffineMapParserState &state) {
/// Parse an affine map definition.
///
/// affine-map-inline ::= dim-and-symbol-id-lists `->` multi-dim-affine-expr
/// ( `size` `(` dim-size (`,` dim-size)* `)` )?
/// dim-size ::= affine-expr | `min` `(` affine-expr ( `,` affine-expr)+ `)`
/// affine-map-inline ::= dim-and-symbol-id-lists `->` multi-dim-affine-expr
/// (`size` `(` dim-size (`,` dim-size)* `)`)?
/// dim-size ::= affine-expr | `min` `(` affine-expr ( `,` affine-expr)+ `)`
///
/// multi-dim-affine-expr ::= `(` affine-expr (`,` affine-expr)* `)
/// multi-dim-affine-expr ::= `(` affine-expr (`,` affine-expr)* `)
// TODO(bondhugula): parse range size information.
AffineMap *Parser::parseAffineMapInline(StringRef mapId) {
AffineMapParserState state;

View File

@ -3,62 +3,105 @@
; Check different error cases.
; -----
#hello_world1 = (i, j) -> ((), j) ; expected-error {{no expression inside parentheses}}
#hello_world = (i, j) -> ((), j) ; expected-error {{no expression inside parentheses}}
; -----
#hello_world2 (i, j) [s0] -> (i, j) ; expected-error {{expected '=' in affine map outlined definition}}
#hello_world = (i, j) -> (->, j) ; expected-error {{expected affine expression}}
; -----
#hello_world3a = (i, j) [s0] -> (2*i*, 3*j*i*2 + 5) ; expected-error {{missing right operand of multiply op}}
#hello_world = (i, j) -> (:) ; expected-error {{expected affine expression}}
; -----
#hello_world3b = (i, j) [s0] -> (i+, i+j+2 + 5) ; expected-error {{missing right operand of add op}}
#hello_world = (i, j) -> (, j) ; expected-error {{expected affine expression}}
; -----
#hello_world4 = (i, j) [s0] -> ((s0 + i, j) ; expected-error {{expected ')'}}
#hello_world (i, j) [s0] -> (i, j) ; expected-error {{expected '=' in affine map outlined definition}}
; -----
#hello_world5 = (i, j) [s0] -> ((s0 + i, j) ; expected-error {{expected ')'}}
#hello_world = (i, j) [s0] -> (2*i*, 3*j*i*2 + 5) ; expected-error {{missing right operand of binary op}}
; -----
#hello_world6 = (i, j) [s0] -> (((s0 + (i + j) + 5), j) ; expected-error {{expected ')'}}
#hello_world = (i, j) [s0] -> (i+, i+j+2 + 5) ; expected-error {{missing right operand of binary op}}
; -----
#hello_world8 = (i, j) [s0] -> i + s0, j) ; expected-error {{expected '(' at start of affine map range}}
#hello_world = (i, j) [s0] -> ((s0 + i, j) ; expected-error {{expected ')'}}
; -----
#hello_world9 = (i, j) [s0] -> (x) ; expected-error {{identifier is neither dimensional nor symbolic}}
#hello_world = (i, j) [s0] -> (((s0 + (i + j) + 5), j) ; expected-error {{expected ')'}}
; -----
#hello_world10 = (i, j, i) [s0] -> (i) ; expected-error {{dimensional identifier name reused}}
#hello_world = (i, j) [s0] -> i + s0, j) ; expected-error {{expected '(' at start of affine map range}}
; -----
#hello_world11 = (i, j) [s0, s1, s0] -> (i) ; expected-error {{symbolic identifier name reused}}
#hello_world = (i, j) [s0] -> (x) ; expected-error {{identifier is neither dimensional nor symbolic}}
; -----
#hello_world12 = (i, j) [i, s0] -> (j) ; expected-error {{dimensional identifier name reused}}
#hello_world = (i, j, i) [s0] -> (i) ; expected-error {{dimensional identifier name reused}}
; -----
#hello_world13 = (i, j) [s0, s1] -> () ; expected-error {{expected list element}}
#hello_world = (i, j) [s0, s1, s0] -> (i) ; expected-error {{symbolic identifier name reused}}
; -----
#hello_world14 = (i, j) [s0, s1] -> (+i, j) ; expected-error {{left operand of binary op missing}}
#hello_world = (i, j) [i, s0] -> (j) ; expected-error {{dimensional identifier name reused}}
; -----
#hello_world15 = (i, j) [s0, s1] -> (i, *j+5) ; expected-error {{left operand of binary op missing}}
#hello_world = (i, j) [s0, s1] -> () ; expected-error {{expected list element}}
;-----
; -----
#hello_world = (i, j) [s0, s1] -> (+i, j) ; expected-error {{left operand of binary op missing}}
#hello_world22 = (i, j) -> (i, 3*d0 + j)
; expected-error@-1 {{identifier is neither dimensional nor symbolic}}
; expected-error@-2 {{missing right operand of binary op}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, *j) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (floordiv i 2, j) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (ceildiv i 2, j) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (mod i 2, j) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (-(), j)
; expected-error@-1 {{no expression inside parentheses}}
; expected-error@-2 {{missing operand of negation}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, *j+5) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, floordiv j+5) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, ceildiv j+5) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, mod j+5) ; expected-error {{left operand of binary op missing}}
; -----
#hello_world = (i, j) [s0, s1] -> (i*j, j) ; expected-error {{non-affine expression: at least one of the multiply operands has to be either a constant or symbolic}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, j + j ceildiv 128 mod 16 * i - 4) ; expected-error {{non-affine expression: at least one of the multiply operands has to be either a constant or symbolic}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, j floordiv i) ; expected-error {{non-affine expression: right operand of floordiv has to be either a constant or symbolic}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, i*2 ceildiv j*5) ; expected-error {{non-affine expression: right operand of ceildiv has to be either a constant or symbolic}}
; -----
#hello_world = (i, j) [s0, s1] -> (i, i mod (2+i)) ; expected-error {{non-affine expression: right operand of mod has to be either a constant or symbolic}}
; -----
#hello_world = (i, j) [s0, s1] -> (-1*i j, j) ; expected-error {{expected ',' or ')'}}
; -----
#hello_world = (i, j) -> (i, 3*d0 + ) ; expected-error {{identifier is neither dimensional nor symbolic}}
; TODO(bondhugula): Add more tests; coverage of error messages emitted not complete
; -----
#ABC = (i,j) -> (i+j)
#ABC = (i,j) -> (i+j) // expected-error {{redefinition of affine map id 'ABC'}}
#ABC = (i,j) -> (i+j) ; expected-error {{redefinition of affine map id 'ABC'}}

View File

@ -51,8 +51,8 @@
; CHECK: #{{[0-9]+}} = (d0, d1) [s0] -> (d0, 0)
#hello_world16 = (i, j) [s1] -> (i, 0)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0] -> (d0, (d0 * d1))
#hello_world17 = (i, j) [s1] -> (i, i*j)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0] -> (d0, (s0 * d1))
#hello_world17 = (i, j) [s0] -> (i, s0*j)
; CHECK: #{{[0-9]+}} = (d0, d1) -> (d0, ((3 * d0) + d1))
#hello_world19 = (i, j) -> (i, 3*i + j)
@ -60,8 +60,8 @@
; CHECK: #{{[0-9]+}} = (d0, d1) -> (d0, (d0 + (3 * d1)))
#hello_world20 = (i, j) -> (i, i + 3*j)
; CHECK: #{{[0-9]+}} = (d0, d1) -> (d0, ((2 + (((d1 * d0) * 9) * d0)) + 1))
#hello_world18 = (i, j) -> (i, 2 + j*i*9*i + 1)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0] -> (d0, ((2 + (((s0 * s0) * 9) * d0)) + 1))
#hello_world18 = (i, j) [N] -> (i, 2 + N*N*9*i + 1)
; CHECK: #{{[0-9]+}} = (d0, d1) -> (1, ((d0 + (3 * d1)) + 5))
#hello_world21 = (i, j) -> (1, i + 3*j + 5)
@ -81,11 +81,23 @@
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (d0, (d1 ceildiv 5))
#hello_world26 = (i, j) [s0, s1] -> (i, j ceildiv 5)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (d0, ((d1 + (((d1 ceildiv 128) mod 16) * d0)) - 4))
#hello_world27 = (i, j) [s0, s1] -> (i, j + j ceildiv 128 mod 16 * i - 4)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (d0, ((d0 - d1) - 5))
#hello_world29 = (i, j) [s0, s1] -> (i, i - j - 5)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (d0, ((d0 - (d0 * d1)) + 2))
#hello_world30 = (i, j) [s0, s1] -> (i, i - i*j + 2)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (d0, ((d0 - (s1 * d1)) + 2))
#hello_world30 = (i, j) [M, N] -> (i, i - N*j + 2)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (((-1 * 5) * d0), ((-1 * 3) * d1), (-1 * 2), ((-1 * 1) * (d0 + d1)), ((-1 * 1) * s0))
#hello_world32 = (i, j) [s0, s1] -> (-5*i, -3*j, -2, -1*(i+j), -1*s0)
; CHECK: #{{[0-9]+}} = (d0, d1) -> ((((-1 * 2) + (-1 * 5)) - (-1 * 3)), ((-1 * 1) * d0))
#hello_world33 = (i, j) -> (-2+-5-(-3), -1*i)
; CHECK: #{{[0-9]+}} = (d0, d1) [s0, s1] -> (d0, (d1 floordiv s0), (d1 mod s0))
#hello_world34 = (i, j) [s0, s1] -> (i, j floordiv s0, j mod s0)
; CHECK: #{{[0-9]+}} = (d0, d1, d2) [s0, s1, s2] -> (((((d0 * s1) * s2) + (d1 * s1)) + d2))
#hello_world35 = (i, j, k) [s0, s1, s2] -> (i*s1*s2 + j*s1 + k)
; CHECK: #{{[0-9]+}} = (d0, d1) -> (2, 8)
#hello_world36 = (i, j) -> (1+1, 5+3)