MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
normalizers.cpp
Go to the documentation of this file.
1#include <mim/def.h>
2#include <mim/plugin.h>
3#include <mim/tuple.h>
4#include <mim/world.h>
5
8#include <mim/plug/cps/cps.h>
10#include <mim/plug/vec/vec.h>
11
13
14namespace mim::plug::tensor {
15
16// There's no good reason keeping 1s around for get/set indices.
17// So this just skips relevant dimensions in the index and shape, and reduces the rank accordingly.
18std::tuple<u64, const Def*, const Def*> fold_shape_and_index(const Def* shape, const Def* index) {
19 auto& w = shape->world();
20
21 DefVec dims;
22 DefVec index_dims;
23 auto r = shape->num_projs();
24 for (size_t i = 0, e = r; i != e; ++i) {
25 auto dim = shape->proj(r, i);
26 if (auto dim_lit = Lit::isa<u64>(dim))
27 if (dim_lit == 1) continue;
28
29 dims.push_back(dim);
30 index_dims.push_back(index->proj(r, i));
31 }
32
33 assert(dims.size() == index_dims.size());
34 return std::make_tuple(dims.size(), w.tuple(dims), w.tuple(index_dims));
35}
36
37const Def* normalize_get(const Def*, const Def* c, const Def* arg) {
38 auto& w = c->world();
39
40 auto [index, arr] = arg->projs<2>();
41 auto callee = c->as<App>();
42 auto [T, r, s] = callee->args<3>();
43
44 w.log().d("get: arr = {}: {}, index = {}, T = {}, r = {}, s = {}", arr, arr->type(), index, T, r, s);
45
46 if (r->isa<Lit>()) {
47 auto [new_r, new_s, new_index] = fold_shape_and_index(s, index);
48 w.log().d("folded shape/index: r = {}, s = {}, index = {}", new_r, new_s, new_index);
49 if (new_r == 0) return arr;
50 if (new_s != s || new_index != index) return op_get(T, w.lit_nat(new_r), new_s, arr, new_index);
51 }
52
53 if (Axm::isa<tensor::set>(arr)) {
54 w.log().d("get after set: try bypass");
55 auto set = arr->as<App>();
56 auto [target_index, _, x] = set->args<3>();
57 if (target_index == index) {
58 w.log().d("bypassed");
59 return x;
60 }
61 }
62 if (Axm::isa<tensor::get>(arr)) {
63 w.log().d("get after get: try bypass");
64 auto get = arr->as<App>();
65 auto [outer_index, outer_arr] = get->args<2>();
66 auto [o_T, o_r, o_s] = get->callee()->as<App>()->args<3>();
67 w.log().d("outer: arr = {}: {}, index = {}, T = {}, r = {}, s = {}", outer_arr, outer_arr->type(), outer_index,
68 o_T, o_r, o_s);
69
70 auto new_r = w.call(core::nat::add, DefVec{r, o_r});
71 auto new_s = w.call<tuple::cat>(DefVec{o_s, s});
72 auto new_index = w.call<tuple::cat>(DefVec{outer_index, index});
73
74 return op_get(T, new_r, new_s, outer_arr, new_index);
75 }
76
77 if (auto rep = Axm::isa<tensor::repeat>(arr)) {
78 // get after repeat: read the input directly at `idx mod s_in` per axis. Decidable when an axis
79 // passes through (`s_in#d == s_out#d`), is size-1 (read at 0), or has a literal extent and a
80 // literal index component (fold the mod); otherwise keep the repeat (the lowering paths emit the
81 // runtime mod).
82 w.log().d("get after repeat: try bypass");
83 auto input = rep->arg();
84 auto [Tr, s_in, s_out] = rep->callee()->as<App>()->uncurry_args<3>();
85 if (auto r_l = Lit::isa<u64>(Tr->proj(2, 1))) {
86 DefVec new_index(*r_l);
87 for (u64 d = 0; d < *r_l; ++d) {
88 auto in_d = s_in->proj(*r_l, d);
89 auto idx_d = index->proj(*r_l, d);
90 if (in_d == s_out->proj(*r_l, d))
91 new_index[d] = idx_d;
92 else if (auto l = Lit::isa<u64>(in_d); l && *l == 1)
93 new_index[d] = w.lit_idx(1, 0);
94 else if (auto e = Lit::isa<u64>(in_d), i = Lit::isa<u64>(idx_d); e && i)
95 new_index[d] = w.lit_idx(*e, *i % *e);
96 else
97 return nullptr;
98 }
99 w.log().d("bypassed");
100 return op_get(T, Tr->proj(2, 1), s_in, input, w.tuple(new_index));
101 }
102 }
103
104 if (auto bc = Axm::isa<tensor::broadcast>(arr)) {
105 // get after broadcast: read the input directly. Per axis, the broadcast either passes the index
106 // through (`s_in#d == s_out#d`) or reads a size-1 input axis at 0; if some axis is neither
107 // decidably equal nor literal 1, keep the broadcast.
108 w.log().d("get after broadcast: try bypass");
109 auto [s_in, s_out, input] = bc->args<3>();
110 auto [b_T, b_r] = bc->callee()->as<App>()->args<2>();
111 if (auto r_l = Lit::isa<u64>(b_r)) {
112 DefVec new_index(*r_l);
113 for (u64 d = 0; d < *r_l; ++d) {
114 auto in_d = s_in->proj(*r_l, d);
115 if (in_d == s_out->proj(*r_l, d))
116 new_index[d] = index->proj(*r_l, d);
117 else if (auto l = Lit::isa<u64>(in_d); l && *l == 1)
118 new_index[d] = w.lit_idx(1, 0);
119 else
120 return nullptr;
121 }
122 w.log().d("bypassed");
123 return op_get(T, b_r, s_in, input, w.tuple(new_index));
124 }
125 }
126
127 return nullptr;
128}
129
130const Def* normalize_set(const Def*, const Def* c, const Def* arg) {
131 auto& w = c->world();
132
133 auto [index, arr, x] = arg->projs<3>();
134 w.log().d("set: arr = {}: {}, index = {}, x = {}: {}", arr, arr->type(), index, x, x->type());
135
136 auto callee = c->as<App>();
137 auto [T, r, s] = callee->args<3>();
138
139 if (r->isa<Lit>()) {
140 auto [new_r, new_s, new_index] = fold_shape_and_index(s, index);
141 w.log().d("folded shape/index: r = {}, s = {}, index = {}", new_r, new_s, new_index);
142 if (new_r == 0) return x;
143 if (new_s != s || new_index != index) return op_set(T, w.lit_nat(new_r), new_s, arr, new_index, x);
144 }
145
146 if (Axm::isa<tensor::get>(x)) {
147 w.log().d("set after get: try bypass");
148 auto get = x->as<App>();
149 auto [inner_index, inner_arr] = get->args<2>();
150 if (inner_arr == arr && inner_index == index) {
151 w.log().d("bypassed");
152 return inner_arr;
153 }
154 }
155
156 if (Axm::isa<tensor::set>(x)) {
157 w.log().d("set after set: try bypass");
158 auto inner_set = x->as<App>();
159 auto [inner_index, inner_arr, inner_x] = inner_set->args<3>();
160 auto [i_T, i_r, i_s] = inner_set->callee()->as<App>()->args<3>();
161
162 w.log().d("inner: arr = {}: {}, index = {}, x = {}, T = {}, r = {}, s = {}", inner_arr, inner_arr->type(),
163 inner_index, inner_x, i_T, i_r, i_s);
164
165 if (auto inner_get = Axm::isa<tensor::get>(inner_arr)) {
166 auto [g_index, g_arr] = inner_get->args<2>();
167 if (g_arr == arr && g_index == index) {
168 auto new_r = w.call(core::nat::add, DefVec{r, i_r});
169 auto new_s = w.call<tuple::cat>(DefVec{s, i_s});
170 auto new_index = w.call<tuple::cat>(DefVec{index, inner_index});
171
172 return op_set(i_T, new_r, new_s, arr, new_index, inner_x);
173 }
174 }
175 w.log().d("no bypass: inner arr is not get(arr, index)");
176 }
177 w.log().d("no normalization applicable");
178 return nullptr;
179}
180
181const Def* normalize_broadcast(const Def*, const Def* c, const Def* arg) {
182 auto& w = c->world();
183
184 auto [s_in, s_out, input] = arg->projs<3>();
185 auto callee = c->as<App>();
186 auto [T, r] = callee->args<2>();
187 w.log().d("broadcast: input = {}: {}, T = {}, r = {}, s_in = {}, s_out = {}", input, input->type(), T, r, s_in,
188 s_out);
189
190 if (s_in == s_out) return input;
191
192 auto r_nat = Lit::isa<u64>(r);
193 if (!r_nat) return nullptr;
194 if (r_nat == 0) return input;
195
196 return nullptr;
197}
198
199const Def* normalize_broadcast_in_dim(const Def*, const Def*, const Def*) { return nullptr; }
200
201const Def* normalize_repeat(const Def*, const Def* c, const Def* arg) {
202 // Identity repeat: if the input and output shapes agree, the repeat is a no-op.
203 auto [Tr, s_in, s_out] = c->as<App>()->uncurry_args<3>();
204 if (s_in == s_out) return arg;
205 return nullptr;
206}
207
208const Def* normalize_reshape(const Def*, const Def* c, const Def* arg) {
209 // Identity reshape: if the input and output shapes agree, the reshape is a no-op.
210 auto [Trr, s_in, s_out] = c->as<App>()->uncurry_args<3>();
211 if (s_in == s_out) return arg;
212 return nullptr;
213}
214
215const Def* normalize_slice(const Def*, const Def* c, const Def* arg) {
216 // Identity slice: every axis starts at 0 with step 1 and keeps its full extent (s_out == s_in) -> the input itself.
217 auto [Tr, s_in, params] = c->as<App>()->uncurry_args<3>();
218 auto [start, step, s_out] = params->projs<3>();
219 if (s_out != s_in) return nullptr;
220 auto r = Lit::isa<u64>(Tr->proj(2, 1));
221 if (!r) return nullptr;
222 for (u64 d = 0; d != *r; ++d) {
223 auto st = Lit::isa<u64>(start->proj(*r, d));
224 auto sp = Lit::isa<u64>(step->proj(*r, d));
225 if (!st || *st != 0 || !sp || *sp != 1) return nullptr;
226 }
227 return arg;
228}
229
230const Def* normalize_flip(const Def*, const Def*, const Def*) { return nullptr; }
231
232const Def* normalize_pad(const Def*, const Def* c, const Def* arg) {
233 // Identity pad: every axis has lo == hi == 0 (so s_out == s_in) -> the input itself (the fill value is irrelevant).
234 auto [Tr, s_in, params] = c->as<App>()->uncurry_args<3>();
235 auto [mode, lo, hi] = params->projs<3>();
236 auto r = Lit::isa<u64>(Tr->proj(2, 1));
237 if (!r) return nullptr;
238 for (u64 d = 0; d != *r; ++d) {
239 auto l = Lit::isa<u64>(lo->proj(*r, d));
240 auto h = Lit::isa<u64>(hi->proj(*r, d));
241 if (!l || *l != 0 || !h || *h != 0) return nullptr;
242 }
243 return arg->proj(2, 0); // input (arg = (input, value))
244}
245
246const Def* normalize_concat(const Def*, const Def*, const Def*) { return nullptr; }
247
248const Def* normalize_if_static(const Def*, const Def*, const Def* arg) {
249 // `tensor.if_static (k, s, d)` picks `s` once `k` has folded to a literal; a still-symbolic `k`
250 // keeps the App stuck, and the tensor lowerings residualize it to `d` (by lowering time,
251 // undecided means runtime).
252 auto [k, s, d] = arg->projs<3>();
253 if (Lit::isa(k)) return s;
254 return nullptr;
255}
256
257const Def* normalize_fastest_axis(const Def*, const Def*, const Def* arg) {
258 // `tensor.fastest_axis (r, t)` reflects which axis of `t` is the fastest-varying (unit-stride)
259 // axis of the tensor actually read once `fuse_tensor`'s read-through has absorbed a pure
260 // re-indexed read behind `t`: without one, `t`'s own last axis; behind one, found by evaluating
261 // the read's access map on distinct `affine.lit` markers — normalization folds the map's
262 // extracts over the marker tuple, and a last component that does not fold back to a marker
263 // (reshape arithmetic) stays unknown. Unknown answers the sentinel `r`.
264 auto& w = arg->world();
265 auto [r, t] = arg->projs<2>();
266 auto r_l = Lit::isa<u64>(r);
267 if (!r_l || *r_l == 0) return r;
268 auto pr = is_pure_read(t);
269 if (!pr) return w.lit_nat(*r_l - 1);
270 // One level only: a source that is itself absorbed would need the composed analysis.
271 if (is_pure_read(pr->src) || Axm::isa<tensor::broadcast>(pr->src)) return r;
272 auto r_src = Lit::isa<u64>(pr->map->type()->as<Pi>()->codom()->arity());
273 if (!r_src || *r_src == 0) return r;
274 // `r` is not type-coupled to `t`; a mismatched rank must answer unknown, not break the app below.
275 auto r_dom = Lit::isa<u64>(pr->map->type()->as<Pi>()->dom()->arity());
276 if (!r_dom || *r_dom != *r_l) return r;
277 // Markers start at 1: 0 is what a broadcast map's `o#d · 0` folds to, so it must not be one.
278 auto markers = DefVec(*r_l, [&](size_t i) { return w.call<affine::lit>(w.lit_nat(i + 1)); });
279 auto last = w.app(pr->map, w.tuple(markers))->proj(*r_src, *r_src - 1);
280 auto c = Axm::isa<affine::lit>(last);
281 if (!c) return r;
282 auto v = Lit::isa<u64>(c->arg());
283 if (!v || *v < 1 || *v > *r_l) return r;
284 return w.lit_nat(*v - 1);
285}
286
287const Def* normalize_shape(const Def*, const Def* c, const Def* arg) {
288 // `tensor.shape r arr` reads the shape off `arr`'s (nested array) type by peeling `r` levels.
289 auto& w = c->world();
290 auto r = Lit::isa<u64>(c->as<App>()->arg()); // the explicit rank `r`
291 if (!r) return nullptr;
292
293 DefVec dims;
294 auto ty = arg->type();
295 for (u64 i = 0; i != *r; ++i)
296 if (auto a = ty->isa<Seq>()) {
297 dims.emplace_back(a->arity());
298 ty = a->body();
299 } else
300 return nullptr; // `arr` is not (statically) a rank-`r` array
301 return w.tuple(dims); // the per-axis sizes; for a rectangular tensor each `arity()` is a plain Nat
302}
303
305
306} // namespace mim::plug::tensor
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
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:623
World & world() const noexcept
Definition def.h:1097
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
const Def * arity() const
Number of elements available to Extract / Insert (may be dynamic).
Definition def.cpp:592
static std::optional< T > isa(const Def *def)
Definition def.h:937
A dependent function type.
Definition lam.h:14
const Def * dom() const
Definition lam.h:35
const Def * codom() const
Definition lam.h:36
Base class for Arr and Pack.
Definition tuple.h:75
The tensor Plugin
Definition constraints.h:5
const Def * normalize_broadcast(const Def *, const Def *c, const Def *arg)
const Def * normalize_if_static(const Def *, const Def *, const Def *arg)
const Def * normalize_slice(const Def *, const Def *c, const Def *arg)
const Def * normalize_repeat(const Def *, const Def *c, const Def *arg)
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 * normalize_pad(const Def *, const Def *c, const Def *arg)
const Def * normalize_get(const Def *, const Def *c, const Def *arg)
const Def * normalize_broadcast_in_dim(const Def *, const Def *, const Def *)
std::optional< PureRead > is_pure_read(const Def *value)
If value is a pure re-indexed read — a copy-combiner map_reduce without reduction loops that writes i...
Definition tensor.h:43
std::tuple< u64, const Def *, const Def * > fold_shape_and_index(const Def *shape, const Def *index)
const Def * normalize_set(const Def *, const Def *c, const Def *arg)
const Def * normalize_concat(const Def *, const Def *, const Def *)
const Def * normalize_fastest_axis(const Def *, const Def *, const Def *arg)
const Def * normalize_reshape(const Def *, const Def *c, const Def *arg)
const Def * normalize_shape(const Def *, const Def *c, const Def *arg)
const Def * normalize_flip(const Def *, const Def *, const Def *)
const Def * op_get(const Def *T, const Def *r, const Def *s, const Def *arr, const Def *index)
Definition tensor.h:64
fe::Vector< const Def * > DefVec
Definition def.h:93
uint64_t u64
Definition types.h:27
#define MIM_tensor_NORMALIZER_IMPL
Definition autogen.h:454