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/// `Extract` / `Insert` 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 writing into a `buffer.alloc`ed destination,
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 plus the
27/// values a `tensor.buf` annotation nominates, and only those types are rewritten to `Buf` — and only at
28/// function boundaries, never as a global type rewrite.
29class LowerToMem : public RWPhase {
30public:
31 using RWPhase::RWPhase;
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 const Def* rewrite_imm_Extract(const Extract*) override;
39 const Def* rewrite_imm_Insert(const Insert*) override;
40
41 /// The type of @p lam with its tensor-typed domain converted.
42 const Pi* conv_type(Lam* lam);
43
44 /// Splits the Extract @p d into the recorded tensor it reads and its *rewritten* index.
45 /// A size-1 axis folds out of the array type, so the index is already the folded one `buffer` expects.
46 /// Yields a null base if @p d does not read a tensor.
47 std::pair<const Def*, const Def*> peel_tensor(const Def*);
48 const Def* lower_splat(const App*);
49 const Def* lower_generate(const App*);
50 const Def* lower_broadcast(const App*);
51 const Def* lower_map_reduce(const App*);
52 const Def* lower_pad(const App*);
53 const Def* lower_concat(const App*);
54 const Def* lower_gather(const App*);
55 const Def* lower_scatter(const App*);
56
57 /// Adapts a call to a bufferized function: materializes value-world tensor arguments into buffers;
58 /// continuation arguments pass through (their domains are converted by `rewrite_mut_Lam`).
59 const Def* lower_call(const App*, Lam* old_callee);
60
61 /// Converts an argument to a converted parameter type: tensor values become buffers (via `buffer.init`
62 /// on a `⊥` memory), recursing through sigmas; anything else is `rewrite`d.
63 const Def* materialize(const Def* old_ty, const Def* old_arg);
64
65 /// The already rewritten @p val as a `buffer.Buf`, materializing a value-world tensor (e.g. a literal)
66 /// from its old-world @p old.
67 /// The result is *not* a buffer if @p old's type was not recorded as a tensor type.
68 const Def* to_buffer(const Def* val, const Def* old);
69
70 /// LowerToMem::materialize of @p old_arg as a buffer.
71 /// A non-buffer value is wrapped in a `buffer.lit` of rank @p r and shape @p shape.
72 const Def* make_buffer(const Def* old_arg, const Def* r, const Def* shape);
73
74 /// LowerToMem::to_buffer on each element of an `«n; …»` operand list, re-tupled so the tuple type is
75 /// re-inferred from the converted elements; `nullptr` if one element is not a recorded tensor type.
76 const Def* buffer_list(const Def* list, const Def* old_list, const Def* n);
77
78 /// Fills a fresh buffer of (old) array type `arr_ty` with the (already rewritten) scalar `scalar`, via
79 /// `buffer.lit`. `btensor.lower_map_reduce` turns that into a fill loop, so it never materializes as a
80 /// monolithic `mem.store` of a giant literal array (which the LLVM backend cannot digest).
81 /// Used for constant splats `‹s; c›` and scalar `tensor.broadcast`s.
82 const Def* splat_buffer(const Def* arr_ty, const Def* scalar);
83
84 /// Pre-pass: records every array type used as a tensor operand/result and the set of bufferized
85 /// functions; hard-errors on program shapes the conversion cannot handle (there is no value-semantics
86 /// fallback in the default pipeline).
87 void collect_tensor_types();
88
89 /// Builds the `buffer.Buf` type for an old tensor array type `«s; T»` (peeling the nested `Arr`s).
90 const Def* buf_of(const Def* arr_ty);
91
92 /// Converts a boundary type: tensor array types become `buffer.Buf`, recursing through (immutable) sigmas
93 /// so that grouped parameters like `[«s; T», Idx s]` are converted as well; anything else is `rewrite`d.
94 const Def* conv_boundary(const Def* t);
95
96 /// A `⊥: mem.M 0` placeholder consumed by emitted buffer operations; AddMem replaces it with the
97 /// scheduler-placed current memory.
98 /// Shared by every op whose result is a pure function of its value operands, so that genuinely equal
99 /// ops still collapse into one (e.g. a weight literal materialized at many sites).
100 const Def* bot_mem();
101
102 /// A *fresh* `mem.M 0` for one emitted buffer/btensor operation: the var of a newly minted continuation
103 /// `con fresh_mem(mem: mem.M 0)` that receives its memory once LowerToMem::wrap_fresh_mem has chained it
104 /// in front of the enclosing lam's body.
105 /// Required by every op that allocates a buffer it then writes into, for two independent reasons:
106 /// 1. Immutable Def%s are hash-consed, so two `buffer.alloc`s agreeing on `(r, s, T)` and sharing one
107 /// placeholder collapse into a single allocation - two distinct tensors would alias one buffer.
108 /// 2. AddMem is a memoizing Rewriter, so two operations sharing one argument tuple
109 /// `(⊥: mem.M 0, …)` - which happens whenever they differ only in their curried callee - are threaded
110 /// from the *same* current memory, and the resulting parallel mem chains collapse in `cps.conv`.
111 /// Mutables are never hash-consed, so the continuations' vars are distinct by construction - no
112 /// distinguishing tag required.
113 const Def* fresh_mem();
114
115 /// Chains the LowerToMem::pending_ continuations in front of the freshly rewritten @p body:
116 /// yields `mem.fresh (0, k₁)` with `k₁ ↦ mem.fresh (0, k₂)`, …, and the last one carries @p body.
117 /// AddMem resolves each request by jumping to the continuation with the scheduler-placed current memory,
118 /// and the `tt` filter beta-reduces the continuations away again as soon as that happens.
119 const Def* wrap_fresh_mem(const Def* body);
120
121 /// The fresh-memory continuations minted while the current lam's body is being rewritten.
122 fe::Vector<Lam*> pending_;
123
124 /// Per-lam memo for the ops that consume a fresh memory (see LowerToMem::rewrite).
125 DefMap<const Def*> fresh_memo_;
126
127 /// A function is bufferized iff it is external, set, and mentions a tensor type in its domain.
128 bool is_tensor_fn(Lam*) const;
129
130 /// Whether a type is a tensor or (recursively, through sigmas and continuation domains) contains one.
131 /// Never descends into `Arr` elements (so an index/shape array is not mistaken for a tensor).
132 bool mentions_tensor(const Def*) const;
133
134 DefSet tensor_ty_;
135
136 /// Whether the program contains any (fully applied) tensor-plugin operation.
137 /// Ops without any bufferized function boundary still lower: their value-world operands are
138 /// materialized into buffers (see `materialize`).
139 bool ops_seen_ = false;
140
141 /// Old-world functions that get bufferized (external, signature mentions a tensor).
142 /// Call sites of these functions must be adapted (see `lower_call`).
143 LamSet tensor_fns_;
144
145 /// Lams passed inside a tensor op's curry chain (combiners, affine index maps): they stay element-level —
146 /// they receive element values, never buffers — even when a parameter type incidentally collides with a
147 /// tensor type (pure type-based role tracking aliases, e.g. an `(x y: I32)` group *is* `«2; I32»`).
148 LamSet op_args_;
149};
150
151} // namespace mim::plug::tensor::phase
Base class for all Defs.
Definition def.h:289
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:360
Creates a new Tuple / Pack by inserting Insert::value at position Insert::index into Insert::tuple.
Definition tuple.h:385
A function.
Definition lam.h:91
A dependent function type.
Definition lam.h:14
RWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:334
Bufferizes the low-level tensor axioms onto the shared buffer layer.
const Def * rewrite_imm_Insert(const Insert *) override
const Def * rewrite_mut_Lam(Lam *) override
const Def * rewrite(const Def *) override
RWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:334
void start() override
Actual entry.
const Def * rewrite_imm_App(const App *) override
const Def * rewrite_imm_Extract(const Extract *) override
GIDSet< Lam * > LamSet
Definition lam.h:211
GIDMap< const Def *, To > DefMap
Definition def.h:93
GIDSet< const Def * > DefSet
Definition def.h:94