MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_to_mem.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/mem/mem.h"
12
14
15namespace {
16
17/// Does `t` (recursively through immutable sigmas) contain a Pi?
18bool contains_pi(const Def* t) {
19 if (t->isa<Pi>()) return true;
20 if (auto sig = t->isa_imm<Sigma>())
21 for (auto op : sig->ops())
22 if (contains_pi(op)) return true;
23 return false;
24}
25
26/// Peels nested `Pack`s off `d`; returns the innermost body iff `d` is a constant splat — every axis a
27/// `Pack` (so `d` is uniform, not e.g. a `Tuple` of distinct rows), bottoming out in a closed scalar
28/// (index-independent, non-array). So `‹784, 1024; 1e-3›` yields the scalar `1e-3`, while an index-dependent
29/// pack `‹i; f i›` or a genuine literal `((1, 2), (3, 4))` yields `nullptr`.
30const Def* splat_scalar(const Def* d) {
31 if (!d->isa<Pack>()) return nullptr;
32 while (auto pack = d->isa<Pack>())
33 d = pack->body();
34 return (d->is_closed() && !d->type()->isa<Arr>()) ? d : nullptr;
35}
36
37/// Is `app` one of the tensor ops this phase bufferizes?
38bool is_tensor_op(const App* app) {
41}
42
43} // namespace
44
45void LowerToMem::collect_tensor_types() {
46 // The default pipeline lowers tensors exclusively through buffers — there is no value-semantics
47 // fallback. A program shape the conversion cannot handle is a hard error, not silent residue.
48 auto gate = [](const char* why, const Def* culprit) { fe::throwf("cannot bufferize: {} ({})", why, culprit); };
49 // Fully folded shapes (`«1; T»` ≡ `T`) denote plain scalars — recording them would poison every
50 // function whose signature mentions the element type, so only genuine array types count as tensors.
51 auto add_tensor_ty = [this](const Def* t) {
52 if (t->isa<Arr>()) tensor_ty_.emplace(t);
53 };
54
55 unique_queue<DefSet> wl;
56 auto push = [&wl](const Def* d) {
57 if (d) wl.push(d);
58 };
59
60 for (auto mut : old_world().externals().muts())
61 push(mut);
62
63 while (!wl.empty()) {
64 auto def = wl.pop();
65
66 if (auto app = def->isa<App>()) {
67 if (auto [axm, curry, trip] = Axm::get(app); axm && curry == 0 && axm->plugin() == tensor::Plugin_Id)
68 ops_seen_ = true;
70 // get/set: the first explicit argument is the tensor `arr`.
71 auto [T, r, s] = app->callee()->as<App>()->args<3>();
72 if (T->isa<Arr>()) gate("tensor with array element type", T);
73 add_tensor_ty(app->arg()->proj(0)->type());
74 } else if (Axm::isa<tensor::map_reduce>(app)) {
75 // result and each of the `nis` inputs are tensors.
76 add_tensor_ty(app->type());
77 auto [nis, meta, shapes, TisRisSis, comb_init, acc_out, accs]
78 = app->callee()->as<App>()->uncurry_args<7>();
79 if (meta->proj(3, 0)->isa<Arr>()) gate("tensor with array element type", meta->proj(3, 0));
80 if (auto nis_l = Lit::isa<u64>(nis)) {
81 auto Tis = TisRisSis->proj(3, 0);
82 for (u64 i = 0; i < *nis_l; ++i) {
83 if (Tis->proj(*nis_l, i)->isa<Arr>()) gate("tensor with array element type", Tis);
84 add_tensor_ty(app->arg()->proj(*nis_l, i)->type());
85 }
86 }
87 } else if (Axm::isa<tensor::broadcast>(app)) {
88 // result «s_out; T» and input «s_in; T» (the 3rd argument) are tensors.
89 auto [T, r] = app->callee()->as<App>()->args<2>();
90 if (T->isa<Arr>()) gate("tensor with array element type", T);
91 add_tensor_ty(app->type());
92 add_tensor_ty(app->arg()->proj(2)->type());
93 } else if (Axm::isa<tensor::pad>(app)) {
94 // callee: pad {T, r} [s_in] [mode, lo, hi]; result «s_out; T» and `input` are tensors.
95 auto [Tr, s_in, params] = app->callee()->as<App>()->uncurry_args<3>();
96 auto [T, r] = Tr->projs<2>();
97 if (T->isa<Arr>()) gate("tensor with array element type", T);
98 if (!Lit::isa<u64>(r) || !Lit::isa<u64>(params->proj(3, 0)))
99 gate("non-literal rank/mode of %tensor.pad", app);
100 add_tensor_ty(app->type());
101 add_tensor_ty(app->arg()->proj(0)->type());
102 } else if (Axm::isa<tensor::concat>(app)) {
103 // callee: concat {T, nis, r} [ax] {Sis}; result «s_out; T» and each input are tensors.
104 auto [TnisR, ax, Sis] = app->callee()->as<App>()->uncurry_args<3>();
105 auto [T, nis, r] = TnisR->projs<3>();
106 if (T->isa<Arr>()) gate("tensor with array element type", T);
107 auto nis_l = Lit::isa<u64>(nis);
108 auto r_l = Lit::isa<u64>(r);
109 auto ax_l = Lit::isa<u64>(ax);
110 if (!nis_l || !r_l || !ax_l) {
111 gate("non-literal nis/rank/axis of %tensor.concat", app);
112 } else {
113 add_tensor_ty(app->type());
114 for (u64 i = 0; i < *nis_l; ++i) {
115 // The loop generation needs literal extents along `ax` for the prefix offsets.
116 if (!Lit::isa<u64>(Sis->proj(*nis_l, i)->proj(*r_l, *ax_l)))
117 gate("non-literal extent along the concat axis", app);
118 add_tensor_ty(app->arg()->proj(*nis_l, i)->type());
119 }
120 }
121 } else if (auto [axm, curry, trip] = Axm::get(app);
122 axm && curry == 0 && axm->plugin() == tensor::Plugin_Id) {
123 // Any other tensor op (a symbolic `shape`, …) has no buffer-world lowering.
124 gate("unbufferizable tensor op", app);
125 }
126 // Lams passed inside a tensor op's curry chain (combiners, affine index maps) are element-level.
127 if (is_tensor_op(app)) {
128 for (const App* a = app; a; a = a->callee()->isa<App>()) {
129 if (auto k = a->arg()->isa_mut<Lam>()) op_args_.emplace(k);
130 for (auto op : a->arg()->ops())
131 if (auto k = op ? op->isa_mut<Lam>() : nullptr) op_args_.emplace(k);
132 }
133 }
134 }
135
136 for (auto op : def->ops())
137 push(op);
138 push(def->type());
139 }
140
141 for (auto mut : old_world().externals().muts())
142 if (auto lam = mut->isa_mut<Lam>(); lam && is_tensor_fn(lam)) tensor_fns_.emplace(lam);
143 // No tensor boundaries AND no tensor ops: nothing for the sweeps below to check.
144 // (Ops without boundaries still bufferize: their value-world operands are materialized.)
145 if (tensor_fns_.empty() && !ops_seen_) return;
146
147 // Higher-order bufferized functions: a continuation parameter whose domain itself contains a Pi would
148 // need boundary conversion inside nested continuation types, which `conv_boundary` does not perform.
149 for (auto old_fn : tensor_fns_) {
150 auto dom = old_fn->type()->dom();
151 auto n = dom->num_projs();
152 for (size_t i = 0; i != n; ++i)
153 if (auto pi = Pi::isa_cn(dom->proj(n, i)); pi && contains_pi(pi->dom()))
154 return gate("higher-order bufferized function", old_fn);
155 }
156
157 // Second sweep: shapes the conversion cannot adapt — the value-semantics path lowers everything instead.
158 unique_queue<DefSet> wl2;
159 for (auto mut : old_world().externals().muts())
160 if (mut) wl2.push(mut);
161 while (!wl2.empty()) {
162 auto def = wl2.pop();
163
164 for (auto op : def->ops()) {
165 if (!op) continue;
166 if (auto fn = op->isa_mut<Lam>()) {
167 // A bufferized function referenced as a value (not as the callee of a call, and not the
168 // binder back-reference of its own variable) cannot be adapted.
169 if (tensor_fns_.contains(fn))
170 if (!(def->isa<App>() && def->as<App>()->callee() == op) && !def->isa<Var>())
171 return gate("bufferized function used as a value", fn);
172 if (!tensor_fns_.contains(fn) && !op_args_.contains(fn) && mentions_tensor(fn->type()->dom())) {
173 // A tensor-typed function the conversion does not rewrite: an unset external keeps its
174 // value ABI, a direct-style local cannot be rebuilt as a continuation — either way a
175 // bufferized caller would pass a buffer against a value-array signature.
176 if (fn->is_external() || !Pi::isa_cn(fn->type()))
177 return gate("unconvertible tensor-typed function", fn);
178 }
179 }
180 wl2.push(op);
181 }
182 if (def->type()) wl2.push(def->type());
183 }
184}
185
187 collect_tensor_types(); // hard-errors on shapes the conversion cannot handle
188 // Nothing tensor-related in the program: skip the whole-world rebuild entirely.
189 if (tensor_fns_.empty() && !ops_seen_) return;
191}
192
193const Def* LowerToMem::buf_of(const Def* arr_ty) {
194 auto& w = new_world();
195 DefVec dims;
196 auto cur = arr_ty;
197 while (auto arr = cur->isa<Arr>()) {
198 dims.push_back(rewrite(arr->arity()));
199 cur = arr->body();
200 }
201 return buffer::type_buf(w.lit_nat(dims.size()), w.tuple(dims), rewrite(cur));
202}
203
204const Def* LowerToMem::fold_index(const Def* shape, const Def* idx) {
205 auto& w = new_world();
206 auto r = shape->num_projs();
207 DefVec out;
208 for (size_t i = 0; i != r; ++i)
209 if (auto l = Lit::isa<u64>(shape->proj(r, i)); !(l && *l == 1)) out.push_back(idx->proj(r, i));
210 return w.tuple(out);
211}
212
213const Def* LowerToMem::bot_mem() {
214 auto& w = new_world();
215 return w.bot(w.call<mem::M>(0));
216}
217
218bool LowerToMem::mentions_tensor(const Def* t) const {
219 if (tensor_ty_.contains(t)) return true;
220 if (auto sig = t->isa<Sigma>()) {
221 for (auto op : sig->ops())
222 if (mentions_tensor(op)) return true;
223 } else if (auto pi = t->isa<Pi>()) {
224 return mentions_tensor(pi->dom()); // descend into continuation domains, but never into `Arr` elements
225 }
226 return false;
227}
228
229bool LowerToMem::is_tensor_fn(Lam* lam) const {
230 return lam->is_external() && lam->is_set() && mentions_tensor(lam->type()->dom());
231}
232
233const Def* LowerToMem::conv_boundary(const Def* t) {
234 if (tensor_ty_.contains(t)) return buf_of(t);
235 if (auto sig = t->isa_imm<Sigma>(); sig && mentions_tensor(sig)) {
236 auto n = sig->num_ops();
237 DefVec ops(n);
238 for (size_t i = 0; i != n; ++i)
239 ops[i] = conv_boundary(sig->op(i));
240 return new_world().sigma(ops);
241 }
242 return rewrite(t);
243}
244
246 if (is_bootstrapping()) return RWPhase::rewrite_mut_Lam(lam);
247
248 // A bufferized function: convert tensor-typed parameters to `%buffer.Buf`, including inside grouped
249 // sigma parameters and continuation domains. No memory is introduced here — AddMem does that.
250 if (is_tensor_fn(lam)) {
251 auto& w = new_world();
252 auto dom = lam->type()->dom();
253 auto n = dom->num_projs();
254
255 DefVec doms(n);
256 for (size_t i = 0; i != n; ++i) {
257 auto d = dom->proj(n, i);
258 if (auto pi = Pi::isa_cn(d))
259 doms[i] = w.cn(conv_boundary(pi->dom()));
260 else
261 doms[i] = conv_boundary(d);
262 }
263
264 auto new_lam = w.mut_con(doms)->set(lam->dbg());
265 map(lam, new_lam);
266 if (lam->num_vars() != 0) map(lam->var(), new_lam->var());
267 new_lam->set(rewrite(lam->filter()), rewrite(lam->body()));
268 return new_lam;
269 }
270
271 // A local continuation carrying tensor types (a return continuation of a bufferized call, a join point,
272 // an error continuation): convert its domain the same way. This is context-independent, so the order in
273 // which references reach it does not matter.
274 if (!lam->is_external() && lam->is_set() && !op_args_.contains(lam)) {
275 if (auto pi = Pi::isa_cn(lam->type()); pi && mentions_tensor(pi->dom())) {
276 auto& w = new_world();
277 auto new_lam = w.mut_con(conv_boundary(pi->dom()))->set(lam->dbg());
278 map(lam, new_lam);
279 if (lam->num_vars() != 0) map(lam->var(), new_lam->var());
280 new_lam->set(rewrite(lam->filter()), rewrite(lam->body()));
281 return new_lam;
282 }
283 }
284
285 return RWPhase::rewrite_mut_Lam(lam);
286}
287
289 if (is_bootstrapping()) return RWPhase::rewrite_imm_App(app);
290 if (Axm::isa<tensor::get>(app)) return lower_get(app);
291 if (Axm::isa<tensor::set>(app)) return lower_set(app);
292 if (Axm::isa<tensor::broadcast>(app)) return lower_broadcast(app);
294 if (Axm::isa<tensor::pad>(app)) return lower_pad(app);
295 if (Axm::isa<tensor::concat>(app)) return lower_concat(app);
296
297 // Call of a bufferized function: adapt the call site.
298 if (auto callee = app->callee()->isa_mut<Lam>(); callee && tensor_fns_.contains(callee))
299 return lower_call(app, callee);
300
301 // Call of a converted continuation (a local lam or a parameter var whose domain mentions a tensor):
302 // materialize value-world tensor arguments into buffers. Element-level lams (op_args_) keep value ABI.
303 if (auto pi = Pi::isa_cn(app->callee()->type()); pi && mentions_tensor(pi->dom())) {
304 if (auto callee = app->callee()->isa_mut<Lam>(); callee && op_args_.contains(callee))
305 return RWPhase::rewrite_imm_App(app);
306 auto& w = new_world();
307 return w.app(rewrite(app->callee()), materialize(pi->dom(), app->arg()));
308 }
309
310 return RWPhase::rewrite_imm_App(app);
311}
312
313const Def* LowerToMem::lower_call(const App* app, Lam* old_callee) {
314 auto& w = new_world();
315 auto new_callee = rewrite(old_callee);
316 auto dom = old_callee->type()->dom();
317 auto n = dom->num_projs();
318
319 DefVec args(n);
320 for (size_t i = 0; i != n; ++i) {
321 auto d = dom->proj(n, i);
322 auto a = app->arg()->proj(n, i);
323 // Continuations pass through: their domains are converted by `rewrite_mut_Lam` to exactly the
324 // domain the callee's new signature expects.
325 args[i] = Pi::isa_cn(d) ? rewrite(a) : materialize(d, a);
326 }
327 return w.app(new_callee, w.tuple(args));
328}
329
330const Def* LowerToMem::splat_buffer(const Def* arr_ty, const Def* scalar) {
331 // `%buffer.constant` sets every element to `scalar`; `%matrix.lower_aff` fills it with a loop rather
332 // than storing a monolithic literal array (which the LLVM backend cannot digest for large shapes).
333 auto [bro, bso, boT] = Axm::isa<buffer::Buf>(buf_of(arr_ty))->args<3>();
334 auto [m, out] = buffer::op_constant(bro, bso, boT, bot_mem(), scalar)->projs<2>();
335 return out;
336}
337
338const Def* LowerToMem::materialize(const Def* old_ty, const Def* old_arg) {
339 auto& w = new_world();
340 if (tensor_ty_.contains(old_ty)) {
341 // A constant splat `‹s; c›` (e.g. a learning-rate or bias literal): a `%buffer.init` would store the
342 // whole array as one giant LLVM constant. Emit `%buffer.constant` (lowered to a fill loop) instead.
343 if (auto c = splat_scalar(old_arg)) return splat_buffer(old_ty, rewrite(c));
344 auto v = rewrite(old_arg);
345 if (Axm::isa<buffer::Buf>(v->type())) return v; // already a buffer
346 auto [br, bs, bT] = Axm::isa<buffer::Buf>(buf_of(old_ty))->args<3>();
347 auto [m, buf] = buffer::op_init(br, bs, bT, bot_mem(), v)->projs<2>();
348 return buf;
349 }
350 if (auto sig = old_ty->isa_imm<Sigma>(); sig && mentions_tensor(sig)) {
351 auto n = sig->num_ops();
352 DefVec ops(n);
353 for (size_t i = 0; i != n; ++i)
354 ops[i] = materialize(sig->op(i), old_arg->proj(n, i));
355 return w.tuple(ops);
356 }
357 return rewrite(old_arg);
358}
359
360const Def* LowerToMem::lower_get(const App* app) {
361 auto c = rewrite(app->callee())->as<App>();
362 auto arg = rewrite(app->arg());
363 auto [arr, index] = arg->projs<2>();
364 auto [T, r, s] = c->args<3>();
365 auto buf = Axm::isa<buffer::Buf>(arr->type());
366 // A `get` on a value-world tensor (e.g. a literal): materialize it into a buffer.
367 if (!buf) {
368 arr = materialize(app->arg()->proj(0)->type(), app->arg()->proj(0));
369 buf = Axm::isa<buffer::Buf>(arr->type());
370 if (!buf) return RWPhase::rewrite_imm_App(app); // not a recorded tensor type: leave it alone
371 }
372 auto [br, bs, bT] = buf->args<3>(); // actual (folded) buffer metadata
373
374 auto [m, v] = buffer::op_read(br, bs, bT, bot_mem(), arr, fold_index(s, index))->projs<2>();
375 return v; // the loaded value
376}
377
378const Def* LowerToMem::lower_set(const App* app) {
379 auto c = rewrite(app->callee())->as<App>();
380 auto arg = rewrite(app->arg());
381 auto [arr, index, x] = arg->projs<3>();
382 auto [T, r, s] = c->args<3>();
383 auto buf = Axm::isa<buffer::Buf>(arr->type());
384 // A `set` on a value-world tensor (e.g. a literal): materialize it into a buffer.
385 if (!buf) {
386 arr = materialize(app->arg()->proj(0)->type(), app->arg()->proj(0));
387 buf = Axm::isa<buffer::Buf>(arr->type());
388 if (!buf) return RWPhase::rewrite_imm_App(app); // not a recorded tensor type: leave it alone
389 }
390 auto [br, bs, bT] = buf->args<3>(); // actual (folded) buffer metadata
391 auto fidx = fold_index(s, index);
392
393 if (reuse_in_place(app)) {
394 auto [m, buf2] = buffer::op_write(br, bs, bT, bot_mem(), arr, fidx, x)->projs<2>();
395 return buf2;
396 }
397
398 // AlwaysAllocate policy: allocate a fresh buffer, copy the source in, then write the element.
399 // This local chain is properly threaded; AddMem splices its `⊥` root into the global chain.
400 auto [m1, q] = buffer::op_alloc(br, bs, bT, bot_mem())->projs<2>();
401 auto m2 = buffer::op_copy(br, bs, bT, m1, q, arr);
402 auto [m3, out] = buffer::op_write(br, bs, bT, m2, q, fidx, x)->projs<2>();
403 return out;
404}
405
406const Def* LowerToMem::lower_broadcast(const App* app) {
407 // Thin bufferization: map the SSA `tensor.broadcast` onto the buffer-world `matrix.broadcast`.
408 // The loop generation lives in the matrix plugin (`%matrix.lower_aff`).
409 auto& w = new_world();
410 auto c = rewrite(app->callee())->as<App>();
411 auto arg = rewrite(app->arg());
412 auto [s_in, s_out, input] = arg->projs<3>();
413 auto [T, r] = c->args<2>();
414
415 // No-op broadcast (already normalized away in most cases).
416 if (s_in == s_out) return input;
417
418 // A broadcast of a value-world tensor (e.g. a literal): materialize it into a buffer.
419 auto in_buf = Axm::isa<buffer::Buf>(input->type());
420 if (!in_buf) {
421 input = materialize(app->arg()->proj(2)->type(), app->arg()->proj(2));
422 in_buf = Axm::isa<buffer::Buf>(input->type());
423 }
424
425 // Rank-0 source: an all-size-1 input shape folds to a plain scalar that is never recorded as a tensor
426 // type, so `materialize` leaves it as a value. Broadcasting a scalar fills every element with it.
427 if (!in_buf) return splat_buffer(app->type(), input);
428
429 // Actual (size-1-folded) input/output buffer shapes — `matrix.broadcast` is parameterised by them.
430 auto [bri, bsi, biT] = in_buf->args<3>();
431 auto [bro, bso, boT] = Axm::isa<buffer::Buf>(buf_of(app->type()))->args<3>();
432
433 auto op = w.annex<matrix::broadcast>();
434 op = w.app(op, w.tuple({T, bri, bsi, bro, bso, r}));
435 op = w.app(op, w.tuple({s_in, s_out}));
436 auto [m, out] = w.app(op, w.tuple({bot_mem(), input}))->projs<2>();
437 return out;
438}
439
440const Def* LowerToMem::lower_map_reduce(const App* app) {
441 // Thin bufferization: map the SSA `tensor.map_reduce` onto the buffer-world `matrix.map_reduce_aff`,
442 // reusing the (rewritten) meta. The loop generation lives in the matrix plugin (`%matrix.lower_aff`).
443 auto& w = new_world();
444 auto c = rewrite(app->callee())->as<App>();
445 auto inputs = rewrite(app->arg()); // the (bufferized) input buffers `is`
446
447 auto [nis, meta, shapes, TisRisSis, comb_init, acc_out, accs] = c->uncurry_args<7>();
448 auto [comb, init] = comb_init->projs<2>();
449
450 // Value-world tensor inputs (e.g. literals): materialize them into buffers.
451 if (auto nis_l = Lit::isa<u64>(nis)) {
452 DefVec ins(*nis_l);
453 for (u64 i = 0; i < *nis_l; ++i) {
454 ins[i] = inputs->proj(*nis_l, i);
455 if (!Axm::isa<buffer::Buf>(ins[i]->type())) {
456 auto old_in = app->arg()->proj(*nis_l, i);
457 ins[i] = materialize(old_in->type(), old_in);
458 if (!Axm::isa<buffer::Buf>(ins[i]->type()))
459 return RWPhase::rewrite_imm_App(app); // not a recorded tensor type: leave it alone
460 }
461 }
462 // Re-tuple the inputs: the generic rewrite rebuilds the argument tuple with its stale value-array
463 // type even when its elements were converted to buffers, which would not be assignable to the op's
464 // `«nis; %buffer.Buf …»` domain.
465 inputs = w.tuple(ins);
466 }
467
468 // Wrap the pure tensor combiner `Fn [To, «nis; Tis»] → To` into the mem-threaded combiner
469 // `Fn [%mem.M 0, To, «nis; Tis»] → [%mem.M 0, To]` that `matrix.map_reduce_aff` expects.
470 auto mem_ty = w.call<mem::M>(0);
471 auto inner = comb->type()->as<Pi>()->dom()->proj(0); // [To, «nis; Tis»]
472 auto [cTo, ins_ty] = inner->projs<2>();
473 auto memcomb = w.mut_fun(w.sigma({mem_ty, cTo, ins_ty}), w.sigma({mem_ty, cTo}))->set("memComb");
474 auto [cm, cacc, cins] = memcomb->var(0_n)->projs<3>();
475 auto cret = memcomb->var(1);
476 auto after = w.mut_con(cTo)->set("afterComb");
477 after->app(true, cret, w.tuple({cm, after->var(0_n)}));
478 memcomb->set(true, w.app(comb, w.tuple({w.tuple({cacc, cins}), after})));
479
480 auto op = w.annex<matrix::map_reduce_aff>();
481 op = w.app(op, nis);
482 op = w.app(op, meta);
483 op = w.app(op, shapes);
484 op = w.app(op, TisRisSis);
485 op = w.app(op, w.tuple({memcomb, init}));
486 op = w.app(op, acc_out);
487 op = w.app(op, accs);
488 auto [m, out] = w.app(op, w.tuple({bot_mem(), inputs}))->projs<2>();
489 return out;
490}
491
492const Def* LowerToMem::lower_pad(const App* app) {
493 // Thin bufferization: map the SSA `tensor.pad` onto the buffer-world `matrix.pad`.
494 // The loop generation lives in the matrix plugin (`%matrix.lower_aff`).
495 auto& w = new_world();
496 auto c = rewrite(app->callee())->as<App>();
497 auto [input, value] = rewrite(app->arg())->projs<2>();
498
499 auto [Tr, s_in, params] = c->uncurry_args<3>();
500 auto [T, r] = Tr->projs<2>();
501 auto [mode, lo, hi] = params->projs<3>();
502
503 // A pad of a value-world tensor (e.g. a literal): materialize it into a buffer.
504 if (!Axm::isa<buffer::Buf>(input->type())) {
505 input = materialize(app->arg()->proj(0)->type(), app->arg()->proj(0));
506 if (!Axm::isa<buffer::Buf>(input->type()))
507 return RWPhase::rewrite_imm_App(app); // not a recorded tensor type: leave it alone
508 }
509
510 auto r_l = Lit::isa<u64>(r);
511 if (!r_l) return RWPhase::rewrite_imm_App(app);
512
513 // The LOGICAL output shape `s_out#d = lo#d + s_in#d + hi#d` — the loop generation iterates it, so it
514 // must keep size-1 axes (the result type's `Buf` folds them away and cannot be used here).
515 DefVec so(*r_l);
516 for (u64 d = 0; d < *r_l; ++d)
517 so[d] = w.call(core::nat::add, DefVec{w.call(core::nat::add, DefVec{lo->proj(*r_l, d), s_in->proj(*r_l, d)}),
518 hi->proj(*r_l, d)});
519 auto s_out = w.tuple(so);
520
521 auto op = w.annex<matrix::pad>();
522 op = w.app(op, w.tuple({T, r}));
523 op = w.app(op, w.tuple({s_in, s_out, mode, lo, hi}));
524 auto [m, out] = w.app(op, w.tuple({bot_mem(), input, value}))->projs<2>();
525 return out;
526}
527
528const Def* LowerToMem::lower_concat(const App* app) {
529 // Thin bufferization: map the SSA `tensor.concat` onto the buffer-world `matrix.concat`.
530 // The loop generation lives in the matrix plugin (`%matrix.lower_aff`).
531 auto& w = new_world();
532 auto c = rewrite(app->callee())->as<App>();
533 auto arg = rewrite(app->arg());
534
535 auto [TnisR, ax, Sis] = c->uncurry_args<3>();
536 auto [T, nis, r] = TnisR->projs<3>();
537
538 auto nis_l = Lit::isa<u64>(nis);
539 auto r_l = Lit::isa<u64>(r);
540 auto ax_l = Lit::isa<u64>(ax);
541 if (!nis_l || !r_l || !ax_l) return RWPhase::rewrite_imm_App(app);
542
543 // Value-world tensor inputs (e.g. literals): materialize them into buffers. Re-tuple the projected
544 // inputs so the tuple type is re-inferred from the converted elements (see `lower_map_reduce`).
545 DefVec ins(*nis_l);
546 for (u64 i = 0; i < *nis_l; ++i) {
547 ins[i] = arg->proj(*nis_l, i);
548 if (!Axm::isa<buffer::Buf>(ins[i]->type())) {
549 auto old_in = app->arg()->proj(*nis_l, i);
550 ins[i] = materialize(old_in->type(), old_in);
551 if (!Axm::isa<buffer::Buf>(ins[i]->type()))
552 return RWPhase::rewrite_imm_App(app); // not a recorded tensor type: leave it alone
553 }
554 }
555 auto inputs = w.tuple(ins);
556
557 // The LOGICAL output shape: the summed extent along `ax` (literal, gated in `collect_tensor_types`),
558 // the shared extents elsewhere — the loop generation iterates it, so it must keep size-1 axes (the
559 // result type's `Buf` folds them away and cannot be used here).
560 u64 sum_ax = 0;
561 for (u64 i = 0; i < *nis_l; ++i) {
562 auto e = Lit::isa<u64>(Sis->proj(*nis_l, i)->proj(*r_l, *ax_l));
563 if (!e) return RWPhase::rewrite_imm_App(app);
564 sum_ax += *e;
565 }
566 DefVec so(*r_l);
567 for (u64 d = 0; d < *r_l; ++d)
568 so[d] = d == *ax_l ? w.lit_nat(sum_ax) : Sis->proj(*nis_l, 0)->proj(*r_l, d);
569 auto s_out = w.tuple(so);
570
571 auto op = w.annex<matrix::concat>();
572 op = w.app(op, w.tuple({T, nis, r}));
573 op = w.app(op, ax);
574 op = w.app(op, Sis);
575 op = w.app(op, s_out);
576 auto [m, out] = w.app(op, w.tuple({bot_mem(), inputs}))->projs<2>();
577 return out;
578}
579
580} // namespace mim::plug::tensor::phase
const Def * callee() const
Definition lam.h:276
const Def * arg() const
Definition lam.h:285
A (possibly paramterized) Array.
Definition tuple.h:121
static auto isa(const Def *def)
Definition axm.h:107
static std::tuple< const Axm *, u8, u8 > get(const Def *def)
Yields currying counter of def.
Definition axm.cpp:38
Base class for all Defs.
Definition def.h:261
bool is_set() const
Yields true if empty or the last op is set.
Definition def.cpp:308
const Def * proj(nat_t a, nat_t i) const
Similar to World::extract while assuming an arity of a, but also works on Sigmas and Arrays.
Definition def.cpp:635
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:527
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:441
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
nat_t num_vars() noexcept
Definition def.h:441
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
bool is_external() const noexcept
Definition def.h:500
nat_t num_projs() const
Yields Def::arity(), if it is a Lit, or 1 otherwise.
Definition def.cpp:628
const T * isa_imm() const
Definition def.h:521
Dbg dbg() const
Definition def.h:556
A function.
Definition lam.h:110
const Def * filter() const
Definition lam.h:122
const Pi * type() const
Definition lam.h:130
const Def * body() const
Definition lam.h:123
static std::optional< T > isa(const Def *def)
Definition def.h:878
const Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
static const Pi * isa_cn(const Def *d)
Is this a continuation - i.e. is the Pi::codom mim::Bottom?
Definition lam.h:47
const Def * dom() const
Definition lam.h:35
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
void start() override
Actual entry.
Definition phase.cpp:128
World & old_world()
Get old Defs from here.
Definition phase.h:367
virtual void push()
Definition rewrite.h:38
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:56
const Def * sigma(Defs ops)
Definition world.cpp:298
const Def * rewrite_mut_Lam(Lam *) override
void start() override
Actual entry.
const Def * rewrite_imm_App(const App *) override
const Def * type_buf(const Def *r, const Def *s, const Def *T)
The buffer type buffer.Buf (r, s, T).
Definition buffer.h:10
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_constant(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *val)
buffer.constant (r, s, T) (mem, val) ↦ [mem.M 0, buffer.Buf (r, s, T)] (every element initialised to ...
Definition buffer.h:47
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_init(const Def *r, const Def *s, const Def *T, const Def *mem, const Def *val)
buffer.init (r, s, T) (mem, val) ↦ [mem.M 0, buffer.Buf (r, s, T)] (initialised with the array value ...
Definition buffer.h:41
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
static constexpr plugin_t Plugin_Id
Definition autogen.h:10
Vector< const Def * > DefVec
Definition def.h:79
uint64_t u64
Definition types.h:27
@ Pi
Definition def.h:109
@ Lam
Definition def.h:109
@ Arr
Definition def.h:109
@ Pack
Definition def.h:109
@ Var
Definition def.h:109
@ Sigma
Definition def.h:109
@ App
Definition def.h:109