|
1 | 1 | # **(WIP)** Documentation for Miri-GenMC
|
2 | 2 |
|
| 3 | +**NOTE: GenMC mode is not yet fully implemented, and has [several correctness issues](https://github.com/rust-lang/miri/issues/4572). Using GenMC mode currently requires manually compiling Miri, see [Usage](#usage).** |
| 4 | + |
| 5 | + |
3 | 6 | [GenMC](https://github.com/MPI-SWS/genmc) is a stateless model checker for exploring concurrent executions of a program.
|
4 | 7 | Miri-GenMC integrates that model checker into Miri.
|
5 | 8 |
|
6 |
| -**NOTE: Currently, no actual GenMC functionality is part of Miri, this is still WIP.** |
| 9 | +Miri in GenMC mode takes a program as input like regular Miri, but instead of running it once, the program is executed repeatedly, until all possible executions allowed by the Rust memory model are explored. |
| 10 | +This includes all possible thread interleavings and all allowed return values for atomic operations, including cases that are very rare to encounter on actual hardware. |
| 11 | +(However, this does not include other sources of non-determinism, such as the absolute addresses of allocations. |
| 12 | +It is hence still possible to have latent bugs in a test case even if they passed GenMC.) |
7 | 13 |
|
8 |
| -<!-- FIXME(genmc): add explanation. --> |
| 14 | +GenMC requires the input program to be bounded, i.e., have finitely many possible executions, otherwise it will not terminate. |
| 15 | +Any loops that may run infinitely must be replaced or bounded (see below). |
| 16 | + |
| 17 | +GenMC makes use of Dynamic Partial Order Reduction (DPOR) to reduce the number of executions that must be explored, but the runtime can still be super-exponential in the size of the input program (number of threads and amount of interaction between threads). |
| 18 | +Large programs may not be verifiable in a reasonable amount of time. |
9 | 19 |
|
10 | 20 | ## Usage
|
11 | 21 |
|
12 | 22 | For testing/developing Miri-GenMC:
|
| 23 | +- install all [dependencies required by GenMC](https://github.com/MPI-SWS/genmc?tab=readme-ov-file#dependencies) |
13 | 24 | - clone the Miri repo.
|
14 | 25 | - build Miri-GenMC with `./miri build --features=genmc`.
|
15 | 26 | - OR: install Miri-GenMC in the current system with `./miri install --features=genmc`
|
@@ -50,6 +61,66 @@ Note that `cargo miri test` in GenMC mode is currently not supported.
|
50 | 61 |
|
51 | 62 | <!-- FIXME(genmc): add tips for using Miri-GenMC more efficiently. -->
|
52 | 63 |
|
| 64 | +### Eliminating unbounded loops |
| 65 | + |
| 66 | +As mentioned above, GenMC requires all loops to be bounded. |
| 67 | +Otherwise, it is not possible to exhaustively explore all executions. |
| 68 | +Currently, Miri-GenMC has no support for automatically bounding loops, so this needs to be done manually. |
| 69 | + |
| 70 | +#### Bounding loops without side effects |
| 71 | + |
| 72 | +The easiest case is that of a loop that simply spins until it observes a certain condition, without any side effects. |
| 73 | +Such loops can be limited to one iteration, as demonstrated by the following example: |
| 74 | + |
| 75 | +```rust |
| 76 | +#[cfg(miri)] |
| 77 | +unsafe extern "Rust" { |
| 78 | + // This is a special function that Miri provides. |
| 79 | + // It blocks the thread calling this function if the condition is false. |
| 80 | + pub unsafe fn miri_genmc_assume(condition: bool); |
| 81 | +} |
| 82 | + |
| 83 | +// This functions loads an atomic boolean in a loop until it is true. |
| 84 | +// GenMC will explore all executions where this does 1, 2, ..., ∞ loads, which means the verification will never terminate. |
| 85 | +fn spin_until_true(flag: &AtomicBool) { |
| 86 | + while !flag.load(Relaxed) { |
| 87 | + std::hint::spin_loop(); |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +// By replacing this loop with an assume statement, the only executions that will be explored are those with exactly 1 load that observes the expected value. |
| 92 | +// Incorrect use of assume statements can lead GenMC to miss important executions, so it is marked `unsafe`. |
| 93 | +fn spin_until_true_genmc(flag: &AtomicBool) { |
| 94 | + unsafe { miri_genmc_assume(flag.load(Relaxed)) }; |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +#### Bounding loops with side effects |
| 99 | + |
| 100 | +Some loops do contain side effects, meaning the number of explored iterations affects the rest of the program. |
| 101 | +Replacing the loop with one iteration like we did above would mean we miss all those possible executions. |
| 102 | + |
| 103 | +In such a case, the loop can be limited to a fixed number of iterations instead. |
| 104 | +The choice of iteration limit trades off verification time for possibly missing bugs requiring more iterations. |
| 105 | + |
| 106 | +```rust |
| 107 | +/// The loop in this function has a side effect, which is to increment the counter for the number of iterations. |
| 108 | +/// Instead of replacing the loop entirely (which would miss all executions with `count > 0`), we limit the loop to at most 3 iterations. |
| 109 | +fn count_until_true_genmc(flag: &AtomicBool) -> u64 { |
| 110 | + let mut count = 0; |
| 111 | + while !flag.load(Relaxed) { |
| 112 | + count += 1; |
| 113 | + std::hint::spin_loop(); |
| 114 | + // Any execution that takes more than 3 iterations will not be explored. |
| 115 | + unsafe { miri_genmc_assume(count <= 3) }; |
| 116 | + } |
| 117 | + count |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +<!-- FIXME: update the code above once Miri supports a loop bounding features like GenMC's `--unroll=N`. --> |
| 122 | +<!-- FIXME: update this section once Miri-GenMC supports automatic program transformations (like spinloop-assume replacement). --> |
| 123 | + |
53 | 124 | ## Limitations
|
54 | 125 |
|
55 | 126 | Some or all of these limitations might get removed in the future:
|
|
0 commit comments