MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_matrix_mediumlevel.cpp
Go to the documentation of this file.
2
3#include <mim/lam.h>
4
8#include "mim/plug/cps/cps.h"
10#include "mim/plug/mem/mem.h"
11
12using namespace std::string_literals;
13
15
16namespace {
17
18/// Builds a counting `affine.For` loop body carrying `acc` (a `{mem, …}` tuple), using i32 iterators.
19std::pair<Lam*, const Def*> counting_for(const Def* bound, DefVec acc, const Def* exit, Sym name) {
20 auto& w = bound->world();
21 auto acc_ty = w.tuple(acc)->type();
22 auto body = w.mut_con({/* iter */ w.type_i32(), /* acc */ acc_ty, /* return */ w.cn(acc_ty)})->set(name);
23 auto for_loop = w.call<affine::For>(body, exit, Defs{w.lit_i32(0), bound, w.lit_i32(1), w.tuple(acc)});
24 return {body, for_loop};
25}
26
27} // namespace
28
30 if (is_bootstrapping()) return RWPhase::rewrite_imm_App(app);
31
32 auto map_reduce_ax = Axm::isa<matrix::map_reduce>(app);
33 if (!map_reduce_ax) return RWPhase::rewrite_imm_App(app);
34
35 // Meta arguments: n = out-count, S = out-dim, T = out-type, m = in-count, NI = in-dim-count, TI = types,
36 // SI = dimensions.
37 // Arguments: mem, zero (accumulator init), comb (combination function), inputs.
38 auto [mem, zero, comb, inputs] = map_reduce_ax->args<4>();
39 auto [n, S, T, m, NI, TI, SI] = map_reduce_ax->callee()->as<App>()->args<7>();
40
41 // Our goal is to generate a call to a function that performs:
42 // ```
43 // matrix = new matrix (n, S, T)
44 // for out_idx { // n for loops
45 // acc = zero
46 // for in_idx { // remaining loops
47 // inps = read from matrices // m-tuple
48 // acc = comb(mem, acc, inps)
49 // }
50 // write acc to output matrix
51 // }
52 // return matrix
53 // ```
54
55 absl::flat_hash_map<u64, const Def*> dims; // idx ↦ nat (size bound = dimension)
56 absl::flat_hash_map<u64, const Def*> iterator; // idx ↦ %Idx (S/NI#i)
57 Vector<u64> out_indices; // output indices 0..n-1
58 Vector<u64> in_indices; // input indices ≥ n
59 Vector<DefVec> input_dims; // i<m ↦ j<NI#i ↦ nat (dimension SI#i#j)
60 Vector<u64> n_input; // i<m ↦ nat (number of dimensions of SI#i)
61
62 auto n_lit = n->isa<Lit>();
63 auto m_lit = m->isa<Lit>();
64 if (!n_lit || !m_lit) return RWPhase::rewrite_imm_App(app);
65
66 auto n_nat = n_lit->get<u64>(); // number of output dimensions (in S)
67 auto m_nat = m_lit->get<u64>(); // number of input matrices
68
69 // Collect output dimensions.
70 for (u64 i = 0; i < n_nat; ++i)
71 dims[i] = S->proj(n_nat, i);
72
73 // Collect the other (input) dimensions.
74 for (u64 i = 0; i < m_nat; ++i) {
75 auto ni_lit = Lit::isa(NI->proj(m_nat, i));
76 if (!ni_lit) return RWPhase::rewrite_imm_App(app);
77 u64 ni_nat = *ni_lit;
78 auto SI_i = SI->proj(m_nat, i);
79 input_dims.emplace_back(DefVec(ni_nat, [&](u64 j) { return SI_i->proj(ni_nat, j); }));
80 n_input.push_back(ni_nat);
81 }
82
83 // Extract the bounds for each (in/out) index.
84 for (u64 i = 0; i < m_nat; ++i) {
85 auto [indices, mat] = inputs->proj(m_nat, i)->projs<2>();
86 for (u64 j = 0; j < n_input[i]; ++j) {
87 auto idx_lit = Lit::isa(indices->proj(n_input[i], j));
88 if (!idx_lit) return RWPhase::rewrite_imm_App(app);
89 u64 idx_nat = *idx_lit;
90 auto dim = input_dims[i][j];
91 if (!dims.contains(idx_nat)) {
92 dims[idx_nat] = dim;
93 } else if (auto dim_lit = dim->isa<Lit>()) { // override with more precise information
94 if (auto prev_lit = dims[idx_nat]->isa<Lit>())
95 assert(dim_lit->get<u64>() == prev_lit->get<u64>() && "dimensions must be equal");
96 else
97 dims[idx_nat] = dim;
98 }
99 }
100 }
101
102 for (auto [idx, dim] : dims)
103 (idx < n_nat ? out_indices : in_indices).push_back(idx);
104 // Sort the indices to make the checks below easier.
105 std::sort(out_indices.begin(), out_indices.end());
106 std::sort(in_indices.begin(), in_indices.end());
107
108 // The analysis above inspected old-world defs; everything taken along into the replacement must be rewritten
109 // into the new world first.
110 auto& w = new_world();
111 mem = rewrite(mem);
112 zero = rewrite(zero);
113 comb = rewrite(comb);
114 n = rewrite(n);
115 S = rewrite(S);
116 T = rewrite(T);
117 for (auto& [_, dim] : dims)
118 dim = rewrite(dim);
119
120 // Create a function `%mem.M 0 → [%mem.M 0, %matrix.Mat (n, S, T)]` to replace the axm call.
121 auto fun = w.mut_fun(w.call<mem::M>(0), rewrite(map_reduce_ax->type()))->set("mapRed");
122 auto call = w.app(cps::op_cps2ds_dep(fun), mem);
123
124 // Flowchart:
125 // ```
126 // init
127 // forOut1 with yieldOut1 => exitOut1 = return_cont
128 // forOut2 with yieldOut2 => exitOut2 = yieldOut1
129 // ... accumulator init
130 // forIn1 with yieldIn1 => exitIn1 = writeCont
131 // forIn2 with yieldIn2 => exitIn2 = yieldIn1
132 // ... read matrices
133 // fun => exitFun = yieldInM
134 // (return path) ... write -> yieldOutN -> ...
135 // ```
136
137 // First create the output matrix.
138 auto [current_mem, init_mat] = buffer::op_alloc(n, S, T, mem)->projs<2>();
139
140 // The continuation to continue on -- return after all output loops.
141 auto cont = fun->var(1);
142 auto current_mut = fun;
143
144 // Each outer loop carries the memory and matrix as accumulator (in an inner monad).
145 DefVec acc = {current_mem, init_mat};
146 for (auto idx : out_indices) {
147 auto dim_nat_def = dims[idx];
148 auto dim = w.call<core::bitcast>(w.type_i32(), dim_nat_def);
149
150 auto [body, for_call] = counting_for(dim, acc, cont, w.sym("forIn_"s + std::to_string(idx)));
151 auto [iter, new_acc, yield] = body->vars<3>();
152 auto [new_mem, new_acc_val] = new_acc->projs<2>();
153 cont = yield;
154 iterator[idx] = w.call<core::bitcast>(w.type_idx(dim_nat_def), iter);
155 acc = {new_mem, new_acc_val};
156 current_mut->set(true, for_call);
157 current_mut = body;
158 }
159
160 // Now the inner loops for the inputs, carrying the elem accumulator and memory (in an inner monad).
161 auto elem_acc = zero->set("acc");
162 current_mem = acc[0];
163 auto wb_matrix = acc[1];
164 assert(wb_matrix);
165
166 // Write the elem back to the matrix; this is the return after all inner loops.
167 auto write_back = mem::mut_con(T)->set("matrixWriteBack");
168 auto [wb_mem, elem_final] = write_back->vars<2>();
169
170 auto output_it_tuple = w.tuple(DefVec((size_t)n_nat, [&](u64 i) {
171 auto idx = out_indices[i];
172 if (idx != i) ELOG("output indices must be consecutive 0..n-1 but {} != {}", idx, i);
173 assert(idx == i && "output indices must be consecutive 0..n-1");
174 return iterator[idx];
175 }));
176 auto [wb_mem2, written_matrix]
177 = buffer::op_write(n, S, T, wb_mem, wb_matrix, output_it_tuple, elem_final)->projs<2>();
178 write_back->app(true, cont, {wb_mem2, written_matrix});
179
180 // From here on the continuations take the elem and memory.
181 acc = {current_mem, elem_acc};
182 cont = write_back;
183 for (auto idx : in_indices) {
184 auto dim_nat_def = dims[idx];
185 auto dim = w.call<core::bitcast>(w.type_i32(), dim_nat_def);
186
187 auto [body, for_call] = counting_for(dim, acc, cont, w.sym("forIn_"s + std::to_string(idx)));
188 auto [iter, new_acc, yield] = body->vars<3>();
189 auto [new_mem, new_acc_val] = new_acc->projs<2>();
190 cont = yield;
191 iterator[idx] = w.call<core::bitcast>(w.type_idx(dim_nat_def), iter);
192 acc = {new_mem, new_acc_val};
193 current_mut->set(true, for_call);
194 current_mut = body;
195 }
196 current_mem = acc[0];
197 elem_acc = acc[1];
198
199 // Read one elem from each input matrix.
200 DefVec input_elems((size_t)m_nat);
201 for (u64 i = 0; i < m_nat; ++i) {
202 auto [input_idx_tup, input_matrix] = inputs->proj(m_nat, i)->projs<2>();
203 auto indices = input_idx_tup->projs(n_input[i]);
204 auto input_it_tuple
205 = w.tuple(DefVec(n_input[i], [&](u64 j) { return iterator[indices[j]->as<Lit>()->get<u64>()]; }));
206
207 auto [new_mem, elem_i] = op_read(current_mem, rewrite(input_matrix), input_it_tuple)->projs<2>();
208 current_mem = new_mem;
209 input_elems[i] = elem_i;
210 }
211
212 current_mut->app(true, comb, {w.tuple({current_mem, elem_acc, w.tuple(input_elems)}), cont});
213
214 return call;
215}
216
217} // namespace mim::plug::matrix::phase
static auto isa(const Def *def)
Definition axm.h:107
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
auto vars(F f) noexcept
Definition def.h:441
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:29
static std::optional< T > isa(const Def *def)
Definition def.h:878
T get() const
Definition def.h:865
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
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:56
This is a thin wrapper for absl::InlinedVector<T, N, A> which is a drop-in replacement for std::vecto...
Definition vector.h:18
#define ELOG(...)
Definition log.h:88
const Def * op_write(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *buf, const Def *idx, const Def *val)
buffer.write (r, s, T) (mem, buf, idx, val) ↦ [mem.M 0, buffer.Buf (r, s, T)].
Definition buffer.h:29
const Def * op_alloc(const Def *r, const Def *s, const Def *T, const Def *mem)
buffer.alloc (r, s, T) mem ↦ [mem.M 0, buffer.Buf (r, s, T)].
Definition buffer.h:16
const Def * op_cps2ds_dep(const Def *k)
Definition cps.h:16
const Def * op_read(const Def *mem, const Def *matrix, const Def *idx)
Reads element idx from a matrix (represented by a buffer.Buf), threading mem.
Definition matrix.h:11
The mem Plugin
Definition mem.h:11
Lam * mut_con(World &w, nat_t a=0)
Yields con[mem.M 0].
Definition mem.h:16
View< const Def * > Defs
Definition def.h:78
Vector< const Def * > DefVec
Definition def.h:79
constexpr decltype(auto) get(Span< T, N > span) noexcept
Definition span.h:119
uint64_t u64
Definition types.h:27