MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
scalarize.cpp
Go to the documentation of this file.
2
3#include "mim/lam.h"
4#include "mim/tuple.h"
5#include "mim/world.h"
6
7namespace mim {
8
9/// The only Pi%s we ever reshape: immutable (non-dependent) continuations.
10static const Pi* isa_flattenable(const Def* def) {
11 if (auto pi = Pi::isa_cn(def); pi && pi->isa_imm()) return pi;
12 return nullptr;
13}
14
15/// A dependent Sigma of values only; other mutable Sigma%s may be recursive or pack an existential, like a closure.
16static bool is_value_telescope(const Def* def) {
17 if (auto [sigma, var] = def->isa_binder<Sigma>(); var)
18 return std::ranges::none_of(sigma->ops(), [](const Def* op) { return op->isa<Type>(); });
19 return false;
20}
21
22/// Is the argument of the Axm application @p app typed by one of the Axm's type variables?
23static bool is_polymorphic_arg(const App* app) {
24 auto n = 0;
25 for (const Def* d = app; auto a = d->isa<App>(); d = a->callee())
26 ++n;
27
28 auto pi = app->uncurry_callee()->type()->isa<Pi>();
29 for (; pi && --n != 0;)
30 pi = pi->codom()->isa<Pi>();
31 if (!pi) return false;
32
33 auto dom = pi->dom();
34 if (auto ex = dom->isa<Extract>()) dom = ex->tuple();
35 return dom->isa<Var>();
36}
37
38/*
39 * Analysis
40 */
41
42bool Scalarize::Analysis::kept(const Pi* pi, size_t dom) const {
43 if (is_top(pi)) return true; // whole Pi pinned
44 auto mask = fe::lookup(keeps_, pi);
45 return mask && mask->test(dom);
46}
47
48void Scalarize::Analysis::keep(const Pi* pi, size_t dom) {
49 if (dom >= pi->num_tdoms() || is_top(pi)) return;
50 auto& mask = keeps_[pi];
51 if (mask.test(dom)) return;
52 mask.set(dom);
53 log().d("keep {}#{}", pi, dom);
54 touch();
55}
56
57void Scalarize::Analysis::keep_var(const Def* def) {
58 if (auto [var, i] = Var::isa_tproj(def); var) {
59 if (auto pi = isa_flattenable(var->binder()->type())) {
60 if (i)
61 keep(pi, *i);
62 else
63 pin(pi);
64 }
65 }
66}
67
68void Scalarize::Analysis::pin_opaque(DefSet& structural, const Def* type) {
69 if (transparent(type)) {
70 if (structural.emplace(type).second)
71 for (auto op : type->ops())
72 pin_opaque(structural, op);
73 } else {
74 pin_imm(type, isa_flattenable);
75 }
76}
77
78void Scalarize::Analysis::keep_indexed(const Def* tuple, const Def* idx) {
79 // Only the outermost index must be constant: `t#(0₂, i)` still splits.
80 if (Idx::isa(idx->unfold_type())) {
81 if (Lit::isa(idx)) return;
82 } else if (auto r = Lit::isa(idx->unfold_type()->arity()); r && *r != 0 && Lit::isa(idx->proj(*r, 0))) {
83 return;
84 }
85
86 keep_var(tuple);
87}
88
89const Def* Scalarize::Analysis::rewrite_mut_Lam(Lam* lam) {
90 // The interface's signature keeps its shape, which the body only meets via coerce().
91 if (!lam->is_rewritable()) {
92 auto structural = DefSet();
93 pin_opaque(structural, lam->type());
94 }
96}
97
98// pin() maps `pi ↦ pi`, which would cut off the traversal into the old Def, so the hooks below inspect afterwards.
99
100const Def* Scalarize::Analysis::rewrite_imm_App(const App* app) {
101 auto res = mim::Analysis::rewrite_imm_App(app);
102 // `tensor.buf x` nominates `x` itself.
103 if (app->axm() && is_polymorphic_arg(app)) keep_var(app->arg());
104 return res;
105}
106
107const Def* Scalarize::Analysis::rewrite_imm_Extract(const Extract* ex) {
109 keep_indexed(ex->tuple(), ex->index());
110 return res;
111}
112
113const Def* Scalarize::Analysis::rewrite_imm_Insert(const Insert* ins) {
114 auto res = mim::Analysis::rewrite_imm_Insert(ins);
115 keep_indexed(ins->tuple(), ins->index());
116 return res;
117}
118
119fe::Bitset Scalarize::Analysis::plan(const Def* type) const {
120 auto mask = fe::Bitset();
121 if (auto pi = isa_flattenable(type)) {
122 // Splitting a dependent domain would shift the indices its Var references.
123 if (auto sig = pi->dom()->isa_mut<Sigma>(); sig && sig->has_var()) return mask;
124 for (size_t i = 0, n = pi->num_tdoms(); i != n; ++i) {
125 auto t = pi->tdom(i);
126 if (!kept(pi, i) && (t->isa_imm() || is_value_telescope(t)) && t->num_tprojs() > 1) mask.set(i);
127 }
128 }
129 return mask;
130}
131
132/*
133 * Keep
134 */
135
136const Def* Scalarize::Keep::rewrite(const Def* old_def) {
137 if (!has_cn(old_def) && !has_intersection(old_def)) return sca_.rewrite(old_def);
138 bind_old(old_def->has_var());
139 return Rewriter::rewrite(old_def);
140}
141
142bool Scalarize::Keep::has_cn(const Def* def) {
143 if (auto [i, ins] = has_cn_.emplace(def, false); !ins) return i->second;
144 auto res = isa_flattenable(def) != nullptr;
145 for (auto d : def->deps())
146 res |= has_cn(d);
147 return has_cn_[def] = res;
148}
149
150/*
151 * Scalarize
152 */
153
154const Def* Scalarize::rewrite_imm_Pi(const Pi* pi) { return flatten(*this, pi, analysis_.plan(pi)); }
155
157 if (!app->axm() || !app->arg()->type()->isa<Pi>()) return RWPhase::rewrite_imm_App(app);
158
159 auto arg = rewrite(app->arg());
160 auto keep = Keep(*this);
161 auto callee = keep.rewrite(app->callee());
162 return new_world().app(callee, coerce(callee->type()->as<Pi>()->dom(), arg));
163}
164
165const Def* Scalarize::flatten(Rewriter& rw, const Pi* pi, const fe::Bitset& mask) {
166 if (mask.none()) return RWPhase::rewrite_imm_Pi(pi);
167
168 auto& w = new_world();
169 auto n = pi->num_tdoms();
170 auto ts = DefVec(n, [&](size_t i) { return rw.rewrite(pi->tdom(i)); });
171 auto num = size_t(0);
172 auto deps = false;
173 for (size_t i = 0; i != n; ++i) {
174 num += mask[i] ? ts[i]->num_tprojs() : 1;
175 deps |= mask[i] && ts[i]->isa_binder<Sigma>().second;
176 }
177
178 const Def* dom;
179 if (deps) {
180 // A telescope's pieces refer to their predecessors, which now live in the new domain's binder.
181 auto sigma = w.mut_sigma(num);
182 for (size_t i = 0, v = 0; i != n; ++i) {
183 if (!mask[i]) {
184 sigma->set(v++, ts[i]);
185 continue;
186 }
187 auto k = ts[i]->num_tprojs();
188 auto parts = DefVec();
189 for (size_t j = 0; j != k; ++j, ++v) {
190 sigma->set(v, w.component(ts[i], parts, k, j));
191 parts.emplace_back(sigma->var(num, v));
192 }
193 }
194 dom = sigma;
195 } else {
196 auto doms = DefVec();
197 for (size_t i = 0; i != n; ++i) {
198 if (mask[i]) {
199 auto pieces = ts[i]->tprojs();
200 doms.insert(doms.end(), pieces.begin(), pieces.end());
201 } else {
202 doms.emplace_back(ts[i]);
203 }
204 }
205 dom = w.sigma(doms);
206 }
207
208 auto sca = w.pi(dom, rw.rewrite(pi->codom()), pi->is_implicit());
209 log().d("scalarize {} → {}", pi, sca);
210 invalidate();
211 return sca;
212}
213
214Lam* Scalarize::stub(Lam* old, const fe::Bitset& mask) {
215 auto n = old->num_tvars();
216 auto types = DefVec(n);
217 // An interface Lam's own Pi is flattened like any other, but what it mentions keeps its shape.
218 auto keep = Keep(*this);
219 auto& rw = old->is_rewritable() ? static_cast<Rewriter&>(*this) : keep;
220 auto type = mask.any() ? flatten(rw, old->type()->as<Pi>(), mask) : rw.rewrite(old->type());
221 if (old->is_set())
222 for (size_t i = 0; i != n; ++i)
223 if (mask[i]) types[i] = rw.rewrite(old->tvar(i)->type());
224
225 auto& w = new_world();
226 auto sca = w.mut_lam(type->as<Pi>())->set(old->dbg_key());
227 map(old, sca);
228 if (!old->is_set() || mask.none()) return sca;
229
230 auto num_sca = sca->num_vars();
231 auto params = DefVec();
232 params.reserve(n);
233 for (size_t i = 0, v = 0; i != n; ++i) {
234 if (mask[i]) {
235 auto t = types[i];
236 auto pieces = DefVec(t->num_tprojs(), [&](size_t) { return sca->var(num_sca, v++); });
237 params.emplace_back(w.tuple(t, pieces));
238 } else {
239 params.emplace_back(sca->var(num_sca, v++));
240 }
241 }
242 map(old->var(), w.tuple(params));
243 return sca;
244}
245
247 auto mask = analysis_.plan(old->type());
248 if (mask.none() && old->is_rewritable()) return RWPhase::rewrite_mut_Lam(old);
249 return rewrite_stub(old, stub(old, mask));
250}
251
252} // namespace mim
bool is_top(const Def *def) const
Definition phase.h:188
const Def * rewrite_mut_Lam(Lam *mut) override
Definition phase.h:290
const Axm * axm() const
Definition lam.h:257
static const Def * uncurry_callee(const Def *def)
The innermost App::callee - the one that isn't an App itself.
Definition lam.h:278
const Def * callee() const
Definition lam.h:237
const Def * arg() const
Definition lam.h:251
Base class for all Defs.
Definition def.h:289
std::pair< D *, const Var * > isa_binder() const
Is this a mutable that introduces a Var?
Definition def.h:503
nat_t num_vars() noexcept
Definition def.h:492
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:360
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:670
A function.
Definition lam.h:91
bool is_rewritable()
Definition lam.h:191
const Pi * type() const
Definition lam.h:112
static std::optional< T > isa(const Def *def)
Definition def.h:976
void invalidate(bool todo=true)
Signals that another round of fixed-point iteration is required, either as part of.
Definition phase.h:79
const fe::Log & log() const
Definition phase.h:60
A dependent function type.
Definition lam.h:14
const Def * tdom(nat_t i) const noexcept
Definition lam.h:40
static const Pi * isa_cn(const Def *d)
Definition lam.h:50
bool is_implicit() const
Definition lam.h:26
const Def * dom() const
Definition lam.h:38
const Def * codom() const
Definition lam.h:39
nat_t num_tdoms() const noexcept
Definition lam.h:40
World & new_world()
Create new Defs into this.
Definition phase.h:379
const Def * coerce(const Def *new_type, const Def *new_val)
Converts the new new_val into a value of the new new_type, where a checking position demands it.
Definition phase.cpp:264
bool is_top(const Def *old_def) const
Definition phase.h:355
const Def * rewrite_stub(Def *, Def *) override
Definition phase.cpp:250
const Def * rewrite_imm_App(const App *) override
Definition phase.cpp:223
Recurseivly rebuilds part of a program into the provided World w.r.t. Rewriter::map.
Definition rewrite.h:21
bool has_intersection(const Def *old_def)
Does old_def depend on the old Vars of any scope?
Definition rewrite.h:107
void bind_old(const Var *var)
Adds the old var - if any - to the innermost scope.
Definition rewrite.cpp:40
virtual const Def * rewrite_imm_Extract(const Extract *)
virtual const Def * rewrite_imm_Insert(const Insert *)
const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.cpp:29
virtual const Def * rewrite_mut_Lam(Lam *)
Rewriter(World &world)
Definition rewrite.cpp:16
virtual const Def * rewrite_imm_Pi(const Pi *)
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:62
virtual const Def * rewrite_imm_App(const App *)
const Def * rewrite_imm_Pi(const Pi *) final
const Def * rewrite_imm_App(const App *) final
const Def * rewrite_mut_Lam(Lam *) final
A dependent tuple type.
Definition tuple.h:39
A variable introduced by a binder (mutable).
Definition def.h:857
static std::pair< const Var *, std::optional< nat_t > > isa_tproj(const Def *def)
Matches a Var v itself or an Extract v#j of it.
Definition def.cpp:223
const Def * app(const Def *callee, const Def *arg)
Definition world.cpp:416
Definition ast.h:16
static const Pi * isa_flattenable(const Def *def)
The only Pis we ever reshape: immutable (non-dependent) continuations.
Definition scalarize.cpp:10
static bool is_value_telescope(const Def *def)
A dependent Sigma of values only; other mutable Sigmas may be recursive or pack an existential,...
Definition scalarize.cpp:16
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94
static bool is_polymorphic_arg(const App *app)
Is the argument of the Axm application app typed by one of the Axm's type variables?
Definition scalarize.cpp:23
@ Lam
Definition def.h:125