MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
free_locals.cpp
Go to the documentation of this file.
2
3#include <fe/worklist.h>
4
5namespace mim::plug::mem::phase {
6
8 if (RWPhase::analyze()) return true;
9
10 auto shared = fe::Vector<std::pair<Lam*, const Def*>>();
11 for (const auto& [lam, chain] : escape_.chains()) {
12 for (size_t i = 0, n = chain.ops.size(); i != n; ++i) {
13 auto alloc = chain.ops[i];
14 if (!EscapeAnalysis::allocates(alloc) || escape_.is_top(alloc) || escape_.freed(alloc)) continue;
15 if (escape_.confined(alloc)) {
16 auto last = fe::lookup(chain.last, alloc);
17 auto j = (last ? *last : i) + 1;
18 frees_[j < n ? chain.ops[j] : chain.tail].emplace_back(alloc);
19 } else {
20 shared.emplace_back(lam, alloc);
21 }
22 }
23 }
24
25 if (!shared.empty()) {
26 auto homes = LamSet();
27 for (auto [lam, _] : shared)
28 homes.emplace(lam);
29 // A home nested in another must hang below it, not below the virtual root.
30 auto roots = fe::Vector<Def*>();
31 for (auto lam : homes)
32 if (std::ranges::none_of(homes, [lam](Lam* home) { return home->nests(lam); })) roots.emplace_back(lam);
33
34 const auto nest = Nest(old_world(), roots);
35 mentions(nest.root());
36 for (auto [lam, alloc] : shared)
37 place(nest[lam], nest[lam], alloc);
38 }
39
40 log().d("free_locals: frees before {} ops, on {} edges", frees_.size(), edges_.size());
41 return false;
42}
43
44const DefSet& FreeLocals::mentions(const Nest::Node* node) {
45 auto res = DefSet();
46 if (auto lam = node->mut() ? node->mut()->isa_mut<Lam>() : nullptr)
47 if (auto i = escape_.chains().find(lam); i != escape_.chains().end())
48 for (auto [alloc, _] : i->second.last)
49 res.emplace(alloc);
50 for (auto child : node->children().nodes()) {
51 const auto& sub = mentions(child);
52 res.insert(sub.begin(), sub.end());
53 }
54 return mentions_[node] = std::move(res);
55}
56
57const DefSet& FreeLocals::reach(const Nest::Node* node) {
58 if (auto i = reach_.find(node); i != reach_.end()) return i->second;
59
60 auto res = DefSet();
61 auto todo = fe::BFSWorklist<GIDSet<const Nest::Node*>>();
62 todo.push(node->sibl_deps().nodes());
63 while (!todo.empty()) {
64 auto s = todo.pop();
65 const auto& sub = mentions_.at(s);
66 res.insert(sub.begin(), sub.end());
67 todo.push(s->sibl_deps().nodes());
68 }
69 return reach_[node] = std::move(res);
70}
71
72void FreeLocals::place(const Nest::Node* home, const Nest::Node* node, const Def* alloc) {
73 for (auto child : node->children().nodes())
74 place(home, child, alloc);
75
76 auto lam = node->mut()->isa_mut<Lam>();
77 if (!lam || escape_.escaping(lam) || (node != home && !live(home, node, alloc))) return;
78 auto i = escape_.chains().find(lam);
79 if (i == escape_.chains().end()) return;
80
81 const auto& chain = i->second;
82 auto deads = DefVec();
83 for (auto target : chain.targets)
84 if (dead(home, target, alloc)) deads.emplace_back(target);
85
86 if (deads.size() == chain.targets.size())
87 frees_[chain.tail].emplace_back(alloc);
88 else
89 for (auto target : deads)
90 edges_[chain.tail][target].emplace_back(alloc);
91}
92
93bool FreeLocals::live(const Nest::Node* home, const Nest::Node* node, const Def* alloc) {
94 if (mentions_.at(node).contains(alloc)) return true;
95 for (auto x = node; x != home; x = x->inest())
96 if (reach(x).contains(alloc)) return true;
97 return false;
98}
99
100bool FreeLocals::dead(const Nest::Node* home, const Def* target, const Def* alloc) {
101 auto scope = home->mut();
102 if (!scope->nests(target)) return true;
103 if (auto lam = target->isa_mut<Lam>()) return !live(home, home->nest()[lam], alloc);
104 return false;
105}
106
108 auto allocs = fe::lookup(frees_, app);
109 auto edges = fe::lookup(edges_, app);
110 if (!allocs && !edges) return RWPhase::rewrite_imm_App(app);
111
112 auto& w = new_world();
113 auto mem = allocs ? mem_def(app->arg()) : nullptr;
114 if (allocs && !mem) log().d("free_locals: `{}` threads no memory, leaking {} allocations", app, allocs->size());
115 auto new_arg = mem ? thread(app->arg(), mem, *allocs) : rewrite(app->arg());
116
117 if (!edges) return w.app(rewrite(app->callee()), new_arg);
118 auto args = DefVec(app->num_targs(), [&](size_t i) {
119 auto arg = app->targ(i);
120 return Pi::isa_cn(arg->type()) ? rewrite_target(arg, *edges) : new_arg->proj(app->num_targs(), i);
121 });
122 return w.app(rewrite_target(app->callee(), *edges), w.tuple(args));
123}
124
125const Def* FreeLocals::thread(const Def* arg, const Def* mem, const DefVec& allocs) {
126 if (arg == mem) return free(rewrite(mem), allocs);
127 if (mem_def(arg) != mem) return rewrite(arg);
128 return new_world().tuple(arg->projs([&](const Def* proj) { return thread(proj, mem, allocs); }));
129}
130
131const Def* FreeLocals::free(const Def* mem, const DefVec& allocs) {
132 // TODO: address spaces other than 0 are barely tested; some, like gpu shared memory, cannot be freed.
133 for (auto alloc : allocs)
134 mem = new_world().call<mem::free>(Defs{mem, rewrite(alloc->proj(2, 1))});
135 return mem;
136}
137
138const Def* FreeLocals::rewrite_target(const Def* target, const DefMap<DefVec>& edges) {
139 auto& w = new_world();
140 if (auto allocs = fe::lookup(edges, target)) return trampoline(rewrite(target), *allocs);
141 if (auto tuple = target->isa<Tuple>())
142 return w.tuple(DefVec(tuple->num_ops(), [&](size_t i) { return rewrite_target(tuple->op(i), edges); }));
143 if (auto sw = Switch(target)) return w.extract(rewrite_target(sw.tuple(), edges), rewrite(sw.index()));
144 return rewrite(target);
145}
146
147const Def* FreeLocals::trampoline(const Def* cont, const DefVec& allocs) {
148 auto& w = new_world();
149 auto lam = w.mut_con(Pi::isa_cn(cont->type())->dom())->set("free_then");
150 auto args = DefVec(lam->vars());
151 auto mem = std::ranges::find_if(args, [](const Def* arg) { return isa_mem(arg); });
152 if (mem == args.end()) {
153 log().d("free_locals: `{}` takes no memory, leaking {} allocations", cont, allocs.size());
154 return cont;
155 }
156 *mem = free(*mem, allocs);
157 lam->app(false, cont, args);
158 return lam;
159}
160
161} // namespace mim::plug::mem::phase
const Def * callee() const
Definition lam.h:237
nat_t num_targs() const noexcept
Definition lam.h:252
const Def * arg() const
Definition lam.h:251
Base class for all Defs.
Definition def.h:289
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:588
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:457
bool nests(Def *mut)
Does this nest mut?
Definition def.cpp:382
A function.
Definition lam.h:91
const SiblDeps & sibl_deps() const
Definition nest.h:116
Def * mut() const
The mutable capsulated in this Node or nullptr, if it's a virtual root comprising several Nodes.
Definition nest.h:39
const Children & children() const
Definition nest.h:82
Nest(Def *root)
Definition nest.cpp:9
const fe::Log & log() const
Definition phase.h:60
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
bool todo() const
Definition phase.h:71
static const Pi * isa_cn(const Def *d)
Definition lam.h:50
const Def * dom() const
Definition lam.h:38
virtual bool analyze()
Runs the optional pre-analysis on old_world(), typically to a fixed point, before rewriting begins.
Definition phase.cpp:202
World & new_world()
Create new Defs into this.
Definition phase.h:379
World & old_world()
Get old Defs from here.
Definition phase.h:378
const Def * rewrite_imm_App(const App *) override
Definition phase.cpp:223
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:62
Switch(const Def *)
Definition tuple.cpp:84
Data constructor for a Sigma.
Definition tuple.h:84
const Def * call(const Def *callee, auto &&arg, Args &&... args)
Definition world.h:674
const Def * tuple(Defs ops)
Definition world.cpp:516
static bool allocates(const App *app)
const LamMap< Chain > & chains() const
bool escaping(Lam *lam) const
May lam run at any time, as it is used as a value or jumped to by an escaping Lam?
const Def * rewrite_imm_App(const App *) final
bool analyze() final
Runs the optional pre-analysis on old_world(), typically to a fixed point, before rewriting begins.
The mem Plugin
const Def * mem_def(const Def *def)
Returns the (first) element of type mem.M a from the given tuple.
Definition mem.h:38
const App * isa_mem(const Def *def)
If def is a mem.M-typed value, yields its memory type mem.M a; otherwise nullptr.
Definition mem.h:25
GIDSet< Lam * > LamSet
Definition lam.h:211
fe::View< const Def * > Defs
Definition def.h:96
GIDMap< const Def *, To > DefMap
Definition def.h:93
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94
auto nodes() const
Definition nest.h:57
auto nodes() const
Definition nest.h:88