MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_map_reduce.cpp
Go to the documentation of this file.
2
3#include <mim/axm.h>
4#include <mim/def.h>
5#include <mim/lam.h>
6
10#include <mim/plug/cps/cps.h>
11#include <mim/plug/mem/mem.h>
12
14
16
17namespace {
18
19/// Drops, from the (unfolded) index `idx`, the components of size-1 dimensions of `shape`
20/// (`buffer.Buf` normalizes size-1 axes away, mirroring the folding of array types).
21const Def* fold_index(const Def* shape, const Def* idx) {
22 auto& w = shape->world();
23 auto r = shape->num_projs();
24 DefVec out;
25 bool dropped = false;
26 for (size_t i = 0; i != r; ++i)
27 if (auto l = Lit::isa<u64>(shape->proj(r, i)); l && *l == 1)
28 dropped = true;
29 else
30 out.push_back(idx->proj(r, i));
31 // Without dropped axes the tuple below would just eta-reduce back to `idx` — but only after
32 // World::tuple's pack normalization has alpha-compared the projections, which walks `idx`'s whole
33 // (mem-threaded, Var-dependent) coordinate chain per elem pair — exponentially. Return `idx` directly.
34 if (!dropped) return idx;
35 return w.tuple(out);
36}
37
38/// Builds a counting `affine.For` loop body carrying `acc` (a `{mem, …}` tuple).
39std::pair<Lam*, const Def*> counting_for(const Def* bound, const Def* acc, const Def* exit, Sym name) {
40 auto& w = bound->world();
41 auto acc_ty = acc->type();
42 auto body = w.mut_con({/* iter */ w.type_i64(), /* acc */ acc_ty, /* return */ w.cn(acc_ty)})->set(name);
43 auto for_loop = w.call<affine::For>(body, exit, Defs{w.lit_i64(0), bound, w.lit_i64(1), acc});
44 return {body, for_loop};
45}
46
47/// Pointwise scaffold shared by `pad`/`concat`: a `[mem, ins] → [mem, Buf]` fun (spliced via
48/// `cps.cps2ds`) that allocates the output buffer, loops over `s_out` carrying `{mem, buf}`, and writes
49/// `compute`'s elem at the (identity) output coordinates.
50/// `compute(iters, ins, mem)` receives the raw i64 loop counters, the fun's inputs var, and the current
51/// mem; it returns `(mem', elem)`.
52template<class Compute>
53const Def* build_pointwise(World& w,
54 const Def* result_ty, // [mem.M 0, buffer.Buf (r, s_out, T)]
55 const Def* op_mem,
56 const Def* op_ins,
57 const Def* s_out,
58 u64 rn,
59 const std::string& name,
60 Compute&& compute) {
61 auto mem_ty = w.call<mem::M>(0);
62 auto fun = w.mut_fun(w.sigma({mem_ty, op_ins->type()}), result_ty)->set(name);
63 auto call = w.app(cps::op_cps2ds_dep(fun), w.tuple({op_mem, op_ins}));
64 auto [fun_mem, ins] = fun->var(0_n)->projs<2>();
65 auto cont = fun->var(1);
66
67 auto [obr, obs, obT] = Axm::isa<buffer::Buf>(result_ty->proj(1))->args<3>();
68 auto [a_mem, out_buf] = buffer::op_alloc(obr, obs, obT, fun_mem)->projs<2>();
69 const Def* acc = w.tuple({a_mem, out_buf});
70 auto current_mut = fun;
71
72 DefVec iters; // raw i64 loop counters
73 iters.reserve(rn);
74 for (u64 d = 0; d < rn; ++d) {
75 auto bound = w.call<core::bitcast>(w.type_i64(), s_out->proj(rn, d));
76 auto [body, for_call] = counting_for(bound, acc, cont, w.sym(name + "_" + std::to_string(d)));
77 auto [iter, new_acc, yield] = body->vars<3>();
78 cont = yield;
79 iters.push_back(iter);
80 acc = new_acc;
81 current_mut->set(true, for_call);
82 current_mut = body;
83 }
84 auto [loop_mem, loop_buf] = acc->projs<2>();
85
86 std::pair<const Def*, const Def*> el = compute(iters, ins, loop_mem);
87 auto [el_mem, elem] = el;
88
89 DefVec wcoords(rn);
90 for (u64 d = 0; d < rn; ++d)
91 wcoords[d] = w.call(core::conv::u, s_out->proj(rn, d), iters[d]);
92 auto [wr_mem, wr_buf]
93 = buffer::op_write(obr, obs, obT, el_mem, loop_buf, fold_index(s_out, w.tuple(wcoords)), elem)->projs<2>();
94 current_mut->app(true, cont, w.tuple({wr_mem, loop_buf}));
95 return call;
96}
97
98} // namespace
99
101 if (is_bootstrapping()) return RWPhase::rewrite_imm_App(app);
102 if (Axm::isa<btensor::map_reduce_post>(app)) return lower_map_reduce_post(app);
103 if (Axm::isa<btensor::broadcast>(app)) return lower_broadcast(app);
104 if (Axm::isa<btensor::pad>(app)) return lower_pad(app);
105 if (Axm::isa<btensor::concat>(app)) return lower_concat(app);
106 if (Axm::isa<buffer::lit>(app)) return lower_buffer_lit(app);
107 if (Axm::isa<btensor::gather>(app)) return lower_gather(app);
108 if (Axm::isa<btensor::scatter>(app)) return lower_scatter(app);
109 return RWPhase::rewrite_imm_App(app);
110}
111
112const Def* LowerMapReduce::lower_buffer_lit(const App* app) {
113 // `buffer.lit (r, s, T) (mem, val)` fills every elem with `val`. Emit a fill loop (via the same
114 // pointwise scaffold as pad/concat) so the store never materializes as one giant literal array. A
115 // non-literal rank has no static loop nest, so leave it for `buffer.lower_ptr`'s monolithic fallback.
116 auto [r, s, T] = app->callee()->as<App>()->args<3>();
117 auto s_out = rewrite(s);
118 auto rn = Lit::isa<u64>(rewrite(r));
119 if (!rn) return RWPhase::rewrite_imm_App(app);
120
121 auto& w = new_world();
122 auto [mem, val] = rewrite(app->arg())->projs<2>();
123 auto result_ty = rewrite(app->type()); // [mem.M 0, buffer.Buf (r, s, T)]
124 // `compute` ignores the loop counters and writes the (loop-invariant) scalar `ins` everywhere.
125 return build_pointwise(
126 w, result_ty, mem, val, s_out, *rn, "constant_fill",
127 [](const DefVec&, const Def* ins, const Def* m) -> std::pair<const Def*, const Def*> { return {m, ins}; });
128}
129
130const Def* LowerMapReduce::lower_map_reduce_post(const App* app) {
131 auto& w = new_world();
132 auto c = rewrite(app->callee())->as<App>();
133
134 auto [nis_nps, meta, shapes, in_tys, comb_init, acc_out, accs_all] = c->uncurry_args<7>();
135 auto [nis, nps] = nis_nps->projs<2>();
136 auto [To, Tp, Ro, Rn, TSched] = meta->projs<5>();
137 auto [So, Sr, sched] = shapes->projs<3>();
138 auto [Tis, Ris, Sis, Tps, Rps, Sps] = in_tys->projs<6>();
139 auto [comb, init, post] = comb_init->projs<3>();
140 auto [accs, post_accs] = accs_all->projs<2>();
141
142 // The final argument is `[mem, is, post_is]`; the result is `[mem, Buf]`.
143 auto [op_mem, op_is, op_post_is] = rewrite(app->arg())->projs<3>();
144 auto result_ty = rewrite(app->type()); // [mem.M 0, buffer.Buf (Ro, So, Tp)]
145
146 auto nis_l = Lit::isa<u64>(nis);
147 auto nps_l = Lit::isa<u64>(nps);
148 auto ro_l = Lit::isa<u64>(Ro), rn_l = Lit::isa<u64>(Rn);
149 if (!nis_l || !nps_l || !ro_l || !rn_l || *rn_l < *ro_l) {
150 log().w("rank counts (nis/nps/Ro/Rn) of {} are not known at lowering time", app);
151 return RWPhase::rewrite_imm_App(app);
152 }
153 auto nis_nat = *nis_l;
154 auto nps_nat = *nps_l;
155 auto ro = *ro_l, rr = *rn_l - *ro_l;
156 auto nloops = *rn_l;
157 auto n = w.lit_nat(nloops);
158
159 // Builds `affine.map @(m, n) @(sin, sout) f idxs mem`. The map is mem-threaded (its divisions consume mem),
160 // and this phase threads real memory, so the caller passes the current mem and receives `(mem', coords)`.
161 auto affine_map = [&](const Def* f, const Def* m, const Def* nn, const Def* sin, const Def* sout, const Def* idxs,
162 const Def* mem) {
163 auto a = w.app(w.annex<affine::map>(), w.tuple({m, nn}));
164 a = w.app(a, w.tuple({sin, sout}));
165 a = w.app(a, f);
166 a = w.app(a, idxs);
167 a = w.app(a, w.lit_nat_0());
168 return w.app(a, mem)->projs<2>();
169 };
170
171 auto mem_ty = w.call<mem::M>(0);
172
173 // `[mem, is, post_is] → [mem, Buf]`, spliced via cps.cps2ds and applied to the op's (mem, is, post_is).
174 auto fun = w.mut_fun(w.sigma({mem_ty, op_is->type(), op_post_is->type()}), result_ty)->set("mapRedAff");
175 auto call = w.app(cps::op_cps2ds_dep(fun), w.tuple({op_mem, op_is, op_post_is}));
176 auto [fun_mem, new_inputs, new_post_is] = fun->var(0_n)->projs<3>();
177 auto cont = fun->var(1);
178
179 // The op's SCHEDULE `sched` is a target-agnostic chooser over a loop-nest builder (canonically
180 // a `tensor.mk_sched` value, selected in the frontend). This is the tensor→btensor boundary,
181 // so bind it HERE to this target's algebra — `btensor.mr_nest` over the output buffer — then
182 // build only the decision-free pieces: the fold step `cell` (read one element per input, call
183 // the combiner) and the write-back `wb` (read the epilogue inputs, run `post`, store) — and
184 // APPLY the nest to them. Unrolling, interchange and the row accumulator are inside the
185 // builder: plain IR, not lowering behavior.
186 auto i32 = w.type_i32();
187
188 // Allocate the output buffer.
189 auto [obr, obs, obT] = Axm::isa<buffer::Buf>(result_ty->proj(1))->args<3>();
190 auto [a_mem, out_buf] = buffer::op_alloc(obr, obs, obT, fun_mem)->projs<2>();
191
192 auto nest_args = w.tuple({Ro, w.lit_nat(rr), Sr, To, result_ty->proj(1)});
193 auto nest = w.app(w.app(sched, w.app(w.annex<btensor::NestT>(), nest_args)),
194 w.app(w.annex<btensor::mr_nest>(), nest_args));
195
196 // The bound nest dictates the exact `cell`/`wb` signatures (its [init, cell, wb] domain) —
197 // building them from the VALUE's own type sidesteps any Arr/Sigma normalization asymmetry.
198 auto sched_dom = nest->type()->as<Pi>()->dom();
199
200 // A combiner/epilogue operand canonically has the axm's `Fn` shape `Cn [[args], Cn ret]`, but an earlier
201 // Scalarize may have flattened an escaped lam to `Cn [args…, Cn ret]` — build the argument to match the
202 // callee's actual domain either way.
203 auto apply_cps = [&](Lam* mut, const Def* f, DefVec parts, const Def* k) {
204 auto dom = f->type()->as<Pi>()->dom();
205 if (dom->num_projs() == parts.size() + 1) {
206 parts.emplace_back(k);
207 mut->app(true, f, w.tuple(parts));
208 } else {
209 mut->app(true, f, w.tuple({w.tuple(parts), k}));
210 }
211 };
212
213 // The nest value's loop-vector components may appear as one «r; I32» value or flattened into r
214 // separate I32 components (normalization decides) — index the domains verbatim either way.
215 auto load_ivs = [&](Lam* l, u64 ndom, u64 pos, u64 cnt) {
216 DefVec out(cnt);
217 if (ndom == pos + cnt + 1) // flattened: cnt I32 scalars, then the continuation
218 for (u64 d = 0; d < cnt; ++d)
219 out[d] = l->var(ndom, pos + d);
220 else
221 for (u64 d = 0; d < cnt; ++d)
222 out[d] = l->var(ndom, pos)->proj(cnt, d);
223 return out;
224 };
225
226 // cell: Cn [mem, To, «ro+rr; I32», Cn [mem, To]] — fold the elements at one loop vector.
227 auto cdom = sched_dom->proj(3, 1)->as<Pi>()->dom();
228 auto cn = cdom->num_projs();
229 auto cell = w.mut_con(cdom)->set("cell");
230 {
231 auto cm = cell->var(cn, 0);
232 auto cacc = cell->var(cn, 1);
233 auto ck = cell->var(cn, cn - 1);
234 auto civs = load_ivs(cell, cn, 2, nloops);
235 DefVec iters_v(nloops);
236 for (u64 d = 0; d < nloops; ++d)
237 iters_v[d] = w.call(core::conv::u, Sr->proj(nloops, d), civs[d]);
238 auto iters = w.tuple(iters_v);
239 auto cur = cm;
240 DefVec input_elems(nis_nat);
241 for (u64 i = 0; i < nis_nat; ++i) {
242 auto in_buf = new_inputs->proj(nis_nat, i);
243 auto [mc_mem, coords]
244 = affine_map(accs->proj(nis_nat, i), Ris->proj(nis_nat, i), n, Sr, Sis->proj(nis_nat, i), iters, cur);
245 cur = mc_mem;
246 auto [ir, is_, iT] = Axm::isa<buffer::Buf>(in_buf->type())->args<3>();
247 auto [rd_mem, rd_val]
248 = buffer::op_read(ir, is_, iT, cur, in_buf, fold_index(Sis->proj(nis_nat, i), coords))->projs<2>();
249 cur = rd_mem;
250 input_elems[i] = rd_val;
251 }
252 apply_cps(cell, comb, {cur, cacc, w.tuple(input_elems)}, ck);
253 }
254
255 // wb: Cn [mem, Buf, To, «ro+rr; I32», Cn [mem, Buf]] — epilogue + store for one folded cell,
256 // threading the output buffer as the nest's write-back target. It receives the full loop
257 // vector; only the leading `ro` output coordinates are read (the trailing reduction slots are
258 // exhausted loop values and are replaced by zeros for `acc_out`).
259 auto wdom = sched_dom->proj(3, 2)->as<Pi>()->dom();
260 auto wn = wdom->num_projs();
261 auto wb = w.mut_con(wdom)->set("wb");
262 {
263 auto wm = wb->var(wn, 0);
264 auto wu = wb->var(wn, 1);
265 auto wv = wb->var(wn, 2);
266 auto wk = wb->var(wn, wn - 1);
267 auto wovs = load_ivs(wb, wn, 3, nloops);
268 DefVec wb_iters(nloops);
269 for (u64 i = 0; i < ro; ++i)
270 wb_iters[i] = w.call(core::conv::u, Sr->proj(nloops, i), wovs[i]);
271 for (u64 j = 0; j < rr; ++j)
272 wb_iters[ro + j] = w.call(core::conv::u, Sr->proj(nloops, ro + j), w.lit(i32, 0));
273 auto [wc_mem, write_coords] = affine_map(acc_out, Ro, n, Sr, So, w.tuple(wb_iters), wm);
274
275 auto pcur = wc_mem;
276 DefVec post_elems(nps_nat);
277 for (u64 j = 0; j < nps_nat; ++j) {
278 auto sps_j = Sps->proj(nps_nat, j);
279 auto [pc_mem, pcoords]
280 = affine_map(post_accs->proj(nps_nat, j), Rps->proj(nps_nat, j), Ro, So, sps_j, write_coords, pcur);
281 pcur = pc_mem;
282 auto p_buf = new_post_is->proj(nps_nat, j);
283 auto [pr, ps_, pT] = Axm::isa<buffer::Buf>(p_buf->type())->args<3>();
284 auto [prd_mem, p_val] = buffer::op_read(pr, ps_, pT, pcur, p_buf, fold_index(sps_j, pcoords))->projs<2>();
285 pcur = prd_mem;
286 post_elems[j] = p_val;
287 }
288 auto after_post = mem::mut_con(Tp)->set("afterPost");
289 auto [post_mem, elem_post] = after_post->vars<2>();
290 auto stored = buffer::op_write(obr, obs, obT, post_mem, wu, fold_index(So, write_coords), elem_post);
291 after_post->app(true, wk, w.tuple({stored->proj(0), stored->proj(1)}));
292 apply_cps(wb, post, {pcur, wv, w.tuple(post_elems)}, after_post);
293 }
294
295 // Apply the nest; the output buffer is threaded through as the nest's write-back target and
296 // yielded straight to the op's continuation.
297 fun->app(true, w.app(nest, w.tuple({init, cell, wb})), w.tuple({a_mem, out_buf, cont}));
298
299 return call;
300}
301
302const Def* LowerMapReduce::lower_broadcast(const App* app) {
303 auto& w = new_world();
304 auto callee = app->callee()->as<App>(); // (broadcast {impl}) (s_in, s_out)
305 auto [s_in, s_out] = rewrite(callee->arg())->projs<2>();
306 auto [op_mem, input] = rewrite(app->arg())->projs<2>();
307 auto result_ty = rewrite(app->type()); // [mem.M 0, buffer.Buf (ro, so, T)]
308
309 auto r_nat = s_out->num_projs();
310
311 auto mem_ty = w.call<mem::M>(0);
312 auto fun = w.mut_fun(w.sigma({mem_ty, input->type()}), result_ty)->set("broadcast");
313 auto call = w.app(cps::op_cps2ds_dep(fun), w.tuple({op_mem, input}));
314 auto [fun_mem, in_buf] = fun->var(0_n)->projs<2>();
315 auto cont = fun->var(1);
316
317 auto [in_r, in_s, in_T] = Axm::isa<buffer::Buf>(in_buf->type())->args<3>();
318 auto [out_r, out_s, out_T] = Axm::isa<buffer::Buf>(result_ty->proj(1))->args<3>();
319
320 auto [a_mem, out_buf] = buffer::op_alloc(out_r, out_s, out_T, fun_mem)->projs<2>();
321 const Def* acc = w.tuple({a_mem, out_buf});
322 auto current_mut = fun;
323 DefVec out_iters;
324 out_iters.reserve(r_nat);
325 for (size_t i = 0; i < r_nat; ++i) {
326 auto dim = s_out->proj(r_nat, i);
327 auto bound = w.call<core::bitcast>(w.type_i64(), dim);
328 auto [body, for_call] = counting_for(bound, acc, cont, w.sym("bcast_" + std::to_string(i)));
329 auto [iter, new_acc, yield] = body->vars<3>();
330 cont = yield;
331 out_iters.push_back(w.call(core::conv::u, dim, iter));
332 acc = new_acc;
333 current_mut->set(true, for_call);
334 current_mut = body;
335 }
336 auto [loop_mem, loop_buf] = acc->projs<2>();
337
338 // Non-size-1 input dims mirror the matching output index; size-1 dims are dropped from each buffer index.
339 auto iters = w.tuple(out_iters);
340 auto [rd_mem, rd_val] = buffer::op_read(in_r, in_s, in_T, loop_mem, in_buf, fold_index(s_in, iters))->projs<2>();
341 auto [wr_mem, wr_buf]
342 = buffer::op_write(out_r, out_s, out_T, rd_mem, loop_buf, fold_index(s_out, iters), rd_val)->projs<2>();
343 current_mut->app(true, cont, w.tuple({wr_mem, loop_buf}));
344
345 return call;
346}
347
348const Def* LowerMapReduce::lower_pad(const App* app) {
349 auto& w = new_world();
350 auto c = rewrite(app->callee())->as<App>();
351
352 // callee: pad {T, r} [s_in] [mode, lo, hi] [s_out]. The shapes are the logical ones; buffer reads and
353 // writes fold size-1 axes (the `Buf` handles are normalized), while the loops cover all logical dims.
354 auto [Tr, s_in, params, s_out] = c->uncurry_args<4>();
355 auto [mode, lo, hi] = params->projs<3>();
356 auto [op_mem, input, value] = rewrite(app->arg())->projs<3>();
357 auto result_ty = rewrite(app->type()); // [mem.M 0, buffer.Buf (r, s_out, T)]
358
359 auto r_l = Lit::isa<u64>(Tr->proj(2, 1));
360 auto mode_l = Lit::isa<u64>(mode);
361 if (!r_l || !mode_l) {
362 log().w("rank/mode of {} is not known at lowering time", app);
363 return RWPhase::rewrite_imm_App(app);
364 }
365 auto rn = *r_l;
366 auto mode_nat = *mode_l;
367 auto i64 = w.type_i64();
368
369 // select(cond, t, f) == `(f, t)#cond` (cf. core.select); cond : Bool.
370 auto sel = [&](const Def* cond, const Def* t, const Def* f) { return w.extract(w.tuple({f, t}), cond); };
371
372 auto compute = [&](const DefVec& iters, const Def* ins, const Def* mem) -> std::pair<const Def*, const Def*> {
373 auto [in_buf, fill] = ins->projs<2>();
374 auto [ibr, ibs, ibT] = Axm::isa<buffer::Buf>(in_buf->type())->args<3>();
375 DefVec clamped(rn); // per-axis read index, kept in range, as `Idx (s_in#d)`
376 DefVec valid; // per-axis in-bounds flag (constant mode only)
377 for (u64 d = 0; d < rn; ++d) {
378 auto lo_d = w.call<core::bitcast>(i64, lo->proj(rn, d));
379 auto sin_d = w.call<core::bitcast>(i64, s_in->proj(rn, d));
380 auto in_d = w.call(core::wrap::sub, core::Mode::none, Defs{iters[d], lo_d}); // o#d − lo#d
381 const Def* idx_i64;
382 if (mode_nat == 0) { // constant: a single unsigned `<` covers both bounds (underflow wraps high)
383 auto v_d = w.call(core::icmp::ul, w.tuple({in_d, sin_d}));
384 valid.push_back(v_d);
385 idx_i64 = sel(v_d, in_d, w.lit_i64(0));
386 } else { // replicate: clamp the read to the nearest edge [0, s_in#d − 1]
387 auto sin_m1 = w.call(core::wrap::sub, core::Mode::none, Defs{sin_d, w.lit_i64(1)});
388 idx_i64 = w.call(core::extrema::smax,
389 w.tuple({w.lit_i64(0), w.call(core::extrema::smin, w.tuple({in_d, sin_m1}))}));
390 }
391 clamped[d] = w.call(core::conv::u, s_in->proj(rn, d), idx_i64);
392 }
393 auto [rd_mem, elem]
394 = buffer::op_read(ibr, ibs, ibT, mem, in_buf, fold_index(s_in, w.tuple(clamped)))->projs<2>();
395 if (mode_nat != 0) return {rd_mem, elem}; // replicate: always a (clamped) read
396 auto all_valid = valid.empty() ? w.lit_tt() : valid[0];
397 for (u64 d = 1; d < valid.size(); ++d)
398 all_valid = w.call(core::bit2::and_, w.lit_nat(2), w.tuple({all_valid, valid[d]}));
399 return {rd_mem, sel(all_valid, elem, fill)}; // constant: fill out-of-region cells with `value`
400 };
401
402 return build_pointwise(w, result_ty, op_mem, w.tuple({input, value}), s_out, rn, "pad", compute);
403}
404
405const Def* LowerMapReduce::lower_concat(const App* app) {
406 auto& w = new_world();
407 auto c = rewrite(app->callee())->as<App>();
408
409 // callee: concat {T, nis, r} [ax] {Sis} [s_out]. The shapes are the logical ones; buffer reads and
410 // writes fold size-1 axes (the `Buf` handles are normalized), while the loops cover all logical dims.
411 auto [TnisR, ax, Sis, s_out] = c->uncurry_args<4>();
412 auto [T, nis, r] = TnisR->projs<3>();
413 auto [op_mem, op_is] = rewrite(app->arg())->projs<2>();
414 auto result_ty = rewrite(app->type()); // [mem.M 0, buffer.Buf (r, s_out, T)]
415
416 auto nis_l = Lit::isa<u64>(nis);
417 auto r_l = Lit::isa<u64>(r);
418 auto ax_l = Lit::isa<u64>(ax);
419 if (!nis_l || !r_l || !ax_l) {
420 log().w("nis/rank/axis of {} are not known at lowering time", app);
421 return RWPhase::rewrite_imm_App(app);
422 }
423 auto nisn = *nis_l, rn = *r_l, axn = *ax_l;
424
425 // Prefix offsets along `ax`: off#i = Σ_{j<i} Sis#i#ax (literal extents required).
426 DefVec off(nisn);
427 fe::Vector<u64> ext(nisn);
428 u64 acc_off = 0;
429 for (u64 i = 0; i < nisn; ++i) {
430 off[i] = w.lit_i64(acc_off);
431 auto ei = Lit::isa<u64>(Sis->proj(nisn, i)->proj(rn, axn));
432 if (!ei) {
433 log().w("extent of input {} of {} along the concat axis is not known at lowering time", i, app);
434 return RWPhase::rewrite_imm_App(app);
435 }
436 ext[i] = *ei;
437 acc_off += *ei;
438 }
439
440 auto sel = [&](const Def* cond, const Def* t, const Def* f) { return w.extract(w.tuple({f, t}), cond); };
441
442 auto compute = [&](const DefVec& iters, const Def* ins, const Def* mem) -> std::pair<const Def*, const Def*> {
443 auto o_ax = iters[axn];
444 const Def* cur = mem;
445 // Read input `i` at `iters`, but with the `ax` coordinate shifted by off#i and clamped into input `i`.
446 auto read_i = [&](u64 i) -> const Def* {
447 auto in_buf = ins->proj(nisn, i);
448 auto [ibr, ibs, ibT] = Axm::isa<buffer::Buf>(in_buf->type())->args<3>();
449 auto Sis_i = Sis->proj(nisn, i);
450 auto e_i_m1 = w.lit_i64(ext[i] - 1);
451 auto loc = w.call(core::wrap::sub, core::Mode::none, Defs{o_ax, off[i]});
452 auto clamp = w.call(core::extrema::smax,
453 w.tuple({w.lit_i64(0), w.call(core::extrema::smin, w.tuple({loc, e_i_m1}))}));
454 DefVec coords(rn);
455 for (u64 d = 0; d < rn; ++d) {
456 auto idx_i64 = (d == axn) ? clamp : iters[d];
457 coords[d] = w.call(core::conv::u, Sis_i->proj(rn, d), idx_i64);
458 }
459 auto [rd_mem, rd_val]
460 = buffer::op_read(ibr, ibs, ibT, cur, in_buf, fold_index(Sis_i, w.tuple(coords)))->projs<2>();
461 cur = rd_mem;
462 return rd_val;
463 };
464 // Select chain: the highest `i` with off#i ≤ o_ax owns the cell (offsets increase, later wins).
465 auto result = read_i(0);
466 for (u64 i = 1; i < nisn; ++i) {
467 auto cond = w.call(core::icmp::uge, w.tuple({o_ax, off[i]}));
468 result = sel(cond, read_i(i), result);
469 }
470 return {cur, result};
471 };
472
473 return build_pointwise(w, result_ty, op_mem, op_is, s_out, rn, "concat", compute);
474}
475
476const Def* LowerMapReduce::lower_gather(const App* app) {
477 auto& w = new_world();
478 auto c = rewrite(app->callee())->as<App>();
479
480 auto [Tr, shapes, dim] = c->uncurry_args<3>();
481 auto [T, r] = Tr->projs<2>();
482 auto [s_src, s_idx] = shapes->projs<2>();
483 auto [op_mem, input, idx] = rewrite(app->arg())->projs<3>();
484 auto result_ty = rewrite(app->type());
485
486 auto r_l = Lit::isa<u64>(r);
487 auto dim_l = Lit::isa<u64>(dim);
488 if (!r_l || !dim_l) {
489 log().w("{} doesn't have lowering-time known rank/axis", app);
490 return RWPhase::rewrite_imm_App(app);
491 }
492 auto rn = *r_l, axis = *dim_l;
493
494 auto compute = [&](Defs iters, const Def* ins, const Def* mem) -> std::pair<const Def*, const Def*> {
495 auto [in_buf, index_buf] = ins->projs<2>();
496 auto [ibr, ibs, ibT] = Axm::isa<buffer::Buf>(in_buf->type())->args<3>();
497 auto [xbr, xbs, xbT] = Axm::isa<buffer::Buf>(index_buf->type())->args<3>();
498
499 DefVec index_coords(rn);
500 for (u64 d = 0; d < rn; ++d)
501 index_coords[d] = w.call(core::conv::u, s_idx->proj(rn, d), iters[d]);
502 auto [index_mem, selected]
503 = buffer::op_read(xbr, xbs, xbT, mem, index_buf, fold_index(s_idx, w.tuple(index_coords)))->projs<2>();
504 auto selected_i64 = w.call<core::bitcast>(w.type_i64(), selected);
505
506 DefVec source_coords(rn);
507 for (u64 d = 0; d < rn; ++d) {
508 auto coordinate = d == axis ? selected_i64 : iters[d];
509 source_coords[d] = w.call(core::conv::u, s_src->proj(rn, d), coordinate);
510 }
511 auto [read_mem, value]
512 = buffer::op_read(ibr, ibs, ibT, index_mem, in_buf, fold_index(s_src, w.tuple(source_coords)))->projs<2>();
513 return {read_mem, value};
514 };
515 return build_pointwise(w, result_ty, op_mem, w.tuple({input, idx}), s_idx, rn, "gather", compute);
516}
517
518const Def* LowerMapReduce::lower_scatter(const App* app) {
519 auto& w = new_world();
520 auto c = rewrite(app->callee())->as<App>();
521
522 auto [Tr, shapes, dim] = c->uncurry_args<3>();
523 auto [T, r] = Tr->projs<2>();
524 auto [s_src, s_idx, s_updates] = shapes->projs<3>();
525 auto [op_mem, input, idx, updates] = rewrite(app->arg())->projs<4>();
526 auto result_ty = rewrite(app->type());
527
528 auto r_l = Lit::isa<u64>(r);
529 auto dim_l = Lit::isa<u64>(dim);
530 if (!r_l || !dim_l) {
531 log().w("{} doesn't have lowering-time known rank/axis", app);
532 return RWPhase::rewrite_imm_App(app);
533 }
534 auto rn = *r_l, axis = *dim_l;
535
536 auto mem_ty = w.call<mem::M>(0);
537 auto fun = w.mut_fun(w.sigma({mem_ty, input->type(), idx->type(), updates->type()}), result_ty)->set("scatter");
538 auto call = w.app(cps::op_cps2ds_dep(fun), w.tuple({op_mem, input, idx, updates}));
539 auto [fun_mem, in_buf, index_buf, update_buf] = fun->var(0_n)->projs<4>();
540 auto cont = fun->var(1);
541
542 auto [obr, obs, obT] = Axm::isa<buffer::Buf>(result_ty->proj(1))->args<3>();
543 auto [a_mem, out_buf] = buffer::op_alloc(obr, obs, obT, fun_mem)->projs<2>();
544 auto copy_mem = buffer::op_copy(obr, obs, obT, a_mem, out_buf, in_buf);
545 const Def* acc = w.tuple({copy_mem, out_buf});
546 auto current = fun;
547 DefVec iters;
548 iters.reserve(rn);
549 for (u64 d = 0; d < rn; ++d) {
550 auto bound = w.call<core::bitcast>(w.type_i64(), s_idx->proj(rn, d));
551 auto [body, for_call] = counting_for(bound, acc, cont, w.sym("scatter_" + std::to_string(d)));
552 auto [iter, new_acc, yield] = body->vars<3>();
553 cont = yield;
554 acc = new_acc;
555 iters.push_back(iter);
556 current->set(true, for_call);
557 current = body;
558 }
559 auto [loop_mem, loop_buf] = acc->projs<2>();
560 auto [xbr, xbs, xbT] = Axm::isa<buffer::Buf>(index_buf->type())->args<3>();
561 auto [ubr, ubs, ubT] = Axm::isa<buffer::Buf>(update_buf->type())->args<3>();
562
563 DefVec index_coords(rn);
564 for (u64 d = 0; d < rn; ++d)
565 index_coords[d] = w.call(core::conv::u, s_idx->proj(rn, d), iters[d]);
566 auto folded_index = fold_index(s_idx, w.tuple(index_coords));
567 DefVec update_coords(rn);
568 for (u64 d = 0; d < rn; ++d)
569 update_coords[d] = w.call(core::conv::u, s_updates->proj(rn, d), iters[d]);
570 auto [index_mem, selected] = buffer::op_read(xbr, xbs, xbT, loop_mem, index_buf, folded_index)->projs<2>();
571 auto [update_mem, update]
572 = buffer::op_read(ubr, ubs, ubT, index_mem, update_buf, fold_index(s_updates, w.tuple(update_coords)))
573 ->projs<2>();
574 auto selected_i64 = w.call<core::bitcast>(w.type_i64(), selected);
575
576 DefVec destination_coords(rn);
577 for (u64 d = 0; d < rn; ++d) {
578 auto coordinate = d == axis ? selected_i64 : iters[d];
579 destination_coords[d] = w.call(core::conv::u, s_src->proj(rn, d), coordinate);
580 }
581 auto [write_mem, written]
582 = buffer::op_write(obr, obs, obT, update_mem, loop_buf, fold_index(s_src, w.tuple(destination_coords)), update)
583 ->projs<2>();
584 current->app(true, cont, w.tuple({write_mem, loop_buf}));
585 return call;
586}
587
588} // namespace mim::plug::btensor::phase
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
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
auto vars(F f) noexcept
Definition def.h:479
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:27
static std::optional< T > isa(const Def *def)
Definition def.h:937
const fe::Log & log() const
Definition phase.h:79
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
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
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
const Def * rewrite_imm_App(const App *) override
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_read(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *buf, const Def *idx)
buffer.read (r, s, T) (mem, buf, idx) ↦ [mem.M 0, T].
Definition buffer.h:22
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_copy(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *dst, const Def *src)
buffer.copy (r, s, T) (mem, dst, src) ↦ mem.M 0 (copies the whole buffer src into dst).
Definition buffer.h:35
@ none
Wrap around.
Definition core.h:16
const Def * op_cps2ds_dep(const Def *k)
Definition cps.h:16
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
fe::View< const Def * > Defs
Definition def.h:91
fe::Vector< const Def * > DefVec
Definition def.h:93
uint64_t u64
Definition types.h:27
@ Pi
Definition def.h:122
@ Lam
Definition def.h:122
@ App
Definition def.h:122