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 <absl/container/fixed_array.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::recreate() {
26 auto ctor = driver().phase(annex());
27 auto ptr = (*ctor)(world());
28 ptr->apply(*this);
29 return ptr;
30}
31
32void Phase::run() {
33 auto profiling = driver().flags().profile != Flags::Profile::None;
34 if (profiling) driver().profiler().start(name());
35 world().verify().log().i("🚀 launch phase `{}`", name());
36 start();
37 world().verify().log().i("🏁 finish phase `{}`", name());
38 if (profiling) driver().profiler().stop();
39}
40
41void Phase::profile_count(std::string_view key, uint64_t n) {
42 if (driver().flags().profile != Flags::Profile::None) driver().profiler().count(key, n);
43}
44
45/*
46 * Analyzer
47 */
48
50 old2news_.clear();
51 worklist_.clear();
52 push();
53 todo_ = false;
54 bootstrapping_ = true; // every full round walks the annexes again - and that walk *is* the bootstrapping half
55}
56
58 curr_sparse_ = !dense_ && !nonlocal_ && !dirty_.empty();
59 nonlocal_ = false;
60 auto seeds = fe::Vector<Def*>(dirty_.begin(), dirty_.end());
61 dirty_.clear();
62
63 prepare();
64
65 if (curr_sparse_) {
66 bootstrapping_ = false; // a sparse round re-drains program muts - it has no annex half
67 log().v("sparse round: re-drain {} dirty muts", seeds.size());
68 std::ranges::sort(seeds, GIDLt<Def*>()); // MutSet iteration order is nondeterministic
69
70 // Pre-install what earlier rounds substituted, so this round prunes everything they already settled -
71 // except for mutables, whose map entry doubles as the per-round "already scheduled" marker and would
72 // suppress their drain. Pruning skips rewrite hooks, which is why only a sparse round does it: the
73 // full round that certifies the fixed point always re-derives from the program.
74 for (auto [concr, abstr] : lattice_)
75 if (!concr->isa_mut()) map(concr, abstr);
76
77 for (auto mut : seeds)
78 rewrite(mut);
79 drain();
80 } else {
81 for (const auto& [flags, e] : world().annexes())
82 rewrite_annex(flags, e.sym, e.def);
83 drain();
84
85 bootstrapping_ = false;
86
87 for (auto mut : world().externals().muts())
89 drain();
90
91 finalize();
92 }
93
94 profile_count(curr_sparse_ ? "rounds.sparse" : "rounds.full");
95 profile_count("muts.drained", std::exchange(num_drained_, size_t(0)));
96
97 // A quiet sparse round only certifies the muts it visited:
98 // force one full round; the fixed point counts only if that one stays quiet, too.
99 if (curr_sparse_ && !todo()) invalidate();
100}
101
102void Analysis::rewrite_annex(flags_t, Sym, const Def* def) { rewrite(def); }
104
105const Def* Analysis::repr_(const Def* slow, const Def* fast) const {
106 while (slow != fast) {
107 auto next = follow(fast);
108 if (!next) return fast;
109 fast = follow(next);
110 if (!fast) return next;
111 slow = follow(slow);
112 assert(slow && "slow lags fast, so fast already traversed slow's successor");
113 }
114
115 auto res = slow;
116 for (auto def = follow(slow); def != slow; def = follow(def))
117 if (def->gid() < res->gid()) res = def;
118 return res;
119}
120
121const Def* Analysis::rewrite(const Def* def) {
122 if (def->isa_mut()) return Rewriter::rewrite(def);
123 return repr(Rewriter::rewrite(def));
124}
125
127 if (lookup(mut)) return mut; // already scheduled this round
128 map(mut, mut);
129 worklist_.emplace_back(mut);
130 return mut;
131}
132
133void Analysis::drain() {
134 while (!worklist_.empty()) {
135 auto mut = worklist_.front();
136 worklist_.pop_front();
137 ++num_drained_;
138
139 auto _ = enter(mut);
140 log().d("enter {}", mut);
141 for (auto d : mut->deps())
142 rewrite(d);
143 leave();
144 }
145}
146
147/*
148 * RWBase
149 */
150
152 auto max_iters = driver().flags().max_fp_iters;
153 bool todo = true;
154 for (uint32_t i = 0; todo; ++i) {
155 if (i >= max_iters) fe::throwf("phase `{}` did not reach a fixed point after {} iterations", name(), max_iters);
156 log().v("iteration {}", i);
157 todo = analyze();
158 }
159
160 // Count the Def%s each half of the walk creates.
161 // For an RWPhase the annex half is a fixed tax proportional to the loaded plugins' annex graph - not to the
162 // program - which is exactly why an InplaceRWPhase skips it by default.
163 auto gid = Rewriter::world().curr_gid();
164 if (rewrite_annexes())
165 for (const auto& [flags, e] : Phase::world().annexes())
166 rewrite_annex(flags, e.sym, e.def);
167 profile_count("rw.defs.annex", Rewriter::world().curr_gid() - gid);
168
169 bootstrapping_ = false;
170
171 gid = Rewriter::world().curr_gid();
172 // mutate(): an in-place rewrite_external may re-externalize, which would invalidate a live iterator.
173 for (auto mut : Phase::world().externals().mutate())
174 rewrite_external(mut);
175 finalize(); // inside the span: work deferred by the root walk belongs to the root walk
176 profile_count("rw.defs.external", Rewriter::world().curr_gid() - gid);
177}
178
180 if (analysis_) {
181 analysis_->reset();
182 analysis_->run();
183 return analysis_->todo();
184 }
185
186 return false;
187}
188
189/*
190 * RWPhase
191 */
192
196}
197
198void RWPhase::rewrite_annex(flags_t f, Sym sym, const Def* def) {
199 new_world().annexes().attach(f, sym, rewrite_root(def));
200}
201
203 auto new_mut = rewrite_root(old_mut)->as_mut();
204 if (old_mut->is_external()) new_mut->externalize();
205}
206
207/*
208 * InplaceRWPhase
209 */
210
211void InplaceRWPhase::rewrite_annex(flags_t flags, Sym, const Def* def) {
212 if (auto new_def = rewrite_root(def); new_def != def) {
213 world().annexes().reattach(flags, new_def);
214 invalidate();
215 }
216}
217
219 auto new_def = rewrite_root(old_mut);
220 if (new_def == old_mut) return;
221
222 // The rewrite replaced the external itself; carry the external flag over.
223 old_mut->internalize();
224 new_def->as_mut()->externalize();
225 invalidate();
226}
227
229 if (auto hole = mut->isa<Hole>()) {
230 auto [last, op] = hole->find();
231 return op ? rewrite(op) : last; // an unresolved Hole stays as is
232 }
233
234 // A mutable's identity is tied to its type, so if the rewrite changes the type, we cannot keep it: fall back to
235 // an RWPhase-style rebuild - Rewriter::rewrite_mut stubs a fresh mutable (in this very World) and maps onto it.
236 if (auto type = mut->type(); type && rewrite(type) != type) {
237 profile_count("inplace.muts.rebuilt");
238 invalidate();
239 return Rewriter::rewrite_mut(mut);
240 }
241
242 map(mut, mut); // keep the identity; doubles as the cycle breaker for recursive mutables
243 if (!mut->is_set()) return mut;
244
245 auto _ = enter(mut);
246 auto new_ops = rewrite(mut->ops());
247 if (!std::ranges::equal(new_ops, mut->ops())) {
248 mut->unset()->set(new_ops);
249 profile_count("inplace.muts.reset");
250 invalidate();
251 }
252
253 return mut;
254}
255
256/*
257 * PhaseMan
258 */
259
260void PhaseMan::apply(bool fp, Phases&& phases) {
261 fixed_point_ = fp;
262 phases_ = std::move(phases);
263 name_ += fixed_point_ ? " tt" : " ff";
264}
265
266void PhaseMan::apply(const App* app) {
267 auto [fp, args] = app->uncurry_args<2>();
268
269 auto phases = Phases();
270 for (auto arg : args->projs())
271 if (auto phase = create(driver().phases(), arg)) phases.emplace_back(std::move(phase));
272
273 apply(Lit::as<bool>(fp), std::move(phases));
274}
275
276void PhaseMan::apply(Phase& phase) {
277 auto& man = static_cast<PhaseMan&>(phase);
278 Phases new_phases;
279 for (auto& old_phase : man.phases())
280 new_phases.emplace_back(std::unique_ptr<Phase>(static_cast<Phase*>(old_phase->recreate().release())));
281 apply(man.fixed_point(), std::move(new_phases));
282}
283
285 auto max_iters = driver().flags().max_fp_iters;
286 auto n = phases().size();
287 // A phase's run is a deterministic function of the World's content.
288 // So a phase only needs to run (again) if the World (may have) changed since its last quiet run.
289 auto stale = absl::FixedArray<bool>(n, true);
290 auto ran = absl::FixedArray<bool>(n, false);
291
292 auto any_stale = [&stale]() { return std::ranges::any_of(stale, [](bool b) { return b; }); };
293
294 for (uint32_t iter = 0; any_stale(); ++iter) {
295 if (iter >= max_iters)
296 fe::throwf("phase `{}` did not reach a fixed point after {} iterations", name(), max_iters);
297 if (fixed_point()) log().v("🔄 fixed-point iteration {}", iter);
298
299 bool todo = false;
300 for (size_t i = 0; i != n; ++i) {
301 auto& phase = phases()[i];
302 if (!stale[i]) {
303 log().v("skip `{}`: World unchanged since its last quiet run", phase->name());
304 profile_count("phases.skipped");
305 continue;
306 }
307
308 if (ran[i]) { // re-runs need a fresh instance
309 auto new_phase = std::unique_ptr<Phase>(static_cast<Phase*>(phase->recreate().release()));
310 swap(new_phase, phase);
311 }
312
313 phase->run();
314 ran[i] = true;
315 stale[i] = false;
316
317 if (phase->todo()) {
318 todo = true;
319 // The World changed: everyone - including this phase itself - gets another look.
320 std::ranges::fill(stale, true);
321 }
322 }
323
324 todo &= fixed_point();
326 if (!fixed_point()) break;
327 }
328}
329
330} // namespace mim
virtual void prepare()
Run before the main analysis.
Definition phase.h:272
void start() override
Actual entry.
Definition phase.cpp:57
const Def * rewrite(const Def *) override
Rewrites def and then maps the result to its repr().
Definition phase.cpp:121
virtual void leave()
Called after curr_mut() has been completely dealt with.
Definition phase.h:292
virtual void rewrite_annex(flags_t, Sym, const Def *)
Definition phase.cpp:102
virtual void rewrite_external(Def *)
Definition phase.cpp:103
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 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
static auto uncurry_args(const Def *def)
Definition lam.h:328
Base class for all Defs.
Definition def.h:273
bool is_set() const
Definition def.h:370
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:196
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:589
constexpr auto ops() const noexcept
Definition def.h:348
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:580
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1111
bool is_external() const noexcept
Definition def.h:553
void internalize()
Definition def.cpp:608
Def * unset()
Unsets all Def::ops; works even, if not set at all or only partially set.
Definition def.cpp:213
Flags & flags()
Definition driver.h:76
const fe::Vector< std::string > & args(std::string_view plugin) const
Yields an empty fe::Vector if plugin has none.
Definition driver.cpp:185
fe::Profiler & profiler()
Definition driver.h:80
auto phase(flags_t flags)
Definition driver.h:191
This node is a hole in the IR that is inferred by its context later on.
Definition check.h:16
const Def * rewrite_mut(Def *mut) override
Definition phase.cpp:228
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
static T as(const Def *def)
Definition def.h:943
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
void apply(bool, Phases &&)
Definition phase.cpp:260
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
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 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
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.
World & world()
Definition phase.h:77
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
virtual void rewrite_external(Def *)=0
void start() override
Actual entry.
Definition phase.cpp:151
virtual bool rewrite_annexes() const =0
virtual const Def * rewrite_root(const Def *def)
Rewrites a root - i.e. an annex or an external.
Definition phase.h:411
virtual bool analyze()
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
Definition phase.cpp:179
virtual void rewrite_annex(flags_t, Sym, const Def *)=0
World & new_world()
Create new Defs into this.
Definition phase.h:452
void rewrite_annex(flags_t, Sym, const Def *) override
Definition phase.cpp:198
void start() override
RWBase::start() and then swaps the two worlds.
Definition phase.cpp:193
void rewrite_external(Def *) override
Definition phase.cpp:202
World & old_world()
Get old Defs from here.
Definition phase.h:451
friend void swap(Rewriter &rw1, Rewriter &rw2) noexcept
Definition rewrite.h:89
World & world()
Definition rewrite.h:35
virtual const Def * rewrite_mut(Def *)
Definition rewrite.cpp:76
virtual void push()
Definition rewrite.h:40
virtual const Def * map(const Def *old_def, const Def *new_def)
Definition rewrite.h:47
std::deque< Def2Def > old2news_
Definition rewrite.h:106
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
auto enter(Def *new_mut)
Updates curr_mut() to new_mut and restores it at the end of the scope.
Definition rewrite.h:109
virtual const Def * lookup(const Def *old_def)
Lookup old_def by searching in reverse through the stack of maps.
Definition rewrite.h:62
const Def * reattach(flags_t flags, const Def *def)
Overwrites the Def of an already attach()ed annex, keeping its Sym.
Definition world.h:246
const Def * attach(flags_t, Sym, const Def *)
Definition world.cpp:61
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
u32 curr_gid() const
Manage global identifier - a unique number for each Def.
Definition world.h:114
World & verify()
Verifies that all externals() and annexes() are Def::is_closed(), if MIM_ENABLE_CHECKS.
Definition world.cpp:784
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Definition world.cpp:129
Annexes & annexes()
Definition world.h:281
Definition ast.h:16
u64 flags_t
Definition types.h:39
std::deque< std::unique_ptr< Phase > > Phases
Definition phase.h:20
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:33
static constexpr plugin_t flags2plugin(flags_t f)
Definition plugin.h:228
@ None
No profiling.
Definition flags.h:14
uint32_t max_fp_iters
Definition flags.h:21
Profile profile
Definition flags.h:27