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

Validity Conditions

We associate borrow PCG edges with validity conditions to identify the control-flow conditions under which they are valid. Consider the following program:

fn main(){
    // BB0
    let mut x = 1;
    let mut y = 2;
    let mut z = 3;
    let mut r = &mut x;
    if rand() {
        // BB1
        r = &mut y;
    }
    // BB2
    if rand2() {
        // BB3
        r = &mut z;
    }
    // BB4
    *r = 5;
}

We represent control flow using a primitive called a branch choice of the form . A branch choice represents the flow of control from one basic block to another. In the above code there are two possible branch choices from bb0: and and one branch choice from bb1 (to bb2).

For any given basic block in the program, an execution path leading to is an ordered list of branch choices. For example, one path of execution to bb4 can be described by: .

Validity conditions conceptually define a predicate on execution paths. For example, the validity conditions describing the control flow where r borrows from z at bb3 require that the path contain .

We represent validity conditions as a partial function mapping relevant blocks to sets of allowed target blocks.

An execution path is valid for validity conditions iff, for each , either:

  • is not a relevant block in , or
  • is in the set of allowed target blocks of in

TODO: Explain how validity conditions are initially determined when creating a new edge at a basic block (inherent VCs). Describe the join informally.

Formal Definition

Validity conditions is a map describing control-flow conditions. For each block , is a strict subset of the real successors of .

The join of validity conditions and is defined as:

Validity conditions are valid for a path iff:

The intuition behind the first case of the join is that if whether a borrow is live does not depend on what successor is taken from some basic block , then it is not necessary to store a predicate about .

This is kind of an optimization, actually removing this case for the join would not have any difference on whether the resulting validity conditions are valid for any block. In the implementation this is useful so we can have a canonical representation (and also reduces the size of the data structure).