|
| 1 | +- **Feature Name:** MIR Linker (mir_linker) |
| 2 | +- **RFC Tracking Issue**: <https://github.com/model-checking/kani/issues/1588> |
| 3 | +- **RFC PR:** <https://github.com/model-checking/kani/pull/1600> |
| 4 | +- **Status:** Under Review |
| 5 | +- **Version:** 0 |
| 6 | + |
| 7 | +------------------- |
| 8 | + |
| 9 | +## Summary |
| 10 | + |
| 11 | +Fix linking issues with the rust standard library in a scalable manner by only generating goto-program for |
| 12 | +code that is reachable from the user harnesses. |
| 13 | + |
| 14 | +## User Impact |
| 15 | + |
| 16 | +The main goal of this RFC is to enable Kani users to link against all supported constructs from the `std` library. |
| 17 | +Currently, Kani will only link to items that are either generic or have an inline annotation. |
| 18 | + |
| 19 | +The approach introduced in this RFC will have the following secondary benefits. |
| 20 | +- Reduce spurious warnings about unsupported features for cases where the feature is not reachable from any harness. |
| 21 | +- In the verification mode, we will likely see a reduction on the compilation time and memory consumption |
| 22 | + by pruning the inputs of symtab2gb and goto-instrument. |
| 23 | + - Compared to linking against the standard library goto-models that take more than 5 GB. |
| 24 | +- In a potential assessment mode, only analyze code that is reachable from all public items in the target crate. |
| 25 | + |
| 26 | +One downside is that if we decide to include a pre-compiled version of the std, our release bundle will double in size |
| 27 | +(See [Rational and Alternatives](0001-mir-linker.md#rational-and-alternatives) |
| 28 | +for more information on the size overhead). |
| 29 | +This will negatively impact the time taken to set up Kani |
| 30 | +(triggered by either the first time a user invokes `kani | cargo-kani` , or explicit invoke the subcommand `setup`). |
| 31 | + |
| 32 | +## User Experience |
| 33 | + |
| 34 | +Once this RFC has been stabilized users shall use Kani in the same manner as they have been today. |
| 35 | +Until then, we wil add an unstable option `--mir-linker` to enable the cross-crate reachability analysis |
| 36 | +and the generation of the goto-program only when compiling the target crate. |
| 37 | + |
| 38 | +Kani setup will likely take longer and more disk space as mentioned in the section above. |
| 39 | +This change will not be guarded by `--mir-linker` option above. |
| 40 | + |
| 41 | +## Detailed Design |
| 42 | + |
| 43 | +In a nutshell, we will no longer generate a goto-program for every crate we compile. |
| 44 | +Instead, we will generate the MIR for every crate, and we will generate only one goto-program. |
| 45 | +This model will only include items reachable from the target crate's harnesses. |
| 46 | + |
| 47 | +The current system flow for a crate verification is the following (Kani here represents either `kani | cargo-kani` |
| 48 | +executable): |
| 49 | + |
| 50 | +1. Kani compiles the user crate as well as all its dependencies. |
| 51 | + For every crate compiled, `kani-compiler` will generate a goto-program. |
| 52 | + This model includes everything reachable from the crate's public functions. |
| 53 | +2. After that, Kani links all models together by invoking `goto-cc`. |
| 54 | + This step will also link against Kani's `C` library. |
| 55 | +3. For every harness, Kani invokes `goto-instrument` to prune the linked model to only include items reachable from the given harness. |
| 56 | +4. Finally, Kani instruments and verify each harness model via `goto-instrument` and `cbmc` calls. |
| 57 | + |
| 58 | +After this RFC, the system flow would be slightly different: |
| 59 | + |
| 60 | +1. Kani compiles the user crate dependencies up to the MIR translation. |
| 61 | + I.e., for every crate compiled, `kani-compiler` will generate an artifact that includes the MIR representation |
| 62 | + of all items in the crate. |
| 63 | +2. Kani will generate the goto-program only while compiling the target user crate. |
| 64 | + It will generate one goto-program that includes all items reachable from any harness in the target crate. |
| 65 | +3. `goto-cc` will still be invoked to link the generated model against Kani's `C` library. |
| 66 | +4. Steps #3 and #4 above will be performed without any change. |
| 67 | + |
| 68 | +This feature will require three main changes to Kani which are detailed in the sub-sections below. |
| 69 | + |
| 70 | +### Kani's Sysroot |
| 71 | + |
| 72 | +Kani currently uses `rustup` sysroot to gather information from the standard library constructs. |
| 73 | +The artifacts from this `sysroot` include the MIR for generic items as well as for items that may be included in |
| 74 | +a crate compilation (e.g.: functions marked with `#[inline]` annotation). |
| 75 | +The artifacts do not include the MIR for items that have already been compiled to the `std` shared library. |
| 76 | +This leaves a gap that cannot be filled by the `kani-compiler`; |
| 77 | +thus, we are unable to translate these items into goto-program. |
| 78 | + |
| 79 | +In order to fulfill this gap, we must compile the standard library from scratch. |
| 80 | +This RFC proposes a similar method to what [`MIRI`](https://github.com/rust-lang/miri) implements. |
| 81 | +We will generate our own sysroot using the `-Z always-encode-mir` compilation flag. |
| 82 | +This sysroot will be pre-compiled and included in our release bundle. |
| 83 | + |
| 84 | +We will compile `kani`'s libraries (`kani` and `std`) also with `-Z always-encode-mir` |
| 85 | +and with the new sysroot. |
| 86 | + |
| 87 | + |
| 88 | +### Cross-Crate Reachability Analysis |
| 89 | + |
| 90 | +`kani-compiler` will include a new `reachability` module to traverse over the local and external MIR items. |
| 91 | +This module will `monomorphize` all generic code as it's performing the traversal. |
| 92 | + |
| 93 | +The traversal logic will be customizable allowing different starting points to be used. |
| 94 | +The two options to be included in this RFC is starting from all local harnesses |
| 95 | +(tagged with `#[kani::proof]`) and all public functions in the local crate. |
| 96 | + |
| 97 | +The `kani-compiler` behavior will be customizable via a new flag: |
| 98 | + |
| 99 | + ``` |
| 100 | + --reachability=[ harnesses | pub_fns | none | legacy ] |
| 101 | + ``` |
| 102 | + |
| 103 | +where: |
| 104 | + |
| 105 | + - `harnesses`: Use the local harnesses as the starting points for the reachability analysis. |
| 106 | + - `pub_fns`: Use the public local functions as the starting points for the reachability analysis. |
| 107 | + - `none`: This will be the default value if `--reachability` flag is not provided. It will skip |
| 108 | + reachability analysis. No goto-program will be generated. |
| 109 | + This will be used to compile dependencies up to the MIR level. |
| 110 | + `kani-compiler` will still generate artifacts with the crate's MIR. |
| 111 | + - `legacy`: Keep `kani-compiler` current behavior by using |
| 112 | + `rustc_monomorphizer::collect_and_partition_mono_items()` which respects the crate boundary. |
| 113 | + This will generate a goto-program for each crate compiled by `kani-compiler`, and it will still have the same |
| 114 | + `std` linking issues. |
| 115 | + *This option will be removed as part of the `rfc` stabilization.* |
| 116 | + |
| 117 | +These flags will not be exposed to the final user. |
| 118 | +They will only be used for the communication between `kani-driver` and `kani-compiler`. |
| 119 | + |
| 120 | +### Dependencies vs Target Crate Compilation |
| 121 | + |
| 122 | +The flags described in the section above will be used by `kani-driver` to implement the new system flow. |
| 123 | +For that, we propose the following mechanism: |
| 124 | + |
| 125 | +- For standalone `kani`, we will pass the option `--reachability=harnesses` to `kani-compiler`. |
| 126 | +- For `cargo-kani`, we will replace |
| 127 | + ``` |
| 128 | + cargo build <FLAGS> |
| 129 | + ``` |
| 130 | + |
| 131 | + with: |
| 132 | + |
| 133 | + ``` |
| 134 | + cargo rustc <FLAGS> -- --reachability=harnesses |
| 135 | + ``` |
| 136 | + |
| 137 | + to build everything. |
| 138 | + This command will compile all dependencies without the `--reachability` argument, and it will only pass `harnesses` |
| 139 | + value to the compiler when compiling the target crate. |
| 140 | + |
| 141 | +## Rational and Alternatives |
| 142 | + |
| 143 | +Not doing anything is not an alternative, since this fixes a major gap in Kani's usability. |
| 144 | + |
| 145 | +### Benefits |
| 146 | + |
| 147 | +- The MIR linker will allow us to fix the linking issues with Rust's standard library. |
| 148 | +- Once stabilized, the MIR linker will be transparent to the user. |
| 149 | +- It will enable more powerful and precise static analysis to `kani-compiler`. |
| 150 | +- It won't require any changes to our dependencies. |
| 151 | +- This will fix the harnesses' dependency on the`#[no_mangle]` annotation |
| 152 | +([Issue-661](https://github.com/model-checking/kani/issues/661)). |
| 153 | + |
| 154 | +### Risks |
| 155 | + |
| 156 | +Failures in the linking stage would not impact the tool soundness. I anticipate the following failure scenarios: |
| 157 | +- ICE (Internal compiler error): Some logic is incorrectly implemented and the linking stage crashes. |
| 158 | + Although this is a bad experience for the user, this will not impact the verification result. |
| 159 | +- Missing items: This would either result in ICE during code generation or a verification failure if the missing |
| 160 | + item is reachable. |
| 161 | +- Extra items: This shouldn't impact the verification results, and they should be pruned by CBMC's reachability |
| 162 | + analysis. |
| 163 | + This is already the case today. In extreme cases, this could include a symbol that we cannot compile and cause an ICE. |
| 164 | + |
| 165 | +The new reachability code would be highly dependent on the `rustc` unstable APIs, which could increase |
| 166 | +the cost of the upstream synchronization. |
| 167 | +That said, the APIs that would be required are already used today. |
| 168 | + |
| 169 | +Finally, this implementation relies on a few unstable options from `cargo` and `rustc`. |
| 170 | +These APIs are used by other tools such as MIRI, so we don't see a high risk that they would be removed. |
| 171 | + |
| 172 | +### Alternatives |
| 173 | + |
| 174 | +The other options explored were: |
| 175 | +1. Pre-compile the standard library, and the kani library, and ship the generated `*symtab.json` files. |
| 176 | +2. Pre-compile the standard library, and the kani library, convert the standard library and dependencies to goto-program |
| 177 | + (via`symtab2gb`) and link them into one single goto-program. |
| 178 | + Ship the generated model. |
| 179 | + |
| 180 | +Both would still require shipping the compiler metadata (via `rlib` or `rmeta`) for the kani library, its |
| 181 | +dependencies, and `kani_macro.so`. |
| 182 | + |
| 183 | +Both alternatives are very similar. They only differ on the artifact that would be shipped. |
| 184 | +They require generating and shipping a custom `sysroot`; |
| 185 | +however, there is no need to implement the reachability algorithm. |
| 186 | + |
| 187 | +We implemented a prototype for the MIR linker and one for the alternatives. |
| 188 | +Both prototypes generate the sysroot as part of the `cargo kani` flow. |
| 189 | + |
| 190 | +We performed a small experiment (on a `c5.4xlarge` ec2 instance running Ubuntu 20.04) to assess the options. |
| 191 | + |
| 192 | +For this experiment, we used the following harness: |
| 193 | +```rust |
| 194 | +#[kani::proof] |
| 195 | +#[kani::unwind(4)] |
| 196 | +pub fn check_format() { |
| 197 | + assert!("2".parse::<u32>().unwrap() == 2); |
| 198 | +} |
| 199 | +``` |
| 200 | +The experiment showed that the MIR linker approach is much more efficient. |
| 201 | + |
| 202 | +See the table bellow for the breakdown of time (in seconds) taken for each major step of |
| 203 | +the harness verification: |
| 204 | + |
| 205 | + |
| 206 | +| Stage | MIR Linker | Alternative | |
| 207 | +----------------------------|------------|-------------| |
| 208 | +| compilation | 22.2s | 64.7s | |
| 209 | +| goto-program generation | 2.4s | 90.7s | |
| 210 | +| goto-program linking | 0.8s | 33.2s | |
| 211 | +| code instrumentation | 0.8s | 33.1 | |
| 212 | +| verification | 0.5s | 8.5s | |
| 213 | + |
| 214 | +It is possible that `goto-cc` time can be improved, but this would also require further experimentation and |
| 215 | +expertise that we don't have today. |
| 216 | + |
| 217 | +Every option would require a custom sysroot to either be built or shipped with Kani. |
| 218 | +The table below shows the size of the sysroot files for the alternative #2 |
| 219 | +(goto-program files) vs compiler artifacts (`*.rmeta` files) |
| 220 | +files with `-Z always-encode-mir` for `x86_64-unknown-linux-gnu` (on Ubuntu 18.04). |
| 221 | + |
| 222 | +| File Type | Raw size | Compressed size | |
| 223 | +|----------------|----------|-----------------| |
| 224 | +| `symtab.json` | 950M | 26M | |
| 225 | +| `symtab.out` | 84M | 24M | |
| 226 | +| `*.rmeta` | 92M | 25M | |
| 227 | + |
| 228 | +These results were obtained by looking at the artifacts generated during the same experiment. |
| 229 | + |
| 230 | +## Open questions |
| 231 | + |
| 232 | +- Should we build or download the sysroot during `kani setup`? |
| 233 | +- What's the best way to enable support to run Kani in the entire `workspace`? |
| 234 | + - One possibility is to run `cargo rustc` per package. |
| 235 | +- Should we codegen all static items no matter what? Static objects can only be initialized via constant function. |
| 236 | + Thus, it shouldn't have any side effect. |
| 237 | + That relies on all constant initializers being evaluated during compilation. |
| 238 | +- What's the best way to handle `cargo kani --tests`? |
| 239 | + We still want to restrict the reachability and codegen to the last compilation step. |
| 240 | + Possible solutions that we considered so far: |
| 241 | + 1. Add a new value to `--reachability`, maybe "on-tests" or something like that. |
| 242 | + This would behave as either "harness" or "none" depending whether RUSTFLAGS has `--tests`. |
| 243 | + 2. Change how we handle not having `--reachability` argument to also take into account the existence of `--tests`. |
| 244 | + |
| 245 | + |
| 246 | +## Future possibilities |
| 247 | + |
| 248 | +- Split the goto-program into two or more items to optimize compilation result caching. |
| 249 | + - Dependencies: One model will include items from all the crate dependencies. |
| 250 | + This model will likely be more stable and require fewer updates. |
| 251 | + - Target crate: The model for all items in the target crate. |
| 252 | +- Do the analysis per-harness. This might be adequate once we have a mechanism to cache translations. |
| 253 | +- Add an option to include external functions to the analysis starting point in order to enable verification when |
| 254 | +calls are made from `C` to `rust`. |
| 255 | +- Contribute the reachability analysis code back to upstream. |
0 commit comments