Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. The Owned PCG of is joined into the Owned PCG of (this may also change the capabilities of )
  2. The capabilities of are joined into the capabilities of .
  3. 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 .

  1. For each local in the MIR body:
    1. If is allocated in both and :
      1. Join the place expansions rooted at into
    2. Otherwise, if is allocated in , it should be deallocated in

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:

  1. If we're at a leaf in that is not a leaf in (having children ), then:
    1. Expand to in
  2. If were at a node in where the expansion is not the same as the expansion in :
    1. 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.

  1. If we're at a place that's a leaf in both and :
    1. If has capability of at least in and has capability exactly in :
      1. 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.
TODO: continue. (An overly formal version is below)

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 .

  1. If either or have any expansions:
    1. Perform
    2. Perform
  2. Otherwise:
    1. Let be the local
    2. Perform
    3. Perform

We define as:

  1. Let
  2. For each expansion (shortest first):
    1. Let be the base of
    2. If there exists a where is a prefix of , then ignore this expansion
    3. Otherwise, let by the set of (direct) expansions from in
    4. If :
      1. Perform
    5. Otherwise, if is empty and :
      1. If :
        1. Perform
      2. Otherwise, perform a normal expansion operation of in
    6. Otherwise, perform
      1. If the result of the join is to collapse to , then add to

We define as:

  1. If
    1. Add expansion to
    2. Perform
  2. Otherwise, if :
    1. Let be the base of
    2. Perform
  3. Otherwise do nothing

Expansion Edge One-Way Join

We define as:

  1. For each place in the expansion of :
    1. Perform

Expansion Edge Two-Way Join

We define as:

  1. Let be the base of
  2. If and :
    1. Perform a regular expand of
  3. Otherwise, if there exists a descendant of in that does not have a capability:
    1. Insert into (do not change capabilities)
  4. Otherwise:
    1. Collapse and to

Place Join

We define as:

  1. If is not a leaf in or is not a leaf in or or :
    1. Abort
  2. Otherwise, let ,
  3. If and :
    1. Perform
  4. If and :
    1. Perform
  5. If and :
    1. Perform

Leaf RW Join

We define as:

  1. Label all postfixes places of in
TODO: Actually it seems like shared references should maintain E capability even if they're dereferenced

Place Capabilitiies Join

The algorithm join() is defined as:

  1. For each p: c in :
    1. If :
      1. Remove capability to in
    2. Otherwise:
      1. If is defined:
        1. Assign capability to in
      2. Otherwise, remove capability to in

Borrow State Join

  1. The borrow graphs are joined
  2. 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

  1. If is a loop head perform the loop join algorithm as described here.
  2. Otherwise:
    1. For each edge in :
      1. If there exists an edge of the same kind in
        1. Join the validity conditions associated with in to the validity conditions associated with in
      2. Otherwise, add to
    2. For all placeholder lifetime projections in :
      1. Label all lifetime projection nodes of the form in with