MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_typed_clos.cpp
Go to the documentation of this file.
2
4
6
7namespace {
8const Def* insert_ret(const Def* def, const Def* ret) {
9 auto new_ops = DefVec(def->num_projs() + 1, [&](auto i) { return (i == def->num_projs()) ? ret : def->proj(i); });
10 auto& w = def->world();
11 return def->is_intro() ? w.tuple(new_ops) : w.sigma(new_ops);
12}
13} // namespace
14
16 auto new_def = rewrite_root(old_mut);
17 // Converted Lam%s are externalized inside make_stub; other externals carry over here.
18 if (auto new_mut = new_def->isa_mut(); new_mut && old_mut->is_external() && !new_mut->is_external())
19 new_mut->externalize();
20}
21
23 while (!worklist_.empty()) {
24 auto [lvm, lcm, old_lam, new_lam] = worklist_.front();
25 worklist_.pop();
26 lvm_ = lvm;
27 lcm_ = lcm;
28 log().d("enter {} (lvm = {}, lcm = {})", new_lam, lvm_, lcm_);
29 if (old_lam->is_set()) new_lam->set(rewrite(old_lam->filter()), rewrite(old_lam->body()));
30 }
31}
32
33Lam* LowerTypedClos::make_stub(Lam* lam, Mode mode, bool adjust_bb_type) {
34 assert(lam && "make_stub: not a lam");
35 if (auto i = lookup(lam); i && i->isa_mut<Lam>()) return i->as_mut<Lam>();
36
37 auto& w = new_world();
38 auto ep = env_param(lam->type()->as<Pi>());
39 auto new_dom = w.sigma(DefVec(lam->num_doms(), [&](auto i) -> const Def* {
40 auto new_dom = rewrite(lam->dom(i));
41 if (i == ep) {
42 if (mode == Unbox) return env_type();
43 if (mode == Box) return w.call<mem::Ptr0>(new_dom);
44 }
45 return new_dom;
46 }));
47 if (Lam::isa_basicblock(lam) && adjust_bb_type) new_dom = insert_ret(new_dom, dummy_ret()->type());
48 auto new_lam = w.mut_lam(w.cn(new_dom))->set(lam->dbg_key());
49 log().d("stub {} → {}", lam, new_lam);
50 if (lam->is_external()) new_lam->externalize();
51
52 auto lcm = mem::mem_var(new_lam);
53 // The environment always lives in slot `ep`; a single-parameter lam has an atomic var (no projection),
54 // so use the whole var there. This selection is independent of `mode` -- the mode only governs how the
55 // environment is subsequently consumed (loaded, bitcast, or passed through), not where it sits.
56 auto env = new_lam->num_vars() < 2 ? new_lam->var() : new_lam->var(ep);
57 if (mode == Box) {
58 // A mem-free closure still has to unbox its heap-allocated environment via a mem.load; if it has no mem of
59 // its own, a throw-away witness is fine here -- if it actually closes over a real mem, that one is recovered
60 // from the unboxed environment below instead (see issue #126).
61 if (!lcm) lcm = w.bot(w.call<mem::M>(0));
62 auto [m, e] = w.call<mem::load>(Defs{lcm, env})->projs<2>();
63 lcm = m->set("mem");
64 env = e->set("closure_env");
65 } else if (mode == Unbox) {
66 env = w.call<core::bitcast>(rewrite(lam->dom(ep)), env)->set("unboxed_env");
67 }
68 auto new_args = w.tuple(DefVec(lam->num_doms(), [&](auto i) {
69 return (i == ep) ? env : (lam->var(i) == mem::mem_var(lam)) ? lcm : new_lam->var(i);
70 }));
71 assert(new_args->num_projs() == lam->num_doms());
72 assert(lam->num_doms() <= new_lam->num_doms());
73 map(lam->var(), new_args);
74
75 // This closure may not have mem as a direct parameter, yet still close over a real one through its captured
76 // environment (e.g. a callback typed without mem that nonetheless uses an outer `mem`). Recover it so
77 // mem-effectful operations inside this closure's own body (e.g. packing a further nested closure) have a real
78 // chain to thread, instead of none (see issue #126).
79 auto lvm = mem::mem_var(lam);
80 // `ep < num_vars()` guards a `Cn []` (or mem-only) closure: there is no environment slot to recover a
81 // mem from, and `lam->var(ep)` would be an out-of-bounds projection reading past the operand array.
82 // Mirrors the `num_vars() < 2` guard applied to `new_lam` above.
83 if (!lvm && ep < lam->num_vars()) {
84 auto old_env = lam->var(ep);
85 if (Axm::isa<mem::M>(old_env->type())) {
86 lvm = old_env;
87 lcm = env;
88 } else if (auto sig = old_env->type()->isa<Sigma>()) {
89 for (size_t i = 0, e = sig->num_ops(); i != e; ++i)
90 if (Axm::isa<mem::M>(sig->op(i))) {
91 lvm = old_env->proj(i);
92 lcm = env->proj(i);
93 break;
94 }
95 }
96 }
97 worklist_.emplace(lvm, lcm, lam, new_lam);
98 map(lam, new_lam);
99 return new_lam;
100}
101
102const Def* LowerTypedClos::rewrite(const Def* def) {
103 if (is_bootstrapping()) return RWPhase::rewrite(def); // rebuild the annexes verbatim
104 if (auto new_def = lookup(def)) return new_def;
105
106 assert((!def->isa<Var>() || !def->as<Var>()->binder()->isa_mut<Lam>()) && "Lam vars should appear in a map!");
107
108 auto& w = new_world();
109
110 // Lower a closure type `[Env: *, Cn [Env, Args..], Env]` to an untyped `(code-ptr, env-ptr)` pair type.
111 if (auto ct = isa_clos_type(def)) {
112 auto pi = rewrite(ct->op(1))->as<Pi>();
113 if (Pi::isa_basicblock(pi)) pi = w.cn(insert_ret(pi->dom(), dummy_ret()->type()));
114 auto env_type = rewrite(ct->op(2));
115 return map(def, w.sigma({pi, env_type}));
116 }
117
118 // Project out of a closure: index 0 is the (erased) env type, 1 the code, 2 the env.
119 if (auto proj = def->isa<Extract>(); proj && isa_clos_type(proj->tuple()->type())) {
120 auto idx = Lit::isa(proj->index());
121 assert(idx && *idx <= 2 && "unknown proj from closure tuple");
122 return map(def, *idx == 0 ? env_type() : rewrite(proj->tuple())->proj(*idx - 1));
123 }
124
125 // Lower a closure literal to an untyped `(code-ptr, env-ptr)` pair, boxing/unboxing the environment.
126 if (auto c = isa_clos_lit(def)) {
127 auto new_type = rewrite(def->type());
128 auto env = rewrite(c.env());
129 auto mode = (env->type()->isa<Idx>() || Axm::isa<mem::Ptr>(env->type())) ? Unbox : Box;
130 const Def* fn = make_stub(c.fnc_as_lam(), mode, true);
131 if (env->type() == w.sigma()) {
132 env = w.bot(env_type()); // optimize empty env
133 } else if (mode == Box) {
134 auto [mem, env_ptr] = mem::op_alloc(env->type(), lcm_)->projs<2>();
135 lcm_ = w.call<mem::store>(Defs{mem, env_ptr, env});
136 map(lvm_, lcm_);
137 env = env_ptr;
138 }
139 fn = w.call<core::bitcast>(new_type->op(0), fn);
140 env = w.call<core::bitcast>(new_type->op(1), env);
141 return map(def, w.tuple({fn, env}));
142 }
143
144 if (auto lam = def->isa_mut<Lam>()) return make_stub(lam, No_Env, false);
145 if (def->isa_mut()) {
146 assert(!isa_clos_type(def));
147 return RWPhase::rewrite_mut(const_cast<Def*>(def));
148 }
149 if (auto var = def->isa<Var>()) return map(def, w.var(rewrite(var->binder())->as_mut()));
150
151 return RWPhase::rewrite(def);
152}
153
154// Give first-class BBs their dummy return continuation.
156 if (is_bootstrapping()) return RWPhase::rewrite_imm_App(app);
157
158 if (auto p = app->callee()->isa<Extract>();
159 p && isa_clos_type(p->tuple()->type()) && Pi::isa_basicblock(app->callee_type())) {
160 auto new_arg = insert_ret(rewrite(app->arg()), dummy_ret()); // arg before callee; see the base hook
161 auto new_callee = rewrite(app->callee());
162 return new_world().app(new_callee, new_arg);
163 }
164
165 return RWPhase::rewrite_imm_App(app);
166}
167
169 if (is_bootstrapping()) return RWPhase::rewrite_imm(def);
170
171 // Leaves and axioms need no mem threading; let the base rebuild them.
172 switch (def->node()) {
173 case Node::Bot:
174 case Node::Top:
175 case Node::Type:
176 case Node::Univ:
177 case Node::Nat:
178 case Node::Axm: return RWPhase::rewrite_imm(def);
179 default: break;
180 }
181
182 auto& w = new_world();
183 auto new_type = rewrite(def->type());
184 auto lcm = lcm_;
185 auto new_def = RWPhase::rewrite_imm(def);
186
187 // Boxing an environment advances lcm_ *while* the operands are rewritten, leaving the operands visited before
188 // it on the stale token. lvm_ maps to the new token by now, so redoing the node picks it up; everything else
189 // is memoized, so nothing is boxed twice.
190 if (lcm_ != lcm) new_def = RWPhase::rewrite_imm(def);
191
192 if (new_type == w.call<mem::M>(0)) { // :store
193 lcm_ = new_def;
194 lvm_ = def;
195 } else if (new_type->isa<Sigma>()) { // :alloc, :slot, ...
196 for (size_t i = 0, e = new_type->num_ops(); i != e; ++i)
197 if (new_type->op(i) == w.call<mem::M>(0)) {
198 lcm_ = w.extract(new_def, i); // new-world mem chain
199 lvm_ = old_world().extract(def, i); // old-world marker, compared against old ops
200 break;
201 }
202 }
203
204 return new_def;
205}
206
207} // namespace mim::plug::clos::phase
const Pi * callee_type() const
Definition lam.h:277
const Def * callee() const
Definition lam.h:275
const Def * arg() const
Definition lam.h:284
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:273
constexpr Node node() const noexcept
Definition def.h:297
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:580
DbgKey dbg_key() const
Cheap handle for other->set(this->dbg_key()).
Definition def.h:610
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:479
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:440
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1111
bool is_external() const noexcept
Definition def.h:553
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:161
A function.
Definition lam.h:113
const Def * dom() const
Definition lam.h:134
const Pi * type() const
Definition lam.h:133
static const Lam * isa_basicblock(const Def *d)
Definition lam.h:145
static std::optional< T > isa(const Def *def)
Definition def.h:937
const fe::Log & log() const
Definition phase.h:79
A dependent function type.
Definition lam.h:14
static const Pi * isa_basicblock(const Def *d)
Is this a continuation (Pi::isa_cn) that is not Pi::isa_returning?
Definition lam.h:56
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
Definition phase.h:403
virtual const Def * rewrite_root(const Def *def)
Rewrites a root - i.e. an annex or an external.
Definition phase.h:411
World & new_world()
Create new Defs into this.
Definition phase.h:452
World & old_world()
Get old Defs from here.
Definition phase.h:451
virtual const Def * rewrite_mut(Def *)
Definition rewrite.cpp:76
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:47
virtual const Def * rewrite_imm(const Def *)
Definition rewrite.cpp:67
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
virtual const Def * lookup(const Def *old_def)
Lookup old_def by searching in reverse through the stack of maps.
Definition rewrite.h:62
A dependent tuple type.
Definition tuple.h:23
A variable introduced by a binder (mutable).
Definition def.h:825
Def * binder() const
The binder of this Var.
Definition def.h:835
const Def * rewrite(const Def *def) final
void finalize() final
Run after all roots have been walked - but for an RWPhase still before the two worlds are swapped.
const Def * rewrite_imm(const Def *def) final
const Def * rewrite_imm_App(const App *) final
ClosLit isa_clos_lit(const Def *def, bool fn_isa_lam=true)
Tries to match a closure literal.
Definition clos.cpp:52
const Sigma * isa_clos_type(const Def *def)
Definition clos.cpp:90
size_t env_param(Defs doms)
Describes where the environment is placed in the argument list: right after a leading mem....
Definition clos.h:101
The mem Plugin
Definition mem.h:11
const Def * mem_var(Lam *lam)
Returns the memory argument of a function if it has one.
Definition mem.h:55
const Def * op_alloc(const Def *type, const Def *as, const Def *mem)
Definition mem.h:129
fe::View< const Def * > Defs
Definition def.h:91
fe::Vector< const Def * > DefVec
Definition def.h:93
@ Nat
Definition def.h:122
@ Univ
Definition def.h:122
@ Bot
Definition def.h:122
@ Axm
Definition def.h:122
@ Type
Definition def.h:122
@ Top
Definition def.h:122