Files
paddlepaddle--paddle/paddle/cinn/optim/simplify_util.cc
T
2026-07-13 12:40:42 +08:00

905 lines
30 KiB
C++

// Copyright (c) 2025 CINN Authors. All Rights Reserved.
//
// 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.
#include "paddle/cinn/optim/simplify_util.h"
#include <algorithm>
#include <stack>
#include <unordered_set>
#include "paddle/cinn/common/const_fold.h"
#include "paddle/cinn/common/shape_constraint.h"
#include "paddle/cinn/common/simplify_special_pattern.h"
#include "paddle/cinn/ir/ir_mutator.h"
#include "paddle/cinn/ir/ir_printer.h"
#include "paddle/cinn/ir/op/ir_operators.h"
#include "paddle/cinn/ir/utils/ir_compare.h"
#include "paddle/cinn/ir/utils/ir_copy.h"
#include "paddle/common/enforce.h"
namespace cinn {
namespace optim {
int ComparePriority(const ir::IndexExpr &lhs, const ir::IndexExpr &rhs) {
if (lhs.node_type() == ir::IrNodeTy::IntImm &&
rhs.node_type() != ir::IrNodeTy::IntImm)
return -1;
if (rhs.node_type() == ir::IrNodeTy::IntImm &&
lhs.node_type() != ir::IrNodeTy::IntImm)
return 1;
if (auto lhsVar = lhs.As<ir::_Var_>()) {
if (auto rhsVar = rhs.As<ir::_Var_>()) {
if (std::make_tuple(lhsVar->name.length(), lhsVar->name) <
std::make_tuple(rhsVar->name.length(), rhsVar->name))
return 1;
else if (std::make_tuple(lhsVar->name.length(), lhsVar->name) ==
std::make_tuple(rhsVar->name.length(), rhsVar->name))
return 0;
else
return -1;
}
}
auto lhsLen = lhs.length();
auto rhsLen = rhs.length();
if (lhsLen < rhsLen) {
return -1;
} else if (lhsLen == rhsLen) {
// Add < Mul < Div < Mod < Min < Max < Cast < Load.
if (lhs.node_type() < rhs.node_type())
return 1;
else if (lhs.node_type() == rhs.node_type())
return 0;
else
return -1;
} else {
return 1;
}
}
bool SortComparePriority(const ir::IndexExpr &lhs, const ir::IndexExpr &rhs) {
return ComparePriority(lhs, rhs) > 0;
}
bool IsSumPartialBySymbol(const ir::IndexExpr &expr,
const ir::IndexExpr &symbol) {
if (expr == symbol) return true;
// TODO(liujinnan): Check Ty
switch (expr.node_type()) {
case ir::IrNodeTy::IntImm: {
return false;
}
case ir::IrNodeTy::_Var_:
return expr == symbol;
case ir::IrNodeTy::Add:
return IsSumPartialBySymbol(expr.operand(0), symbol) ||
IsSumPartialBySymbol(expr.operand(1), symbol);
case ir::IrNodeTy::Mul: {
if (expr.operand(1).is_constant() && expr.operand(1).get_constant() == -1)
return IsSumPartialBySymbol(expr.operand(0), symbol);
else
return expr.operand(0) == symbol || expr.operand(1) == symbol;
}
case ir::IrNodeTy::Div: {
return IsSumPartialBySymbol(expr.operand(0), symbol);
}
case ir::IrNodeTy::Mod:
case ir::IrNodeTy::Min:
case ir::IrNodeTy::Max:
case ir::IrNodeTy::Load:
case ir::IrNodeTy::Cast:
return false;
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of expr in IsSumPartialBySymbol which is: %s",
expr));
}
}
ir::IndexExpr SimplifySymbolicAdd(const ir::IndexExpr &lhs,
const ir::IndexExpr &sym,
const ir::IndexExpr &outer_mul_factor) {
if (lhs == sym) return sym * (outer_mul_factor + ir::IndexExpr(1));
switch (lhs.node_type()) {
case ir::IrNodeTy::IntImm: {
auto imm = lhs.As<ir::IntImm>();
if (imm->value != 0)
PADDLE_THROW(::common::errors::Fatal("Error in SimplifySymbolicAdd!"));
return ir::IndexExpr(0);
}
case ir::IrNodeTy::_Var_: {
return sym * (outer_mul_factor + ir::IndexExpr(1));
}
case ir::IrNodeTy::Add: {
if (!IsSumPartialBySymbol(lhs.operand(0), sym))
return lhs.operand(0) +
SimplifySymbolicAdd(lhs.operand(1), sym, outer_mul_factor);
return SimplifySymbolicAdd(lhs.operand(0), sym, outer_mul_factor) +
lhs.operand(1);
}
case ir::IrNodeTy::Mul: {
if (lhs.operand(1).is_constant() && lhs.operand(1).as_int64() == -1) {
return SimplifySymbolicAdd(lhs.operand(0), sym, -outer_mul_factor) *
lhs.operand(1);
}
if (lhs.operand(0) == sym)
return lhs.operand(0) * (lhs.operand(1) + outer_mul_factor);
return (lhs.operand(0) + outer_mul_factor) * lhs.operand(1);
}
case ir::IrNodeTy::Mod:
PADDLE_THROW(::common::errors::Fatal("Error in SimplifySymbolicAdd!"));
case ir::IrNodeTy::Div: {
return SimplifySymbolicAdd(
lhs.operand(0), sym, lhs.operand(1) * outer_mul_factor) /
lhs.operand(1);
}
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of lhs in SimplifySymbolicAdd which is: %s", lhs));
}
}
bool IsDivisibleBySymbol(const ir::IndexExpr &expr,
const ir::IndexExpr &symbol,
const ir::IrNodeTy &ty) {
if (expr == symbol) return true;
// TODO(liujinnan): Check Ty
switch (expr.node_type()) {
case ir::IrNodeTy::IntImm: {
auto imm = expr.As<ir::IntImm>();
return imm->value == 0;
}
case ir::IrNodeTy::_Var_:
return expr == symbol;
case ir::IrNodeTy::Add:
return IsDivisibleBySymbol(expr.operand(0), symbol, ty) &&
IsDivisibleBySymbol(expr.operand(1), symbol, ty);
case ir::IrNodeTy::Mul:
// Because (S0 / 7 * 100) / S0 is not divisible by S0, so we push
// `expr.node_type()` into third parameter.
return IsDivisibleBySymbol(expr.operand(0), symbol, expr.node_type()) ||
IsDivisibleBySymbol(expr.operand(1), symbol, expr.node_type());
case ir::IrNodeTy::Mod:
// Because S0 % 3 + S0 % 5 is not divisible by S0, so we push
// `expr.node_type()` into third parameter.
return IsDivisibleBySymbol(expr.operand(0), symbol, expr.node_type()) &&
IsDivisibleBySymbol(expr.operand(1), symbol, expr.node_type());
case ir::IrNodeTy::Div: {
if (ty != expr.node_type()) return false;
return IsDivisibleBySymbol(expr.operand(0), symbol, expr.node_type());
}
case ir::IrNodeTy::Min:
case ir::IrNodeTy::Max:
case ir::IrNodeTy::Load:
case ir::IrNodeTy::Cast:
return false;
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of expr in IsDivisibleBySymbol which is: %s",
expr));
}
}
ir::IndexExpr SimplifySymbolicDivide(const ir::IndexExpr &lhs,
const ir::IndexExpr &sym,
const ir::IrNodeTy &ty) {
if (lhs == sym) return ir::IndexExpr(1);
switch (lhs.node_type()) {
case ir::IrNodeTy::IntImm: {
auto imm = lhs.As<ir::IntImm>();
if (imm->value != 0)
PADDLE_THROW(
::common::errors::Fatal("Error in SimplifySymbolicDivide!"));
return ir::IndexExpr(0);
}
case ir::IrNodeTy::_Var_:
return ir::IndexExpr(1);
case ir::IrNodeTy::Add:
return SimplifySymbolicDivide(lhs.operand(0), sym, ty) +
SimplifySymbolicDivide(lhs.operand(1), sym, ty);
case ir::IrNodeTy::Mul: {
if (!IsDivisibleBySymbol(lhs.operand(0), sym, ty))
return lhs.operand(0) * SimplifySymbolicDivide(lhs.operand(1), sym, ty);
return SimplifySymbolicDivide(lhs.operand(0), sym, ty) * lhs.operand(1);
}
case ir::IrNodeTy::Mod:
return SimplifySymbolicDivide(lhs.operand(0), sym, lhs.node_type()) %
SimplifySymbolicDivide(lhs.operand(1), sym, lhs.node_type());
case ir::IrNodeTy::Div: {
return SimplifySymbolicDivide(lhs.operand(0), sym, lhs.node_type()) /
lhs.operand(1);
}
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of lhs in SimplifySymbolicDivide which is: %s",
lhs));
}
}
bool ProveDivisible(const ir::IndexExpr &lhs, const ir::IndexExpr &rhs) {
if (IsZero(lhs % rhs)) return true;
if (IsZero(optim::ArithSimplify(lhs % rhs))) return true;
return false;
}
bool IsNegatedIndexExpr(const ir::IndexExpr &candidate,
ir::IndexExpr &expr) { // NOLINT
if (auto mul = candidate.As<ir::Mul>()) {
if (mul->b().is_constant() && mul->b().get_constant() == -1) {
expr = mul->a();
return true;
}
}
return false;
}
ir::IndexExpr::IndexType VerifyIndex(const ir::Expr &expr) {
switch (expr.node_type()) {
case ir::IrNodeTy::_Var_: {
if (expr.type().is_index_type()) {
return expr.as_var()->is_let_symbol ? ir::IndexExpr::IndexType::kLoad
: ir::IndexExpr::IndexType::kValid;
} else {
return ir::IndexExpr::IndexType::kInvalid;
}
}
case ir::IrNodeTy::IntImm: {
return expr.type().is_index_type() ? ir::IndexExpr::IndexType::kValid
: ir::IndexExpr::IndexType::kInvalid;
}
case ir::IrNodeTy::Load: {
if (!expr.type().is_index_type())
return ir::IndexExpr::IndexType::kInvalid;
auto load = expr.As<ir::Load>();
for (const auto &indices : load->indices) {
if (VerifyIndex(indices) == ir::IndexExpr::IndexType::kInvalid)
return ir::IndexExpr::IndexType::kInvalid;
}
return ir::IndexExpr::IndexType::kLoad;
}
case ir::IrNodeTy::Cast: {
ir::IndexExpr::IndexType result = VerifyIndex(expr->operand(0));
return result != ir::IndexExpr::IndexType::kInvalid &&
expr.type().is_index_type()
? ir::IndexExpr::IndexType::kCast
: ir::IndexExpr::IndexType::kInvalid;
}
case ir::IrNodeTy::Add:
case ir::IrNodeTy::Sub:
case ir::IrNodeTy::Mul:
case ir::IrNodeTy::Div:
case ir::IrNodeTy::Mod:
case ir::IrNodeTy::Max:
case ir::IrNodeTy::Min: {
ir::IndexExpr::IndexType left = VerifyIndex(expr->operand(0));
ir::IndexExpr::IndexType right = VerifyIndex(expr->operand(1));
if (left == ir::IndexExpr::IndexType::kInvalid ||
right == ir::IndexExpr::IndexType::kInvalid)
return ir::IndexExpr::IndexType::kInvalid;
return std::max(left, right);
}
}
return ir::IndexExpr::IndexType::kInvalid;
}
ir::IndexExpr ConstructIndexExprByNodeType(const ir::IrNodeTy &ty,
const ir::IndexExpr &lhs,
const ir::IndexExpr &rhs,
bool simplify_flag) {
switch (ty) {
case ir::IrNodeTy::Add:
return simplify_flag ? lhs + rhs : ir::Add::Make(lhs, rhs);
case ir::IrNodeTy::Sub:
return simplify_flag
? lhs - rhs
: ir::Add::Make(lhs, ir::Mul::Make(rhs, ir::IndexExpr(-1)));
case ir::IrNodeTy::Mul:
return simplify_flag ? lhs * rhs : ir::Mul::Make(lhs, rhs);
case ir::IrNodeTy::Div:
return simplify_flag ? lhs / rhs : ir::Div::Make(lhs, rhs);
case ir::IrNodeTy::Mod:
return simplify_flag ? lhs % rhs : ir::Mod::Make(lhs, rhs);
case ir::IrNodeTy::Min:
return ir::Min::Make(lhs, rhs);
case ir::IrNodeTy::Max:
return ir::Max::Make(lhs, rhs);
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type in Constructir::IndexExprByNodeType, which is: %s",
ty));
}
}
ir::IndexExpr ChangeSeqOfDivMod(const ir::IndexExpr &expr) {
switch (expr.node_type()) {
case ir::IrNodeTy::IntImm:
case ir::IrNodeTy::_Var_:
case ir::IrNodeTy::Cast:
case ir::IrNodeTy::Load: {
return expr;
}
case ir::IrNodeTy::Add:
case ir::IrNodeTy::Sub:
case ir::IrNodeTy::Mul:
case ir::IrNodeTy::Min:
case ir::IrNodeTy::Max:
case ir::IrNodeTy::Div: {
auto lhs = ChangeSeqOfDivMod(expr.operand(0));
auto rhs = ChangeSeqOfDivMod(expr.operand(1));
return ConstructIndexExprByNodeType(expr.node_type(), lhs, rhs, false);
}
case ir::IrNodeTy::Mod: {
if (expr.operand(0).node_type() == ir::IrNodeTy::Div) {
auto div_lhs = ChangeSeqOfDivMod(expr.operand(0).operand(0));
auto div_rhs = ChangeSeqOfDivMod(expr.operand(0).operand(1));
auto mod_rhs = ChangeSeqOfDivMod(expr.operand(1));
return div_lhs % (div_rhs * mod_rhs) / div_rhs;
} else {
auto lhs = ChangeSeqOfDivMod(expr.operand(0));
auto rhs = ChangeSeqOfDivMod(expr.operand(1));
if (lhs.node_type() == ir::IrNodeTy::Div) {
return (lhs.operand(0) % (lhs.operand(1) * rhs)) / lhs.operand(1);
}
return ConstructIndexExprByNodeType(expr.node_type(), lhs, rhs, false);
}
}
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of expr in ChangeSeqOfDivMod which is: %s", expr));
}
}
std::optional<ir::IndexExpr> DivByPartMul(const ir::IndexExpr &lhs,
const ir::IndexExpr &rhs,
ir::IrNodeTy ty) {
std::vector<ir::IndexExpr> elems = GetFlattenExprs<ir::Mul>(rhs);
ir::IndexExpr result = ir::ir_utils::IRCopy(lhs);
for (const auto &elem : elems) {
if (IsDivisibleBySymbol(result, elem, ty)) {
result = SimplifySymbolicDivide(result, elem, ty);
} else {
return std::nullopt;
}
}
return result;
}
std::optional<ir::IndexExpr> SimplifyComplexMod(const ir::IndexExpr &lhs,
const ir::IndexExpr &rhs) {
if (lhs == rhs) return ir::IndexExpr(lhs.type(), 0);
switch (lhs.node_type()) {
case ir::IrNodeTy::Add: {
auto simplify_lhs = SimplifyComplexMod(lhs.operand(0), rhs);
auto simplify_rhs = SimplifyComplexMod(lhs.operand(1), rhs);
if (simplify_lhs.has_value() && simplify_rhs.has_value())
return (simplify_lhs.value() + simplify_rhs.value());
return std::nullopt;
}
case ir::IrNodeTy::Mul: {
// (S0 % 4 * S1 % 8) % 4 != S0 % 4 * S1 % 4;
if (DivByPartMul(lhs, rhs, ir::IrNodeTy::Mod))
return ir::IndexExpr(lhs.type(), 0);
return std::nullopt;
}
case ir::IrNodeTy::Div:
case ir::IrNodeTy::IntImm:
case ir::IrNodeTy::_Var_:
case ir::IrNodeTy::Min:
case ir::IrNodeTy::Max:
case ir::IrNodeTy::Load:
case ir::IrNodeTy::Cast: {
return std::nullopt;
}
case ir::IrNodeTy::Mod: {
if (DivByPartMul(lhs.operand(1), rhs, ir::IrNodeTy::Mod)) {
return lhs.operand(0) % rhs;
}
return std::nullopt;
}
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of expr in SimplifyComplexMod which is: %s", lhs));
}
return std::nullopt;
}
bool CheckPattern(const ir::IndexExpr &expr,
const ir::IndexExpr &pattern,
std::unordered_map<std::string, ir::IndexExpr> *map) {
// pattern may include Var to match any expr.
if (expr.node_type() != pattern.node_type() &&
pattern.node_type() != ir::IrNodeTy::_Var_)
return false;
switch (pattern.node_type()) {
case ir::IrNodeTy::Add:
case ir::IrNodeTy::Sub:
case ir::IrNodeTy::Mul:
case ir::IrNodeTy::Div:
case ir::IrNodeTy::Mod:
case ir::IrNodeTy::Min:
case ir::IrNodeTy::Max: {
return CheckPattern(expr.operand(0), pattern.operand(0), map) &&
CheckPattern(expr.operand(1), pattern.operand(1), map);
}
case ir::IrNodeTy::_Var_: {
auto it = map->find(pattern.As<ir::_Var_>()->name);
if (it != map->end()) {
return expr == it->second;
} else {
map->insert(std::make_pair(pattern.As<ir::_Var_>()->name, expr));
return true;
}
}
case ir::IrNodeTy::IntImm: {
return expr.As<ir::IntImm>()->value == pattern.As<ir::IntImm>()->value;
}
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Unsupported type of expr in CheckPattern which is: %s", expr));
}
return false;
}
bool IsPureMath(Expr expr) {
std::set<ir::IrNodeTy> valid_node_tys({
ir::IrNodeTy ::_Var_,
ir::IrNodeTy ::IntImm,
ir::IrNodeTy ::Sum,
ir::IrNodeTy ::Product,
ir::IrNodeTy ::FracOp,
ir::IrNodeTy ::FloatImm,
ir::IrNodeTy ::Add,
ir::IrNodeTy ::Sub,
ir::IrNodeTy ::Div,
ir::IrNodeTy ::Mul,
ir::IrNodeTy::Mod,
ir::IrNodeTy ::Minus,
});
auto complex_nodes = ir::ir_utils::CollectIRNodes(expr, [&](const Expr *n) {
return !valid_node_tys.count(n->node_type());
});
#ifdef CINN_DEBUG
for (auto &node : complex_nodes) {
VLOG(3) << "Found " << node->node_type() << " " << Expr(node);
}
#endif
return complex_nodes.empty();
}
/*!
* \brief Index Token in Tokenizer and Parser
*/
struct IndexToken {
enum class TokenType {
kNumber,
kVar,
kPlus,
kMinus,
kMultiply,
kDivide,
kModulo,
kLeftParen,
kRightParen,
kEnd
};
TokenType type;
std::string value;
explicit IndexToken(TokenType t, const std::string &v = "")
: type(t), value(v) {}
};
/*!
* \brief Tokenizer for IndexExpr, split the input string into IndexToken.
*/
class Tokenizer {
public:
explicit Tokenizer(const std::string &in) : input(in), pos(0) {}
// generate IndexToken for the next `pos`. it supports the following:
// 1. Number: 123, 1234...
// 2. Variable: a, b, a_1, aa, f1...
// 3. Operator: +, -, *, /, %, (, )
// 4. Whitespace
IndexToken NextToken() {
// skip whitespace
while (pos < input.size() && std::isspace(input[pos])) {
pos++;
}
// check if we reached the end of the input
if (pos >= input.size()) {
return IndexToken(IndexToken::TokenType::kEnd);
}
char c = input[pos++];
// deal with number (0, 1, 11, 123...) not support float.
if (std::isdigit(c)) {
std::string num;
num += c;
while (pos < input.size() && std::isdigit(input[pos])) {
num += input[pos++];
}
return IndexToken(IndexToken::TokenType::kNumber, num);
}
// deal with variable name (a, b, a1, a123, a_1...).
if (std::isalpha(c) || input[pos] == '_') {
std::string var;
var += c;
while (pos < input.size() &&
(std::isalnum(input[pos]) || input[pos] == '_')) {
var += input[pos++];
}
return IndexToken(IndexToken::TokenType::kVar, var);
}
// deal with operator {+, -, *, /, %, '(', ')'}.
switch (c) {
case '+':
return IndexToken(IndexToken::TokenType::kPlus);
case '-':
return IndexToken(IndexToken::TokenType::kMinus);
case '*':
return IndexToken(IndexToken::TokenType::kMultiply);
case '/':
return IndexToken(IndexToken::TokenType::kDivide);
case '%':
return IndexToken(IndexToken::TokenType::kModulo);
case '(':
return IndexToken(IndexToken::TokenType::kLeftParen);
case ')':
return IndexToken(IndexToken::TokenType::kRightParen);
default:
PADDLE_THROW(::common::errors::InvalidArgument(
"Tokenizer Unexpected character: %s", c));
}
}
private:
const std::string &input;
size_t pos;
};
/*!
* \brief Parser for IndexExpr, parse the input string into ir::Expr.
*/
class Parser {
public:
explicit Parser(const std::string &input)
: tokenizer(input), currentToken(tokenizer.NextToken()) {}
ir::Expr Parse() { return ParseExpression(); }
private:
void Advance() { currentToken = tokenizer.NextToken(); }
// Processing addition and subtraction expressions, with the lowest priority.
ir::Expr ParseExpression() {
auto left = ParseTerm();
while (currentToken.type == IndexToken::TokenType::kPlus ||
currentToken.type == IndexToken::TokenType::kMinus) {
auto op = currentToken.type;
Advance();
auto right = ParseTerm();
if (op == IndexToken::TokenType::kPlus) {
left = ir::Add::Make(left, right);
} else {
left = ir::Sub::Make(left, right);
}
}
return left;
}
// Process multiplication, division and modulo expressions, with higher
// priority than addition and subtraction, and the parsing result appears as
// one Term. e.g. a * b + c, a * b is a Term.
ir::Expr ParseTerm() {
auto left = ParseFactor();
while (currentToken.type == IndexToken::TokenType::kMultiply ||
currentToken.type == IndexToken::TokenType::kDivide ||
currentToken.type == IndexToken::TokenType::kModulo) {
auto op = currentToken.type;
Advance();
auto right = ParseFactor();
if (op == IndexToken::TokenType::kMultiply) {
left = ir::Mul::Make(left, right);
} else if (op == IndexToken::TokenType::kDivide) {
left = ir::Div::Make(left, right);
} else {
left = ir::Mod::Make(left, right);
}
}
return left;
}
// Process numeric, variables and brackets, with the highest priority, as
// parameters for each item.
ir::Expr ParseFactor() {
if (currentToken.type == IndexToken::TokenType::kNumber) {
int value = std::stoi(currentToken.value);
Advance();
return ir::Expr(value);
} else if (currentToken.type == IndexToken::TokenType::kVar) {
auto var_name = currentToken.value;
Advance();
return GetOrCreateVar(var_name);
} else if (currentToken.type == IndexToken::TokenType::kLeftParen) {
Advance();
auto expr = ParseExpression();
if (currentToken.type != IndexToken::TokenType::kRightParen) {
PADDLE_THROW(::common::errors::InvalidArgument(
"Parser Expected ')', because of '(' in before."));
}
Advance();
return expr;
} else {
PADDLE_THROW(
::common::errors::InvalidArgument("Parser Unexpected IndexToken"));
}
}
ir::Expr GetOrCreateVar(const std::string &var_name) {
if (vars.find(var_name) == vars.end()) {
vars[var_name] = ir::Var(var_name);
}
return vars[var_name];
}
Tokenizer tokenizer;
IndexToken currentToken;
std::unordered_map<std::string, ir::Var> vars;
};
ir::Expr ParseExpressionFromString(const std::string &expr_str) {
thread_local static std::unordered_map<std::string, ir::Expr> cache;
auto it = cache.find(expr_str);
if (it != cache.end()) {
return it->second;
}
Parser parser(expr_str);
auto result = parser.Parse();
cache[expr_str] = result;
return result;
}
std::optional<std::unordered_map<std::string, ir::IndexExpr>> MatchPattern(
const ir::IndexExpr &expr,
const std::string &pattern_str,
const std::function<bool(
const std::unordered_map<std::string, ir::IndexExpr> &)> &condition) {
// Parse the pattern string into an IndexExpr
ir::IndexExpr pattern = ParseExpressionFromString(pattern_str);
std::unordered_map<std::string, ir::IndexExpr> map;
if (CheckPattern(expr, pattern, &map)) {
// Apply the condition if provided
if (condition && !condition(map)) return std::nullopt;
return map;
}
return std::nullopt;
}
/*!
* \brief Optimize linear division and modulo operations with constant
* denominators.
*
* This function handles linear expressions of the form
* `(a * C1 + b) / C2` and `(a * C1 + b) % C2`
* where C1 and C2 are constants. It specifically targets:
* 1. Linear combinations in the numerator (sums of terms)
* 2. Constant denominators
*
* The optimization:
* 1. Separates terms divisible by the denominator (linear coefficients)
* 2. Groups remaining terms as a remainder expression
* 3. For division:
* - Returns the sum of divisible terms if remainder < denominator
* - Otherwise preserves the original division
* 4. For modulo:
* - Returns the remainder if it's provably smaller than denominator
* - Otherwise preserves the original modulo
*
* Example linear optimizations:
* 1. Linear division: (x * 8 + y * 4 + 3) / 4 → x*2 + y + 0 (when 3 < 4)
* 2. Linear modulo: (x * 8 + y * 4 + 3) % 4 → 0 + 0 + 3
* 3. Partial division: (x * 6 + 5) / 3 → x * 2 + 5 / 3 (when 5 >= 3)
*
* \param expr The linear division/modulo expression to optimize
* \param ana Symbolic analyzer for proving expression bounds
* \return Simplified expression if provably correct, original otherwise
*/
ir::IndexExpr HandleDivModWithConstants(
const ir::IndexExpr &expr, const common::SymbolicExprAnalyzer &ana) {
// Get numerator and denominator
auto numerator = expr.operand(0);
auto denominator = expr.operand(1);
// Check if denominator is a constant
if (!denominator.is_constant()) {
return expr;
}
int64_t denom_val = denominator.as_int64();
// Recursively expand addition chain and collect all terms
std::vector<ir::IndexExpr> terms = optim::GetFlattenExprs<ir::Add>(numerator);
if (terms.empty()) {
return expr;
}
// Separate terms that are multiples of denominator from other terms
std::vector<ir::IndexExpr> multiple_terms;
std::vector<ir::IndexExpr> remainder_terms;
for (auto &term : terms) {
if (term.node_type() == ir::IrNodeTy::Mul) {
auto rhs = term.operand(1);
if (rhs.is_constant() && rhs.as_int64() % denom_val == 0) {
// Extract terms divisible by denominator
multiple_terms.push_back(
term.operand(0) *
(rhs.as_int64() / denom_val)); // Extract multiplicand part
continue;
}
}
// Extract terms not divisible by denominator
auto remainder_upper = ana.UpperBound(term);
if (!ana.ProveLT(remainder_upper, denominator).value_or(false)) {
return expr;
}
remainder_terms.push_back(term);
}
// Build remainder expression
ir::IndexExpr remainder_expr;
if (remainder_terms.empty()) {
remainder_expr = ir::IndexExpr(0);
} else if (remainder_terms.size() == 1) {
remainder_expr = remainder_terms[0];
} else {
remainder_expr = ir::Add::Make(remainder_terms[0], remainder_terms[1]);
for (size_t i = 2; i < remainder_terms.size(); ++i) {
remainder_expr = ir::Add::Make(remainder_expr, remainder_terms[i]);
}
}
// Build multiplicand terms expression
ir::IndexExpr multiple_expr;
if (multiple_terms.empty()) {
multiple_expr = ir::IndexExpr(0);
} else if (multiple_terms.size() == 1) {
multiple_expr = multiple_terms[0];
} else {
multiple_expr = ir::Add::Make(multiple_terms[0], multiple_terms[1]);
for (size_t i = 2; i < multiple_terms.size(); ++i) {
multiple_expr = ir::Add::Make(multiple_expr, multiple_terms[i]);
}
}
// Verify if remainder range is less than denominator
auto remainder_upper = ana.UpperBound(remainder_expr);
if (!ana.ProveLT(remainder_upper, denominator).value_or(false)) {
// If remainder is greater than denominator, the division result is non-zero
if (expr.node_type() == ir::IrNodeTy::Div) {
return ir::Add::Make(multiple_expr,
ir::Div::Make(remainder_expr, denominator));
} else { // Modulo operation
return ir::Mod::Make(remainder_expr, denominator);
}
} else {
// If remainder is less than denominator, the division result is zero
if (expr.node_type() == ir::IrNodeTy::Div) {
return multiple_expr;
} else { // Modulo operation
return remainder_expr;
}
}
}
ir::IndexExpr BoundSimplify(const ir::IndexExpr &expr) {
// Return expr if expr is not a division or modulo
if (expr.node_type() != ir::IrNodeTy::Div &&
expr.node_type() != ir::IrNodeTy::Mod)
return expr;
common::cas_intervals_t var_intervals =
common::CollectVarIntervalsOfExprs({expr});
common::SymbolicExprAnalyzer ana(var_intervals);
// Because the SymbolicExprAnalyzer bound result is [lower, upper],
// `ProveLT` is used here instead of `ProveLE`.
auto canBeSimplified =
ana.ProveLT(ana.UpperBound(expr.operand(0)), expr.operand(1));
if (canBeSimplified.value_or(false)) {
if (expr.node_type() == ir::IrNodeTy::Div) {
return ir::IndexExpr(0);
} else if (expr.node_type() == ir::IrNodeTy::Mod) {
return expr.operand(0);
}
}
return HandleDivModWithConstants(expr, ana);
}
ir::IndexExpr BroadcastSimplify(const ir::IndexExpr &expr) {
// Two consecutive modular operations.
auto opt_map =
MatchPattern(expr,
"f % a % b",
[](const std::unordered_map<std::string, ir::IndexExpr> &m) {
return m.at("a").node_type() == ir::IrNodeTy::Max ||
m.at("a").node_type() == ir::IrNodeTy::Mul;
});
if (!opt_map) return expr;
auto &map = opt_map.value();
auto ll = map.at("f");
auto lr = map.at("a");
auto r = map.at("b");
auto CanSimplifyMaxMod = [](const ir::IndexExpr &lr, const ir::IndexExpr &r) {
auto lr_elems = GetFlattenExprs<ir::Max>(lr);
auto r_elems = GetFlattenExprs<ir::Max>(r);
// The second modulus is a subset of the first modulus.
for (auto &&r_elem : r_elems) {
if (std::find(lr_elems.begin(), lr_elems.end(), r_elem) == lr_elems.end())
return false;
}
// The first modulus is broadcastable.
auto &constraint = cinn::common::ShapeConstraintManager::Instance();
return constraint.IsBroadcastable(lr_elems) ? true : false;
};
if (lr.node_type() == ir::IrNodeTy::Max) {
if (CanSimplifyMaxMod(lr, r)) return ll % r;
return expr;
} else {
std::unordered_map<ir::IndexExpr, int> r_elems;
std::unordered_map<ir::IndexExpr, int> lr_elems;
UnpackReduction<ir::Mul>(r, [&](ir::IndexExpr val) { r_elems[val]++; });
UnpackReduction<ir::Mul>(lr, [&](ir::IndexExpr val) { lr_elems[val]++; });
bool can_simplify = false;
for (const auto &[r_first, r_second] : r_elems) {
for (auto &[lr_first, lr_second] : lr_elems) {
// Check equal relationship between the two operands.
if (lr_first == r_first && lr_second >= r_second) {
lr_second -= r_second;
can_simplify = true;
break;
}
// Check broadcastable relationship between the two operands.
if (lr_first.node_type() == ir::IrNodeTy::Max &&
CanSimplifyMaxMod(lr_first, r_first) && lr_second >= r_second) {
lr_second -= r_second;
can_simplify = true;
break;
}
}
if (!can_simplify) return expr;
}
return ll % r;
}
}
} // namespace optim
} // namespace cinn