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, *env;
17 auto j = 0;
18 for (size_t i = 0; i < def->num_projs(); i++) {
19 auto op = def->proj(i);
20 if (op == w.call<mem::M>(0) || op->type() == w.call<mem::M>(0))
21 mem = op;
22 else if (i == Sjlj_Env_Param)
23 env = op;
24 else
25 new_ops[j++] = op;
26 }
27 assert(mem && env);
28 // Unwrap a single remaining component: we want the bare value here, not a 1-element tuple/sigma wrapper.
29 auto remaining = new_ops.size() == 1 ? new_ops[0] : def->is_intro() ? w.tuple(new_ops) : w.sigma(new_ops);
30 return {mem, env, remaining};
31}
32
33const Def* rebuild(const Def* mem, const Def* env, Defs remaining) {
34 auto& w = mem->world();
35 auto new_ops = DefVec(remaining.size() + 2, [&](auto i) -> const Def* {
36 static_assert(Sjlj_Env_Param == 1);
37 if (i == 0) return mem;
38 if (i == 1) return env;
39 return remaining[i - 2];
40 });
41 return w.tuple(new_ops);
42}
43
44} // namespace
45
46void Clos2SJLJ::get_exn_closures(const Def* def, DefSet& visited) {
47 if (!def->is_term() || def->isa_mut<Lam>() || visited.contains(def)) return;
48 visited.emplace(def);
49 if (auto c = isa_clos_lit(def)) {
50 auto lam = c.fnc_as_lam();
51 if (c.is_basicblock() && !ignore_.contains(lam)) {
52 DLOG("FOUND exn closure: {}", c.fnc_as_lam());
53 lam2tag_[c.fnc_as_lam()] = {lam2tag_.size() + 1, c.env()};
54 }
55 get_exn_closures(c.env(), visited);
56 } else {
57 for (auto op : def->ops())
58 get_exn_closures(op, visited);
59 }
60}
61
62void Clos2SJLJ::get_exn_closures(Lam* lam) {
63 lam2tag_.clear();
64 if (!lam->is_set() || !Lam::isa_cn(lam)) return;
65 auto app = lam->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 DLOG("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 || !tlam) {
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 || !lpad) {
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->ops());
116 lpad->app(false, lam, full_args);
117 ignore_.emplace(lpad);
118 }
119 return lpad;
120}
121
122void Clos2SJLJ::convert(Lam* lam) {
123 auto& w = new_world();
124 get_exn_closures(lam);
125 if (lam2tag_.empty()) return;
126
127 {
128 auto m0 = mem::mem_var(lam);
129 auto [m1, jb] = w.call<clos::alloc_jmpbuf>(m0)->projs<2>();
130 auto [m2, rb] = mem::op_alloc(void_ptr(), m1)->projs<2>();
131 auto new_args = lam->vars();
132 new_args[0] = m2;
133 auto new_defs = lam->reduce(w.tuple(new_args));
134 lam->unset()->set(new_defs);
135
136 cur_jbuf_ = jb;
137 cur_rbuf_ = rb;
138
139 // apparently the reduce can change the id of the closures, so we have to do it again :(
140 get_exn_closures(lam);
141 }
142
143 auto body = lam->body()->as<App>();
144
145 auto branch_type = clos_type(w.cn(w.call<mem::M>(0)));
146 auto branches = DefVec(lam2tag_.size() + 1);
147 {
148 auto env = w.tuple(body->args().view().subspan(1));
149 auto new_callee = mem::mut_con(env->type())->set("sjlj_wrap");
150 auto [m, env_var, _] = split(new_callee->var());
151 auto new_args = DefVec(env->num_projs() + 1, [&](size_t i) { return (i == 0) ? m : env_var->proj(i - 1); });
152 new_callee->app(false, body->callee(), new_args);
153 branches[0] = clos_pack(env, new_callee, branch_type);
154 }
155
156 for (auto [exn_lam, p] : lam2tag_) {
157 auto [i, env] = p;
158 branches[i] = clos_pack(env, get_lpad(exn_lam, cur_rbuf_), branch_type);
159 }
160
161 auto m0 = body->arg(0);
162 assert(m0->type() == w.call<mem::M>(0));
163 auto [m1, tag] = w.call<setjmp>(Defs{m0, cur_jbuf_})->projs<2>();
164 tag = w.call(core::conv::s, branches.size(), tag);
165 auto filter = lam->filter();
166 auto branch = w.extract(w.tuple(branches), tag);
167 lam->unset()->set({filter, clos_apply(branch, m1)});
168
169 // Finally, replace the exception closures (which now live in the branch envs) with throw closures.
170 auto memo = Def2Def();
171 auto new_body = subst_exn_closures(lam->body(), memo);
172 lam->unset()->set({filter, new_body});
173}
174
175/// Substitutes closure literals of tagged exception Lams by throw closures within @p def's
176/// (immutable) graph; does not descend into mutables.
177const Def* Clos2SJLJ::subst_exn_closures(const Def* def, Def2Def& memo) {
178 if (auto i = memo.find(def); i != memo.end()) return i->second;
179 if (auto c = isa_clos_lit(def); c && lam2tag_.contains(c.fnc_as_lam())) {
180 auto& w = new_world();
181 auto [i, _] = lam2tag_[c.fnc_as_lam()];
182 auto tlam = get_throw(c.fnc_as_lam()->dom());
183 return memo[def] = clos_pack(w.tuple({cur_jbuf_, cur_rbuf_, w.lit_idx(i)}), tlam, c.type());
184 }
185 if (def->isa_mut() || !def->is_term()) return def;
186 if (def->isa<Var>()) return def; // atomic; binder is in binder_ and not descended into here
187 auto new_ops = DefVec(def->num_ops(), [&](size_t i) { return subst_exn_closures(def->op(i), memo); });
188 return memo[def] = def->rebuild(def->type(), new_ops);
189}
190
192 auto new_def = RWPhase::rewrite_mut_Lam(old);
193 if (auto lam = new_def->isa_mut<Lam>(); lam && !is_bootstrapping()) convert(lam);
194 return new_def;
195}
196
197} // namespace mim::plug::clos::phase
Base class for all Defs.
Definition def.h:261
auto projs(F f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:402
A function.
Definition lam.h:110
static const Lam * isa_cn(const Def *d)
Definition lam.h:141
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:29
const Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
World & new_world()
Create new Defs into this.
Definition phase.h:368
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
Definition phase.h:356
const Def * rewrite_mut_Lam(Lam *) final
#define DLOG(...)
Vaporizes to nothingness in Debug build.
Definition log.h:94
ClosLit isa_clos_lit(const Def *def, bool fn_isa_lam=true)
Tries to match a closure literal.
Definition clos.cpp:50
const Def * clos_sub_env(size_t ep, const Def *tup_or_sig, const Def *new_env)
Definition clos.h:135
Sigma * clos_type(const Pi *pi)
Creates a typed closure type from pi.
Definition clos.cpp:99
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
const Sigma * isa_clos_type(const Def *def)
Definition clos.cpp:88
const Def * mem_var(Lam *lam)
Returns the memory argument of a function if it has one.
Definition mem.h:55
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:129
View< const Def * > Defs
Definition def.h:78
DefMap< const Def * > Def2Def
Definition def.h:77
Vector< const Def * > DefVec
Definition def.h:79
GIDSet< const Def * > DefSet
Definition def.h:76
@ Lam
Definition def.h:109
@ Var
Definition def.h:109
@ Extract
Definition def.h:109
@ App
Definition def.h:109
@ Tuple
Definition def.h:109