18bool contains_pi(
const Def* t) {
19 if (
t->isa<
Pi>())
return true;
20 if (
auto sig =
t->isa_imm<
Sigma>())
21 for (
auto op : sig->ops())
22 if (contains_pi(op))
return true;
30const Def* splat_scalar(
const Def* d) {
31 if (!
d->isa<
Pack>())
return nullptr;
32 while (
auto pack =
d->isa<
Pack>())
34 return (
d->is_closed() && !
d->type()->isa<
Arr>()) ?
d :
nullptr;
38bool is_tensor_op(
const App* app) {
45void LowerToMem::collect_tensor_types() {
48 auto gate = [](
const char* why,
const Def* culprit) { fe::throwf(
"cannot bufferize: {} ({})", why, culprit); };
51 auto add_tensor_ty = [
this](
const Def*
t) {
52 if (
t->isa<
Arr>()) tensor_ty_.emplace(t);
55 unique_queue<DefSet> wl;
56 auto push = [&wl](
const Def*
d) {
60 for (
auto mut :
old_world().externals().muts())
66 if (
auto app = def->isa<
App>()) {
72 if (T->isa<
Arr>()) gate(
"tensor with array element type", T);
73 add_tensor_ty(app->arg()->proj(0)->type());
76 add_tensor_ty(app->type());
77 auto [nis, meta, shapes, TisRisSis, comb_init, acc_out, accs]
78 = app->callee()->as<
App>()->uncurry_args<7>();
79 if (meta->proj(3, 0)->isa<
Arr>()) gate(
"tensor with array element type", meta->proj(3, 0));
81 auto Tis = TisRisSis->proj(3, 0);
82 for (
u64 i = 0; i < *nis_l; ++i) {
83 if (Tis->proj(*nis_l, i)->isa<
Arr>()) gate(
"tensor with array element type", Tis);
84 add_tensor_ty(app->arg()->proj(*nis_l, i)->type());
90 if (T->isa<
Arr>()) gate(
"tensor with array element type", T);
91 add_tensor_ty(app->type());
92 add_tensor_ty(app->arg()->proj(2)->type());
95 auto [Tr, s_in, params] = app->callee()->as<
App>()->uncurry_args<3>();
96 auto [T,
r] = Tr->projs<2>();
97 if (T->isa<
Arr>()) gate(
"tensor with array element type", T);
99 gate(
"non-literal rank/mode of %tensor.pad", app);
100 add_tensor_ty(app->type());
101 add_tensor_ty(app->arg()->proj(0)->type());
104 auto [TnisR, ax, Sis] = app->callee()->as<
App>()->uncurry_args<3>();
105 auto [T, nis,
r] = TnisR->projs<3>();
106 if (T->isa<
Arr>()) gate(
"tensor with array element type", T);
110 if (!nis_l || !r_l || !ax_l) {
111 gate(
"non-literal nis/rank/axis of %tensor.concat", app);
113 add_tensor_ty(app->type());
114 for (
u64 i = 0; i < *nis_l; ++i) {
117 gate(
"non-literal extent along the concat axis", app);
118 add_tensor_ty(app->arg()->proj(*nis_l, i)->type());
121 }
else if (
auto [axm, curry, trip] =
Axm::get(app);
124 gate(
"unbufferizable tensor op", app);
127 if (is_tensor_op(app)) {
128 for (
const App* a = app;
a;
a =
a->callee()->isa<App>()) {
129 if (
auto k =
a->arg()->isa_mut<
Lam>()) op_args_.emplace(k);
130 for (
auto op :
a->arg()->ops())
131 if (
auto k = op ?
op->isa_mut<
Lam>() :
nullptr) op_args_.emplace(k);
136 for (
auto op : def->ops())
141 for (
auto mut :
old_world().externals().muts())
142 if (
auto lam = mut->isa_mut<
Lam>(); lam && is_tensor_fn(lam)) tensor_fns_.emplace(lam);
145 if (tensor_fns_.empty() && !ops_seen_)
return;
149 for (
auto old_fn : tensor_fns_) {
150 auto dom = old_fn->type()->dom();
151 auto n = dom->num_projs();
152 for (
size_t i = 0; i != n; ++i)
153 if (
auto pi =
Pi::isa_cn(dom->proj(n, i)); pi && contains_pi(pi->dom()))
154 return gate(
"higher-order bufferized function", old_fn);
158 unique_queue<DefSet> wl2;
159 for (
auto mut :
old_world().externals().muts())
160 if (mut) wl2.push(mut);
161 while (!wl2.empty()) {
162 auto def = wl2.pop();
164 for (
auto op : def->ops()) {
166 if (
auto fn =
op->isa_mut<
Lam>()) {
169 if (tensor_fns_.contains(fn))
170 if (!(def->isa<
App>() && def->as<
App>()->callee() == op) && !def->isa<
Var>())
171 return gate(
"bufferized function used as a value", fn);
172 if (!tensor_fns_.contains(fn) && !op_args_.contains(fn) && mentions_tensor(fn->type()->dom())) {
176 if (fn->is_external() || !
Pi::isa_cn(fn->type()))
177 return gate(
"unconvertible tensor-typed function", fn);
182 if (def->type()) wl2.push(def->type());
187 collect_tensor_types();
189 if (tensor_fns_.empty() && !ops_seen_)
return;
193const Def* LowerToMem::buf_of(
const Def* arr_ty) {
197 while (
auto arr = cur->isa<
Arr>()) {
198 dims.push_back(
rewrite(arr->arity()));
204const Def* LowerToMem::fold_index(
const Def*
shape,
const Def* idx) {
206 auto r =
shape->num_projs();
208 for (
size_t i = 0; i != r; ++i)
209 if (
auto l =
Lit::isa<u64>(
shape->proj(r, i)); !(l && *l == 1)) out.push_back(idx->proj(r, i));
213const Def* LowerToMem::bot_mem() {
215 return w.bot(w.call<
mem::M>(0));
218bool LowerToMem::mentions_tensor(
const Def* t)
const {
219 if (tensor_ty_.contains(t))
return true;
220 if (
auto sig = t->isa<
Sigma>()) {
221 for (
auto op : sig->ops())
222 if (mentions_tensor(op))
return true;
223 }
else if (
auto pi =
t->isa<
Pi>()) {
224 return mentions_tensor(pi->dom());
229bool LowerToMem::is_tensor_fn(
Lam* lam)
const {
230 return lam->is_external() && lam->is_set() && mentions_tensor(lam->type()->dom());
233const Def* LowerToMem::conv_boundary(
const Def* t) {
234 if (tensor_ty_.contains(t))
return buf_of(t);
235 if (
auto sig =
t->isa_imm<
Sigma>(); sig && mentions_tensor(sig)) {
236 auto n = sig->num_ops();
238 for (
size_t i = 0; i != n; ++i)
239 ops[i] = conv_boundary(sig->op(i));
250 if (is_tensor_fn(lam)) {
256 for (
size_t i = 0; i != n; ++i) {
257 auto d = dom->proj(n, i);
259 doms[i] = w.cn(conv_boundary(pi->dom()));
261 doms[i] = conv_boundary(d);
264 auto new_lam = w.mut_con(doms)->set(lam->
dbg());
275 if (
auto pi =
Pi::isa_cn(lam->
type()); pi && mentions_tensor(pi->dom())) {
277 auto new_lam = w.mut_con(conv_boundary(pi->dom()))->set(lam->
dbg());
285 return RWPhase::rewrite_mut_Lam(lam);
298 if (
auto callee = app->
callee()->
isa_mut<
Lam>(); callee && tensor_fns_.contains(callee))
299 return lower_call(app, callee);
304 if (
auto callee = app->
callee()->
isa_mut<
Lam>(); callee && op_args_.contains(callee))
305 return RWPhase::rewrite_imm_App(app);
310 return RWPhase::rewrite_imm_App(app);
313const Def* LowerToMem::lower_call(
const App* app,
Lam* old_callee) {
315 auto new_callee =
rewrite(old_callee);
316 auto dom = old_callee->
type()->
dom();
320 for (
size_t i = 0; i != n; ++i) {
321 auto d = dom->proj(n, i);
322 auto a = app->
arg()->
proj(n, i);
327 return w.app(new_callee, w.tuple(
args));
330const Def* LowerToMem::splat_buffer(
const Def* arr_ty,
const Def* scalar) {
338const Def* LowerToMem::materialize(
const Def* old_ty,
const Def* old_arg) {
340 if (tensor_ty_.contains(old_ty)) {
343 if (
auto c = splat_scalar(old_arg))
return splat_buffer(old_ty,
rewrite(c));
350 if (
auto sig = old_ty->
isa_imm<
Sigma>(); sig && mentions_tensor(sig)) {
351 auto n = sig->num_ops();
353 for (
size_t i = 0; i != n; ++i)
354 ops[i] = materialize(sig->op(i), old_arg->
proj(n, i));
360const Def* LowerToMem::lower_get(
const App* app) {
362 auto arg =
rewrite(app->arg());
363 auto [arr,
index] = arg->projs<2>();
364 auto [T,
r,
s] =
c->args<3>();
368 arr = materialize(app->arg()->proj(0)->type(), app->arg()->proj(0));
370 if (!buf)
return RWPhase::rewrite_imm_App(app);
372 auto [br, bs, bT] = buf->args<3>();
378const Def* LowerToMem::lower_set(
const App* app) {
380 auto arg =
rewrite(app->arg());
381 auto [arr,
index, x] = arg->projs<3>();
382 auto [T,
r,
s] =
c->args<3>();
386 arr = materialize(app->arg()->proj(0)->type(), app->arg()->proj(0));
388 if (!buf)
return RWPhase::rewrite_imm_App(app);
390 auto [br, bs, bT] = buf->args<3>();
391 auto fidx = fold_index(s, index);
393 if (reuse_in_place(app)) {
406const Def* LowerToMem::lower_broadcast(
const App* app) {
411 auto arg =
rewrite(app->arg());
412 auto [s_in, s_out, input] = arg->projs<3>();
413 auto [T,
r] =
c->args<2>();
416 if (s_in == s_out)
return input;
421 input = materialize(app->arg()->proj(2)->type(), app->arg()->proj(2));
427 if (!in_buf)
return splat_buffer(app->type(), input);
430 auto [bri, bsi, biT] = in_buf->args<3>();
434 op =
w.app(op,
w.tuple({T, bri, bsi, bro, bso, r}));
435 op =
w.app(op,
w.tuple({s_in, s_out}));
436 auto [m, out] =
w.app(op,
w.tuple({bot_mem(), input}))->projs<2>();
440const Def* LowerToMem::lower_map_reduce(
const App* app) {
445 auto inputs =
rewrite(app->arg());
447 auto [nis, meta, shapes, TisRisSis, comb_init, acc_out, accs] =
c->uncurry_args<7>();
448 auto [comb,
init] = comb_init->projs<2>();
453 for (
u64 i = 0; i < *nis_l; ++i) {
454 ins[i] = inputs->proj(*nis_l, i);
456 auto old_in = app->arg()->proj(*nis_l, i);
457 ins[i] = materialize(old_in->type(), old_in);
459 return RWPhase::rewrite_imm_App(app);
465 inputs =
w.tuple(ins);
470 auto mem_ty =
w.call<
mem::M>(0);
471 auto inner = comb->type()->as<
Pi>()->dom()->proj(0);
472 auto [cTo, ins_ty] = inner->projs<2>();
473 auto memcomb =
w.mut_fun(
w.sigma({mem_ty, cTo, ins_ty}),
w.sigma({mem_ty, cTo}))->set(
"memComb");
474 auto [cm, cacc, cins] = memcomb->var(0_n)->projs<3>();
475 auto cret = memcomb->var(1);
476 auto after =
w.mut_con(cTo)->set(
"afterComb");
477 after->app(
true, cret,
w.tuple({cm, after->var(0_n)}));
478 memcomb->set(
true,
w.app(comb,
w.tuple({w.tuple({cacc, cins}), after})));
482 op =
w.app(op, meta);
483 op =
w.app(op, shapes);
484 op =
w.app(op, TisRisSis);
485 op =
w.app(op,
w.tuple({memcomb, init}));
486 op =
w.app(op, acc_out);
487 op =
w.app(op, accs);
488 auto [m, out] =
w.app(op,
w.tuple({bot_mem(), inputs}))->projs<2>();
492const Def* LowerToMem::lower_pad(
const App* app) {
495 auto&
w = new_world();
496 auto c = rewrite(app->callee())->as<
App>();
497 auto [input, value] = rewrite(app->arg())->projs<2>();
499 auto [Tr, s_in, params] =
c->uncurry_args<3>();
500 auto [T,
r] = Tr->projs<2>();
501 auto [
mode, lo, hi] = params->projs<3>();
505 input = materialize(app->arg()->proj(0)->type(), app->arg()->proj(0));
507 return RWPhase::rewrite_imm_App(app);
511 if (!r_l)
return RWPhase::rewrite_imm_App(app);
516 for (
u64 d = 0;
d < *r_l; ++
d)
517 so[d] =
w.call(
core::nat::add,
DefVec{w.call(core::nat::add, DefVec{lo->proj(*r_l, d), s_in->proj(*r_l, d)}),
519 auto s_out =
w.tuple(so);
522 op =
w.app(op,
w.tuple({T, r}));
523 op =
w.app(op,
w.tuple({s_in, s_out, mode, lo, hi}));
524 auto [m, out] =
w.app(op,
w.tuple({bot_mem(), input, value}))->projs<2>();
528const Def* LowerToMem::lower_concat(
const App* app) {
531 auto&
w = new_world();
532 auto c = rewrite(app->callee())->as<
App>();
533 auto arg = rewrite(app->arg());
535 auto [TnisR, ax, Sis] =
c->uncurry_args<3>();
536 auto [T, nis,
r] = TnisR->projs<3>();
541 if (!nis_l || !r_l || !ax_l)
return RWPhase::rewrite_imm_App(app);
546 for (
u64 i = 0; i < *nis_l; ++i) {
547 ins[i] = arg->proj(*nis_l, i);
549 auto old_in = app->arg()->proj(*nis_l, i);
550 ins[i] = materialize(old_in->type(), old_in);
552 return RWPhase::rewrite_imm_App(app);
555 auto inputs =
w.tuple(ins);
561 for (
u64 i = 0; i < *nis_l; ++i) {
563 if (!e)
return RWPhase::rewrite_imm_App(app);
567 for (
u64 d = 0;
d < *r_l; ++
d)
568 so[d] = d == *ax_l ?
w.lit_nat(sum_ax) : Sis->proj(*nis_l, 0)->proj(*r_l, d);
569 auto s_out =
w.tuple(so);
572 op =
w.app(op,
w.tuple({T, nis, r}));
575 op =
w.app(op, s_out);
576 auto [m, out] =
w.app(op,
w.tuple({bot_mem(), inputs}))->projs<2>();
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.
bool is_set() const
Yields true if empty or the last op is set.
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 * var(nat_t a, nat_t i) noexcept
auto projs(F f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
nat_t num_vars() noexcept
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
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
const Def * filter() const
static std::optional< T > isa(const Def *def)
const Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
static const Pi * isa_cn(const Def *d)
Is this a continuation - i.e. is the Pi::codom mim::Bottom?
World & new_world()
Create new Defs into this.
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
void start() override
Actual entry.
World & old_world()
Get old Defs from here.
virtual const Def * rewrite(const Def *)
const Def * sigma(Defs ops)
const Def * rewrite_mut_Lam(Lam *) override
void start() override
Actual entry.
const Def * rewrite_imm_App(const App *) override
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_constant(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *val)
buffer.constant (r, s, T) (mem, val) ↦ [mem.M 0, buffer.Buf (r, s, T)] (every element initialised to ...
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).
static constexpr plugin_t Plugin_Id
Vector< const Def * > DefVec