Skip to content

Commit f024d23

Browse files
authored
Delay writing metadata file (no UX impact) (rust-lang#2628)
Kani compiler will now only store KaniMetadata after compiling all harnesses. Before, we were storing before codegen in the first iteration of the compiler. This will still allow us to generate metadata without actually performing codegen, if we ever implement a `kani list` subcommand. The metadata won't be stored though if Kani fails to codegen. However, we don't do anything extra with that file if the compilation fails. This change is required for rust-lang#2493 and contracts work. This will allow us to store information collected during code generation.
1 parent db42ee9 commit f024d23

File tree

2 files changed

+174
-47
lines changed

2 files changed

+174
-47
lines changed

kani-compiler/src/kani_compiler.rs

Lines changed: 171 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use std::collections::{BTreeMap, HashMap};
3939
use std::fs::File;
4040
use std::io::BufWriter;
4141
use std::mem;
42+
use std::path::{Path, PathBuf};
4243
use std::process::ExitCode;
4344
use std::sync::{Arc, Mutex};
4445
use tracing::debug;
@@ -71,12 +72,21 @@ type HarnessId = DefPathHash;
7172
/// A set of stubs.
7273
type Stubs = BTreeMap<DefPathHash, DefPathHash>;
7374

74-
#[derive(Debug)]
75+
#[derive(Clone, Debug)]
7576
struct HarnessInfo {
7677
pub metadata: HarnessMetadata,
7778
pub stub_map: Stubs,
7879
}
7980

81+
/// Store some relevant information about the crate compilation.
82+
#[derive(Clone, Debug)]
83+
struct CrateInfo {
84+
/// The name of the crate being compiled.
85+
pub name: String,
86+
/// The metadata output path that shall be generated as part of the crate compilation.
87+
pub output_path: PathBuf,
88+
}
89+
8090
/// Represents the current compilation stage.
8191
///
8292
/// The Kani compiler may run the Rust compiler multiple times since stubbing has to be applied
@@ -85,20 +95,28 @@ struct HarnessInfo {
8595
/// - We always start in the [CompilationStage::Init].
8696
/// - After [CompilationStage::Init] we transition to either
8797
/// - [CompilationStage::CodegenNoStubs] on a regular crate compilation, this will follow Init.
88-
/// - [CompilationStage::Done], running the compiler to gather information, such as `--version`
89-
/// will skip code generation completely, and there is no work to be done.
98+
/// - [CompilationStage::CompilationSkipped], running the compiler to gather information, such as
99+
/// `--version` will skip code generation completely, and there is no work to be done.
90100
/// - After the [CompilationStage::CodegenNoStubs], we transition to either
91101
/// - [CompilationStage::CodegenWithStubs] when there is at least one harness with stubs.
92102
/// - [CompilationStage::Done] where there is no harness left to process.
93103
/// - The [CompilationStage::CodegenWithStubs] can last multiple Rust compiler runs. Once there is
94104
/// no harness left, we move to [CompilationStage::Done].
105+
/// - The final stages are either [CompilationStage::Done] or [CompilationStage::CompilationSkipped].
106+
/// - [CompilationStage::Done] represents the final state of the compiler after a successful
107+
/// compilation. The crate metadata is stored here (even if no codegen was actually performed).
108+
/// - [CompilationStage::CompilationSkipped] no compilation was actually performed.
109+
/// No work needs to be done.
110+
/// - Note: In a scenario where the compilation fails, the compiler will exit immediately,
111+
/// independent on the stage. Any artifact produced shouldn't be used.
95112
/// I.e.:
96113
/// ```dot
97114
/// graph CompilationStage {
98-
/// Init -> {CodegenNoStubs, Done}
115+
/// Init -> {CodegenNoStubs, CompilationSkipped}
99116
/// CodegenNoStubs -> {CodegenStubs, Done}
100117
/// // Loop up to N harnesses times.
101118
/// CodegenStubs -> {CodegenStubs, Done}
119+
/// CompilationSkipped
102120
/// Done
103121
/// }
104122
/// ```
@@ -108,11 +126,14 @@ enum CompilationStage {
108126
/// Initial state that the compiler is always instantiated with.
109127
/// In this stage, we initialize the Query and collect all harnesses.
110128
Init,
129+
/// State where the compiler ran but didn't actually compile anything (e.g.: --version).
130+
CompilationSkipped,
111131
/// Stage where the compiler will perform codegen of all harnesses that don't use stub.
112132
CodegenNoStubs {
113133
target_harnesses: Vec<HarnessId>,
114134
next_harnesses: Vec<Vec<HarnessId>>,
115135
all_harnesses: HashMap<HarnessId, HarnessInfo>,
136+
crate_info: CrateInfo,
116137
},
117138
/// Stage where the compiler will codegen harnesses that use stub, one group at a time.
118139
/// The harnesses at this stage are grouped according to the stubs they are using. For now,
@@ -121,18 +142,17 @@ enum CompilationStage {
121142
target_harnesses: Vec<HarnessId>,
122143
next_harnesses: Vec<Vec<HarnessId>>,
123144
all_harnesses: HashMap<HarnessId, HarnessInfo>,
145+
crate_info: CrateInfo,
146+
},
147+
Done {
148+
metadata: Option<(KaniMetadata, CrateInfo)>,
124149
},
125-
Done,
126150
}
127151

128152
impl CompilationStage {
129153
pub fn is_init(&self) -> bool {
130154
matches!(self, CompilationStage::Init)
131155
}
132-
133-
pub fn is_done(&self) -> bool {
134-
matches!(self, CompilationStage::Done)
135-
}
136156
}
137157

138158
/// This object controls the compiler behavior.
@@ -160,7 +180,7 @@ impl KaniCompiler {
160180
/// Since harnesses may have different attributes that affect compilation, Kani compiler can
161181
/// actually invoke the rust compiler multiple times.
162182
pub fn run(&mut self, orig_args: Vec<String>) -> Result<(), ErrorGuaranteed> {
163-
while !self.stage.is_done() {
183+
loop {
164184
debug!(next=?self.stage, "run");
165185
match &self.stage {
166186
CompilationStage::Init => {
@@ -178,37 +198,64 @@ impl KaniCompiler {
178198
args.push(extra_arg);
179199
self.run_compilation_session(&args)?;
180200
}
181-
CompilationStage::Done => {
182-
unreachable!("There's nothing to be done here.")
201+
CompilationStage::Done { metadata: Some((kani_metadata, crate_info)) } => {
202+
// Only store metadata for harnesses for now.
203+
// TODO: This should only skip None.
204+
// https://github.com/model-checking/kani/issues/2493
205+
if self.queries.lock().unwrap().reachability_analysis
206+
== ReachabilityType::Harnesses
207+
{
208+
// Store metadata file.
209+
// We delay storing the metadata so we can include information collected
210+
// during codegen.
211+
self.store_metadata(&kani_metadata, &crate_info.output_path);
212+
}
213+
return Ok(());
214+
}
215+
CompilationStage::Done { metadata: None }
216+
| CompilationStage::CompilationSkipped => {
217+
return Ok(());
183218
}
184219
};
185220

186221
self.next_stage();
187222
}
188-
Ok(())
189223
}
190224

191225
/// Set up the next compilation stage after a `rustc` run.
192226
fn next_stage(&mut self) {
193227
self.stage = match &mut self.stage {
194228
CompilationStage::Init => {
195229
// This may occur when user passes arguments like --version or --help.
196-
CompilationStage::Done
230+
CompilationStage::Done { metadata: None }
197231
}
198-
CompilationStage::CodegenNoStubs { next_harnesses, all_harnesses, .. }
199-
| CompilationStage::CodegenWithStubs { next_harnesses, all_harnesses, .. } => {
232+
CompilationStage::CodegenNoStubs {
233+
next_harnesses, all_harnesses, crate_info, ..
234+
}
235+
| CompilationStage::CodegenWithStubs {
236+
next_harnesses,
237+
all_harnesses,
238+
crate_info,
239+
..
240+
} => {
200241
if let Some(target_harnesses) = next_harnesses.pop() {
201242
assert!(!target_harnesses.is_empty(), "expected at least one target harness");
202243
CompilationStage::CodegenWithStubs {
203244
target_harnesses,
204245
next_harnesses: mem::take(next_harnesses),
205246
all_harnesses: mem::take(all_harnesses),
247+
crate_info: crate_info.clone(),
206248
}
207249
} else {
208-
CompilationStage::Done
250+
CompilationStage::Done {
251+
metadata: Some((
252+
generate_metadata(&crate_info, all_harnesses),
253+
crate_info.clone(),
254+
)),
255+
}
209256
}
210257
}
211-
CompilationStage::Done => {
258+
CompilationStage::Done { .. } | CompilationStage::CompilationSkipped => {
212259
unreachable!()
213260
}
214261
};
@@ -225,6 +272,10 @@ impl KaniCompiler {
225272

226273
/// Gather and process all harnesses from this crate that shall be compiled.
227274
fn process_harnesses(&self, tcx: TyCtxt) -> CompilationStage {
275+
let crate_info = CrateInfo {
276+
name: tcx.crate_name(LOCAL_CRATE).as_str().into(),
277+
output_path: metadata_output_path(tcx),
278+
};
228279
if self.queries.lock().unwrap().reachability_analysis == ReachabilityType::Harnesses {
229280
let base_filename = tcx.output_filenames(()).output_path(OutputType::Object);
230281
let harnesses = filter_crate_items(tcx, |_, def_id| is_proof_harness(tcx, def_id));
@@ -250,21 +301,21 @@ impl KaniCompiler {
250301
// Generate code without stubs.
251302
(all_harnesses.keys().cloned().collect(), vec![])
252303
};
253-
// Store metadata file.
254-
self.store_metadata(tcx, &all_harnesses);
255304

256-
// Even if no_stubs is empty we still need to store metadata.
305+
// Even if no_stubs is empty we still need to store rustc metadata.
257306
CompilationStage::CodegenNoStubs {
258307
target_harnesses: no_stubs,
259308
next_harnesses: group_by_stubs(with_stubs, &all_harnesses),
260309
all_harnesses,
310+
crate_info,
261311
}
262312
} else {
263313
// Leave other reachability type handling as is for now.
264314
CompilationStage::CodegenNoStubs {
265315
target_harnesses: vec![],
266316
next_harnesses: vec![],
267317
all_harnesses: HashMap::default(),
318+
crate_info,
268319
}
269320
}
270321
}
@@ -291,25 +342,14 @@ impl KaniCompiler {
291342
.collect();
292343
Compilation::Continue
293344
}
294-
CompilationStage::Init | CompilationStage::Done => unreachable!(),
345+
CompilationStage::Init
346+
| CompilationStage::Done { .. }
347+
| CompilationStage::CompilationSkipped => unreachable!(),
295348
}
296349
}
297350

298351
/// Write the metadata to a file
299-
fn store_metadata(&self, tcx: TyCtxt, all_harnesses: &HashMap<HarnessId, HarnessInfo>) {
300-
let (proof_harnesses, test_harnesses) = all_harnesses
301-
.values()
302-
.map(|info| &info.metadata)
303-
.cloned()
304-
.partition(|md| md.attributes.proof);
305-
let metadata = KaniMetadata {
306-
crate_name: tcx.crate_name(LOCAL_CRATE).as_str().into(),
307-
proof_harnesses,
308-
unsupported_features: vec![],
309-
test_harnesses,
310-
};
311-
let mut filename = tcx.output_filenames(()).output_path(OutputType::Object);
312-
filename.set_extension(ArtifactType::Metadata);
352+
fn store_metadata(&self, metadata: &KaniMetadata, filename: &Path) {
313353
debug!(?filename, "write_metadata");
314354
let out_file = File::create(&filename).unwrap();
315355
let writer = BufWriter::new(out_file);
@@ -388,10 +428,34 @@ impl Callbacks for KaniCompiler {
388428
}
389429
}
390430

431+
/// Generate [KaniMetadata] for the target crate.
432+
fn generate_metadata(
433+
crate_info: &CrateInfo,
434+
all_harnesses: &HashMap<HarnessId, HarnessInfo>,
435+
) -> KaniMetadata {
436+
let (proof_harnesses, test_harnesses) = all_harnesses
437+
.values()
438+
.map(|info| &info.metadata)
439+
.cloned()
440+
.partition(|md| md.attributes.proof);
441+
KaniMetadata {
442+
crate_name: crate_info.name.clone(),
443+
proof_harnesses,
444+
unsupported_features: vec![],
445+
test_harnesses,
446+
}
447+
}
448+
449+
/// Extract the filename for the metadata file.
450+
fn metadata_output_path(tcx: TyCtxt) -> PathBuf {
451+
let mut filename = tcx.output_filenames(()).output_path(OutputType::Object);
452+
filename.set_extension(ArtifactType::Metadata);
453+
filename
454+
}
455+
391456
#[cfg(test)]
392457
mod tests {
393-
use super::{HarnessInfo, Stubs};
394-
use crate::kani_compiler::{group_by_stubs, HarnessId};
458+
use super::*;
395459
use kani_metadata::{HarnessAttributes, HarnessMetadata};
396460
use rustc_data_structures::fingerprint::Fingerprint;
397461
use rustc_hir::definitions::DefPathHash;
@@ -404,12 +468,12 @@ mod tests {
404468
DefPathHash(Fingerprint::new(id, 0))
405469
}
406470

407-
fn mock_metadata() -> HarnessMetadata {
471+
fn mock_metadata(name: String, krate: String) -> HarnessMetadata {
408472
HarnessMetadata {
409-
pretty_name: String::from("dummy"),
410-
mangled_name: String::from("dummy"),
411-
crate_name: String::from("dummy"),
412-
original_file: String::from("dummy"),
473+
pretty_name: name.clone(),
474+
mangled_name: name.clone(),
475+
original_file: format!("{}.rs", krate),
476+
crate_name: krate,
413477
original_start_line: 10,
414478
original_end_line: 20,
415479
goto_file: None,
@@ -418,7 +482,7 @@ mod tests {
418482
}
419483

420484
fn mock_info_with_stubs(stub_map: Stubs) -> HarnessInfo {
421-
HarnessInfo { metadata: mock_metadata(), stub_map }
485+
HarnessInfo { metadata: mock_metadata("dummy".to_string(), "crate".to_string()), stub_map }
422486
}
423487

424488
#[test]
@@ -458,4 +522,67 @@ mod tests {
458522
);
459523
assert!(grouped.contains(&vec![harness_2]));
460524
}
525+
526+
#[test]
527+
fn test_generate_metadata() {
528+
// Mock inputs.
529+
let name = "my_crate".to_string();
530+
let crate_info = CrateInfo { name: name.clone(), output_path: PathBuf::default() };
531+
532+
let mut info = mock_info_with_stubs(Stubs::default());
533+
info.metadata.attributes.proof = true;
534+
let id = mock_next_id();
535+
let all_harnesses = HashMap::from([(id, info.clone())]);
536+
537+
// Call generate metadata.
538+
let metadata = generate_metadata(&crate_info, &all_harnesses);
539+
540+
// Check output.
541+
assert_eq!(metadata.crate_name, name);
542+
assert_eq!(metadata.proof_harnesses.len(), 1);
543+
assert_eq!(*metadata.proof_harnesses.first().unwrap(), info.metadata);
544+
}
545+
546+
#[test]
547+
fn test_generate_empty_metadata() {
548+
// Mock inputs.
549+
let name = "my_crate".to_string();
550+
let crate_info = CrateInfo { name: name.clone(), output_path: PathBuf::default() };
551+
let all_harnesses = HashMap::new();
552+
553+
// Call generate metadata.
554+
let metadata = generate_metadata(&crate_info, &all_harnesses);
555+
556+
// Check output.
557+
assert_eq!(metadata.crate_name, name);
558+
assert_eq!(metadata.proof_harnesses.len(), 0);
559+
}
560+
561+
#[test]
562+
fn test_generate_metadata_with_multiple_harness() {
563+
// Mock inputs.
564+
let krate = "my_crate".to_string();
565+
let crate_info = CrateInfo { name: krate.clone(), output_path: PathBuf::default() };
566+
567+
let harnesses = ["h1", "h2", "h3"];
568+
let infos = harnesses.map(|harness| {
569+
let mut metadata = mock_metadata(harness.to_string(), krate.clone());
570+
metadata.attributes.proof = true;
571+
(mock_next_id(), HarnessInfo { stub_map: Stubs::default(), metadata })
572+
});
573+
let all_harnesses = HashMap::from(infos.clone());
574+
575+
// Call generate metadata.
576+
let metadata = generate_metadata(&crate_info, &all_harnesses);
577+
578+
// Check output.
579+
assert_eq!(metadata.crate_name, krate);
580+
assert_eq!(metadata.proof_harnesses.len(), infos.len());
581+
assert!(
582+
metadata
583+
.proof_harnesses
584+
.iter()
585+
.all(|harness| harnesses.contains(&&*harness.pretty_name))
586+
);
587+
}
461588
}

0 commit comments

Comments
 (0)