Skip to content

Commit c9e396e

Browse files
Split compiler flags to avoid dependency recompilation (#4211)
We currently pass all Kani options to `rustc` for use by the `kani-compiler`. This means that the Cargo build cache will detect a [compilation fingerprint](https://doc.rust-lang.org/nightly/nightly-rustc/cargo/core/compiler/fingerprint/index.html) change whenever these options change and have to fully recompile the project (including all dependencies). However, for the majority of options, this is wholly unnecessary, as they only affect our codegen of the target crate once it’s already been lowered to MIR. This PR now passes most of `kani-compiler`’s options only to the target crate so that they don’t change how dependencies are built and we can avoid their recompilation. There are two exceptions: - `--ignore-global-assembly`: this is needed even in dependencies because [the check we do](https://github.com/model-checking/kani/blob/2bdc9ba359ce26f80e6eb9b023b39266f726de8f/kani-compiler/src/kani_middle/mod.rs#L47-L60) to detect global assembly happens even if we are skipping codegen. - `--check-version`: this ensures we recompile everything when the user upgrades Kani versions. While not strictly necessary, existing comments understandably seem to want this behavior, and upgrades are rare enough I don’t see it being a real issue. Resolves #2230 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 533403e commit c9e396e

File tree

4 files changed

+56
-20
lines changed

4 files changed

+56
-20
lines changed

kani-driver/src/call_cargo.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,11 @@ crate-type = ["lib"]
7777
pub fn cargo_build_std(&self, std_path: &Path, krate_path: &Path) -> Result<Vec<Artifact>> {
7878
let lib_path = lib_no_core_folder().unwrap();
7979
let mut rustc_args = self.kani_rustc_flags(LibConfig::new_no_core(lib_path));
80-
rustc_args.push(to_rustc_arg(self.kani_compiler_flags()).into());
81-
rustc_args.push(self.reachability_arg().into());
80+
rustc_args.push(to_rustc_arg(self.kani_compiler_local_flags()).into());
8281
// Ignore global assembly, since `compiler_builtins` has some.
83-
rustc_args.push(to_rustc_arg(vec!["--ignore-global-asm".to_string()]).into());
82+
rustc_args.push(
83+
to_rustc_arg(vec!["--ignore-global-asm".to_string(), self.reachability_arg()]).into(),
84+
);
8485

8586
let mut cargo_args: Vec<OsString> = vec!["build".into()];
8687
cargo_args.append(&mut cargo_config_args());
@@ -145,7 +146,7 @@ crate-type = ["lib"]
145146

146147
let lib_path = lib_folder().unwrap();
147148
let mut rustc_args = self.kani_rustc_flags(LibConfig::new(lib_path));
148-
rustc_args.push(to_rustc_arg(self.kani_compiler_flags()).into());
149+
rustc_args.push(to_rustc_arg(self.kani_compiler_dependency_flags()).into());
149150

150151
let mut cargo_args: Vec<OsString> = vec!["rustc".into()];
151152
if let Some(path) = &self.args.cargo.manifest_path {
@@ -192,10 +193,16 @@ crate-type = ["lib"]
192193
// This is the desired behavior because we only want to construct `CodegenUnits` for the target package;
193194
// i.e., if some dependency has harnesses, we don't want to run them.
194195

195-
// If you are adding a new `kani-compiler` argument, you likely want to put it `kani_compiler_flags()` instead,
196-
// unless there a reason it shouldn't be passed to dependencies.
197-
// (Note that at the time of writing, passing the other compiler args to dependencies is a no-op, since `--reachability=None` skips codegen anyway.)
198-
let pkg_args = vec!["--".into(), self.reachability_arg()];
196+
// If you are adding a new `kani-compiler` argument, you likely want to put it here, unless there is a specific
197+
// reason it would be used in dependencies that are skipping reachability and codegen.
198+
// Note that passing compiler args to dependencies is a currently no-op, since `--reachability=None` skips codegen
199+
// anyway. However, this will cause unneeded recompilation of dependencies should those args change, and thus
200+
// should be avoided if possible.
201+
let mut kani_pkg_args = vec![self.reachability_arg()];
202+
kani_pkg_args.extend(self.kani_compiler_local_flags());
203+
204+
// Convert package args to rustc args for passing
205+
let pkg_args = vec!["--".into(), to_rustc_arg(kani_pkg_args)];
199206

200207
let mut found_target = false;
201208
let packages = self.packages_to_verify(&self.args, &metadata)?;

kani-driver/src/call_single_file.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl KaniSession {
5151
crate_name: &String,
5252
outdir: &Path,
5353
) -> Result<()> {
54-
let mut kani_args = self.kani_compiler_flags();
54+
let mut kani_args = self.kani_compiler_local_flags();
5555
kani_args.push(format!("--reachability={}", self.reachability_mode()));
5656

5757
let lib_path = lib_folder().unwrap();
@@ -81,6 +81,7 @@ impl KaniSession {
8181
let mut cmd = Command::new(&self.kani_compiler);
8282
let kani_compiler_args = to_rustc_arg(kani_args);
8383
cmd.arg(kani_compiler_args).args(rustc_args);
84+
8485
// This is only required for stable but is a no-op for nightly channels
8586
cmd.env("RUSTC_BOOTSTRAP", "1");
8687

@@ -94,13 +95,26 @@ impl KaniSession {
9495

9596
/// Create a compiler option that represents the reachability mode.
9697
pub fn reachability_arg(&self) -> String {
97-
to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode())])
98+
format!("--reachability={}", self.reachability_mode())
9899
}
99100

100-
/// These arguments are arguments passed to kani-compiler that are `kani` compiler specific.
101-
pub fn kani_compiler_flags(&self) -> Vec<String> {
101+
/// The `kani-compiler`-specific arguments that should be passed when building all crates,
102+
/// including dependencies.
103+
pub fn kani_compiler_dependency_flags(&self) -> Vec<String> {
102104
let mut flags = vec![check_version()];
103105

106+
if self.args.ignore_global_asm {
107+
flags.push("--ignore-global-asm".into());
108+
}
109+
110+
flags
111+
}
112+
113+
/// The `kani-compiler`-specific arguments that should be passed only to the local crate
114+
/// being compiled.
115+
pub fn kani_compiler_local_flags(&self) -> Vec<String> {
116+
let mut flags = vec![];
117+
104118
if self.args.common_args.debug {
105119
flags.push("--log-level=debug".into());
106120
} else if self.args.common_args.verbose {
@@ -116,9 +130,6 @@ impl KaniSession {
116130
if self.args.assertion_reach_checks() {
117131
flags.push("--assertion-reach-checks".into());
118132
}
119-
if self.args.ignore_global_asm {
120-
flags.push("--ignore-global-asm".into());
121-
}
122133

123134
if self.args.is_stubbing_enabled() {
124135
flags.push("--enable-stubbing".into());

tests/script-based-pre/build-cache-dirty/rebuild.expected

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
Initial compilation
2-
omplete - 2 successfully verified harnesses, 0 failures, 2 total.
2+
Complete - 2 successfully verified harnesses, 0 failures, 2 total.
33
target/initial.log:Compiled 2 crates
44
target/initial.log:Checking harness check_u8_i16...
55
target/initial.log:Checking harness check_u8_u32...
66
target/initial.log:Complete - 2 successfully verified harnesses, 0 failures, 2 total.
7-
Run with a new argument that affects compilation
8-
target/enable_checks.log:Compiled 2 crates
7+
Run with a new argument that doesn't affect dependency compilation
8+
target/enable_checks.log:Compiled 1 crates
99
target/enable_checks.log:Checking harness check_u8_i16...
1010
target/enable_checks.log:Checking harness check_u8_u32...
1111
target/enable_checks.log:Complete - 2 successfully verified harnesses, 0 failures, 2 total.
12+
Run with another new argument that doesn't affect dependency compilation
13+
target/no_assert_contracts.log:Compiled 1 crates
14+
target/no_assert_contracts.log:Checking harness check_u8_i16...
15+
target/no_assert_contracts.log:Checking harness check_u8_u32...
16+
target/no_assert_contracts.log:Complete - 2 successfully verified harnesses, 0 failures, 2 total.
17+
Run with a new argument that affects dependency compilation
18+
target/ignore_asm.log:Compiled 2 crates
19+
target/ignore_asm.log:Checking harness check_u8_i16...
20+
target/ignore_asm.log:Checking harness check_u8_u32...
21+
target/ignore_asm.log:Complete - 2 successfully verified harnesses, 0 failures, 2 total.
1222
Run after change to the source code
1323
target/changed_src.log:Compiled 1 crates
1424
target/changed_src.log:Checking harness noop_check...

tests/script-based-pre/build-cache-dirty/rebuild.sh

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function check_kani {
2424
2>&1 | tee "${log_file}"
2525
else
2626
cargo kani --manifest-path "${MANIFEST}" --target-dir "${OUT_DIR}" \
27-
"${args}" 2>&1 | tee "${log_file}"
27+
${args} 2>&1 | tee "${log_file}"
2828
fi
2929

3030
# Print information about the generated log file.
@@ -49,9 +49,17 @@ cp -r target_lib ${OUT_DIR}
4949
echo "Initial compilation"
5050
check_kani --no-assertion-reach-checks initial.log
5151

52-
echo "Run with a new argument that affects compilation"
52+
# Check that most codegen flags only recompile the target crate
53+
echo "Run with a new argument that doesn't affect dependency compilation"
5354
check_kani "" enable_checks.log
5455

56+
echo "Run with another new argument that doesn't affect dependency compilation"
57+
check_kani "-Z function-contracts --no-assert-contracts" no_assert_contracts.log
58+
59+
# But ensure that specific ones recompile dependencies too
60+
echo "Run with a new argument that affects dependency compilation"
61+
check_kani "-Z unstable-options --ignore-global-asm" ignore_asm.log
62+
5563
echo "Run after change to the source code"
5664
echo '
5765
#[kani::proof]

0 commit comments

Comments
 (0)