#include "gpu/intel/jit/ir/v2/tensor.hpp"
#include <array>
#include "gemmstone/../../dsl/ir/pass/simplify.hpp"
#include "gpu/intel/jit/ir/linear_expr.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace jit {
namespace v2 {
static bool is_abx_tag(const std::string &s) {
std::array<bool, 'z' - 'a' + 1> seen;
seen.fill(false);
for (auto c : s) {
auto c_lower = std::tolower(c);
if (std::isalpha(c) && c == c_lower && c != 'x') {
seen[c - 'a'] = true;
}
}
for (size_t i = 0; i < seen.size(); i++) {
if (seen[i]) continue;
for (; i < seen.size(); i++) {
if (seen[i]) return false;
}
}
return true;
}
std::string block_t::brief_str() const {
ostringstream_t oss;
oss << "[dim = " << dim;
oss << ", size = " << size;
oss << ", stride = " << stride << "]";
return oss.str();
}
std::string block_t::str() const {
ostringstream_t oss;
oss << "block:" << std::endl;
oss << " dim: " << dim << std::endl;
oss << " size: " << size << std::endl;
oss << " stride: " << stride;
return oss.str();
}
layout_desc_t::layout_desc_t(const pvar_map_t<char> &letter_map)
: letter_map_(letter_map) {
auto append = [&](const pvar_t &dim) {
if (letter_map_.has(dim)) canonical_ += letter_map_[dim];
};
append(pvars::mb);
append(pvars::g);
append(pvars::oc);
append(pvars::ic);
append(pvars::id);
append(pvars::ih);
append(pvars::iw);
append(pvars::od);
append(pvars::oh);
append(pvars::ow);
append(pvars::kd);
append(pvars::kh);
append(pvars::kw);
}
char layout_desc_t::layout_letter(const pvar_t &dim) const {
if (!letter_map_.has(dim)) return '?';
return letter_map_.at(dim);
}
pvar_t layout_desc_t::prb_dim(int idx) const {
gpu_assert(idx >= 0 && idx < ndims());
char c = canonical_[idx];
for (auto &d : letter_map_) {
if (layout_letter(d) == c) return d;
}
gpu_error_not_expected();
return pvar_t();
}
int layout_desc_t::dim_index(const pvar_t &dim) const {
for (int i = 0; i < ndims(); i++) {
if (canonical_[i] == layout_letter(dim)) return i;
}
gpu_error_not_expected();
return -1;
}
std::string layout_desc_t::to_abx_tag(const std::string &tag) const {
if (is_abx_tag(tag)) return tag;
const char *tensor_map = canonical().c_str();
std::string ret;
for (auto c : tag) {
if (!std::isalpha(c) || c == 'x') {
ret += c;
continue;
}
auto c_lower = std::tolower(c);
bool found = false;
for (int i = 0; i < (int)std::strlen(tensor_map); i++) {
if (tensor_map[i] == c_lower) {
char ab = dim_idx::as_tag(i);
ret += (c == c_lower ? ab
: static_cast<char>(std::toupper(ab)));
found = true;
break;
}
}
gpu_assert(found);
}
return ret;
}
std::string layout_desc_t::str() const {
ostringstream_t oss;
oss << "canonical: " << canonical_ << std::endl;
oss << ir_utils::add_tag("letter_map", letter_map_.str());
return oss.str();
}
void dim_mapper_t::set_dim(
const pvar_t &dim, const expr_t &expr, bool has_underflow) {
map_.set(dim, {expr.is_empty() ? index_var(dim) : expr, has_underflow});
}
const expr_t &dim_mapper_t::expr(const pvar_t &dim) const {
if (is_empty()) return index_var(dim);
return map_[dim].expr;
}
bool dim_mapper_t::has_underflow(const pvar_t &dim) const {
if (is_empty()) return false;
return map_[dim].has_underflow;
}
std::string dim_mapper_t::str() const {
ostringstream_t oss;
oss << "dim_mapper:" << std::endl;
for (auto &dim : map_) {
oss << " " << dim.str() << " -> ";
oss << map_[dim].str() << std::endl;
}
return oss.str();
}
void layout_raw_tag_t::add_entry(char letter, int block, bool is_blocked) {
entries_.emplace_back(letter, block, is_blocked);
}
int layout_raw_tag_t::entry_index(char letter) {
for (int i = 0; i < (int)entries_.size(); i++) {
if (entries_[i].letter == letter) return i;
}
gpu_error_not_expected();
return -1;
}
void layout_raw_tag_t::add_dim(char letter, int pos) {
gpu_assert(!has_x());
std::vector<layout_raw_tag_entry_t> new_entries;
for (int i = 0; i < (int)entries_.size(); i++) {
auto &e = entries_[i];
if (i == pos) new_entries.emplace_back(letter, 0, false);
char new_letter = e.letter;
if (new_letter >= letter) new_letter++;
new_entries.emplace_back(new_letter, e.block, e.is_blocked);
}
entries_ = std::move(new_entries);
}
void layout_raw_tag_t::remove_dim(char letter) {
gpu_assert(!has_x());
std::vector<layout_raw_tag_entry_t> new_entries;
for (auto &e : entries_) {
if (e.letter == letter) continue;
char new_letter = e.letter;
if (e.letter > letter) new_letter--;
new_entries.emplace_back(new_letter, e.block, e.is_blocked);
}
entries_ = std::move(new_entries);
}
bool layout_raw_tag_t::is_blocked(char letter) const {
for (auto &e : entries_) {
if (e.letter == letter && e.is_blocked) return true;
}
return false;
}
bool layout_raw_tag_t::is_blocked() const {
for (auto &e : entries_) {
if (e.is_blocked) return true;
}
return false;
}
dim_idx_t layout_raw_tag_t::ndims() const {
gpu_assert(!is_any() && !has_x());
dim_idx_t max_index = 0;
for (auto &e : entries_) {
max_index = std::max(max_index, e.index());
}
return max_index + 1;
}
dim_idx_t layout_raw_tag_t::non_x_ndims() const {
gpu_assert(!is_any());
std::array<bool, 'z' - 'a' + 1> seen;
seen.fill(false);
for (auto &e : entries_) {
if (!e.is_x()) seen[e.index()] = true;
}
int ret = 0;
for (auto b : seen)
if (b) ret++;
return ret;
}
std::string layout_raw_tag_t::str() const {
if (is_any()) return "any";
std::string s;
for (auto &e : entries_)
s += e.str();
if (has_x()) return s;
std::string x;
for (dim_idx_t i = ndims() - 1; i >= 2; i--) {
if (is_blocked(dim_idx::as_tag(i))) break;
x = dim_idx::as_tag(i) + x;
}
while (!x.empty()) {
auto pos = s.find(x);
if (pos != std::string::npos) {
s.replace(pos, x.length(), "x");
break;
}
x.erase(0, 1);
}
return s;
}
bool layout_raw_tag_t::matches(const layout_raw_tag_t &other,
const layout_desc_t &desc, const tile_t &sizes) const {
if (is_any()) return true;
int n0 = nentries();
int n1 = other.nentries();
auto skip0 = skip_mask(desc, sizes);
auto skip1 = other.skip_mask(desc, sizes);
int i0 = 0;
int i1 = 0;
for (;;) {
while (i0 < n0 && skip0[i0])
i0++;
while (i1 < n1 && skip1[i1])
i1++;
if (i0 >= n0 || i1 >= n1) break;
if (entries_[i0] != other.entries_[i1]) return false;
i0++;
i1++;
}
return i0 == n0 && i1 == n1;
}
bool layout_raw_tag_t::has_x() const {
for (auto &e : entries_)
if (e.is_x()) return true;
return false;
}
void layout_raw_tag_t::expand_x(dim_idx_t ndims) {
if (!has_x() || ndims == 0) return;
std::vector<layout_raw_tag_entry_t> new_entries;
for (auto &e : entries_) {
if (e.is_x()) {
for (dim_idx_t i = non_x_ndims(); i < ndims; i++) {
auto new_e = e;
new_e.letter = dim_idx::as_tag(i);
new_entries.push_back(new_e);
}
} else {
new_entries.push_back(e);
}
}
entries_ = std::move(new_entries);
}
std::vector<layout_raw_tag_entry_t> layout_raw_tag_t::to_entries(
const std::string &tag) {
if (tag == "any") return {};
gpu_assert(is_abx_tag(tag)) << tag;
std::array<bool, 'z' - 'a' + 1> is_blocked;
is_blocked.fill(false);
auto letter_blocks = parse_letter_blocks(tag);
for (auto &p : letter_blocks) {
if (p.second != 0) is_blocked[std::tolower(p.first) - 'a'] = true;
}
std::vector<layout_raw_tag_entry_t> entries;
for (auto &p : letter_blocks) {
char letter = static_cast<char>(std::tolower(p.first));
entries.emplace_back();
auto &e = entries.back();
e.letter = letter;
e.block = p.second;
e.is_blocked = is_blocked[letter - 'a'];
}
return entries;
}
std::vector<bool> layout_raw_tag_t::skip_mask(
const layout_desc_t &desc, const tile_t &sizes) const {
std::vector<bool> ret(nentries());
auto rem_sizes = sizes;
for (int i = nentries() - 1; i >= 0; i--) {
auto &e = entries_[i];
int idx = e.letter - 'a';
auto dim = desc.prb_dim(idx);
gpu_assert(sizes.has(dim));
if (e.block != 0) {
gpu_assert(e.block != 1);
rem_sizes[dim] = utils::rnd_up(rem_sizes[dim], e.block);
}
if (rem_sizes[dim] == 1) ret[i] = true;
if (e.block != 0) rem_sizes[dim] /= e.block;
}
return ret;
}
std::vector<std::pair<char, int>> layout_raw_tag_t::parse_letter_blocks(
const std::string &tag) {
std::vector<std::pair<char, int>> ret;
stringstream_t ss(tag);
while (!ss.eof()) {
int next = ss.peek();
if (ss.eof()) break;
int block = 0;
while (std::isdigit(next)) {
block = 10 * block + (next - '0');
ss.ignore(1);
next = ss.peek();
}
char letter = char(ss.peek());
gpu_assert(!ss.eof());
ss.ignore(1);
ret.emplace_back(letter, block);
}
return ret;
}
static void advance(icoord_t &idx, const std::vector<pvar_t> &_idx_order,
const tile_t &bound, const tile_t &block) {
dim_t inc = 1;
auto idx_order = _idx_order;
if (idx_order.empty()) {
for (auto &d : idx)
idx_order.push_back(d);
} else {
pvar_map_t<bool> seen;
for (auto &d : idx_order)
seen[d] = true;
gpu_assert(seen.size() == idx.size());
for (auto &d : idx)
gpu_assert(seen.has(d));
}
for (auto &d : idx_order) {
dim_t inc_idx = (idx[d] / block[d] + inc) % bound[d];
inc = (idx[d] / block[d] + inc) / bound[d];
idx[d] = inc_idx * block[d];
if (inc == 0) break;
}
}
static void advance(std::vector<int> &idxs, const std::vector<block_t> &blocks,
const std::vector<dim_t> &block_incs) {
gpu_assert(idxs.size() == blocks.size());
gpu_assert(idxs.size() == block_incs.size());
for (size_t i = 0; i < idxs.size(); i++) {
auto block_inc = into<int>(block_incs[i]);
int size = blocks[i].int_size();
if (idxs[i] + block_inc < size) {
idxs[i] += block_inc;
break;
}
idxs[i] = 0;
}
}
static inline void advance(
std::vector<int> &idxs, const std::vector<block_t> &blocks, int inc) {
for (size_t i = 0; i < idxs.size(); i++) {
int size = blocks[i].int_size();
int inc_idx = (idxs[i] + inc) % size;
inc = (idxs[i] + inc) / size;
idxs[i] = inc_idx;
if (inc == 0) break;
}
}
bool layout_tag_t::matches(
const layout_tag_t &other, const tile_t &sizes, bool check_type) const {
if (check_type && type_ != other.type_) return false;
return raw_tag().matches(other.raw_tag(), desc_, sizes);
}
std::string layout_tag_t::str() const {
if (is_empty()) return "x";
ostringstream_t oss;
oss << raw_tag_ << ":" << type_ << ":" << is_strided_;
return oss.str();
}
int layout_t::elems() const {
gpu_assert(has_const_sizes());
int ret = 1;
for (auto &b : blocks_)
ret *= b.int_size();
return ret;
}
int layout_t::size() const {
gpu_assert(has_const_sizes());
gpu_assert(has_const_strides());
if (is_empty()) return 0;
int max_off = 0;
int max_block_size = 0;
for (auto &b : blocks_) {
max_off += (b.int_size() - 1) * b.int_stride();
max_block_size
= std::max(max_block_size, b.int_size() * b.int_stride());
}
int max_off_bytes = (max_off + 1) * type().size();
return std::max(max_off_bytes, max_block_size * type().size());
}
int layout_t::nblocks(const pvar_t &dim) const {
int ret = 0;
for (auto &b : blocks_)
if (b.dim == dim) ret++;
return ret;
}
int layout_t::int_dim_size(const pvar_t &dim) const {
int ret = 1;
for (auto &b : blocks_)
if (b.dim == dim) ret *= b.int_size();
return ret;
}
bool layout_t::has_const_sizes() const {
for (auto &b : blocks_)
if (!b.has_const_size()) return false;
return true;
}
bool layout_t::has_const_strides() const {
for (auto &b : blocks_)
if (!b.has_const_stride()) return false;
return true;
}
tile_t layout_t::int_dim_sizes() const {
tile_t ret;
for (auto &b : blocks_)
ret[b.dim] = ret.get(b.dim, 1) * b.int_size();
return ret;
}
pvar_map_t<expr_t> layout_t::dim_sizes() const {
pvar_map_t<expr_t> ret;
for (auto &b : blocks_)
ret[b.dim] = ret.get(b.dim, 1) * b.size;
return ret;
}
int layout_t::inner_block(const pvar_t &dim, bool with_outer) const {
int ret = 1;
int outer = 1;
for (auto &b : blocks_) {
if (b.dim == dim) {
if (b.has_const_size()) {
ret *= b.int_size();
outer = b.int_size();
} else {
outer = 1;
}
}
}
if (!with_outer) ret /= outer;
return ret;
}
int layout_t::inner_stride() const {
if (nblocks() == 0) return 1;
return blocks_[0].int_stride();
}
expr_t layout_t::stride(const pvar_t &dim, int dim_block_idx) const {
int idx = 0;
for (auto &b : blocks_) {
if (b.dim != dim) continue;
if (idx == dim_block_idx) { return b.stride; }
idx++;
}
return expr_t();
}
expr_t layout_t::shift_in_bytes(const std::vector<int> &block_off) const {
expr_t ret = 0;
for (int i = 0; i < nblocks(); i++) {
auto &b = blocks_[i];
if (block_off[i] != 0) ret += block_off[i] * b.stride;
}
return ret * type_.size();
}
dim_t layout_t::offset_in_bytes(icoord_t coord) const {
gpu_assert(has_const_sizes() && has_const_strides());
dim_t ret = to_cpp<dim_t>(base_);
for (int i = 0; i < nblocks(); i++) {
auto &b = blocks_[i];
dim_t &rem_dim = coord[b.dim];
ret += (rem_dim % b.int_size()) * b.int_stride();
rem_dim /= b.int_size();
}
return ret * type_.size();
}
bool layout_t::is_blocked_by(const pvar_t &dim, int block) const {
if (block == 1) return true;
if (nblocks() == 0) return false;
auto &b = blocks_[0];
if (b.dim != dim) return false;
if (!b.has_const_size()) return false;
return (b.int_size() % block == 0);
}
bool layout_t::is_blocked_by(const layout_t &other) const {
if (other.is_empty()) return true;
gpu_assert(other.type() == type());
if (nblocks() < other.nblocks()) return false;
for (int i = 0; i < other.nblocks(); i++) {
bool is_last = (i == other.nblocks() - 1);
auto &b = blocks()[i];
auto &b_other = other.blocks()[i];
if (b.dim != b_other.dim) return false;
if (!b.has_same_stride(b_other)) return false;
if (is_last && b.has_const_size() && b_other.has_const_size()) {
if (b.int_size() % b_other.int_size() != 0) return false;
} else if (!b.has_same_size(b_other)) {
return false;
}
}
return true;
}
void layout_t::add_block(
const pvar_t &dim, const expr_t &size, const expr_t &_stride) {
if (size.is(1)) return;
expr_t stride = _stride;
if (stride.is_empty()) {
stride = 1;
if (!blocks_.empty()) {
auto &last = blocks_.back();
stride = last.size * last.stride;
if (stride_pad_ != 1 && stride.is<int_imm_t>()) {
stride = utils::rnd_up(to_int(stride), stride_pad_);
}
}
}
blocks_.emplace_back(dim, size, stride);
}
void layout_t::remove(const pvar_t &dim) {
std::vector<block_t> new_blocks;
for (auto &b : blocks_) {
if (b.dim == dim) continue;
new_blocks.push_back(b);
}
auto new_letter_map = desc_.letter_map();
new_letter_map.unset(dim);
desc_ = layout_desc_t(new_letter_map);
blocks_ = std::move(new_blocks);
}
void layout_t::block_by(const std::vector<block_t> &inner_blocks) {
gpu_assert(has_zero_base());
gpu_assert(has_const_sizes());
auto rem_sizes = int_dim_sizes();
for (auto &b : inner_blocks) {
if (!rem_sizes.try_factor(b.dim, b.int_size()))
gpu_error_not_expected();
}
auto old_blocks = std::move(blocks_);
pad(1);
blocks_.clear();
for (auto &b : inner_blocks) {
add_block(b.dim, b.size);
}
for (auto &b : old_blocks) {
dim_t b_size = b.int_size();
bool ok = rem_sizes.try_factor(b.dim, b_size);
if (!ok) {
b_size = math::gcd(b_size, rem_sizes.at(b.dim));
ok = rem_sizes.try_factor(b.dim, b_size);
}
gpu_assert(ok);
if (b_size == 1) continue;
add_block(b.dim, b_size);
}
for (auto &d : rem_sizes)
gpu_assert(rem_sizes.at(d) == 1);
normalize();
}
void layout_t::normalize() {
block_t *prev = nullptr;
expr_t stride = 1;
bool changed = false;
for (int i = 0; i < nblocks(); i++) {
auto &cur = blocks_[i];
if (prev && cur.dim == prev->dim && cur.stride.is_equal(stride)) {
prev->size *= cur.size;
cur.dim = pvar_t();
changed = true;
} else {
prev = &cur;
}
stride = cur.size * cur.stride;
}
if (!changed) return;
std::vector<block_t> new_blocks;
new_blocks.reserve(blocks_.size());
for (auto &b : blocks_) {
if (!b.dim.is_undef()) new_blocks.push_back(b);
}
blocks_ = std::move(new_blocks);
}
layout_t layout_t::split_block(
const block_t *block_ptr, dim_t inner, dim_t outer) const {
std::vector<block_t> split_blocks;
split_blocks.reserve(blocks_.size() + 1);
for (auto &b : blocks_) {
if (&b != block_ptr) {
split_blocks.push_back(b);
continue;
}
gpu_assert(b.has_const_size());
gpu_assert(b.int_size() == inner * outer);
split_blocks.emplace_back(b.dim, inner, b.stride);
split_blocks.emplace_back(b.dim, outer, inner * b.stride);
}
return layout_t(desc(), type(), base(), split_blocks);
}
bool try_div_mod(const expr_t &a, int b, const var_range_info_t &range_info,
expr_t &div, expr_t &mod) {
if (is_const(a)) {
auto _a = to_cpp<dim_t>(a);
if (_a % b != 0) return false;
div = _a / b;
mod = _a % b;
return true;
}
dim_t factor = linear_max_pow2_divisor(a);
if (factor % b == 0) {
div = linear_div(a, b);
mod = expr_t(0);
return true;
}
auto _linear = to_linear(a);
auto &linear = _linear.as<linear_t>();
dim_t c_factor = linear_max_pow2_divisor(linear.c);
if (c_factor % b != 0) return false;
expr_t a_div = linear_div(linear.c, b);
expr_t a_mod;
for (int i = 0; i < linear.nargs(); i++) {
auto &u = linear.u_vec[i];
auto &v = linear.v_vec[i];
dim_t u_factor = linear_max_pow2_divisor(u);
if (u_factor % b == 0) {
a_div += linear_div(u, b) * v;
continue;
}
if (range_info.bound(v) > b) return false;
if (a_mod) return false;
a_mod = v;
}
div = std::move(a_div);
mod = std::move(a_mod);
return true;
}
layout_t layout_t::sub(const dim_mapper_t &dim_mapper, const coord_t &coord,
const tile_t &tile, const var_range_info_t &var_range_info) const {
auto idxs = coord;
auto rem_sizes = tile;
expr_t base = base_;
std::vector<block_t> mapped_blocks;
pvar_map_t<bool> idx_final;
pvar_map_t<int> rem_blocks;
for (auto &d : dims())
rem_blocks[d] = nblocks(d);
for (auto &b : blocks()) {
rem_blocks[b.dim]--;
bool is_outer = rem_blocks[b.dim] == 0;
auto &expr = dim_mapper.expr(b.dim);
auto _linear = to_linear(expr);
auto &linear = _linear.as<linear_t>();
expr_t off = linear.c;
for (int i = 0; i < linear.nargs(); i++) {
auto dim = to_index_pvar(linear.v_vec[i]);
if (!idxs.has(dim)) idxs[dim] = expr_t(0);
if (!rem_sizes.has(dim)) rem_sizes[dim] = 1;
dim_t &cur_size = rem_sizes[dim];
dim_t mapped_size = cur_size;
if (b.has_const_size() && cur_size != 1) {
gpu_assert(linear.nargs() == 1);
int b_size = b.int_size();
if (cur_size % b_size != 0) {
if (b_size % cur_size == 0) {
dim_t inner = cur_size;
dim_t outer = b_size / cur_size;
return split_block(&b, inner, outer)
.sub(dim_mapper, coord, tile, var_range_info);
}
}
if (!is_outer) mapped_size = b_size;
}
if (mapped_size != 1) {
cur_size /= mapped_size;
auto mapped_stride = linear.u_vec[i] * b.stride;
mapped_blocks.emplace_back(dim, mapped_size, mapped_stride);
}
bool is_final = true;
if (b.has_const_size() && !is_outer) {
gpu_assert(off.is(0));
gpu_assert(!idx_final.has(dim));
expr_t div;
expr_t mod;
if (try_div_mod(idxs[dim], b.int_size(), var_range_info, div,
mod)) {
idxs[dim] = std::move(div);
off = std::move(mod);
is_final = false;
}
}
if (is_final) {
gpu_assert(!idx_final.has(dim));
idx_final.set(dim, true);
off += idxs[dim] * linear.u_vec[i];
}
}
base += off * b.stride;
}
return layout_t(dim_mapper.layout_desc(), type(), base, mapped_blocks);
}
layout_t layout_t::make_dense() const {
gpu_assert(has_const_sizes() && has_const_strides());
dim_t stride = 1;
auto new_blocks = blocks_;
for (auto &b : new_blocks) {
b.stride = expr_t(stride);
stride *= b.int_size();
}
return layout_t(desc_, type_, base_, new_blocks);
}
layout_t layout_t::retype(const dsl::type_t &new_type, bool dense) const {
if (new_type == type_) return *this;
auto ret = layout_t(desc_, new_type, base_, blocks_);
if (dense) return ret.make_dense();
return ret;
}
coord_t layout_t::to_coord(const std::vector<int> &block_idx) const {
gpu_assert((int)block_idx.size() == nblocks());
coord_t ret;
tile_t block_sizes;
for (int i = 0; i < nblocks(); i++) {
auto &d = blocks_[i].dim;
if (!block_sizes.has(d)) block_sizes[d] = 1;
auto &blk = block_sizes[d];
ret[d] = ret.get(d, 0) + block_idx[i] * blk;
blk *= blocks_[i].int_size();
}
return ret;
}
int layout_t::to_linear_index(const tile_t &tile, const coord_t &coord) const {
gpu_assert(has_const_sizes());
std::vector<dim_t> tile_blocks;
auto rem_tile = tile;
for (auto &b : blocks_) {
if (!rem_tile.has(b.dim)) rem_tile[b.dim] = 1;
dim_t &rem = rem_tile[b.dim];
dim_t factor = 1;
if (rem != 1 && b.int_size() != 1) {
factor = math::gcd(to_cpp<dim_t>(b.size), rem);
gpu_assert(factor == std::min(to_cpp<dim_t>(b.size), rem));
rem /= factor;
}
tile_blocks.push_back(factor);
}
for (auto &d : rem_tile)
gpu_assert(rem_tile[d] == 1);
int ntiles = ir_utils::safe_div(elems(), tile.elems());
std::vector<int> idx(nblocks());
for (int i = 0; i < ntiles; i++) {
auto i_coord = to_coord(idx);
if (i_coord.drop_defaults() == coord.drop_defaults()) return i;
advance(idx, blocks_, tile_blocks);
}
gpu_error_not_expected();
return -1;
}
std::string layout_t::blocks_str() const {
if (blocks_.empty()) return "(scalar)";
std::string ret;
expr_t stride(1);
pvar_map_t<int> seen;
for (auto &b : blocks_) {
std::string b_str;
char letter = desc_.layout_letter(b.dim);
if (b.has_const_size()) {
b_str = std::to_string(b.int_size());
b_str.append(1, letter);
} else {
b_str.append(1,
seen[b.dim] ? static_cast<char>(std::toupper(letter))
: letter);
}
if (b.has_const_stride() && b.int_stride() != to_int(stride)) {
b_str.append(1, '*');
}
b_str += ret;
std::swap(b_str, ret);
if (b.has_const_size() && b.has_const_stride())
stride = b.stride * b.size;
seen[b.dim] = true;
}
return ret;
}
std::string layout_t::str() const {
if (is_empty()) return "(empty)";
ostringstream_t oss;
oss << blocks_str();
oss << ":" + type().str();
if (!base_.is(0)) {
oss << std::endl;
oss << ir_utils::add_tag("base", base_.str());
}
return oss.str();
}
std::string layout_t::str_with_size(const dsl::hw_t &hw) const {
ostringstream_t oss;
oss << str();
int regs = (hw.ngen_hw() == ngen::HW::Unknown
? 0
: utils::div_up(size(), hw.grf_size()));
oss << " (" << size() << " bytes, ";
oss << regs << " regs)";
return oss.str();
}
void for_each(const tile_t &base_tile, const tile_t &tile,
const std::function<void(const icoord_t &)> &func) {
for_each(base_tile, tile, {}, func);
}
void for_each(const tile_t &base_tile, const tile_t &_tile,
const std::vector<pvar_t> &idx_order,
const std::function<void(const icoord_t &)> &func) {
auto tile = _tile;
for (auto &d : tile) {
gpu_assert(base_tile.has(d));
}
icoord_t idx;
tile_t bound;
dim_t ntiles = 1;
for (auto &d : base_tile) {
if (!tile.has(d)) tile[d] = 1;
idx[d] = 0;
bound[d] = utils::div_up(base_tile[d], tile[d]);
ntiles *= bound[d];
}
for (dim_t i = 0; i < ntiles; i++) {
func(idx);
advance(idx, idx_order, bound, tile);
}
}
block_iterator_t::block_iterator_t(const layout_t &layout, bool set_to_end)
: parent_(&layout), block_idx_(set_to_end ? parent_->nblocks() : 0) {
gpu_assert(layout.has_const_sizes());
if (is_end()) return;
block_ = parent_->blocks().front();
block_.size = 1;
}
block_iterator_t &block_iterator_t::operator++() {
if (!has_next()) {
set_to_end();
return *this;
}
int factor = next_factor();
if (factor != -1) {
elems_ /= block_.int_size();
elems_ *= factor;
block_.size = factor;
return *this;
}
block_idx_++;
block_ = parent_->blocks()[block_idx_];
factor = next_factor(true);
block_.size = factor;
elems_ *= factor;
return *this;
}
block_t block_iterator_t::remaining_block() const {
gpu_assert(!is_end());
auto &b = parent_->blocks()[block_idx_];
int size = b.int_size() / block_.int_size();
auto stride = block_.stride * block_.size;
return block_t(b.dim, size, stride);
}
bool block_iterator_t::is_dense(const prover_t &prover) const {
if (is_end()) return false;
expr_t stride = 1;
for (int i = 0; i < block_idx_; i++) {
auto &b = parent_->blocks()[i];
if (!prover.require(b.stride == stride)) return false;
stride = b.int_size() * b.stride;
}
return prover.require(block_.stride == stride);
}
int block_iterator_t::elems(const pvar_t &dim) const {
if (dim.is_undef()) return elems_;
int ret = 1;
auto &blocks = parent_->blocks();
for (int i = 0; i < block_idx_; i++) {
if (blocks[i].dim == dim) ret *= blocks[i].int_size();
}
if (block_.dim == dim) ret *= block_.int_size();
return ret;
}
layout_t block_iterator_t::sub_layout(int _stride) const {
layout_t ret(parent_->desc(), parent_->type());
expr_t stride = _stride;
for (int i = 0; i < block_idx_; i++) {
ret.add_block(
parent_->blocks()[i].dim, parent_->blocks()[i].size, stride);
stride = expr_t();
}
if (!block_.is_empty()) ret.add_block(block_.dim, block_.size, stride);
return ret;
}
std::string block_iterator_t::str() const {
ostringstream_t oss;
oss << "block_idx: " << block_idx_ << std::endl;
oss << "block: " << block_.brief_str();
return ir_utils::add_tag("block_iterator", oss.str());
}
void block_iterator_t::set_to_end() {
block_idx_ = parent_->nblocks();
block_ = block_t();
elems_ = parent_->elems();
}
int block_iterator_t::next_factor(bool is_first) const {
if (is_end()) return -1;
auto &b = parent_->blocks()[block_idx_];
int size = b.int_size();
int start = (is_first ? 2 : block_.int_size() + 1);
for (int i = start; i <= size; i++) {
if (size % i == 0) { return i; }
}
return -1;
}
void add_remaining_blocks(layout_t &layout, const block_iterator_t &it) {
if (it.is_end()) return;
auto rem_block = it.remaining_block();
layout.add_block(rem_block.dim, rem_block.size);
auto &parent = it.parent();
for (int i = it.block_index() + 1; i < parent.nblocks(); i++) {
auto &b = parent.blocks()[i];
layout.add_block(b.dim, b.size);
}
}
layout_iterator_t::layout_iterator_t(const layout_t &layout, bool is_end)
: parent_(&layout)
, total_elems_(parent_->elems())
, offset_(is_end ? total_elems_ : 0)
, block_off_(parent_->nblocks()) {}
void layout_iterator_t::next(int elems) {
if (!has_next(elems)) {
set_to_end();
return;
}
advance(block_off_, parent_->blocks(), elems);
offset_ += elems;
}
int layout_iterator_t::offset(const pvar_t &dim) const {
int ret = 1;
int stride = 1;
for (int i = 0; i < parent_->nblocks(); i++) {
auto &b = parent_->blocks()[i];
if (b.dim == dim) { ret += stride * block_off_[i]; }
stride *= b.int_size();
}
return ret;
}
icoord_t layout_iterator_t::coord() const {
icoord_t ret;
tile_t sizes;
for (int i = 0; i < parent_->nblocks(); i++) {
auto &b = parent_->blocks()[i];
ret[b.dim] = ret.get(b.dim, 0) + block_off_[i] * sizes.get(b.dim, 1);
sizes[b.dim] = sizes.get(b.dim, 1) * b.int_size();
}
return ret;
}
std::string layout_iterator_t::str() const {
using ir_utils::operator<<;
ostringstream_t oss;
oss << "offset: " << offset_ << std::endl;
oss << "block_off: " << block_off_;
return ir_utils::add_tag("layout_iterator", oss.str());
}
dim_mask_desc_t::dim_mask_desc_t(const pvar_t &dim, const expr_t &expr,
const expr_t &bound, int block, bool has_underflow)
: dim(dim)
, bound(bound)
, block(block)
, has_underflow(has_underflow)
, base(0) {
gpu_assert(math::is_pow2(block));
init_abc_xy(expr);
}
expr_t dim_mask_desc_t::to_expr(const coord_t &coord, bool with_const) const {
expr_t ret = (with_const ? c : 0);
if (coord.has(x_dim)) ret += a * coord[x_dim];
if (!y_dim.is_undef() && coord.has(y_dim)) ret += b * coord[y_dim];
return ret;
}
dim_mask_desc_t dim_mask_desc_t::map(const coord_t &coord) const {
auto ret = *this;
ret.base = simplify_rewrite(to_expr(coord));
if (!is_identity()) return ret;
dim_t x_div = linear_max_pow2_divisor(coord.get(x_dim, 0));
ret.block = math::gcd(block, x_div);
return ret;
}
bool dim_mask_desc_t::has(const pvar_t &dim) const {
return utils::one_of(dim, x_dim, y_dim);
}
expr_t dim_mask_desc_t::dim_stride(const pvar_t &dim) const {
if (dim == x_dim) return a;
if (dim == y_dim) return b;
return expr_t(0);
}
std::string dim_mask_desc_t::str() const {
coord_t dummy_coord;
if (x) dummy_coord[x_dim] = x;
if (y) dummy_coord[y_dim] = y;
auto expr = simplify_rewrite(to_expr(dummy_coord));
ostringstream_t oss;
oss << expr << " < " << bound << " (has_underflow: " << has_underflow << ")"
<< std::endl;
oss << "base: " << base << std::endl;
oss << "block: " << block;
return oss.str();
}
void dim_mask_desc_t::init_abc_xy(const expr_t &expr) {
auto _linear = to_linear(expr);
auto &linear = _linear.as<linear_t>();
c = linear.c;
a = linear.u_vec[0];
x = linear.v_vec[0];
if (linear.nargs() > 1) {
b = linear.u_vec[1];
y = linear.v_vec[1];
}
x_dim = to_index_pvar(x);
y_dim = to_index_pvar(y);
}
mask_desc_t::mask_desc_t(
const dim_mapper_t &dim_mapper, const layout_t &layout) {
auto dim_sizes = layout.dim_sizes();
for (auto &d : dim_sizes) {
auto &expr = dim_mapper.expr(d);
int block = ir_utils::max_pow2_divisor(layout.inner_block(d));
if (block == 1) {
const int large_pow2 = (1 << 10);
block = large_pow2;
}
dim_masks_.emplace_back(d, expr, simplify_rewrite(dim_sizes[d]), block,
dim_mapper.has_underflow(d));
}
}
const dim_mask_desc_t &mask_desc_t::operator[](int idx) const {
gpu_assert(idx >= 0 && idx < nmasks());
return dim_masks_[idx];
}
dim_mask_desc_t &mask_desc_t::operator[](int idx) {
gpu_assert(idx >= 0 && idx < nmasks());
return dim_masks_[idx];
}
mask_desc_t mask_desc_t::map(const coord_t &coord) const {
auto ret = *this;
for (auto &dm : ret.dim_masks_)
dm = dm.map(coord);
return ret;
}
bool mask_desc_t::is_uniform(
const block_iterator_t &it, const prover_t &prover) const {
for (auto &dm : dim_masks_) {
if (!dm.has((*it).dim)) continue;
if (!dm.is_identity()) return false;
int dim_size = it.elems((*it).dim);
gpu_assert(math::is_pow2(dim_size));
if (dim_size > dm.block) return false;
if (!prover.require(dm.bound % dim_size == 0)) return false;
}
return true;
}
std::string mask_desc_t::str() const {
ostringstream_t oss;
for (int i = 0; i < nmasks(); i++) {
if (i != 0) oss << std::endl;
auto tag = "#" + std::to_string(i);
oss << ir_utils::add_tag(tag, dim_masks_[i].str());
}
return oss.str();
}
plane_t::plane_t(const layout_t &layout, const mask_desc_t &mask_desc)
: type(layout.type()) {
const block_t *w_block = nullptr;
const block_t *h_block = nullptr;
for (auto &b : layout.blocks()) {
if (b.has_const_size() && b.int_size() == 1) continue;
if (!w_block) {
if (!b.stride.is(1)) return;
w_block = &b;
continue;
}
if (!h_block) {
h_block = &b;
continue;
}
break;
}
if (!w_block || !h_block) return;
if (!w_block->has_const_size()) return;
if (!h_block->has_const_size()) return;
w_dim = w_block->dim;
h_dim = h_block->dim;
w = w_block->int_size();
h = h_block->int_size();
if (layout.nblocks(w_dim) != 1) return;
if (layout.nblocks(h_dim) != 1) return;
const dim_mask_desc_t *x_mask_desc = nullptr;
const dim_mask_desc_t *y_mask_desc = nullptr;
for (int i = 0; i < mask_desc.nmasks(); i++) {
auto &dmd = mask_desc[i];
if (dmd.has(w_dim)) {
if (x_mask_desc) return;
x_mask_desc = &dmd;
}
if (dmd.has(h_dim)) {
if (y_mask_desc) return;
y_mask_desc = &dmd;
}
}
if (!x_mask_desc || !y_mask_desc) return;
if (!x_mask_desc->dim_stride(w_dim).is(1)) return;
y_stride = y_mask_desc->dim_stride(h_dim);
if (!y_stride.is<int_imm_t>() && !y_stride.is<const_var_t>()) return;
x_dim = x_mask_desc->dim;
y_dim = y_mask_desc->dim;
x = x_mask_desc->base;
y = y_mask_desc->base;
W = x_mask_desc->bound;
H = y_mask_desc->bound;
P = layout.stride(h_dim, 0);
is_valid = true;
}
void grid_splitter_t::add(const expr_t &idx, dim_t size) {
gpu_assert(size > 1);
idxs_.emplace_back(idx, size);
}
bool grid_splitter_t::is_empty() const {
for (auto &idx : idxs_)
if (idx.size != 1) return false;
return true;
}
expr_t grid_splitter_t::pop(int _size) {
expr_t cur = 0;
int size = _size;
for (auto &idx : idxs_) {
if (idx.size == 1) continue;
if (size == 1) break;
cur = size * cur;
cur += idx.pop(size);
}
gpu_assert(size == 1);
return register_index(simplify_rewrite(cur), _size);
}
expr_t grid_splitter_t::index_t::pop(int &n) {
if (n == 1) return 0;
if (size >= n) {
gpu_assert(size % n == 0);
auto ret = (size == n ? expr : expr % n);
expr = (size == n ? 0 : expr / n);
size /= n;
n = 1;
return ret;
}
gpu_assert(n % size == 0);
n = static_cast<int>(n / size);
size = 1;
auto ret = expr;
expr = expr_t(0);
return ret;
}
expr_t grid_splitter_t::register_index(const expr_t &expr, int size) {
if (expr.is<var_t>()) return expr;
int idx = (int)virt_grid_idxs_.size();
auto var = var_t::make(
dsl::type_t::s32(), "virt_grid_idx" + std::to_string(idx));
virt_grid_idxs_.emplace(var, expr);
var_range_info_.set_bound(var, size);
return var;
}
view_t::view_t(const dim_mapper_t &dim_mapper, const layout_t &base_layout,
const coord_t &coord, const tile_t &tile,
const var_range_info_t &var_range_info)
: dim_mapper_(dim_mapper)
, base_layout_(base_layout)
, coord_(coord)
, tile_(tile) {
mask_desc_t base_mask_desc(dim_mapper, base_layout);
layout_ = base_layout.sub(dim_mapper, coord, tile, var_range_info);
mask_desc_ = base_mask_desc.map(coord);
plane_ = plane_t(layout_, mask_desc_);
}
std::string view_t::str() const {
ostringstream_t oss;
oss << ir_utils::add_tag("coord", coord_.str()) << std::endl;
oss << "tile: " << tile_ << std::endl;
oss << ir_utils::add_tag("layout", layout_.str()) << std::endl;
oss << ir_utils::add_tag("mask_desc", mask_desc_.str());
return oss.str();
}
view_t view_t::scatterize(int stride_bytes, const prover_t &prover) const {
if (base_layout_.blocks().empty()) return view_t();
int type_size = base_layout_.type().size();
auto &block0 = base_layout_.blocks()[0];
auto &compress_dim = block0.dim;
if (!block0.has_const_stride() || block0.int_stride() != 1) return view_t();
if (base_layout_.nblocks(compress_dim) != 1) return view_t();
if (!tile_.has(compress_dim)) return view_t();
if (stride_bytes % type_size != 0) return view_t();
int stride = stride_bytes / type_size;
dim_t size = tile_.at(compress_dim);
if (size % stride != 0) return view_t();
int compress_mask_idx = -1;
for (int i = 0; i < mask_desc_.nmasks(); i++) {
auto &dmd = mask_desc_[i];
if (dmd.has(compress_dim)) {
if (compress_mask_idx != -1) return view_t();
if (!dmd.is_identity()) return view_t();
compress_mask_idx = i;
}
}
if (compress_mask_idx != -1) {
auto &dmd = mask_desc_[compress_mask_idx];
gpu_assert(dmd.dim == compress_dim);
gpu_assert(dmd.x_dim == compress_dim);
gpu_assert(dmd.bound.is_equal(block0.size));
if (!prover.require(dmd.base % stride == 0)) return view_t();
if (!prover.require(dmd.bound % stride == 0)) return view_t();
}
auto new_blocks = base_layout_.blocks();
new_blocks[0] = block_t(compress_dim, block0.size / stride, stride);
auto base_layout = layout_t(base_layout_.desc(), base_layout_.type(),
base_layout_.base(), new_blocks);
auto coord = coord_;
auto tile = tile_;
tile[compress_dim] /= stride;
coord[compress_dim] = linear_div(coord[compress_dim], stride);
view_t ret(dim_mapper(), base_layout, coord, tile);
if (compress_mask_idx != -1) {
auto &new_dmd = ret.mask_desc_[compress_mask_idx];
new_dmd.a = expr_t(stride);
new_dmd.base *= stride;
new_dmd.bound = block0.size;
}
return ret;
}
layout_t split_layout(const layout_t &layout, dim_t inner_elems,
dim_t outer_elems, std::vector<int> &inner_block_idxs,
std::vector<int> &outer_block_idxs) {
dim_t cur_elems = 1;
auto in_inner = [&]() { return cur_elems < inner_elems; };
auto in_outer = [&]() {
return cur_elems >= inner_elems
&& cur_elems < inner_elems * outer_elems;
};
inner_block_idxs.clear();
outer_block_idxs.clear();
for (int i = 0; i < layout.nblocks(); i++) {
auto &b = layout.blocks()[i];
int b_size = b.int_size();
gpu_assert(b_size != 1);
if (in_inner()) {
inner_block_idxs.push_back(i);
if (cur_elems * b_size > inner_elems) {
dim_t b_inner = ir_utils::safe_div(inner_elems, cur_elems);
int b_outer = ir_utils::safe_div(b_size, b_inner);
auto new_layout = layout.split_block(&b, b_inner, b_outer);
return split_layout(new_layout, inner_elems, outer_elems,
inner_block_idxs, outer_block_idxs);
}
} else if (in_outer()) {
outer_block_idxs.push_back(i);
if (cur_elems * b_size > inner_elems * outer_elems) {
dim_t b_inner = ir_utils::safe_div(
cur_elems, inner_elems * outer_elems);
int b_outer = ir_utils::safe_div(b_size, b_inner);
auto new_layout = layout.split_block(&b, b_inner, b_outer);
return split_layout(new_layout, inner_elems, outer_elems,
inner_block_idxs, outer_block_idxs);
}
} else {
break;
}
cur_elems *= b_size;
}
return layout;
}
view_t view_t::split(const dim_mapper_t &dim_mapper,
const layout_t &base_layout, const coord_t &_coord, const tile_t &_tile,
grid_splitter_t &grid_splitter) {
auto coord = dim_mapper.layout_desc().filter_dim_map(_coord);
auto tile = dim_mapper.layout_desc().filter_dim_map(_tile);
tile_t split_tile = tile;
coord_t split_coord = coord;
int outer_elems = grid_splitter.size();
dim_t inner_elems = tile.elems() / outer_elems;
std::vector<int> inner_idxs;
std::vector<int> outer_idxs;
auto layout = split_layout(base_layout.sub(dim_mapper, coord, tile),
inner_elems, outer_elems, inner_idxs, outer_idxs);
tile_t inner_dims;
for (int i = 0; i < layout.nblocks(); i++) {
auto &b = layout.blocks()[i];
if (!inner_dims.has(b.dim)) inner_dims[b.dim] = 1;
if (std::find(outer_idxs.begin(), outer_idxs.end(), i)
!= outer_idxs.end()) {
int b_size = b.int_size();
split_tile[b.dim]
= ir_utils::safe_div(split_tile.at(b.dim), b_size);
if (!split_coord.has(b.dim)) split_coord[b.dim] = expr_t(0);
split_coord[b.dim]
+= grid_splitter.pop(b_size) * inner_dims.at(b.dim);
split_coord[b.dim] = simplify_rewrite(split_coord[b.dim]);
}
inner_dims[b.dim] *= b.int_size();
}
gpu_assert(grid_splitter.is_empty());
return view_t(dim_mapper, base_layout, split_coord, split_tile,
grid_splitter.var_range_info());
}
} } } } } }