MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
seo.cpp
Go to the documentation of this file.
2
3#include <mim/lam.h>
4
5#include "mim/plug/mem/mem.h"
6
7namespace mim::plug::mem::phase {
8
9/*
10 * Helpers
11 */
12
13enum {
14 Proxy_SCCP_Top, // proxy(var) <- var reached ⊤ for propagation but still awaits GVN bundling
15 Proxy_Bundle, // proxy(lam, var1, var2, ..., varn) <- GVN congruence class
16 Proxy_Sloxy, // proxy(lam, ptr) <- slot invoked at lam where ptr is the slot cons's ptr var
17 Proxy_Phi, // proxy(lam, sloxy) <- phi we need at lam for sloxy
18};
19
20static size_t idx_of(Defs vars, const Def* p) {
21 auto i = std::ranges::find(vars, p);
22 assert(i != vars.end());
23 return i - vars.begin();
24}
25
26static const Proxy* isa_bundle(const Def* def, Lam* lam);
27
28/// Does @p lam's signature refer to its own binder's Var?
29/// Such a signature cannot be narrowed: dropping a component would tear its siblings off the binder.
30static bool is_dependent(Lam* lam) { return lam->type()->isa_mut() || lam->type()->dom()->isa_mut(); }
31
34 visited_.clear();
35 lam2sloxy2val_.clear();
36 first_.clear();
37}
38
39/*
40 * Main Analysis
41 */
42
43// SCCP
44
45const Proxy* SEO::Analysis::mk_sccp_top(const Def* var) { return world().proxy(var->type(), {var}, Proxy_SCCP_Top); }
46
47const Def* SEO::Analysis::sccp_join(Lam* lam, const Def* var, const Def* def) {
48 log().d("sccp join: {} ⊔ {}", var, def);
49 auto cur = lattice(var);
50 if (cur == var) return var; // ⊤ is final
51
52 // Pin mem.M-typed vars to top: mem must stay threaded through every lam,
53 // as later stages (clos conversion, ll backend) rely on each lam having its own mem var.
54 if (Axm::isa<mem::M>(var->type())) return pin(var), var;
55
56 // A site passing `var` itself (a backedge threading a loop-invariant argument through) contributes
57 // nothing: the other call sites alone determine the value.
58 if (def == var) return cur ? cur : var;
59
60 // `⊥ ⊔ x` is `x`, but unusable if lam nests it.
61 // A closed def can never be nested, and Def::nests allocates a fresh MutSet per call - so skip it.
62 if (!def->isa<Proxy>() && !def->is_closed() && lam->nests(def)) {
63 log().d("cannot propagate {} → {}: out of scope", var, def);
64 return pin(var), var;
65 }
66
67 // Frozen: a ⊤ / this-lam's bundle wins and is kept across rounds (needs cur to exist).
68 if (cur && Proxy::isa<Proxy_SCCP_Top>(cur)) return cur;
69 if (cur && isa_bundle(cur, lam)) return cur;
70
71 // First touch of `var` this round, including `cur == ⊥`: restart the join here.
72 // `⊥` must set `first_`, or the next site would discard this value and let a later one win instead of reaching ⊤.
73 // Discarding what earlier sites contributed requires a round that revisits *every* `lam` call site;
74 // apply_known() taints all of lam2callers_ whenever the abstract vars change, so the next round does.
75 if (auto [_, ins] = first_.emplace(var); ins) {
76 log().d("first contribution; restart: {} → {}", var, def);
77 lattice_force(var, def); // may descend from an earlier round's ⊤ - hence force, not lattice()
78 return def;
79 }
80
81 // Every first_ insertion also writes the lattice, so past the first touch `cur` exists;
82 // and it cannot be ⊤, as the `cur == var` check bailed out on entry.
83 assert(cur && cur != var);
84 if (def->isa<Bot>() || cur == def) return cur; // cur ⊔ ⊥ = cur; def ⊔ def = def
85 if (cur->isa<Bot>()) return lattice(var, def), def; // ⊥ ⊔ def = def
86
87 log().d("cannot propagate {} → {}; cur = {}; try GVN", var, def, cur);
88 auto top = mk_sccp_top(var);
89 return lattice(var, top), top;
90}
91
92// GVN
93
94static const Proxy* isa_bundle(const Def* def, Lam* lam) {
95 if (auto bundle = Proxy::isa<Proxy_Bundle>(def); bundle && bundle->op(0) == lam) return bundle;
96 return nullptr;
97}
98
99const Proxy* SEO::Analysis::mk_bundle(Lam* lam, const Def* var, Defs bundle_vars) {
100 return world().proxy(var->type(), cat(lam, bundle_vars), Proxy_Bundle)->set(var->dbg_key());
101}
102
103void SEO::Analysis::gvn_bundle(Lam* lam, Defs vars, Defs abstr_args, fe::Span<const Def*> abstr_vars) {
104 auto n = vars.size();
105 auto idxs = fe::Vector<size_t>();
106
107 for (size_t i = 0; i != n; ++i) {
108 if (!Proxy::isa<Proxy_SCCP_Top>(abstr_vars[i])) continue;
109
110 idxs.clear();
111 idxs.emplace_back(i);
112 for (size_t j = i + 1; j != n; ++j)
113 if (Proxy::isa<Proxy_SCCP_Top>(abstr_vars[j]) && abstr_args[j] == abstr_args[i]) idxs.emplace_back(j);
114
115 if (idxs.size() == 1) {
116 pin(vars[i]);
117 abstr_vars[i] = vars[i];
118 } else {
119 auto bundle = mk_bundle(lam, vars[i], DefVec(idxs, [&](size_t j) { return vars[j]; }));
120 for (auto j : idxs)
121 lattice(vars[j], abstr_vars[j] = bundle);
122 log().d("bundle: {}", bundle);
123 }
124 }
125}
126
127void SEO::Analysis::gvn_split(Lam* lam, Defs vars, fe::Span<const Def*> abstr_args, fe::Span<const Def*> abstr_vars) {
128 // E.g.: Say we started with `{a, b, c, d, e}` as a single bundle for all tvars of `lam`.
129 // Now, we see `lam (x, y, x, y, z)`. Then we have to build:
130 // a -> {a, c}
131 // b -> {b, d}
132 // c -> {a, c}
133 // d -> {b, d}
134 // e -> e (top)
135 auto split = DefVec();
136
137 for (size_t i = 0, n = vars.size(); i != n; ++i) {
138 auto bundle = isa_bundle(abstr_vars[i], lam);
139 if (!bundle) continue;
140
141 auto members = bundle->ops().subspan(1);
142 assert(members.size() > 1 && "a bundle is only ever built for a congruence class");
143
144 split.clear();
145 for (auto p : members)
146 if (abstr_args[idx_of(vars, p)] == abstr_args[i]) split.emplace_back(p);
147
148 if (split.size() == members.size()) continue;
149
150 // vars[i] is a member of its own bundle and trivially agrees with itself, so it is in split.
151 if (split.size() == 1) {
152 pin(vars[i]);
153 abstr_vars[i] = vars[i];
154 log().d("GVN single: {}", vars[i]);
155 } else {
156 auto new_bundle = mk_bundle(lam, vars[i], split);
157 log().d("GVN split: {}", new_bundle);
158 for (auto p : split) {
159 auto j = idx_of(vars, p);
160 lattice(vars[j], abstr_vars[j] = new_bundle);
161 }
162 }
163 }
164}
165
166// SSA
167
168static const Def* mk_phi(World& w, Lam* lam, const Def* sloxy) {
169 return w.proxy(pointee(sloxy), {lam, sloxy}, Proxy_Phi)->set(sloxy->dbg_key());
170}
171
172const Def* SEO::Analysis::lam2sloxy2val(Lam* lam, const Def* sloxy) {
173 if (auto sloxy2val = fe::lookup(lam2sloxy2val_, lam))
174 if (auto val = fe::lookup(*sloxy2val, sloxy)) return val;
175
176 auto phi = mk_phi(world(), lam, sloxy);
177 log().d("no value for sloxy {}; use phi {}", sloxy, phi);
178 if (auto val = lattice(phi); val && !Proxy::isa<Proxy_SCCP_Top>(val)) return val;
179 return nullptr;
180}
181
182bool SEO::Analysis::can_supply(Lam* lam, const Def* sloxy) {
183 if (auto i = lam2callers_.find(lam); i != lam2callers_.end())
184 for (auto caller : i->second)
185 if (auto c = caller->isa_mut<Lam>(); c && !lam2sloxy2val(c, sloxy)) return false;
186 return true;
187}
188
189void SEO::Analysis::propagate_phis(Lam* lam, DefVec& phis, DefVec& abstr_args) {
190 for (auto ptr : slots()) {
191 if (auto sloxy = Proxy::isa<Proxy_Sloxy>(rewrite(ptr))) {
192 if (auto value = sloxy2val(sloxy)) {
193 auto phi = mk_phi(world(), lam, sloxy);
194 phis.emplace_back(phi);
195 abstr_args.emplace_back(value);
196 log().d("propagate phi {} for sloxy {} with value {}", phi, sloxy, value);
197 } else {
198 log().d("no value for sloxy {}", sloxy);
199 }
200 }
201 }
202}
203
204const Def* SEO::Analysis::apply_known(Lam* known, Defs abstr_targs) {
205 auto n = abstr_targs.size();
206 assert(n == known->num_tvars());
207 log().d("known edge: {} → {}", curr_mut(), known);
208 if (auto mut = curr_mut()) lam2callers_[known].emplace(mut);
209 rewrite(known); // enqueue so its body is drained this round; a no-op if already scheduled
210
211 if (is_dependent(known)) {
212 log().d("dependent signature; keeping all vars: {}", known);
213 for (auto var : known->tvars())
214 pin(var);
215 return abstract_app(known, world().tuple(abstr_targs));
216 }
217
218 auto v = version();
219
220 DefVec all_vars(n, [&](size_t i) { return known->var(n, i); });
221 DefVec all_abstr_args(abstr_targs.begin(), abstr_targs.end());
222
223 propagate_phis(known, all_vars, all_abstr_args);
224
225 auto all_abstr_vars = DefVec(all_vars.size());
226 for (size_t i = 0, e = all_vars.size(); i != e; ++i)
227 all_abstr_vars[i] = sccp_join(known, all_vars[i], all_abstr_args[i]);
228
229 gvn_bundle(known, all_vars, all_abstr_args, all_abstr_vars);
230 gvn_split(known, all_vars, all_abstr_args, all_abstr_vars);
231
232 lattice(known->var(), world().tuple(all_abstr_vars.span().subspan(0, n)));
233
234 for (size_t i = n, e = all_vars.size(); i != e; ++i)
235 lattice(all_vars[i], all_abstr_vars[i]);
236
237 // Something about known's abstract vars/phis changed: the next round must re-join them from *all* call
238 // sites - sccp_join's first_-restart discards any unvisited site's contribution, so re-visiting only the
239 // writer would be unsound in a sparse round (e.g. it would constant-fold a loop's backedge forever).
240 if (version() != v)
241 for (auto caller : lam2callers_[known])
242 taint(caller);
243
244 return abstract_app(known, world().tuple(all_abstr_args.span().subspan(0, n)));
245}
246
247const Def* SEO::Analysis::abstract_app(const Def* callee, const Def* arg) {
248 // Only an Axm keeps the checked, normalizing World::app: its normalizer is what folds a propagated
249 // expression. Everywhere else abstract operands (⊤/⊥/proxies) need not be assignable to the domain -
250 // and World::app would either throw or partially evaluate the callee.
251 if (std::get<0>(Axm::next(callee))) return world().app(callee, arg);
252 auto pi = callee->type()->isa<Pi>();
253 return world().raw_app(pi ? pi->reduce(arg) : arg->type(), callee, arg);
254}
255
256const Def* SEO::Analysis::rewrite_imm_App(const App* app) {
257 if (auto slot = Axm::isa<mem::slot>(app)) {
258 if (!is_top(slot)) {
259 if (auto [mem, ret_lam, _, ptr] = split_slot(slot); ret_lam) {
260 auto abstr_mem = rewrite(mem);
261 auto sloxy = world().proxy(ptr->type(), {curr_mut(), ptr}, Proxy_Sloxy)->set(slot->dbg_key());
262 sloxy2slot_[sloxy] = slot;
263 slots_.emplace(ptr);
264 log().d("slot {} → sloxy {}", ptr, sloxy);
265 // The slot is ptr's *defining* site: mark first_ so the ⊥ joined below cannot restart it away.
266 fe::assert_emplace(first_, ptr);
267 lattice(ptr, sloxy);
268 // Treat the slot jump like an app of `ret_lam` so mem and existing phis flow across the edge.
269 // The ptr var is defined *by* the slot, so pass ⊥ (not the sloxy) as its abstract argument:
270 // this keeps the sloxy out of the abstract body, so it only survives if an unresolved
271 // load/store actually references it - `lattice(ptr, sloxy)` above still drives that resolution.
272 return apply_known(ret_lam, {abstr_mem, world().bot(ptr->type())});
273 }
274 }
275 } else if (auto store = Axm::isa<mem::store>(app)) {
276 auto [mem, ptr, val] = store->args<3>();
277 auto abstr_mem = rewrite(mem);
278 auto abstr_ptr = rewrite(ptr);
279 auto abstr_val = rewrite(val);
280
281 if (auto sloxy = Proxy::isa<Proxy_Sloxy>(abstr_ptr)) {
282 sloxy2val(sloxy, abstr_val);
283 log().d("store: {} ← {}", sloxy, abstr_val);
284 return abstr_mem;
285 }
286 log().d("store to unknown ptr: {} ← {}", abstr_ptr, abstr_val);
287 } else if (auto load = Axm::isa<mem::load>(app)) {
288 auto [mem, ptr] = load->args<2>();
289 auto [_, val] = load->projs<2>();
290 auto abstr_mem = rewrite(mem);
291 auto abstr_ptr = rewrite(ptr);
292
293 if (auto sloxy = Proxy::isa<Proxy_Sloxy>(abstr_ptr)) {
294 if (auto abstr_val = sloxy2val(sloxy)) {
295 log().d("load: {} → {}", sloxy, abstr_val);
296 lattice(val, abstr_val);
297 return world().tuple({abstr_mem, abstr_val});
298 }
299 log().d("load of unknown value: {}", sloxy);
300 } else {
301 log().d("load from unknown ptr: {}", abstr_ptr);
302 }
303 } else {
304 auto abstr_callee = rewrite(app->callee());
305 auto abstr_arg = rewrite(app->arg());
306 auto known = abstr_callee->isa_mut<Lam>();
307 if (isa_optimizable(known)) {
308 auto n = known->num_tvars();
309 return apply_known(known, DefVec(n, [&](size_t i) { return abstr_arg->proj(n, i); }));
310 }
311 }
312
313 // Rewrite the arg before the callee; see Rewriter::rewrite_imm_App.
314 auto new_arg = rewrite(app->arg());
315 auto new_callee = rewrite(app->callee());
316 return abstract_app(new_callee, new_arg);
317}
318
320 if (auto src = curr_mut()->isa<Lam>(); src && src->is_set()) {
321 auto phi_vars = DefVec();
322 auto phi_abstr_args = DefVec();
323
324 auto propagate_unknowns = [this, &phi_vars, &phi_abstr_args, src](const Def* abstr) {
325 for (auto mut : abstr->local_muts())
326 if (auto dst = mut->isa<Lam>(); dst && dst->is_open()) {
327 log().d("unknown edge: {} → {}", src, dst);
328 propagate_phis(dst, phi_vars, phi_abstr_args);
329 }
330 };
331
332 auto abstr = rewrite(src->body());
333 if (auto app = abstr->isa<App>()) {
334 if (!app->callee()->isa<Lam>()) propagate_unknowns(app->callee());
335 propagate_unknowns(app->arg());
336 }
337
338 for (size_t i = 0, e = phi_vars.size(); i != e; ++i) {
339 if (is_top(phi_vars[i])) continue; // ⊤ is final - lattice() must not descend from it
340 fe::assert_emplace(first_, phi_vars[i]);
341 lattice(phi_vars[i], phi_abstr_args[i]);
342 }
343 }
344}
345
346/*
347 * Post-Analysis:
348 * Finds sloxies that are still present + unknown lambdas
349 */
350
351static bool keep(Lam* lam, const Def* old_var, const Def* abstr) {
352 if (!abstr) return true; // no info -> keep
353 if (old_var == abstr) return true; // top
354 if (Proxy::isa<Proxy_SCCP_Top>(abstr)) return true; // pending ⊤: nothing was propagated -> keep
355 if (auto bundle = isa_bundle(abstr, lam)) return bundle->op(1) == old_var; // use first in GVN bundle
356 return false;
357}
358
360 for (auto def : world().roots())
361 analyze(def);
362}
363
364void SEO::Analysis::analyze(const Def* def) {
365 if (def->isa<Var>()) return; // do not run escape analysis through a Var (would remap it via lookup)
366 if (auto [_, ins] = visited_.emplace(def); !ins) return;
367 if (auto l = lookup(def)) def = l; // get abstracted value of def
368
369 if (auto proxy = def->isa<Proxy>()) {
370 if (proxy->tag() == Proxy_Sloxy) {
371 auto ptr = proxy->op(1); // the continuation's slot var; see rewrite_imm_App
372 auto slot = fe::lookup(sloxy2slot_, proxy);
373 assert(slot);
374 pin(slot);
375 pin(ptr);
376 log().d("sloxy {} survived; pinning slot {} to ⊤", proxy, slot);
377 }
378 return; // never walk a proxy's deps (would drag in meta info)
379 }
380
381 // A Lam is unknown (and hence its vars must go to top) iff it is reached as a *value*.
382 if (auto app = def->isa<App>()) {
383 if (auto slot = Axm::isa<mem::slot>(app)) {
384 // The slot jump applies its continuation, so `ret_lam` is known - not reached as a value.
385 auto [mem, ret_lam, _, __] = split_slot(slot);
386 analyze(app->type());
387 analyze(mem); // the ptr var has no argument - the slot itself defines it
388 for (auto d : ret_lam->deps())
389 analyze(d);
390 return;
391 }
392 if (auto lam = app->callee()->isa_mut<Lam>(); isa_optimizable(lam)) {
393 // lam is applied here, it's known: traverse its body without pinning its vars to top
394 analyze(app->type());
395
396 // only analyze args that we keep
397 for (size_t i = 0, e = lam->num_tdoms(); i != e; ++i) {
398 auto old_var = lam->var(e, i);
399 if (keep(lam, old_var, lattice(old_var))) analyze(app->arg(e, i));
400 }
401
402 for (auto d : lam->deps())
403 analyze(d);
404
405 return;
406 }
407 } else if (auto [lam, var] = def->isa_binder<Lam>(); lam) {
408 log().d("unknown lam: {}", lam);
409 unknowns_.emplace(lam);
410 for (auto v : var->tprojs())
411 pin(v);
412 }
413
414 for (auto d : def->deps())
415 analyze(d);
416}
417
418/*
419 * Transformation:
420 * Apply analysis info to code
421 */
422
423const Def* SEO::isa_optimized_sloxy(const Def* def) const {
424 if (auto l = lattice(def))
425 if (auto sloxy = Proxy::isa<Proxy_Sloxy>(l)) return sloxy;
426 return nullptr;
427}
428
430 if (Super::analyze()) return true;
431
432 for (auto ptr : analysis_.slots())
433 if (auto sloxy = isa_optimized_sloxy(ptr)) sloxies_.emplace_back(sloxy);
434 return false;
435}
436
437const SEO::Sig& SEO::sig_of(Lam* old_lam) {
438 auto [i, ins] = lam2sig_.emplace(old_lam, Sig());
439 auto& sig = i->second;
440 if (!ins) return sig;
441
442 size_t n = old_lam->num_tvars();
443 auto var = old_lam->has_var();
444
445 for (size_t j = 0; j != n; ++j) {
446 auto old_var = old_lam->var(n, j);
447 if (keep(old_lam, old_var, lattice(old_var))) sig.keeps.set(j);
448 }
449
450 for (auto sloxy : sloxies_) {
451 auto phi = mk_phi(old_world(), old_lam, sloxy);
452 if (var && phi->type()->has_free_var(var)) continue; // not expressible in old_lam's signature
453 auto val = lattice(phi);
454 if (!val || Proxy::isa<Proxy_SCCP_Top>(val)) continue;
455 // build_args() needs an argument at *every* call site; else the slot has to survive.
456 if (analysis_.can_supply(old_lam, sloxy)) sig.phis.emplace_back(sloxy, phi, val, keep(old_lam, phi, val));
457 }
458
459 // A new signature is needed iff some var is dropped/propagated/merged or some phi has to be threaded in -
460 // except for an unknown lam, which is used as a value somewhere and hence must keep its signature as is.
461 sig.num_vars = sig.keeps.count();
462 auto todo = sig.num_vars != n;
463 for (const auto& p : sig.phis)
464 if (p.keep) ++sig.num_vars, todo = true;
465 sig.todo = todo && !analysis_.unknowns().contains(old_lam);
466
467 return sig;
468}
469
470const Def* SEO::rewrite_imm_App(const App* old_app) {
471 if (auto slot = Axm::isa<mem::slot>(old_app)) {
472 auto [mem, ret_lam, _, ptr] = split_slot(slot);
473
474 if (isa_optimized_sloxy(ptr)) {
475 // The slot was promoted away: jump straight to the (rebuilt) continuation, dropping the ptr var.
476 profile_count("seo.slots.eliminated");
477 assert(!analysis_.unknowns().contains(ret_lam)); // promoted -> ret_lam was never reached as a value
478 auto new_lam = build_lam(ret_lam);
479 auto new_args = build_args(ret_lam, {mem, ptr});
480 return map(old_app, new_world().app(new_lam, new_args));
481 }
482
483 // The slot survives: keep the allocation, forwarding the continuation.
484 auto [T, a] = slot->decurry()->args<2>();
485 auto new_mem = rewrite(mem);
486 auto new_ret_lam = rewrite(ret_lam)->as_mut<Lam>();
487 return map(old_app, mem::op_slot(rewrite(T), rewrite(a), new_mem, new_ret_lam));
488 } else if (auto store = Axm::isa<mem::store>(old_app)) {
489 auto [mem, ptr, val] = store->args<3>();
490 if (isa_optimized_sloxy(ptr)) return rewrite(mem);
491 } else if (auto load = Axm::isa<mem::load>(old_app)) {
492 auto [res_mem, res_val] = load->projs<2>();
493 auto [mem, ptr] = load->args<2>();
494 if (auto sloxy = isa_optimized_sloxy(ptr)) {
495 auto abstr_val = abstracted(res_val);
496 assert(abstr_val && "a promoted slot implies every load from it resolved");
497 log().d("load from {} resolved to {}", sloxy, abstr_val);
498 auto new_mem = rewrite(mem);
499 return new_world().tuple({new_mem, rewrite(abstr_val)});
500 }
501 } else {
502 auto old_lam = old_app->callee()->isa_mut<Lam>();
503 if (!old_lam) {
504 // The callee may fold to a rebuilt lam in the new world only,
505 // e.g. a branch `(f, t)#cond` whose cond becomes constant after GVN merged vars.
506 if (auto new_lam = rewrite(old_app->callee())->isa_mut<Lam>())
507 if (auto ol = fe::lookup(lam_new2old_, new_lam)) old_lam = ol;
508 }
509
510 if (old_lam) {
511 log().d("in {}: app of {}", curr_mut(), old_lam);
512
513 if (sig_of(old_lam).todo) {
514 log().d("needs seo: {}", old_lam);
515 size_t n = old_lam->num_tvars();
516 auto new_lam = build_lam(old_lam);
517 auto new_args = build_args(old_lam, DefVec(n, [&](size_t i) { return old_app->arg(n, i); }));
518 return map(old_app, new_world().app(new_lam, new_args));
519 }
520 }
521 }
522
523 return Super::rewrite_imm_App(old_app);
524}
525
526const Def* SEO::rewrite_mut_Lam(Lam* old_lam) {
527 // A lam that gets a new signature must never be rebuilt generically:
528 // otherwise two new versions of old_lam exist and their (hash-consed, cached) body defs
529 // reference whichever version was rewritten first - leaving free vars in the other one.
530 if (sig_of(old_lam).todo) return build_lam(old_lam);
531 return Super::rewrite_mut_Lam(old_lam);
532}
533
534const Def* SEO::rewrite_imm_Var(const Var* var) {
535 // The generic Var rewrite fabricates var(new_binder) - the wrong arity for a signature build_lam()
536 // narrowed. Reachable before build_lam() maps the whole var, e.g. from rewriting a propagated value.
537 if (auto old_lam = var->binder()->isa_mut<Lam>())
538 if (sig_of(old_lam).todo) return var_of(old_lam);
539 return Super::rewrite_imm_Var(var);
540}
541
542const Def* SEO::var_of(Lam* old_lam) {
543 auto new_lam = build_lam(old_lam);
544 const auto& sig = sig_of(old_lam);
545 size_t n = old_lam->num_tvars(), j = 0;
546 auto elems = DefVec(n);
547
548 for (size_t i = 0; i != n; ++i)
549 if (sig.keeps[i])
550 elems[i] = new_lam->var(sig.num_vars, j++);
551 else if (auto abstr = lattice(old_lam->var(n, i)); Proxy::isa<Proxy_Sloxy>(abstr))
552 elems[i] = new_world().bot(rewrite(old_lam->dom(n, i))); // a promoted slot carries no value
553 else
554 elems[i] = rewrite(abstr); // SCCP propagate
555
556 return new_world().tuple(elems);
557}
558
559Lam* SEO::build_lam(Lam* old_lam) {
560 assert(!is_dependent(old_lam) && "apply_known pins a dependent signature to \u22a4");
561 if (auto memo = fe::lookup(lam_old2new_, old_lam)) return memo;
562
563 log().d("build new lam for {}", old_lam);
564 invalidate();
565 const auto& sig = sig_of(old_lam);
566 size_t n = old_lam->num_tvars();
567 auto new_doms = DefVec();
568
569 for (size_t i = 0; i != n; ++i)
570 if (sig.keeps[i])
571 new_doms.emplace_back(rewrite(old_lam->dom(n, i)));
572 else if (auto abstr = lattice(old_lam->var(n, i)); isa_bundle(abstr, old_lam))
573 profile_count("seo.gvn.vars_merged");
574 else if (!Proxy::isa<Proxy_Sloxy>(abstr))
575 profile_count("seo.sccp.vars_eliminated");
576 for (const auto& p : sig.phis)
577 if (p.keep) new_doms.emplace_back(rewrite(p.phi->type()));
578
579 auto new_lam = new_world().mut_lam(new_doms, rewrite(old_lam->codom()))->set(old_lam->dbg_key());
580 lam_old2new_[old_lam] = new_lam;
581 lam_new2old_[new_lam] = old_lam;
582
583 // Map all *kept* vars/phis before rewriting any propagated value below: that rewrite may recursively
584 // re-enter old_lam and must be able to resolve them - see var_of().
585 // map_root(), because a Var mapping is context-free and must outlive a scalarization scope.
586 size_t j = 0;
587 for (size_t i = 0; i != n; ++i)
588 if (sig.keeps[i]) {
589 auto old_var = old_lam->var(n, i);
590 auto v = new_lam->var(sig.num_vars, j++)->set(old_var->dbg_key());
591 map_root(old_var, v);
592 if (auto bundle = isa_bundle(lattice(old_var), old_lam)) map_root(bundle, v); // GVN bundle
593 }
594
595 for (const auto& p : sig.phis)
596 if (p.keep) {
597 auto v = new_lam->var(sig.num_vars, j++);
598 profile_count("phis.materialized");
599 log().d("map phi {} → {}", p.phi, v);
600 map_root(p.phi, v);
601 if (p.val != p.phi) map_root(p.val, v); // phi is part of a GVN bundle
602 }
603
604 // Map the whole var *before* rewriting a dropped phi's value below: such a value may project a *dropped*
605 // var of old_lam (e.g. its now-removed empty closure env), which only the whole var resolves.
606 map_root(old_lam->var(), var_of(old_lam));
607
608 for (const auto& p : sig.phis)
609 if (!p.keep) {
610 log().d("map phi {} → its propagated value {}", p.phi, p.val);
611 map_root(p.phi, rewrite(p.val));
612 }
613
614 auto _ = enter(old_lam);
615 new_lam->set(rewrite(old_lam->filter()), rewrite(old_lam->body()));
616 return new_lam;
617}
618
619DefVec SEO::build_args(Lam* old_lam, Defs old_targs) {
620 const auto& sig = sig_of(old_lam);
621 size_t n = old_lam->num_tvars();
622 assert(old_targs.size() == n);
623 auto new_args = DefVec();
624
625 for (size_t i = 0; i != n; ++i)
626 if (sig.keeps[i]) new_args.emplace_back(rewrite(old_targs[i]));
627
628 for (const auto& p : sig.phis)
629 if (p.keep) {
630 auto arg = analysis_.lam2sloxy2val(curr_mut<Lam>(), p.sloxy);
631 assert(arg);
632 log().d("wire up phi argument {} for sloxy {}", arg, p.sloxy);
633 new_args.emplace_back(rewrite(arg));
634 }
635
636 return new_args;
637}
638
639} // namespace mim::plug::mem::phase
virtual void leave()
Called after curr_mut() has been completely dealt with.
Definition phase.h:292
virtual void reset()
Clears the rewriter map and resets Phase::todo() and is_bootstrapping() for the next fixed-point iter...
Definition phase.cpp:49
virtual void finalize()
Run after the main analysis - only in full rounds, so it always sees the complete abstract World.
Definition phase.h:274
const Def * callee() const
Definition lam.h:275
const Def * arg() const
Definition lam.h:284
static std::tuple< const Axm *, u8, u8 > next(const Def *callee)
Like Axm::get, but advances the counter as one more App is about to be built on top of callee.
Definition axm.cpp:44
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:273
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:196
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:589
Defs deps() const noexcept
Definition def.cpp:468
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:580
DbgKey dbg_key() const
Cheap handle for other->set(this->dbg_key()).
Definition def.h:610
std::pair< D *, const Var * > isa_binder() const
Is this a mutable that introduces a Var?
Definition def.h:490
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:479
nat_t num_vars() noexcept
Definition def.h:479
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1111
bool nests(Def *mut)
Does this nest mut?
Definition def.cpp:378
nat_t num_tvars() noexcept
Definition def.h:479
auto tprojs(F f) const
Definition def.h:453
bool is_closed() const
Same as !has_free_vars().
Definition def.cpp:353
const Var * has_var()
Only returns not nullptr, if Var of this mutable has ever been created.
Definition def.h:483
A function.
Definition lam.h:113
const Def * filter() const
Definition lam.h:125
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:27
const Def * dom() const
Definition lam.h:134
const Pi * type() const
Definition lam.h:133
const Def * body() const
Definition lam.h:126
const Def * codom() const
Definition lam.h:135
void invalidate(bool todo=true)
Signals that another round of fixed-point iteration is required, either as part of.
Definition phase.h:98
const fe::Log & log() const
Definition phase.h:79
void profile_count(std::string_view key, uint64_t n=1)
Adds n to the custom fe::Profiler counter key of the current run; no-op unless profiling is enabled.
Definition phase.cpp:41
bool todo() const
Definition phase.h:90
World & world()
Definition phase.h:77
const Def * dom() const
Definition lam.h:35
Used as intermediate value during optimizatinos such as Analysis.
Definition def.h:1032
static const Proxy * isa(const Def *def)
Definition def.h:1046
const Def * lattice(const Def *def) const
Returns the abstract value computed by the associated Analysis for def, or nullptr if no value is ava...
Definition phase.h:379
const Def * abstracted(const Def *def) const
Returns lattice(def) if it differs from def (i.e. we learned something), otherwise nullptr.
Definition phase.h:382
virtual bool analyze()
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
Definition phase.cpp:179
World & new_world()
Create new Defs into this.
Definition phase.h:452
World & world()=delete
Hides both and forbids direct access.
World & old_world()
Get old Defs from here.
Definition phase.h:451
D * curr_mut() const
Definition rewrite.h:96
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:47
const Def * map_root(const Def *old_def, const Def *new_def)
Like map() but records into the root map, so the entry outlives the current push()/pop() scope.
Definition rewrite.h:52
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
auto enter(Def *new_mut)
Updates curr_mut() to new_mut and restores it at the end of the scope.
Definition rewrite.h:109
virtual const Def * lookup(const Def *old_def)
Lookup old_def by searching in reverse through the stack of maps.
Definition rewrite.h:62
A variable introduced by a binder (mutable).
Definition def.h:825
Def * binder() const
The binder of this Var.
Definition def.h:835
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
const Proxy * proxy(const Def *type, Defs ops, flags_t tag)
Definition world.h:338
const Def * app(const Def *callee, const Def *arg)
Definition world.cpp:237
const Def * bot(const Def *type)
Definition world.h:591
const Def * tuple(Defs ops)
Definition world.cpp:326
const Def * raw_app(const Axm *axm, u8 curry, u8 trip, const Def *type, const Def *callee, const Def *arg)
Definition world.cpp:312
Lam * mut_lam(const Pi *pi)
Definition world.h:402
bool analyze() final
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
Definition seo.cpp:429
const Def * rewrite_imm_Var(const Var *) final
Definition seo.cpp:534
const Def * rewrite_imm_App(const App *) final
Definition seo.cpp:470
const Def * rewrite_mut_Lam(Lam *) final
Definition seo.cpp:526
static bool is_dependent(Lam *lam)
Does lam's signature refer to its own binder's Var?
Definition seo.cpp:30
static bool keep(Lam *lam, const Def *old_var, const Def *abstr)
Definition seo.cpp:351
static const Proxy * isa_bundle(const Def *def, Lam *lam)
Definition seo.cpp:94
static const Def * mk_phi(World &w, Lam *lam, const Def *sloxy)
Definition seo.cpp:168
static size_t idx_of(Defs vars, const Def *p)
Definition seo.cpp:20
The mem Plugin
Definition mem.h:11
std::tuple< const Def *, Lam *, const Def *, const Def * > split_slot(const App *slot)
Decomposes the continuation-based slot into (mem, ret_lam, ret_mem, ptr), where ret_mem is the contin...
Definition mem.h:150
const Def * pointee(const Def *ptr)
Definition mem.h:104
const Def * op_slot(const Def *type, const Def *as, const Def *mem, const Def *ret)
Definition mem.h:140
fe::View< const Def * > Defs
Definition def.h:91
Lam * isa_optimizable(Lam *lam)
These are Lams that are.
Definition lam.h:349
fe::Vector< const Def * > DefVec
Definition def.h:93
TExt< false > Bot
Definition lattice.h:164
DefVec cat(Defs, Defs)
Definition tuple.cpp:73
@ Pi
Definition def.h:122
@ Lam
Definition def.h:122