Coupling (WIP)
The PCG tracks ownership and borrowing at a fine-grained level, and in some
cases this granularity cannot be "observed" by the type system. For example,
lifetime projection nodes can represent a notion of reborrowing that is more
precise than Rust's borrow-checker itself. For example, consider the choose
function:
#![allow(unused)] fn main() { fn choose<'a, T>(choice: bool, lhs: &'a mut T, rhs: &'a mut T) -> &'a mut T { if choice { lhs } else { rhs } } }
The the PCG shape of a call choose(x, y) function: consists of two edges
and
. However, because
the compiler only tracks lifetimes, the borrows of x and y will always
expire at the same time. Accordingly, the two edges corresponding to the call
will always be removed from the graph at the same time. These edges are
therefore coupled, because the Rust type system forces the PCG to remove them
at the same time.
Motivation
Because the type systems forces a set of coupled edges to be removed "all-at-once", edges that are known to be coupled could be treated as a single hyperedge.
The primary reason for doing so is to provide more information analysis tools.
For example, Prusti uses coupling information to generate the shape of magic
wands: in the choose, the coupled hyperedge provides precisely the shape of
magic wand that Prusti encodes (although this is not always the case).
Another benefit is that coupling can reduce the size of the graphs.
Formal Definitions
Hyperedge
A hyperedge is an object with an associated set of source nodes and target nodes. The functions and denote the source and target nodes respectively.
Coupled Edges
A coupled edge is a hyperedge defined by a set of underlying hyperedges, where the sources and targets are defined as follows:
Let be the union of the sources of and be the union of the targets of . Then and .
Hypergraph
A hypergraph is a tuple where is a set of nodes and is a set of hyperedges. Functions and return the sets of nodes and hyperedges respectively.
Blocked Nodes
A node is blocked in iff and is not a leaf in .
Descendant Relation
We define the descendant relation as
Frontier
A set of nodes is a frontier of a hypergraph (denoted ) iff and is closed under .
If is a frontier of , it defines a valid expiry. The valid expiry is the subgraph of obtained by removing all nodes in and all edges containing sources or targets in . The expired edges of a valid
Reachable Subgraph
A graph is a reachable subgraph of a graph iff there exists a frontier such that
Desired Properties of Coupled Edges
Edges that will always be removed from the graph at the same time should definitely be coupled. Formally:
A set of edges expire together on a graph iff for all reachable subgraphs , either contains all edges in or none of them, i.e.:
- , or
If a set of edges expire together on a graph , then there must exist a set of edges that are coupled for .
Note that we could in principle define coupling as such, but we could also consider a stronger definition we describe below:
Definition Based on Unblocking Frontier Expiries
Our stronger definition is based on the following two observations:
First, if removing a frontier does not unblock any node in a graph, there is no way for the removal to be observed in the program. Therefore, such frontiers do not need to be considered for the purpose of asserting properties about a place once it becomes accessible. We can instead define coupling via notion of unblockings of a graph, where an unblocking is an ordered list of the sets of nodes that become available by repeated removal of frontiers.
Unblockings
An unblocking of a graph is an ordered partitioning of the non-root nodes of into non-empty subsets , satisfying the property that there exists a frontier of with an expiry that unblocks all nodes in , and is an unblocking of . The function denotes the set of all unblockings of .
Correspondingly, we can define edges as coupled if they always observably expire together, i.e. at all points when a node becomes accessible, they are either all in the graph or none of them are.
Reachable Subgraphs
Formally, for a graph and an unblocking of , the reachable subgraphs of an unblocking is the list of graphs where . The function is the lifting of to sets of unblockings:
Therefore, edges should be coupled if they are either all present or all absent for each graph in .
The second observation is that this set can be computed by considering only a subset of the unblockings in . This is because an unblocking can subsume an unblocking in the sense that the reachable subgraphs of are a superset of the reachable subgraphs of .
Subsumption
An unblocking is immediately subsumed by an unblocking (denoted ) iff there exists an such that and and .
subsumes (denoted ) iff is in the transitive closure of .
Theorem (Subsumption): If , then
Distinct Unblockings
The distinct unblockings of a graph (denoted ) is the subset of unblockings obtained by removing all non-minimal elements w.r.t .
Theorem (Distinct Unblockings): For all graphs ,
Effective and Maximal Coupling
A set of edges are effectively coupled for a graph iff for all reachable subgraphs in the distinct unblockings of , contains either all edges in or none of them. A set of edges is maximally coupled if it is effectively coupled and not a subset of an effectively coupled set.
Theorem (Correctness)
If a set of edges expire together on a graph , then there exists a set of edges that are maximally coupled on .
Proof
Recall that a set of edges expire together on if every reachable subgraph either contains all edges in or none of them, and that a set of edges are definitely coupled if for all reachable subgraph in the distinct unblockings of contains either all edges in or none of them.
Therefore it is sufficient to show that the reachable subgraphs in the distinct unblockings of is a subset of the reachable subgraphs of . The proof makes use of the following lemma:
Lemma: Valid Expiry on Unions of Frontier Nodes
If is a frontier of and is a frontier of , then is a frontier of and:
Proof is TODO
Then, let be an arbitrary unblocking of , it follows by induction on the list of frontiers corresponding to the nodes in , that any for every reachable subgraph of , there exists a frontier of such that . Therefore, for all unblockings of , the reachable subgraphs of are a subset of the reachable subgraphs of .
Test Graphs
m function
#![allow(unused)] fn main() { fn m<'a: 'c, 'b: 'e, 'c, 'd, 'e, T>( x: &'a mut T, y: &'b mut T, ) -> (&'c mut T, &'d mut T, &'e mut T) where 'a: 'd, 'b: 'd { unimplemented!() } }
w function
#![allow(unused)] fn main() { fn w<'a: 'd, 'b: 'd, 'c: 'e, 'd, 'e T>( x: &'a mut T, y: &'b mut T, z: &'c mut T, ) -> (&'d mut T, &'e mut T) where 'b: 'e { unimplemented!() } }
Previous Example
Additional Examples from HackMD
One Lifetime Reborrower Function
#![allow(unused)] fn main() { fn f<'a>(x: &'a mut T) -> &'a mut T { x } }
Possible Outlives Reborrower Function
#![allow(unused)] fn main() { fn f<'a, 'b: 'a>(x: &'a mut T, y: &'b mut T) -> &'a mut T { todo!() } }