24 assert(vars[*i] == p);
27 auto i = std::ranges::find(vars, p);
28 assert(i != vars.end());
29 return i - vars.begin();
37 lam2sloxy2val_.clear();
48const Proxy* SEO::Analysis::mk_sccp_top(
const Def* var) {
52const Def* SEO::Analysis::sccp_join(
Lam* lam,
const Def* var,
const Def* def) {
53 log().d(
"sccp join: {} ⊔ {}", var, def);
55 if (cur == var)
return var;
59 if (def->isa<
Bot>()) {
60 if (!cur)
return lattice(var, def), def;
62 log().d(
"cannot propagate {} → {}: invisible at ⊥-site {}", var, cur, caller);
74 if (def == var)
return cur ? cur : var;
79 log().d(
"cannot propagate {} → {}: out of scope", var, def);
90 if (
auto [_, ins] = first_.emplace(var); ins) {
91 log().d(
"first contribution; restart: {} → {}", var, def);
92 lattice_force(var, def);
98 assert(cur && cur != var);
99 if (cur == def)
return cur;
100 if (cur->isa<
Bot>())
return lattice(var, def), def;
102 log().d(
"cannot propagate {} → {}; cur = {}; try GVN", var, def, cur);
103 auto top = mk_sccp_top(var);
114const Proxy* SEO::Analysis::mk_bundle(
Lam* lam,
const Def* var,
Defs bundle_vars) {
118void SEO::Analysis::gvn_bundle(
Lam* lam,
Defs vars,
Defs abstr_args, fe::Span<const Def*> abstr_vars) {
119 auto n = vars.size();
120 auto idxs = fe::Vector<size_t>();
122 for (
size_t i = 0; i != n; ++i) {
126 idxs.emplace_back(i);
127 for (
size_t j = i + 1; j != n; ++j)
130 if (idxs.size() == 1) {
132 abstr_vars[i] = vars[i];
134 auto bundle = mk_bundle(lam, vars[i],
DefVec(idxs, [&](
size_t j) {
return vars[j]; }));
136 lattice(vars[j], abstr_vars[j] = bundle);
137 log().d(
"bundle: {}", bundle);
142void SEO::Analysis::gvn_split(Lam* lam,
Defs vars, fe::Span<const Def*> abstr_args, fe::Span<const Def*> abstr_vars) {
152 for (
size_t i = 0, n = vars.size(); i != n; ++i) {
153 if (
auto bundle =
isa_bundle(abstr_vars[i], lam)) {
154 auto members = bundle->ops().subspan(1);
155 assert(members.size() > 1 &&
"a bundle is only ever built for a congruence class");
158 for (
auto p : members)
159 if (abstr_args[
idx_of(vars, p)] == abstr_args[i]) split.emplace_back(p);
161 if (split.size() != members.size()) {
163 if (split.size() == 1) {
165 abstr_vars[i] = vars[i];
166 log().d(
"GVN single: {}", vars[i]);
168 auto new_bundle = mk_bundle(lam, vars[i], split);
169 log().d(
"GVN split: {}", new_bundle);
170 for (
auto p : split) {
172 lattice(vars[j], abstr_vars[j] = new_bundle);
187const Def* SEO::Analysis::lam2sloxy2val(
Lam* lam,
const Def* sloxy) {
188 if (
auto sloxy2val = fe::lookup(lam2sloxy2val_, lam))
189 if (
auto val = fe::lookup(*sloxy2val, sloxy))
return val;
192 log().d(
"no value for sloxy {}; use phi {}", sloxy, phi);
197bool SEO::Analysis::can_supply(
Lam* lam,
const Def* sloxy) {
198 if (
auto i = lam2callers_.find(lam); i != lam2callers_.end())
199 for (
auto caller : i->second)
200 if (
auto c = caller->isa_mut<
Lam>(); c && !lam2sloxy2val(c, sloxy))
return false;
209 for (
auto ptr = abstr_ptr;;) {
211 std::ranges::reverse(path);
212 return {sloxy, path};
214 auto [base, index] =
lea->args<2>();
217 if (!index->isa<
Lit>() || !n || agg->
num_tprojs() != *n)
return {};
218 path.emplace_back(index);
225Vars SEO::Analysis::reachable_slots(VarWorklist&
todo) {
226 if (var2slot_.empty())
return {};
230 while (!
todo.empty()) {
231 auto var =
todo.pop();
232 if (var2slot_.contains(var)) res = vars.insert(res, var);
233 if (
auto abstr =
lattice(var); abstr && abstr != var)
todo.push(non_mem_free_vars(abstr));
234 if (
auto stored = fe::lookup(slot2stored_, var))
todo.push(*stored);
239void SEO::Analysis::live(Lam* lam,
Defs abstr_args) {
240 auto todo = VarWorklist();
241 todo.push(lam->free_vars());
242 if (
auto var = lam->has_var())
todo.push(var);
243 for (
auto arg : abstr_args)
244 todo.push(arg->free_vars());
246 auto& live = lam2live_[lam];
247 auto grown =
world().
vars().merge(live, reachable_slots(
todo));
248 if (grown != live) live = grown, touch();
251Vars SEO::Analysis::non_mem_free_vars(
const Def* def) {
253 if (
auto var = def->isa<Var>())
return Vars{var};
254 if (def->isa_mut())
return def->free_vars();
255 if (
auto res = fe::lookup(non_mem_fvs_, def))
return *res;
258 for (
auto d : def->deps())
259 res =
world().
vars().merge(res, non_mem_free_vars(d));
260 return non_mem_fvs_[def] = res;
263void SEO::Analysis::pin_reachable_slots(
const Def* abstr_callee,
const Def* abstr_arg) {
264 if (var2slot_.empty())
return;
266 auto fvsc = non_mem_free_vars(abstr_callee);
267 auto fvsa = non_mem_free_vars(abstr_arg);
268 auto todo = VarWorklist();
269 todo.push(
world().vars().merge(fvsc, fvsa));
270 for (
auto var : reachable_slots(
todo))
272 log().d(
"slot var {} reaches a direct-style call", var);
277void SEO::Analysis::pin_slot(
const App*
slot) {
283 if (
auto i = slot2resolved_.find(ret_lam->has_var()); i != slot2resolved_.end())
284 for (
auto concr : i->second)
285 lattice_erase(concr);
288void SEO::Analysis::resolve(
const Def* sloxy,
const Def* concr,
const Def* abstr) {
290 slot2resolved_[
slot_var(sloxy->op(1))].emplace(concr);
293void SEO::Analysis::propagate_phis(Lam* lam,
DefVec& phis,
DefVec& abstr_args) {
294 const auto& live = lam2live_[lam];
295 for (
auto ptr : slots()) {
296 if (!live.contains(
slot_var(ptr))) {
299 if (
auto value = sloxy2val(sloxy)) {
301 phis.emplace_back(phi);
302 abstr_args.emplace_back(value);
303 log().d(
"propagate phi {} for sloxy {} with value {}", phi, sloxy, value);
305 log().d(
"no value for sloxy {}", sloxy);
311const Def* SEO::Analysis::apply_known(Lam* known,
Defs abstr_targs) {
312 auto n = abstr_targs.size();
313 assert(n == known->num_tvars());
315 if (
auto mut =
curr_mut()) lam2callers_[known].emplace(mut);
318 if (known->is_dependent()) {
319 log().d(
"dependent signature; keeping all vars: {}", known);
320 for (
auto var : known->tvars())
322 return abstract_app(known,
world().tuple(abstr_targs));
327 auto all_vars = known->tvars();
328 DefVec all_abstr_args(n, [&](
size_t i) {
331 return abstr_targs[i];
334 live(known, all_abstr_args);
335 propagate_phis(known, all_vars, all_abstr_args);
337 auto all_abstr_vars =
DefVec(all_vars.size());
338 for (
size_t i = 0, e = all_vars.size(); i != e; ++i)
339 all_abstr_vars[i] = sccp_join(known, all_vars[i], all_abstr_args[i]);
341 gvn_bundle(known, all_vars, all_abstr_args, all_abstr_vars);
342 gvn_split(known, all_vars, all_abstr_args, all_abstr_vars);
344 lattice(known->var(),
world().tuple(all_abstr_vars.span().subspan(0, n)));
346 for (
size_t i = n, e = all_vars.size(); i != e; ++i)
347 lattice(all_vars[i], all_abstr_vars[i]);
353 for (
auto caller : lam2callers_[known])
356 return abstract_app(known,
world().tuple(all_abstr_args.span().subspan(0, n)));
359const Def* SEO::Analysis::abstract_app(
const Def* callee,
const Def* arg) {
364 auto pi = callee->
type()->isa<
Pi>();
365 return world().
raw_app(pi ? pi->reduce(arg) : arg->type(), callee, arg);
368const Def* SEO::Analysis::rewrite_imm_App(
const App* app) {
375 var2slot_[ret_lam->has_var()] =
slot;
376 log().d(
"slot {} → sloxy {}", ptr, sloxy);
378 fe::assert_emplace(first_, ptr);
385 return apply_known(ret_lam, {abstr_mem,
world().
bot(ptr->type())});
389 auto [mem, ptr, val] =
store->args<3>();
394 if (
auto [sloxy, path] =
sloxy_path(abstr_ptr); sloxy) {
395 if (
auto agg = path.empty() ? abstr_val : sloxy2val(sloxy)) {
396 auto& stored = slot2stored_[
slot_var(sloxy->op(1))];
397 if (
auto grown =
world().vars().merge(stored, non_mem_free_vars(abstr_val)); grown != stored)
398 stored = grown, touch();
399 if (!path.empty()) resolve(sloxy, ptr, abstr_ptr);
400 sloxy2val(sloxy, path.empty() ? abstr_val :
world().
insert(agg,
world().tuple(path), abstr_val));
401 log().d(
"store: {} ← {}", abstr_ptr, abstr_val);
405 log().d(
"unresolved store: {} ← {}", abstr_ptr, abstr_val);
407 auto [mem, ptr] =
load->args<2>();
408 auto [_, val] =
load->projs<2>();
412 if (
auto [sloxy, path] =
sloxy_path(abstr_ptr); sloxy) {
413 if (
auto agg = sloxy2val(sloxy)) {
415 log().d(
"load: {} → {}", abstr_ptr, abstr_val);
416 if (!path.empty()) resolve(sloxy, ptr, abstr_ptr);
417 resolve(sloxy, val, abstr_val);
420 log().d(
"load of unknown value: {}", sloxy);
422 log().d(
"load from unknown ptr: {}", abstr_ptr);
425 auto abstr_callee =
rewrite(app->callee());
426 auto abstr_arg =
rewrite(app->arg());
428 if (
has_mem_ty(app->type())) pin_reachable_slots(abstr_callee, abstr_arg);
430 auto n = known->num_tvars();
431 return apply_known(known, abstr_arg->projs(n));
436 auto new_arg =
rewrite(app->arg());
437 auto new_callee =
rewrite(app->callee());
438 return abstract_app(new_callee, new_arg);
442 if (
auto src =
curr_mut()->isa<Lam>(); src && src->is_set()) {
443 auto phi_dsts = fe::Vector<Lam*>();
445 auto phi_abstr_args =
DefVec();
447 auto propagate_unknowns = [
this, &phi_dsts, &phi_vars, &phi_abstr_args, src](
const Def* abstr) {
448 for (
auto mut : abstr->local_muts())
449 if (
auto dst = mut->isa<Lam>(); dst && dst->is_open()) {
450 log().d(
"unknown edge: {} → {}", src, dst);
452 propagate_phis(dst, phi_vars, phi_abstr_args);
453 phi_dsts.resize(phi_vars.size(), dst);
457 auto abstr =
rewrite(src->body());
458 if (
auto app = abstr->isa<App>()) {
459 if (!app->callee()->isa<Lam>()) propagate_unknowns(app->callee());
460 propagate_unknowns(app->arg());
464 for (
size_t i = 0, e = phi_vars.size(); i != e; ++i)
465 sccp_join(phi_dsts[i], phi_vars[i], phi_abstr_args[i]);
475 if (!abstr)
return true;
476 if (old_var == abstr)
return true;
477 if (abstr->isa<
Bot>())
return false;
479 if (
auto bundle =
isa_bundle(abstr, lam))
return bundle->op(1) == old_var;
484 for (
auto def :
world().externals().muts())
488void SEO::Analysis::analyze(
const Def* def) {
489 if (def->isa<
Var>())
return;
490 if (
auto [_, ins] = visited_.emplace(def); !ins)
return;
491 if (
auto l =
lookup(def)) def = l;
493 if (
auto proxy = def->isa<
Proxy>()) {
496 log().d(
"sloxy {} survived; pinning slot {} to ⊤", proxy,
slot);
503 if (
auto app = def->isa<App>()) {
509 for (
auto d : ret_lam->deps())
520 for (
size_t i = 0, e = lam->num_tvars(); i != e; ++i)
521 if (
auto old_var = lam->tvar(i);
keep(lam, old_var,
lattice(old_var)))
analyze(app->targ(i));
523 for (
auto d : lam->deps())
528 }
else if (
auto [lam, var] = def->
isa_binder<Lam>(); lam) {
529 log().d(
"unknown lam: {}", lam);
530 unknowns_.emplace(lam);
532 for (
auto v : var->
tprojs())
536 for (
auto d : def->
deps())
545const Def* SEO::isa_promoted(
const Def* old_ptr)
const {
546 if (
auto abstr =
lattice(old_ptr))
547 if (
auto [sloxy, _] =
sloxy_path(abstr); sloxy &&
lattice(sloxy->op(1)) == sloxy)
return sloxy;
554 for (
auto ptr : analysis_.slots())
555 if (
auto sloxy = isa_promoted(ptr)) sloxies_.emplace_back(sloxy);
559const SEO::Sig& SEO::sig_of(
Lam* old_lam) {
560 auto [i, ins] = lam2sig_.emplace(old_lam, Sig());
561 auto& sig = i->second;
562 if (!ins)
return sig;
566 sig.sieve =
Sieve(old_lam->
tvars(), [
this, old_lam](
const Def* var) { return keep(old_lam, var, lattice(var)); });
568 for (
auto sloxy : sloxies_) {
570 if (var && phi->type()->has_free_var(var))
continue;
574 if (analysis_.can_supply(old_lam, sloxy)) sig.phis.emplace_back(sloxy, phi, val);
577 sig.sieve.cat(sig.phis.size(), [old_lam, &sig](
size_t i) {
578 auto [_, phi, val] = sig.phis[i];
579 return keep(old_lam, phi, val);
584 sig.todo = !sig.sieve.all() && !analysis_.unknowns().contains(old_lam);
594 if (isa_promoted(ptr)) {
597 assert(!analysis_.unknowns().contains(ret_lam));
598 auto new_lam = build_lam(ret_lam);
599 auto new_args = build_args(ret_lam, {
mem, ptr});
600 return map(old_app,
new_world().app(new_lam, new_args));
604 auto [T, a] =
slot->decurry()->args<2>();
609 auto [
mem, ptr, val] =
store->args<3>();
612 auto [res_mem, res_val] =
load->projs<2>();
613 auto [
mem, ptr] =
load->args<2>();
614 if (
auto sloxy = isa_promoted(ptr)) {
616 assert(abstr_val &&
"a promoted slot implies every load from it resolved");
617 log().d(
"load from {} resolved to {}", sloxy, abstr_val);
627 if (
auto ol = fe::lookup(lam_new2old_, new_lam)) old_lam = ol;
633 if (sig_of(old_lam).
todo) {
634 log().d(
"needs seo: {}", old_lam);
635 auto new_lam = build_lam(old_lam);
636 auto new_args = build_args(old_lam, old_app->
targs());
637 return map(old_app,
new_world().app(new_lam, new_args));
649 if (sig_of(old_lam).
todo)
return build_lam(old_lam);
657 if (sig_of(old_lam).
todo)
return var_of(old_lam);
661const Def* SEO::var_of(
Lam* old_lam) {
662 auto new_lam = build_lam(old_lam);
663 const auto& sig = sig_of(old_lam);
667 for (
size_t i = 0; i != n; ++i)
669 elems[i] = new_lam->var(sig.sieve.num_new(), j);
678Lam* SEO::build_lam(
Lam* old_lam) {
679 assert(!old_lam->
is_dependent() &&
"apply_known pins a dependent signature to \u22a4");
680 if (
auto memo = fe::lookup(lam_old2new_, old_lam))
return memo;
682 log().d(
"build new lam for {}", old_lam);
684 const auto& sig = sig_of(old_lam);
688 for (
size_t i = 0; i != n; ++i)
695 for (
size_t i = 0, e = sig.phis.size(); i != e; ++i)
696 if (sig.sieve.cat(i) !=
Sieve::Gone) new_doms.emplace_back(
rewrite(sig.phis[i].phi->type()));
699 lam_old2new_[old_lam] = new_lam;
700 lam_new2old_[new_lam] = old_lam;
704 for (
size_t i = 0; i != n; ++i)
706 auto old_var = old_lam->
tvar(i);
707 auto v = new_lam->
var(sig.sieve.num_new(), j)->
set(old_var->dbg_key());
712 for (
size_t i = 0, e = sig.phis.size(); i != e; ++i)
714 const auto& p = sig.phis[i];
715 auto v = new_lam->var(sig.sieve.num_new(), j);
717 log().d(
"map phi {} → {}", p.phi, v);
719 if (p.val != p.phi)
map(p.val, v);
724 map(old_lam->
var(), var_of(old_lam));
726 for (
size_t i = 0, e = sig.phis.size(); i != e; ++i)
728 const auto& p = sig.phis[i];
729 log().d(
"map phi {} → its propagated value {}", p.phi, p.val);
733 auto _ =
enter(old_lam);
738DefVec SEO::build_args(Lam* old_lam,
Defs old_targs) {
739 const auto& sig = sig_of(old_lam);
740 size_t n = old_lam->num_tvars();
741 assert(old_targs.size() == n);
744 for (
size_t i = 0; i != n; ++i)
747 for (
size_t i = 0, e = sig.phis.size(); i != e; ++i)
749 auto sloxy = sig.phis[i].sloxy;
752 log().d(
"wire up phi argument {} for sloxy {}", arg, sloxy);
753 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() for the next fixed-point iteration.
virtual void finalize()
Run after the main analysis - only in full rounds, so it always sees the complete abstract World.
auto targs(Projector auto f) const noexcept
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
nat_t num_tprojs() const
As above but yields 1, if an Arr/Pack exceeds Flags::scalarize_threshold.
auto tprojs(Projector auto f) const
bool in_scope(Def *mut) const
Is every free Var of this bound by mut or by a mutable that nests mut?
const Def * tvar(nat_t i) noexcept
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
static DefVec cat(Defs a, Defs b)
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
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
auto tvars(Projector auto f) 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 * tdom(nat_t i) const noexcept
static const Def * eta_reduce(const Def *def)
Fast-forwards def along its chain of η-redexes λ x = f x where x ∉ FV(f).
bool is_dependent() const
Does this type() itself or dom() has a Var?
static Lam * isa_rewritable(const Def *def)
Yields def as a mutable Lam, if it is_rewritable() and nullptr otherwise.
const Def * codom() const
static std::optional< T > isa(const Def *def)
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)
virtual bool analyze()
Runs the optional pre-analysis on old_world(), typically to a fixed point, before rewriting begins.
World & new_world()
Create new Defs into this.
const Def * lookup(const Def *) override
Lookup old_def by searching in reverse through the stack of maps.
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.
bool is_top(const Def *old_def) const
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,...
World & old_world()
Get old Defs from here.
const Def * rewrite_imm_App(const App *) override
const Def * map(const Def *old_def, const Def *new_def)
virtual const Def * rewrite_mut_Lam(Lam *)
virtual const Def * rewrite_imm_Var(const Var *)
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.
static constexpr size_t Gone
A variable introduced by a binder (mutable).
static std::pair< const Var *, std::optional< nat_t > > isa_tproj(const Def *def)
Matches a Var v itself or an Extract v#j of it.
Def * binder() const
The binder of this Var.
The World represents the whole program and manages creation of MimIR nodes (Defs).
const Def * app(const Def *callee, const Def *arg)
const Def * bot(const Def *type)
const Def * tuple(Defs ops)
const Def * extract(const Def *d, const Def *i)
const Proxy * proxy(const Def *type, Defs ops, u32 tag, bool vars=false, bool muts=false)
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 old_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
@ bot
Alias for Mode::fast.
static std::pair< const Proxy *, DefVec > sloxy_path(const Def *abstr_ptr)
The sloxy abstr_ptr points into, and the literal mem.lea indices on the way, outermost first.
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 const Var * slot_var(const Def *ptr)
static size_t idx_of(Defs vars, const Def *p)
bool has_mem_ty(const Def *type)
Is type mem.M or a Sigma with a mem.M component?
const App * isa_mem(const Def *def)
If def is a mem.M-typed value, yields its memory type mem.M a; otherwise nullptr.
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
fe::Vector< const Def * > DefVec
fe::Patricia< const Var, DefKey >::Set Vars