MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
clos_conv.cpp
Go to the documentation of this file.
2
4
5using namespace std::literals;
6
8
9namespace {
10
11bool is_memop_res(const Def* fd) {
12 auto proj = fd->isa<Extract>();
13 if (!proj) return false;
14 auto types = proj->tuple()->type()->ops();
15 return std::ranges::any_of(types, [](auto d) { return Axm::isa<mem::M>(d); });
16}
17
18/// The free (non-closed, not-nested) Def%s directly reachable from @p nest's root.
19DefSet free_defs(const Nest& nest) {
20 DefSet bound, free;
21 std::queue<const Def*> queue;
22 queue.emplace(nest.root()->mut());
23
24 while (!queue.empty()) {
25 for (auto op : pop(queue)->deps()) {
26 if (op->is_closed()) continue; // nothing free in here
27 if (nest.contains(op)) {
28 if (auto [_, ins] = bound.emplace(op); ins) queue.emplace(op);
29 } else {
30 free.emplace(op);
31 }
32 }
33 }
34
35 return free;
36}
37
38} // namespace
39
40/*
41 * Free variable analysis
42 */
43
44void FreeDefAna::classify(Node* node, const Def* fd, bool& spawned_pred, NodeQueue& worklist) {
45 assert(!Axm::isa<mem::M>(fd) && "mem tokens must not be free");
46 if (fd->is_closed()) return;
47
48 if (auto [var, lam] = isa_var_proj<Lam>(fd); var && lam) {
49 if (var != lam->ret_var()) node->add_fvs(fd);
50 } else if (auto free_bb = Axm::isa(attr::free_bb, fd)) {
51 node->add_fvs(free_bb);
52 } else if (auto pred = fd->isa_mut()) {
53 // A referenced nested mutable contributes its own free defs (once it is closure-converted).
54 if (pred != node->mut) {
55 auto [pnode, inserted] = build_node(pred, worklist);
56 node->preds.emplace_back(pnode);
57 pnode->succs.emplace_back(node);
58 spawned_pred |= inserted;
59 }
60 } else if (fd->has_dep(Dep::Var) && !fd->isa<Tuple>()) {
61 // Note: a Var may still be closed if its type is a mut, so the isa_var_proj case above is not redundant.
62 node->add_fvs(fd);
63 } else if (is_memop_res(fd)) {
64 node->add_fvs(fd); // results of memops must not be floated down
65 } else {
66 for (auto op : fd->ops())
67 classify(node, op, spawned_pred, worklist);
68 }
69}
70
71std::pair<FreeDefAna::Node*, bool> FreeDefAna::build_node(Def* mut, NodeQueue& worklist) {
72 auto [p, inserted] = lam2node_.emplace(mut, nullptr);
73 if (!inserted) return {p->second.get(), false};
74 world().DLOG("FVA: create node: {}", mut);
75
76 p->second = std::make_unique<Node>(mut);
77 auto node = p->second.get();
78 bool spawned_pred = false;
79 for (auto fd : free_defs(Nest(mut)))
80 classify(node, fd, spawned_pred, worklist);
81
82 // A node with no fresh predecessors is ready to be settled right away.
83 if (!spawned_pred) {
84 worklist.push(node);
85 world().DLOG("FVA: init {}", mut);
86 }
87 return {node, true};
88}
89
90void FreeDefAna::propagate(NodeQueue& worklist) {
91 while (!worklist.empty()) {
92 auto node = pop(worklist);
93 if (is_done(node)) continue;
94 auto changed = is_bot(node);
95 mark(node);
96 for (auto pred : node->preds)
97 for (auto pfv : pred->fvs)
98 changed |= node->add_fvs(pfv).second;
99 if (changed)
100 for (auto succ : node->succs)
101 worklist.push(succ);
102 }
103}
104
106 auto worklist = NodeQueue();
107 auto [node, _] = build_node(lam, worklist);
108 if (!is_done(node)) {
109 ++cur_pass_;
110 propagate(worklist);
111 }
112 return node->fvs;
113}
114
115/*
116 * Closure Conversion
117 */
118
120 // Bootstrapping: rebuild all annexes verbatim - closure conversion is disabled while converting_ is false.
121 for (const auto& [flags, e] : old_world().annexes())
122 rewrite_annex(flags, e.sym, e.def);
123
124 // Convert everything reachable from the externals, each in its own fresh substitution scope.
125 converting_ = true;
126 for (auto old_mut : old_world().externals().muts()) {
127 push();
128 auto new_def = rewrite(old_mut);
129 pop();
130 // Non-closure externals (e.g. data) carry their external-ness over directly;
131 // converted Lam%s are externalized through their wrapper inside make_stub instead.
132 if (auto new_mut = new_def->isa_mut(); new_mut && old_mut->is_external() && !new_mut->is_external())
133 new_mut->externalize();
134 }
135
136 // Rewrite the deferred closure bodies, each in isolation.
137 while (!body_worklist_.empty()) {
138 auto fn = body_worklist_.front();
139 body_worklist_.pop();
140 push();
141 rewrite_body(closures_.at(fn));
142 pop();
143 }
144
146}
147
148const Def* ClosConv::rewrite_imm_Pi(const Pi* pi) {
149 if (converting_ && Pi::isa_cn(pi)) return clos_type_of(pi);
150 return RWPhase::rewrite_imm_Pi(pi);
151}
152
154 if (converting_ && Pi::isa_cn(pi)) return clos_type_of(pi);
155 return RWPhase::rewrite_mut_Pi(pi);
156}
157
159 if (!converting_ || !Lam::isa_cn(old_lam)) return RWPhase::rewrite_mut_Lam(old_lam);
160
161 auto& w = new_world();
162 auto stub = make_stub(old_lam);
163 auto clos_ty = rewrite(old_lam->type());
164 // Rewrite the individual free defs, not the (possibly normalized) env tuple:
165 // its normal form may reference defs that are not free vars and hence not in the current map.
166 auto env = w.tuple(DefVec(stub.fvs.size(), [&](auto i) { return rewrite(stub.fvs[i]); }));
167 auto closure = clos_pack(env, stub.fn, clos_ty);
168 DLOG("RW: pack {} ~> {} : {}", old_lam, closure, clos_ty);
169 return map(old_lam, closure);
170}
171
173 if (!converting_) return RWPhase::rewrite_imm_App(app);
174
175 if (auto a = Axm::isa<attr>(app))
176 if (auto handled = rewrite_attr(a)) return handled;
177
178 auto new_callee = rewrite(app->callee());
179 auto new_arg = rewrite(app->arg());
180 if (new_callee->type()->isa<Sigma>()) return clos_apply(new_callee, new_arg);
181 return new_world().app(new_callee, new_arg);
182}
183
184const Def* ClosConv::rewrite_attr(Axm::IsA<attr, App> a) {
185 auto& w = new_world();
186 switch (a.id()) {
187 case attr::returning:
188 // A return continuation is *not* closure converted; it stays a plain Cn sharing the enclosing scope.
189 // After η-expansion this should be its only occurrence, so mapping it into the current scope suffices.
190 if (auto ret_lam = a->arg()->isa_mut<Lam>()) {
191 auto new_doms = DefVec(ret_lam->num_doms(), [&](auto i) { return rewrite(ret_lam->dom(i)); });
192 auto new_lam = w.mut_lam(w.cn(new_doms))->set(ret_lam->dbg());
193 map(ret_lam, new_lam);
194 if (ret_lam->is_set()) new_lam->set(rewrite(ret_lam->filter()), rewrite(ret_lam->body()));
195 return new_lam;
196 }
197 return nullptr;
199 case attr::free_bb: {
200 // A free/first-class basic block captures nothing: it gets an empty environment and its body is
201 // rewritten right here, sharing the enclosing scope (same η-conversion remark as above).
202 auto bb_lam = a->arg()->isa_mut<Lam>();
203 assert(bb_lam && Lam::isa_basicblock(bb_lam));
204 auto stub = make_stub({}, bb_lam);
205 auto pack = clos_pack(w.tuple(), stub.fn, rewrite(bb_lam->type()));
206 map(bb_lam, pack);
207 rewrite_body(stub);
208 return pack;
209 }
210 default: return nullptr;
211 }
212}
213
215 // A closure body may still refer to a ret_var of an *enclosing* Lam: return continuations are not
216 // closure-converted, and the FVA deliberately excludes ret_vars, so they are never captured into an env.
217 // Map such a projection onto the corresponding var of the enclosing Lam's converted stub.
218 // This is a known workaround; the principled fix is to capture escaping enclosing BBs/ret_vars in the
219 // environment (tracked by issue #117).
220 if (converting_)
221 if (auto [var, lam] = isa_var_proj<Lam>(ex); var && lam && lam->ret_var() == var) {
222 auto new_fn = make_stub(lam).fn;
223 auto new_idx = skip_env(env_param(new_fn->type()->as<Pi>()), Lit::as(var->index()));
224 return new_fn->var(new_idx);
225 }
226 return RWPhase::rewrite_imm_Extract(ex);
227}
228
230 // Globals are rewritten once and shared, in isolation from any surrounding continuation scope.
231 if (auto i = glob_muts_.find(global); i != glob_muts_.end()) return i->second;
232 push();
233 auto new_global = RWPhase::rewrite_mut_Global(global);
234 pop();
235 return glob_muts_[global] = new_global;
236}
237
238const Pi* ClosConv::rewrite_ret_cn(const Pi* pi) {
239 assert(Pi::isa_basicblock(pi));
240 return new_world().cn(DefVec(pi->num_doms(), [&](auto i) { return rewrite(pi->dom(i)); }));
241}
242
243const Def* ClosConv::clos_type_of(const Pi* pi, const Def* env_type) {
244 if (!env_type)
245 if (auto i = glob_muts_.find(pi); i != glob_muts_.end()) return i->second;
246
247 auto new_doms = DefVec(pi->num_doms(), [&](auto i) {
248 return (i == pi->num_doms() - 1 && Pi::isa_returning(pi)) ? rewrite_ret_cn(pi->ret_pi()) : rewrite(pi->dom(i));
249 });
250 auto ct = ctype(new_world(), new_doms, env_type);
251 if (!env_type) {
252 glob_muts_.emplace(pi, ct);
253 DLOG("C-TYPE: pct {} ~~> {}", pi, ct);
254 } else {
255 DLOG("C-TYPE: ct {}, env = {} ~~> {}", pi, env_type, ct);
256 }
257 return ct;
258}
259
260ClosConv::Stub ClosConv::make_stub(const DefSet& fvs, Lam* old_lam) {
261 auto& w = new_world();
262 auto fv_vec = DefVec(fvs.begin(), fvs.end());
263 auto env_type = rewrite(old_world().tuple(fv_vec)->type());
264 auto new_fn_type = clos_type_of(old_lam->type(), env_type)->as<Pi>();
265 auto new_fn = w.mut_lam(new_fn_type)->set(old_lam->dbg());
266
267 if (!isa_optimizable(old_lam)) {
268 // External or imported (unset) Lam%s get an η-wrapper that hides the environment.
269 auto ep = env_param(new_fn_type);
270 auto new_ext_type = w.cn(clos_remove_env(ep, new_fn_type->dom()));
271 auto new_ext_lam = w.mut_lam(new_ext_type)->set(old_lam->dbg());
272 DLOG("wrap ext lam: {} -> stub: {}, ext: {}", old_lam, new_fn, new_ext_lam);
273 if (old_lam->is_set()) {
274 if (old_lam->is_external()) new_ext_lam->externalize();
275 auto env = w.tuple(DefVec(fv_vec.size(), [&](auto i) { return rewrite(fv_vec[i]); }));
276 new_ext_lam->app(false, new_fn, clos_insert_env(ep, env, new_ext_lam->var()));
277 // new_fn's body is rewritten later via the body worklist.
278 } else {
279 new_ext_lam->unset();
280 new_fn->app(false, new_ext_lam, clos_remove_env(ep, new_fn->var()));
281 }
282 }
283
284 DLOG("STUB {} ~~> {}", old_lam, new_fn);
285 auto stub = Stub{old_lam, std::move(fv_vec), new_fn};
286 closures_.try_emplace(old_lam, stub);
287 closures_.try_emplace(new_fn, stub);
288 return stub;
289}
290
291ClosConv::Stub ClosConv::make_stub(Lam* old_lam) {
292 if (auto i = closures_.find(old_lam); i != closures_.end()) return i->second;
293 auto stub = make_stub(fva_.run(old_lam), old_lam);
294 body_worklist_.emplace(stub.fn);
295 return stub;
296}
297
298void ClosConv::rewrite_body(const Stub& stub) {
299 auto old_fn = stub.old_fn;
300 if (!old_fn->is_set()) return;
301
302 auto& w = new_world();
303 auto new_fn = stub.fn;
304 auto ep = env_param(new_fn->type()->as<Pi>());
305 auto env_val = new_fn->var(ep)->set("closure_env");
306 DLOG("rw body: {} [old={}]", new_fn, old_fn);
307 if (stub.fvs.size() == 1) {
308 map(stub.fvs.front(), env_val);
309 } else {
310 for (size_t i = 0, e = stub.fvs.size(); i != e; ++i) {
311 auto fv = stub.fvs[i];
312 auto sym = w.sym("fv_"s + (fv->sym() ? fv->sym().str() : std::to_string(i)));
313 map(fv, env_val->proj(i)->set(sym));
314 }
315 }
316
317 auto params = w.tuple(DefVec(old_fn->num_doms(), [&](auto i) { return new_fn->var(skip_env(ep, i)); }));
318 map(old_fn->var(), params);
319 new_fn->set(rewrite(old_fn->filter()), rewrite(old_fn->body()));
320}
321
322} // namespace mim::plug::clos::phase
const Def * callee() const
Definition lam.h:276
const Def * arg() const
Definition lam.h:285
static auto isa(const Def *def)
Definition axm.h:107
Base class for all Defs.
Definition def.h:261
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:210
A function.
Definition lam.h:110
static const Lam * isa_cn(const Def *d)
Definition lam.h:141
const Pi * type() const
Definition lam.h:130
static const Lam * isa_basicblock(const Def *d)
Definition lam.h:142
static T as(const Def *def)
Definition def.h:884
A dependent function type.
Definition lam.h:14
static const Pi * isa_cn(const Def *d)
Is this a continuation - i.e. is the Pi::codom mim::Bottom?
Definition lam.h:47
static const Pi * isa_basicblock(const Def *d)
Is this a continuation (Pi::isa_cn) that is not Pi::isa_returning?
Definition lam.h:51
World & new_world()
Create new Defs into this.
Definition phase.h:368
virtual void rewrite_annex(flags_t, Sym, const Def *)
Definition phase.cpp:158
World & old_world()
Get old Defs from here.
Definition phase.h:367
friend void swap(Rewriter &rw1, Rewriter &rw2) noexcept
Definition rewrite.h:82
virtual void push()
Definition rewrite.h:38
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:45
virtual void pop()
Definition rewrite.h:39
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:56
A dependent tuple type.
Definition tuple.h:22
const Pi * cn()
Definition world.h:371
const Def * app(const Def *callee, const Def *arg)
Definition world.cpp:205
const Def * rewrite_mut_Global(Global *) final
const Def * rewrite_imm_Extract(const Extract *) final
const Def * rewrite_mut_Pi(Pi *) final
const Def * rewrite_imm_App(const App *) final
const Def * rewrite_imm_Pi(const Pi *) final
const Def * rewrite_mut_Lam(Lam *) final
void start() override
Actual entry.
const DefSet & run(Lam *lam)
Returns the free defs lam has to capture; see the class description.
#define DLOG(...)
Vaporizes to nothingness in Debug build.
Definition log.h:94
const Def * clos_remove_env(size_t ep, size_t i, std::function< const Def *(size_t)> f)
Definition clos.cpp:122
const Def * ctype(World &w, Defs doms, const Def *env_type=nullptr)
Builds a closure type from the domains doms of a Cn.
Definition clos.cpp:124
const Def * clos_insert_env(size_t ep, size_t i, const Def *env, std::function< const Def *(size_t)> f)
Definition clos.cpp:118
std::tuple< const Extract *, N * > isa_var_proj(const Def *def)
If def is a projection var#i of the Var of some mutable of type N, returns (projection,...
Definition clos.h:72
size_t skip_env(size_t ep, size_t i)
Same as shift_env, but skips the env param instead.
Definition clos.h:107
const Def * clos_pack(const Def *env, const Def *fn, const Def *ct=nullptr)
Pack a typed closure.
Definition clos.cpp:59
const Def * clos_apply(const Def *closure, const Def *args)
Apply a closure to arguments.
Definition clos.cpp:76
size_t env_param(Defs doms)
Describes where the environment is placed in the argument list: right after a leading mem....
Definition clos.h:100
auto pop(S &s) -> decltype(s.top(), typename S::value_type())
Definition util.h:82
Vector< const Def * > DefVec
Definition def.h:79
@ Var
Depends on a Var.
Definition def.h:123
Lam * isa_optimizable(Lam *lam)
These are Lams that are.
Definition lam.h:352
GIDSet< const Def * > DefSet
Definition def.h:76
Node
Definition def.h:107
@ Pi
Definition def.h:109
@ Lam
Definition def.h:109
@ Extract
Definition def.h:109
@ Tuple
Definition def.h:109