3#include <fe/worklist.h>
24bool contains_pi(
const Def* t) {
25 if (
t->isa<Pi>())
return true;
26 if (
auto sig =
t->isa_imm<Sigma>())
27 for (
auto op : sig->ops())
28 if (contains_pi(op))
return true;
36const Def* splat_scalar(
const Def* d) {
37 if (!
d->isa<Pack>())
return nullptr;
38 while (
auto pack =
d->isa<Pack>())
40 return (
d->is_closed() && !
d->type()->isa<Arr>()) ?
d :
nullptr;
44bool wants_fresh_mem(
const App* app) {
54Lam* param_of(
const Def* d) {
55 while (
auto ex =
d->isa<Extract>())
57 if (
auto var =
d->isa<Var>())
return var->binder()->isa_mut<
Lam>();
62bool holds_buf(
const Def* d) {
64 if (
auto tuple =
d->isa<Tuple>())
return std::ranges::any_of(tuple->ops(), holds_buf);
69const Def* scalar_result(
const App* app,
const Def* mem,
const Def*
buf) {
70 if (app->type()->isa<Arr>())
return buf;
76void LowerToMem::collect_tensor_types() {
79 auto gate = [](
const char* why,
const Def* culprit) { fe::throwf(
"cannot bufferize: {} (`{}`)", why, culprit); };
80 auto elem = [&gate](
const Def* T) {
81 if (T->isa<Arr>()) gate(
"tensor with array element type", T);
85 auto add_tensor_ty = [
this](
const Def*
t) {
86 if (
t->isa<Arr>()) tensor_ty_.emplace(t);
89 fe::BFSWorklist<DefSet> wl;
90 auto push = [&wl](
const Def*
d) {
94 for (
auto mut :
old_world().externals().muts())
100 if (
auto app = def->isa<App>()) {
101 auto [axm, curry, trip] =
Axm::get(app);
102 bool tensor_op = axm && curry == 0 && axm->plugin() == tensor::Plugin_Id;
103 ops_seen_ |= tensor_op;
107 add_tensor_ty(app->type());
111 add_tensor_ty(app->type());
113 auto [meta, s_out] = app->callee()->as<
App>()->uncurry_args<2>();
114 auto [T,
r] = meta->projs<2>();
116 if (!
Lit::isa<u64>(r)) gate(
"non-literal rank of `tensor.generate`", app);
117 add_tensor_ty(app->type());
120 add_tensor_ty(app->type());
122 auto [is, post_is] = app->arg()->projs<2>();
123 elem(mr.To), elem(mr.Tp);
125 for (
u64 i = 0; i < *nis_l; ++i) {
126 elem(mr.Tis->proj(*nis_l, i));
127 add_tensor_ty(is->proj(*nis_l, i)->type());
130 for (
u64 j = 0; j < *nps_l; ++j) {
131 elem(mr.Tps->proj(*nps_l, j));
132 add_tensor_ty(post_is->proj(*nps_l, j)->type());
138 add_tensor_ty(app->type());
139 add_tensor_ty(app->arg()->proj(3, 2)->type());
142 auto [Tr, s_in, params] = app->callee()->as<
App>()->uncurry_args<3>();
143 auto [T,
r] = Tr->projs<2>();
144 auto [
mode, lo, hi] = params->projs<3>();
147 add_tensor_ty(app->type());
148 add_tensor_ty(app->arg()->proj(2, 0)->type());
151 auto [TnisR, ax, Sis] = app->callee()->as<
App>()->uncurry_args<3>();
152 auto [T, nis,
r] = TnisR->projs<3>();
157 if (!nis_l || !r_l || !ax_l) {
158 gate(
"non-literal nis/rank/axis of `tensor.concat`", app);
160 add_tensor_ty(app->type());
161 for (
u64 i = 0; i < *nis_l; ++i) {
164 gate(
"non-literal extent along the concat axis", app);
165 add_tensor_ty(app->arg()->proj(*nis_l, i)->type());
172 auto [Tr, shapes, dim] = app->callee()->as<
App>()->uncurry_args<3>();
173 auto [T,
r] = Tr->projs<2>();
176 add_tensor_ty(app->type());
177 add_tensor_ty(app->arg()->proj(2, 0)->type());
178 add_tensor_ty(app->arg()->proj(2, 1)->type());
180 auto [Tr, shapes, dim] = app->callee()->as<
App>()->uncurry_args<3>();
181 auto [T,
r] = Tr->projs<2>();
184 add_tensor_ty(app->type());
185 add_tensor_ty(app->arg()->proj(3, 0)->type());
186 add_tensor_ty(app->arg()->proj(3, 1)->type());
187 add_tensor_ty(app->arg()->proj(3, 2)->type());
188 }
else if (tensor_op) {
190 gate(
"unbufferizable tensor op", app);
196 if (is_tensor_op(app)) {
197 auto args = fe::BFSWorklist<DefSet>();
198 for (
const App* a = app;
a;
a =
a->callee()->isa<App>())
200 while (!
args.empty()) {
202 if (
auto k =
d->isa_mut<Lam>()) op_args_.emplace(k);
203 for (
auto op :
d->ops())
204 if (op)
args.push(op);
209 for (
auto op : def->ops())
214 for (
auto mut :
old_world().externals().muts())
215 if (
auto lam = mut->isa_mut<Lam>(); lam && is_tensor_fn(lam)) tensor_fns_.emplace(lam);
218 if (tensor_fns_.empty() && !ops_seen_)
return;
222 for (
auto old_fn : tensor_fns_) {
223 auto dom = old_fn->type()->dom();
224 auto n = dom->num_projs();
225 for (
size_t i = 0; i != n; ++i)
226 if (
auto pi =
Pi::isa_cn(dom->proj(n, i)); pi && contains_pi(pi->dom()))
227 return gate(
"higher-order bufferized function", old_fn);
232 auto covers_every_axis = [
this](
const Def* agg,
const Def* index) {
233 auto arr = agg->type()->isa<
Arr>();
234 if (!arr || !tensor_ty_.contains(arr))
return true;
235 auto r = arr->shape().rank();
236 auto k =
Shape(index).rank();
237 return !
r || !k || *k >= *
r;
241 fe::BFSWorklist<DefSet> wl2;
242 for (
auto mut :
old_world().externals().muts())
244 while (!wl2.empty()) {
245 auto def = wl2.pop();
247 if (
auto ex = def->isa<Extract>(); ex && !covers_every_axis(ex->tuple(), ex->index()))
248 ex->blame(
"cannot bufferize: partial tensor read yields a sub-tensor").bail();
249 if (
auto in = def->isa<Insert>(); in && !covers_every_axis(in->tuple(), in->index()))
250 in->blame(
"cannot bufferize: partial tensor write targets a sub-tensor").bail();
252 for (
auto op : def->ops()) {
254 if (
auto fn =
op->isa_mut<Lam>()) {
257 if (tensor_fns_.contains(fn))
258 if (!(def->isa<App>() && def->as<App>()->callee() == op) && !def->isa<Var>())
259 return gate(
"bufferized function used as a value", fn);
260 if (!tensor_fns_.contains(fn) && !op_args_.contains(fn) && mentions_tensor(fn->type()->dom())) {
264 if (fn->is_external() || !
Pi::isa_cn(fn->type()))
265 return gate(
"unconvertible tensor-typed function", fn);
270 if (def->type()) wl2.push(def->type());
275 collect_tensor_types();
277 if (tensor_fns_.empty() && !ops_seen_)
return;
280 assert(pending_.empty());
283const Def* LowerToMem::buf_of(
const Def* arr_ty) {
285 auto arr = arr_ty->isa<
Arr>();
291const Def* LowerToMem::bot_mem() {
293 return w.bot(w.call<
mem::M>(0));
296const Def* LowerToMem::fresh_mem() {
298 pending_.push_back(k);
302const Def* LowerToMem::wrap_fresh_mem(
const Def* body) {
305 for (
auto k : pending_ | std::views::reverse) {
307 body = w.app(w.annex<
mem::fresh>(), w.tuple({w.lit_nat_0(), k}));
316 auto app = old_def->isa<
App>();
317 auto ins = old_def->isa<
Insert>();
318 if ((app && wants_fresh_mem(app)) || (ins && tensor_ty_.contains(ins->tuple()->
type()))) {
319 if (
auto new_def = fe::lookup(fresh_memo_, old_def))
return new_def;
321 fresh_memo_.emplace(old_def, new_def);
327bool LowerToMem::mentions_tensor(
const Def* t)
const {
328 if (tensor_ty_.contains(t))
return true;
329 if (
auto sig = t->isa<
Sigma>()) {
330 for (
auto op : sig->ops())
331 if (mentions_tensor(op))
return true;
332 }
else if (
auto pi = t->isa<
Pi>()) {
333 return mentions_tensor(pi->dom());
338bool LowerToMem::is_tensor_fn(
Lam* lam)
const {
342const Def* LowerToMem::conv_boundary(
const Def* t) {
343 if (tensor_ty_.contains(t))
return buf_of(t);
344 if (
auto sig = t->isa_imm<Sigma>(); sig && mentions_tensor(sig)) {
345 auto n = sig->num_ops();
347 for (
size_t i = 0; i != n; ++i)
348 ops[i] = conv_boundary(sig->op(i));
358 auto _ = fe::Restore(pending_, {});
359 auto __ = fe::Restore(fresh_memo_, {});
362 if (!lam->
is_set())
return new_lam;
364 auto ___ =
enter(lam);
367 new_lam->set(filter, body);
368 return seal(lam, new_lam);
371const Pi* LowerToMem::conv_type(
Lam* lam) {
375 if (is_tensor_fn(lam)) {
378 DefVec doms(n, [&](
size_t i) ->
const Def* {
379 auto d = dom->proj(n, i);
380 if (
auto pi =
Pi::isa_cn(d))
return w.cn(conv_boundary(pi->dom()));
381 return conv_boundary(d);
383 return w.cn(w.sigma(doms));
390 if (
auto pi =
Pi::isa_cn(lam->
type()); pi && mentions_tensor(pi->dom()))
return w.cn(conv_boundary(pi->dom()));
410 if (
auto callee = app->
callee()->
isa_mut<
Lam>(); callee && tensor_fns_.contains(callee))
411 return lower_call(app, callee);
418 auto elementwise = [&](
const Def* callee) {
420 if (!lam) lam = param_of(callee);
421 return lam && op_args_.contains(lam);
431const Def* LowerToMem::lower_call(
const App* app,
Lam* old_callee) {
433 auto new_callee =
rewrite(old_callee);
434 auto dom = old_callee->
type()->
dom();
438 for (
size_t i = 0; i != n; ++i) {
439 auto d = dom->proj(n, i);
440 auto a = app->
arg()->
proj(n, i);
445 return w.app(new_callee, w.tuple(
args));
448const Def* LowerToMem::splat_buffer(
const Def* arr_ty,
const Def* scalar) {
456const Def* LowerToMem::materialize(
const Def* old_ty,
const Def* old_arg) {
458 if (tensor_ty_.contains(old_ty)) {
461 if (
auto c = splat_scalar(old_arg))
return splat_buffer(old_ty,
rewrite(c));
465 const Def* culprit = old_arg;
467 if (
auto lam = param_of(old_arg->
op(0))) culprit = lam;
468 culprit->
blame(
"cannot bufferize: tensor parameter was split into parts; annotate it with `tensor.buf`")
475 if (
auto sig = old_ty->
isa_imm<Sigma>(); sig && mentions_tensor(sig)) {
476 auto n = sig->num_ops();
478 for (
size_t i = 0; i != n; ++i)
479 ops[i] = materialize(sig->op(i), old_arg->
proj(n, i));
485const Def* LowerToMem::to_buffer(
const Def* val,
const Def* old) {
487 return materialize(old->type(), old);
490const Def* LowerToMem::buffer_list(
const Def* list,
const Def* old_list,
const Def* n) {
492 if (!n_l)
return list;
494 DefVec ins(*n_l, [&](
size_t i) {
return to_buffer(list->proj(*n_l, i), old_list->proj(*n_l, i)); });
500std::pair<const Def*, const Def*> LowerToMem::peel_tensor(
const Def* d) {
504 if (!ex || !tensor_ty_.contains(ex->tuple()->type()))
return {
nullptr,
nullptr};
505 return {ex->tuple(),
rewrite(ex->index())};
512 auto [old_arr, index] = peel_tensor(extract);
526 auto arr =
rewrite(insert->tuple());
529 auto [br, bs, bT] =
buf->args<3>();
530 auto x =
rewrite(insert->value());
531 auto idx =
rewrite(insert->index());
540const Def* LowerToMem::lower_splat(
const App* app) {
541 auto value = app->
arg()->
proj(2, 1);
550const Def* LowerToMem::lower_generate(
const App* app) {
553 auto [T, r] = meta->projs<2>();
561 if (!app->
type()->isa<
Arr>()) {
562 DefVec zeros(rn, [&](
size_t) {
return w.lit_i64(0); });
563 return w.call(body, w.tuple(zeros));
566 auto out_ty = buf_of(app->
type());
568 auto result_ty = w.sigma({w.call<
mem::M>(0), out_ty});
569 auto compute = [&](
Defs iters,
const Def*,
const Def* m) -> std::pair<const Def*, const Def*> {
570 return {m, w.call(body, iters)};
576const Def* LowerToMem::lower_broadcast(
const App* app) {
581 auto arg =
rewrite(app->arg());
582 auto [s_in, s_out, input] = arg->projs<3>();
583 auto [T,
r] =
c->args<2>();
586 if (s_in == s_out)
return input;
588 input = to_buffer(input, app->arg()->proj(3, 2));
593 if (!in_buf)
return splat_buffer(app->type(), input);
596 auto [bri, bsi, biT] = in_buf->args<3>();
602 op =
w.app(op,
w.tuple({T, bri, bsi, bro, bso, r}));
603 op =
w.app(op,
w.tuple({s_in, s_out}));
605 auto [m, out] =
w.app(op,
w.tuple({am, dst, input}))->projs<2>();
609const Def* LowerToMem::lower_map_reduce(
const App* app) {
615 auto [is, post_is] =
args->projs<2>();
622 auto [old_is, old_post_is] = app->arg()->projs<2>();
623 is = buffer_list(is, old_is, mr.nis);
624 post_is = buffer_list(post_is, old_post_is, mr.nps);
629 auto mem_ty =
w.call<
mem::M>(0);
630 auto inner = mr.comb->type()->as<
Pi>()->dom()->proj(2, 0);
631 auto [cTo, ins_ty] = inner->projs<2>();
632 auto memcomb =
w.mut_fun(
w.sigma({mem_ty, cTo, ins_ty}),
w.sigma({mem_ty, cTo}))->set(
"memComb");
633 auto [cdata, cret] = memcomb->vars<2>();
634 auto [cm, cacc, cins] = cdata->projs<3>();
635 auto after =
w.mut_con(cTo)->set(
"afterComb");
636 after->app(
true, cret,
w.tuple({cm, after->var()}));
637 memcomb->set(
true,
w.app(mr.comb,
w.tuple({w.tuple({cacc, cins}), after})));
642 auto pin = mr.post->type()->as<
Pi>()->dom()->proj(2, 0);
643 auto [pTo, pexts_ty] = pin->projs<2>();
644 auto mempost =
w.mut_fun(
w.sigma({mem_ty, pTo, pexts_ty}),
w.sigma({mem_ty, pTp}))->set(
"memPost");
645 auto [pdata, pret] = mempost->vars<2>();
646 auto [pm, pacc, pexts] = pdata->projs<3>();
647 auto pafter =
w.mut_con(pTp)->set(
"afterPost");
648 pafter->app(
true, pret,
w.tuple({pm, pafter->var()}));
649 mempost->set(
true,
w.app(mr.post,
w.tuple({
w.tuple({pacc, pexts}), pafter})));
655 op =
w.app(op, mr.nis_nps);
656 op =
w.app(op, mr.meta);
657 op =
w.app(op, mr.shapes);
658 op =
w.app(op, mr.in_tys);
659 op =
w.app(op,
w.tuple({memcomb, mr.init, mempost}));
660 op =
w.app(op, mr.map_out);
661 op =
w.app(op, mr.maps_all);
663 auto [m, out] =
w.app(op,
w.tuple({am, dst, is, post_is}))->projs<2>();
664 return scalar_result(app, m, out);
667const Def* LowerToMem::lower_pad(
const App* app) {
670 auto&
w = new_world();
671 auto c = rewrite(app->callee())->as<
App>();
672 auto [input, value] = rewrite(app->arg())->projs<2>();
674 auto [Tr, s_in, params] =
c->uncurry_args<3>();
675 auto [T,
r] = Tr->projs<2>();
676 auto [
mode, lo, hi] = params->projs<3>();
678 input = to_buffer(input, app->arg()->proj(2, 0));
688 DefVec so(*r_l, [&](
size_t d) {
return add(
add(lo->proj(*r_l, d), s_in->proj(*r_l, d)), hi->proj(*r_l, d)); });
689 auto s_out =
w.tuple(so);
697const Def* LowerToMem::lower_concat(
const App* app) {
700 auto&
w = new_world();
701 auto c = rewrite(app->callee())->as<
App>();
702 auto arg = rewrite(app->arg());
704 auto [TnisR, ax, Sis] =
c->uncurry_args<3>();
705 auto [T, nis,
r] = TnisR->projs<3>();
712 auto inputs = buffer_list(arg, app->arg(), nis);
719 for (
u64 i = 0; i < *nis_l; ++i) {
724 DefVec so(*r_l, [&](
size_t d) {
return d == *ax_l ?
w.lit_nat(sum_ax) : Sis->proj(*nis_l, 0)->proj(*r_l, d); });
725 auto s_out =
w.tuple(so);
732 op =
w.app(op,
w.tuple({T, nis, r}));
735 op =
w.app(op, s_out);
737 auto [m, out] =
w.app(op,
w.tuple({am, dst, inputs}))->projs<2>();
741const Def* LowerToMem::make_buffer(
const Def* old_arg,
const Def* r,
const Def*
shape) {
742 auto value =
materialize(old_arg->type(), old_arg);
747const Def* LowerToMem::lower_gather(
const App* app) {
748 auto&
w = new_world();
749 auto c = rewrite(app->callee())->as<
App>();
751 auto [Tr, shapes, dim] =
c->uncurry_args<3>();
752 auto [T,
r] = Tr->projs<2>();
753 auto [s_src, s_idx] = shapes->projs<2>();
755 auto [old_input, old_index] = app->args<2>();
756 auto input = make_buffer(old_input, r, s_src);
757 auto index = make_buffer(old_index, r, s_idx);
761 auto [out_mem, out_buf] =
w.call(op,
Defs{s_src, s_idx}, dim,
Defs{am, dst, input, index})->projs<2>();
762 return scalar_result(app, out_mem, out_buf);
765const Def* LowerToMem::lower_scatter(
const App* app) {
766 auto&
w = new_world();
767 auto c = rewrite(app->callee())->as<
App>();
769 auto [Tr, shapes, dim] =
c->uncurry_args<3>();
770 auto [T,
r] = Tr->projs<2>();
771 auto [s_src, s_idx, s_updates] = shapes->projs<3>();
773 auto [old_input, old_index, old_updates] = app->args<3>();
774 auto input = make_buffer(old_input, r, s_src);
775 auto index = make_buffer(old_index, r, s_idx);
776 auto updates = make_buffer(old_updates, r, s_updates);
780 auto [out_mem, out_buf]
781 =
w.call(op,
Defs{s_src, s_idx, s_updates}, dim,
Defs{am, dst, input, index, updates})->projs<2>();
782 return scalar_result(app, out_mem, out_buf);
const Def * callee() const
A (possibly paramterized) Array.
static auto isa(const Def *def)
static std::tuple< const Axm *, u8, u8 > get(const Def *def)
Yields currying counter of def.
const Def * proj(nat_t a, nat_t i) const
Similar to World::extract while assuming an arity of a, but also works on Sigmas and Arrays.
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
const Def * op(size_t i) const noexcept
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
fe::Error & blame(fe::cite_string< Args... > s, Args &&... args) const
Reports an error that blames this; chain Error::n for Notes and Error::bail to throw.
bool is_external() const noexcept
nat_t num_projs() const
Yields Def::arity(), if it is a Lit, or 1 otherwise.
const T * isa_imm() const
constexpr size_t num_ops() const noexcept
Creates a new Tuple / Pack by inserting Insert::value at position Insert::index into Insert::tuple.
const Def * filter() const
Lam * set(Filter filter, const Def *body)
static std::optional< T > isa(const Def *def)
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
A dependent function type.
static const Pi * isa_cn(const Def *d)
World & new_world()
Create new Defs into this.
void start() override
Actual entry.
World & old_world()
Get old Defs from here.
const Def * rewrite_imm_App(const App *) override
virtual const Def * rewrite_imm_Extract(const Extract *)
virtual const Def * rewrite_imm_Insert(const Insert *)
const Def * map(const Def *old_def, const Def *new_def)
const Def * seal(Def *old_mut, Def *new_mut)
The tail of rewrite_stub: immutabilizes new_mut in hindsight, as rewriting may have made it vacuous.
virtual const Def * rewrite(const Def *)
auto enter(Def *new_mut)
Updates curr_mut() to new_mut and restores it at the end of the scope.
void push(Vars old_vars={}, Vars new_vars={})
constexpr Shape() noexcept=default
const Def * sigma(Defs ops)
const Def * tuple(Defs ops)
Lam * mut_lam(const Pi *pi)
const Def * rewrite_imm_Insert(const Insert *) override
const Def * rewrite_mut_Lam(Lam *) override
const Def * rewrite(const Def *) override
void start() override
Actual entry.
const Def * rewrite_imm_App(const App *) override
const Def * rewrite_imm_Extract(const Extract *) override
const Def * build_pointwise(const Def *result_ty, const Def *mem, const Def *dst, const Def *ins, const Def *s_out, u64 rn, const std::string &name, auto compute)
A [mem, dst, ins] → [mem, Buf] fun spliced via cps.cps2ds_dep that loops over s_out carrying [mem,...
const Def * type_buf(const Def *r, const Def *s, const Def *T)
The buffer type buffer.Buf (r, s, T).
const Def * op_write(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *buf, const Def *idx, const Def *val)
buffer.write (r, s, T) (mem, buf, idx, val) ↦ [mem.M 0, buffer.Buf (r, s, T)].
const Def * op_lit(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *val)
buffer.lit (r, s, T) (mem, val) ↦ [mem.M 0, buffer.Buf (r, s, T)] (every element initialised to val).
const Def * op_read(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *buf, const Def *idx)
buffer.read (r, s, T) (mem, buf, idx) ↦ [mem.M 0, T].
const Def * op_init(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *val)
buffer.init (r, s, T) (mem, val) ↦ [mem.M 0, buffer.Buf (r, s, T)] (initialised with the array value ...
const Def * op_alloc(const Def *r, const Def *s, const Def *T, const Def *mem)
buffer.alloc (r, s, T) mem ↦ [mem.M 0, buffer.Buf (r, s, T)].
const Def * op_copy(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *dst, const Def *src)
buffer.copy (r, s, T) (mem, dst, src) ↦ mem.M 0 (copies the whole buffer src into dst).
Lam * mut_con(World &w, nat_t a=0)
Yields con[mem.M 0].
bool check_scatter_shape_constraints(const App *app, const Def *rank, const Def *dim, const Def *src_shape, const Def *idx_shape, const Def *updates_shape)
Checks statically decidable scatter constraints, blaming app for a violation.
bool check_gather_shape_constraints(const App *app, const Def *rank, const Def *dim, const Def *src_shape, const Def *idx_shape)
Checks statically decidable gather constraints, blaming app for a violation.
fe::View< const Def * > Defs
fe::Vector< const Def * > DefVec
static MapRedPost of(const App *callee)