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

Obtaining Capability to a Place

The operation to obtain capability to a place in a PCG proceeds as follows.

When performing this operation to satisfy the place capability required for a statement, the analysis guarantees that no live mutable borrows conflicting with 1. At a high level, capability to is obtained by first collapsing the owned places in to

Step 1 - Label dereferences of shared borrows stored in

Reborrows of shared references derived from (i.e. from any postfix of ) will survive even if is moved or mutated. Therefore, if permits the function to mutate (i.e. ), then the analysis labels all places that project from shared references derived from .

Formally:

If , then for each place in where:

  • is a strict postfix of , and
  • is a shared reference, and
  • is in

The analysis labels every postfix of in .

Step 2 - Collapse

Implementation

This operation is implemented as PlaceObtainer::obtain

The obtain(p, o) operation reorganizes a PCG state to a new state in where the PCG node for a place is present in the graph. The parameter o is an Obtain Type which describes the reason for the expansion. The reason is either:

  • To obtain a specified capability to the place
  • To access the place because it is the borrowed place of a two-phase borrow

An Obtain Type is associated with a result capability which is either the specified capability (in the first case), or Read (in the case of access for two-phase borrows).

The "two-phase" borrow case is likely unnecessary: we can use the borrow-checker to detect if the place is also the borrowed place of a two-phase borrow reserved at the current location. In fact the current implementation make similar queries as part of the expand step.

Note that a place node for is not necessarily present in the graph before this occurs.

This proceeds as follows:

Step 1 - Label dereferences of shared borrows stored in

(Same as high-level description)

Step 2 - Upgrading Closest Ancestor From R to E

This step is included to handle a relatively uncommon case (see the Rationale section below).

If the obtain operation is called with permission or and the closest ancestor to , that is, the longest prefix of for which there exists a node in the graph, has capability, we upgrade 's capability to in exchange for removing capability to all pre- and postfix places of in the graph (excluding itself).

This is sound because if we need to obtain non-read capability to place, and there are any ancestors of place in the graph with R capability, one such ancestor originally had E capability was subsequently downgraded. This function finds such an ancestor (if one exists), and performs the capability exchange.

Perhaps it would be better to explicitly track downgrades in the analysis so that they can be upgraded later? This will make the soundness argument more convincing.

Rationale

It's possible that we want to obtain exclusive or write permission to a field that we currently only have read access for. For example, consider the following case:

  • There is an existing shared borrow of (*c).f1
  • Therefore we have read permission to *c, (*c).f1, and (*c).f2
  • Then, we want to create a mutable borrow of (*c).f2
  • This requires obtaining exclusive permission to (*c).f2

We can upgrade capability of (*c).f2 from R to E by downgrading all other pre-and postfix places of (*c).f2 to None (in this case c and *c). In the example, (*c).f2 is actually the closest read ancestor, but this is not always the case (e.g. if we wanted to obtain (*c).f2.f3 instead)

Step 3 - Collapse

Then, if a node for exists in the graph and 's capability is not at least as strong as , collapse the subgraph rooted at (and obtain capability for ) by performing the collapse(p, c) operation.

collapse

The collapse(p) operation is implemented as follows:

  • For each such that is a prefix of (from longest to shortest) and there is a node for in the graph:
    • perform the Collapse Repack Operation on .
    • For each lifetime in the type of :
      • Create a new lifetime projection node
      • For each lifetime projection node where is an expansion of :
        • Label
        • Create a new BorrowFlow edge

Step 4 - Labelling

At this point, if is , we know that a subsequent operation will mutate . As a result, if there exists a lifetime projection node for (for example, if stores a borrow that has since been reborrowed), it will no longer be tied to the current value of . So, we label with reason ReAssign.

Step 5 - Expand

At this point there should be a node for some prefix of in the graph such that .

We expand the graph to (and obtain the capability for ) by performing the expandTo(p, o) operation.

expandTo

The expandTo operation is implemented as follows:

  • For each strict prefix of (from shortest to longest):
    • If expanding one level adds new edges to the graph, then
      • We expand the lifetime projections of one level

The operation to expand a place one level is the expandPlaceOneLevel operation, and the operation to expand the lifetime projections one level is expandLifetimeProjectionsOneLevel.

expandLifetimeProjectionsOneLevel

expandLifetimeProjectionsOneLevel is defined with three parameters:

  • : The place to expand
  • : The target expansion of
  • : The Obtain Type

The operation is implemented as follows:

  • Let be the expansion of using
  • For each lifetime projection of :
    • Let be the set of lifetime projections in with lifetime
    • If is nonempty2:
      • We identify the base lifetime projection as follows:
        • Let be the current snapshot location
        • If is not an obtain for capability R:
        • Otherwise, if is blocked by a two-phase borrow live at :
          • Let be the reservation location of the conflicting borrow
        • Otherwise,
      • Create a new Borrow PCG Expansion hyperedge

  1. If the program requires capability to the place to do some action, then the place cannot be borrowed mutably. This invariant does not hold when we are obtaining capability to the place in order to construct a loop abstraction.

  2. This could happen e.g. expanding an x : Option<&'a T> to a x@None