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