MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
normalizers.cpp
Go to the documentation of this file.
3#include <mim/plug/cps/cps.h>
5#include <mim/plug/vec/vec.h>
6
7#include "mim/def.h"
8#include "mim/plugin.h"
9#include "mim/tuple.h"
10#include "mim/world.h"
11
12#include "mim/util/sets.h"
13
15
16namespace mim::plug::tensor {
17
18// There's no good reason keeping 1s around for get/set indices.
19// So this just skips relevant dimensions in the index and shape, and reduces the rank accordingly.
20std::tuple<u64, const Def*, const Def*> fold_shape_and_index(const Def* shape, const Def* index) {
21 auto& w = shape->world();
22
23 DefVec dims;
24 DefVec index_dims;
25 auto r = shape->num_projs();
26 for (size_t i = 0, e = r; i != e; ++i) {
27 auto dim = shape->proj(r, i);
28 if (auto dim_lit = Lit::isa<u64>(dim))
29 if (dim_lit == 1) continue;
30
31 dims.push_back(dim);
32 index_dims.push_back(index->proj(r, i));
33 }
34
35 assert(dims.size() == index_dims.size());
36 return std::make_tuple(dims.size(), w.tuple(dims), w.tuple(index_dims));
37}
38
39const Def* normalize_get(const Def*, const Def* c, const Def* arg) {
40 auto& w = c->world();
41
42 auto [arr, index] = arg->projs<2>();
43 auto callee = c->as<App>();
44 auto [T, r, s] = callee->args<3>();
45
46 w.DLOG("normalize_get");
47 w.DLOG(" arr = {} : {}", arr, arr->type());
48 w.DLOG(" index = {} : {}", index, index->type());
49 w.DLOG(" T = {} : {}", T, T->type());
50 w.DLOG(" r = {} : {}", r, r->type());
51 w.DLOG(" s = {} : {}", s, s->type());
52
53 if (r->isa<Lit>()) {
54 auto [new_r, new_s, new_index] = fold_shape_and_index(s, index);
55 w.DLOG(" new_index = {} : {}", new_index, new_index->type());
56 w.DLOG(" new_s = {} : {}", new_s, new_s->type());
57 w.DLOG(" new_r = {} : {}", w.lit_nat(new_r), w.lit_nat(new_r)->type());
58 if (new_r == 0) return arr;
59 if (new_s != s || new_index != index) return op_get(T, w.lit_nat(new_r), new_s, arr, new_index);
60 }
61
62 if (Axm::isa<tensor::set>(arr)) {
63 w.DLOG("get after set, try to bypass");
64 auto set = arr->as<App>();
65 auto [_, target_index, x] = set->args<3>();
66 if (target_index == index) {
67 w.DLOG("bypass successful");
68 return x;
69 }
70 }
71 if (Axm::isa<tensor::get>(arr)) {
72 w.DLOG("get after get, try to bypass");
73 auto get = arr->as<App>();
74 auto [outer_arr, outer_index] = get->args<2>();
75 auto [o_T, o_r, o_s] = get->callee()->as<App>()->args<3>();
76 w.DLOG(" outer_arr = {} : {}", outer_arr, outer_arr->type());
77 w.DLOG(" outer_index = {} : {}", outer_index, outer_index->type());
78 w.DLOG(" o_T = {} : {}", o_T, o_T->type());
79 w.DLOG(" o_r = {} : {}", o_r, o_r->type());
80 w.DLOG(" o_s = {} : {}", o_s, o_s->type());
81
82 auto new_r = w.call(core::nat::add, DefVec{r, o_r});
83 auto new_s = w.call<tuple::cat>(DefVec{o_s, s});
84 auto new_index = w.call<tuple::cat>(DefVec{outer_index, index});
85
86 return op_get(T, new_r, new_s, outer_arr, new_index);
87 }
88
89 if (auto rep = Axm::isa<tensor::repeat>(arr)) {
90 // get after repeat: read the input directly at `idx mod s_in` per axis. Decidable when an axis
91 // passes through (`s_in#d == s_out#d`), is size-1 (read at 0), or has a literal extent and a
92 // literal index component (fold the mod); otherwise keep the repeat (the lowering paths emit the
93 // runtime mod).
94 w.DLOG("get after repeat, try to bypass");
95 auto input = rep->arg();
96 auto [Tr, s_in, s_out] = rep->callee()->as<App>()->uncurry_args<3>();
97 if (auto r_l = Lit::isa<u64>(Tr->proj(2, 1))) {
98 DefVec new_index(*r_l);
99 for (u64 d = 0; d < *r_l; ++d) {
100 auto in_d = s_in->proj(*r_l, d);
101 auto idx_d = index->proj(*r_l, d);
102 if (in_d == s_out->proj(*r_l, d)) {
103 new_index[d] = idx_d;
104 } else if (auto l = Lit::isa<u64>(in_d); l && *l == 1) {
105 new_index[d] = w.lit_idx(1, 0);
106 } else if (auto e = Lit::isa<u64>(in_d), i = Lit::isa<u64>(idx_d); e && i) {
107 new_index[d] = w.lit_idx(*e, *i % *e);
108 } else {
109 return nullptr;
110 }
111 }
112 w.DLOG("bypass successful");
113 return op_get(T, Tr->proj(2, 1), s_in, input, w.tuple(new_index));
114 }
115 }
116
117 if (auto bc = Axm::isa<tensor::broadcast>(arr)) {
118 // get after broadcast: read the input directly. Per axis, the broadcast either passes the index
119 // through (`s_in#d == s_out#d`) or reads a size-1 input axis at 0; if some axis is neither
120 // decidably equal nor literal 1, keep the broadcast.
121 w.DLOG("get after broadcast, try to bypass");
122 auto [s_in, s_out, input] = bc->args<3>();
123 auto [b_T, b_r] = bc->callee()->as<App>()->args<2>();
124 if (auto r_l = Lit::isa<u64>(b_r)) {
125 DefVec new_index(*r_l);
126 for (u64 d = 0; d < *r_l; ++d) {
127 auto in_d = s_in->proj(*r_l, d);
128 if (in_d == s_out->proj(*r_l, d))
129 new_index[d] = index->proj(*r_l, d);
130 else if (auto l = Lit::isa<u64>(in_d); l && *l == 1)
131 new_index[d] = w.lit_idx(1, 0);
132 else
133 return nullptr;
134 }
135 w.DLOG("bypass successful");
136 return op_get(T, b_r, s_in, input, w.tuple(new_index));
137 }
138 }
139
140 return nullptr;
141}
142
143const Def* normalize_set(const Def*, const Def* c, const Def* arg) {
144 auto& w = c->world();
145
146 auto [arr, index, x] = arg->projs<3>();
147 w.DLOG("normalize_set");
148 w.DLOG(" arr = {} : {}", arr, arr->type());
149 w.DLOG(" index = {} : {}", index, index->type());
150 w.DLOG(" x = {} : {}", x, x->type());
151
152 auto callee = c->as<App>();
153 auto [T, r, s] = callee->args<3>();
154
155 if (r->isa<Lit>()) {
156 auto [new_r, new_s, new_index] = fold_shape_and_index(s, index);
157 w.DLOG(" new_index = {} : {}", new_index, new_index->type());
158 w.DLOG(" new_s = {} : {}", new_s, new_s->type());
159 w.DLOG(" new_r = {} : {}", w.lit_nat(new_r), w.lit_nat(new_r)->type());
160 if (new_r == 0) return x;
161 if (new_s != s || new_index != index) return op_set(T, w.lit_nat(new_r), new_s, arr, new_index, x);
162 }
163
164 if (Axm::isa<tensor::get>(x)) {
165 w.DLOG("set after get, try to bypass");
166 auto get = x->as<App>();
167 auto [inner_arr, inner_index] = get->args<2>();
168 if (inner_arr == arr && inner_index == index) {
169 w.DLOG("bypass successful");
170 return inner_arr;
171 }
172 }
173
174 if (Axm::isa<tensor::set>(x)) {
175 w.DLOG("set after set, try to bypass");
176 auto inner_set = x->as<App>();
177 auto [inner_arr, inner_index, inner_x] = inner_set->args<3>();
178 auto [i_T, i_r, i_s] = inner_set->callee()->as<App>()->args<3>();
179
180 w.DLOG(" inner_arr = {} : {}", inner_arr, inner_arr->type());
181 w.DLOG(" inner_index = {} : {}", inner_index, inner_index->type());
182 w.DLOG(" inner_x = {} : {}", inner_x, inner_x->type());
183 w.DLOG(" i_T = {} : {}", i_T, i_T->type());
184 w.DLOG(" i_r = {} : {}", i_r, i_r->type());
185 w.DLOG(" i_s = {} : {}", i_s, i_s->type());
186
187 if (auto inner_get = Axm::isa<tensor::get>(inner_arr)) {
188 auto [g_arr, g_index] = inner_get->args<2>();
189 if (g_arr == arr && g_index == index) {
190 auto new_r = w.call(core::nat::add, DefVec{r, i_r});
191 auto new_s = w.call<tuple::cat>(DefVec{s, i_s});
192 auto new_index = w.call<tuple::cat>(DefVec{index, inner_index});
193
194 return op_set(i_T, new_r, new_s, arr, new_index, inner_x);
195 }
196 }
197 w.DLOG("set after set bypass not applicable: inner_arr is not get(arr, index)");
198 }
199 w.DLOG("no normalization applicable");
200 return nullptr;
201}
202
203const Def* normalize_broadcast(const Def*, const Def* c, const Def* arg) {
204 auto& w = c->world();
205
206 auto [s_in, s_out, input] = arg->projs<3>();
207 auto callee = c->as<App>();
208 auto [T, r] = callee->args<2>();
209 w.DLOG("normalize_broadcast");
210 w.DLOG(" s_out = {} : {}", s_out, s_out->type());
211 w.DLOG(" input = {} : {}", input, input->type());
212 w.DLOG(" T = {} : {}", T, T->type());
213 w.DLOG(" r = {} : {}", r, r->type());
214 w.DLOG(" s_in = {} : {}", s_in, s_in->type());
215
216 if (s_in == s_out) return input;
217
218 auto r_nat = Lit::isa<u64>(r);
219 if (!r_nat) return nullptr;
220 if (r_nat == 0) return input;
221
222 return nullptr;
223}
224
225const Def* normalize_broadcast_in_dim(const Def*, const Def*, const Def*) { return nullptr; }
226
227const Def* normalize_map_reduce(const Def*, const Def*, const Def*) {
228 // TODO: fold size-1 loop dimensions / identity access maps.
229 return nullptr;
230}
231
232const Def* normalize_repeat(const Def*, const Def* c, const Def* arg) {
233 // Identity repeat: if the input and output shapes agree, the repeat is a no-op.
234 auto [Tr, s_in, s_out] = c->as<App>()->uncurry_args<3>();
235 if (s_in == s_out) return arg;
236 return nullptr;
237}
238
239const Def* normalize_reshape(const Def*, const Def* c, const Def* arg) {
240 // Identity reshape: if the input and output shapes agree, the reshape is a no-op.
241 auto [Trr, s_in, s_out] = c->as<App>()->uncurry_args<3>();
242 if (s_in == s_out) return arg;
243 return nullptr;
244}
245
246const Def* normalize_slice(const Def*, const Def* c, const Def* arg) {
247 // Identity slice: every axis starts at 0 with step 1 and keeps its full extent (s_out == s_in) -> the input itself.
248 auto [Tr, s_in, params] = c->as<App>()->uncurry_args<3>();
249 auto [start, step, s_out] = params->projs<3>();
250 if (s_out != s_in) return nullptr;
251 auto r = Lit::isa<u64>(Tr->proj(2, 1));
252 if (!r) return nullptr;
253 for (u64 d = 0; d != *r; ++d) {
254 auto st = Lit::isa<u64>(start->proj(*r, d));
255 auto sp = Lit::isa<u64>(step->proj(*r, d));
256 if (!st || *st != 0 || !sp || *sp != 1) return nullptr;
257 }
258 return arg;
259}
260
261const Def* normalize_flip(const Def*, const Def*, const Def*) { return nullptr; }
262
263const Def* normalize_pad(const Def*, const Def* c, const Def* arg) {
264 // Identity pad: every axis has lo == hi == 0 (so s_out == s_in) -> the input itself (the fill value is irrelevant).
265 auto [Tr, s_in, params] = c->as<App>()->uncurry_args<3>();
266 auto [mode, lo, hi] = params->projs<3>();
267 auto r = Lit::isa<u64>(Tr->proj(2, 1));
268 if (!r) return nullptr;
269 for (u64 d = 0; d != *r; ++d) {
270 auto l = Lit::isa<u64>(lo->proj(*r, d));
271 auto h = Lit::isa<u64>(hi->proj(*r, d));
272 if (!l || *l != 0 || !h || *h != 0) return nullptr;
273 }
274 return arg->proj(2, 0); // input (arg = (input, value))
275}
276
277const Def* normalize_concat(const Def*, const Def*, const Def*) { return nullptr; }
278
279const Def* normalize_shape(const Def*, const Def* c, const Def* arg) {
280 // `%tensor.shape r arr` reads the shape off `arr`'s (nested array) type by peeling `r` levels.
281 auto& w = c->world();
282 auto r = Lit::isa<u64>(c->as<App>()->arg()); // the explicit rank `r`
283 if (!r) return nullptr;
284
285 DefVec dims;
286 auto ty = arg->type();
287 for (u64 i = 0; i != *r; ++i)
288 if (auto a = ty->isa<Seq>()) {
289 dims.emplace_back(a->arity());
290 ty = a->body();
291 } else
292 return nullptr; // `arr` is not (statically) a rank-`r` array
293 return w.tuple(dims); // the per-axis sizes; for a rectangular tensor each `arity()` is a plain Nat
294}
295
297
298} // namespace mim::plug::tensor
const Def * callee() const
Definition lam.h:276
const Def * arg() const
Definition lam.h:285
static auto isa(const Def *def)
Definition axm.h:107
Base class for all Defs.
Definition def.h:261
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
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
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
static std::optional< T > isa(const Def *def)
Definition def.h:878
Base class for Arr and Pack.
Definition tuple.h:86
The tensor Plugin
Definition fuse.h:5
const Def * normalize_broadcast(const Def *, const Def *c, 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:17
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::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_reshape(const Def *, const Def *c, const Def *arg)
const Def * normalize_shape(const Def *, const Def *c, const Def *arg)
const Def * normalize_map_reduce(const Def *, const Def *, const Def *)
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:9
Vector< const Def * > DefVec
Definition def.h:79
uint64_t u64
Definition types.h:27
#define MIM_tensor_NORMALIZER_IMPL
Definition autogen.h:348