escape analysis #51
Labels
No Milestone
No Assignees
3 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: M-Labs/nac3#51
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
https://github.com/m-labs/artiq/issues/1678#issuecomment-932899551
iirc the plan is to implement it later, when other features are finished.
and there is another plan about using region-based memory management, and we do not need escape analysis if we use that.
Approach: We are doing a field-sensitive intraprocedural dataflow analysis on each function to determine the lifetime of each values, and whether certain assignment/return operations are illegal based on the lifetime information. There are currently 5 different lifetimes: static, non-local, local (precise), local (imprecise) and unknown.
We will only track the lifetime of fields of local objects, because we cannot assign objects with non-static lifetime to other objects. The distinction between precise and imprecise is to improve the precision of lifetime tracking: for precise objects, we can use strong update (replace), but we need weak update (unification) for imprecise objects. Note that when a value is unified with imprecise local lifetime, all its fields will be downgraded from precise local lifetime to imprecise local lifetime.
Unification rules:
Function call handling: We treat the result of function calls to have lifetime unknown (except constructors, which the return value has lifetime precise local), because the function may return one of its parameters (which may have any lifetime). Because users are allowed to assign global variables to parameter fields, we will unify each field (transitively) of each parameter with static lifetime to account for this possibility. If the function's return type is
T
, values of typeT
reachable from the function parameters will be downgraded from precise local to imprecise local, because it is possible that it will alias with the function's return value.Plan: Support optional lifetime annotation for functions and object fields, and will be enforced by the escape analyzer. Functions will be treated as transfer functions that transform input parameter lifetimes with an additional return value. Lifetime variables can be treated as non-local lifetime, with additional capabilities: an object with lifetime
X
can be assigned to a field annotated with lifetimeX
, and unification with other lifetime variables/non-local lifetime will cause it to become a normal non-local lifetime. Example (the syntax is not finalized yet):Note that the following lifetime annotation is incorrect because
'static
is different from'c
And we will downgrade values with precise local lifetime to imprecise local lifetime according to the transfer function: if its type occurs in the transfer function output multiple times with compatible lifetime (same lifetime variable, non-local lifetime that are not another lifetime variable, or unknown lifetime).
That seems like a pretty nice tradeoff!
Here are some remarks off the top of my head:
Abstract Interpretation
I see that you are using the term "unification" a lot. Here I assume (hope) that lifetime information is not actually part of types, but some extra information that would be computed and checked after type checking, right? If so, unification might not be the best framework to work in.
I understand What you have as a (degenerate) abstract interpretation semi-lattice of the form (pardon my ASCII art):
And what you want to do is compute a fixed point of some annotations of each value in created/accessible in the current function.
Several things come to mind from this interpretation:
First, it seems we could easily make the lattice more interesting by replacing the
Local (precise)
andLocal (imprecise)
levels by the sublattice of sets of precise local value, possibly including all local values (the bottom of the sublattice, which would be equivalent to the oldLocal (imprecise)
).HOWEVER, this is just a refinement that may come later if the current system turns out to be too crude, and we don't need to do it now.
Second, what you mean by unification here would be to take the meet of elements in the lattice. This is valid for all places that are in output (i.e., positive) position. But in case of input positions, what one should choose instead would be the join.
I don't know whether this is relevant in the current system. It would certainly be if we supported first-class functions. Imagine something like
f if ... else g
wheref
takes parameters of static lifetimes as input, andg
takes parameters of unknown lifetimes as input. It is wrong to take their meet here (unknown), because it may result inf
being passed something that's not static, which would be unsound.You can construct a similar problem with mutation. If
l0
is a list supposed to contain global values andl1
is one supposed to contain unknown values, then(l0 if ... else l1)[0] = some_unknown_value
will also be wrong. In the case of mutable fields/list-slots, what we really need is two lifetimes – one input lifetime and one output lifetime – and taking the meet of two such objects should take the meet of the output lifetimes but the join of the input lifetimes.Memory Management
How does memory management work, exactly? Is everything stack allocated? If so, I don't currently see a poblem with the approach. But if we introduced dynamic allocations at some point, things could get tricky, for example when analayzing
lcaol_obj.x = dyn_alloc(SomeClass()) if ... else some_static_value
. (How would we tell which value should be deallocated and when?)Lifetime Polymorphism
I suppose you're talking about those lifetime variables we got from instantiating a function's lifetime signature? Because within the body of a function with lifetime variables declared, these variables should probably be treated as rigid (as in: they cannot be unified with anything else than themselves), right?.
What do
x
andy
refer to, here? Where are they bound? Are they lifetime variables ofFoo
? If so, why isn'tFoo
declared generic in them?