MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
escape_analysis.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include <mim/tuple.h>
6
7namespace mim::plug::mem {
8
9namespace {
10
11void dedup(DefVec& defs) {
12 std::ranges::sort(defs, {}, &Def::gid);
13 defs.erase(std::ranges::unique(defs).begin(), defs.end());
14}
15
16} // namespace
17
19 auto res = mim::Analysis::rewrite_imm(def);
20 if (def->isa<Var>() || !def->type()) return res;
21
22 // A value whose type cannot hold a handle (a loaded scalar, say) cannot alias one.
23 if (!may_hold(def->type())) {
24 // Unless it is computed from one, as a bitcast would.
25 if (auto app = res->isa<App>(); app && !Pi::isa_cn(app->callee()->type()) && !mem_def(app->arg()))
26 escape(app->arg());
27 return res;
28 }
29
30 auto carried = DefVec();
31 if (auto app = res->isa<App>()) {
32 if (allocates(app)) {
33 carried.emplace_back(app);
34 } else {
35 join(carried, app->arg());
36 if (!app->callee()->isa_mut<Lam>()) join(carried, app->callee());
37 }
38 } else if (Var::isa_tproj(res).first || res->isa_mut()) {
39 join(carried, res);
40 } else {
41 for (auto op : res->ops())
42 join(carried, op);
43 }
44 dedup(carried);
45
46 if (carried.empty()) return res;
47 if (res != def) aliases_[res] = carried;
48 aliases_[def] = std::move(carried);
49 return res;
50}
51
53 if (auto alloc = freed_alloc(app)) freed_.emplace(alloc);
55}
56
58 auto lam = curr_mut<Lam>();
59 if (!lam || !lam->is_set()) return;
60
61 auto tail = lam->body()->isa<App>();
62 auto ops = tail ? walk(lam, tail) : std::nullopt;
63 if (!ops) {
64 if (!tail) return escape(lam->body());
65 escape(tail->callee());
66 escape(tail->arg());
67 return;
68 }
69
70 auto escaping = escaping_.contains(lam);
71 auto& chain = chains_[lam] = Chain{tail, std::move(*ops), {}, {}};
72 auto own = DefSet(chain.ops.begin(), chain.ops.end());
73 for (size_t i = 0, n = chain.ops.size(); i != n; ++i) {
74 auto op = chain.ops[i];
75 auto carried = DefVec();
76 join(carried, op->callee());
77 join(carried, op->arg());
78 dedup(carried);
79 for (auto d : carried) {
80 if (auto k = d->isa_mut<Lam>()) {
81 escape(k);
82 continue;
83 }
84 chain.last[d] = i;
85 if (escaping)
86 pin(d);
87 else if (!own.contains(d))
88 shared_.emplace(d);
89 }
90
91 // A pointer stored into memory may be loaded again anywhere.
92 if (auto store = Axm::isa<mem::store>(op)) {
93 escape(store->arg(3, 2));
94 } else if (!Axm::isa<mem::load>(op) && !freed_alloc(op)) {
95 escape(op->callee());
96 escape(op->arg());
97 }
98 }
99
100 if (auto known = jumps(tail->callee()); !known.empty()) {
101 for (auto k : known)
102 jump(k, tail, chain.targets);
103 } else {
104 control(tail->callee(), chain.targets);
105 for (auto arg : tail->targs())
106 pass(rewrite(arg), chain.targets);
107 }
108
109 if (escaping)
110 for (auto target : chain.targets)
111 if (auto k = target->isa_mut<Lam>()) escape(k);
112}
113
114bool EscapeAnalysis::may_hold(const Def* type) const {
115 if (type->isa_mut() || type->isa<Pi>() || Axm::isa<mem::Ptr>(type)) return true;
116 if (auto sigma = type->isa<Sigma>())
117 return std::ranges::any_of(sigma->ops(), [this](const Def* op) { return may_hold(op); });
118 if (auto arr = type->isa<Arr>()) return may_hold(arr->body());
119 return false;
120}
121
122std::optional<fe::Vector<const App*>> EscapeAnalysis::walk(Lam* lam, const App* tail) {
123 auto ops = fe::Vector<const App*>();
124 auto done = LamSet();
125 if (auto mem = exit_mem(tail, done); mem && !walk(lam, mem, ops)) return {};
126 std::ranges::reverse(ops);
127 return ops;
128}
129
130const Def* EscapeAnalysis::walk(Lam* lam, const Def* mem, fe::Vector<const App*>& ops) {
131 auto var = mem_var(lam);
132 auto lvar = lam->has_var();
133 while (mem != var) {
134 if (!var && (!lvar || !mem->has_free_var(lvar))) break;
135 auto ex = mem->isa<Extract>();
136 auto op = ex ? ex->tuple()->isa<App>() : mem->isa<App>();
137 if (!op || !(mem = mem_def(op->arg()))) return nullptr;
138 ops.emplace_back(op);
139 }
140 return mem;
141}
142
143const Def* EscapeAnalysis::exit_mem(const App* tail, LamSet& done) {
144 if (auto mem = mem_def(tail->arg())) return mem;
145
146 const Def* res = nullptr;
147 for (auto arg : tail->targs())
148 if (auto k = arg->isa_mut<Lam>(); k && k->is_set() && !mem_var(k) && done.emplace(k).second)
149 if (auto body = k->body()->isa<App>())
150 if (auto mem = exit_mem(body, done)) {
151 auto ops = fe::Vector<const App*>();
152 auto start = walk(k, mem, ops);
153 if (!start || (res && start != res)) return nullptr;
154 res = start;
155 }
156 return res;
157}
158
159void EscapeAnalysis::join(DefVec& carried, const Def* def) const {
160 if (auto lam = def->isa_mut<Lam>()) {
161 if (!lam->is_closed()) carried.emplace_back(lam);
162 } else if (auto [var, i] = Var::isa_tproj(def); var && i) {
163 if (auto val = lattice(def); val && val != def) join(carried, val);
164 } else if (var) {
165 if (auto lam = var->binder()->isa_mut<Lam>())
166 for (auto tvar : lam->tvars())
167 if (auto val = lattice(tvar); val && val != tvar) join(carried, val);
168 } else if (auto i = aliases_.find(def); i != aliases_.end()) {
169 carried.insert(carried.end(), i->second.begin(), i->second.end());
170 }
171}
172
173void EscapeAnalysis::escape(const Def* def) {
174 auto carried = DefVec();
175 join(carried, def);
176 for (auto d : carried)
177 if (auto lam = d->isa_mut<Lam>())
178 escape(lam);
179 else
180 pin(d);
181}
182
183fe::Vector<Lam*> EscapeAnalysis::jumps(const Def* callee) {
184 auto known = [](const Def* def) -> Lam* {
185 if (auto k = Lam::isa_rewritable(def); k && !k->is_dependent()) return k;
186 return nullptr;
187 };
188
189 if (auto k = known(callee)) return {k};
190 auto res = fe::Vector<Lam*>();
191 if (auto sw = Switch(callee)) {
192 for (size_t i = 0, n = sw.num_targets(); i != n; ++i) {
193 auto k = known(sw.target(i));
194 if (!k) return {};
195 res.emplace_back(k);
196 }
197 }
198 return res;
199}
200
201void EscapeAnalysis::jump(Lam* k, const App* tail, DefVec& targets) {
202 targets.emplace_back(k);
203 for (size_t i = 0, n = k->num_tvars(); i != n; ++i) {
204 auto arg = rewrite(tail->targ(i));
205 auto var = k->tvar(i);
206 if (Pi::isa_cn(arg->type()))
207 control(arg, targets);
208 else if (may_hold(var->type()))
209 propagate(k, var, arg);
210 }
211}
212
213void EscapeAnalysis::propagate(Lam* k, const Def* var, const Def* val) {
214 if (val == var) return;
215 auto cur = lattice(var);
216 if (cur == var) return escape(val);
217 auto outside = val->in_scope(k) && !val->free_vars().contains(k->has_var());
218 if (outside && !cur) {
219 lattice(var, val);
220 return;
221 }
222 if (outside && cur == val) return;
223 escape(val);
224 if (cur) escape(cur);
225 pin(var);
226}
227
228void EscapeAnalysis::control(const Def* def, DefVec& targets) {
229 if (def->is_closed()) return;
230 if (auto tuple = def->isa<Tuple>()) {
231 for (auto op : tuple->ops())
232 control(op, targets);
233 } else if (auto sw = Switch(def)) {
234 for (size_t i = 0, n = sw.num_targets(); i != n; ++i)
235 control(sw.target(i), targets);
236 } else if (auto app = def->isa<App>(); app && app->axm()) {
237 control(app->callee(), targets);
238 pass(app->arg(), targets);
239 } else {
240 targets.emplace_back(def);
241 if (!def->isa_mut()) escape(def);
242 }
243}
244
245} // namespace mim::plug::mem
const Def * rewrite(const Def *) override
Rewrites def and then maps the result to its repr().
Definition phase.cpp:154
const auto & lattice() const
The whole map; used e.g. to diff two fixed-point runs.
Definition phase.h:165
bool pin(const Def *def)
Monotonically forces def to ⊤ (keep as is).
Definition phase.h:227
A (possibly paramterized) Array.
Definition tuple.h:305
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
constexpr u32 gid() const noexcept
Global id - unique number for this Def.
Definition def.h:312
A function.
Definition lam.h:91
bool is_dependent() const
Does this type() itself or dom() has a Var?
Definition lam.h:116
static Lam * isa_rewritable(const Def *def)
Yields def as a mutable Lam, if it is_rewritable() and nullptr otherwise.
Definition lam.h:193
virtual void start()=0
Actual entry.
A dependent function type.
Definition lam.h:14
static const Pi * isa_cn(const Def *d)
Definition lam.h:50
D * curr_mut() const
Definition rewrite.h:98
virtual const Def * rewrite_imm(const Def *)
Definition rewrite.cpp:74
virtual const Def * rewrite_imm_App(const App *)
A dependent tuple type.
Definition tuple.h:39
Switch(const Def *)
Definition tuple.cpp:84
A variable introduced by a binder (mutable).
Definition def.h:857
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.
Definition def.cpp:223
const Def * rewrite_imm_App(const App *) override
static bool allocates(const App *app)
void leave() override
Called after curr_mut() has been completely dealt with.
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?
static const App * freed_alloc(const Def *def)
The allocation def releases if it is a mem.free of one; nullptr otherwise.
const Def * rewrite_imm(const Def *) override
Records what each value may carry: the allocations it aliases and the nested Lams in it.
The mem Plugin
const Def * mem_var(Lam *lam)
Returns the memory argument of a function if it has one.
Definition mem.h:51
const Def * mem_def(const Def *def)
Returns the (first) element of type mem.M a from the given tuple.
Definition mem.h:38
GIDSet< Lam * > LamSet
Definition lam.h:211
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94