#include "gpu/intel/jit/ir/linear_expr.hpp"
#include "common/math_utils.hpp"
#include "gemmstone/../../dsl/ir/pass/simplify.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace jit {
std::vector<expr_t> op_split(op_kind_t kind, const expr_t &e) {
auto *op = e.as_ptr<binary_op_t>();
if (!op || op->op_kind != kind) return {e};
auto a_args = op_split(kind, op->a);
auto b_args = op_split(kind, op->b);
std::vector<expr_t> args;
args.insert(args.end(), a_args.begin(), a_args.end());
args.insert(args.end(), b_args.begin(), b_args.end());
return args;
}
expr_t op_combine(op_kind_t kind, const std::vector<expr_t> &args) {
bool is_add = (kind == op_kind_t::_add);
bool is_mul = (kind == op_kind_t::_mul);
gpu_assert(is_add || is_mul);
expr_t ret = (is_add ? 0 : 1);
for (auto &a : args) {
if (a.is_empty()) continue;
ret = binary_op_t::make(kind, ret, a);
}
return simplify_rewrite(ret);
}
bool is_const_expr(const expr_t &e) {
if (e.is<const_var_t>()) return true;
if (e.is<var_t>()) return false;
if (e.is<int_imm_t>()) return true;
if (auto *op = e.as_ptr<unary_op_t>()) return is_const_expr(op->a);
if (auto *op = e.as_ptr<binary_op_t>()) {
return is_const_expr(op->a) && is_const_expr(op->b);
}
gpu_error_not_expected() << e;
return false;
}
object_t linear_normalize_expander_t::_mutate(const binary_op_t &_obj) {
auto op_kind = _obj.as<binary_op_t>().op_kind;
if (op_kind == op_kind_t::_sub) {
auto &op = _obj.as<binary_op_t>();
auto a = mutate(op.a);
auto b = mutate(op.b * -1);
return simplify_rewrite(a + b);
}
auto obj = ir_mutator_t::_mutate(_obj);
auto &op = obj.as<binary_op_t>();
if (op.op_kind != op_kind_t::_mul) return obj;
auto a = op.a;
auto b = op.b;
if (!is_const_expr(b)) std::swap(a, b);
gpu_assert(is_const_expr(b));
auto a_args = op_split(op_kind_t::_add, a);
auto b_args = op_split(op_kind_t::_add, b);
expr_t ret = 0;
for (auto &a : a_args) {
if (!is_const_expr(a)) {
ret += a * op_combine(op_kind_t::_add, b_args);
continue;
}
for (auto &b : b_args) {
ret += a * b;
}
}
return simplify_rewrite(ret);
}
expr_t linear_normalize_reduce(const expr_t &e,
object_eq_map_t<expr_t, int64_t> factors, int64_t const_factor) {
auto mul_args = op_split(op_kind_t::_mul, e);
for (auto &ma : mul_args) {
if (is_const(ma)) {
int64_t ma_const = to_cpp<int64_t>(ma);
int64_t div = math::gcd(const_factor, ma_const);
const_factor /= div;
ma = ma_const / div;
continue;
}
auto it = factors.find(ma);
if (it == factors.end() || it->second == 0) continue;
factors[ma]--;
ma = expr_t();
}
gpu_assert(const_factor == 1);
for (auto &kv : factors) {
gpu_assert(kv.second == 0);
}
return op_combine(op_kind_t::_mul, mul_args);
}
object_eq_map_t<expr_t, int64_t> find_common_factors(
const std::vector<expr_t> &add_args, int64_t &const_factor) {
const_factor = 1;
object_eq_map_t<expr_t, int64_t> common;
for (int i = 0; i < (int)add_args.size(); i++) {
auto mul_args = op_split(op_kind_t::_mul, add_args[i]);
if (i == 0) {
for (auto &ma : mul_args) {
if (is_const(ma)) {
const_factor *= to_cpp<int64_t>(ma);
continue;
}
common[ma]++;
}
} else {
auto i_common = common;
int64_t i_const_factor = 1;
common.clear();
for (auto &ma : mul_args) {
if (is_const(ma)) {
i_const_factor *= to_cpp<int64_t>(ma);
continue;
}
auto it = i_common.find(ma);
if (it == i_common.end() || it->second == 0) continue;
it->second--;
common[ma]++;
}
const_factor = math::gcd(const_factor, i_const_factor);
}
}
return common;
}
expr_t linear_normalize_const_factor_out(const expr_t &_e) {
auto e = simplify_rewrite(_e);
gpu_assert(is_const_expr(e));
auto add_args = op_split(op_kind_t::_add, e);
if (add_args.size() <= 1) return e;
int64_t const_factor;
auto common = find_common_factors(add_args, const_factor);
if (common.empty() && const_factor == 1) return e;
for (auto &a : add_args) {
a = linear_normalize_reduce(a, common, const_factor);
}
std::vector<expr_t> v_common;
v_common.emplace_back(const_factor);
for (auto &kv : common) {
for (int i = 0; i < kv.second; i++)
v_common.push_back(kv.first);
}
auto a = op_combine(op_kind_t::_mul, v_common);
auto b = op_combine(op_kind_t::_add, add_args);
return simplify_rewrite(a * b);
}
std::pair<expr_t, expr_t> split_to_coef_and_index(const expr_t &e) {
auto args = op_split(op_kind_t::_mul, e);
expr_t coef = 1;
expr_t idx;
for (auto &a : args) {
if (a.is<var_t>()) {
gpu_assert(idx.is_empty());
idx = a;
} else if (is_const_expr(a)) {
coef *= a;
} else {
gpu_error_not_expected() << a;
}
}
return std::make_pair(coef, idx);
}
expr_t to_linear(const expr_t &_e) {
auto e = linear_normalize_expander_t().mutate(_e);
auto add_args = op_split(op_kind_t::_add, e);
expr_t c = 0;
std::vector<expr_t> u;
std::vector<expr_t> v;
for (auto &a : add_args) {
auto p = split_to_coef_and_index(a);
if (p.second.is_empty()) {
c += p.first;
continue;
}
u.push_back(linear_normalize_const_factor_out(p.first));
v.push_back(p.second);
}
c = linear_normalize_const_factor_out(c);
return linear_t::make(c, u, v);
}
class linear_coef_t {
public:
explicit linear_coef_t(const expr_t &value = expr_t(0)) : imm_(1) {
auto mul_args = op_split(op_kind_t::_mul, value);
for (auto &a : mul_args)
mul_impl(a);
}
bool is_zero() const { return factors_.empty() && imm_ == 0; }
int64_t imm() const { return imm_; }
void set_imm(int64_t imm) { imm_ = imm; }
void keep_const_vars_only() {
std::vector<expr_t> new_factors;
for (auto &f : factors_) {
if (f.is<const_var_t>()) new_factors.push_back(f);
}
factors_ = std::move(new_factors);
}
linear_coef_t &operator/=(int64_t factor) {
gpu_assert(imm_ % factor == 0);
imm_ /= factor;
return *this;
}
linear_coef_t &intersect(const linear_coef_t &other) {
if (other.is_zero()) return *this;
if (is_zero()) {
*this = other;
return *this;
}
imm_ = math::gcd(imm_, other.imm_);
auto lhs = op_combine(op_kind_t::_mul, factors_);
auto rhs = op_combine(op_kind_t::_mul, other.factors_);
int64_t const_factor = 1;
auto common = find_common_factors(
{std::move(lhs), std::move(rhs)}, const_factor);
gpu_assert(const_factor == 1);
factors_.clear();
for (auto &kv : common) {
for (int i = 0; i < kv.second; i++)
factors_.push_back(kv.first);
}
return *this;
}
expr_t to_expr() const {
expr_t ret = imm_;
for (auto &f : factors_)
ret *= f;
return simplify_rewrite(ret);
}
std::string str() const {
ostringstream_t oss;
oss << "imm: " << imm_;
if (factors_.empty()) return oss.str();
oss << std::endl << "factors:";
for (auto &f : factors_) {
oss << std::endl;
oss << " " << f;
}
return oss.str();
}
XE_DEFINE_DUMP()
static expr_t div(const expr_t &e, int64_t factor) {
linear_coef_t coef(e);
coef /= factor;
return coef.to_expr();
}
static std::vector<expr_t> div(
const std::vector<expr_t> &v, int64_t factor) {
std::vector<expr_t> ret;
ret.reserve(v.size());
for (auto &e : v)
ret.push_back(div(e, factor));
return ret;
}
private:
void mul_impl(const expr_t &e) {
gpu_assert(is_const_expr(e)) << e;
if (is_const(e)) {
imm_ *= to_cpp<int64_t>(e);
if (imm_ == 0) factors_.clear();
return;
}
factors_.push_back(e);
}
int64_t imm_ = 0;
std::vector<expr_t> factors_;
};
int64_t linear_max_pow2_divisor_impl(const expr_t &e) {
const int64_t large_pow2 = (1 << 20);
if (e.is(0)) return large_pow2;
if (e.is<const_var_t>()) return 1;
if (e.is<var_t>()) return 1;
if (auto *imm = e.as_ptr<int_imm_t>())
return ir_utils::max_pow2_divisor(imm->value);
if (auto *op = e.as_ptr<unary_op_t>()) {
return linear_max_pow2_divisor_impl(op->a);
}
if (auto *op = e.as_ptr<binary_op_t>()) {
switch (op->op_kind) {
case op_kind_t::_add:
case op_kind_t::_sub: {
auto a = linear_max_pow2_divisor_impl(op->a);
auto b = linear_max_pow2_divisor_impl(op->b);
return math::gcd(a, b);
}
case op_kind_t::_mul: {
auto a = linear_max_pow2_divisor_impl(op->a);
auto b = linear_max_pow2_divisor_impl(op->b);
return a * b;
}
case op_kind_t::_div:
case op_kind_t::_div_up:
case op_kind_t::_mod: return 1;
default: gpu_error_not_expected() << e;
}
}
gpu_error_not_expected() << e;
return 1;
}
int64_t linear_max_pow2_divisor(const expr_t &e) {
auto _linear = to_linear(e);
auto &linear = _linear.as<linear_t>();
int64_t ret = linear_max_pow2_divisor_impl(linear.c);
for (auto &u : linear.u_vec)
ret = math::gcd(ret, linear_max_pow2_divisor_impl(u));
return ret;
}
expr_t linear_div(const expr_t &e, int64_t factor) {
auto _linear = to_linear(e);
auto &linear = _linear.as<linear_t>();
auto c = linear_coef_t::div(linear.c, factor);
auto u_vec = linear_coef_t::div(linear.u_vec, factor);
return linear_t::to_expr(c, u_vec, linear.v_vec);
}
expr_t simplify_linear_mod_reduce(const expr_t &e, int64_t factor) {
if (factor == 1) return 0;
if (is_const(e)) return to_cpp<int64_t>(e) % factor;
if (e.is<const_var_t>()) return e;
if (auto *op = e.as_ptr<binary_op_t>()) {
auto a = simplify_linear_mod_reduce(op->a, factor);
auto b = simplify_linear_mod_reduce(op->b, factor);
switch (op->op_kind) {
case op_kind_t::_add:
if (a.is(0)) return b;
if (b.is(0)) return a;
return simplify_rewrite(a + b);
case op_kind_t::_mul:
if (a.is(0)) return 0;
if (b.is(0)) return 0;
return simplify_rewrite(a * b);
default: break;
}
}
return e;
}
expr_t simplify_linear_mod(const expr_t &e, int64_t factor) {
gpu_assert(factor > 0);
if (factor == 1) return 0;
auto _linear = to_linear(e);
auto &linear = _linear.as<linear_t>();
std::vector<linear_coef_t> coefs;
coefs.emplace_back(linear.c);
for (auto &u : linear.u_vec)
coefs.emplace_back(u);
linear_coef_t common;
for (auto &c : coefs) {
auto add_args = op_split(op_kind_t::_add,
linear_normalize_expander_t().mutate(c.to_expr()));
for (auto &a : add_args) {
linear_coef_t ca(a);
if (ca.imm() % factor == 0) continue;
common.intersect(ca);
}
}
if (common.imm() == 0) return 0;
int64_t div = math::gcd(common.imm(), factor);
int64_t new_factor = factor / div;
common.set_imm(1);
common.keep_const_vars_only();
auto reduced = simplify_linear_mod_reduce(common.to_expr(), new_factor);
return reduced % new_factor;
}
expr_t split_to_linear_impl(
const expr_t &expr, const expr_t &idx, expr_t &inc) {
if (auto *linear = expr.as_ptr<linear_t>()) {
for (int i = 0; i < linear->nargs(); i++) {
if (linear->v_vec[i].impl() == idx.impl()) {
auto u_vec = linear->u_vec;
auto v_vec = linear->v_vec;
u_vec.erase(u_vec.begin() + i);
v_vec.erase(v_vec.begin() + i);
inc = linear->u_vec[i];
return linear_t::make(linear->c, u_vec, v_vec);
}
}
inc = expr_t(0);
return expr;
}
gpu_error_not_expected() << expr;
return expr;
}
void split_to_linear(const expr_t &expr, const std::vector<expr_t> &idxs,
const std::vector<expr_t> &start, expr_t &init,
std::vector<expr_t> &incs) {
incs = std::vector<expr_t>(idxs.size());
init = to_linear(expr);
expr_t start_shift = 0;
for (size_t i = 0; i < idxs.size(); i++) {
init = split_to_linear_impl(init, idxs[i], incs[i]);
if (start[i].is(0)) continue;
start_shift += start[i] * incs[i];
}
init = init.as<linear_t>().to_expr() + start_shift;
}
} } } } }