From 65d290556f4852dc5149ae2407e1fd7d700aefb2 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 28 Nov 2024 20:33:08 -0800 Subject: [PATCH] Write a new post about proving the connection between semantics and CFGs Signed-off-by: Danila Fedorin --- content/blog/06_spa_agda_cfg/index.md | 1 + .../07_spa_agda_semantics_and_cfg/index.md | 376 ++++++++++++++++++ .../while-cfg.dot | 15 + .../while-cfg.png | Bin 0 -> 14727 bytes 4 files changed, 392 insertions(+) create mode 100644 content/blog/07_spa_agda_semantics_and_cfg/index.md create mode 100644 content/blog/07_spa_agda_semantics_and_cfg/while-cfg.dot create mode 100644 content/blog/07_spa_agda_semantics_and_cfg/while-cfg.png diff --git a/content/blog/06_spa_agda_cfg/index.md b/content/blog/06_spa_agda_cfg/index.md index 915c93d..256f615 100644 --- a/content/blog/06_spa_agda_cfg/index.md +++ b/content/blog/06_spa_agda_cfg/index.md @@ -190,6 +190,7 @@ some of the indices of the elements within. For instance, in the lists concatenated list `[x, y]`, the index of `x` is still `0`, but the index of `y` is `1`. More generally, when we concatenate two lists `l1` and `l2`, the indices into `l1` remain unchanged, whereas the indices `l2` are shifted by `length l1`. +{#fin-reindexing} Actually, that's not all there is to it. The _values_ of the indices into the left list don't change, but their types do! They start as `Fin (length l1)`, diff --git a/content/blog/07_spa_agda_semantics_and_cfg/index.md b/content/blog/07_spa_agda_semantics_and_cfg/index.md new file mode 100644 index 0000000..5659105 --- /dev/null +++ b/content/blog/07_spa_agda_semantics_and_cfg/index.md @@ -0,0 +1,376 @@ +--- +title: "Implementing and Verifying \"Static Program Analysis\" in Agda, Part 7: Connecting Semantics and Control Flow Graphs" +series: "Static Program Analysis in Agda" +description: "In this post, I prove that the Control Flow Graphs from the previous sections are sound according to our language's semantics" +date: 2024-11-28T20:32:00-07:00 +tags: ["Agda", "Programming Languages"] +--- + +In the previous two posts, I covered two ways of looking at programs in +my little toy language: + +* [In part 5]({{< relref "05_spa_agda_semantics" >}}), I covered the + __formal semantics__ of the programming language. These are precise rules + that describe how programs are executed. These serve as the source of truth + for what each statement and expression does. + + Because they are the source of truth, they capture all information about + how programs are executed. To determine that a program starts in one + environment and ends in another (getting a judgement \(\rho_1, s \Rightarrow \rho_2\)), + we need to actually run the program. In fact, our Agda definitions + encoding the semantics actually produce proof trees, which contain every + single step of the program's execution. +* [In part 6]({{< relref "06_spa_agda_cfg" >}}), I covered + __Control Flow Graphs__ (CFGs), which in short arranged code into a structure + that represents how execution moves from one statement or expression to the + next. + + Unlike the semantics, CFGs do not capture a program's entire execution; + they merely contain the possible orders in which statements can be evaluated. + Instead of capturing the exact number of iterations performed by a `while` + loop, they encode repetition as cycles in the graph. Because they are + missing some information, they're more of an approximation of a program's + behavior. + +Our analyses operate on CFGs, but it is our semantics that actually determine +how a program behaves. In order for our analyses to be able to produce correct +results, we need to make sure that there isn't a disconnect between the +approximation and the truth. In the previous post, I stated the property I will +use to establish the connection between the two perspectives: + +> For each possible execution of a program according to its semantics, +> there exists a corresponding path through the graph. + +By ensuring this property, we will guarantee that our Control Flow Graphs +account for anything that might happen. Thus, a correct analysis built on top +of the graphs will produce results that match reality. + +### Traces: Paths Through a Graph +A CFG contains each "basic" statement in our program, by definition; when we're +executing the program, we are therefore running code in one of the CFG's nodes. +When we switch from one node to another, there ought to be an edge between the +two, since edges in the CFG encode possible control flow. We keep doing this +until the program terminates (if ever). + +Now, I said that there "ought to be edges" in the graph that correspond to +our program's execution. Moreover, the endpoints of these edges have to line +up, since we can only switch which basic block / node we're executing by following +an edge. As a result, if our CFG is correct, then for every program execution, +there is a path between the CFG's nodes that matches the statements that we +were executing. + +Take the following program and CFG from the previous post as an example. + +{{< sidebyside >}} +{{% sidebysideitem weight="0.55" %}} +``` +x = 2; +while x { + x = x - 1; +} +y = x; +``` +{{% /sidebysideitem %}} +{{< sidebysideitem weight="0.5" >}} +{{< figure src="while-cfg.png" label="CFG for simple `while` code." class="small" >}} +{{< /sidebysideitem >}} +{{< /sidebyside >}} + +We start by executing `x = 2`, which is the top node in the CFG. Then, we execute +the condition of the loop, `x`. This condition is in the second node from +the top; fortunately, there exists an edge between `x = 2` and `x` that +allows for this possibility. Once we computed `x`, we know that it's nonzero, +and therefore we proceed to the loop body. This is the statement `x = x - 1`, +contained in the bottom left node in the CFG. There is once again an edge +between `x` and that node; so far, so good. Once we're done executing the +statement, we go back to the top of the loop again, following the edge back to +the middle node. We then execute the condition, loop body, and condition again. +At that point we have reduced `x` to zero, so the condition produces a falsey +value. We exit the loop and execute `y = x`, which is allowed by the edge from +the middle node to the bottom right node. + +We will want to show that every possible execution of the program (e.g., +with different variable assignments) corresponds to a path in the CFG. If one +doesn't, then our program can do something that our CFG doesn't account for, +which means that our analyses will not be correct. + +I will define a `Trace` datatype, which will be an embellished +path through the graph. At its core, a path is simply a list of indices +together with edges that connect them. Viewed another way, it's a list of edges, +where each edge's endpoint is the next edge's starting point. We want to make +illegal states unrepresentable, and therefore use the type system to assert +that the edges are compatible. The easiest way to do this is by making +our `Trace` indexed by its start and end points. An empty trace, containing +no edges, will start and end in the same node; the `::` equivalent for the trace +will allow prepending one edge, starting at node `i1` and ending in `i2`, to +another trace which starts in `i2` and ends in some arbitrary `i3`. Here's +an initial stab at that: + +```Agda +module _ {g : Graph} where + open Graph g using (Index; edges; inputs; outputs) + + data Trace : Index → Index → Set where + Trace-single : ∀ {idx : Index} → Trace idx idx + Trace-edge : ∀ {idx₁ idx₂ idx₃ : Index} → + (idx₁ , idx₂) ∈ edges → + Trace idx₂ idx₃ → Trace idx₁ idx₃ +``` + +This isn't enough, though. Suppose you had a function that takes an evaluation +judgement and produces a trace, resulting in a signature like this: + +```Agda +buildCfg-sufficient : ∀ {s : Stmt} {ρ₁ ρ₂ : Env} → ρ₁ , s ⇒ˢ ρ₂ → + let g = buildCfg s + in Σ (Index g × Index g) (λ (idx₁ , idx₂) → Trace {g} idx₁ idx₂) +``` + +What's stopping this function from returning _any_ trace through the graph, +including one that doesn't even include the statements in our program `s`? +We need to narrow the type somewhat to require that the nodes it visits have +some relation to the program execution in question. + +We could do this by indexing the `Trace` data type by a list of statements +that we expect it to match, and requiring that for each constructor, the +statements of the starting node be at the front of that list. We could compute +the list of executed statements in order using +{{< sidenote "right" "full-execution=note" "a recursive function on the `_,_⇒ˢ_` data type." >}} +I mentioned earlier that our encoding of the semantics is actually defining +a proof tree, which includes every step of the computation. That's why we can +write a function that takes the proof tree and extracts the executed statements. +{{< /sidenote >}} + +That would work, but it loses a bit of information. The execution judgement +contains not only each statement that was evaluated, but also the environments +before and after evaluating it. Keeping those around will be useful: eventually, +we'd like to state the invariant that at every CFG node, the results of our +analysis match the current program environment. Thus, instead of indexing simply +by the statements of code, I chose to index my `Trace` by the +starting and ending environment, and to require it to contain evaluation judgements +for each node's code. The judgements include the statements that were evaluated, +which we can match against the code in the CFG node. However, they also assert +that the environments before and after are connected by that code in the +language's formal semantics. The resulting definition is as follows: + +{{< codelines "Agda" "agda-spa/Language/Traces.agda" 10 18 >}} + +The `g [ idx ]` and `g [ idx₁ ]` represent accessing the basic block code at +indices `idx` and `idx₁` in graph `g`. + +### Trace Preservation by Graph Operations + +Our proofs of trace existence will have the same "shape" as the functions that +build the graph. To prove the trace property, we'll assume that evaluations of +sub-statements correspond to traces in the sub-graphs, and use that to prove +that the full statements have corresponding traces in the full graph. We built +up graphs by combining sub-graphs for sub-statements, using `_∙_` (overlaying +two graphs), `_↦_` (sequencing two graphs) and `loop` (creating a zero-or-more +loop in the graph). Thus, to make the jump from sub-graphs to full graphs, +we'll need to prove that traces persist through overlaying, sequencing, +and looping. + +Take `_∙_`, for instance; we want to show that if a trace exists in the left +operand of overlaying, it also exists in the final graph. This leads to +the following statement and proof: + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 88 97 >}} + +There are some details there to discuss. +* First, we have to change the + indices of the returned `Trace`. That's because they start out as indices + into the graph `g₁`, but become indices into the graph `g₁ ∙ g₂`. To take + care of this re-indexing, we have to make use of the `↑ˡ` operators, + which I described in [this section of the previous post]({{< relref "06_spa_agda_cfg#fin-reindexing" >}}). +* Next, in either case, we need to show that the new index acquired via `↑ˡ` + returns the same basic block in the new graph as the old index returned in + the original graph. Fortunately, the Agda standard library provides a proof + of this, `lookup-++ˡ`. The resulting equality is the following: + + ```Agda + g₁ [ idx₁ ] ≡ (g₁ ∙ g₂) [ idx₁ ↑ˡ Graph.size g₂ ] + ``` + + This allows us to use the evaluation judgement in each constructor for + traces in the output of the function. +* Lastly, in the `Trace-edge` case, we have to additionally return a proof that the + edge used by the trace still exists in the output graph. This follows + from the fact that we include the edges from `g₁` after re-indexing them. + + ```Agda + ; edges = (Graph.edges g₁ ↑ˡᵉ Graph.size g₂) List.++ + (Graph.size g₁ ↑ʳᵉ Graph.edges g₂) + + ``` + + The `↑ˡᵉ` function is just a list `map` with `↑ˡ`. Thus, if a pair of edges + is in the original list (`Graph.edges g₁`), as is evidenced by `idx₁→idx`, + then its re-indexing is in the mapped list. To show this, I use the utility + lemma `x∈xs⇒fx∈fxs`. The mapped list is the left-hand-side of a `List.++` + operator, so I additionally use the lemma `∈-++⁺ˡ` that shows membership is + preserved by list concatenation. + +The proof of `Trace-∙ʳ`, the same property but for the right-hand operand `g₂`, +is very similar, as are the proofs for sequencing. I give their statements, +but not their proofs, below. + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 99 101 >}} +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 139 141 >}} +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 150 152 >}} +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 175 176 >}} + +Preserving traces is unfortunately not quite enough. The thing that we're missing +is looping: the same sub-graph can be re-traversed several times as part of +execution, which suggests that we ought to be able to combine multiple traces +through a loop graph into one. Using our earlier concrete example, we might +have traces for evaluating `x` then `x = x -1` with the variable `x` being +mapped first to `2` and then to `1`. These traces occur back-to-back, so we +will put them together into a single trace. To prove some properties about this, +I'll define a more precise type of trace. + +### End-To-End Traces +The key way that traces through a loop graph are combined is through the +back-edges. Specifically, our `loop` graphs have edges from each of the `output` +nodes to each of the `input` nodes. Thus, if we have two paths, both +starting at the beginning of the graph and ending at the end, we know that +the first path's end has an edge to the second path's beginning. This is +enough to combine them. + +This logic doesn't work if one of the paths ends in the middle of the graph, +and not on one of the `output`s. That's because there is no guarantee that there +is a connecting edge. + +To make things easier, I defined a new data type of "end-to-end" traces, whose +first nodes are one of the graph's `input`s, and whose last nodes are one +of the graph's `output`s. + +{{< codelines "Agda" "agda-spa/Language/Traces.agda" 27 36 >}} + +We can trivially lift the proofs from the previous section to end-to-end traces. +For example, here's the lifted version of the first property we proved: + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 110 121 >}} + +The other lifted properties are similar. + +For looping, the proofs get far more tedious, because of just how many +sources of edges there are in the output graph --- they span four lines: + +{{< codelines "Agda" "agda-spa/Language/Graphs.agda" 84 94 "hl_lines=5-8" >}} + +I therefore made use of two helper lemmas. The first is about list membership +under concatenation. Simply put, if you concatenate a bunch of lists, and +one of them (`l`) contains some element `x`, then the concatenation contains +`x` too. + +{{< codelines "Agda" "agda-spa/Utils.agda" 82 85 >}} + +I then specialized this lemma for concatenated groups of edges. + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 162 172 "hl_lines=9-11" >}} + +Now we can finally prove end-to-end properties of loop graphs. The simplest one is +that they allow the code within them to be entirely bypassed +(as when the loop body is evaluated zero times). I called this +`EndToEndTrace-loop⁰`. The "input" node of the loop graph is index `zero`, +while the "output" node of the loop graph is index `suc zero`. Thus, the key +step is to show that an edge between these two indices exists: + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 227 240 "hl_lines=5-6" >}} + +The only remaining novelty is the `trace` field of the returned `EndToEndTrace`. +It uses the trace concatenation operation `++⟨_⟩`. This operator allows concatenating +two traces, which start and end at distinct nodes, as long as there's an edge +that connects them: + +{{< codelines "Agda" "agda-spa/Language/Traces.agda" 21 25 >}} + +The expression on line 239 of `Properties.agda` is simply the single-edge trace +constructed from the edge `0 -> 1` that connects the start and end nodes of the +loop graph. Both of those nodes is empty, so no code is evaluated in that case. + +The proof for combining several traces through a loop follows a very similar +pattern. However, instead of constructing a single-edge trace as we did above, +it concatenates two traces from its arguments. Also, instead of using +the edge from the first node to the last, it instead uses an edge from the +last to the first, as I described at the very beginning of this section. + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 209 225 "hl_lines=8-9" >}} + +### Proof of Sufficiency + +We now have all the pieces to show each execution of our program has a corresponding +trace through a graph. Here is the whole proof: + +{{< codelines "Agda" "agda-spa/Language/Properties.agda" 281 296 >}} + +We proceed by +{{< sidenote "right" "derivation-note" "checking what inference rule was used to execute a particular statement," >}} +Precisely, we proceed by induction on the derivation of \(\rho_1, s \Rightarrow \rho_2\). +{{< /sidenote >}} +because that's what tells us what the program did in that particular moment. + +* When executing a basic statement, we know that we constructed a singleton + graph that contains one node with that statement. Thus, we can trivially + construct a single-step trace without any edges. +* When executing a sequence of statements, we have two induction hypotheses. + These state that the sub-graphs we construct for the first and second statement + have the trace property. We also have two evaluation judgements (one for each + statement), which means that we can apply that property to get traces. The + `buildCfg` function sequences the two graphs, and we can sequence + the two traces through them, resulting in a trace through the final output. +* For both the `then` and `else` cases of evaluating an `if` statement, + we observe that `buildCfg` overlays the sub-graphs of the two branches using + `_∙_`. We also know that the two sub-graphs have the trace property. + * In the `then` case, since we have an evaluation judgement for + `s₁` (in variable `ρ₁,s₁⇒ρ₂`), we conclude that there's a correct trace + through the `then` sub-graph. Since that graph is the left operand of + `_∙_`, we use `EndToEndTrace-∙ˡ` to show that the trace is preserved in the full graph. + * In the `else` case things are symmetric. We are evaluating `s₂`, with + a judgement given by `ρ₁,s₂⇒ρ₂`. We use that to conclude that there's a + trace through the graph built from `s₂`. Since this sub-graph is the right + operand of `_∙_`, we use `EndToEndTrace-∙ʳ` to show that it's preserved in + the full graph. +* For the `true` case of `while`, we have two evaluation judgements: one + for the body and one for the loop again, this time + in a new environment. They are stored in `ρ₁,s⇒ρ₂` and `ρ₂,ws⇒ρ₃`, respectively. + The statement being evaluated by `ρ₂,ws⇒ρ₃` is actually the exact same statement + that's being evaluated at the top level of the proof. Thus, we can use + `EndToEndTrace-loop²`, which sequences two traces through the same graph. + + We also use `EndToEndTrace-loop` to lift the trace through `buildCfg s` into + a trace through `buildCfg (while e s)`. +* For the `false` case of the `while`, we don't execute any instructions, + and finish evaluating right away. This corresponds to the do-nothing trace, + which we have established exists using `EndToEndTrace-loop⁰`. + +That's it! We have now validated that the Control Flow Graphs we construct +match the semantics of the programming language, which makes them a good +input to our static program analyses. We can finally start writing those! + +### Defining and Verifying Static Program Analyses + +We have all the pieces we need to define a formally-verified forward analysis: + +* We have used the framework of lattices to encode the precision of program + analysis outputs. Smaller elements in a lattice are more specific, + meaning more useful information. +* We have [implemented fixed-point algorithm]({{< relref "04_spa_agda_fixedpoint" >}}), + which finds the smallest solutions to equations in the form \(f(x) = x\) + for monotonic functions over lattices. By defining our analysis as such a function, + we can apply the algorithm to find the most precise steady-state description + of our program. +* We have defined how our programs are executed, which is crucial for defining + "correctness". + +Here's how these pieces will fit together. We will construct a +finite-height lattice. Every single element of this lattice will contain +information about each variable at each node in the Control Flow Graph. We will +then define a monotonic function that updates this information using the +structure encoded in the CFG's edges and nodes. Then, using the fixed-point algorithm, +we will find the least element of the lattice, which will give us a precise +description of all program variables at all points in the program. Because +we have just validated our CFGs to be faithful to the language's semantics, +we'll be able to prove that our algorithm produces accurate results. + +The next post or two will be the last stretch; I hope to see you there! diff --git a/content/blog/07_spa_agda_semantics_and_cfg/while-cfg.dot b/content/blog/07_spa_agda_semantics_and_cfg/while-cfg.dot new file mode 100644 index 0000000..293696f --- /dev/null +++ b/content/blog/07_spa_agda_semantics_and_cfg/while-cfg.dot @@ -0,0 +1,15 @@ +digraph G { + graph[dpi=300 fontsize=14 fontname="Courier New"]; + node[shape=rectangle style="filled" fillcolor="#fafafa" penwidth=0.5 color="#aaaaaa"]; + edge[arrowsize=0.3 color="#444444"] + + node_begin [label="x = 2;\l"] + node_cond [label="x\l"] + node_body [label="x = x - 1\l"] + node_end [label="y = x\l"] + + node_begin -> node_cond + node_cond -> node_body + node_cond -> node_end + node_body -> node_cond +} diff --git a/content/blog/07_spa_agda_semantics_and_cfg/while-cfg.png b/content/blog/07_spa_agda_semantics_and_cfg/while-cfg.png new file mode 100644 index 0000000000000000000000000000000000000000..f196a6c422db15fd099926e0102e57235a65c5ff GIT binary patch literal 14727 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 o-z+yE{|~%!_?s@Zet@(+TDW+B