MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_to_mem.h
Go to the documentation of this file.
1#pragma once
2
3#include <mim/def.h>
4#include <mim/lam.h>
5#include <mim/phase.h>
6
8
9/// Bufferizes the low-level tensor axioms onto the shared `buffer` layer.
10/// `get` / `set` become `buffer.read` / `buffer.write`, `generate` becomes an allocating fill loop,
11/// `map_reduce` / `broadcast` / `pad` / `concat` /
12/// `gather` / `scatter` become their buffer-world `btensor.*` counterparts,
13/// and tensor array values `«s; T»` become `buffer.Buf (r, s, T)` handles.
14/// Afterwards `buffer.lower_ptr` lowers the buffer layer to `mem.Ptr` + `mem.lea` / `mem.load` / `mem.store`.
15///
16/// This phase is *conversion-only*: it rewrites types and operations but does not thread the `mem.M`
17/// memory monad itself. Emitted buffer operations consume a `⊥: mem.M 0` placeholder (or a short local
18/// chain rooted in a LowerToMem::fresh_mem continuation's var), and the SSA value dependencies keep them
19/// anchored and ordered. The `mem.add_mem` phase
20/// (mim::plug::mem::phase::AddMem), scheduled right after this one in the pipeline, then mem-extends all
21/// continuations and rewires every memory operand to the scheduler-placed current memory — handling returns,
22/// error continuations, join points, branch arms, and interleaving with a caller's own memory operations
23/// uniformly.
24///
25/// Which array types denote tensors (as opposed to index/shape arrays that share the `Arr` structure) is
26/// determined by *role*: a pre-pass collects the array operand/result types of the tensor operations, and only
27/// those types are rewritten to `Buf` — and only at function boundaries, never as a global type rewrite.
28class LowerToMem : public RWPhase {
29public:
32
33private:
34 void start() override;
35 const Def* rewrite(const Def*) override;
36 const Def* rewrite_mut_Lam(Lam*) override;
37 const Def* rewrite_imm_App(const App*) override;
38
39 /// The conversion part of LowerToMem::rewrite_mut_Lam (boundary conversion, local continuations, or the
40 /// generic RWPhase rewrite); the override itself only scopes the fresh-memory bookkeeping around it.
41 const Def* conv_mut_Lam(Lam*);
42
43 const Def* lower_get(const App*);
44 const Def* lower_set(const App*);
45 const Def* lower_splat(const App*);
46 const Def* lower_generate(const App*);
47 const Def* lower_broadcast(const App*);
48 const Def* lower_map_reduce(const App*);
49 const Def* lower_pad(const App*);
50 const Def* lower_concat(const App*);
51 const Def* lower_gather(const App*);
52 const Def* lower_scatter(const App*);
53
54 /// Adapts a call to a bufferized function: materializes value-world tensor arguments into buffers;
55 /// continuation arguments pass through (their domains are converted by `rewrite_mut_Lam`).
56 const Def* lower_call(const App*, Lam* old_callee);
57
58 /// Converts an argument to a converted parameter type: tensor values become buffers (via `buffer.init`
59 /// on a `⊥` memory), recursing through sigmas; anything else is `rewrite`d.
60 const Def* materialize(const Def* old_ty, const Def* old_arg);
61
62 /// The already rewritten @p val as a `buffer.Buf`, materializing a value-world tensor (e.g. a literal)
63 /// from its old-world @p old.
64 /// The result is *not* a buffer if @p old's type was not recorded as a tensor type.
65 const Def* to_buffer(const Def* val, const Def* old);
66
67 /// LowerToMem::to_buffer on each element of an `«n; …»` operand list, re-tupled so the tuple type is
68 /// re-inferred from the converted elements; `nullptr` if one element is not a recorded tensor type.
69 const Def* buffer_list(const Def* list, const Def* old_list, const Def* n);
70
71 /// Fills a fresh buffer of (old) array type `arr_ty` with the (already rewritten) scalar `scalar`, via
72 /// `buffer.lit`. `btensor.lower_map_reduce` turns that into a fill loop, so it never materializes as a
73 /// monolithic `mem.store` of a giant literal array (which the LLVM backend cannot digest).
74 /// Used for constant splats `‹s; c›` and scalar `tensor.broadcast`s.
75 const Def* splat_buffer(const Def* arr_ty, const Def* scalar);
76
77 /// Buffer-reuse policy. `false` (the initial *always-allocate-and-copy* policy) is always sound.
78 /// A future liveness-based policy may return `true` to write into the source buffer in place.
79 bool reuse_in_place(const App*) const { return false; }
80
81 /// Pre-pass: records every array type used as a tensor operand/result and the set of bufferized
82 /// functions; hard-errors on program shapes the conversion cannot handle (there is no value-semantics
83 /// fallback in the default pipeline).
84 void collect_tensor_types();
85
86 /// Builds the `buffer.Buf` type for an old tensor array type `«s; T»` (peeling the nested `Arr`s).
87 const Def* buf_of(const Def* arr_ty);
88
89 /// Converts a boundary type: tensor array types become `buffer.Buf`, recursing through (immutable) sigmas
90 /// so that grouped parameters like `[«s; T», Idx s]` are converted as well; anything else is `rewrite`d.
91 const Def* conv_boundary(const Def* t);
92
93 /// Drops, from the (unfolded) index `idx`, the components of size-1 dimensions of `shape`.
94 /// MimIR folds size-1 dimensions out of array/buffer types (`«3,1;T»` ≡ `«3;T»`), so an index addressing a
95 /// buffer must match the folded shape.
96 const Def* fold_index(const Def* shape, const Def* idx);
97
98 /// A `⊥: mem.M 0` placeholder consumed by emitted buffer operations; AddMem replaces it with the
99 /// scheduler-placed current memory.
100 /// Shared by every op whose result is a pure function of its value operands, so that genuinely equal
101 /// ops still collapse into one (e.g. a weight literal materialized at many sites).
102 const Def* bot_mem();
103
104 /// A *fresh* `mem.M 0` for one emitted buffer/btensor operation: the var of a newly minted continuation
105 /// `con fresh_mem(mem: mem.M 0)` that receives its memory once LowerToMem::wrap_fresh_mem has chained it
106 /// in front of the enclosing lam's body.
107 /// Required by every op that allocates a buffer it then writes into, for two independent reasons:
108 /// 1. Immutable Def%s are hash-consed, so two `buffer.alloc`s agreeing on `(r, s, T)` and sharing one
109 /// placeholder collapse into a single allocation - two distinct tensors would alias one buffer.
110 /// 2. AddMem is a memoizing Rewriter, so two operations sharing one argument tuple
111 /// `(⊥: mem.M 0, …)` - which happens whenever they differ only in their curried callee - are threaded
112 /// from the *same* current memory, and the resulting parallel mem chains collapse in `cps.conv`.
113 /// Mutables are never hash-consed, so the continuations' vars are distinct by construction - no
114 /// distinguishing tag required.
115 const Def* fresh_mem();
116
117 /// Chains the LowerToMem::pending_ continuations in front of @p new_lam's freshly rewritten body:
118 /// `new_lam ↦ mem.fresh (0, k₁)`, `k₁ ↦ mem.fresh (0, k₂)`, …, and the last one carries the body.
119 /// AddMem resolves each request by jumping to the continuation with the scheduler-placed current memory,
120 /// and the `tt` filter beta-reduces the continuations away again as soon as that happens.
121 void wrap_fresh_mem(Lam* new_lam);
122
123 /// The fresh-memory continuations minted while the current lam's body is being rewritten.
124 fe::Vector<Lam*> pending_;
125
126 /// Per-lam memo for the ops that consume a fresh memory (see LowerToMem::rewrite).
127 DefMap<const Def*> fresh_memo_;
128
129 /// A function is bufferized iff it is external, set, and mentions a tensor type in its domain.
130 bool is_tensor_fn(Lam*) const;
131
132 /// Whether a type is a tensor or (recursively, through sigmas and continuation domains) contains one.
133 /// Never descends into `Arr` elements (so an index/shape array is not mistaken for a tensor).
134 bool mentions_tensor(const Def*) const;
135
136 DefSet tensor_ty_;
137
138 /// Whether the program contains any (fully applied) tensor-plugin operation.
139 /// Ops without any bufferized function boundary still lower: their value-world operands are
140 /// materialized into buffers (see `materialize`).
141 bool ops_seen_ = false;
142
143 /// Old-world functions that get bufferized (external, signature mentions a tensor).
144 /// Call sites of these functions must be adapted (see `lower_call`).
145 LamSet tensor_fns_;
146
147 /// Lams passed inside a tensor op's curry chain (combiners, affine index maps): they stay element-level —
148 /// they receive element values, never buffers — even when a parameter type incidentally collides with a
149 /// tensor type (pure type-based role tracking aliases, e.g. an `(x y: I32)` group *is* `«2; I32»`).
150 LamSet op_args_;
151};
152
153} // namespace mim::plug::tensor::phase
Base class for all Defs.
Definition def.h:273
A function.
Definition lam.h:113
flags_t annex() const
Definition phase.h:81
RWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:431
World & world()=delete
Hides both and forbids direct access.
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
const Def * rewrite_mut_Lam(Lam *) override
const Def * rewrite(const Def *) override
void start() override
Actual entry.
const Def * rewrite_imm_App(const App *) override
LowerToMem(World &world, flags_t annex)
GIDSet< Lam * > LamSet
Definition lam.h:220
u64 flags_t
Definition types.h:39
GIDMap< const Def *, To > DefMap
Definition def.h:88
GIDSet< const Def * > DefSet
Definition def.h:89