Open petemud opened 2 months ago
Thanks for bringing this issue to our attention. I do not remember now why support for them was dropped. Irreducible graphs have not surfaced before in Boogie applications so we never considered adding support for them. Do you have any concrete application where you need them?
Do you have any concrete application?
Yes. See "Real-world example" from above. I'm using boogie to prove properties of libvsync atomics, which are implemented in ARM assembly
ok, I will look into it. But it will take me some time. Do you think you can make progress by transforming to irreducible CFGs on your side? I assume you have a toolchain for generating Boogie and you can do the transformation in that toolchain.
I don't actually have any toolchain. The project is small. Part of it is hand-written Boogie model for ARM and part is a parser from assembly. I use Boogie precisely because I need to deal with control flow. I was first doing everything in Coq and then SMT-LIB directly, but there dealing with control flow is too cumbersome. Your "Weakest-precondition of unstructured programs" paper looked promising, so I switched to Boogie.
Irreducibility shouldn't be a problem really. The transformations Boogie makes are:
assert
at the beginning of basic block into assert
at the end of all direct predecessors and assume
instead of assert
in current blockhavoc
variables that change on path from loop header to back edgeBoth of these don't require irreducibility. Irreducibility just ensures that there is only one loop header, so you can find where to apply 1 and 2. In irreducible graph - with more than one loop entry - you can just use the block that comes first in code as loop header.
P.S. I'm currently writing a Coq proof that doing this is actually ok
Irreducibility shouldn't be a problem really. The transformations Boogie makes are:
1. Split `assert` at the beginning of basic block into `assert` at the end of all direct predecessors and `assume` instead of `assert` in current block 2. Cut back edges and `havoc` variables that change on path from loop header to back edge
Both of these don't require irreducibility. Irreducibility just ensures that there is only one loop header, so you can find where to apply 1 and 2. In irreducible graph - with more than one loop entry - you can just use the block that comes first in code as loop header.
Reducibility is a requirement for the back-edge elimination to be sound (that is, each loop must have at most one entry point). This example shows why irreducibility leads to unsoundness in general:
procedure irreducible() {
var i: int;
var j: int;
i := 1;
goto B1, B4;
B1:
assume j > 0;
goto B2;
B2:
assert i > 0;
goto B3;
B3:
assert j > 0;
goto B4;
B4:
i := i+1;
goto B2;
}
Here, B2 and B4 are two entry points of the same loop and thus the resulting CFG is irreducible.
This program is not correct and thus should not be verified by Boogie, because the execution path B4 -> B2 -> B3 leads to failure if j < 0
holds at the beginning.
Suppose we choose to eliminate the back edge from B4 to B2 (instead of B3 to B4). Then, the resulting program is correct (and would be verified by Boogie), which shows the unsoundness.
One correct way of verifying this program would be to first turn the CFG into a reducible CFG with the same executions and to then do the back-edge elimination.
P.S. I'm currently writing a Coq proof that doing this is actually ok
Interesting. We made a subset of Boogie proof-producing using Isabelle a while ago (see https://link.springer.com/chapter/10.1007/978-3-030-81688-9_33), which included formally justifying the back-edge elimination (in our approach, a proof is produced on every run instead of proving the approach once and for all, which simplies things). It would be interesting to know how your proof compares to ours.
I studied this issue more thoroughly. It appears to me that there are two separate issues going on:
The behavior of the option /extractLoops
is unpredictable if an irreducible loop is encountered. The loop extraction procedure (extraction to a recursive procedure) works only for irreducible loops. Today, this option silently unrolls loops to a depth supplied by another option RecursionBound
. I think it is better to decouple the behavior of /extractLoops
from RecursionBound
and instead tie it to the option LoopUnrollCount
which can be set from the command line. The default of LoopUnrollCount
is -1 which means no unrolling and which could be used to propagate the IrreducibleGraph exception during loop extraction.
Boogie does not support irreducible graphs and we have a user who needs this feature. So we should try to add this feature.
@gauravpartha You are absolutely correct. I was wrong about cutting back edge in irreducible graph, because it also cuts a forward edge in corresponding reducible graph.
graph LR
A["`*A:*`"]
B["`*B:* x := 1`"]
C["`*C:*`"]
D["`*D:* **assert** x == 1`"]
E["`*E:*`"]
F["`*F:*`"]
A --> B & E
B --> C
C --> D
D --> E
%%E -. blocks between E to C .-> C
E --> F
F -.-> C
graph LR
A["`*A:*`"]
B["`*B:* x := 1`"]
C["`*C:*`"]
D["`*D:* **assert** x == 1`"]
E["`*E:*`"]; E'["`*E':*`"]
F["`*F:*`"]; F'["`*F':*`"]
A --> B & E'
B --> C
C --> D
D --> E
%%E -.blocks between E to C.-> C
%%E' -.blocks between E to C.-> C
E --> F
F -.-> C
E' --> F'
F' -.-> C
I still think - not sure if it's advantageous though - that this issue can be worked around without copying any blocks by:
havoc
ing anythingassert
s from loop head to its predecessors, i.e. only using invariants.
For reducible loops automatically assert
ing variables that don't change should be as easy as havoc
ing those that changegraph LR
A["`*A:*`"]
B["`*B:* x := 1`"]
C["`*C:*`"]
D["`*D:* **assert** x == 1`"]
E["`*E:*`"]
F["`*F:*`"]
A --> B & E
B -.-> C
C --> D
D --> E
%%E -.blocks between E to C.-> C
%%E' -.blocks between E to C.-> C
E --> F
F -.-> C
An example of verification condition in the style of WP of unstructured programs. Which fails. As it should
(declare-const x Int)
(declare-const A_ok Bool)
(declare-const B_ok Bool)
(declare-const C_ok Bool)
(declare-const D_ok Bool)
(declare-const E_ok Bool)
(declare-const F_ok Bool)
(define-fun A_be () Bool
(= A_ok
(and B_ok E_ok)
)
)
(define-fun B_be () Bool
(= B_ok
(=> (= x 1) true)
)
)
(define-fun C_be () Bool
(= C_ok D_ok)
)
(define-fun D_be () Bool
(= D_ok
(and (= x 1) E_ok)
)
)
(define-fun E_be () Bool
(= E_ok F_ok)
)
(define-fun F_be () Bool
(= F_ok true)
)
(assert
(not
(=>
(and
A_be
B_be
C_be
D_be
E_be
F_be
)
(and
A_ok
C_ok
)
)
)
)
(check-sat)
(get-model)
Even if an automatic method for converting an irreducible CFG to a reducible CFG could be implemented, I don't understand how it would address the problem of specifying the appropriate loop invariant needed for verifying the loop. For well-structured (or reducible) loops, it is clear to the programmer where to put the loop invariant and this information is exploited by Boogie to generate the appropriate verification conditions.
Any suggestions?
Invariant always comes in loop header. Here are a couple of suggestions for which one of two loop headers to use:
assert
s; fail if both loop entries have theminvariant
keyword. This keyword would only be allowed in a loop header and have the same meaning as assert
. Again, if both loop entries have invariant
, then fail
History of commits
added code to handle irreducible graphs
Support for irreducible graphs (with extractLoops)
So now the only surviving support for irreducible flow graphs I found is undocumented
/extractLoops
optionProblem with
/extractLoops
Let's say, we have the canonical nonreducible flow graph
According to the Dragon Book (9.7.6 Handling Nonreducible Flow Graphs) this flow graph should be transformed into one of
Meanwhile
/extractLoops
just unrolls loop and produces control flow which is not equivalent to originalReal-world example
https://github.com/open-s4c/libvsync/blob/main/include/vsync/atomic/internal/arm64.h#L551-L564
Conclusions
Are there any unsolvable problems with properly handling irreducible flow graphs, that it was removed by @shazqadeer 28d46f807724f88d77b113200cfb769cb76fa28b? Can it be brought back or are there other ways of dealing with this issue in Boogie?
Quotes