29 if (e == var)
return 0;
33 auto [x, c] = semiop->args<2>();
40 auto [a, b] = op->args<2>();
43 if (a_const == b_const)
return {};
59 auto lam =
map->isa_mut<
Lam>();
60 if (!lam || !lam->is_set())
return false;
61 auto var = lam->
var();
62 auto body = lam->body();
63 if (body == var)
return true;
68 auto used = fe::Bitset();
69 auto mark = [&](
const Def* elem) {
71 if (!i || *i >= *n)
return false;
77 for (
auto elem :
tuple->ops())
78 if (!mark(elem))
return false;
79 }
else if (
auto pack = body->isa<
Pack>()) {
81 if (!pack->is_set() || !mark(pack->body()))
return false;
82 }
else if (!mark(body)) {
86 return used.count() == *n;
91 auto dom = outer->
type()->as<
Pi>()->dom();
92 auto codom = inner->
type()->as<
Pi>()->codom();
93 auto lam = w.mut_lam(dom, codom)->set(
"fused_map");
94 lam->set(
true, w.app(inner, w.app(outer, lam->
var())));
104 T.emplace_back(t),
R.emplace_back(r),
S.emplace_back(s),
maps.emplace_back(m),
is.emplace_back(v);
112 auto shapes = mr->uncurry_args<8>()[2];
113 return shapes->proj(3, 0) == S;
128 if (!pr->src->type()->isa<
Arr>())
return {};
133 auto [T, r] = bc->callee()->as<
App>()->args<2>();
134 auto [s_in, s_out, input] = bc->arg()->projs<3>();
138 if (!input->type()->isa<
Arr>())
return {};
142 auto vec_ty = slot_map->
type()->as<
Pi>()->codom();
143 auto lam = w.mut_lam(vec_ty, vec_ty)->set(
"bcast_map");
145 for (
u64 d = 0; d < *r_l; ++d) {
146 auto in_d = s_in->proj(*r_l, d);
147 auto o_d = lam->var(*r_l, d);
148 if (in_d == s_out->proj(*r_l, d))
155 lam->set(
true, w.tuple(elems));
157 return PureRead{input, lam, T, r, s_in};
186const Def* Fuse::fuse_map_reduce(
const App* app) {
190 log().d(
"consider for fusion: comb = {}, init = {}, To = {}, Ro = {}", mr.comb, mr.init, mr.To, mr.Ro);
191 log().d(
" inputs: nis = {}, Tis = {}, Ris = {}, Sis = {}, is = {}", mr.nis, mr.Tis, mr.Ris, mr.Sis, is);
196 if (!nis_lit)
return nullptr;
197 auto nis_nat = *nis_lit;
211 fe::Vector<std::optional<InnerInfo>> infos(nis_nat);
213 for (
u64 k = 0; k < nis_nat; ++k) {
215 if (!inner)
continue;
218 auto inner_is = inner->arg()->proj(2, 0);
221 if (!inner_nis_nat || im.So != mr.Sis->proj(nis_nat, k))
continue;
226 if (!inner_nps_nat || *inner_nps_nat != 0)
continue;
235 if (!inner_ro || !inner_rn || *inner_ro != *inner_rn)
continue;
236 if (im.Sr != im.So)
continue;
250 infos[k] = InnerInfo{.comb = im.comb,
257 .nis = *inner_nis_nat,
261 if (std::ranges::none_of(infos, [](
const auto& info) {
return info.has_value(); }))
return nullptr;
266 fe::Vector<u64> new_pos(nis_nat);
268 for (
u64 i = 0; i < nis_nat; ++i) {
269 new_pos[i] = new_nis_nat;
270 new_nis_nat += infos[i] ? infos[i]->nis : 1;
273 auto slots = Slots();
274 for (
u64 i = 0; i < nis_nat; ++i) {
275 if (
auto& info = infos[i]) {
276 auto outer_map_i = mr.maps->proj(nis_nat, i);
279 bool shared = !sole_consumer(is->proj(nis_nat, i));
281 auto v =
info->is->proj(
info->nis, l);
282 if (shared) shared_.insert(v);
289 slots.push(mr.Tis->proj(nis_nat, i), mr.Ris->proj(nis_nat, i), mr.Sis->proj(nis_nat, i),
290 mr.maps->proj(nis_nat, i), is->proj(nis_nat, i));
294 auto new_Tis =
w.tuple(slots.T);
295 auto new_Ris =
w.tuple(slots.R);
296 auto new_Sis =
w.tuple(slots.S);
297 auto new_maps =
w.tuple(slots.maps);
298 auto new_is =
w.tuple(slots.is);
300 auto new_nis_def =
w.lit_nat(new_nis_nat);
317 auto inputs_sigma =
w.sigma(slots.T);
318 auto data_sigma =
w.sigma({mr.To, inputs_sigma});
319 auto ret_cn_type =
w.cn(mr.To);
320 auto new_comb =
w.mut_con({data_sigma, ret_cn_type})->
set(
"fused_comb");
321 auto [new_data, new_ret] = new_comb->vars<2>();
322 auto [new_acc, new_in] = new_data->projs<2>();
324 fe::Vector<u64> fused_indices;
325 for (
u64 i = 0; i < nis_nat; ++i)
326 if (infos[i]) fused_indices.emplace_back(i);
328 fe::Vector<Lam*> inner_rets(fused_indices.size(),
329 [&](
size_t r) { return w.mut_con(infos[fused_indices[r]]->To)->set(
"inner_ret"); });
332 DefVec outer_inputs_vec(nis_nat);
333 for (
u64 i = 0, r = 0; i < nis_nat; ++i)
334 outer_inputs_vec[i] = infos[i] ? inner_rets[r++]->var() : new_in->proj(new_nis_nat, new_pos[i]);
337 for (
size_t r = 0;
r < fused_indices.size(); ++
r) {
338 const auto&
info = *infos[fused_indices[
r]];
340 [&](
size_t l) { return new_in->proj(new_nis_nat, new_pos[fused_indices[r]] + l); });
341 Lam* caller =
r == 0 ? new_comb : inner_rets[
r - 1];
342 caller->app(
true,
info.comb, {w.tuple({info.init, w.tuple(inner_inputs_vec)}), inner_rets[
r]});
346 inner_rets.back()->app(
true, mr.comb, {
w.tuple({new_acc, w.tuple(outer_inputs_vec)}), new_ret});
351 res =
w.app(res, {new_nis_def, mr.nps});
352 res =
w.app(res, mr.meta);
353 res =
w.app(res, mr.shapes);
354 res =
w.app(res, {new_Tis, new_Ris, new_Sis, mr.Tps, mr.Rps, mr.Sps});
355 res =
w.app(res, {new_comb, mr.init, mr.post});
356 res =
w.app(res, mr.map_out);
357 res =
w.app(res, {new_maps, mr.post_maps});
358 res =
w.app(res, {new_is, post_is});
369 auto pi =
map->type()->isa<
Pi>();
370 if (!pi)
return false;
371 auto expected = w.app(w.app(w.annex<
tensor::reshape_map>(), {r_in, r_out}), {s_in, s_out});
372 auto epi = expected->type()->isa<
Pi>();
373 if (!epi || epi->dom() != pi->dom() || epi->codom() != pi->codom())
return false;
374 auto probe = w.mut_lam(pi->dom(), pi->codom());
375 return w.app(
map, probe->var()) == w.app(expected, probe->var());
381bool Fuse::sole_consumer(
const Def* d)
const {
382 if (shared_.contains(d))
return false;
383 auto old_it = new2old_.find(d);
384 if (old_it == new2old_.end())
return false;
385 auto cnt = mr_consumers_.find(old_it->second);
386 return cnt != mr_consumers_.end() && cnt->second == 1;
411const Def* Fuse::fuse_epilogue(
const App* callee,
const Def* arg) {
414 auto& w = new_world();
420 if (!nis_lit || !nps_lit || !ro_lit || !rn_lit || *ro_lit != *rn_lit)
return nullptr;
421 auto nis_nat = *nis_lit;
422 auto nps_nat = *nps_lit;
423 if (nis_nat == 0)
return nullptr;
424 if (mr.Sr != mr.So)
return nullptr;
432 auto [is, ps] = arg->
projs<2>();
438 const Def* inner_def =
nullptr;
440 for (
u64 k = 0; k < nis_nat && !inner_def; ++k) {
442 if (!cand || !sole_consumer(cand) || !reads_as_produced(cand, mr.Sis->proj(nis_nat, k)))
continue;
444 auto m = mr.maps->proj(nis_nat, k);
448 }
else if (mr.Sis->proj(nis_nat, k) != mr.So
449 && is_unpack_read(w, m, mr.Ris->proj(nis_nat, k), mr.Sis->proj(nis_nat, k), mr.Ro, mr.So)) {
455 if (!inner_def)
return nullptr;
461 const Def* to_cells =
nullptr;
464 {mr.So, mr.Sis->proj(nis_nat, k0)});
468 auto [i_is, i_ps] = inner->arg()->projs<2>();
471 if (!i_nps_lit)
return nullptr;
472 auto i_nps_nat = *i_nps_lit;
474 log().d(
"fuse trailing map {} {} into the epilogue of {}", callee, arg, inner_def);
479 auto new_nps = i_nps_nat + (nis_nat - 1) + nps_nat;
480 auto to_cell = [&](
const Def* m) {
return to_cells ?
compose_map(w, m, to_cells) : m; };
482 for (
u64 j = 0; j < i_nps_nat; ++j)
483 eps.push(
im.Tps->proj(i_nps_nat, j),
im.Rps->proj(i_nps_nat, j),
im.Sps->proj(i_nps_nat, j),
484 im.post_maps->proj(i_nps_nat, j), i_ps->proj(i_nps_nat, j));
485 for (
u64 i = 0; i < nis_nat; ++i)
487 eps.push(mr.Tis->proj(nis_nat, i), mr.Ris->proj(nis_nat, i), mr.Sis->proj(nis_nat, i),
488 to_cell(mr.maps->proj(nis_nat, i)), is->proj(nis_nat, i));
489 for (
u64 j = 0; j < nps_nat; ++j)
490 eps.push(mr.Tps->proj(nps_nat, j), mr.Rps->proj(nps_nat, j), mr.Sps->proj(nps_nat, j),
491 to_cell(mr.post_maps->proj(nps_nat, j)), ps->proj(nps_nat, j));
495 auto fused_post =
w.mut_con({
w.sigma({
im.To,
w.sigma(eps.T)}),
w.cn(mr.Tp)})->
set(
"fused_post");
496 auto after_ip =
w.mut_con(
im.Tp)->set(
"afterInnerPost");
497 auto after_comb =
w.mut_con(mr.To)->set(
"afterComb");
498 auto [fused_data, fused_ret] = fused_post->vars<2>();
499 auto [x, extras] = fused_data->projs<2>();
501 DefVec i_extras(i_nps_nat, [&](
size_t j) {
return extras->proj(new_nps, j); });
502 fused_post->app(
true,
im.post, {w.tuple({x, w.tuple(i_extras)}), after_ip});
504 DefVec comb_inputs(nis_nat);
505 for (
u64 i = 0, r = 0; i < nis_nat; ++i)
506 comb_inputs[i] = i == k0 ? after_ip->var() : extras->proj(new_nps, i_nps_nat + r++);
507 after_ip->app(
true, mr.comb, {w.tuple({mr.init, w.tuple(comb_inputs)}), after_comb});
509 DefVec o_extras(nps_nat, [&](
size_t j) {
return extras->proj(new_nps, i_nps_nat + (nis_nat - 1) + j); });
510 after_comb->app(
true, mr.post, {w.tuple({after_comb->var(), w.tuple(o_extras)}), fused_ret});
515 res =
w.app(res, {
im.nis,
w.lit_nat(new_nps)});
516 res =
w.app(res, {
im.To, mr.Tp,
im.Ro,
im.Rn,
im.TSched});
517 res =
w.app(res,
im.shapes);
518 res =
w.app(res, {
im.Tis,
im.Ris,
im.Sis,
w.tuple(eps.T),
w.tuple(eps.R),
w.tuple(eps.S)});
519 res =
w.app(res, {
im.comb,
im.init, fused_post});
520 res =
w.app(res,
im.map_out);
521 res =
w.app(res, {
im.maps,
w.tuple(eps.maps)});
522 res =
w.app(res, {i_is,
w.tuple(eps.is)});
530 impl =
w.app(impl, {mr.Tp, mr.Ris->proj(nis_nat, k0), mr.Ro});
531 impl =
w.app(impl, mr.Sis->proj(nis_nat, k0));
532 impl =
w.app(impl, mr.So);
533 res =
w.app(impl, res);
545const Def* Fuse::fuse_read_through(
const App* callee,
const Def* arg) {
550 if (!nis_lit || !nps_lit)
return nullptr;
551 auto nis_nat = *nis_lit;
552 auto nps_nat = *nps_lit;
554 auto&
w = new_world();
556 auto [is, ps] = arg->projs<2>();
558 bool changed =
false;
559 auto rewire = [&](
u64 n,
const Def* Ts,
const Def* Rs,
const Def* Ss,
const Def* ms,
const Def* vs) {
560 auto slots = Slots();
561 for (
u64 i = 0; i < n; ++i) {
562 auto T = Ts->proj(n, i),
R = Rs->proj(n, i),
S = Ss->proj(n, i);
563 auto m = ms->proj(n, i), v = vs->proj(n, i);
565 log().d(
"read input {} of {} through {}", i, callee, v);
568 if (!sole_consumer(v)) shared_.insert(
rt->src);
572 slots.push(T, R, S, m, v);
577 auto ins = rewire(nis_nat, mr.Tis, mr.Ris, mr.Sis, mr.maps, is);
578 auto eps = rewire(nps_nat, mr.Tps, mr.Rps, mr.Sps, mr.post_maps, ps);
579 if (!changed)
return nullptr;
582 res =
w.app(res, mr.nis_nps);
583 res =
w.app(res, mr.meta);
584 res =
w.app(res, mr.shapes);
585 res =
w.app(res, {
w.tuple(ins.T),
w.tuple(ins.R),
w.tuple(ins.S),
w.tuple(eps.T),
w.tuple(eps.R),
w.tuple(eps.S)});
586 res =
w.app(res, mr.comb_init);
587 res =
w.app(res, mr.map_out);
588 res =
w.app(res, {
w.tuple(ins.maps),
w.tuple(eps.maps)});
589 res =
w.app(res, {
w.tuple(ins.is),
w.tuple(eps.is)});
608 const App* cur =
nullptr;
609 if (
auto res = fuse_map_reduce(app)) {
610 log().d(
"fused map_reduce {} → {}", app, res);
611 cur = res->as<
App>();
620 for (
bool progress =
true; progress;) {
622 const Def* res = fuse_read_through(callee, arg);
624 res = fuse_epilogue(callee, arg);
625 if (res)
log().d(
"fused trailing map {} into its producer's epilogue → {}", app, res);
628 cur = res->as<
App>();
638 new2old_[result] = app;
643 if (
auto pr =
is_pure_read(result); pr && is_mr(pr->src) && !new2old_.contains(pr->src))
644 new2old_[pr->src] = app;
const Def * callee() const
A (possibly paramterized) Array.
static auto isa(const Def *def)
const Def * var(nat_t a, nat_t i) 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).
static std::optional< T > isa(const Def *def)
A (possibly paramterized) Tuple.
const fe::Log & log() const
A dependent function type.
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(const Def *)
Data constructor for a Sigma.
The World represents the whole program and manages creation of MimIR nodes (Defs).
void start() override
Actual entry.
const Def * rewrite_imm_App(const App *) final
static std::optional< PureRead > read_through(World &w, const Def *value, const Def *slot_map, const Def *S)
If value is a pure re-indexed read — a copy-combiner map_reduce (reshape/transpose/slice/ flip/repeat...
static const Def * compose_map(World &w, const Def *inner, const Def *outer)
inner ∘ outer: feeds the outer op's read coordinates for one input into the inner op's access map.
static bool is_unpack_read(World &w, const Def *map, const Def *r_in, const Def *s_in, const Def *r_out, const Def *s_out)
Is map the row-major reshape read tensor.reshape_map (s_in, s_out) — the map that reads a PACKED prod...
static bool reads_as_produced(const Def *value, const Def *S)
Does a slot that declares value in shape S read it in the shape it is produced in?
static std::optional< u64 > injective_coord(const Def *var, const Def *e)
If e reads coordinate var#i injectively, returns i: a plain extract, possibly strided (affine....
static bool reads_injectively(const Def *map)
Checks that map provably reads through every loop index of its domain: its body is the identity,...
bool is_identity_post(const Def *post)
Is post the (rebuilt) CPS identity tensor.id, i.e.
DefMap< u64 > count_consumers(const World &world, std::predicate< const Def * > auto pred)
Counts the consumers of every def of world matched by pred.
std::optional< PureRead > is_pure_read(const Def *value)
If value is a pure re-indexed read — a copy-combiner map_reduce without reduction loops that writes i...
bool is_copy_comb(const Def *comb)
Recognizes the (rebuilt) tensor_copy combiner (acc, ys) ↦ ys#0: the result is exactly the single inpu...
bool is_identity_map(const Def *map)
Is map the identity access map λ x. x?
A pure re-indexed read: the source tensor, the access map into it (over the read's output coordinates...
fe::View< const Def * > Defs
fe::Vector< const Def * > DefVec
static MapRedPost of(const App *callee)
The five parallel per-slot lists of a map_reduce_post input group: element type, rank,...
void push(const Def *t, const Def *r, const Def *s, const Def *m, const Def *v)