Loop Unrolling and Bounded Model Checking
Loop unrolling replaces a while loop by a fixed number of iterations k and a terminator expectation to be used if more than k iterations run.
For example, it under-approximates least fixed-point semantics (wp and ert, k = 0):
This simple proof rule does not require invariants, just a choice of how many loop iterations to be done.
There are three main applications of loop unrolling:
- Approximating loop semantics: Approximating the expected value of loops to gain insight into the unbounded semantics.
- Verification: Proving specifications of loops.
- Refutation: Refuting specifications of loops. This is often called bounded model checking.
Read the Soundness section for details on the soundness of the proof rule.
Caesar supports using probabilistic model checking to compute and verify expected values of probabilistic programs. For probabilistic model checking, one would bound the number of explored states as opposed to the number of loop iterations like is done here. Both techniques are related, but we explain the difference between applying the two techniques in more detail here.
Usage
A while loop is annotated by the @unroll(k, terminator) annotation where k is a number literal and terminator is an expression of type EUReal.
The terminator expression must correspond to the initial value of the fixed-point iteration semantics of the loop.
By Kleene's fixed-point theorem, one should use the following values for terminator:
- For greatest fixed-point semantics (
wlp), one chooses the top element of the lattice:1for the one-bounded wlp semantics,- or
\inftyfor the unbounded expectation-based semantics.
- For least fixed-point semantics (
wp,ert), one chooses the bottom element of the lattice:0for expectation-based semantics (wp, ert).
Approximating Loop Semantics
Loop unrolling can be used to approximate expected value semantics of a loop to gain insight into the actual semantics.
The idea is to have Caesar to calculate the expected value of the loop after a fixed number of iterations.
As the unrolling approaches the true semantics as k increases, we can use this to e.g. guess the expected value after an unbounded number of iterations.
This might help to find e.g. an inductive invariant for the loop.
In contrast to the applications of verification and refutation, unrolling for approximations does not require a pre, but just a post expectation.
Consider this simple geometric loop example.
We want to encode a loop with wp semantics, so we use 0 as our terminator.
@wp proc geo1_unroll() -> (c: UInt)
pre \infty
post 1
{
c = 0
var cont: Bool = true
@unroll(3, 0) // k = 3, terminator = 0
while cont {
var prob_choice: Bool = flip(0.5)
if prob_choice { cont = false } else { c = c + 1 }
}
}
By using pre \infty, we ensure that Caesar will print a counter-example for (non-infinite) values of the pre.
Caesar will show a counter-example with an evaluation ("the pre-quantity evaluated to:") when we run it.
We can evaluate different values for k to see how the expected value of c changes after more iterations.
- For
k = 0, the expected value is0. - For
k = 1, the expected value is0. - For
k = 2, the expected value is0.5. - For
k = 3, the expected value is0.75. - For
k = 4, the expected value is0.875.
This sequence is a consequence of the definition of the fixed-point semantics and the corresponding fixed-point iteration.
We can guess (and it is true) that the expected value will eventually converge to 1 as k approaches infinity.
Verification with Loop Unrolling
Loop unrolling can be used to prove specifications of loops.
We modified the above example to show a lower bound of 0.75 on the probability value of termination in at most 3 unrollings of the loop.
@wp proc geo1_unroll() -> (c: UInt)
pre 0.75
post 1
{
c = 0
var cont: Bool = true
@unroll(3, 0) // k = 3, terminator = 0
while cont {
var prob_choice: Bool = flip(0.5)
if prob_choice { cont = false } else { c = c + 1 }
}
}
For verification, the following combinations are sound. More details can be found in the Soundness section.
- For
wlp, use@unroll(k, 1)in acoprocto verify upper bounds on the greatest fixed-point semantics. - For
wpandert, use@unroll(k, 0)in aprocto verify lower bounds on the least fixed-point semantics.
A counter-example from Caesar to verification will only be a counter-example for the -unrolled program, but not for the original program.
Use the calculus annotations @wp, @wlp, @ert to have Caesar check you apply the unroll proof correctly to verify a specification.
Refutation (Bounded Model Checking)
Loop unrolling is often used to refute specifications as well. This is done because it is a simple way to unroll a loop and then check the specification can be refuted already after a fixed number of iterations. This is in contrast to e.g. the induction proof rule, which requires an inductive invariant to be specified to refute specifications properly. When used for refutations, this technique is often called bounded model checking (BMC).
In the following example, we use loop unrolling to refute an upper bound (coproc) of least fixed-point semantics (wp).
Because we want to approximate least fixpoint semantics, we use 0 as our terminator (c.f. Usage).
coproc geo1_bmc(init_c: UInt) -> (c: UInt)
pre init_c + 0.99
post c
{
c = init_c
var cont: Bool = true
@unroll(12, 0) // k = 12, terminator = 0
while cont {
var prob_choice: Bool = flip(0.5)
if prob_choice { cont = false } else { c = c + 1 }
}
}
Trying to verify geo1_unroll will yield a counter-example to verification.
It is a true counter-example to init_c + 0.99 being an upper bound for the original program.
The following combinations are sound for refutations. More details can be found in the Soundness section.
- For
wlp, use@unroll(k, 1)in aprocto refute a lower bound on the greatest fixed-point semantics. - For
wp, use@unroll(k, 0)in acoprocto refute an upper bound on the least fixed-point semantics.
If the program verifies in the above cases, we do not know whether the specification holds for the original semantics.
For example, the example program above verifies if you change k = 11; however this gives you no guarantee that the original program geo1_unroll satisfies the specification.
At the moment, the calculus annotations @wlp, @wp, @ert do not support the use of @unroll for refutations.
They currently require soundness for verification only, therefore they currently can not be used to check the soundness of a refutation.
Soundness
By Kleene's fixed-point theorem, the following soundness conditions hold:
For the application of verification, this means:
procverifies using@unroll(k, 1)specification also holds for the originalwlpsemantics.coprocverifies using@unroll(k, 0)specification also holds for the originalwpsemantics.coprocverifies using@unroll(k, 0)specification also holds for the originalertsemantics.
For the application of refutation (bounded model checking), this means:
coprocrefutes using@unroll(k, 1)specification does not hold for the originalwlpsemantics.procrefutes using@unroll(k, 0)specification does not hold for the originalwpsemantics.procrefutes using@unroll(k, 0)