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