MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
phase.h
Go to the documentation of this file.
1#pragma once
2
3#include <memory>
4
5#include <fe/assert.h>
6#include <fe/cast.h>
7
8#include "mim/def.h"
9#include "mim/nest.h"
10#include "mim/rewrite.h"
11#include "mim/world.h"
12
13namespace mim {
14
15class Nest;
16class Phase;
17class PhaseMan;
18class World;
19
20using Phases = std::deque<std::unique_ptr<Phase>>;
21
22/// A Phase performs one self-contained task over the whole World.
23/// Phases are intended to run in a classical sequence, one after another.
24/// @see @ref phases_phase
25class Phase : public fe::RuntimeCast<Phase> {
26public:
27 /// @name Construction & Destruction
28 ///@{
29 Phase(World& world, std::string name)
30 : world_(world)
31 , name_(std::move(name)) {}
33
34 virtual ~Phase() = default;
35
36 virtual std::unique_ptr<Phase> recreate(); ///< Creates a new instance; needed by a fixed-point PhaseMan.
37 virtual void apply(const App*) {} ///< Invoked if your Phase has additional args.
38 virtual void apply(Phase&) {} ///< Dito, but invoked by Phase::recreate.
39
40 /// @name Redirection
41 /// A Phase may resolve to a *different* Phase (or to nothing) after Phase::apply.
42 /// This is used by the `compile.named` stage that resolves a string to another plugin's annex.
43 ///@{
44 virtual bool redirects() const { return false; } ///< If `true`, Phase::create uses take_resolved().
45 virtual std::unique_ptr<Phase> take_resolved() {
46 return {};
47 } ///< The Phase to use instead; `nullptr` means *elide*.
48 ///@}
49
50 static std::unique_ptr<Phase> create(const Flags2Phases& phases, const Def* def) {
51 auto& world = def->world();
52 auto p_def = App::uncurry_callee(def);
53 world.log().d("apply phase {}", p_def);
54
55 if (auto axm = p_def->isa<Axm>())
56 if (auto i = phases.find(axm->flags()); i != phases.end()) {
57 auto phase = i->second(world);
58 if (phase) {
59 phase->apply(def->isa<App>());
60 if (phase->redirects()) return phase->take_resolved();
61 }
62 return phase;
63 } else
64 fe::throwf("phase `{}` not found", axm->sym());
65 else
66 fe::throwf("unsupported callee for a phase: `{}`", p_def);
67 }
68
69 template<class A, class P>
70 static void hook(Flags2Phases& phases) {
71 fe::assert_emplace(phases, Annex::base<A>(), [](World& w) { return std::make_unique<P>(w, Annex::base<A>()); });
72 }
73 ///@}
74
75 /// @name Getters
76 ///@{
77 World& world() { return world_; }
78 Driver& driver() { return world().driver(); }
79 const fe::Log& log() const { return world_.log(); }
80 std::string_view name() const { return name_; }
81 flags_t annex() const { return annex_; }
82
83 /// Command-line arguments passed to this Phase's plugin via `-X <plugin>:<arg>`.
84 /// Derived from Phase::annex; yields an empty fe::Vector for name-constructed Phase%s.
85 const fe::Vector<std::string>& args();
86 ///@}
87
88 /// @name Fixed-Point Handling
89 ///@{
90 bool todo() const { return todo_; }
91
92 /// Signals that another round of fixed-point iteration is required, either
93 /// as part of
94 /// - a pipeline managed by PhaseMan, or
95 /// - the optional pre-analysis of an RWBase.
96 ///
97 /// Calling `invalidate(todo)` bitwise-ORs @p todo into the internal `todo_` flag.
98 void invalidate(bool todo = true) { todo_ |= todo; }
99 ///@}
100
101 /// @name run
102 ///@{
103 virtual void run(); ///< Entry point and generates some debug output; invokes Phase::start.
104 virtual void start() = 0; ///< Actual entry.
105
106 /// Runs a single Phase.
107 template<class P, class... Args>
108 static void run(Args&&... args) {
109 P p(std::forward<Args>(args)...);
110 p.run();
111 }
112
113 /// Adds @p n to the custom fe::Profiler counter @p key of the current run; no-op unless profiling is enabled.
114 void profile_count(std::string_view key, uint64_t n = 1);
115 ///@}
116
117private:
118 World& world_;
119 flags_t annex_ = 0;
120 bool todo_ = false;
121
122protected:
123 std::string name_;
124
125 friend class Analysis;
126};
127
128/// Traverses the current World using Rewriter infrastructure while staying in the same world.
129///
130/// It recursively rewrites
131/// 1. all World::annexes() (during which Analysis::is_bootstrapping() is `true`), and then
132/// 2. all World::externals() (during which it is `false`).
133///
134/// Analysis provides a reusable lattice() mapping old Def%s to abstract values, represented as ordinary MimIR Def%s.
135///
136/// Fixed-point iteration is *sparse* by default:
137/// whenever a lattice write changes observable information, the mutable currently being drained is recorded as *dirty*.
138/// The next round then re-drains only those dirty mutables - plus everything reachable from them - instead of walking
139/// the whole World. At the start of such a round, the accumulated lattice() is replayed into the rewriter map, so a
140/// dirty mutable's body sees the substitutions its (non-revisited) producers installed in earlier rounds. Since dirt
141/// tracks *writers* - not readers - a sparse round may miss affected mutables; hence, once sparse rounds quiesce, one
142/// final **full** round certifies the fixed point.
143/// That replay only *prunes* the walk; what the lattice means is applied by rewrite() through repr(), in every kind of
144/// round alike - see repr() for why an analysis whose values are expressions needs that.
145/// @note You can override
146/// - Rewriter::rewrite(),
147/// - Rewriter::rewrite_imm(),
148/// - Rewriter::rewrite_mut(), etc.
149/// @see @ref phases_analysis
150/// @see @ref ssa-without-dominance for how an Analysis substitutes Def::nests for the classical SSA dominance check.
151class Analysis : public Phase, public Rewriter {
152public:
153 /// @name Construction & Destruction
154 ///@{
155 Analysis(World& world, std::string name)
156 : Phase(world, std::move(name))
157 , Rewriter(world) {}
161
162 /// Clears the rewriter map and resets Phase::todo() and is_bootstrapping() for the next fixed-point iteration.
163 /// lattice() is **preserved** across iterations so that abstract values accumulated in earlier
164 /// rounds remain available - this is what makes fixed-point convergence possible.
165 /// The dirty set survives as well; start() consumes it to decide whether the round can be sparse.
166 /// @see RWBase::analyze
167 virtual void reset();
168 ///@}
169
170 /// @name Getters
171 ///@{
172 using Phase::world; ///< Disambiguates the Phase/Rewriter double base; for an Analysis both denote the same World.
173 bool is_bootstrapping() const { return bootstrapping_; }
174 ///@}
175
176 /// @name Sparse Fixed-Point Iteration
177 ///@{
178 bool is_sparse() const {
179 return curr_sparse_;
180 } ///< Does the current round only re-drain last round's dirty mutables?
181 void make_dense() { dense_ = true; } ///< Forces whole-World rounds unconditionally.
182 /// Bumped on every observable lattice change; snapshot it around a code region to detect changes.
183 size_t version() const { return version_; }
184 ///@}
185
186 /// @name lattice
187 /// Conventions: *absent* = ⊥ (nothing known); `def ↦ def` = ⊤ (keep as is).
188 /// Subclasses may store their own sentinels in between as ordinary Def%s (e.g. SEO's GVN-bundle Proxy%s).
189 ///@{
190 const auto& lattice() const { return lattice_; } ///< The whole map; used e.g. to diff two fixed-point runs.
191
192 /// @returns the abstract value recorded for @p def, or `nullptr` if unknown.
193 const Def* lattice(const Def* def) const {
194 if (auto i = lattice_.find(def); i != lattice_.end()) return i->second;
195 return nullptr;
196 }
197
198 /// The representative of @p def: follows `def ↦ lattice(def)` to the end of its chain.
199 ///
200 /// The lattice records *expressions*, so one and the same abstract value can be spelled at several depths
201 /// of a chain (a var, and the value it was propagated to).
202 /// repr() collapses those spellings to one def, so that comparing two of them answers whether they mean the same
203 /// value - not whether they were derived by the same route. This is union-find's *find* without the path
204 /// compression: the chain is what the lattice means, and its links keep changing between rounds - see
205 /// Analysis::lattice_force.
206 const Def* repr(const Def* def) const {
207 auto a = follow(def);
208 if (!a) return def; // ⊥ or ⊤
209 auto b = follow(a);
210 return b ? repr_(a, b) : a;
211 }
212
213 /// @returns whether @p def is pinned to ⊤ (`def ↦ def`).
214 bool is_top(const Def* def) const {
215 auto i = lattice_.find(def);
216 return i != lattice_.end() && i->second == def;
217 }
218 ///@}
219
220protected:
221 /// @name lattice
222 ///@{
223
224 /// **Non-monotone** write of `concr ↦ abstr` into lattice() and map().
225 /// This is the escape hatch for analyses that must overwrite an earlier round's value (descending from ⊤ is fine).
226 /// invalidate()s iff the stored value changed; an *absent* entry counts as changed - even for ⊤ -
227 /// since a non-monotone lattice's consumers may well distinguish ⊥ from ⊤.
228 /// Every change also touch()es curr_mut() - the seed set of the next sparse round.
229 /// @returns `true` iff this changed the entry - i.e. iff it invalidate()d.
230 bool lattice_force(const Def* concr, const Def* abstr) {
231 map(concr, abstr);
232
233 if (auto [i, ins] = lattice_.emplace(concr, abstr); !ins) {
234 if (i->second == abstr) return false;
235 i->second = abstr;
236 }
237 return touch(), true;
238 }
239
240 /// Writes `concr ↦ abstr` into lattice() and map().
241 /// invalidate()s - and thereby triggers another fixed-point round - iff this changes observable information:
242 /// an existing entry was overwritten, or a fresh fact other than ⊤ was inserted.
243 /// Freshly inserting ⊤ (`concr ↦ concr`) stays silent, as it is indistinguishable from *absent* for consumers.
244 /// Every change also touch()es curr_mut() - the seed set of the next sparse round.
245 /// @returns `true` iff this changed observable information - i.e. iff it invalidate()d.
246 bool lattice(const Def* concr, const Def* abstr) {
247 map(concr, abstr);
248
249 if (auto [i, ins] = lattice_.emplace(concr, abstr); !ins) {
250 assert((i->second != concr || abstr == concr) && "monotonicity violation: must not descend from ⊤");
251 if (i->second == abstr) return false;
252 i->second = abstr;
253 } else if (concr == abstr) {
254 return false;
255 }
256 return touch(), true;
257 }
258
259 /// Monotonically forces @p def to ⊤ (keep as is).
260 /// @returns `true` iff this changed observable information - i.e. iff it invalidate()d.
261 bool pin(const Def* def) { return lattice(def, def); }
262
263 /// Additionally schedules @p mut for the next sparse round.
264 /// Use this when a lattice change must re-visit *other* mutables than curr_mut() -
265 /// e.g. all call sites of a Lam whose var's abstract value changed.
266 void taint(Def* mut) { dirty_.emplace(mut); }
267 ///@}
268
269 /// @name Rewrite
270 ///@{
271 void start() override;
272 virtual void prepare() {} ///< Run **before** the main analysis.
273 /// Run **after** the main analysis - only in **full** rounds, so it always sees the complete abstract World.
274 virtual void finalize() {}
275 virtual void rewrite_annex(flags_t, Sym, const Def*);
276 virtual void rewrite_external(Def*);
277 const Def* rewrite_imm_Proxy(const Proxy* proxy) override { return proxy; } ///< By default: ignore Proxy%s.
278
279 /// Rewrites @p def and then maps the result to its repr().
280 ///
281 /// This is how the lattice is applied: at the *point of use*, and only after the structural rewrite - so an
282 /// Analysis's hooks always see the program itself. Pre-installing the substitutions into map() instead
283 /// would skip those hooks, and only a sparse round would do so - putting the two kinds of round in
284 /// different regimes, where they spell one and the same abstract value differently.
285 const Def* rewrite(const Def*) override;
286
287 /// Schedules @p mut for a breadth-first visit of its dependencies and records `mut -> mut`.
288 /// Mutables are enqueued instead of recursed into; Analysis::drain then walks them in BFS order.
289 /// The `mut -> mut` entry doubles as the per-round "already scheduled" marker (Rewriter::old2news_ is
290 /// cleared by reset()), so each mutable's deps are visited at most once per fixed-point round.
291 Def* rewrite_mut(Def*) override;
292 virtual void leave() {} ///< Called after curr_mut() has been completely dealt with.
293 ///@}
294
295private:
296 /// The next def on @p def's chain, or `nullptr` if @p def ends it (⊥ or ⊤).
297 const Def* follow(const Def* def) const {
298 auto i = lattice_.find(def);
299 return i == lattice_.end() || i->second == def ? nullptr : i->second;
300 }
301
302 /// The tail of repr() past its first two links: chases @p slow and @p fast until the chain ends or they
303 /// meet in a cycle, whose minimum-gid member is its (entry-independent) representative.
304 const Def* repr_(const Def* slow, const Def* fast) const;
305
306 /// Observable lattice information changed: records curr_mut() as *dirty* - the seed set of the next sparse
307 /// round - and invalidate()s. Outside of any mutable (annex walk, finalize()) the change cannot be
308 /// attributed to a mutable; then the next round falls back to a full one.
309 void touch() {
310 ++version_;
311 if (auto mut = curr_mut())
312 dirty_.emplace(mut);
313 else
314 nonlocal_ = true;
315 invalidate();
316 }
317
318 /// Walks all enqueued mutables' dependencies - in BFS order - under each mutable's curr_mut() scope.
319 void drain();
320
321 Def2Def lattice_;
322 std::deque<Def*> worklist_;
323 MutSet dirty_; ///< Muts whose drain changed the lattice this round; seeds the next sparse round.
324 size_t version_ = 0; ///< @see version()
325 bool nonlocal_ = false; ///< The lattice changed outside of any mut; the next round must be a full one.
326 bool curr_sparse_ = false; ///< Is the current round sparse?
327 bool dense_ = false; ///< @see make_dense()
328 bool bootstrapping_ = true;
329 size_t num_drained_ = 0; ///< muts drained this round; flushed into the fe::Profiler
330};
331
332/// Common base of the two rewriting Phase%s: RWPhase rebuilds the World, InplaceRWPhase stays in it.
333///
334/// Both are a Phase *and* a Rewriter, both run an optional analyze() to a fixed point, and both then rewrite
335/// 1. all World::annexes() - if rewrite_annexes() says so - during which is_bootstrapping() is `true`, and then
336/// 2. all World::externals() during which it is `false`.
337///
338/// If an associated Analysis is provided, the rewrite can query its abstract results through lattice().
339///
340/// @note You can override
341/// - Rewriter::rewrite(),
342/// - Rewriter::rewrite_imm(),
343/// - Rewriter::rewrite_mut(), etc.
344/// @see @ref phases_rwbase
345class RWBase : public Phase, public Rewriter {
346protected:
347 /// @name Construction
348 /// Rewrite **in place**: Phase::world and Rewriter::world are the same.
349 ///@{
351 : Phase(world, std::move(name))
352 , Rewriter(world)
353 , analysis_(analysis) {}
358
359 /// Rewrite the World of Phase::world **into** @p new_world.
360 RWBase(World& world, std::string name, Analysis* analysis, std::unique_ptr<World>&& new_world)
361 : Phase(world, std::move(name))
362 , Rewriter(std::move(new_world))
363 , analysis_(analysis) {}
364 RWBase(World& world, flags_t annex, Analysis* analysis, std::unique_ptr<World>&& new_world)
365 : Phase(world, annex)
366 , Rewriter(std::move(new_world))
367 , analysis_(analysis) {}
368 ///@}
369
370public:
371 /// @name Analysis
372 ///@{
373 Analysis* analysis() { return analysis_; }
374 const Analysis* analysis() const { return analysis_; }
375
376 /// Returns the abstract value computed by the associated Analysis for @p def, or `nullptr` if no value is
377 /// available.
378 /// @note @p def is a Def of the World the Analysis ran on - the **old** one in the case of an RWPhase.
379 const Def* lattice(const Def* def) const { return analysis_ ? analysis_->lattice(def) : nullptr; }
380
381 /// Returns lattice(@p def) if it differs from @p def (i.e. we learned something), otherwise `nullptr`.
382 const Def* abstracted(const Def* def) const {
383 auto l = lattice(def);
384 return l && l != def ? l : nullptr;
385 }
386
387 /// Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
388 ///
389 /// If analysis() is set, this is the natural place to iterate until Phase::todo() becomes `false`.
390 /// If no Analysis is needed, simply return `false`.
391 virtual bool analyze();
392 ///@}
393
394 /// @name Rewrite
395 ///@{
396 /// Should start() walk the annex roots as well?
397 virtual bool rewrite_annexes() const = 0;
398 virtual void rewrite_annex(flags_t, Sym, const Def*) = 0;
399 virtual void rewrite_external(Def*) = 0;
400
401 /// Returns whether we are currently bootstrapping (rewriting annexes).
402 /// While bootstrapping, you have to skip rewrites that refer to other annexes, as they might not yet be available.
403 bool is_bootstrapping() const { return bootstrapping_; }
404 ///@}
405
406protected:
407 void start() override;
408
409 /// Rewrites a *root* - i.e.\ an annex or an external.
410 /// Defaults to rewrite(); override if roots need to be exempt from some of your rewrites.
411 virtual const Def* rewrite_root(const Def* def) { return rewrite(def); }
412
413 /// Run **after** all roots have been walked - but for an RWPhase still **before** the two worlds are swapped.
414 /// This is where you drain a worklist of rewrites your hooks deferred (see e.g. clos::phase::ClosConv).
415 virtual void finalize() {}
416
417private:
418 Analysis* analysis_;
419 bool bootstrapping_ = true;
420};
421
422/// Rebuilds old_world() into new_world() and then swaps them.
423///
424/// During bootstrapping, rewrites that depend on other annexes may need to be skipped,
425/// since those annexes might not yet exist in the new world.
426/// @see @ref phases_rwphase
427class RWPhase : public RWBase {
428public:
429 /// @name Construction
430 ///@{
431 RWPhase(World& world, std::string name, Analysis* analysis = nullptr)
432 : RWBase(world, std::move(name), analysis, world.inherit()) {}
435 ///@}
436
437 /// @name Rewrite
438 ///@{
439 void rewrite_annex(flags_t, Sym, const Def*) override;
440 void rewrite_external(Def*) override;
441 ///@}
442
443 /// @name World
444 /// * Phase::world is the **old** one.
445 /// * Rewriter::world is the **new** one.
446 /// * RWPhase::world is deleted to not confuse this.
447 ///@{
448 using Phase::world;
449 using Rewriter::world;
450 World& world() = delete; ///< Hides both and forbids direct access.
451 World& old_world() { return Phase::world(); } ///< Get **old** Def%s from here.
452 World& new_world() { return Rewriter::world(); } ///< Create **new** Def%s into this.
453 ///@}
454
455protected:
456 void start() override; ///< RWBase::start() and then swaps the two worlds.
457
458private:
459 /// An RWPhase *has* to walk the annexes: it must re-create every one of them to populate new_world()'s table.
460 bool rewrite_annexes() const final { return true; }
461};
462
463/// Rewrites the **current** World **in place** - unlike an RWPhase, which rebuilds a new World.
464///
465/// A *mutable* keeps its identity: only its ops() are Def::set anew, and only if the rewrite actually changed them.
466/// So hash-consing makes every unaffected Def free instead of a per-run rebuild tax.
467/// A mutable whose *type* changes is the one exception - identity is tied to the type - and falls back to an
468/// RWPhase-style stub rebuild in this same World.
469/// This matters most for the annex graph: it is proportional to the loaded plugins - not to the program - and a
470/// *local* rewrite never touches it, yet an RWPhase re-creates all of it on **every** run.
471///
472/// Prune subtrees that provably cannot change - e.g. with Def::is_ground - to turn the traversal from
473/// *"hash-cons every node"* into *"touch only what matters"*.
474///
475/// Since a change is only ever committed if it really is one, Phase::todo() is exact: a quiet run costs a pruned
476/// traversal and nothing else.
477///
478/// @warning An InplaceRWPhase
479/// * cannot immutabilize a mutable that the rewrite made vacuous (unless it takes the type-change fallback),
480/// * must not hand out a fresh identity for something already in its target shape - that would never converge, and
481/// * leaves what it replaced behind as garbage until the next Cleanup.
482///
483/// Use an RWPhase for anything else.
484/// @see @ref phases_inplace_rw_phase
485class InplaceRWPhase : public RWBase {
486public:
487 /// @name Construction
488 ///@{
489 InplaceRWPhase(World& world, std::string name, Analysis* analysis = nullptr)
490 : RWBase(world, std::move(name), analysis) {}
493 ///@}
494
495 /// @name Getters
496 ///@{
497 using Phase::world; ///< Disambiguates the Phase/Rewriter double base; for an InplaceRWPhase both are the same.
498 ///@}
499
500 /// @name Rewrite
501 ///@{
502 /// An RWPhase *has* to walk the annexes; an InplaceRWPhase finds that table already correct, so the annex graph -
503 /// which is proportional to the loaded plugins, not to the program - is pure extra coverage here, and a *local*
504 /// rewrite gains nothing from it: whatever the program actually uses is reached through the externals anyway.
505 /// Hence this defaults to `false`; say `true` if your rewrite must also see *unused* annexes.
506 bool rewrite_annexes() const override { return false; }
507
508 void rewrite_annex(flags_t, Sym, const Def*) override;
509 void rewrite_external(Def*) override;
510 ///@}
511
512protected:
513 /// @name Rewrite
514 ///@{
515 /// Keeps @p mut's identity and Def::set%s its ops anew iff rewriting them changed anything.
516 const Def* rewrite_mut(Def* mut) override;
517 ///@}
518};
519
520/// An RWPhase that searches for a pattern and replaces it.
521/// Implement the replace() hook - or use the MIM_REPL macro for an inline definition.
522class Repl : public RWPhase {
523public:
526
527 /// replace() inspects and builds Def%s of the **old** world; the RWPhase machinery carries the result over.
528 World& world() { return old_world(); }
529
530 /// @returns the replacement or `nullptr` if the pattern does not match.
531 virtual const Def* replace(const Def* def) = 0;
532
533private:
534 const Def* rewrite(const Def* def) final {
535 for (bool todo = true; todo;) {
536 todo = false;
537 if (auto subst = replace(def)) todo = true, def = subst;
538 }
539
540 return Rewriter::rewrite(def);
541 }
542};
543
544#define MIM_CONCAT_INNER(a, b) a##b
545#define MIM_CONCAT(a, b) MIM_CONCAT_INNER(a, b)
546
547#define MIM_REPL(__phases, __annex, ...) MIM_REPL_IMPL(__phases, __annex, __LINE__, __VA_ARGS__)
548
549// clang-format off
550#define MIM_REPL_IMPL(__phases, __annex, __id, ...) \
551 struct MIM_CONCAT(Repl_, __id) : ::mim::Repl { \
552 MIM_CONCAT(Repl_, __id)(::mim::World & world, ::mim::flags_t annex) \
553 : Repl(world, annex) {} \
554 \
555 const ::mim::Def* replace(const ::mim::Def* def) final __VA_ARGS__ \
556 }; \
557 ::mim::Phase::hook<__annex, MIM_CONCAT(Repl_, __id)>(__phases)
558// clang-format on
559
560/// Removes unreachable and dead code by rebuilding the whole World into a new one and `swap`ping them afterwards.
561/// @see @ref phases_rwphase
562class Cleanup : public RWPhase {
563public:
565 : RWPhase(world, "cleanup") {}
568};
569
570/// Organizes several Phase%s into a pipeline.
571/// If fixed_point() is `true`, rerun the whole pipeline until all Phase::todo()%s flags remain `false`.
572/// @see @ref phases_phase_man
573class PhaseMan : public Phase {
574public:
575 /// @name Construction
576 ///@{
579
580 void apply(bool, Phases&&);
581 void apply(const App*) final;
582 void apply(Phase&) final;
583 ///@}
584
585 /// @name Getters
586 ///@{
587 bool fixed_point() const { return fixed_point_; }
588 auto& phases() { return phases_; }
589 const auto& phases() const { return phases_; }
590 ///@}
591
592private:
593 void start() final;
594
595 Phases phases_;
596 bool fixed_point_;
597};
598
599/// Transitively visits all *reachable*, [*closed*](@ref Def::is_closed) mutables in the World.
600/// * Select with `elide_empty` whether you want to visit trivial mutables without body.
601/// * Set `schedule` if the mutables should be scheduled to ensure a correct order of dependencies.
602/// * If you are only interested in specific mutables, you can pass this to @p M.
603/// @see @ref phases_closed_mut_phase
604template<class M = Def>
605class ClosedMutPhase : public Phase {
606public:
607 ClosedMutPhase(World& world, std::string name, bool elide_empty, bool schedule = false)
608 : Phase(world, std::move(name))
609 , elide_empty_(elide_empty)
610 , schedule_(schedule) {}
612 : Phase(world, annex)
613 , elide_empty_(elide_empty)
614 , schedule_(schedule) {}
615
616 bool elide_empty() const { return elide_empty_; }
617 bool schedule() const { return schedule_; }
618
619protected:
620 void start() override {
621 world().template for_each<M>(elide_empty(), [this](M* mut) { root_ = mut, visit(mut); }, schedule());
622 }
623 virtual void visit(M*) = 0;
624 M* root() const { return root_; }
625
626private:
627 const bool elide_empty_;
628 const bool schedule_;
629 M* root_;
630};
631
632/// Like ClosedMutPhase but computes a Nest for each NestPhase::visit.
633/// @see @ref phases_nest_phase
634template<class M = Def>
635class NestPhase : public ClosedMutPhase<M> {
636public:
637 NestPhase(World& world, std::string name, bool elide_empty, bool schedule = false)
638 : ClosedMutPhase<M>(world, std::move(name), elide_empty, schedule) {}
641
642 const Nest& nest() const { return *nest_; }
643 virtual void visit(const Nest&) = 0;
644
645private:
646 void visit(M* mut) final {
647 Nest nest(mut);
648 nest_ = &nest;
649 visit(nest);
650 }
651
652 const Nest* nest_;
653};
654
655} // namespace mim
Traverses the current World using Rewriter infrastructure while staying in the same world.
Definition phase.h:151
virtual void prepare()
Run before the main analysis.
Definition phase.h:272
size_t version() const
Bumped on every observable lattice change; snapshot it around a code region to detect changes.
Definition phase.h:183
void start() override
Actual entry.
Definition phase.cpp:57
bool lattice_force(const Def *concr, const Def *abstr)
Non-monotone write of concr ↦ abstr into lattice() and map().
Definition phase.h:230
const Def * rewrite(const Def *) override
Rewrites def and then maps the result to its repr().
Definition phase.cpp:121
const Def * rewrite_imm_Proxy(const Proxy *proxy) override
By default: ignore Proxys.
Definition phase.h:277
Analysis(World &world, std::string name)
Definition phase.h:155
void make_dense()
Forces whole-World rounds unconditionally.
Definition phase.h:181
virtual void leave()
Called after curr_mut() has been completely dealt with.
Definition phase.h:292
bool lattice(const Def *concr, const Def *abstr)
Writes concr ↦ abstr into lattice() and map().
Definition phase.h:246
void taint(Def *mut)
Additionally schedules mut for the next sparse round.
Definition phase.h:266
virtual void rewrite_annex(flags_t, Sym, const Def *)
Definition phase.cpp:102
const Def * lattice(const Def *def) const
Definition phase.h:193
bool is_top(const Def *def) const
Definition phase.h:214
virtual void rewrite_external(Def *)
Definition phase.cpp:103
bool is_sparse() const
Does the current round only re-drain last round's dirty mutables?
Definition phase.h:178
virtual void reset()
Clears the rewriter map and resets Phase::todo() and is_bootstrapping() for the next fixed-point iter...
Definition phase.cpp:49
virtual void finalize()
Run after the main analysis - only in full rounds, so it always sees the complete abstract World.
Definition phase.h:274
const auto & lattice() const
The whole map; used e.g. to diff two fixed-point runs.
Definition phase.h:190
bool pin(const Def *def)
Monotonically forces def to ⊤ (keep as is).
Definition phase.h:261
bool is_bootstrapping() const
< Disambiguates the Phase/Rewriter double base; for an Analysis both denote the same World.
Definition phase.h:173
Analysis(World &world, flags_t annex)
Definition phase.h:158
const Def * repr(const Def *def) const
The representative of def: follows def ↦ lattice(def) to the end of its chain.
Definition phase.h:206
Def * rewrite_mut(Def *) override
Schedules mut for a breadth-first visit of its dependencies and records mut -> mut.
Definition phase.cpp:126
World & world()
Definition phase.h:77
const Def * uncurry_callee() const
Definition lam.h:326
Definition axm.h:9
Cleanup(World &world, flags_t annex)
Definition phase.h:566
Cleanup(World &world)
Definition phase.h:564
M * root() const
Definition phase.h:624
void start() override
Actual entry.
Definition phase.h:620
ClosedMutPhase(World &world, flags_t annex, bool elide_empty, bool schedule=false)
Definition phase.h:611
bool schedule() const
Definition phase.h:617
bool elide_empty() const
Definition phase.h:616
virtual void visit(M *)=0
ClosedMutPhase(World &world, std::string name, bool elide_empty, bool schedule=false)
Definition phase.h:607
Base class for all Defs.
Definition def.h:273
World & world() const noexcept
Definition def.h:1097
Some "global" variables needed all over the place.
Definition driver.h:63
const Def * rewrite_mut(Def *mut) override
Definition phase.cpp:228
InplaceRWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:489
bool rewrite_annexes() const override
Definition phase.h:506
InplaceRWPhase(World &world, flags_t annex, Analysis *analysis=nullptr)
Definition phase.h:491
void rewrite_annex(flags_t, Sym, const Def *) override
Definition phase.cpp:211
void rewrite_external(Def *) override
Definition phase.cpp:218
World & world()
Definition phase.h:77
virtual void visit(const Nest &)=0
const Nest & nest() const
Definition phase.h:642
void visit(M *mut) final
Definition phase.h:646
NestPhase(World &world, std::string name, bool elide_empty, bool schedule=false)
Definition phase.h:637
NestPhase(World &world, flags_t annex, bool elide_empty, bool schedule=false)
Definition phase.h:639
Builds a nesting tree for all mutables/binders.
Definition nest.h:31
Organizes several Phases into a pipeline.
Definition phase.h:573
PhaseMan(World &world, flags_t annex)
Definition phase.h:577
bool fixed_point() const
Definition phase.h:587
auto & phases()
Definition phase.h:588
void start() final
Actual entry.
Definition phase.cpp:284
const auto & phases() const
Definition phase.h:589
void apply(bool, Phases &&)
Definition phase.cpp:260
A Phase performs one self-contained task over the whole World.
Definition phase.h:25
virtual std::unique_ptr< Phase > recreate()
Creates a new instance; needed by a fixed-point PhaseMan.
Definition phase.cpp:25
std::string name_
Definition phase.h:123
static void hook(Flags2Phases &phases)
Definition phase.h:70
friend class Analysis
Definition phase.h:125
void invalidate(bool todo=true)
Signals that another round of fixed-point iteration is required, either as part of.
Definition phase.h:98
flags_t annex() const
Definition phase.h:81
const fe::Log & log() const
Definition phase.h:79
Phase(World &world, std::string name)
Definition phase.h:29
static void run(Args &&... args)
Runs a single Phase.
Definition phase.h:108
static std::unique_ptr< Phase > create(const Flags2Phases &phases, const Def *def)
Definition phase.h:50
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:41
virtual std::unique_ptr< Phase > take_resolved()
The Phase to use instead; nullptr means elide.
Definition phase.h:45
virtual void apply(Phase &)
Dito, but invoked by Phase::recreate.
Definition phase.h:38
Driver & driver()
Definition phase.h:78
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:32
bool todo() const
Definition phase.h:90
std::string_view name() const
Definition phase.h:80
virtual void start()=0
Actual entry.
virtual void apply(const App *)
Invoked if your Phase has additional args.
Definition phase.h:37
virtual bool redirects() const
If true, Phase::create uses take_resolved().
Definition phase.h:44
virtual ~Phase()=default
World & world()
Definition phase.h:77
Used as intermediate value during optimizatinos such as Analysis.
Definition def.h:1032
Analysis * analysis()
Definition phase.h:373
virtual void finalize()
Run after all roots have been walked - but for an RWPhase still before the two worlds are swapped.
Definition phase.h:415
const Def * lattice(const Def *def) const
Returns the abstract value computed by the associated Analysis for def, or nullptr if no value is ava...
Definition phase.h:379
virtual void rewrite_external(Def *)=0
const Analysis * analysis() const
Definition phase.h:374
RWBase(World &world, flags_t annex, Analysis *analysis, std::unique_ptr< World > &&new_world)
Definition phase.h:364
void start() override
Actual entry.
Definition phase.cpp:151
RWBase(World &world, std::string name, Analysis *analysis)
Definition phase.h:350
RWBase(World &world, flags_t annex, Analysis *analysis)
Definition phase.h:354
virtual bool rewrite_annexes() const =0
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
Definition phase.h:403
virtual const Def * rewrite_root(const Def *def)
Rewrites a root - i.e. an annex or an external.
Definition phase.h:411
const Def * abstracted(const Def *def) const
Returns lattice(def) if it differs from def (i.e. we learned something), otherwise nullptr.
Definition phase.h:382
virtual bool analyze()
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
Definition phase.cpp:179
RWBase(World &world, std::string name, Analysis *analysis, std::unique_ptr< World > &&new_world)
Rewrite the World of Phase::world into new_world.
Definition phase.h:360
virtual void rewrite_annex(flags_t, Sym, const Def *)=0
World & new_world()
Create new Defs into this.
Definition phase.h:452
RWPhase(World &world, flags_t annex, Analysis *analysis=nullptr)
Definition phase.h:433
void rewrite_annex(flags_t, Sym, const Def *) override
Definition phase.cpp:198
RWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:431
void start() override
RWBase::start() and then swaps the two worlds.
Definition phase.cpp:193
bool rewrite_annexes() const final
An RWPhase has to walk the annexes: it must re-create every one of them to populate new_world()'s tab...
Definition phase.h:460
World & world()=delete
Hides both and forbids direct access.
void rewrite_external(Def *) override
Definition phase.cpp:202
World & old_world()
Get old Defs from here.
Definition phase.h:451
const Def * rewrite(const Def *def) final
Definition phase.h:534
virtual const Def * replace(const Def *def)=0
Repl(World &world, flags_t annex)
Definition phase.h:524
World & world()
replace() inspects and builds Defs of the old world; the RWPhase machinery carries the result over.
Definition phase.h:528
World & world()
Definition rewrite.h:35
D * curr_mut() const
Definition rewrite.h:96
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:47
Rewriter(std::unique_ptr< World > &&ptr)
Definition rewrite.cpp:16
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
Driver & driver()
Definition world.h:103
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Definition world.cpp:129
Definition ast.h:16
DefMap< const Def * > Def2Def
Definition def.h:90
u64 flags_t
Definition types.h:39
absl::flat_hash_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
GIDSet< Def * > MutSet
Definition def.h:101
std::deque< std::unique_ptr< Phase > > Phases
Definition phase.h:20
static consteval flags_t base()
Definition plugin.h:250