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 // Bootstrapping: rebuild all annexes verbatim (see LowerTypedClos::rewrite).
17 for (const auto& [flags, e] : old_world().annexes())
18 rewrite_annex(flags, e.sym, e.def);
19
20 converting_ = true;
21 dummy_ret_ = new_world().bot(new_world().cn(new_world().call<mem::M>(0)));
22
23 for (auto old_mut : old_world().externals().muts()) {
24 auto new_def = rewrite(old_mut);
25 // Converted Lam%s are externalized inside make_stub; other externals carry over here.
26 if (auto new_mut = new_def->isa_mut(); new_mut && old_mut->is_external() && !new_mut->is_external())
27 new_mut->externalize();
28 }
29
30 while (!worklist_.empty()) {
31 auto [lvm, lcm, old_lam, new_lam] = worklist_.front();
32 worklist_.pop();
33 lvm_ = lvm;
34 lcm_ = lcm;
35 DLOG("in {} (lvm={}, lcm={})", new_lam, lvm_, lcm_);
36 if (old_lam->is_set()) new_lam->set(rewrite(old_lam->filter()), rewrite(old_lam->body()));
37 }
38
40}
41
42Lam* LowerTypedClos::make_stub(Lam* lam, Mode mode, bool adjust_bb_type) {
43 assert(lam && "make_stub: not a lam");
44 if (auto i = lookup(lam); i && i->isa_mut<Lam>()) return i->as_mut<Lam>();
45
46 auto& w = new_world();
47 auto ep = env_param(lam->type()->as<Pi>());
48 auto new_dom = w.sigma(DefVec(lam->num_doms(), [&](auto i) -> const Def* {
49 auto new_dom = rewrite(lam->dom(i));
50 if (i == ep) {
51 if (mode == Unbox) return env_type();
52 if (mode == Box) return w.call<mem::Ptr0>(new_dom);
53 }
54 return new_dom;
55 }));
56 if (Lam::isa_basicblock(lam) && adjust_bb_type) new_dom = insert_ret(new_dom, dummy_ret_->type());
57 auto new_lam = w.mut_lam(w.cn(new_dom))->set(lam->dbg());
58 DLOG("stub {} ~> {}", lam, new_lam);
59 if (lam->is_external()) new_lam->externalize();
60
61 auto lcm = mem::mem_var(new_lam);
62 // The environment always lives in slot `ep`; a single-parameter lam has an atomic var (no projection),
63 // so use the whole var there. This selection is independent of `mode` -- the mode only governs how the
64 // environment is subsequently consumed (loaded, bitcast, or passed through), not where it sits.
65 auto env = new_lam->num_vars() < 2 ? new_lam->var() : new_lam->var(ep);
66 if (mode == Box) {
67 // A mem-free closure still has to unbox its heap-allocated environment via a mem.load; if it has no mem of
68 // its own, a throw-away witness is fine here -- if it actually closes over a real mem, that one is recovered
69 // from the unboxed environment below instead (see issue #126).
70 if (!lcm) lcm = w.bot(w.call<mem::M>(0));
71 auto [m, e] = w.call<mem::load>(Defs{lcm, env})->projs<2>();
72 lcm = m->set("mem");
73 env = e->set("closure_env");
74 } else if (mode == Unbox) {
75 env = w.call<core::bitcast>(rewrite(lam->dom(ep)), env)->set("unboxed_env");
76 }
77 auto new_args = w.tuple(DefVec(lam->num_doms(), [&](auto i) {
78 return (i == ep) ? env : (lam->var(i) == mem::mem_var(lam)) ? lcm : new_lam->var(i);
79 }));
80 assert(new_args->num_projs() == lam->num_doms());
81 assert(lam->num_doms() <= new_lam->num_doms());
82 map(lam->var(), new_args);
83
84 // This closure may not have mem as a direct parameter, yet still close over a real one through its captured
85 // environment (e.g. a callback typed without mem that nonetheless uses an outer `mem`). Recover it so
86 // mem-effectful operations inside this closure's own body (e.g. packing a further nested closure) have a real
87 // chain to thread, instead of none (see issue #126).
88 auto lvm = mem::mem_var(lam);
89 if (!lvm) {
90 auto old_env = lam->var(ep);
91 if (Axm::isa<mem::M>(old_env->type())) {
92 lvm = old_env;
93 lcm = env;
94 } else if (auto sig = old_env->type()->isa<Sigma>()) {
95 for (size_t i = 0, e = sig->num_ops(); i != e; ++i)
96 if (Axm::isa<mem::M>(sig->op(i))) {
97 lvm = old_env->proj(i);
98 lcm = env->proj(i);
99 break;
100 }
101 }
102 }
103 worklist_.emplace(lvm, lcm, lam, new_lam);
104 map(lam, new_lam);
105 return new_lam;
106}
107
108const Def* LowerTypedClos::rewrite(const Def* def) {
109 if (!converting_) return RWPhase::rewrite(def); // bootstrapping: rebuild verbatim
110 if (auto new_def = lookup(def)) return new_def;
111
112 assert((!def->isa<Var>() || !def->as<Var>()->binder()->isa_mut<Lam>()) && "Lam vars should appear in a map!");
113
114 auto& w = new_world();
115
116 // Lower a closure type `[Env: *, Cn [Env, Args..], Env]` to an untyped `(code-ptr, env-ptr)` pair type.
117 if (auto ct = isa_clos_type(def)) {
118 auto pi = rewrite(ct->op(1))->as<Pi>();
119 if (Pi::isa_basicblock(pi)) pi = w.cn(insert_ret(pi->dom(), dummy_ret_->type()));
120 auto env_type = rewrite(ct->op(2));
121 return map(def, w.sigma({pi, env_type}));
122 }
123
124 // Project out of a closure: index 0 is the (erased) env type, 1 the code, 2 the env.
125 if (auto proj = def->isa<Extract>(); proj && isa_clos_type(proj->tuple()->type())) {
126 auto idx = Lit::isa(proj->index());
127 assert(idx && *idx <= 2 && "unknown proj from closure tuple");
128 return map(def, *idx == 0 ? env_type() : rewrite(proj->tuple())->proj(*idx - 1));
129 }
130
131 // Lower a closure literal to an untyped `(code-ptr, env-ptr)` pair, boxing/unboxing the environment.
132 if (auto c = isa_clos_lit(def)) {
133 auto new_type = rewrite(def->type());
134 auto env = rewrite(c.env());
135 auto mode = (env->type()->isa<Idx>() || Axm::isa<mem::Ptr>(env->type())) ? Unbox : Box;
136 const Def* fn = make_stub(c.fnc_as_lam(), mode, true);
137 if (env->type() == w.sigma()) {
138 env = w.bot(env_type()); // optimize empty env
139 } else if (mode == Box) {
140 auto [mem, env_ptr] = mem::op_alloc(env->type(), lcm_)->projs<2>();
141 lcm_ = w.call<mem::store>(Defs{mem, env_ptr, env});
142 map(lvm_, lcm_);
143 env = env_ptr;
144 }
145 fn = w.call<core::bitcast>(new_type->op(0), fn);
146 env = w.call<core::bitcast>(new_type->op(1), env);
147 return map(def, w.tuple({fn, env}));
148 }
149
150 if (auto lam = def->isa_mut<Lam>()) return make_stub(lam, No_Env, false);
151 if (def->isa_mut()) {
152 assert(!isa_clos_type(def));
153 return RWPhase::rewrite_mut(const_cast<Def*>(def));
154 }
155 if (auto var = def->isa<Var>()) return map(def, w.var(rewrite(var->binder())->as_mut()));
156
157 // Leaves and axioms need no mem threading; let the base rebuild them.
158 switch (def->node()) {
159 case Node::Bot:
160 case Node::Top:
161 case Node::Type:
162 case Node::Univ:
163 case Node::Nat:
164 case Node::Axm: return RWPhase::rewrite_imm(def);
165 default: break;
166 }
167
168 // Generic immutable: rebuild, add a dummy return continuation to first-class BBs, then thread the mem token.
169 auto new_type = rewrite(def->type());
170 auto new_ops = DefVec(def->num_ops(), [&](auto i) { return rewrite(def->op(i)); });
171 if (auto app = def->isa<App>())
172 if (auto p = app->callee()->isa<Extract>();
173 p && isa_clos_type(p->tuple()->type()) && Pi::isa_basicblock(app->callee_type()))
174 new_ops[1] = insert_ret(new_ops[1], dummy_ret_);
175 auto new_def = def->rebuild(w, new_type, new_ops);
176
177 // Rethread the operand that consumed this function's current mem chain end (lvm_): boxing above may have
178 // advanced lcm_ past what the operand was rewritten to. Only that operand - other mem operands may belong to
179 // another function's chain (shared subgraphs) and rethreading them would corrupt it.
180 for (size_t i = 0, e = new_def->num_ops(); i != e; ++i)
181 if (def->op(i) == lvm_ && new_def->op(i)->type() == w.call<mem::M>(0)) new_def = new_def->refine(i, lcm_);
182
183 if (new_type == w.call<mem::M>(0)) { // :store
184 lcm_ = new_def;
185 lvm_ = def;
186 } else if (new_type->isa<Sigma>()) { // :alloc, :slot, ...
187 for (size_t i = 0, e = new_type->num_ops(); i != e; ++i)
188 if (new_type->op(i) == w.call<mem::M>(0)) {
189 lcm_ = w.extract(new_def, i); // new-world mem chain
190 lvm_ = old_world().extract(def, i); // old-world marker, compared against old ops
191 break;
192 }
193 }
194
195 return map(def, new_def);
196}
197
198} // namespace mim::plug::clos::phase
static auto isa(const Def *def)
Definition axm.h:107
Base class for all Defs.
Definition def.h:261
constexpr Node node() const noexcept
Definition def.h:286
const Def * refine(size_t i, const Def *new_op) const
Definition def.cpp:242
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:527
const Def * op(size_t i) const noexcept
Definition def.h:320
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:441
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
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
bool is_external() const noexcept
Definition def.h:500
const Def * rebuild(World &w, const Def *type, Defs ops) const
Def::rebuilds this Def while using new_op as substitute for its i'th Def::op.
Definition def.h:599
Dbg dbg() const
Definition def.h:556
constexpr size_t num_ops() const noexcept
Definition def.h:321
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
const Def * dom() const
Definition lam.h:131
const Pi * type() const
Definition lam.h:130
static const Lam * isa_basicblock(const Def *d)
Definition lam.h:142
static std::optional< T > isa(const Def *def)
Definition def.h:878
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: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 const Def * rewrite_mut(Def *)
Definition rewrite.cpp:77
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:45
virtual const Def * rewrite_imm(const Def *)
Definition rewrite.cpp:68
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:56
virtual const Def * lookup(const Def *old_def)
Lookup old_def by searching in reverse through the stack of maps.
Definition rewrite.h:55
A dependent tuple type.
Definition tuple.h:22
A variable introduced by a binder (mutable).
Definition def.h:756
Def * binder() const
The binder of this Var.
Definition def.h:766
const Def * bot(const Def *type)
Definition world.h:569
const Def * rewrite(const Def *def) 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 Sigma * isa_clos_type(const Def *def)
Definition clos.cpp:88
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
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
View< const Def * > Defs
Definition def.h:78
Vector< const Def * > DefVec
Definition def.h:79
auto lookup(const C &container, const K &key)
Yields pointer to element (or the element itself if it is already a pointer), if found and nullptr ot...
Definition util.h:99
@ Nat
Definition def.h:109
@ Univ
Definition def.h:109
@ Bot
Definition def.h:109
@ Axm
Definition def.h:109
@ Sigma
Definition def.h:109
@ Type
Definition def.h:109
@ Top
Definition def.h:109