MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
phase.cpp
Go to the documentation of this file.
1#include "mim/phase.h"
2
3#include <algorithm>
4#include <memory>
5#include <utility>
6
7#include <fe/bitset.h>
8
9#include "mim/driver.h"
10#include "mim/flags.h"
11
12namespace mim {
13
14/*
15 * Phase
16 */
17
19 : world_(world)
20 , annex_(annex)
21 , name_(world.annex(annex)->sym()) {}
22
23const fe::Vector<std::string>& Phase::args() { return driver().args(Annex::demangle(Annex::flags2plugin(annex_))); }
24
25std::unique_ptr<Phase> Phase::create(const Flags2Phases& phases, const Def* def) {
26 auto& world = def->world();
27 auto p_def = App::uncurry_callee(def);
28 world.log().d("apply phase {}", p_def);
29
30 if (auto axm = p_def->isa<Axm>())
31 if (auto i = phases.find(axm->flags()); i != phases.end()) {
32 auto phase = i->second(world);
33 if (phase) {
34 phase->apply(def->isa<App>());
35 if (phase->redirects()) return phase->take_resolved();
36 }
37 return phase;
38 } else
39 fe::throwf("no phase registered for stage `{}`; is its plugin loaded?", axm->sym());
40 else
41 fe::throwf("unsupported callee for a phase: `{}`", p_def);
42}
43
44std::unique_ptr<Phase> Phase::recreate() {
45 auto ctor = driver().phase(annex());
46 auto ptr = (*ctor)(world());
47 ptr->apply(*this);
48 return ptr;
49}
50
51void Phase::run() {
52 auto profiling = driver().flags().profile != Flags::Profile::None;
53 if (profiling) driver().profiler().start(name());
54 world().verify().log().i("🚀 launch phase `{}`", name());
55 start();
56 world().verify().log().i("🏁 finish phase `{}`", name());
57 if (profiling) driver().profiler().stop();
58}
59
60void Phase::profile_count(std::string_view key, uint64_t n) {
61 if (driver().flags().profile != Flags::Profile::None) driver().profiler().count(key, n);
62}
63
64/*
65 * Analyzer
66 */
67
69 scopes_.clear();
70 worklist_.clear();
71 push();
72 todo_ = false;
73}
74
76 auto _ = world().lenient();
77
78 curr_sparse_ = !dense_ && !nonlocal_ && !dirty_.empty();
79 nonlocal_ = false;
80 auto seeds = fe::Vector<Def*>(dirty_.begin(), dirty_.end());
81 dirty_.clear();
82
83 prepare();
84
85 if (curr_sparse_) {
86 log().v("sparse round: re-drain {} dirty muts", seeds.size());
87 std::ranges::sort(seeds, GIDLt<Def*>()); // MutSet iteration order is nondeterministic
88
89 // Pre-install what earlier rounds substituted, so this round prunes everything they already settled -
90 // except for mutables, whose map entry doubles as the per-round "already scheduled" marker and would
91 // suppress their drain. Pruning skips rewrite hooks, which is why only a sparse round does it: the
92 // full round that certifies the fixed point always re-derives from the program.
93 old2new().reserve(lattice_.size());
94 for (auto [concr, abstr] : lattice_)
95 if (!concr->isa_mut()) map(concr, abstr);
96
97 for (auto mut : seeds)
98 rewrite(mut);
99 drain();
100 } else {
101 for (auto mut : world().externals().muts())
102 rewrite_external(mut);
103 drain();
104
105 finalize();
106 }
107
108 profile_count(curr_sparse_ ? "rounds.sparse" : "rounds.full");
109 profile_count("muts.drained", std::exchange(num_drained_, size_t(0)));
110
111 // A quiet sparse round only certifies the muts it visited:
112 // force one full round; the fixed point counts only if that one stays quiet, too.
113 if (curr_sparse_ && !todo()) invalidate();
114}
115
117
118const Def* Analysis::lookup(const Def* def) {
119 if (auto res = Rewriter::lookup(def)) return res;
120 return world().annexes().opaque(def) ? def : nullptr;
121}
122
123bool Analysis::lattice_force(const Def* concr, const Def* abstr) {
124 if (is_singleton(concr)) return false;
125 map(concr, abstr);
126
127 if (auto [i, ins] = lattice_.emplace(concr, abstr); !ins) {
128 if (i->second == abstr) return false;
129 i->second = abstr;
130 }
131 return touch(), true;
132}
133
134bool Analysis::lattice(const Def* concr, const Def* abstr) {
135 if (is_singleton(concr)) return false;
136 map(concr, abstr);
137
138 if (auto [i, ins] = lattice_.emplace(concr, abstr); !ins) {
139 assert((i->second != concr || abstr == concr) && "monotonicity violation: must not descend from ⊤");
140 if (i->second == abstr) return false;
141 i->second = abstr;
142 } else if (concr == abstr) {
143 return false;
144 }
145 return touch(), true;
146}
147
148bool Analysis::is_cyclic(const Def* def) const {
149 auto slow = follow(def), fast = follow(slow);
150 for (; slow != fast; slow = follow(slow), fast = follow(follow(fast))) {}
151 return follow(fast) != fast;
152}
153
154const Def* Analysis::rewrite(const Def* def) {
155 if (def->isa_mut()) return Rewriter::rewrite(def);
156 return repr(Rewriter::rewrite(def));
157}
158
160 if (lookup(mut)) return mut; // already scheduled this round
161 map(mut, mut);
162 worklist_.emplace_back(mut);
163 return Rewriter::rewrite_mut(mut);
164}
165
166void Analysis::drain() {
167 while (!worklist_.empty()) {
168 auto mut = worklist_.front();
169 worklist_.pop_front();
170 ++num_drained_;
171
172 auto _ = enter(mut);
173 log().d("enter {}", mut);
174 for (auto d : mut->deps())
175 rewrite(d);
176 leave();
177 }
178}
179
180/*
181 * RWPhase
182 */
183
185 auto max_iters = driver().flags().max_fp_iters;
186 bool todo = true;
187 for (uint32_t i = 0; todo; ++i) {
188 if (i >= max_iters) fe::throwf("phase `{}` did not reach a fixed point after {} iterations", name(), max_iters);
189 log().v("iteration {}", i);
190 todo = analyze();
191 }
192
193 auto gid = new_world().curr_gid();
194 for (auto mut : old_world().externals().muts())
195 rewrite_external(mut);
196 finalize();
197 profile_count("rw.defs", new_world().curr_gid() - gid);
198
200}
201
203 if (analysis_) {
204 analysis_->reset();
205 analysis_->run();
206 return analysis_->todo();
207 }
208
209 return false;
210}
211
212const Def* RWPhase::lookup(const Def* old_def) {
213 if (auto new_def = Rewriter::lookup(old_def)) return new_def;
214 if (auto flags = old_world().annexes().opaque(old_def)) return map(old_def, new_world().annex(*flags));
215 return nullptr;
216}
217
219 // A phase may have externalized the new mutable itself already, or replaced it by an immutable.
220 if (auto new_mut = rewrite(old_mut)->isa_mut(); new_mut && !new_mut->is_external()) new_mut->externalize();
221}
222
223const Def* RWPhase::rewrite_imm_App(const App* app) {
224 // The arg goes first for the same reason as in Rewriter::rewrite_imm_App.
225 auto arg = rewrite(app->arg());
226 auto callee = rewrite(app->callee());
227 if (auto pi = callee->isa_type<Pi>()) arg = coerce(pi->dom(), arg);
228 return new_world().app(callee, arg);
229}
230
231/// Is @p tuple%'s type what World::tuple would infer from its ops?
232static bool has_inferred_type(const Tuple* tuple) {
233 auto& w = tuple->world();
234 return !w.is_frozen() && tuple->type() == Tuple::infer(w, tuple->ops());
235}
236
238 // Only an annotated type constrains the ops; an inferred one follows them.
239 if (has_inferred_type(tuple)) return new_world().tuple(rewrite(tuple->ops()));
240
241 auto type = rewrite(tuple->type());
242 auto ops = rewrite(tuple->ops());
243 auto n = ops.size();
244 if (Lit::isa(type->arity()) != n) return coerce(type, new_world().tuple(ops));
245 for (size_t i = 0; i != n; ++i)
246 ops[i] = coerce(new_world().component(type, Defs(ops).subspan(0, i), n, i), ops[i]);
247 return new_world().tuple(type, ops);
248}
249
250const Def* RWPhase::rewrite_stub(Def* old_mut, Def* new_mut) {
251 auto old_lam = old_mut->isa<Lam>();
252 auto new_lam = new_mut->isa<Lam>();
253 if (!old_lam || !new_lam || !old_lam->is_set()) return Rewriter::rewrite_stub(old_mut, new_mut);
254
255 map(old_mut, new_mut);
256 auto _ = enter(old_mut);
257 auto pi = new_lam->type();
258 // Creating the Var just to reduce a non-dependent codomain would mark it as used.
259 auto codom = pi->has_var() ? pi->reduce(new_lam->var()) : pi->codom();
260 new_lam->set(rewrite(old_lam->filter()), coerce(codom, rewrite(old_lam->body())));
261 return seal(old_mut, new_mut);
262}
263
264const Def* RWPhase::coerce(const Def* type, const Def* val) {
265 if (val->type() == type) return val;
266 auto todo = DefVec{val};
267 auto res = reshape(todo, type);
268 while (!todo.empty() && new_world().inhabitant(todo.back()->type()))
269 todo.pop_back();
270 if (!todo.empty()) fe::throwf("cannot coerce `{}` to `{}`", val, type);
271 return res;
272}
273
274const Def* RWPhase::reshape(DefVec& todo, const Def* type) {
275 auto& w = new_world();
276 auto front = todo.empty() ? nullptr : todo.back();
277 if (front) {
278 if (auto res = Check::coerce(type, front)) return todo.pop_back(), res;
279 if (auto res = eta_coerce(type, front)) return todo.pop_back(), res;
280 }
281
282 if (auto s = w.inhabitant(type)) return s;
283 if (front && w.inhabitant(front->type())) return todo.pop_back(), reshape(todo, type);
284
285 if (splittable(type)) {
286 auto n = type->num_tprojs();
287 auto parts = DefVec();
288 for (size_t i = 0; i != n; ++i)
289 parts.emplace_back(reshape(todo, w.component(type, parts, n, i)));
290 return w.tuple(type, parts);
291 }
292
293 if (front && front->type()->num_tprojs() != 1) {
294 todo.pop_back();
295 auto pieces = front->tprojs();
296 todo.insert(todo.end(), pieces.rbegin(), pieces.rend());
297 return reshape(todo, type);
298 }
299
300 fe::throwf("cannot coerce to `{}`", type);
301}
302
303const Def* RWPhase::eta_coerce(const Def* type, const Def* val) {
304 auto dst = type->isa<Pi>();
305 auto src = val->type()->isa<Pi>();
306 if (!dst || !src) return nullptr;
307 auto& memo = etas_[type][val];
308 if (memo) return memo;
309
310 auto eta = new_world().mut_lam(dst)->set(val->dbg_key());
311 memo = eta; // before the recursion below may rehash etas_
312 auto arg = coerce(src->dom(), eta->var());
313 auto codom = dst->reduce(eta->var());
314 log().d("coerce {}: {} → {}", val, src, dst);
315 return eta->set(false, coerce(codom, new_world().app(val, arg)));
316}
317
318/*
319 * PhaseMan
320 */
321
322void PhaseMan::apply(bool fp, Phases&& phases) {
323 fixed_point_ = fp;
324 phases_ = std::move(phases);
325 name_ += fixed_point_ ? " tt" : " ff";
326}
327
328void PhaseMan::apply(const App* app) {
329 auto [fp, args] = app->uncurry_args<2>();
330
331 auto phases = Phases();
332 for (auto arg : args->projs())
333 if (auto phase = create(driver().phases(), arg)) phases.emplace_back(std::move(phase));
334
335 apply(Lit::as<bool>(fp), std::move(phases));
336}
337
338void PhaseMan::apply(Phase& phase) {
339 auto& man = static_cast<PhaseMan&>(phase);
340 Phases new_phases;
341 for (auto& old_phase : man.phases())
342 new_phases.emplace_back(old_phase->recreate());
343 apply(man.fixed_point(), std::move(new_phases));
344}
345
347 auto max_iters = driver().flags().max_fp_iters;
348 auto n = phases().size();
349 // A phase's run is a deterministic function of the World's content.
350 // So a phase only needs to run (again) if the World (may have) changed since its last quiet run.
351 auto all = fe::Bitset(n, true);
352 auto stale = all;
353 auto ran = fe::Bitset(n, false);
354
355 for (uint32_t iter = 0; stale.any(); ++iter) {
356 if (iter >= max_iters)
357 fe::throwf("phase `{}` did not reach a fixed point after {} iterations", name(), max_iters);
358 if (fixed_point()) log().v("🔄 fixed-point iteration {}", iter);
359
360 bool todo = false;
361 for (size_t i = 0; i != n; ++i) {
362 auto& phase = phases()[i];
363 if (!stale.test(i)) {
364 log().v("skip `{}`: World unchanged since its last quiet run", phase->name());
365 profile_count("phases.skipped");
366 continue;
367 }
368
369 if (ran.test(i)) phase = phase->recreate(); // re-runs need a fresh instance
370
371 phase->run();
372 ran.set(i);
373 stale.clear(i);
374
375 if (phase->todo()) {
376 todo = true;
377 // The World changed: everyone - including this phase itself - gets another look.
378 stale = all;
379 }
380 }
381
382 todo &= fixed_point();
384 if (!fixed_point()) break;
385 }
386}
387
388} // namespace mim
virtual void prepare()
Run before the main analysis.
Definition phase.h:266
void start() override
Actual entry.
Definition phase.cpp:75
bool lattice_force(const Def *concr, const Def *abstr)
Non-monotone write of concr ↦ abstr into lattice() and map().
Definition phase.cpp:123
const Def * rewrite(const Def *) override
Rewrites def and then maps the result to its repr().
Definition phase.cpp:154
const Def * lookup(const Def *) override
Lookup old_def by searching in reverse through the stack of maps.
Definition phase.cpp:118
const Def * rewrite_mut(Def *) final
Schedules mut for a breadth-first visit of its dependencies and records mut -> mut.
Definition phase.cpp:159
virtual void leave()
Called after curr_mut() has been completely dealt with.
Definition phase.h:292
void touch()
Observable analysis information changed: records curr_mut() as dirty - the seed set of the next spars...
Definition phase.h:253
virtual void rewrite_external(Def *)
Definition phase.cpp:116
virtual void reset()
Clears the rewriter map and resets Phase::todo() for the next fixed-point iteration.
Definition phase.cpp:68
virtual void finalize()
Run after the main analysis - only in full rounds, so it always sees the complete abstract World.
Definition phase.h:268
const auto & lattice() const
The whole map; used e.g. to diff two fixed-point runs.
Definition phase.h:165
static bool is_singleton(const Def *def)
The sole inhabitant of a singleton type is ⊤ and shared by all binders of that type,...
Definition phase.h:199
const Def * repr(const Def *def) const
The representative of def: follows def ↦ lattice(def) to the end of its chain.
Definition phase.h:181
World & world()
Definition phase.h:58
const Def * callee() const
Definition lam.h:237
static auto uncurry_args(const Def *def)
Definition lam.h:274
const Def * uncurry_callee() const
Definition lam.h:279
const Def * arg() const
Definition lam.h:251
Definition axm.h:9
static const Def * coerce(const Def *type, const Def *value)
Can value be assigned to sth of type?
Definition check.h:105
Base class for all Defs.
Definition def.h:289
bool is_set() const
Definition def.h:396
World & world() const noexcept
Definition def.h:1105
constexpr auto ops() const noexcept
Definition def.h:374
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:588
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:492
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
bool is_external() const noexcept
Definition def.h:569
constexpr auto reduce(const Def *arg) const
Definition def.h:681
const Var * has_var()
Only returns not nullptr, if Var of this mutable has ever been created.
Definition def.h:496
Flags & flags()
Definition driver.h:77
const fe::Vector< std::string > & args(std::string_view plugin) const
Yields an empty fe::Vector if plugin has none.
Definition driver.cpp:221
fe::Profiler & profiler()
Definition driver.h:81
auto phase(flags_t flags)
Definition driver.h:211
A function.
Definition lam.h:91
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:34
const Pi * type() const
Definition lam.h:112
static std::optional< T > isa(const Def *def)
Definition def.h:976
static T as(const Def *def)
Definition def.h:982
PhaseMan(World &world, flags_t annex)
Definition phase.h:483
bool fixed_point() const
Definition phase.h:493
auto & phases()
Definition phase.h:494
void start() final
Actual entry.
Definition phase.cpp:346
void apply(bool, Phases &&)
Definition phase.cpp:322
virtual std::unique_ptr< Phase > recreate()
Creates a new instance; needed by a fixed-point PhaseMan.
Definition phase.cpp:44
std::string name_
Definition phase.h:104
void invalidate(bool todo=true)
Signals that another round of fixed-point iteration is required, either as part of.
Definition phase.h:79
flags_t annex() const
Definition phase.h:62
const fe::Log & log() const
Definition phase.h:60
Phase(World &world, std::string name)
Definition phase.h:27
void profile_count(std::string_view key, uint64_t n=1)
Adds n to the custom fe::Profiler counter key of the current run; no-op unless profiling is enabled.
Definition phase.cpp:60
Driver & driver()
Definition phase.h:59
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
Definition phase.cpp:51
static std::unique_ptr< Phase > create(const Flags2Phases &phases, const Def *def)
Definition phase.cpp:25
bool todo() const
Definition phase.h:71
std::string_view name() const
Definition phase.h:61
virtual void start()=0
Actual entry.
World & world()
Definition phase.h:58
A dependent function type.
Definition lam.h:14
virtual bool analyze()
Runs the optional pre-analysis on old_world(), typically to a fixed point, before rewriting begins.
Definition phase.cpp:202
World & new_world()
Create new Defs into this.
Definition phase.h:379
virtual void finalize()
Run after all roots have been walked but still before the two worlds are swapped.
Definition phase.h:406
const Def * lookup(const Def *) override
Lookup old_def by searching in reverse through the stack of maps.
Definition phase.cpp:212
const Def * rewrite_imm_Tuple(const Tuple *) override
Definition phase.cpp:237
static bool splittable(const Def *type)
Does coerce() explode and reassemble values of type?
Definition phase.h:399
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
void start() override
Actual entry.
Definition phase.cpp:184
const Def * rewrite_stub(Def *, Def *) override
Definition phase.cpp:250
World & old_world()
Get old Defs from here.
Definition phase.h:378
virtual void rewrite_external(Def *)
Definition phase.cpp:218
const Def * rewrite_imm_App(const App *) override
Definition phase.cpp:223
fe::Vector< Scope > scopes_
Definition rewrite.h:127
friend void swap(Rewriter &rw1, Rewriter &rw2) noexcept
Definition rewrite.h:133
virtual const Def * rewrite_mut(Def *)
Definition rewrite.cpp:83
virtual const Def * rewrite_stub(Def *, Def *)
Definition rewrite.cpp:257
const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.cpp:29
const Def * seal(Def *old_mut, Def *new_mut)
The tail of rewrite_stub: immutabilizes new_mut in hindsight, as rewriting may have made it vacuous.
Definition rewrite.cpp:280
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:62
auto enter(Def *new_mut)
Updates curr_mut() to new_mut and restores it at the end of the scope.
Definition rewrite.h:115
void push(Vars old_vars={}, Vars new_vars={})
Definition rewrite.h:40
Def2Def & old2new()
The map of the innermost push() scope.
Definition rewrite.h:104
virtual const Def * lookup(const Def *old_def)
Lookup old_def by searching in reverse through the stack of maps.
Definition rewrite.h:66
Data constructor for a Sigma.
Definition tuple.h:84
static const Def * infer(World &, Defs)
Definition check.cpp:488
const flags_t * opaque(const Def *d) const
Like flags() but only for an Axm or a non-Lam mutable - whose identity phases must preserve.
Definition world.h:245
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
const Def * app(const Def *callee, const Def *arg)
Definition world.cpp:416
u32 curr_gid() const
Manage global identifier - a unique number for each Def.
Definition world.h:118
World & verify()
Verifies that all externals() and annexes() are Def::is_closed(), if MIM_ENABLE_CHECKS.
Definition world.cpp:1262
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Definition world.cpp:224
const Def * tuple(Defs ops)
Definition world.cpp:516
Annexes & annexes()
Definition world.h:289
auto lenient() const
Definition world.h:168
Lam * mut_lam(const Pi *pi)
Definition world.h:415
Definition ast.h:16
u64 flags_t
Definition types.h:39
fe::View< const Def * > Defs
Definition def.h:96
static bool has_inferred_type(const Tuple *tuple)
Is tuple's type what World::tuple would infer from its ops?
Definition phase.cpp:232
fe::Vector< const Def * > DefVec
Definition def.h:98
std::deque< std::unique_ptr< Phase > > Phases
Definition phase.h:18
ankerl::unordered_dense::map< flags_t, std::function< std::unique_ptr< Phase >(World &)> > Flags2Phases
Maps an axiom of a Phase to a function that creates one.
Definition plugin.h:30
@ Pi
Definition def.h:125
static std::string demangle(plugin_t plugin)
Reverts an Axm::mangled plugin back to its name; never longer than Annex::Max_Plugin_Size.
Definition plugin.cpp:29
static constexpr plugin_t flags2plugin(flags_t f)
Definition plugin.h:254
@ None
No profiling.
Definition flags.h:14
uint32_t max_fp_iters
Definition flags.h:21
Profile profile
Definition flags.h:28