MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_map_reduce.cpp
Go to the documentation of this file.
2
3#include <optional>
4
5#include <mim/def.h>
6#include <mim/lam.h>
7
8#include <mim/util/types.h>
9
11#include <mim/plug/core/core.h>
12#include <mim/plug/cps/cps.h>
13#include <mim/plug/mem/mem.h>
14
17
19
20const Def* LowerMapReduce::rec_broadcast(const Def* s_in, const Def* s_out, const Def* input, u64 r, u64 i) {
21 auto& w = new_world();
22 // Base case: all dimensions have been processed; `input` is the final scalar.
23 if (i == r) return input;
24
25 auto s_in_ri = s_in->proj(r, i), s_out_ri = s_out->proj(r, i);
26 log().d("broadcast dimension {} of {}: {} → {}, input = {}: {}", i, r, s_in_ri, s_out_ri, input, input->type());
27
28 if (s_in_ri == s_out_ri) {
29 if (auto s_in_lit = Lit::isa<u64>(s_in_ri)) {
30 DefVec inputs(*s_in_lit,
31 [&](size_t j) { return rec_broadcast(s_in, s_out, input->proj(*s_in_lit, j), r, i + 1); });
32 return w.tuple(inputs);
33 } else {
34 // TODO: we could probably support non-literal sizes as well, but we would need to generate loops to copy
35 // the data instead of just packing it.
36 log().w("dimension {} has equal but non-literal extent: {}", i, s_in_ri);
37 return nullptr;
38 }
39 }
40
41 if (auto s_in_lit = Lit::isa<u64>(s_in_ri); s_in_lit && *s_in_lit == 1) {
42 log().d("dimension {}: packing the size-1 input to {}", i, s_out_ri);
43 return w.pack(s_out_ri, rec_broadcast(s_in, s_out, input, r, i + 1));
44 }
45
46 log().w("cannot broadcast dimension {}: {} → {}", i, s_in_ri, s_out_ri);
47 return nullptr;
48}
49
50const Def* LowerMapReduce::lower_broadcast(const App* app) {
51 auto& w = new_world();
52 auto c = rewrite(app->callee());
53 auto arg = rewrite(app->arg());
54
55 auto [s_in, s_out, input] = arg->projs<3>();
56 auto callee = c->as<App>();
57 auto [T, r] = callee->args<2>();
58 log().d("lower broadcast: input = {}: {}, T = {}, r = {}, s_in = {}, s_out = {}", input, input->type(), T, r, s_in,
59 s_out);
60
61 auto r_nat = Lit::isa<u64>(r);
62 if (!r_nat) {
63 log().w("rank {} of {} is not known at lowering time", r, app);
64 return nullptr;
65 }
66 // r_nat will never be 0, as we would have normalized this case away already
67 if (s_in == s_out) return input;
68
69 if (*r_nat == 1) {
70 if (auto s_in_lit = Lit::isa<u64>(s_in)) {
71 assert(*s_in_lit == 1 && "input dimensions must be 1 or equal to the output dimension");
72 return w.pack(s_out, input);
73 }
74 }
75
76 auto result = rec_broadcast(s_in, s_out, input, *r_nat, 0);
77 log().d("broadcast result: {}", result);
78 return result;
79}
80
81namespace {
82
83std::pair<Lam*, const Def*> counting_for(const Def* bound, const Def* acc, const Def* exit, Sym name) {
84 auto& w = bound->world();
85 auto acc_ty = acc->type();
86 auto body = w.mut_con({/* iter */ w.type_i64(), /* acc */ acc_ty, /* return */ w.cn(acc_ty)})->set(name);
87 auto for_loop = w.call<affine::For>(body, exit, Defs{w.lit_i64(0), bound, w.lit_i64(1), acc});
88 return {body, for_loop};
89}
90
91/// Nests one counting loop per bound of @p dims inside @p cur, threading @p cur, @p exit and @p acc down to
92/// the innermost body; @returns the raw i64 loop counters.
93DefVec build_loops(World& w, Lam*& cur, const Def*& exit, const Def*& acc, Defs dims, std::string_view name) {
94 auto iters = DefVec();
95 iters.reserve(dims.size());
96 for (size_t i = 0, e = dims.size(); i != e; ++i) {
97 auto bound = w.call<core::bitcast>(w.type_i64(), dims[i]);
98 auto [body, for_call] = counting_for(bound, acc, exit, w.sym(std::format("{}_{}", name, i)));
99 auto [iter, new_acc, yield] = body->vars<3>();
100 exit = yield;
101 acc = new_acc;
102 iters.emplace_back(iter);
103 cur->set(true, for_call);
104 cur = body;
105 }
106 return iters;
107}
108
109const Def* elem_type(const Def* type, u64 r) {
110 for (u64 i = 0; i != r; ++i)
111 if (auto seq = type->isa<Seq>())
112 type = seq->body();
113 else
114 break;
115 return type;
116}
117
118const Def* nested_extract(World& w, const Def* matrix, const Def* coords, const Def* shape, u64 r) {
119 return op_get(elem_type(matrix->type(), r), w.lit_nat(r), shape, matrix, coords);
120}
121
122const Def* nested_insert(World& w, const Def* matrix, const Def* coords, const Def* shape, u64 r, const Def* elem) {
123 return op_set(elem_type(matrix->type(), r), w.lit_nat(r), shape, matrix, coords, elem);
124}
125
126/// The literal values of @p def's @p n projections, or nothing if one of them is not a literal.
127std::optional<fe::Vector<u64>> lit_projs(const Def* def, u64 n) {
128 auto res = fe::Vector<u64>(n);
129 for (u64 i = 0; i != n; ++i)
130 if (auto l = Lit::isa<u64>(def->proj(n, i)))
131 res[i] = *l;
132 else
133 return {};
134 return res;
135}
136
137/// `select(cond, t, f)` as `(f, t)#cond` (cf. core.select); `cond: Bool`.
138const Def* select(World& w, const Def* cond, const Def* t, const Def* f) { return w.extract(w.tuple({f, t}), cond); }
139
140/// Clamps the i64 @p x into `[0, bound − 1]`.
141const Def* clamp(World& w, const Def* x, const Def* bound) {
142 auto hi = w.call(core::wrap::sub, core::Mode::none, Defs{bound, w.lit_i64(1)});
143 return w.call(core::extrema::smax, w.tuple({w.lit_i64(0), w.call(core::extrema::smin, w.tuple({x, hi}))}));
144}
145
146} // namespace
147
148const Def* LowerMapReduce::lower_map_reduce(const App* app) {
149 // meta arguments:
150 // * nis = in-count, nps = epilogue-input count (nat)
151 // * To = accumulator type, Tp = out-element type (post: Fn [To, «nps; Tps»] → Tp), Ro = #output loops =
152 // result rank, Rn = #loops in total
153 // * So = result shape (Ro*nat)
154 // * Sr = the full loop bounds Rn*nat: the leading Ro are the output-loop bounds, the trailing Rn - Ro the
155 // reductions
156 // * Tis/Ris/Sis, Tps/Rps/Sps = (epilogue) input types/ranks/shapes
157 // arguments:
158 // * f = combination function (CPS), init = accumulator init, post = per-output-cell epilogue (CPS),
159 // applied to the folded accumulator and the epilogue elements right before the write-back
160 // * acc_out = affine map from the Rn loop vector to the Ro write coordinates in the result «So» (the reduction
161 // part is not in scope at write-back, so acc_out must depend only on the leading Ro output indices)
162 // * accs = per-input affine map from the Rn loop vector to the input's read coordinates
163 // * post_accs = per-epilogue-input affine map from the Ro output-cell (write) coordinates to its read coordinates
164 // * is, post_is = input tensors
165 auto& w = new_world();
166 auto c = rewrite(app->callee())->as<App>();
167 auto inputs = rewrite(app->arg());
168 auto type = rewrite(app->type());
169
170 auto [nis_nps, meta, shapes, in_tys, comb_init, acc_out, accs_all] = c->uncurry_args<7>();
171 auto [nis, nps] = nis_nps->projs<2>();
172 auto [To, Tp, Ro, Rn, TSched] = meta->projs<5>();
173 auto [So, Sr, sched] = shapes->projs<3>();
174 auto [Tis, Ris, Sis, Tps, Rps, Sps] = in_tys->projs<6>();
175 auto [comb, init, post] = comb_init->projs<3>();
176 auto [accs, post_accs] = accs_all->projs<2>();
177
178 auto nis_l = Lit::isa<u64>(nis);
179 auto nps_l = Lit::isa<u64>(nps);
180 auto ro_l = Lit::isa<u64>(Ro), rn_l = Lit::isa<u64>(Rn);
181 if (!nis_l || !nps_l || !ro_l || !rn_l || *rn_l < *ro_l) {
182 log().w("rank counts (nis/nps/Ro/Rn) of {} are not known at lowering time", app);
183 return nullptr;
184 }
185 auto nis_nat = *nis_l;
186 auto nps_nat = *nps_l;
187 auto ro = *ro_l, rr = *rn_l - *ro_l;
188 auto nloops = *rn_l; // length of the full loop vector (= length of Sr)
189 auto n = w.lit_nat(nloops); // passed as the affine maps' domain length
190
191 // ranks of each input must be literal so that we know how many `extract`s to emit
192 auto ris_nat = lit_projs(Ris, nis_nat);
193 auto rps_nat = lit_projs(Rps, nps_nat);
194 if (!ris_nat || !rps_nat) {
195 log().w("the input ranks of {} are not known at lowering time", app);
196 return nullptr;
197 }
198
199 // Builds `affine.map @(m, n) @(sin, sout) f idxs mem` and returns the result coordinates (dropping the returned
200 // mem). The emitted `affine.map` is lowered to core arithmetic by the subsequent affine.lower_index. We
201 // invent a fresh `⊥ : mem.M 0` for the mem operand here; real mem threading is wired up later by `add_mem`.
202 auto mem0 = w.app(w.annex<mem::M>(), w.lit_nat(0));
203 auto affine_map = [&](const Def* f, const Def* m, const Def* n, const Def* sin, const Def* sout, const Def* idxs) {
204 auto a = w.app(w.annex<affine::map>(), w.tuple({m, n}));
205 a = w.app(a, w.tuple({sin, sout}));
206 a = w.app(a, f);
207 a = w.app(a, idxs);
208 a = w.app(a, w.lit_nat_0());
209 return w.app(a, w.bot(mem0))->proj(2, 1); // drop the returned mem at proj 0
210 };
211
212 try {
213 auto fun = w.mut_fun(inputs->type(), type)->set("mapRed");
214 auto ds_fun = cps::op_cps2ds_dep(fun)->set("dsFun");
215 auto call = w.app(ds_fun, inputs)->set("call");
216
217 auto [new_inputs, cont] = fun->vars<2>();
218 auto [new_is, new_post_is] = new_inputs->set("is")->projs<2>();
219 auto sr = Sr->projs(nloops);
220
221 // Outer (parallel) loops over the leading Ro bounds of `Sr`, collecting the output iteration indices.
222 const Def* acc = w.bot(cont->type()->as<Pi>()->dom());
223 auto current_mut = fun;
224 auto raw_out = build_loops(w, current_mut, cont, acc, Defs(sr).subspan(0, ro), "forOut");
225 DefVec out_iters(ro, [&](size_t i) { return w.call(core::conv::u, sr[i], raw_out[i]); });
226 auto wb_matrix = acc;
227
228 // Write-back: run the `post` epilogue on the accumulated element, then narrow the result into the
229 // output at the affine write coordinates `acc_out`.
230 // acc_out takes the full (Ro+Rr) loop vector, but the reduction loops have already been folded away here, so we
231 // pass 0 for those slots; acc_out must depend only on the leading Ro output indices.
232 auto write_back = w.mut_con(To)->set("writeBack");
233 auto element_final = write_back->var();
234 DefVec wb_iters = out_iters;
235 for (u64 j = 0; j < rr; ++j)
236 wb_iters.emplace_back(w.call(core::conv::u, sr[ro + j], w.lit_i64(0)));
237 auto write_coords = affine_map(acc_out, Ro, n, Sr, So, w.tuple(wb_iters)); // «Ro; Idx (So#k)»
238
239 // Read one element from each epilogue input at its post_accs-mapped output-cell coordinates.
240 DefVec post_elements(nps_nat, [&](size_t j) {
241 auto sps_j = Sps->proj(nps_nat, j);
242 auto coords = affine_map(post_accs->proj(nps_nat, j), Rps->proj(nps_nat, j), Ro, So, sps_j, write_coords);
243 return nested_extract(w, new_post_is->proj(nps_nat, j), coords, sps_j, (*rps_nat)[j]);
244 });
245
246 auto after_post = w.mut_con(Tp)->set("afterPost");
247 after_post->app(true, cont, nested_insert(w, wb_matrix, write_coords, So, ro, after_post->var()));
248 write_back->app(true, post, {w.tuple({element_final, w.tuple(post_elements)}), after_post});
249
250 // Inner (reduction) loops over the trailing `Rr` bounds of `Sr`, collecting the reduction iteration
251 // indices.
252 acc = init;
253 cont = write_back;
254 auto raw_red = build_loops(w, current_mut, cont, acc, Defs(sr).subspan(ro, rr), "forIn");
255 auto element_acc = acc;
256
257 // The full loop iteration vector `(o…, r…)`; its moduli are exactly `Sr`.
258 DefVec iters_v = out_iters;
259 for (u64 j = 0; j != rr; ++j)
260 iters_v.emplace_back(w.call(core::conv::u, sr[ro + j], raw_red[j]));
261 auto iters = w.tuple(iters_v);
262
263 // Read one element from each input at its affine read coordinates.
264 DefVec input_elements(nis_nat, [&](size_t i) {
265 auto sis_i = Sis->proj(nis_nat, i);
266 auto coords = affine_map(accs->proj(nis_nat, i), Ris->proj(nis_nat, i), n, Sr, sis_i, iters);
267 return nested_extract(w, new_is->proj(nis_nat, i), coords, sis_i, (*ris_nat)[i]);
268 });
269
270 comb->set("comb");
271 post->set("post");
272 current_mut->app(true, comb, {w.tuple({element_acc, w.tuple(input_elements)}), cont});
273 return call;
274 } catch (const std::exception& e) { fe::throwf("failed to lower `tensor.map_reduce`: {}", e.what()); }
275}
276
277const Def* LowerMapReduce::build_pointwise(const Def* inputs,
278 const Def* type,
279 const Def* So,
280 u64 ro,
281 std::function<const Def*(Defs, const Def*)> compute) {
282 auto& w = new_world();
283
284 auto fun = w.mut_fun(inputs->type(), type)->set("pointwise");
285 auto ds_fun = cps::op_cps2ds_dep(fun)->set("dsFun");
286 auto call = w.app(ds_fun, inputs)->set("call");
287
288 auto [new_inputs, cont] = fun->vars<2>();
289 new_inputs->set("is");
290
291 // Output loops over `So`, collecting the raw i64 iteration indices for `compute`.
292 const Def* acc = w.bot(cont->type()->as<Pi>()->dom());
293 auto current_mut = fun;
294 auto so = So->projs(ro);
295 auto out_iters = build_loops(w, current_mut, cont, acc, so, "forOut"); // raw i64 loop counters
296 auto wb_matrix = acc;
297
298 // Write the computed element at the (identity) output coordinates; convert the i64 counters to `Idx (So#k)`.
299 DefVec write_coords(ro, [&](size_t i) { return w.call(core::conv::u, so[i], out_iters[i]); });
300 auto element = compute(out_iters, new_inputs);
301 current_mut->app(true, cont, nested_insert(w, wb_matrix, w.tuple(write_coords), So, ro, element));
302 return call;
303}
304
305const Def* LowerMapReduce::lower_generate(const App* app) {
306 auto& w = new_world();
307 auto c = rewrite(app->callee())->as<App>();
308 auto body = rewrite(app->arg());
309 auto type = rewrite(app->type());
310
311 auto [meta, s_out] = c->uncurry_args<2>();
312 auto [T, r] = meta->projs<2>();
313 auto r_l = Lit::isa<u64>(r);
314 if (!r_l) {
315 log().w("rank {} of {} is not known at lowering time", r, app);
316 return nullptr;
317 }
318 auto rn = *r_l;
319
320 // Nested arrays erase literal singleton axes. If every logical axis is
321 // erased (including rank zero), the sole element is body((0, ..., 0)).
322 if (!type->isa<Arr>()) {
323 DefVec zeros(rn, [&](size_t) { return w.lit_i64(0); });
324 return w.app(body, w.tuple(zeros));
325 }
326
327 auto unit = w.tuple(Defs{});
328 auto compute = [&](Defs out_iters, const Def*) { return w.call(body, out_iters); };
329 return build_pointwise(unit, type, s_out, rn, compute);
330}
331
332const Def* LowerMapReduce::lower_pad(const App* app) {
333 auto& w = new_world();
334 auto c = rewrite(app->callee())->as<App>();
335 auto args = rewrite(app->arg()); // (input, value)
336 auto type = rewrite(app->type());
337
338 // callee: pad {T, r} [s_in] [mode, lo, hi]
339 auto [Tr, s_in, params] = c->uncurry_args<3>();
340 auto [T, r] = Tr->projs<2>();
341 auto [mode, lo, hi] = params->projs<3>();
342
343 auto r_l = Lit::isa<u64>(r);
344 auto mode_l = Lit::isa<u64>(mode);
345 if (!r_l || !mode_l) {
346 log().w("rank/mode of {} is not known at lowering time", app);
347 return nullptr;
348 }
349 auto rn = *r_l;
350 auto mode_nat = *mode_l;
351 auto i64 = w.type_i64();
352
353 // Deduce the output shape: s_out#d = lo#d + s_in#d + hi#d.
354 DefVec so(rn);
355 auto inner_type = type;
356 for (u64 d = 0; d < rn; ++d) {
357 auto inner_type_seq = inner_type->as<Seq>();
358 so[d] = inner_type_seq->arity();
359 inner_type = inner_type_seq->body();
360 }
361 auto s_out = w.tuple(so);
362
363 auto compute = [&](Defs out_iters, const Def* new_inputs) -> const Def* {
364 auto [input, value] = new_inputs->projs<2>();
365 DefVec clamped(rn); // per-axis read index, kept in range, as `Idx (s_in#d)`
366 DefVec valid; // per-axis in-bounds flag (constant mode only)
367 for (u64 d = 0; d < rn; ++d) {
368 auto lo_d = w.call<core::bitcast>(i64, lo->proj(rn, d));
369 auto sin_d = w.call<core::bitcast>(i64, s_in->proj(rn, d));
370 auto in_d = w.call(core::wrap::sub, core::Mode::none, Defs{out_iters[d], lo_d}); // o#d − lo#d
371 const Def* idx_i64;
372 if (mode_nat == 0) { // constant: a single unsigned `<` covers both bounds (underflow wraps high)
373 auto v_d = w.call(core::icmp::ul, w.tuple({in_d, sin_d}));
374 valid.push_back(v_d);
375 idx_i64 = select(w, v_d, in_d, w.lit_i64(0));
376 } else { // replicate: clamp the read to the nearest edge [0, s_in#d − 1]
377 idx_i64 = clamp(w, in_d, sin_d);
378 }
379 clamped[d] = w.call(core::conv::u, s_in->proj(rn, d), idx_i64);
380 }
381 auto elem = nested_extract(w, input, w.tuple(clamped), s_in, rn);
382 if (mode_nat != 0) return elem; // replicate: always a (clamped) read
383 auto all_valid = valid.empty() ? w.lit_tt() : valid[0];
384 for (u64 d = 1; d < valid.size(); ++d)
385 all_valid = w.call(core::bit2::and_, w.lit_nat(2), w.tuple({all_valid, valid[d]}));
386 return select(w, all_valid, elem, value); // constant: fill out-of-region cells with `value`
387 };
388
389 return build_pointwise(args, type, s_out, rn, compute);
390}
391
392const Def* LowerMapReduce::lower_concat(const App* app) {
393 auto& w = new_world();
394 auto c = rewrite(app->callee())->as<App>();
395 auto args = rewrite(app->arg()); // the `is` input tuple
396 auto type = rewrite(app->type());
397
398 // callee: concat {T, nis, r} [ax] {Sis}
399 auto [TnisR, ax, Sis] = c->uncurry_args<3>();
400 auto [T, nis, r] = TnisR->projs<3>();
401
402 auto nis_l = Lit::isa<u64>(nis);
403 auto r_l = Lit::isa<u64>(r);
404 auto ax_l = Lit::isa<u64>(ax);
405 if (!nis_l || !r_l || !ax_l) {
406 log().w("nis/r/ax of {} are not known at lowering time", app);
407 return nullptr;
408 }
409 auto nisn = *nis_l, rn = *r_l, axn = *ax_l;
410 auto i64 = w.type_i64();
411
412 // Prefix offsets along `ax`: off#i = Σ_{j<i} Sis#j#ax (literal extents required).
413 DefVec off(nisn);
414 u64 acc_off = 0;
415 for (u64 i = 0; i < nisn; ++i) {
416 off[i] = w.lit_i64(acc_off);
417 auto ei = Lit::isa<u64>(Sis->proj(nisn, i)->proj(rn, axn));
418 if (!ei) {
419 log().w("extent of input {} of {} along the concat axis is not known at lowering time", i, app);
420 return nullptr;
421 }
422 acc_off += *ei;
423 }
424
425 // Deduce the output shape: the summed extent along `ax`, the shared extents elsewhere.
426 DefVec so(rn);
427 for (u64 d = 0; d < rn; ++d)
428 so[d] = (d == axn) ? w.lit_nat(acc_off) : Sis->proj(nisn, 0)->proj(rn, d);
429 auto s_out = w.tuple(so);
430
431 auto compute = [&](Defs out_iters, const Def* new_inputs) -> const Def* {
432 auto o_ax = out_iters[axn];
433 // Read input `i` at `out_iters`, but with the `ax` coordinate shifted by off#i and clamped into input `i`.
434 auto read_i = [&](u64 i) -> const Def* {
435 auto Sis_i = Sis->proj(nisn, i);
436 auto e_i = w.call<core::bitcast>(i64, Sis_i->proj(rn, axn));
437 auto loc = w.call(core::wrap::sub, core::Mode::none, Defs{o_ax, off[i]});
438 auto in_ax = clamp(w, loc, e_i);
439 DefVec coords(rn, [&](size_t d) {
440 return w.call(core::conv::u, Sis_i->proj(rn, d), d == axn ? in_ax : out_iters[d]);
441 });
442 return nested_extract(w, new_inputs->proj(nisn, i), w.tuple(coords), Sis_i, rn);
443 };
444 // Select chain: the highest `i` with off#i ≤ o_ax owns the cell (offsets increase, later wins).
445 auto result = read_i(0);
446 for (u64 i = 1; i < nisn; ++i) {
447 auto cond = w.call(core::icmp::uge, w.tuple({o_ax, off[i]}));
448 result = select(w, cond, read_i(i), result);
449 }
450 return result;
451 };
452
453 return build_pointwise(args, type, s_out, rn, compute);
454}
455
456const Def* LowerMapReduce::lower_gather(const App* app) {
457 auto& w = new_world();
458 auto c = rewrite(app->callee())->as<App>();
459 auto args = rewrite(app->arg());
460 auto type = rewrite(app->type());
461
462 auto [Tr, shapes, dim] = c->uncurry_args<3>();
463 auto [T, r] = Tr->projs<2>();
464 auto [s_src, s_idx] = shapes->projs<2>();
465 auto r_l = Lit::isa<u64>(r);
466 auto dim_l = Lit::isa<u64>(dim);
467 if (!r_l || !dim_l) return nullptr;
468
469 // On every axis other than `dim`, gather reuses the output coordinate as
470 // an input coordinate. The shared checker rejects statically provable
471 // violations before any source buffer access is emitted.
472 if (!check_gather_shape_constraints(r, dim, s_src, s_idx)) return nullptr;
473
474 auto compute = [&](Defs out_indices, const Def* inputs) -> const Def* {
475 auto element = w.app(w.annex<tensor::gather_pointwise_elem_impl>(), {T, r});
476 return w.call(element, Defs{s_src, s_idx}, dim, out_indices, inputs);
477 };
478 return build_pointwise(args, type, s_idx, *r_l, compute);
479}
480
481const Def* LowerMapReduce::lower_scatter(const App* app) {
482 auto& w = new_world();
483 auto c = rewrite(app->callee())->as<App>();
484 auto args = rewrite(app->arg());
485 auto type = rewrite(app->type());
486
487 auto [Tr, shapes, dim] = c->uncurry_args<3>();
488 auto [T, r] = Tr->projs<2>();
489 auto [s_src, s_idx, s_updates] = shapes->projs<3>();
490 auto r_l = Lit::isa<u64>(r);
491 auto dim_l = Lit::isa<u64>(dim);
492 if (!r_l || !dim_l) return nullptr;
493 auto rn = *r_l;
494
495 // Every scatter visit reads one update at the same coordinate as the
496 // index tensor, while non-dim coordinates are also reused in the source.
497 if (!check_scatter_shape_constraints(r, dim, s_src, s_idx, s_updates)) return nullptr;
498
499 auto fun = w.mut_fun(args->type(), type)->set("scatter");
500 auto ds_fun = cps::op_cps2ds_dep(fun)->set("dsFun");
501 auto call = w.app(ds_fun, args)->set("call");
502
503 auto [iiu, cont] = fun->vars<2>();
504 auto [input, index, updates] = iiu->projs<3>();
505 auto acc = input;
506 auto current = fun;
507 auto dims = s_idx->projs(rn);
508 auto visit_indices = build_loops(w, current, cont, acc, dims, "scatter");
509
510 auto step = w.app(w.annex<tensor::scatter_step_impl>(), {T, r});
511 auto next = w.call(step, Defs{s_src, s_idx, s_updates}, dim, visit_indices, Defs{acc, index, updates});
512 current->app(true, cont, next);
513 return call;
514}
515
517 // A `tensor.if_static` still stuck at lowering time guards a runtime value: residualize to
518 // its dynamic branch.
519 if (Axm::isa<tensor::if_static>(app)) return rewrite(app->arg(3, 2));
520 if (auto bc = Axm::isa<tensor::broadcast>(app)) {
521 if (auto res = lower_broadcast(bc)) return res;
522 } else if (auto mr = Axm::isa<tensor::map_reduce_post>(app)) {
523 if (auto res = lower_map_reduce(mr)) return res;
524 } else if (auto generate = Axm::isa<tensor::generate>(app)) {
525 if (auto res = lower_generate(generate)) return res;
526 } else if (auto pad = Axm::isa<tensor::pad>(app)) {
527 if (auto res = lower_pad(pad)) return res;
528 } else if (auto cat = Axm::isa<tensor::concat>(app)) {
529 if (auto res = lower_concat(cat)) return res;
530 } else if (auto gather = Axm::isa<tensor::gather>(app)) {
531 if (auto res = lower_gather(gather)) return res;
532 } else if (auto scatter = Axm::isa<tensor::scatter>(app)) {
533 if (auto res = lower_scatter(scatter)) return res;
534 }
535 return RWPhase::rewrite_imm_App(app);
536}
537
538} // namespace mim::plug::tensor::phase
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
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:196
static std::optional< T > isa(const Def *def)
Definition def.h:937
const fe::Log & log() const
Definition phase.h:79
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 *) final
@ none
Wrap around.
Definition core.h:16
const Def * op_cps2ds_dep(const Def *k)
Definition cps.h:16
bool check_scatter_shape_constraints(const Def *rank, const Def *dim, const Def *source_shape, const Def *index_shape, const Def *updates_shape)
Checks statically decidable scatter constraints. Returns false for unresolved relations.
bool check_gather_shape_constraints(const Def *rank, const Def *dim, const Def *source_shape, const Def *index_shape)
Checks statically decidable gather constraints. Returns false for unresolved relations.
const Def * op_set(const Def *T, const Def *r, const Def *s, const Def *arr, const Def *index, const Def *x)
Definition tensor.h:70
const Def * op_get(const Def *T, const Def *r, const Def *s, const Def *arr, const Def *index)
Definition tensor.h:64
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
DefVec cat(Defs, Defs)
Definition tuple.cpp:73
@ App
Definition def.h:122