#include "gpu/intel/jit/pass/shuffle_splitter.hpp"
#include "gemmstone/../../dsl/ir/pass/trace.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace jit {
class shuffle_splitter_t : public ir_mutator_t {
public:
static expr_t add(const expr_t &e, const expr_t &ee) {
if (e.is_empty()) {
return ee;
} else if (ee.is_empty()) {
return e;
} else {
return e + ee;
}
}
object_t _mutate(const binary_op_t &obj) override {
if (obj.op_kind != op_kind_t::_add) return ir_mutator_t::_mutate(obj);
auto new_obj = ir_mutator_t::_mutate(obj);
auto args = split_by_add(new_obj, obj.type.elems());
if (args.size() <= 1) return new_obj;
std::vector<expr_t> bcasts;
std::vector<expr_t> non_bcasts;
for (auto &a : args) {
if (a.type().elems() != obj.type.elems()) {
bcasts.push_back(a);
} else {
non_bcasts.push_back(a);
}
}
if (bcasts.size() <= 1) return new_obj;
int elems = obj.type.elems();
expr_t e = shuffle_t::make_broadcast(make_add(bcasts), elems);
if (!non_bcasts.empty()) e = add(e, make_add(non_bcasts));
gpu_assert(e);
return std::move(e);
}
object_t _mutate(const shuffle_t &obj) override {
object_t new_obj = ir_mutator_t::_mutate(obj);
if (obj.is_broadcast() || !new_obj.is<shuffle_t>()) return new_obj;
auto &o = new_obj.as<shuffle_t>();
expr_t vec_bcast;
std::vector<expr_t> vec_const;
std::vector<expr_t> vec_off;
std::vector<object_eq_map_t<expr_t, int>> args;
bool can_split = false;
const expr_t zero = cast(0, o.type.base());
for (auto &v : o.vec) {
if (!v.type().is_int()) return new_obj;
auto v_args = split_by_add(v, v.type().elems());
if (v_args.size() > 1) can_split = true;
expr_t e_const = zero;
args.emplace_back();
for (auto &a : v_args) {
if (is_const(a)) {
e_const += a;
} else {
args.back()[a] += 1;
}
}
vec_const.emplace_back(const_fold(e_const));
}
if (!can_split) return new_obj;
auto intersect = [](object_eq_map_t<expr_t, int> &a,
object_eq_map_t<expr_t, int> &b) {
object_eq_map_t<expr_t, int> c;
for (auto &kv : a) {
auto &key = kv.first;
int rep_a = kv.second;
int rep_b = b[key];
int rep_c = std::min(rep_a, rep_b);
if (rep_c > 0) c[key] = rep_c;
}
return c;
};
auto difference = [](object_eq_map_t<expr_t, int> &a,
object_eq_map_t<expr_t, int> &b) {
object_eq_map_t<expr_t, int> c;
for (auto &kv : a) {
auto key = kv.first;
int rep_a = kv.second;
int rep_b = b[key];
int rep_c = rep_a - rep_b;
if (rep_c > 0) c[key] = rep_c;
}
return c;
};
auto is_empty_or_fill = [&](std::vector<expr_t> &vec) {
for (auto &c : vec) {
if (c && !c.is_equal(zero)) { return false; }
if (c.is_empty()) c = zero;
}
return true;
};
auto is_bcast = [](const std::vector<expr_t> &vec) {
for (auto &c : vec) {
if (!c.is_equal(vec[0])) { return false; }
}
return true;
};
auto get_bcast_difference
= [](const expr_t &expr_a, const expr_t &expr_b) {
if (!expr_a.is<shuffle_t>() || !expr_b.is<shuffle_t>())
return expr_t();
auto &a = expr_a.as<shuffle_t>();
auto &b = expr_b.as<shuffle_t>();
if (a.idx.size() != b.idx.size()) return expr_t();
if (a.vec.size() != b.vec.size()) return expr_t();
for (size_t i = 0; i < a.idx.size(); i++) {
if (a.idx[i] != b.idx[i]) return expr_t();
}
if (a.vec.empty()) return expr_t();
expr_t offset = const_fold(a.vec[0] - b.vec[0]);
for (size_t i = 0; i < a.vec.size(); i++) {
expr_t new_offset = const_fold(a.vec[i] - b.vec[i]);
if (!offset.is_equal(new_offset)) return expr_t();
}
return offset;
};
auto base_args = args[0];
for (int i = 1; i < (int)args.size(); i++) {
base_args = intersect(base_args, args[i]);
}
vec_bcast = make_add(base_args);
vec_off.reserve(args.size());
for (auto &a : args)
vec_off.emplace_back(make_add(difference(a, base_args)));
bool is_bcast_empty = base_args.empty();
bool is_consts_empty = is_empty_or_fill(vec_const);
bool is_consts_bcast = is_bcast(vec_const);
bool is_off_empty = is_empty_or_fill(vec_off);
expr_t const_shuffle;
if (!is_consts_empty) {
const_shuffle = shuffle_t::make(vec_const, o.idx);
if (!is_consts_bcast) {
expr_t offset;
for (auto &k : const_shuffles_) {
offset = get_bcast_difference(const_shuffle, k);
if (offset) {
vec_bcast = add(vec_bcast, offset);
const_shuffle = k;
is_consts_bcast
= is_bcast(const_shuffle.as<shuffle_t>().vec);
break;
}
}
if (offset.is_empty()) {
const_shuffles_.emplace(const_shuffle);
}
}
if (is_consts_bcast) {
const_shuffle = shuffle_t::make_broadcast(
const_shuffle.as<shuffle_t>().vec[0], o.type.elems());
}
}
expr_t e;
if (!is_bcast_empty)
e = add(e, shuffle_t::make_broadcast(vec_bcast, o.type.elems()));
if (!is_off_empty) e = add(e, shuffle_t::make(vec_off, o.idx));
e = add(e, const_shuffle);
return std::move(e);
}
private:
object_eq_set_t<expr_t> const_shuffles_;
static std::vector<expr_t> split_by_add(const expr_t &e, int elems) {
auto *shuffle = e.as_ptr<shuffle_t>();
if (shuffle && shuffle->is_broadcast() && shuffle->elems() == elems) {
return split_by_add(shuffle->vec[0], elems);
}
auto *op = e.as_ptr<binary_op_t>();
if (!op || op->op_kind != op_kind_t::_add) return {e};
auto a_args = split_by_add(op->a, elems);
auto b_args = split_by_add(op->b, elems);
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;
}
static expr_t make_add(const std::vector<expr_t> &args) {
if (args.empty()) return 0;
expr_t e = args[0];
for (int i = 1; i < (int)args.size(); i++)
e = e + args[i];
return e;
}
static expr_t make_add(const object_eq_map_t<expr_t, int> &args) {
if (args.empty()) return 0;
expr_t e;
for (auto &kv : args)
if (kv.second == 0)
continue;
else if (kv.second == 1)
e = add(e, kv.first);
else
e = add(e, kv.second * kv.first);
return e;
}
};
stmt_t split_shuffle(const stmt_t &s, ir_context_t &ir_ctx) {
ir::trace_start();
auto ret = shuffle_splitter_t().mutate(s);
ir::trace_pass("split_shuffle", ret, ir_ctx);
return ret;
}
} } } } }