#include "gpu/intel/jit/pass/overflow.hpp"
#include "gemmstone/../../dsl/ir/pass/trace.hpp"
#include "gpu/intel/jit/pass/expr_scalarizer.hpp"
#include "gpu/intel/logging.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace jit {
class overflow_bound_finder_t : public bound_finder_base_t {
public:
bool has_var(const expr_t &e) const {
gpu_assert(is_var(e)) << "Expected variable, found: " << e;
auto it = var_bounds_.find(e);
return it != var_bounds_.end();
}
std::pair<int64_t, int64_t> find_bounds(const expr_t &e) const {
int64_t lo = find_low_bound(e);
int64_t hi = find_high_bound(e);
return std::make_pair(lo, hi);
}
int64_t get_var_bound(const expr_t &e, bool is_low) const override {
gpu_assert(has_var(e)) << "Variable not found: " << e;
auto &lo_hi = var_bounds_.at(e);
return is_low ? lo_hi.first : lo_hi.second;
}
void set_var_bounds(
const expr_t &e, const std::pair<int64_t, int64_t> &lo_hi) {
gpu_assert(is_good_bound(lo_hi.first))
<< "Can't compute low bound for " << e;
gpu_assert(is_good_bound(lo_hi.second))
<< "Can't compute high bound for " << e;
var_bounds_.emplace(e, lo_hi);
}
protected:
int64_t find_bound_impl(const expr_t &e, bool is_low) const override {
auto *cast = e.as_ptr<cast_t>();
if (cast) {
if (e.type().is_u64() && cast->expr.type().is_ptr()) {
return is_low ? 0 : std::numeric_limits<uint32_t>::max();
} else if (e.type().is_u32() && cast->expr.type().is_ptr()) {
return is_low ? 0 : std::numeric_limits<uint16_t>::max();
}
}
return bound_finder_base_t::find_bound_impl(e, is_low);
}
private:
object_map_t<expr_t, std::pair<int64_t, int64_t>> var_bounds_;
};
struct overflow_context_t {
overflow_bound_finder_t bound_finder;
object_map_t<expr_t, std::vector<expr_t>> vec_vars;
object_set_t<expr_t> vars_with_load;
bool contains_load(const expr_t &e) const {
if (!find_objects<load_t>(e).empty()) return true;
for (auto &v : find_objects<var_t>(e)) {
if (vars_with_load.count(v) != 0) return true;
}
return false;
}
};
class expr_overflow_fixer_t : public ir_mutator_t {
public:
expr_overflow_fixer_t(const overflow_context_t &ctx) : ctx_(ctx) {}
object_t _mutate(const binary_op_t &obj) override {
return mutate_expr(obj);
}
object_t _mutate(const unary_op_t &obj) override {
return mutate_expr(obj);
}
private:
template <typename T>
object_t mutate_expr(const T &obj) {
expr_t new_obj = ir_mutator_t::_mutate(obj);
if (!new_obj.type().is_x32()) return std::move(new_obj);
if (ctx_.contains_load(new_obj)) return std::move(new_obj);
bool found_overflow = false;
int elems = new_obj.type().elems();
for (int i = 0; i < elems; i++) {
expr_scalarizer_t scalarizer(elems, i, ctx_.vec_vars);
expr_t value = scalarizer.mutate(new_obj);
int64_t lo = ctx_.bound_finder.find_low_bound(value);
int64_t hi = ctx_.bound_finder.find_high_bound(value);
bool ok = bound_finder_base_t::is_good_bound(lo)
&& bound_finder_base_t::is_good_bound(hi);
if (ok) {
int64_t type_lo = value.type().is_s32()
? (int64_t)std::numeric_limits<int32_t>::min()
: (int64_t)std::numeric_limits<uint32_t>::min();
int64_t type_hi = value.type().is_s32()
? (int64_t)std::numeric_limits<int32_t>::max()
: (int64_t)std::numeric_limits<uint32_t>::max();
bool is_overflow = (lo < type_lo || hi > type_hi);
if (is_overflow) {
found_overflow = true;
gpu_warning()
<< "Found overflow: " << value
<< " low bound: " << lo << " high bound: " << hi;
break;
}
}
}
if (found_overflow) return fix_overflow(new_obj);
return std::move(new_obj);
}
static expr_t fix_overflow(const expr_t &e) {
auto *binary = e.as_ptr<binary_op_t>();
if (binary) {
return binary_op_t::make(binary->op_kind,
cast(binary->a, dsl::type_t::u64(e.type().elems())),
binary->b);
}
gpu_error_not_expected() << "Can't fix overflow: " << e;
return e;
}
const overflow_context_t &ctx_;
};
expr_t fix_expr_overflow(const expr_t &e, const overflow_context_t &ctx) {
auto e_fixed = expr_overflow_fixer_t(ctx).mutate(e);
if (e_fixed.is_same(e)) return e;
auto nary = reorder_nary_add_args(
nary_op_canonicalize(e), true);
auto e_reordered = nary_op_back_transform(nary);
auto e_reordered_fixed = expr_overflow_fixer_t(ctx).mutate(e_reordered);
if (e_reordered_fixed.is_same(e_reordered)) {
return e_reordered;
}
return e_fixed;
}
class overflow_fixer_t : public ir_mutator_t {
public:
overflow_fixer_t(ir_context_t &ir_ctx) : ir_ctx_(ir_ctx) {
for (auto &kv : ir_ctx.cset().relations()) {
int64_t lo = bound_finder_base_t::unlimited_bound(true);
int64_t hi = bound_finder_base_t::unlimited_bound(false);
for (auto &rel : kv.second) {
bool is_ge = (rel.op_kind() == op_kind_t::_ge);
bool is_le = (rel.op_kind() == op_kind_t::_le);
gpu_assert(is_ge || is_le);
if (rel.op_kind() == op_kind_t::_ge) {
lo = std::max(to_cpp<int64_t>(rel.rhs()), lo);
} else if (rel.op_kind() == op_kind_t::_le) {
hi = std::min(to_cpp<int64_t>(rel.rhs()), hi);
} else {
gpu_error_not_expected()
<< "Only >= or <= is expected, found: "
<< to_string(rel.op_kind());
}
}
ctx_.bound_finder.set_var_bounds(kv.first, {lo, hi});
}
}
object_t _mutate(const alloc_t &obj) override {
return ir_mutator_t::_mutate(obj);
}
object_t _mutate(const binary_op_t &obj) override {
return fix_expr_overflow(obj, ctx_);
}
object_t _mutate(const for_t &obj) override {
auto lo = is_const(obj.init)
? to_cpp<int64_t>(obj.init)
: ctx_.bound_finder.find_bounds(obj.init).first;
auto hi = is_const(obj.bound)
? to_cpp<int64_t>(obj.bound) - 1
: ctx_.bound_finder.find_bounds(obj.bound).second;
ctx_.bound_finder.set_var_bounds(obj.var, {lo, hi});
return ir_mutator_t::_mutate(obj);
}
object_t _mutate(const let_t &obj) override {
bool ok = true;
if (!obj.var.type().is_int()) ok = false;
if (ok && obj.value.is_empty()) ok = false;
if (ok && obj.value.type().is_bool()) ok = false;
if (ok && ctx_.bound_finder.has_var(obj.var)) ok = false;
if (ok) {
if (ctx_.contains_load(obj.value)) {
ctx_.vars_with_load.insert(obj.var);
ok = false;
}
}
if (ok) {
int elems = obj.var.type().elems();
ctx_.vec_vars[obj.var].reserve(elems);
for (int i = 0; i < elems; i++) {
auto var_i = make_vec_var(obj.var, elems, i);
expr_scalarizer_t scalarizer(elems, i, ctx_.vec_vars);
auto value_i = scalarizer.mutate(obj.value);
auto lo_hi = ctx_.bound_finder.find_bounds(value_i);
ctx_.bound_finder.set_var_bounds(var_i, lo_hi);
ctx_.vec_vars[obj.var].push_back(std::move(var_i));
}
}
expr_t var = obj.var;
expr_t value = mutate(obj.value);
stmt_t body = mutate(obj.body);
if (value.is_same(obj.value) && body.is_same(obj.body)) return obj;
if (value && !value.type().is_bool()
&& value.type() != obj.value.type()) {
auto old_var = var;
var = ir_ctx_.create_tmp_var(
value.type(), old_var.as<var_t>().name);
body = substitute_with_different_type(body, old_var, var);
}
return let_t::make(var, value, body);
}
object_t _mutate(const unary_op_t &obj) override {
return fix_expr_overflow(obj, ctx_);
}
private:
static expr_t make_vec_var(const expr_t &_var, int elems, int idx) {
if (elems == 1) return _var;
auto &var = _var.as<var_t>();
auto vec_name = var.name + "_" + std::to_string(idx) + "_";
return var_t::make(var.type.base(), vec_name);
}
ir_context_t &ir_ctx_;
overflow_context_t ctx_;
};
stmt_t fix_int32_overflow(const stmt_t &s, ir_context_t &ir_ctx) {
ir::trace_start();
auto ret = overflow_fixer_t(ir_ctx).mutate(s);
ir::trace_pass("fix_int32_overflow", ret, ir_ctx);
return ret;
}
} } } } }