MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
seo.h
Go to the documentation of this file.
1#pragma once
2
3#include <absl/container/btree_set.h>
4#include <fe/bitset.h>
5
6#include <mim/def.h>
7#include <mim/phase.h>
8
9#include <mim/util/gid.h>
10
11namespace mim::plug::mem::phase {
12
13/// Symbolic Expression Optimization.
14/// Based on [SSA Translation Is an Abstract Interpretation](https://dl.acm.org/doi/10.1145/3571258).
15/// In addition:
16/// * propagates whole expressions - not just constants
17/// * optimistically combines φs/Vars that are already present with those being constructed through SSA translation
18/// * since abstract domain is a MimIR expression, stack slots themselves can be propagated etc.
19///
20/// Additional papers worth reading:
21/// * [Constant propagation with conditional branches](https://dl.acm.org/doi/pdf/10.1145/103135.103136)
22/// * [Detecting equality of variables in programs](https://dl.acm.org/doi/10.1145/73560.73561)
23/// * [Combining analyses, combining optimizations](https://dl.acm.org/doi/pdf/10.1145/201059.201061)
24/// * [Simple and efficient construction of static single assignment
25/// form](https://dl.acm.org/doi/10.1007/978-3-642-37051-9_6)
26///
27/// Due to MimIR's sea of node structure a number of other optimizations kick in such as arithmetic simplifications and
28/// code motion.
29///
30/// Lattice per Lam::var:
31/// ```
32/// ⊤ ← Keep as is
33/// |
34/// Bundle ← Vars that (horizontally) behave the same build a single congruence class
35/// |
36/// Expr ← Whole expression is propagated (vertically) through var
37/// |
38/// ⊥
39/// ```
40/// A var that has reached ⊤ for propagation but still awaits GVN bundling is marked with a dedicated Proxy sentinel.
41class SEO : public RWPhase {
42private:
43 using Super = mim::RWPhase;
44
45 class Analysis : public mim::Analysis {
46 public:
47 using Super = mim::Analysis;
48
50 : mim::Analysis(world, "SEO::Analyzer") {}
51
52 void reset() final;
53
54 const LamSet& unknowns() const { return unknowns_; }
55
56 // SSA
57 const auto& slots() const { return slots_; }
58 const Def* lam2sloxy2val(Lam* lam, const Def* sloxy);
59 /// Can *every* recorded call site of @p lam supply a value for @p sloxy?
60 /// Only then may build_args() find an argument for a phi threaded through @p lam.
61 bool can_supply(Lam* lam, const Def* sloxy);
62
63 private:
64 // SCCP
65 const Proxy* mk_sccp_top(const Def* var);
66 const Def* sccp_join(Lam*, const Def*, const Def*);
67
68 /// Applies @p known to @p abstr_targs (one per tvar): propagates phis, runs SCCP + GVN, and sets the vars.
69 const Def* apply_known(Lam* known, Defs abstr_targs);
70 /// An App that merely records "@p callee applied to these abstract args"; never emitted code.
71 const Def* abstract_app(const Def* callee, const Def* arg);
72
73 // GVN
74 const Proxy* mk_bundle(Lam* lam, const Def* var, Defs bundle_vars);
75 void gvn_bundle(Lam*, Defs, Defs, fe::Span<const Def*>);
76 void gvn_split(Lam*, Defs, fe::Span<const Def*>, fe::Span<const Def*>);
77
78 // SSA
79 void propagate_phis(Lam*, DefVec& vars, DefVec& abstr_args);
80 const Def* sloxy2val(const Def* sloxy) { return lam2sloxy2val(curr_mut<Lam>(), sloxy); }
81 const Def* sloxy2val(const Def* sloxy, const Def* val) { return lam2sloxy2val_[curr_mut<Lam>()][sloxy] = val; }
82 const Def* rewrite_imm_App(const App*) final;
83 void leave() final;
84
85 // post-processing analysis to find sloxies that must be set to top
86 void finalize() final;
87 void analyze(const Def*);
88
89 // local (reset between iterations)
90 absl::node_hash_map<Lam*, Def2Def, GIDHash<const Def*>> lam2sloxy2val_;
91 DefSet visited_;
92 DefSet first_;
93 Def2Def sloxy2slot_; // global (kept between iterations)
94 absl::btree_set<const Def*, GIDLt<const Def*>> slots_; // actually slot ptrs
95 LamSet unknowns_; // Lam%s reached as a *value*; their signature must stay untouched
96 LamMap<MutSet> lam2callers_; // all muts that apply a Lam; tainted when the Lam's abstract vars change
97 };
98
99public:
101 : RWPhase(world, annex, &analysis_)
102 , analysis_(world) {}
103
104private:
105 const Def* rewrite_imm_App(const App*) final;
106 const Def* rewrite_imm_Var(const Var*) final;
107 const Def* rewrite_mut_Lam(Lam*) final;
108
109 /// A live phi for a Lam: the @p sloxy it stands for, the @p phi proxy, its abstract @p val,
110 /// and whether the new signature keeps it as a var.
111 struct Phi {
112 const Def* sloxy;
113 const Def* phi;
114 const Def* val;
115 bool keep;
116 };
117
118 /// The new signature of an old Lam.
119 struct Sig {
120 fe::Vector<Phi> phis; ///< Its live phis.
121 fe::Bitset keeps; ///< Which tvars of the old Lam are kept as is?
122 size_t num_vars = 0; ///< Vars of the new Lam: the kept old ones plus the kept phis.
123 bool todo = false; ///< Does the old Lam need a new signature at all?
124 };
125
126 bool analyze() final;
127
128 /// Was the SSA construction able to eliminate this sloxy?
129 const Def* isa_optimized_sloxy(const Def*) const;
130 /// The (memoized) new signature of @p old_lam.
131 const Sig& sig_of(Lam* old_lam);
132 /// The new spelling of @p old_lam's var: kept projections become new vars, dropped ones their
133 /// propagated value (⊥ for a promoted slot). The single source of truth for both build_lam() and
134 /// rewrite_imm_Var(), and hence well-defined independent of build order.
135 const Def* var_of(Lam* old_lam);
136 /// Builds (and caches) the new Lam for @p old_lam with propagated vars removed and kept phis appended.
137 Lam* build_lam(Lam* old_lam);
138 /// Builds the argument list for a jump to @p old_lam (with the given @p old_targs, one per tvar)
139 /// matching the signature built by build_lam().
140 DefVec build_args(Lam* old_lam, Defs old_targs);
141
142 Analysis analysis_;
143 Lam2Lam lam_old2new_;
144 Lam2Lam lam_new2old_;
145 absl::node_hash_map<Lam*, Sig, GIDHash<Lam*>> lam2sig_; // node_hash_map: a Sig& outlives nested rewrites
146 DefVec sloxies_; // the eliminated sloxies; every Lam's phi candidates
147};
148
149} // namespace mim::plug::mem::phase
Traverses the current World using Rewriter infrastructure while staying in the same world.
Definition phase.h:151
virtual void leave()
Called after curr_mut() has been completely dealt with.
Definition phase.h:292
Base class for all Defs.
Definition def.h:273
A function.
Definition lam.h:113
friend class Analysis
Definition phase.h:125
flags_t annex() const
Definition phase.h:81
bool todo() const
Definition phase.h:90
Used as intermediate value during optimizatinos such as Analysis.
Definition def.h:1032
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
Rebuilds old_world() into new_world() and then swaps them.
Definition phase.h:427
RWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:431
World & world()=delete
Hides both and forbids direct access.
D * curr_mut() const
Definition rewrite.h:96
void reset(std::unique_ptr< World > &&ptr)
Definition rewrite.cpp:29
A variable introduced by a binder (mutable).
Definition def.h:825
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
bool analyze() final
Runs the optional pre-analysis on Phase::world, typically to a fixed point, before rewriting begins.
Definition seo.cpp:429
const Def * rewrite_imm_Var(const Var *) final
Definition seo.cpp:534
const Def * rewrite_imm_App(const App *) final
Definition seo.cpp:470
const Def * rewrite_mut_Lam(Lam *) final
Definition seo.cpp:526
SEO(World &world, flags_t annex)
Definition seo.h:100
DefMap< const Def * > Def2Def
Definition def.h:90
GIDSet< Lam * > LamSet
Definition lam.h:220
u64 flags_t
Definition types.h:39
fe::View< const Def * > Defs
Definition def.h:91
LamMap< Lam * > Lam2Lam
Definition lam.h:221
GIDMap< Lam *, To > LamMap
Definition lam.h:219
fe::Vector< const Def * > DefVec
Definition def.h:93
GIDSet< const Def * > DefSet
Definition def.h:89