21 auto i = std::ranges::find(vars, p);
22 assert(i != vars.end());
23 return i - vars.begin();
35 lam2sloxy2val_.clear();
47const Def* SEO::Analysis::sccp_join(
Lam* lam,
const Def* var,
const Def* def) {
48 log().d(
"sccp join: {} ⊔ {}", var, def);
50 if (cur == var)
return var;
58 if (def == var)
return cur ? cur : var;
63 log().d(
"cannot propagate {} → {}: out of scope", var, def);
75 if (
auto [_, ins] = first_.emplace(var); ins) {
76 log().d(
"first contribution; restart: {} → {}", var, def);
77 lattice_force(var, def);
83 assert(cur && cur != var);
84 if (def->isa<
Bot>() || cur == def)
return cur;
85 if (cur->isa<
Bot>())
return lattice(var, def), def;
87 log().d(
"cannot propagate {} → {}; cur = {}; try GVN", var, def, cur);
88 auto top = mk_sccp_top(var);
99const Proxy* SEO::Analysis::mk_bundle(
Lam* lam,
const Def* var,
Defs bundle_vars) {
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>();
107 for (
size_t i = 0; i != n; ++i) {
111 idxs.emplace_back(i);
112 for (
size_t j = i + 1; j != n; ++j)
115 if (idxs.size() == 1) {
117 abstr_vars[i] = vars[i];
119 auto bundle = mk_bundle(lam, vars[i],
DefVec(idxs, [&](
size_t j) {
return vars[j]; }));
121 lattice(vars[j], abstr_vars[j] = bundle);
122 log().d(
"bundle: {}", bundle);
127void SEO::Analysis::gvn_split(Lam* lam,
Defs vars, fe::Span<const Def*> abstr_args, fe::Span<const Def*> abstr_vars) {
137 for (
size_t i = 0, n = vars.size(); i != n; ++i) {
139 if (!bundle)
continue;
141 auto members = bundle->ops().subspan(1);
142 assert(members.size() > 1 &&
"a bundle is only ever built for a congruence class");
145 for (
auto p : members)
146 if (abstr_args[
idx_of(vars, p)] == abstr_args[i]) split.emplace_back(p);
148 if (split.size() == members.size())
continue;
151 if (split.size() == 1) {
153 abstr_vars[i] = vars[i];
154 log().d(
"GVN single: {}", vars[i]);
156 auto new_bundle = mk_bundle(lam, vars[i], split);
157 log().d(
"GVN split: {}", new_bundle);
158 for (
auto p : split) {
160 lattice(vars[j], abstr_vars[j] = new_bundle);
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;
177 log().d(
"no value for sloxy {}; use phi {}", sloxy, phi);
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;
189void SEO::Analysis::propagate_phis(
Lam* lam,
DefVec& phis,
DefVec& abstr_args) {
190 for (
auto ptr : slots()) {
192 if (
auto value = sloxy2val(sloxy)) {
194 phis.emplace_back(phi);
195 abstr_args.emplace_back(value);
196 log().d(
"propagate phi {} for sloxy {} with value {}", phi, sloxy, value);
198 log().d(
"no value for sloxy {}", sloxy);
204const Def* SEO::Analysis::apply_known(Lam* known,
Defs abstr_targs) {
205 auto n = abstr_targs.size();
206 assert(n == known->num_tvars());
208 if (
auto mut =
curr_mut()) lam2callers_[known].emplace(mut);
212 log().d(
"dependent signature; keeping all vars: {}", known);
213 for (
auto var : known->tvars())
215 return abstract_app(known,
world().tuple(abstr_targs));
220 DefVec all_vars(n, [&](
size_t i) {
return known->var(n, i); });
221 DefVec all_abstr_args(abstr_targs.begin(), abstr_targs.end());
223 propagate_phis(known, all_vars, all_abstr_args);
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]);
229 gvn_bundle(known, all_vars, all_abstr_args, all_abstr_vars);
230 gvn_split(known, all_vars, all_abstr_args, all_abstr_vars);
232 lattice(known->var(),
world().tuple(all_abstr_vars.span().subspan(0, n)));
234 for (
size_t i = n, e = all_vars.size(); i != e; ++i)
235 lattice(all_vars[i], all_abstr_vars[i]);
241 for (
auto caller : lam2callers_[known])
244 return abstract_app(known,
world().tuple(all_abstr_args.span().subspan(0, n)));
247const Def* SEO::Analysis::abstract_app(
const Def* callee,
const Def* arg) {
252 auto pi = callee->
type()->isa<
Pi>();
253 return world().
raw_app(pi ? pi->reduce(arg) : arg->type(), callee, arg);
256const Def* SEO::Analysis::rewrite_imm_App(
const App* app) {
262 sloxy2slot_[sloxy] =
slot;
264 log().d(
"slot {} → sloxy {}", ptr, sloxy);
266 fe::assert_emplace(first_, ptr);
272 return apply_known(ret_lam, {abstr_mem,
world().
bot(ptr->type())});
276 auto [mem, ptr, val] =
store->args<3>();
282 sloxy2val(sloxy, abstr_val);
283 log().d(
"store: {} ← {}", sloxy, abstr_val);
286 log().d(
"store to unknown ptr: {} ← {}", abstr_ptr, abstr_val);
288 auto [mem, ptr] =
load->args<2>();
289 auto [_, val] =
load->projs<2>();
294 if (
auto abstr_val = sloxy2val(sloxy)) {
295 log().d(
"load: {} → {}", sloxy, abstr_val);
299 log().d(
"load of unknown value: {}", sloxy);
301 log().d(
"load from unknown ptr: {}", abstr_ptr);
304 auto abstr_callee =
rewrite(app->callee());
305 auto abstr_arg =
rewrite(app->arg());
306 auto known = abstr_callee->isa_mut<
Lam>();
308 auto n = known->num_tvars();
309 return apply_known(known,
DefVec(n, [&](
size_t i) {
return abstr_arg->proj(n, i); }));
314 auto new_arg =
rewrite(app->arg());
315 auto new_callee =
rewrite(app->callee());
316 return abstract_app(new_callee, new_arg);
320 if (
auto src =
curr_mut()->isa<Lam>(); src && src->is_set()) {
322 auto phi_abstr_args =
DefVec();
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);
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());
338 for (
size_t i = 0, e = phi_vars.size(); i != e; ++i) {
339 if (is_top(phi_vars[i]))
continue;
340 fe::assert_emplace(first_, phi_vars[i]);
341 lattice(phi_vars[i], phi_abstr_args[i]);
352 if (!abstr)
return true;
353 if (old_var == abstr)
return true;
355 if (
auto bundle =
isa_bundle(abstr, lam))
return bundle->op(1) == old_var;
360 for (
auto def :
world().roots())
364void SEO::Analysis::analyze(
const Def* def) {
365 if (def->isa<
Var>())
return;
366 if (
auto [_, ins] = visited_.emplace(def); !ins)
return;
367 if (
auto l =
lookup(def)) def = l;
369 if (
auto proxy = def->isa<
Proxy>()) {
371 auto ptr = proxy->op(1);
372 auto slot = fe::lookup(sloxy2slot_, proxy);
376 log().d(
"sloxy {} survived; pinning slot {} to ⊤", proxy,
slot);
382 if (
auto app = def->isa<App>()) {
388 for (
auto d : ret_lam->deps())
397 for (
size_t i = 0, e = lam->num_tdoms(); i != e; ++i) {
398 auto old_var = lam->
var(e, i);
402 for (
auto d : lam->
deps())
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())
414 for (
auto d : def->
deps())
423const Def* SEO::isa_optimized_sloxy(
const Def* def)
const {
432 for (
auto ptr : analysis_.slots())
433 if (
auto sloxy = isa_optimized_sloxy(ptr)) sloxies_.emplace_back(sloxy);
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;
445 for (
size_t j = 0; j != n; ++j) {
446 auto old_var = old_lam->
var(n, j);
450 for (
auto sloxy : sloxies_) {
452 if (var && phi->type()->has_free_var(var))
continue;
456 if (analysis_.can_supply(old_lam, sloxy)) sig.phis.emplace_back(sloxy, phi, val,
keep(old_lam, phi, val));
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);
474 if (isa_optimized_sloxy(ptr)) {
477 assert(!analysis_.unknowns().contains(ret_lam));
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));
484 auto [T, a] =
slot->decurry()->args<2>();
489 auto [
mem, ptr, val] =
store->args<3>();
490 if (isa_optimized_sloxy(ptr))
return rewrite(
mem);
492 auto [res_mem, res_val] =
load->projs<2>();
493 auto [
mem, ptr] =
load->args<2>();
494 if (
auto sloxy = isa_optimized_sloxy(ptr)) {
496 assert(abstr_val &&
"a promoted slot implies every load from it resolved");
497 log().d(
"load from {} resolved to {}", sloxy, abstr_val);
507 if (
auto ol = fe::lookup(lam_new2old_, new_lam)) old_lam = ol;
513 if (sig_of(old_lam).
todo) {
514 log().d(
"needs seo: {}", old_lam);
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));
523 return Super::rewrite_imm_App(old_app);
530 if (sig_of(old_lam).
todo)
return build_lam(old_lam);
531 return Super::rewrite_mut_Lam(old_lam);
538 if (sig_of(old_lam).
todo)
return var_of(old_lam);
539 return Super::rewrite_imm_Var(var);
542const Def* SEO::var_of(
Lam* old_lam) {
543 auto new_lam = build_lam(old_lam);
544 const auto& sig = sig_of(old_lam);
548 for (
size_t i = 0; i != n; ++i)
550 elems[i] = new_lam->var(sig.num_vars, j++);
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;
563 log().d(
"build new lam for {}", old_lam);
565 const auto& sig = sig_of(old_lam);
569 for (
size_t i = 0; i != n; ++i)
571 new_doms.emplace_back(
rewrite(old_lam->
dom(n, i)));
576 for (
const auto& p : sig.phis)
577 if (p.keep) new_doms.emplace_back(
rewrite(p.phi->type()));
580 lam_old2new_[old_lam] = new_lam;
581 lam_new2old_[new_lam] = old_lam;
587 for (
size_t i = 0; i != n; ++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());
595 for (
const auto& p : sig.phis)
597 auto v = new_lam->var(sig.num_vars, j++);
599 log().d(
"map phi {} → {}", p.phi, v);
601 if (p.val != p.phi)
map_root(p.val, v);
608 for (
const auto& p : sig.phis)
610 log().d(
"map phi {} → its propagated value {}", p.phi, p.val);
614 auto _ =
enter(old_lam);
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);
625 for (
size_t i = 0; i != n; ++i)
626 if (sig.keeps[i]) new_args.emplace_back(
rewrite(old_targs[i]));
628 for (
const auto& p : sig.phis)
630 auto arg = analysis_.lam2sloxy2val(
curr_mut<Lam>(), p.sloxy);
632 log().d(
"wire up phi argument {} for sloxy {}", arg, p.sloxy);
633 new_args.emplace_back(
rewrite(arg));
virtual void leave()
Called after curr_mut() has been completely dealt with.
virtual void reset()
Clears the rewriter map and resets Phase::todo() and is_bootstrapping() for the next fixed-point iter...
virtual void finalize()
Run after the main analysis - only in full rounds, so it always sees the complete abstract World.
const Def * callee() const
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.
static auto isa(const Def *def)
Def * set(size_t i, const Def *)
Successively set from left to right.
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Defs deps() const noexcept
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
DbgKey dbg_key() const
Cheap handle for other->set(this->dbg_key()).
std::pair< D *, const Var * > isa_binder() const
Is this a mutable that introduces a Var?
const Def * var(nat_t a, nat_t i) noexcept
nat_t num_vars() noexcept
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
bool nests(Def *mut)
Does this nest mut?
nat_t num_tvars() noexcept
bool is_closed() const
Same as !has_free_vars().
const Var * has_var()
Only returns not nullptr, if Var of this mutable has ever been created.
const Def * filter() const
Lam * set(Filter filter, const Def *body)
const Def * codom() const
void invalidate(bool todo=true)
Signals that another round of fixed-point iteration is required, either as part of.
const fe::Log & log() const
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.
Used as intermediate value during optimizatinos such as Analysis.
static const Proxy * isa(const Def *def)
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...
const Def * abstracted(const Def *def) const
Returns lattice(def) if it differs from def (i.e. we learned something), otherwise nullptr.
virtual bool analyze()
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
World & new_world()
Create new Defs into this.
World & world()=delete
Hides both and forbids direct access.
World & old_world()
Get old Defs from here.
virtual const Def * map(const Def *old_def, const Def *new_def)
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.
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.
virtual const Def * lookup(const Def *old_def)
Lookup old_def by searching in reverse through the stack of maps.
A variable introduced by a binder (mutable).
Def * binder() const
The binder of this Var.
The World represents the whole program and manages creation of MimIR nodes (Defs).
const Proxy * proxy(const Def *type, Defs ops, flags_t tag)
const Def * app(const Def *callee, const Def *arg)
const Def * bot(const Def *type)
const Def * tuple(Defs ops)
const Def * raw_app(const Axm *axm, u8 curry, u8 trip, const Def *type, const Def *callee, const Def *arg)
Lam * mut_lam(const Pi *pi)
bool analyze() final
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
const Def * rewrite_imm_Var(const Var *) final
const Def * rewrite_imm_App(const App *) final
const Def * rewrite_mut_Lam(Lam *) final
static bool is_dependent(Lam *lam)
Does lam's signature refer to its own binder's Var?
static bool keep(Lam *lam, const Def *old_var, const Def *abstr)
static const Proxy * isa_bundle(const Def *def, Lam *lam)
static const Def * mk_phi(World &w, Lam *lam, const Def *sloxy)
static size_t idx_of(Defs vars, const Def *p)
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...
const Def * pointee(const Def *ptr)
const Def * op_slot(const Def *type, const Def *as, const Def *mem, const Def *ret)
fe::View< const Def * > Defs
Lam * isa_optimizable(Lam *lam)
These are Lams that are.
fe::Vector< const Def * > DefVec