MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
normalizers.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3
4#include <fe/format.h>
5
6#include <mim/def.h>
7#include <mim/driver.h>
8#include <mim/plugin.h>
9#include <mim/tuple.h>
10#include <mim/world.h>
11
13
15
16namespace mim::plug::tensor {
17
18const Def* normalize_broadcast(const Def* type, const Def* c, const Def* arg) {
19 auto& w = c->world();
20
21 auto [s_in, s_out, input] = arg->projs<3>();
22 auto callee = c->as<App>();
23 auto [T, r] = callee->args<2>();
24 w.log().d("broadcast: input = {}: {}, T = {}, r = {}, s_in = {}, s_out = {}", input, input->type(), T, r, s_in,
25 s_out);
26
27 if (s_in == s_out || input->type() == type) return input;
28
29 return nullptr;
30}
31
32const Def* normalize_broadcast_in_dim(const Def*, const Def*, const Def*) { return nullptr; }
33
34const Def* normalize_repeat(const Def* type, const Def* c, const Def* arg) {
35 // Identity repeat: if the input and output shapes agree, the repeat is a no-op.
36 // Size-1 axes collapse out of the type, so equal types suffice.
37 auto [Tr, s_in, s_out] = c->as<App>()->uncurry_args<3>();
38 if (s_in == s_out || arg->type() == type) return arg;
39 return nullptr;
40}
41
42const Def* normalize_reshape(const Def* type, const Def* c, const Def* arg) {
43 // Identity reshape: if the input and output shapes agree, the reshape is a no-op.
44 // Size-1 axes collapse out of the type, so equal types suffice.
45 auto [Trr, s_in, s_out] = c->as<App>()->uncurry_args<3>();
46 if (s_in == s_out || arg->type() == type) return arg;
47 return nullptr;
48}
49
50const Def* normalize_transpose(const Def*, const Def* c, const Def* arg) {
51 auto callee = c->as<App>()->decurry();
52 auto perm = lit_perm(callee->arg());
53 if (!perm) return nullptr;
54 auto r = perm->size();
55 if (std::ranges::equal(*perm, std::views::iota(u64(0), u64(r)))) return arg;
56
57 auto app = Axm::isa<tensor::transpose>(arg);
58 if (!app) return nullptr;
59 auto inner = transpose_perm(app);
60 if (!inner || inner->size() != r) return nullptr;
61
62 // Axis `j` of the inner input ends up at `perm#(inner#j)`; an identity composite collapses in the rebuilt app.
63 auto& w = c->world();
64 auto comp = DefVec(r, [&](size_t j) { return w.lit_idx(r, (*perm)[(*inner)[j]]); });
65 return w.app(w.app(w.app(callee->callee(), w.tuple(comp)), app->decurry()->arg()), app->arg());
66}
67
68const Def* normalize_slice(const Def*, const Def* c, const Def* arg) {
69 // Identity slice: every axis starts at 0 with step 1 and keeps its full extent (s_out == s_in) -> the input itself.
70 auto [Tr, s_in, params] = c->as<App>()->uncurry_args<3>();
71 auto [start, step, s_out] = params->projs<3>();
72 if (s_out != s_in) return nullptr;
73 auto r = Lit::isa<u64>(Tr->proj(2, 1));
74 if (!r) return nullptr;
75 for (u64 d = 0; d != *r; ++d) {
76 auto st = Lit::isa<u64>(start->proj(*r, d));
77 auto sp = Lit::isa<u64>(step->proj(*r, d));
78 if (!st || *st != 0 || !sp || *sp != 1) return nullptr;
79 }
80 return arg;
81}
82
83const Def* normalize_flip(const Def*, const Def*, const Def*) { return nullptr; }
84
85const Def* normalize_pad(const Def*, const Def* c, const Def* arg) {
86 // Identity pad: every axis has lo == hi == 0 (so s_out == s_in) -> the input itself (the fill value is irrelevant).
87 auto [Tr, s_in, params] = c->as<App>()->uncurry_args<3>();
88 auto [mode, lo, hi] = params->projs<3>();
89 auto r = Lit::isa<u64>(Tr->proj(2, 1));
90 if (!r) return nullptr;
91 for (u64 d = 0; d != *r; ++d) {
92 auto l = Lit::isa<u64>(lo->proj(*r, d));
93 auto h = Lit::isa<u64>(hi->proj(*r, d));
94 if (!l || *l != 0 || !h || *h != 0) return nullptr;
95 }
96 return arg->proj(2, 0); // input (arg = (input, value))
97}
98
99const Def* normalize_concat(const Def*, const Def*, const Def*) { return nullptr; }
100
101const Def* normalize_if_static(const Def*, const Def*, const Def* arg) {
102 // `tensor.if_static (k, s, d)` picks `s` once `k` has folded to a literal; a still-symbolic `k`
103 // keeps the App stuck, and the tensor lowerings residualize it to `d` (by lowering time,
104 // undecided means runtime).
105 auto [k, s, d] = arg->projs<3>();
106 if (Lit::isa(k)) return s;
107 return nullptr;
108}
109
110const Def* normalize_fastest_axis(const Def*, const Def*, const Def* arg) {
111 // `tensor.fastest_axis (r, t)` reflects which axis of `t` is the fastest-varying (unit-stride)
112 // axis of the tensor actually read once `fuse_tensor`'s read-through has absorbed a pure
113 // re-indexed read behind `t`: without one, `t`'s own last axis; behind one, found by evaluating
114 // the read's access map on distinct `affine.lit` markers — normalization folds the map's
115 // extracts over the marker tuple, and a last component that does not fold back to a marker
116 // (reshape arithmetic) stays unknown. Unknown answers the sentinel `r`.
117 auto& w = arg->world();
118 auto [r, t] = arg->projs<2>();
119 auto r_l = Lit::isa<u64>(r);
120 if (!r_l || *r_l == 0) return r;
121 auto pr = is_pure_read(t);
122 if (!pr) return w.lit_nat(*r_l - 1);
123 // One level only: a source that is itself absorbed would need the composed analysis.
124 if (is_pure_read(pr->src) || Axm::isa<tensor::broadcast>(pr->src)) return r;
125 auto r_src = Lit::isa<u64>(pr->map->type()->as<Pi>()->codom()->arity());
126 if (!r_src || *r_src == 0) return r;
127 // `r` is not type-coupled to `t`; a mismatched rank must answer unknown, not break the app below.
128 auto r_dom = Lit::isa<u64>(pr->map->type()->as<Pi>()->dom()->arity());
129 if (!r_dom || *r_dom != *r_l) return r;
130 // Markers start at 1: 0 is what a broadcast map's `o#d · 0` folds to, so it must not be one.
131 auto markers = DefVec(*r_l, [&](size_t i) { return w.call<affine::lit>(w.lit_nat(i + 1)); });
132 auto last = w.app(pr->map, w.tuple(markers))->proj(*r_src, *r_src - 1);
133 auto c = Axm::isa<affine::lit>(last);
134 if (!c) return r;
135 auto v = Lit::isa<u64>(c->arg());
136 if (!v || *v < 1 || *v > *r_l) return r;
137 return w.lit_nat(*v - 1);
138}
139
140/// The vector widths `-X tensor:vec-width` accepts.
141constexpr std::array Vec_widths = {1_u64, 4_u64, 8_u64, 16_u64};
142
143const Def* normalize_vec_width(const Def*, const Def*, const Def* arg) {
144 // Read off the Driver so one graph can be re-emitted for another target by another Driver.
145 auto& w = arg->world();
146 auto n = w.driver().arg_nat("tensor", "vec-width");
147 if (!n) return arg;
148 if (std::ranges::find(Vec_widths, *n) == Vec_widths.end()) {
149 w.log().w("ignoring `-X tensor:vec-width={}`: not one of {}", *n, fe::Join(Vec_widths, ", "));
150 return arg;
151 }
152 return w.lit_nat(*n);
153}
154
155const Def* normalize_shape(const Def*, const Def* c, const Def* arg) {
156 // `tensor.shape r arr` is the first `r` axes of `arr`'s (fused) array type.
157 auto& w = c->world();
158 auto r = Lit::isa<u64>(c->as<App>()->arg()); // the explicit rank `r`
159 if (!r) return nullptr;
160 if (*r == 0) return w.tuple();
161
162 auto arr = arg->type()->isa<Arr>();
163 if (!arr) return nullptr;
164 auto shape = arr->shape();
165 auto rank = shape.rank();
166 if (!rank || *rank < *r) return nullptr; // `arr` is not (statically) at least rank `r`
167 return *shape.take(*r);
168}
169
171
172} // namespace mim::plug::tensor
auto args(Projector auto f) const noexcept
Definition lam.h:252
const Def * arg() const
Definition lam.h:251
A (possibly paramterized) Array.
Definition tuple.h:305
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
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:647
World & world() const noexcept
Definition def.h:1105
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:457
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
const Def * arity() const
Number of elements available to Extract / Insert (may be dynamic).
Definition def.cpp:612
static std::optional< T > isa(const Def *def)
Definition def.h:976
A dependent function type.
Definition lam.h:14
const Def * dom() const
Definition lam.h:38
const Def * codom() const
Definition lam.h:39
The tensor Plugin
Definition constraints.h:5
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_transpose(const Def *, const Def *c, const Def *arg)
const Def * normalize_pad(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:51
const Def * normalize_concat(const Def *, const Def *, const Def *)
const Def * normalize_fastest_axis(const Def *, const Def *, const Def *arg)
std::optional< fe::Vector< u64 > > transpose_perm(const App *app)
The permutation of a transpose app, if it is literal.
Definition tensor.h:88
std::optional< fe::Vector< u64 > > lit_perm(const Def *perm)
perm as literals, if it is literal.
Definition tensor.h:82
const Def * normalize_shape(const Def *, const Def *c, const Def *arg)
constexpr std::array Vec_widths
The vector widths -X tensor:vec-width accepts.
const Def * normalize_flip(const Def *, const Def *, const Def *)
const Def * normalize_broadcast(const Def *type, const Def *c, const Def *arg)
const Def * normalize_vec_width(const Def *, const Def *, const Def *arg)
const Def * normalize_repeat(const Def *type, const Def *c, const Def *arg)
const Def * normalize_reshape(const Def *type, const Def *c, const Def *arg)
fe::Vector< const Def * > DefVec
Definition def.h:98
uint64_t u64
Definition types.h:27
#define MIM_tensor_NORMALIZER_IMPL
Definition autogen.h:462