MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
add_mem.cpp
Go to the documentation of this file.
2
3#include <fe/worklist.h>
4
5#include "mim/plug/mem/mem.h"
6
7// TODO make parametric in address space
8
9namespace mim::plug::mem::phase {
10
12 // Collect the lams whose ABI is pinned: everything (transitively) reachable from an axm-app argument
13 // (combiners, affine index mappings, initial accumulators of `btensor.map_reduce_post`, …).
14 auto queue = fe::BFSWorklist<DefSet>();
15 auto pinned = fe::BFSWorklist<DefSet>();
16 for (auto mut : old_world().externals().muts())
17 queue.push(mut);
18
19 while (!queue.empty()) {
20 auto def = queue.pop();
21
22 // `mem.fresh`'s return continuation is *not* pinned: it receives the current memory (see
23 // rewrite_imm_App below), so its body must be mem-threaded like any other continuation.
24 if (auto app = def->isa<App>(); app && app->axm() && !Axm::isa<mem::fresh>(app))
25 for (auto arg : app->arg()->projs())
26 if (auto lam = arg->isa_mut<Lam>()) pinned.push(lam);
27
28 for (auto d : def->deps())
29 queue.push(d);
30 }
31
32 while (!pinned.empty()) {
33 auto def = pinned.pop();
34 if (auto lam = def->isa_mut<Lam>()) preserved_.emplace(lam);
35 for (auto d : def->deps())
36 pinned.push(d);
37 }
38
39 return false; // one prepass suffices
40}
41
42const Def* AddMem::rewrite(const Def* old_def) {
43 // Type rewriting is MODE-DEPENDENT: outside a pinned ABI every continuation pi gains a leading
44 // mem, inside one it must not - but the rewrite memo is shared. Whichever mode first touches a
45 // shared type (e.g. a plain 'Cn F32' return pi) would poison the other, hash-order-dependently.
46 // While preserving, rebuild immutable types fresh, neither reading nor storing the memo.
47 if (preserving_ && !is_bootstrapping()) {
48 if (auto pi = old_def->isa_imm<Pi>()) return rewrite_imm_Pi(pi);
49 if (auto sigma = old_def->isa_imm<Sigma>()) return rewrite_imm_Sigma(sigma);
50 if (auto arr = old_def->isa_imm<Arr>()) return rewrite_imm_Seq(arr);
51 }
52 if (curr_mem_ && !preserving_ && !is_bootstrapping()) {
53 // A tuple with a direct memory operand is context-dependent: the operand splices to the memory
54 // current *at this use*. Two ops with hash-consed identical argument tuples (e.g. two `(⊥, val)`
55 // buffer fills) must splice *different* memories - so never store or reuse such a tuple via the
56 // rewrite memo; rebuild it at every occurrence.
57 if (auto tuple = old_def->isa<Tuple>();
58 tuple && std::ranges::any_of(tuple->ops(), [](const Def* op) { return isa_mem(op); }))
60 }
61 auto new_def = Rewriter::rewrite(old_def);
62 // Rewrite every memory operand to the current memory - after threading the operand's producers, which
63 // advances curr_mem_ along the way. Placeholders (`⊥`/`⊤ : mem.M 0`) are thereby spliced into the chain.
64 if (curr_mem_ && !preserving_ && !is_bootstrapping() && !old_def->isa_mut() && isa_mem(old_def)) return curr_mem_;
65 return new_def;
66}
67
68const Def* AddMem::rewrite_imm_Pi(const Pi* pi) {
69 auto new_pi = Rewriter::rewrite_imm_Pi(pi)->as<Pi>();
70 if (is_bootstrapping() || preserving_) return new_pi;
71
72 // Only continuations are mem-extended; a pi that already threads memory is left alone.
73 if (Pi::isa_cn(pi) && !has_leading_mem(pi)) {
74 auto& w = new_world();
75 auto mem = w.call<mem::M>(0);
76 auto dom = new_pi->dom();
77
78 // A dependent domain refers to its own Var.
79 // So prepending a leading mem shifts every component's index by one.
80 // Rebuild the domain as a fresh mutable Sigma and remap the old domain-Var to the shifted components of the new
81 // one. Otherwise the dependent references dangle (see issue #177).
82 if (auto [sigma, old_var] = dom->isa_binder<Sigma>(); sigma) {
83 auto n = sigma->num_ops();
84 auto new_sigma = w.mut_sigma(sigma->type(), n + 1);
85 new_sigma->set(0, mem);
86 // Component `i` may only refer to earlier components, whose (index-shifted) new Vars are already
87 // set - so build the substitution per component; padding slots `≥ i` are never extracted.
88 for (size_t i = 0; i != n; ++i) {
89 auto shift = w.tuple(DefVec(n, [&](size_t j) { return j < i ? new_sigma->var(n + 1, j + 1) : mem; }));
90 auto rw = VarRewriter(old_var, shift);
91 new_sigma->set(i + 1, rw.rewrite(sigma->op(i)));
92 }
93 return w.cn(new_sigma);
94 }
95
96 auto new_dom = DefVec();
97 new_dom.emplace_back(mem);
98 for (size_t i = 0, e = new_pi->num_doms(); i != e; ++i)
99 new_dom.emplace_back(new_pi->dom(i));
100 return w.cn(new_dom);
101 }
102 return new_pi;
103}
104
106 if (is_bootstrapping() || preserving_) return Rewriter::rewrite_mut_Lam(old_lam);
107
108 // Pinned ABI (an axm-app argument and everything below it): rewrite verbatim - no memory threaded or added.
109 if (preserved_.contains(old_lam)) {
110 auto _ = fe::Restore(preserving_, true);
111 return Rewriter::rewrite_mut_Lam(old_lam);
112 }
113
114 auto new_lam = new_world().mut_lam(rewrite(old_lam->type())->as<Pi>())->set(old_lam->dbg_key());
115 map(old_lam, new_lam);
116
117 // Map the parameters, accounting for a possibly inserted leading mem var.
118 if (auto n = old_lam->num_vars(); n != 0) {
119 auto offset = new_lam->num_doms() - old_lam->num_doms(); // 1 iff we prepended a mem var
120 for (size_t i = 0; i != n; ++i)
121 map(old_lam->var(i), new_lam->var(i + offset)->set(old_lam->var(i)->dbg_key()));
122 // A use of the whole parameter tuple is reconstructed from the new (shifted) components.
123 if (n > 1)
124 map(old_lam->var(), new_world().tuple(DefVec(n, [&](size_t i) { return new_lam->var(i + offset); })));
125 }
126
127 if (!old_lam->is_set()) return new_lam;
128
129 // The body's current memory is this lam's (leading or grouped) mem parameter - or none for direct-style fns.
130 auto _ = fe::Restore(curr_mem_, mem::mem_var(new_lam));
131 new_lam->set(rewrite(old_lam->filter()), rewrite(old_lam->body()));
132 return new_lam;
133}
134
135const Def* AddMem::rewrite_imm_App(const App* app) {
136 if (is_bootstrapping() || preserving_ || !curr_mem_) return Rewriter::rewrite_imm_App(app);
137
138 auto& w = new_world();
139
140 // `mem.fresh (a, k)`: the request for a fresh memory resolves to the memory that is current right
141 // here - jump to `k` with it. (Like the rest of this phase, only address space 0 is threaded.)
142 if (Axm::isa<mem::fresh>(app)) {
143 auto [_, k] = app->args<2>();
144 return w.app(rewrite(k), curr_mem_);
145 }
146 // Rewrite the argument before the callee (as the base Rewriter does). This threads the current memory
147 // through the argument's memory effects first; and because operands are rewritten before their users, a
148 // shared memory operation is anchored in the scope that consumes its result mem (its own scope) rather
149 // than one that merely reuses its non-mem result. curr_mem_ then holds the memory *after* the argument.
150 auto new_arg = rewrite(app->arg());
151 auto mem = curr_mem_;
152 auto new_callee = rewrite(app->callee());
153
154 auto old_pi = app->callee()->type()->isa<Pi>();
155 auto new_pi = new_callee->type()->isa<Pi>();
156 if (old_pi && new_pi && new_pi->num_doms() == old_pi->num_doms() + 1) {
157 // The callee gained a leading mem parameter: splice the current memory in front of the arguments.
158 auto n = old_pi->num_doms();
159 auto args = DefVec(n + 1);
160 args[0] = mem;
161 for (size_t i = 0; i != n; ++i)
162 args[i + 1] = new_arg->proj(n, i);
163 new_arg = w.tuple(args);
164 }
165
166 auto new_app = w.app(new_callee, new_arg);
167 advance_mem(new_app);
168 return new_app;
169}
170
172 if (is_bootstrapping() || preserving_ || !curr_mem_) return Rewriter::rewrite_imm_Tuple(tuple);
173
174 // The current memory must be threaded through the operands in the right order, because a memory operand
175 // (which resolves to the *current* memory) is positioned freely relative to the operands that establish
176 // the real ordering. Rewrite in three groups:
177 // 1. plain values first - e.g. the `buf` of a buffer op's `(⊥ : mem.M 0, buf)` argument, whose memory
178 // effects must precede the memory operand;
179 // 2. memory operands next - now resolving to the up-to-date current memory;
180 // 3. continuation values last - their bodies run later, so a shared memory operation they capture must
181 // already have been anchored (threaded) in this scope by the memory operands above.
182 auto rank = [](const Def* op) { return isa_mem(op) ? 1 : (op->type() && Pi::isa_cn(op->type()) ? 2 : 0); };
183
184 auto& w = new_world();
185 auto n = tuple->num_ops();
186 auto new_ops = DefVec(n);
187 for (int r = 0; r != 3; ++r)
188 for (size_t i = 0; i != n; ++i)
189 if (rank(tuple->op(i)) == r) new_ops[i] = rewrite(tuple->op(i));
190 return w.tuple(rewrite(tuple->type()), new_ops);
191}
192
193void AddMem::advance_mem(const Def* def) {
194 if (auto m = mem_def(def)) curr_mem_ = m;
195}
196
197} // namespace mim::plug::mem::phase
const Axm * axm() const
Definition lam.h:290
const Def * callee() const
Definition lam.h:275
const Def * arg() const
Definition lam.h:284
A (possibly paramterized) Array.
Definition tuple.h:110
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:273
bool is_set() const
Definition def.h:370
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:196
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
nat_t num_vars() noexcept
Definition def.h:479
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1111
const T * isa_imm() const
Definition def.h:574
A function.
Definition lam.h:113
const Def * filter() const
Definition lam.h:125
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:27
const Pi * type() const
Definition lam.h:133
const Def * body() const
Definition lam.h:126
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
static const Pi * isa_cn(const Def *d)
Definition lam.h:46
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
Definition phase.h:403
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_imm_Seq(const Seq *seq)
Definition rewrite.cpp:232
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:47
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
A dependent tuple type.
Definition tuple.h:23
Data constructor for a Sigma.
Definition tuple.h:61
VarRewriter(World &world)
Definition rewrite.h:118
Lam * mut_lam(const Pi *pi)
Definition world.h:402
bool analyze() final
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
Definition add_mem.cpp:11
const Def * rewrite_imm_Tuple(const Tuple *) override
Definition add_mem.cpp:171
const Def * rewrite_imm_Pi(const Pi *) override
Definition add_mem.cpp:68
const Def * rewrite(const Def *) override
Definition add_mem.cpp:42
const Def * rewrite_mut_Lam(Lam *) override
Definition add_mem.cpp:105
const Def * rewrite_imm_App(const App *) override
Definition add_mem.cpp:135
The mem Plugin
Definition mem.h:11
bool has_leading_mem(const Pi *pi)
Does pi already thread memory - a leading mem.M, either directly or grouped as the first component of...
Definition mem.h:33
const Def * mem_var(Lam *lam)
Returns the memory argument of a function if it has one.
Definition mem.h:55
const Def * mem_def(const Def *def)
Returns the (first) element of type mem.M a from the given tuple.
Definition mem.h:42
const App * isa_mem(const Def *def)
If def is a mem.M-typed value, yields its memory type mem.M a; otherwise nullptr.
Definition mem.h:26
The tuple Plugin
fe::Vector< const Def * > DefVec
Definition def.h:93