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