blob: 52d122c93d006d3dde28581220aa8a33d37ba8a9 [file] [edit]
#include "expr.h"
#include "simplify.h"
namespace slinky {
std::string node_context::name(symbol_id i) const {
if (i < id_to_name.size()) {
return id_to_name[i];
} else {
return "<" + std::to_string(i) + ">";
}
}
symbol_id node_context::insert(const std::string& name) {
std::optional<symbol_id> id = lookup(name);
if (!id) {
id = id_to_name.size();
id_to_name.push_back(name);
}
return *id;
}
symbol_id node_context::insert_unique(const std::string& prefix) {
symbol_id id = id_to_name.size();
id_to_name.push_back(prefix + std::to_string(id));
return id;
}
std::optional<symbol_id> node_context::lookup(const std::string& name) const {
// TODO: At some point we might need a better data structure than doing this linear search.
for (symbol_id i = 0; i < id_to_name.size(); ++i) {
if (id_to_name[i] == name) {
return i;
}
}
return {};
}
template <typename T>
const T* make_bin_op(expr a, expr b) {
auto n = new T();
n->a = std::move(a);
n->b = std::move(b);
return n;
}
template <typename T, typename Body>
const T* make_let(symbol_id name, expr value, Body body) {
auto n = new T();
n->name = name;
n->value = std::move(value);
n->body = std::move(body);
return n;
}
expr let::make(symbol_id name, expr value, expr body) { return make_let<let>(name, std::move(value), std::move(body)); }
stmt let_stmt::make(symbol_id name, expr value, stmt body) {
return make_let<let_stmt>(name, std::move(value), std::move(body));
}
// TODO(https://github.com/dsharlet/slinky/issues/4): At this time, the top CPU user
// of simplify_fuzz is malloc/free. Perhaps caching common values of variables (yes
// we can cache variables!) would be worth doing.
const variable* make_variable(symbol_id name) {
auto n = new variable();
n->name = name;
return n;
}
const constant* make_constant(index_t value) {
auto n = new constant();
n->value = value;
return n;
}
expr::expr(index_t value) : expr(make_constant(value)) {}
expr variable::make(symbol_id name) { return make_variable(name); }
expr wildcard::make(symbol_id name, std::function<bool(const expr&)> matches) {
auto n = new wildcard();
n->name = name;
n->matches = std::move(matches);
return n;
}
expr constant::make(index_t value) { return make_constant(value); }
expr constant::make(const void* value) { return make(reinterpret_cast<index_t>(value)); }
expr add::make(expr a, expr b) { return make_bin_op<add>(std::move(a), std::move(b)); }
expr sub::make(expr a, expr b) { return make_bin_op<sub>(std::move(a), std::move(b)); }
expr mul::make(expr a, expr b) { return make_bin_op<mul>(std::move(a), std::move(b)); }
expr div::make(expr a, expr b) { return make_bin_op<div>(std::move(a), std::move(b)); }
expr mod::make(expr a, expr b) { return make_bin_op<mod>(std::move(a), std::move(b)); }
expr min::make(expr a, expr b) { return make_bin_op<min>(std::move(a), std::move(b)); }
expr max::make(expr a, expr b) { return make_bin_op<max>(std::move(a), std::move(b)); }
expr equal::make(expr a, expr b) { return make_bin_op<equal>(std::move(a), std::move(b)); }
expr not_equal::make(expr a, expr b) { return make_bin_op<not_equal>(std::move(a), std::move(b)); }
expr less::make(expr a, expr b) { return make_bin_op<less>(std::move(a), std::move(b)); }
expr less_equal::make(expr a, expr b) { return make_bin_op<less_equal>(std::move(a), std::move(b)); }
expr logical_and::make(expr a, expr b) { return make_bin_op<logical_and>(std::move(a), std::move(b)); }
expr logical_or::make(expr a, expr b) { return make_bin_op<logical_or>(std::move(a), std::move(b)); }
expr logical_not::make(expr x) {
logical_not* n = new logical_not();
n->x = std::move(x);
return n;
}
expr operator+(expr a, expr b) { return add::make(std::move(a), std::move(b)); }
expr operator-(expr a, expr b) { return sub::make(std::move(a), std::move(b)); }
expr operator*(expr a, expr b) { return mul::make(std::move(a), std::move(b)); }
expr operator/(expr a, expr b) { return div::make(std::move(a), std::move(b)); }
expr operator%(expr a, expr b) { return mod::make(std::move(a), std::move(b)); }
expr min(expr a, expr b) { return min::make(std::move(a), std::move(b)); }
expr max(expr a, expr b) { return max::make(std::move(a), std::move(b)); }
expr clamp(expr x, expr a, expr b) { return min::make(max::make(std::move(x), std::move(a)), std::move(b)); }
expr select(expr c, expr t, expr f) { return select::make(std::move(c), std::move(t), std::move(f)); }
expr operator==(expr a, expr b) { return equal::make(std::move(a), std::move(b)); }
expr operator!=(expr a, expr b) { return not_equal::make(std::move(a), std::move(b)); }
expr operator<(expr a, expr b) { return less::make(std::move(a), std::move(b)); }
expr operator<=(expr a, expr b) { return less_equal::make(std::move(a), std::move(b)); }
expr operator>(expr a, expr b) { return less::make(std::move(b), std::move(a)); }
expr operator>=(expr a, expr b) { return less_equal::make(std::move(b), std::move(a)); }
expr operator&&(expr a, expr b) { return logical_and::make(std::move(a), std::move(b)); }
expr operator||(expr a, expr b) { return logical_or::make(std::move(a), std::move(b)); }
expr operator!(expr x) { return logical_not::make(std::move(x)); }
expr min(std::span<expr> x) {
if (x.empty()) {
return expr();
} else if (x.size() == 1) {
return x[0];
} else {
return min(x[0], min(x.subspan(1)));
}
}
expr max(std::span<expr> x) {
if (x.empty()) {
return expr();
} else if (x.size() == 1) {
return x[0];
} else {
return max(x[0], max(x.subspan(1)));
}
}
const interval_expr& interval_expr::all() {
static interval_expr x = {negative_infinity(), positive_infinity()};
return x;
}
const interval_expr& interval_expr::none() {
static interval_expr x = {positive_infinity(), negative_infinity()};
return x;
}
const interval_expr& interval_expr::union_identity() { return none(); }
const interval_expr& interval_expr::intersection_identity() { return all(); }
interval_expr& interval_expr::operator*=(const expr& scale) {
if (is_non_negative(scale)) {
min = simplify(static_cast<mul*>(nullptr), min, scale);
max = simplify(static_cast<mul*>(nullptr), max, scale);
} else if (is_negative(scale)) {
std::swap(min, max);
min = simplify(static_cast<mul*>(nullptr), min, scale);
max = simplify(static_cast<mul*>(nullptr), max, scale);
} else {
min = simplify(static_cast<mul*>(nullptr), min, scale);
max = simplify(static_cast<mul*>(nullptr), max, scale);
*this |= bounds(max, min);
}
return *this;
}
interval_expr& interval_expr::operator/=(const expr& scale) {
if (is_non_negative(scale)) {
min = simplify(static_cast<div*>(nullptr), min, scale);
max = simplify(static_cast<div*>(nullptr), max, scale);
} else if (is_negative(scale)) {
std::swap(min, max);
min = simplify(static_cast<div*>(nullptr), min, scale);
max = simplify(static_cast<div*>(nullptr), max, scale);
} else {
min = simplify(static_cast<div*>(nullptr), min, scale);
max = simplify(static_cast<div*>(nullptr), max, scale);
*this |= bounds(max, min);
}
return *this;
}
interval_expr& interval_expr::operator+=(const expr& offset) {
min = simplify(static_cast<add*>(nullptr), min, offset);
max = simplify(static_cast<add*>(nullptr), max, offset);
return *this;
}
interval_expr& interval_expr::operator-=(const expr& offset) {
min = simplify(static_cast<sub*>(nullptr), min, offset);
max = simplify(static_cast<sub*>(nullptr), max, offset);
return *this;
}
interval_expr interval_expr::operator*(const expr& scale) const {
interval_expr result(*this);
result *= scale;
return result;
}
interval_expr interval_expr::operator/(const expr& scale) const {
interval_expr result(*this);
result /= scale;
return result;
}
interval_expr interval_expr::operator+(const expr& offset) const {
interval_expr result(*this);
result += offset;
return result;
}
interval_expr interval_expr::operator-(const expr& offset) const {
interval_expr result(*this);
result -= offset;
return result;
}
interval_expr interval_expr::operator-() const { return {-max, -min}; }
interval_expr& interval_expr::operator|=(const interval_expr& r) {
min = simplify(static_cast<class min*>(nullptr), min, r.min);
max = simplify(static_cast<class max*>(nullptr), max, r.max);
return *this;
}
interval_expr& interval_expr::operator&=(const interval_expr& r) {
min = simplify(static_cast<class max*>(nullptr), min, r.min);
max = simplify(static_cast<class min*>(nullptr), max, r.max);
return *this;
}
interval_expr interval_expr::operator|(const interval_expr& r) const {
interval_expr result(*this);
result |= r;
return result;
}
interval_expr interval_expr::operator&(const interval_expr& r) const {
interval_expr result(*this);
result &= r;
return result;
}
box_expr operator|(box_expr a, const box_expr& b) {
assert(a.size() == b.size());
for (std::size_t i = 0; i < a.size(); ++i) {
a[i] |= b[i];
}
return a;
}
box_expr operator&(box_expr a, const box_expr& b) {
assert(a.size() == b.size());
for (std::size_t i = 0; i < a.size(); ++i) {
a[i] &= b[i];
}
return a;
}
expr select::make(expr condition, expr true_value, expr false_value) {
auto n = new select();
n->condition = std::move(condition);
n->true_value = std::move(true_value);
n->false_value = std::move(false_value);
return n;
}
expr call::make(slinky::intrinsic i, std::vector<expr> args) {
auto n = new call();
n->intrinsic = i;
n->args = std::move(args);
return n;
}
stmt call_func::make(
call_func::callable target, std::vector<expr> scalar_args, std::vector<symbol_id> buffer_args, const func* fn) {
auto n = new call_func();
n->target = std::move(target);
n->scalar_args = std::move(scalar_args);
n->buffer_args = std::move(buffer_args);
n->fn = fn;
return n;
}
stmt block::make(stmt a, stmt b) {
auto n = new block();
n->a = std::move(a);
n->b = std::move(b);
return n;
}
stmt loop::make(symbol_id name, interval_expr bounds, stmt body) {
auto l = new loop();
l->name = name;
l->bounds = std::move(bounds);
l->body = std::move(body);
return l;
}
stmt if_then_else::make(expr condition, stmt true_body, stmt false_body) {
auto n = new if_then_else();
n->condition = std::move(condition);
n->true_body = std::move(true_body);
n->false_body = std::move(false_body);
return n;
}
stmt allocate::make(memory_type storage, symbol_id name, std::size_t elem_size, std::vector<dim_expr> dims, stmt body) {
auto n = new allocate();
n->storage = storage;
n->name = name;
n->elem_size = elem_size;
n->dims = std::move(dims);
n->body = std::move(body);
return n;
}
stmt make_buffer::make(symbol_id name, expr base, expr elem_size, std::vector<dim_expr> dims, stmt body) {
auto n = new make_buffer();
n->name = name;
n->base = std::move(base);
n->elem_size = std::move(elem_size);
n->dims = std::move(dims);
n->body = std::move(body);
return n;
}
stmt crop_buffer::make(symbol_id name, std::vector<interval_expr> bounds, stmt body) {
auto n = new crop_buffer();
n->name = name;
n->bounds = std::move(bounds);
n->body = std::move(body);
return n;
}
stmt crop_dim::make(symbol_id name, int dim, interval_expr bounds, stmt body) {
auto n = new crop_dim();
n->name = name;
n->dim = dim;
n->bounds = std::move(bounds);
n->body = std::move(body);
return n;
}
stmt slice_buffer::make(symbol_id name, std::vector<expr> at, stmt body) {
auto n = new slice_buffer();
n->name = name;
n->at = std::move(at);
n->body = std::move(body);
return n;
}
stmt slice_dim::make(symbol_id name, int dim, expr at, stmt body) {
auto n = new slice_dim();
n->name = name;
n->dim = dim;
n->at = std::move(at);
n->body = std::move(body);
return n;
}
stmt truncate_rank::make(symbol_id name, int rank, stmt body) {
auto n = new truncate_rank();
n->name = name;
n->rank = rank;
n->body = std::move(body);
return n;
}
stmt check::make(expr condition) {
auto n = new check();
n->condition = std::move(condition);
return n;
}
const expr& positive_infinity() {
static expr e = call::make(intrinsic::positive_infinity, {});
return e;
}
const expr& negative_infinity() {
static expr e = call::make(intrinsic::negative_infinity, {});
return e;
}
const expr& indeterminate() {
static expr e = call::make(intrinsic::indeterminate, {});
return e;
}
expr abs(expr x) { return call::make(intrinsic::abs, {std::move(x)}); }
expr buffer_rank(expr buf) { return call::make(intrinsic::buffer_rank, {std::move(buf)}); }
expr buffer_base(expr buf) { return call::make(intrinsic::buffer_base, {std::move(buf)}); }
expr buffer_elem_size(expr buf) { return call::make(intrinsic::buffer_elem_size, {std::move(buf)}); }
expr buffer_min(expr buf, expr dim) { return call::make(intrinsic::buffer_min, {std::move(buf), std::move(dim)}); }
expr buffer_max(expr buf, expr dim) { return call::make(intrinsic::buffer_max, {std::move(buf), std::move(dim)}); }
expr buffer_extent(expr buf, expr dim) { return call::make(intrinsic::buffer_extent, {std::move(buf), std::move(dim)}); }
expr buffer_stride(expr buf, expr dim) { return call::make(intrinsic::buffer_stride, {std::move(buf), std::move(dim)}); }
expr buffer_fold_factor(expr buf, expr dim) {
return call::make(intrinsic::buffer_fold_factor, {std::move(buf), std::move(dim)});
}
expr buffer_at(expr buf, const std::vector<expr>& at) {
std::vector<expr> args = {buf};
args.insert(args.end(), at.begin(), at.end());
return call::make(intrinsic::buffer_at, std::move(args));
}
expr buffer_at(expr buf, const std::vector<var>& at) {
std::vector<expr> args = {buf};
args.reserve(at.size() + 1);
for (const var& i : at) {
args.push_back(i);
}
return call::make(intrinsic::buffer_at, std::move(args));
}
interval_expr buffer_bounds(const expr& buf, const expr& dim) { return {buffer_min(buf, dim), buffer_max(buf, dim)}; }
dim_expr buffer_dim(const expr& buf, const expr& dim) {
return {buffer_bounds(buf, dim), buffer_stride(buf, dim), buffer_fold_factor(buf, dim)};
}
bool is_buffer_intrinsic(intrinsic i) {
switch (i) {
case intrinsic::buffer_rank:
case intrinsic::buffer_base:
case intrinsic::buffer_elem_size:
case intrinsic::buffer_size_bytes:
case intrinsic::buffer_min:
case intrinsic::buffer_max:
case intrinsic::buffer_stride:
case intrinsic::buffer_fold_factor:
case intrinsic::buffer_extent:
case intrinsic::buffer_at: return true;
default: return false;
}
}
var::var() {}
var::var(symbol_id name) : e_(variable::make(name)) {}
var::var(node_context& ctx, const std::string& name) : e_(variable::make(ctx.insert(name))) {}
} // namespace slinky