MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
escape_analysis.h
Go to the documentation of this file.
1#pragma once
2
3#include <optional>
4
5#include <mim/phase.h>
6
7#include "mim/plug/mem/mem.h"
8
9namespace mim::plug::mem {
10
11/// Determines which allocations stay within the scope that allocates them.
12///
13/// The *home* of an allocation is the Lam on whose memory chain it sits.
14/// An allocation *escapes* if it flows into the data of a tail call - returned, or handed to a callee or continuation -
15/// or if an escaping Lam mentions it.
16/// Any op but `mem.load`, the address of a `mem.store`, and a `mem.free` of the allocation itself may retain the
17/// pointers it is handed, so they escape there. A Lam escapes if its use is *unknown* - it is used as a value - or if
18/// an escaping Lam jumps to it. An axiom in control position is assumed to only pass control on to the continuations it
19/// receives.
20///
21/// A buffer-typed parameter of a Lam takes on the value its known call sites pass, as in SCCP.
22/// An external Lam has unknown call sites, so whatever is passed to it escapes.
23/// A value only flows into a Lam that its scope strictly encloses; otherwise it would outlive its scope and escapes.
24///
25/// Lattice: an escaping allocation is ⊤ - keep as is; *absent* means it stays local.
26/// A parameter is ⊤ if its call sites disagree; everything they pass escapes.
28public:
29 struct Chain {
30 const App* tail;
31 fe::Vector<const App*> ops; ///< The ops threading the memory from the Lam's Var to its tail, in program order.
32 DefMap<size_t> last; ///< Per allocation the ops mention: the index of the last op that does so.
33 DefVec targets; ///< The continuations the tail transfers control to.
34 };
35
37 : mim::Analysis(world, "mem::EscapeAnalysis") {}
38
39 static bool allocates(const App* app) { return Axm::isa<mem::alloc>(app) || Axm::isa<mem::malloc>(app); }
40
41 /// @name Results
42 ///@{
43 /// The Lam%s with an intact memory chain.
44 const LamMap<Chain>& chains() const { return chains_; }
45 /// May @p lam run at any time, as it is used as a value or jumped to by an escaping Lam?
46 bool escaping(Lam* lam) const { return escaping_.contains(lam); }
47 /// Is the local @p alloc only mentioned on its home's own memory chain?
48 bool confined(const Def* alloc) const { return !shared_.contains(alloc); }
49 /// Does a `mem.free` release @p alloc?
50 bool freed(const Def* alloc) const { return freed_.contains(alloc); }
51 /// The allocation @p def releases if it is a `mem.free` of one; `nullptr` otherwise.
52 static const App* freed_alloc(const Def* def) {
53 if (auto free = Axm::isa<mem::free>(def))
54 if (auto ex = free->arg(2, 1)->isa<Extract>())
55 if (auto app = ex->tuple()->isa<App>(); app && allocates(app)) return app;
56 return nullptr;
57 }
58 ///@}
59
60private:
61 /// Records what each value may carry: the allocations it aliases and the nested Lam%s in it.
62 const Def* rewrite_imm(const Def*) override;
63 const Def* rewrite_imm_App(const App*) override;
64 void leave() override;
65
66 bool may_hold(const Def* type) const;
67 std::optional<fe::Vector<const App*>> walk(Lam* lam, const App* tail);
68 /// Yields where @p mem starts in @p lam - its memory Var or the memory it captures - or `nullptr` if lost.
69 /// Collects the @p ops on the way in reverse program order.
70 const Def* walk(Lam* lam, const Def* mem, fe::Vector<const App*>& ops);
71 /// The memory @p tail hands on - directly or captured by the continuation of a direct-style call.
72 const Def* exit_mem(const App* tail, LamSet& done);
73 /// Adds what @p def may carry to @p carried, possibly twice; a nested Lam carries itself.
74 void join(DefVec& carried, const Def* def) const;
75
76 void escape(Lam* lam) {
77 if (escaping_.emplace(lam).second) taint(lam), touch();
78 }
79 /// Everything @p def may carry escapes.
80 void escape(const Def* def);
81
82 /// The Lam%s a tail jumps to directly - itself or through a jump table - or nothing.
83 static fe::Vector<Lam*> jumps(const Def* callee);
84 void jump(Lam* k, const App* tail, DefVec& targets);
85 /// Joins @p val into @p k%'s parameter @p var.
86 void propagate(Lam* k, const Def* var, const Def* val);
87 /// A continuation handed to a tail transfers control; anything else escapes.
88 void pass(const Def* def, DefVec& targets) { Pi::isa_cn(def->type()) ? control(def, targets) : escape(def); }
89 /// Collects the continuations a tail transfers control to; the data along the way escapes.
90 void control(const Def* def, DefVec& targets);
91
92 LamMap<Chain> chains_;
93 DefMap<DefVec> aliases_; ///< What a value may carry; *absent* means nothing.
94 DefSet shared_;
95 DefSet freed_;
96 LamSet escaping_;
97};
98
99} // namespace mim::plug::mem
Traverses the current World using Rewriter infrastructure while staying in the same world.
Definition phase.h:130
Analysis(World &world, std::string name)
Definition phase.h:134
void taint(Def *mut)
Additionally schedules mut for the next sparse round.
Definition phase.h:247
void touch()
Observable analysis information changed: records curr_mut() as dirty - the seed set of the next spars...
Definition phase.h:253
World & world()
Definition phase.h:58
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:360
A function.
Definition lam.h:91
static const Pi * isa_cn(const Def *d)
Definition lam.h:50
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
const Def * rewrite_imm_App(const App *) override
static bool allocates(const App *app)
DefMap< size_t > last
Per allocation the ops mention: the index of the last op that does so.
bool confined(const Def *alloc) const
Is the local alloc only mentioned on its home's own memory chain?
bool freed(const Def *alloc) const
Does a mem.free release alloc?
fe::Vector< const App * > ops
The ops threading the memory from the Lam's Var to its tail, in program order.
const LamMap< Chain > & chains() const
void leave() override
Called after curr_mut() has been completely dealt with.
DefVec targets
The continuations the tail transfers control to.
bool escaping(Lam *lam) const
May lam run at any time, as it is used as a value or jumped to by an escaping Lam?
static const App * freed_alloc(const Def *def)
The allocation def releases if it is a mem.free of one; nullptr otherwise.
const Def * rewrite_imm(const Def *) override
Records what each value may carry: the allocations it aliases and the nested Lams in it.
The mem Plugin
Definition ast.h:16
GIDSet< Lam * > LamSet
Definition lam.h:211
GIDMap< const Def *, To > DefMap
Definition def.h:93
GIDMap< Lam *, To > LamMap
Definition lam.h:210
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94