Join Operation
The join algorithm on PCGs takes as input PCGs and and mutates to join in .
We define the join in this manner because this is similar to how the implementation works.
The algorithm proceeds in the follow steps:
- The Owned PCG of is joined into the Owned PCG of (this may also change the capabilities of )
- The capabilities of are joined into the capabilities of .
- The Borrow State of is joined into the Borrow State of
We now describe each step in detail:
Owned PCG Join
Let be the owned PCG of and the PCG of .
- For each local in the MIR body:
- If is allocated in both and :
- Join the place expansions rooted at into
- Otherwise, if is allocated in , it should be deallocated in
- If is allocated in both and :
Joining Local Expansions (Algorithm Overview)
The algorithm for joining local expansions also involves modifying capabilities (including to borrowed places), and also may cause some places to be labelled. We "emulate" a modification of the block to be joined in, so that we can use the resulting modified capabilities and borrows graph in the remainder of the join. We implement the join by defining a "one-way" join operation , and then performing a join and subsequently . The owned PCGs of and (including the associated capabilities) should be the same after the join. Formally, we could imagine a partial order where iff the owned PCG of can be transformed to the owned PCG of by a sequence of repack operations on its owned PCG. The join identifies the maximum graph (w.r.t ) where and .
Before we present the full algorithm (that takes into account borrows), we first
describe a version that would work in the absence of borrows and therefore only
considers capabilities W and E. The key point of the join is that all
places/capabilities in the resulting joined PCGs should be obtainable from the
originals (i.e. via a sequence of PCG repacks).
The join starts from the root of both trees, and navigate downwards. For
expansions that are the same in both, nothing needs to happen. If the other
tree is expanded more than us, we continue expanding ours: the only reason it
would be expanded is that part of it is moved out, so we can expand and weaken
the LHS to match the RHS. For example, if the LHS is {x: E}, and the RHS is
{x.f: E, x.g: W}, we'd want to expand x in the LHS (note that the subsequent
capability join will properly account for capabilities).
If we're at a place where the expansions differ in both trees (e.g. x -> {x@Some} vs x -> {x@None}), we collapse both to the place (e.g x) and set
capability to the place to W. Neither of the expansion places are accessible by
both, so the "most" capability we could obtain is the W permission to the base.
More formally, we define the one-way join as the traversal where:
- If we're at a leaf in that is not a leaf in
(having children ), then:
- Expand to in
- If were at a node in where the expansion is not the same as the expansion in :
- Collapse both and to for capability
Now, we extend the join to work with borrows and the read capability . Borrows complicate the story because it enables mutually exclusive (at runtime) places to appear in the PCG. For example:
#![allow(unused)] fn main() { fn f(x: Either<String, String>) -> &String; { let result = match x { Left(l) => &l, Right(r) => &r }; result } }
After the assignment, we should have capability to both x@Left and
x@Right because they are borrowed into result. In the case of exclusive
borrows, such places would not have capability but still present in the owned
PCG (again, to reflect the target of result). Therefore, the collapse and
expand rule previously shown are no longer sufficient.
- If we're at a place that's a leaf in both and :
- If has capability of at least in and has capability exactly in :
- Then, after the join, the place can have at most capability . Its also possible that we can now have incompatible different owned expansions of in the joined PCG, that have been borrowed under mutually exclusive paths.
- If has capability of at least in and has capability exactly in :
Local Expansions Join:
The algorithm joins a set of place expansions into a set of place expansions , and makes corresponding changes to borrows and capabilities .
- If either or have any expansions:
- Perform
- Perform
- Otherwise:
- Let be the local
- Perform
- Perform
We define as:
- Let
- For each expansion (shortest first):
- Let be the base of
- If there exists a where is a prefix of , then ignore this expansion
- Otherwise, let by the set of (direct) expansions from in
- If :
- Perform
- Otherwise, if is empty and :
- If :
- Perform
- Otherwise, perform a normal expansion operation of in
- If :
- Otherwise, perform
- If the result of the join is to collapse to , then add to
We define as:
- If
- Add expansion to
- Perform
- Otherwise, if :
- Let be the base of
- Perform
- Otherwise do nothing
Expansion Edge One-Way Join
We define as:
- For each place in the expansion of :
- Perform
Expansion Edge Two-Way Join
We define as:
- Let be the base of
- If and :
- Perform a regular expand of
- Otherwise, if there exists a descendant of in that does not have a capability:
- Insert into (do not change capabilities)
- Otherwise:
- Collapse and to
Place Join
We define as:
- If is not a leaf in or is not a leaf in or or :
- Abort
- Otherwise, let ,
- If and :
- Perform
- If and :
- Perform
- If and :
- Perform
Leaf RW Join
We define as:
- Label all postfixes places of in
Place Capabilitiies Join
The algorithm join() is defined as:
- For each
p: cin :- If :
- Remove capability to in
- Otherwise:
- If is defined:
- Assign capability to in
- Otherwise, remove capability to in
- If is defined:
- If :
Borrow State Join
- The borrow graphs are joined
- The validity conditions are joined
Borrow PCG Join
Joining into , where is the block for and is the block for .
Update the validity conditions for each edge in to require the branch condition .
Update the validity
- If is a loop head perform the loop join algorithm as described here.
- Otherwise:
- For each edge in :
- If there exists an edge of the same kind in
- Join the validity conditions associated with in to the validity conditions associated with in
- Otherwise, add to
- If there exists an edge of the same kind in
- For all placeholder lifetime projections in :
- Label all lifetime projection nodes of the form in with
- For each edge in :