blog-static/content/blog/06_spa_agda_cfg/index.md
Danila Fedorin f2e424944e Start working on the control flow graphs post
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2024-11-13 19:57:08 -08:00

8.3 KiB
Raw Blame History

title series description date tags draft
Implementing and Verifying "Static Program Analysis" in Agda, Part 6: Control Flow Graphs Static Program Analysis in Agda In this post, I show how I show an Agda definition of control flow graph building 2024-11-13T17:32:42-07:00
Agda
Programming Languages
true

In the previous section, I've given a formal definition of the programming language that I've been trying to analyze. This formal definition serves as the "ground truth" for how our little imperative programs are executed; however, program analyses (especially in practice) seldom use the formal semantics as their subject matter. Instead, they focus on more pragmatic program representations from the world of compilers. One such representation are Control Flow Graphs (CFGs).

Let's start by building some informal intuition. CFGs are pretty much what their name suggests. They are a type of graph. Edges in CFGs represent how execution might jump from one piece of code to another (how control might flow).

For example, take the below program.

x = ...;
if x {
  x = 1;
} else {
  x = 0;
}
y = x;

The CFG might look like this:

{{< figure src="if-cfg.png" label="CFG for simple if-else code." class="small" >}}

Here, the initialization of x with ..., as well as the if condition (just x), are guaranteed to execute one after another, so they occupy a single node. From there, depending on the condition, the control flow can jump to one of the branches of the if statement: the "then" branch if the condition is true, and the "else" branch if the condition is false. As a result, there are two arrows coming out of the initial node. Once either branch is executed, control always jumps to the code right after the if statement (the y = x). Thus, both the x = 1 and x = 0 nodes have a single arrow to the y = x node.

As another example, if you had a loop:

x = ...;
while x {
  x = x - 1;
}
y = x;

The CFG would look like this:

{{< figure src="while-cfg.png" label="CFG for simple while code." class="small" >}}

Here, condition of the loop (x) is not always guaranteed to execute together with the code that initializes x. That's because the condition of the loop is checked after every iteration, whereas the code before the loop is executed only once. As a result, x = ... and x occupy distinct CFG nodes. From there, the control flow can proceed in two different ways, depending on the value of x. If x is truthy, the program will proceed to the loop body (decrementing x). If x is falsy, the program will skip the loop body altogether, and go to the code right after the loop (y = x). This is indicated by the two arrows going out of the x node. After executing the body, we return to the condition of the loop to see if we need to run another iteration. Because of this, the decrementing node has an arrow back to the loop condition.

Now, let's be a bit more precise. Control Flow Graphs are defined as follows:

  • The nodes are basic blocks. Paraphrasing Wikipedia's definition, a basic block is a piece of code that has only one entry point and one exit point.

    The one-entry-point rule means that it's not possible to jump into the middle of the basic block, executing only half of its instructions. The execution of a basic block always begins at the top. Symmetrically, the one-exit-point rule means that you can't jump away to other code (even within the same block), skipping some instructions. The execution of a basic block always ends at the bottom.

    As a result of these constraints, when running a basic block, you are guaranteed to execute every instruction in exactly the order they occur in, and execute each instruction exactly once.

  • The edges are jumps between basic blocks. We've already seen how if and while statements introduce these jumps.

Basic blocks can only be made of code that doen't jump (otherwise, we violate the single-exit-point policy). In the previous post, we defined exactly this kind of code as [simple statements]({{< relref "05_spa_agda_semantics#introduce-simple-statements" >}}). So, in our control flow graph, nodes will be sequences of simple statements. {#list-basic-stmts}

Control Flow Graphs in Agda

Basic Definition

At an abstract level, it's easy to say "it's just a graph where X is Y" about anything. It's much harder to give a precise definition of such a graph, particularly if you want to rule out invalid graphs (e.g., ones with edges pointing nowhere). In Agda, I chose the represent a two lists: one of nodes, and one of edges. Each node is simply a list of BasicStmts, as I described in a preceding paragraph. An edge is simply a pair of numbers, each number encoding the index of the node connected by the edge.

Here's where it gets a little complicated. I don't want to use plain natural numbers for indices, because that means you can easily introduce "broken" edge. For example, what if you have 4 nodes, and you have an edge (5, 5)? Therefore, I picked the finite natural numbers represented by Fin.

data Fin :   Set where
  zero : Fin (suc n)
  suc  : (i : Fin n)  Fin (suc n)

Specifically, Fin n is the type of natural numbers less than n. Following this definition, Fin 3 represents the numbers 0, 1 and 2. These are represented using the same constructors as Nat: zero and suc. The type of zero is Fin (suc n) for any n; this makes sense because zero is less than any number plus one. For suc, the bound n of the input i is incremented by one, leading to another suc n in the final type. This makes sense because if i < n, then i + 1 < n + 1. I've previously explained this data type [in another post on this site]({{< relref "01_aoc_coq#aside-vectors-and-finite-mathbbn" >}}).

Here's my definition of Graphs written using Fin:

{{< codelines "Agda" "agda-spa/Language/Graphs.agda" 24 39 >}}

I explicitly used a size field, which determines how many nodes are in the graph, and serves both as the upper bound the edge indices as well as the size nodes field. From there, an index Index into the node list is {{< sidenote "right" "size-note" "just a natural number less than size," >}} Ther are size natural numbers less than size:
0, 1, ..., size - 1. {{< /sidenote >}} and an edge is just a pair of indices. The graph then contains a vector (exact-length list) nodes of all the basic blocks, and then a list of edges edges.

There are two fields here that I have not yet said anything about: inputs and outputs. When we have a complete CFG for our programs, these fields are totally unnecessary. However, as we are building the CFG, these will come in handy, by telling us how to stitch together smaller sub-graphs that we've already built. Let's talk about that next.

Combining Graphs

Suppose you're building a CFG for a program in the following form:

code1;
code2;

Where code1 and code2 are arbitrary pieces of code, which could include statements, loops, and pretty much anything else. Besides the fact that they occur one after another, these pieces of code are unrelated, and we can build CFGs for each one them independently. However, the fact that code1 and code2 are in sequence means that the full control flow graph for the above program should have edges going from the nodes in code1 to the nodes in code2. Of course, not every node in code1 should have such edges: that would mean that after executing any "basic" sequence of instructions, you could suddenly decide to skip the rest of code1 and move on to executing code2.

Thus, we need to be more precise about what edges we need to insert; we want to insert edges between the "final" nodes in code1 (where control ends up after code1 is finished executing) and the "initial" nodes in code2 (where control would begin once we started executing code2). Those are the outputs and inputs, respectively. When stitching together sequenced control graphs, we will connect each of the outputs of one to each of the inputs of the other.

This is defined by the operation _↦_:

{{< codelines "Agda" "agda-spa/Language/Graphs.agda" 72 83 >}}