MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
clos2sjlj.cpp
Go to the documentation of this file.
2
4
6
7namespace {
8
9// Exception-handling closures (sjlj branches, throw/landing-pad continuations) are always constructed by this
10// phase itself with an explicit leading `mem.M`, so their env slot is always 1 -- see the callers of split/rebuild.
11constexpr size_t Sjlj_Env_Param = 1_u64;
12
13std::array<const Def*, 3> split(const Def* def) {
14 auto new_ops = DefVec(def->num_projs() - 2, nullptr);
15 auto& w = def->world();
16 const Def* mem = nullptr;
17 const Def* env = nullptr;
18 auto j = 0;
19 for (size_t i = 0, n = def->num_projs(); i < n; i++) {
20 auto op = def->proj(n, i);
21 if (op == w.call<mem::M>(0) || op->type() == w.call<mem::M>(0))
22 mem = op;
23 else if (i == Sjlj_Env_Param)
24 env = op;
25 else
26 new_ops[j++] = op;
27 }
28 assert(mem && env);
29 // Unwrap a single remaining component: we want the bare value here, not a 1-element tuple/sigma wrapper.
30 auto remaining = new_ops.size() == 1 ? new_ops[0] : def->is_intro() ? w.tuple(new_ops) : w.sigma(new_ops);
31 return {mem, env, remaining};
32}
33
34const Def* rebuild(const Def* mem, const Def* env, Defs remaining) {
35 auto& w = mem->world();
36 auto new_ops = DefVec(remaining.size() + 2, [&](auto i) -> const Def* {
37 static_assert(Sjlj_Env_Param == 1);
38 if (i == 0) return mem;
39 if (i == 1) return env;
40 return remaining[i - 2];
41 });
42 return w.tuple(new_ops);
43}
44
45} // namespace
46
47void Clos2SJLJ::get_exn_closures(const Def* def, DefSet& visited) {
48 if (!def->is_term() || def->isa_mut<Lam>() || visited.contains(def)) return;
49 visited.emplace(def);
50 if (auto c = isa_clos_lit(def)) {
51 auto lam = c.fnc_as_lam();
52 if (c.is_basicblock() && !ignore_.contains(lam)) {
53 log().d("exception closure: {}", c.fnc_as_lam());
54 lam2tag_[c.fnc_as_lam()] = {lam2tag_.size() + 1, c.env()};
55 }
56 get_exn_closures(c.env(), visited);
57 } else {
58 for (auto op : def->ops())
59 get_exn_closures(op, visited);
60 }
61}
62
63void Clos2SJLJ::get_exn_closures(const Def* body) {
64 lam2tag_.clear();
65 auto app = body->isa<App>();
66 if (!app) return;
67 if (auto p = app->callee()->isa<Extract>(); p && isa_clos_type(p->tuple()->type())) {
68 auto p2 = p->tuple()->isa<Extract>();
69 if (p2 && p2->tuple()->isa<Tuple>()) {
70 // branch: Check the closure environments, but be careful not to traverse
71 // the closures themselves
72 auto branches = p2->tuple()->ops();
73 for (auto b : branches) {
74 auto c = isa_clos_lit(b);
75 if (c) {
76 ignore_.emplace(c.fnc_as_lam());
77 log().d("ignore: {}", c.fnc_as_lam());
78 }
79 }
80 }
81 }
82 auto visited = DefSet();
83 get_exn_closures(app->arg(), visited);
84}
85
86Lam* Clos2SJLJ::get_throw(const Def* dom) {
87 auto& w = new_world();
88 auto [p, inserted] = dom2throw_.emplace(dom, nullptr);
89 auto& tlam = p->second;
90 if (inserted) {
91 tlam = w.mut_con(clos_sub_env(Sjlj_Env_Param, dom, w.sigma({jb_type(), rb_type(), tag_type()})))->set("throw");
92 auto [m0, env, var] = split(tlam->var());
93 auto [jbuf, rbuf, tag] = env->projs<3>();
94 auto [m1, r] = mem::op_alloc(var->type(), m0)->projs<2>();
95 auto m2 = w.call<mem::store>(Defs{m1, r, var});
96 rbuf = w.call<core::bitcast>(w.call<mem::Ptr0>(w.call<mem::Ptr0>(var->type())), rbuf);
97 auto m3 = w.call<mem::store>(Defs{m2, rbuf, r});
98 tlam->set(false, w.call<longjmp>(Defs{m3, jbuf, tag}));
99 ignore_.emplace(tlam);
100 }
101 return tlam;
102}
103
104Lam* Clos2SJLJ::get_lpad(Lam* lam, const Def* rb) {
105 auto& w = new_world();
106 auto [p, inserted] = lam2lpad_.emplace(w.tuple({lam, rb}), nullptr);
107 auto& lpad = p->second;
108 if (inserted) {
109 auto [_, env_type, dom] = split(lam->dom());
110 lpad = mem::mut_con(env_type)->set("lpad");
111 auto [m, env, __] = split(lpad->var());
112 auto [m1, arg_ptr] = w.call<mem::load>(Defs{m, rb})->projs<2>();
113 arg_ptr = w.call<core::bitcast>(w.call<mem::Ptr0>(dom), arg_ptr);
114 auto [m2, args] = w.call<mem::load>(Defs{m1, arg_ptr})->projs<2>();
115 auto full_args = (lam->num_doms() == 3) ? rebuild(m2, env, {args}) : rebuild(m2, env, args->projs());
116 lpad->app(false, lam, full_args);
117 ignore_.emplace(lpad);
118 }
119 return lpad;
120}
121
122std::pair<const Def*, const Def*> Clos2SJLJ::convert(Lam* lam, const Def* filter, const Def* body) {
123 auto& w = new_world();
124 if (!Lam::isa_cn(lam)) return {filter, body};
125 get_exn_closures(body);
126 if (lam2tag_.empty()) return {filter, body};
127
128 {
129 auto m0 = mem::mem_var(lam);
130 auto [m1, jb] = w.call<clos::alloc_jmpbuf>(m0)->projs<2>();
131 auto [m2, rb] = mem::op_alloc(void_ptr(), m1)->projs<2>();
132 auto new_args = lam->vars();
133 new_args[0] = m2;
134 auto rw = VarRewriter(lam->has_var(), w.tuple(new_args));
135 filter = rw.rewrite(filter);
136 body = rw.rewrite(body);
137
138 cur_jbuf_ = jb;
139 cur_rbuf_ = rb;
140
141 // The substitution rebuilds the closures, so collect them again.
142 get_exn_closures(body);
143 }
144
145 auto app = body->as<App>();
146
147 auto branch_type = clos_type(w.cn(w.call<mem::M>(0)));
148 auto branches = DefVec(lam2tag_.size() + 1);
149 {
150 auto env = w.tuple(app->args().view().subspan(1));
151 auto new_callee = mem::mut_con(env->type())->set("sjlj_wrap");
152 auto [m, env_var, _] = split(new_callee->var());
153 auto ne = env->num_projs();
154 auto new_args = DefVec(ne + 1, [&](size_t i) { return (i == 0) ? m : env_var->proj(ne, i - 1); });
155 new_callee->app(false, app->callee(), new_args);
156 branches[0] = clos_pack(env, new_callee, branch_type);
157 }
158
159 for (auto [exn_lam, p] : lam2tag_) {
160 auto [i, env] = p;
161 branches[i] = clos_pack(env, get_lpad(exn_lam, cur_rbuf_), branch_type);
162 }
163
164 auto m0 = app->arg(app->num_args(), 0);
165 assert(m0->type() == w.call<mem::M>(0));
166 auto [m1, tag] = w.call<setjmp>(Defs{m0, cur_jbuf_})->projs<2>();
167 tag = w.call(core::conv::s, branches.size(), tag);
168 auto branch = w.extract(w.tuple(branches), tag);
169
170 // Finally, replace the exception closures (which now live in the branch envs) with throw closures.
171 push({},
172 Vars(lam->has_var())); // cur_jbuf_/cur_rbuf_ are per-Lam, so the substitution must not outlive this convert()
173 body = subst_exn_closures(clos_apply(branch, m1));
174 pop();
175 return {filter, body};
176}
177
178// convert() substitutes within the *already rewritten* body, so a new-world Def means "substitute in place".
179const Def* Clos2SJLJ::rewrite(const Def* def) {
180 return &def->world() == &new_world() ? subst_exn_closures(def) : RWPhase::rewrite(def);
181}
182
183const Def* Clos2SJLJ::subst_exn_closures(const Def* def) {
184 if (auto new_def = lookup(def)) return new_def;
185 if (auto c = isa_clos_lit(def); c && lam2tag_.contains(c.fnc_as_lam())) {
186 auto& w = new_world();
187 auto [i, _] = lam2tag_[c.fnc_as_lam()];
188 auto tlam = get_throw(c.fnc_as_lam()->dom());
189 return map(def, clos_pack(w.tuple({cur_jbuf_, cur_rbuf_, w.lit_idx(i)}), tlam, c.type()));
190 }
191 if (def->isa_mut() || (!def->is_term() && def->is_closed())) return def; // a type may depend on a rewritten term
192 if (def->isa<Var>()) return def; // atomic; binder is in binder_ and not descended into here
193 return rewrite_imm(def)->set(def->dbg_key());
194}
195
197 auto new_lam = new_world().mut_lam(rewrite(old_lam->type())->as<Pi>());
198 map(old_lam, new_lam);
199 if (!old_lam->is_set()) return new_lam;
200
201 auto _ = enter(old_lam);
202 auto [filter, body] = convert(new_lam, rewrite(old_lam->filter()), rewrite(old_lam->body()));
203 new_lam->set(filter, body);
204 return seal(old_lam, new_lam);
205}
206
207} // namespace mim::plug::clos::phase
Base class for all Defs.
Definition def.h:289
bool is_set() const
Definition def.h:396
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:188
World & world() const noexcept
Definition def.h:1105
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:588
bool is_term() const
Is this Def a term, i.e. is its type() a Type?
Definition def.cpp:498
DbgKey dbg_key() const
Cheap handle for other->set(this->dbg_key()).
Definition def.h:631
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 is_closed() const
Same as !has_free_vars().
Definition def.cpp:357
A function.
Definition lam.h:91
const Def * filter() const
Definition lam.h:103
static const Lam * isa_cn(const Def *d)
Definition lam.h:125
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:34
const Pi * type() const
Definition lam.h:112
const Def * body() const
Definition lam.h:104
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
A dependent function type.
Definition lam.h:14
World & new_world()
Create new Defs into this.
Definition phase.h:379
const Def * lookup(const Def *) override
Lookup old_def by searching in reverse through the stack of maps.
Definition phase.cpp:212
void pop()
Definition rewrite.h:41
const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.cpp:29
const Def * seal(Def *old_mut, Def *new_mut)
The tail of rewrite_stub: immutabilizes new_mut in hindsight, as rewriting may have made it vacuous.
Definition rewrite.cpp:280
virtual const Def * rewrite_imm(const Def *)
Definition rewrite.cpp:74
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:62
auto enter(Def *new_mut)
Updates curr_mut() to new_mut and restores it at the end of the scope.
Definition rewrite.h:115
void push(Vars old_vars={}, Vars new_vars={})
Definition rewrite.h:40
VarRewriter(World &world)
Definition rewrite.h:146
A variable introduced by a binder (mutable).
Definition def.h:857
Lam * mut_lam(const Pi *pi)
Definition world.h:415
const Def * rewrite(const Def *) final
const Def * rewrite_mut_Lam(Lam *) final
ClosLit isa_clos_lit(const Def *def, bool fn_isa_lam=true)
Tries to match a closure literal.
Definition clos.cpp:49
const Def * clos_sub_env(size_t ep, const Def *tup_or_sig, const Def *new_env)
Definition clos.h:142
Sigma * clos_type(const Pi *pi)
Creates a typed closure type from pi.
Definition clos.cpp:97
const Def * clos_pack(const Def *env, const Def *fn, const Def *ct=nullptr)
Pack a typed closure.
Definition clos.cpp:57
const Def * clos_apply(const Def *closure, const Def *args)
Apply a closure to arguments.
Definition clos.cpp:74
const Sigma * isa_clos_type(const Def *def)
Definition clos.cpp:86
const Def * mem_var(Lam *lam)
Returns the memory argument of a function if it has one.
Definition mem.h:51
Lam * mut_con(World &w, nat_t a=0)
Yields con[mem.M 0].
Definition mem.h:16
const Def * op_alloc(const Def *type, const Def *as, const Def *mem)
Definition mem.h:108
plug::core::M M
Definition autogen.h:14
fe::View< const Def * > Defs
Definition def.h:96
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94
fe::Patricia< const Var, DefKey >::Set Vars
Definition def.h:115
@ Lam
Definition def.h:125
@ Extract
Definition def.h:125
@ App
Definition def.h:125