|
| 1 | +# Turning the max-with-lt program into a property proof |
| 2 | + |
| 3 | +## Example program that we start from |
| 4 | + |
| 5 | +```rust |
| 6 | +fn main() { |
| 7 | + |
| 8 | + let a:usize = 42; |
| 9 | + let b:usize = 22; |
| 10 | + let c:usize = 0; |
| 11 | + |
| 12 | + let result = maximum(a, b, c); |
| 13 | + |
| 14 | + assert!(result >= a && result >= b && result >= c |
| 15 | + && (result == a || result == b || result == c ) ); |
| 16 | +} |
| 17 | + |
| 18 | +fn maximum(a: usize, b: usize, c: usize) -> usize { |
| 19 | + // max(a, max(b, c)) |
| 20 | + let max_ab = if a < b {b} else {a}; |
| 21 | + if max_ab < c {c} else {max_ab} |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | +We want to prove a property of `maximum`: |
| 26 | +- When called with `a`, `b`, and `c`, |
| 27 | +- the `result` will be greater or equal all of the arguments, |
| 28 | +- and equal to one (or more) of them. |
| 29 | + |
| 30 | +The `main` program above states this using some concrete values of `a`, `b`, and `c`. We will run this program to construct a general symbolic claim and prove it. |
| 31 | + |
| 32 | +In a future version, we will be able to start directly with the `maximum` function call and provide symbolic arguments to it. This will save some manual work setting up the claim file and fits the target of proving based on property tests. |
| 33 | + |
| 34 | +## Extracting Stable MIR for the program |
| 35 | + |
| 36 | +Before we can run the program using the MIR semantics, we have to compile it with a special compiler to extract Stable MIR from it. This step differs a bit depending on whether the program has multiple crates, in our case it is just a simple `rustc` invocation. This creates `main-max-with-lt.smir.json`. (Run the below commmands from the `mir-semantics/rust-verification-proofs/maximum-proof/` directory). |
| 37 | + |
| 38 | +```shell |
| 39 | +cargo -Z unstable-options -C ../../deps/stable-mir-json/ run -- -Zno-codegen --out-dir $PWD $PWD/main-max-with-lt.rs |
| 40 | +``` |
| 41 | +The Stable MIR for the program can also be rendered as a graph, using the `--dot` option. This creates `main-max-with-lt.smir.dot`. |
| 42 | + |
| 43 | +```shell |
| 44 | +cargo -Z unstable-options -C ../../deps/stable-mir-json/ run -- --dot -Zno-codegen --out-dir $PWD $PWD/main-max-with-lt.rs |
| 45 | +``` |
| 46 | +## Constructing the claim by executing `main` to certain points |
| 47 | +Through concrete execution of the parsed K program we can interrupt the execution after a given number of rewrite steps to inspect the intermediate state. This will help us with writing our claim manually until the process is automated. |
| 48 | + |
| 49 | +1. The program (`main`) reaches the call to `maximum` after 22 steps. |
| 50 | + The following command runs it and displays the resulting program state. |
| 51 | + |
| 52 | + ```shell |
| 53 | + poetry -C ../../kmir/ run -- kmir run $PWD/main-max-with-lt.smir.json --depth 22 | less -S |
| 54 | + ``` |
| 55 | + - Arguments `a`, `b`, and `c` are initialised to `Integer`s as `locals[1]` to `locals[3]` |
| 56 | + - A `call` terminator calling function `ty(25)` is executed next (front of the `k` cell) |
| 57 | + - The function table contains `ty(25) -> "maximum" code. |
| 58 | + - Other state (how `main` continues, its other local variables, and some internal functions) is relevant to the proof we want to perform. |
| 59 | +2. The program executes for a total of 92 steps to reach the point where it `return`s from `maximum`. |
| 60 | + The following command runs it and displays the resulting program state. |
| 61 | +
|
| 62 | + ```shell |
| 63 | + poetry -C ../../kmir/ run -- kmir run $PWD/main-max-with-lt.smir.json --depth 92 | less -S |
| 64 | + ``` |
| 65 | + - The value `locals[0]` is now set to an `Integer`. This will be the target of our assertions. |
| 66 | + - A `return` terminator is executed next (front of the `k` cell), it will return `locals[0]` |
| 67 | + - It should be an `Integer` with the desired properties as stated above |
| 68 | + |
| 69 | +State 1. defines our start state for the claim. Irrelevant parts are elided (replaced by variables). |
| 70 | +* The code of the `maximum` function in the `functions` table needs to be kept. We also keep its identifier `ty(25)`. Other functions can be removed (we won't perform a return). |
| 71 | +* The `call` terminator is kept, calling `ty(25)` with arguments from `locals[1,2,3]`. `target` is modified to be `noBasicBlockIdx` to force termination of the prover (no block to jump back to). |
| 72 | +* The four locals `0` - `3` are required in their original order to provide the function arguments. The values of `a`, `b`, and `c` in locals `1` - `3` are replaced with symbolic variables used in the proof. |
| 73 | +* We could keep all other locals but do not have to (however it is important that the list of locals has a known length). |
| 74 | +* `main`s other details in `currentFrame` are irrelevant and elided. |
| 75 | +
|
| 76 | +
|
| 77 | +State 2. is the end state, where all that matters is the returned value. |
| 78 | +
|
| 79 | +* The `locals` list should contain this `?RESULT` value at index `0` |
| 80 | +* The `?RESULT` value should have the properties stated (equivalent to the assertion in `main`) |
| 81 | +* Because of the modified `target`, the program should end, i.e., have an `#EndProgram` in the `k` cell. |
| 82 | +
|
| 83 | +The above is written as a _claim_ in K framework language into a `maximum-spec.k` file. |
| 84 | +Most of the syntax can be copied from the output of the `kmir run` commands above, and irrelevant parts replaced by `_` (LHS) or `?_` (RHS). |
| 85 | +
|
| 86 | +Alternatively, it is possible to construct a claim that the entire rest of the program after initialising the variables will result in the desired `?RESULT`, i.e., the assertion in `main` is executed successfully and the program ends in `#EndProgram` after checking it. This would require more steps. |
| 87 | +
|
| 88 | +## Running the prover on the claim and viewing the proof |
| 89 | +Now that we have constructed claim, we can run use the KMIR verifier to perform symbollic execution, and can view the state of proof through the KMIR proof viewer. |
| 90 | +```shell |
| 91 | +poetry -C ../../kmir/ run -- kmir prove run $PWD/maximum-spec.k --proof-dir $PWD/proof |
| 92 | +``` |
| 93 | +
|
| 94 | +The proof steps are saved in the `$PWD/proof` directory for later inspection using `kmir prove view`. This is especially important when the proof does _not_ succeed immediately. |
| 95 | +
|
| 96 | +```shell |
| 97 | +poetry -C ../../kmir/ run -- kmir prove view MAXIMUM-SPEC.maximum-spec --proof-dir $PWD/proof |
| 98 | +``` |
0 commit comments