MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
clos_conv.h
Go to the documentation of this file.
1#pragma once
2
3#include <queue>
4
5#include <mim/phase.h>
6
7#include <mim/plug/mem/mem.h>
8
10
11namespace mim::plug::clos::phase {
12
13/// Computes, on demand and with memoization, the free Def%s that a Lam must capture in its closure environment.
14///
15/// A referenced nested mutable does not become a free def itself; instead it contributes *its* free defs, since it
16/// will be closure-converted too.
17/// This makes the free-def sets of (mutually) recursive Lam%s inter-dependent, so they are solved to a fixed point
18/// over a little dependency graph: an edge `pred → node` means `node` must include all of `pred`'s free defs.
19///
20/// Mutables are only treated as free defs directly if annotated with clos::attr::free_bb / clos::attr::fstclass_bb;
21/// immutables carrying free vars are broken down to their relevant leaves.
23public:
25 : world_(world) {}
26
27 /// Returns the free defs @p lam has to capture; see the class description.
28 const DefSet& run(Lam* lam);
29
30private:
31 /// A memoized free-def set together with its dependency edges in the fixed-point graph.
32 struct Node {
33 Def* mut;
34 DefSet fvs;
35 fe::Vector<Node*> preds; ///< Nodes whose free defs must flow into this one.
36 fe::Vector<Node*> succs; ///< Reverse of preds; used to re-enqueue dependents when fvs grow.
37 unsigned pass = 0; ///< 0 = uninitialized; otherwise the pass that last touched this node.
38
39 auto add_fvs(const Def* def) {
40 assert(!Axm::isa<mem::M>(def->type()));
41 return fvs.emplace(def);
42 }
43 };
44
45 using NodeQueue = std::queue<Node*>;
46
47 bool is_bot(Node* node) const { return node->pass == 0; } ///< Not yet initialized?
48 bool is_done(Node* node) const { return !is_bot(node) && node->pass < cur_pass_; } ///< Settled in an earlier pass?
49 void mark(Node* node) { node->pass = cur_pass_; }
50
51 /// Classifies a free def of @p node, either adding it as a captured fv or spawning/linking a predecessor Node.
52 /// @p spawned_pred is set if this created a fresh (uninitialized) predecessor Node.
53 void classify(Node* node, const Def* fd, bool& spawned_pred, NodeQueue& worklist);
54
55 std::pair<Node*, bool> build_node(Def* mut, NodeQueue& worklist);
56 void propagate(NodeQueue& worklist);
57
58 World& world() { return world_; }
59
60 World& world_;
61 unsigned cur_pass_ = 1;
63};
64
65/// Performs *typed closure conversion*, rebuilding the old world into a new one.
66/// This is based on the [Simply Typed Closure Conversion](https://dl.acm.org/doi/abs/10.1145/237721.237791).
67/// Closures are represented using tuples: `[Env: *, Cn [Env, Args..], Env]`.
68/// In general only *continuations* are converted; different kinds of Lam%s are treated differently:
69/// - *returning continuations* ("functions"), *join-points* and *branches* are fully closure converted.
70/// - *return continuations* are not closure converted.
71/// - *first-class continuations* get a "dummy" closure; they still have free variables.
72///
73/// This phase relies on ClosConvPrep to introduce annotations for these cases.
74///
75/// A converted Lam is first stubbed (ClosConv::make_stub) and packed at each use site, while its body is
76/// enqueued and rewritten later (ClosConv::rewrite_body) in its own fresh substitution scope.
77/// This isolation is essential: inside a closure's body its free defs are replaced by projections of *its own*
78/// environment, and those substitutions must not leak into the enclosing scope where the closure was packed.
79///
80/// @note Direct-style Def%s are not rewritten, which can be a problem for certain Axm%s such as
81/// `ax : (B : *, int → B) → (int → B)`.
82/// There is also no machinery for free variables in a Lam's type, which may break polymorphic functions.
83class ClosConv : public RWPhase {
84public:
87 , fva_(world) {} // the FVA operates on the old world
88
89private:
90 /// A closure-converted Lam: the code part @p fn capturing the free defs @p fvs of @p old_fn.
91 struct Stub {
92 Lam* old_fn;
93 /// The captured free defs.
94 /// Do not recover them from the env tuple's ops: World::tuple may normalize it (η-reduction back to the
95 /// underlying def, unary tuple, uniform Pack).
96 DefVec fvs;
97 Lam* fn;
98 };
99
100 /// @name Rewrite hooks
101 ///@{
102 void rewrite_external(Def*) final;
103 void finalize() final;
104 const Def* rewrite_imm_Pi(const Pi*) final;
105 const Def* rewrite_mut_Pi(Pi*) final;
106 const Def* rewrite_mut_Lam(Lam*) final;
107 const Def* rewrite_imm_App(const App*) final;
108 const Def* rewrite_imm_Extract(const Extract*) final;
109 const Def* rewrite_mut_Global(Global*) final;
110 ///@}
111
112 /// Handles the `clos.attr.{returning,free_bb,fstclass_bb}` wrappers; returns `nullptr` if @p a is none of these.
113 const Def* rewrite_attr(Axm::IsA<attr, App> a);
114
115 Stub make_stub(const DefSet& fvs, Lam* old_lam);
116 Stub make_stub(Lam* old_lam);
117 void rewrite_body(const Stub&);
118
119 /// Builds the closure type for the `Cn` @p pi; with @p env_type, the bare code `Cn` instead of the Sigma.
120 const Def* clos_type_of(const Pi* pi, const Def* env_type = nullptr);
121 /// Rewrites a return continuation's type: stays a plain `Cn`, but its domains are closure-converted.
122 const Pi* rewrite_ret_cn(const Pi*);
123
124 FreeDefAna fva_;
125 DefMap<Stub> closures_; ///< old_fn *and* new fn ↦ Stub.
126
127 /// Muts that must be rewritten uniformly across the whole module: closure types and globals.
128 /// Such muts must not depend on defs living inside the scope of a continuation.
129 Def2Def glob_muts_;
130
131 std::queue<Lam*> body_worklist_; ///< New fns whose bodies are yet to be rewritten.
132};
133
134} // namespace mim::plug::clos::phase
Definition axm.h:9
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:273
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1111
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:161
A function.
Definition lam.h:113
flags_t annex() const
Definition phase.h:81
A dependent function type.
Definition lam.h:14
RWPhase(World &world, std::string name, Analysis *analysis=nullptr)
Definition phase.h:431
World & world()=delete
Hides both and forbids direct access.
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
const Def * rewrite_mut_Global(Global *) final
void rewrite_external(Def *) final
const Def * rewrite_imm_Extract(const Extract *) final
const Def * rewrite_mut_Pi(Pi *) final
ClosConv(World &world, flags_t annex)
Definition clos_conv.h:85
const Def * rewrite_imm_App(const App *) final
const Def * rewrite_imm_Pi(const Pi *) final
void finalize() final
Run after all roots have been walked - but for an RWPhase still before the two worlds are swapped.
const Def * rewrite_mut_Lam(Lam *) final
const DefSet & run(Lam *lam)
Returns the free defs lam has to capture; see the class description.
DefMap< const Def * > Def2Def
Definition def.h:90
u64 flags_t
Definition types.h:39
GIDMap< const Def *, To > DefMap
Definition def.h:88
fe::Vector< const Def * > DefVec
Definition def.h:93
GIDSet< const Def * > DefSet
Definition def.h:89
Node
Definition def.h:120