3#include <absl/container/fixed_array.h>
24 auto i = std::ranges::find(vars, p);
25 assert(i != vars.end());
26 return i - vars.begin();
34 lam2sloxy2val_.clear();
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;
56 DLOG(
"cannot propagate {} -> {}: out of scope", var, def);
75 if (
auto [_, ins] = first_.emplace(var); ins) {
76 DLOG(
"first; 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 DLOG(
"cannot propagate {} -> {}; cur: {}, trying 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) {
104 auto n_all = vars.size();
105 for (
size_t i = 0; i != n_all; ++i) {
108 auto bundle_vars =
DefVec();
109 bundle_vars.emplace_back(vars[i]);
111 for (
size_t j = i + 1; j != n_all; ++j)
113 bundle_vars.emplace_back(vars[j]);
115 if (bundle_vars.size() == 1) {
117 abstr_vars[i] = vars[i];
119 auto bundle = mk_bundle(lam, vars[i], bundle_vars);
121 for (
auto p : bundle->ops().subspan(1)) {
123 lattice(vars[j], abstr_vars[j] = bundle);
126 DLOG(
"bundle: {}", bundle);
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();
144 for (
auto p : bundle->ops().subspan(1)) {
146 if (p == vars[j] && abstr_args[i] == abstr_args[j]) split_vars.emplace_back(vars[j]);
149 auto new_num = split_vars.size();
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);
158 for (
auto p : new_proxy->ops().subspan(1)) {
160 if (p == vars[j])
lattice(vars[j], abstr_vars[j] = new_proxy);
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;
179 DLOG(
"sloxy {} not found in sloxy2val map; use phi {}", sloxy, phi);
184void SEO::Analysis::propagate_phis(
Lam* lam,
DefVec& phis,
DefVec& abstr_args) {
185 for (
auto ptr : slots()) {
187 if (
auto value = sloxy2val(sloxy)) {
189 phis.emplace_back(phi);
190 abstr_args.emplace_back(value);
191 DLOG(
"propagate phi {} for slot {} w/ val {}", phi, sloxy, value);
193 DLOG(
"no value found for {}", sloxy);
200 if (def->isa<
Proxy>())
return;
201 if (
auto [_, ins] = visited.emplace(def); !ins)
return;
204 if (lam->
is_open()) res.emplace(lam);
210 for (
auto d : def->
deps())
215 if (def->isa<
Lam>())
return;
221const Def* SEO::Analysis::apply_known(
Lam* known,
Defs abstr_targs) {
222 auto n = abstr_targs.size();
225 if (
auto mut =
curr_mut()) lam2callers_[known].emplace(mut);
230 DefVec all_vars(n, [&](
size_t i) {
return known->
tvar(i); });
231 DefVec all_abstr_args(abstr_targs.begin(), abstr_targs.end());
233 propagate_phis(known, all_vars, all_abstr_args);
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]);
239 gvn_bundle(known, all_vars, all_abstr_args, all_abstr_vars);
240 gvn_split(known, all_vars, all_abstr_args, all_abstr_vars);
244 for (
size_t i = n, e = all_vars.size(); i != e; ++i)
245 lattice(all_vars[i], all_abstr_vars[i]);
251 for (
auto caller : lam2callers_[known])
254 return world().
app(known, all_abstr_args.span().subspan(0, n));
257const Def* SEO::Analysis::rewrite_imm_App(
const App* app) {
263 sloxy2slot_[sloxy] =
slot;
265 DLOG(
"slot {} -> sloxy {}", ptr, sloxy);
273 return apply_known(ret_lam, {abstr_mem,
world().
bot(ptr->type())});
277 auto [mem, ptr, val] =
store->args<3>();
283 sloxy2val(sloxy, abstr_val);
284 DLOG(
"store: {} <- {}", sloxy, abstr_val);
287 DLOG(
"store w/ unknown ptr: {} <- {}", abstr_ptr, abstr_val);
289 auto [mem, ptr] =
load->args<2>();
290 auto [_, val] =
load->projs<2>();
295 if (
auto abstr_val = sloxy2val(sloxy)) {
296 DLOG(
"load: {} -> {}", sloxy, abstr_val);
300 DLOG(
"load w/ unknown value: {}", sloxy);
302 DLOG(
"load w/ unknown ptr: {}", abstr_ptr);
307 auto known = abstr_callee->isa_mut<
Lam>();
309 DefVec abstr_targs(app->num_targs(), [&](
size_t i) { return abstr_arg->tproj(i); });
310 return apply_known(known, abstr_targs);
314 auto phi_abstr_args =
DefVec();
320 for (
auto lam : lams) {
321 assert(lam != known && lam->
is_open());
323 propagate_phis(lam, phi_vars, phi_abstr_args);
326 for (
size_t i = 0, e = phi_vars.size(); i != e; ++i) {
328 lattice(phi_vars[i], phi_abstr_args[i]);
332 return Super::rewrite_imm_App(app);
341 if (!abstr)
return true;
342 if (old_var == abstr)
return true;
344 if (
auto bundle =
isa_bundle(abstr, lam))
return bundle->op(1) == old_var;
349 for (
auto def :
world().roots())
353void SEO::Analysis::analyze(
const Def* def) {
354 if (def->isa<
Var>())
return;
355 if (
auto [_, ins] = visited_.emplace(def); !ins)
return;
356 if (
auto l =
lookup(def)) def = l;
358 if (
auto proxy = def->isa<
Proxy>()) {
360 auto ptr = proxy->op(1);
361 auto slot = sloxy2slot_[proxy];
365 DLOG(
"sloxy {} survived; setting slot to top: {}", proxy,
slot);
371 if (
auto app = def->isa<
App>()) {
377 for (
auto d : ret_lam->deps())
386 for (
size_t i = 0, e = lam->num_tdoms(); i != e; ++i) {
387 auto old_var = lam->
var(e, i);
391 for (
auto d : lam->
deps())
397 DLOG(
"lam {} unknown", lam);
398 unknowns_.emplace(lam);
399 for (
auto v : var->
tprojs())
403 for (
auto d : def->
deps())
412const Def* SEO::isa_optimized_sloxy(
const Def* def)
const {
422 if (isa_optimized_sloxy(ptr)) {
425 assert(!analysis_.unknowns().contains(ret_lam));
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));
433 auto [T, a] =
slot->decurry()->args<2>();
438 auto [
mem, ptr, val] =
store->args<3>();
439 if (isa_optimized_sloxy(ptr))
return rewrite(
mem);
441 auto [res_mem, res_val] =
load->projs<2>();
442 auto [
mem, ptr] =
load->args<2>();
443 if (
auto sloxy = isa_optimized_sloxy(ptr)) {
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);
456 if (
auto ol =
mim::lookup(lam_new2old_, new_lam)) old_lam = ol;
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));
473 return Super::rewrite_imm_App(old_app);
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);
485 auto [i, ins] = lam2phis_.emplace(old_lam,
Vector<Phi>());
486 auto& phis = i->second;
488 for (
auto ptr : analysis_.slots())
489 if (
auto sloxy = isa_optimized_sloxy(ptr)) {
492 phis.emplace_back(sloxy, phi, val);
500 if (analysis_.unknowns().contains(old_lam))
return false;
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;
509 for (
auto [sloxy, phi, val] : phis)
510 if (
keep(old_lam, phi, val))
return true;
516 if (
auto new_lam =
mim::lookup(lam_old2new_, old_lam))
return new_lam;
518 DLOG(
"building a new lam for {}", old_lam);
520 size_t num_old = old_lam->num_tvars();
523 auto keeps = absl::FixedArray<bool>(num_old);
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)));
531 for (
auto [sloxy, phi, val] : phis)
532 if (
keep(old_lam, phi, val)) new_doms.emplace_back(
rewrite(phi->type()));
534 size_t num_new_vars = new_doms.size();
537 auto var_map = absl::FixedArray<const Def*>(num_old);
539 lam_old2new_[old_lam] = new_lam;
540 lam_new2old_[new_lam] = old_lam;
548 for (
size_t i = 0; i != num_old; ++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);
558 for (
auto [sloxy, phi, val] : phis) {
559 if (
keep(old_lam, phi, val)) {
560 auto v = new_lam->var(num_new_vars, j++);
562 DLOG(
"mapping phi {} to {}", phi, v);
564 if (val != phi)
map(val, v);
569 for (
size_t i = 0; i != num_old; ++i)
571 auto old_var = old_lam->var(num_old, i);
580 DLOG(
"propagate: old_lam {} - new_lam {}; var {} - with {}", old_lam, new_lam, i, new_def);
581 var_map[i] = new_def;
588 map(old_lam->var(), var_map);
590 for (
auto [sloxy, phi, val] : phis)
591 if (!
keep(old_lam, phi, val)) {
592 DLOG(
"mapping phi {} to its propagated value {}", phi, val);
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);
607 size_t num_old = old_lam->num_tvars();
608 assert(old_targs.size() == num_old);
611 for (
size_t i = 0; i != num_old; ++i) {
612 auto old_var = old_lam->var(num_old, i);
614 if (
keep(old_lam, old_var, abstr)) new_args.emplace_back(
rewrite(old_targs[i]));
617 DLOG(
"wiring up phi arguments");
618 for (
auto [sloxy, phi, val] : phis)
619 if (
keep(old_lam, phi, val)) {
622 new_args.emplace_back(
rewrite(arg));
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.
const Def * callee() const
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
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.
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
bool is_open() const
Has free_vars()?
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
Lam * set(Filter filter, const Def *body)
void invalidate(bool todo=true)
Signals that another round of fixed-point iteration is required, either as part of.
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.
static const Proxy * isa(const Def *def)
virtual bool analyze()
Runs the optional pre-analysis on RWPhase::old_world(), typically to a fixed point,...
World & new_world()
Create new Defs into this.
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.
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.
virtual const Def * map(const Def *old_def, const Def *new_def)
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.
This is a thin wrapper for std::span<T, N> with the following additional features:
A variable introduced by a binder (mutable).
This is a thin wrapper for absl::InlinedVector<T, N, A> which is a drop-in replacement for std::vecto...
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)
Lam * mut_lam(const Pi *pi)
const Def * rewrite_imm_App(const App *) final
const Def * rewrite_mut_Lam(Lam *) final
#define DLOG(...)
Vaporizes to nothingness in Debug build.
static void find_unknowns(DefSet &visited, LamSet &res, const Def *def)
static void find_unknowns_callee(DefSet &visited, LamSet &res, const Def *def)
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)
Vector< const Def * > DefVec
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.
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...
Lam * isa_optimizable(Lam *lam)
These are Lams that are.
GIDSet< const Def * > DefSet