Skip to content

Commit 0a987ff

Browse files
authored
Add --mir-linker flag and wire it up to the compiler (rust-lang#1652)
Since the MIR linker hasn't been hooked up yet, the compiler will fail if this option is selected as shown by the new tests.
1 parent 0fa9ee3 commit 0a987ff

File tree

17 files changed

+228
-75
lines changed

17 files changed

+228
-75
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ dependencies = [
380380
name = "kani_queries"
381381
version = "0.10.0"
382382
dependencies = [
383+
"strum",
384+
"strum_macros",
383385
"tracing",
384386
]
385387

kani-compiler/kani_queries/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ unsound_experiments = []
1313

1414
[dependencies]
1515
tracing = {version = "0.1"}
16+
strum = {version = "0.24.0"}
17+
strum_macros = {version = "0.24.0"}
18+

kani-compiler/kani_queries/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use std::sync::atomic::{AtomicBool, Ordering};
5+
#[cfg(not(feature = "unsound_experiments"))]
6+
use std::sync::Mutex;
7+
use strum_macros::{AsRefStr, EnumString, EnumVariantNames};
58

69
#[cfg(feature = "unsound_experiments")]
710
mod unsound_experiments;
@@ -12,6 +15,25 @@ use {
1215
std::sync::{Arc, Mutex},
1316
};
1417

18+
#[derive(Debug, Clone, Copy, AsRefStr, EnumString, EnumVariantNames, PartialEq, Eq)]
19+
#[strum(serialize_all = "snake_case")]
20+
pub enum ReachabilityType {
21+
/// Start the cross-crate reachability analysis from all harnesses in the local crate.
22+
Harnesses,
23+
/// Use standard rustc monomorphizer algorithm.
24+
Legacy,
25+
/// Don't perform any reachability analysis. This will skip codegen for this crate.
26+
None,
27+
/// Start the cross-crate reachability analysis from all public functions in the local crate.
28+
PubFns,
29+
}
30+
31+
impl Default for ReachabilityType {
32+
fn default() -> Self {
33+
ReachabilityType::None
34+
}
35+
}
36+
1537
pub trait UserInput {
1638
fn set_symbol_table_passes(&mut self, passes: Vec<String>);
1739
fn get_symbol_table_passes(&self) -> Vec<String>;
@@ -28,6 +50,9 @@ pub trait UserInput {
2850
fn set_ignore_global_asm(&mut self, global_asm: bool);
2951
fn get_ignore_global_asm(&self) -> bool;
3052

53+
fn set_reachability_analysis(&mut self, reachability: ReachabilityType);
54+
fn get_reachability_analysis(&self) -> ReachabilityType;
55+
3156
#[cfg(feature = "unsound_experiments")]
3257
fn get_unsound_experiments(&self) -> Arc<Mutex<UnsoundExperiments>>;
3358
}
@@ -39,6 +64,7 @@ pub struct QueryDb {
3964
symbol_table_passes: Vec<String>,
4065
json_pretty_print: AtomicBool,
4166
ignore_global_asm: AtomicBool,
67+
reachability_analysis: Mutex<ReachabilityType>,
4268
#[cfg(feature = "unsound_experiments")]
4369
unsound_experiments: Arc<Mutex<UnsoundExperiments>>,
4470
}
@@ -84,6 +110,14 @@ impl UserInput for QueryDb {
84110
self.ignore_global_asm.load(Ordering::Relaxed)
85111
}
86112

113+
fn set_reachability_analysis(&mut self, reachability: ReachabilityType) {
114+
*self.reachability_analysis.get_mut().unwrap() = reachability;
115+
}
116+
117+
fn get_reachability_analysis(&self) -> ReachabilityType {
118+
*self.reachability_analysis.lock().unwrap()
119+
}
120+
87121
#[cfg(feature = "unsound_experiments")]
88122
fn get_unsound_experiments(&self) -> Arc<Mutex<UnsoundExperiments>> {
89123
self.unsound_experiments.clone()

kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bitflags::_core::any::Any;
88
use cbmc::goto_program::{symtab_transformer, Location};
99
use cbmc::InternedString;
1010
use kani_metadata::KaniMetadata;
11-
use kani_queries::{QueryDb, UserInput};
11+
use kani_queries::{QueryDb, ReachabilityType, UserInput};
1212
use rustc_codegen_ssa::traits::CodegenBackend;
1313
use rustc_codegen_ssa::{CodegenResults, CrateInfo};
1414
use rustc_data_structures::fx::FxHashMap;
@@ -60,7 +60,7 @@ impl CodegenBackend for GotocCodegenBackend {
6060
super::utils::init();
6161

6262
check_target(tcx.sess);
63-
check_options(tcx.sess, need_metadata_module);
63+
check_options(tcx.sess, need_metadata_module, self.queries.clone());
6464

6565
let codegen_units: &'tcx [CodegenUnit<'_>] = tcx.collect_and_partition_mono_items(()).1;
6666
let mut c = GotocCtx::new(tcx, self.queries.clone());
@@ -246,7 +246,7 @@ fn check_target(session: &Session) {
246246
session.abort_if_errors();
247247
}
248248

249-
fn check_options(session: &Session, need_metadata_module: bool) {
249+
fn check_options(session: &Session, need_metadata_module: bool, queries: Rc<QueryDb>) {
250250
// The requirements for `min_global_align` and `endian` are needed to build
251251
// a valid CBMC machine model in function `machine_model_from_session` from
252252
// src/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs
@@ -281,6 +281,14 @@ fn check_options(session: &Session, need_metadata_module: bool) {
281281
session.err("Kani cannot generate metadata module.");
282282
}
283283

284+
if queries.get_reachability_analysis() != ReachabilityType::Legacy {
285+
let err_msg = format!(
286+
"Using {} reachability mode is still unsupported.",
287+
queries.get_reachability_analysis().as_ref()
288+
);
289+
session.err(&err_msg);
290+
}
291+
284292
session.abort_if_errors();
285293
}
286294

kani-compiler/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ mod unsound_experiments;
3333

3434
use crate::session::init_session;
3535
use clap::ArgMatches;
36-
use kani_queries::{QueryDb, UserInput};
36+
use kani_queries::{QueryDb, ReachabilityType, UserInput};
3737
use rustc_driver::{Callbacks, RunCompiler};
3838
use std::env;
3939
use std::ffi::OsStr;
4040
use std::path::PathBuf;
4141
use std::rc::Rc;
42+
use std::str::FromStr as _;
4243

4344
/// This function generates all rustc configurations required by our goto-c codegen.
4445
fn rustc_gotoc_flags(lib_path: &str) -> Vec<String> {
@@ -91,6 +92,9 @@ fn main() -> Result<(), &'static str> {
9192
queries.set_check_assertion_reachability(matches.is_present(parser::ASSERTION_REACH_CHECKS));
9293
queries.set_output_pretty_json(matches.is_present(parser::PRETTY_OUTPUT_FILES));
9394
queries.set_ignore_global_asm(matches.is_present(parser::IGNORE_GLOBAL_ASM));
95+
queries.set_reachability_analysis(
96+
ReachabilityType::from_str(matches.value_of(parser::REACHABILITY).unwrap()).unwrap(),
97+
);
9498
#[cfg(feature = "unsound_experiments")]
9599
crate::unsound_experiments::arg_parser::add_unsound_experiment_args_to_queries(
96100
&mut queries,

kani-compiler/src/parser.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use clap::{
55
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, AppSettings,
66
Arg,
77
};
8+
use kani_queries::ReachabilityType;
89
use std::env;
10+
use strum::VariantNames as _;
911

1012
/// Option name used to set log level.
1113
pub const LOG_LEVEL: &str = "log-level";
@@ -40,6 +42,10 @@ pub const IGNORE_GLOBAL_ASM: &str = "ignore-global-asm";
4042
/// Option name used to override the sysroot.
4143
pub const SYSROOT: &str = "sysroot";
4244

45+
/// Option name used to select which reachability analysis to perform.
46+
pub const REACHABILITY: &str = "reachability";
47+
pub const REACHABILITY_FLAG: &str = "--reachability";
48+
4349
/// Option name used to pass extra rustc-options.
4450
pub const RUSTC_OPTIONS: &str = "rustc-options";
4551

@@ -133,6 +139,14 @@ pub fn parser<'a, 'b>() -> App<'a, 'b> {
133139
.long("--assertion-reach-checks")
134140
.help("Check the reachability of every assertion."),
135141
)
142+
.arg(
143+
Arg::with_name(REACHABILITY)
144+
.long(REACHABILITY_FLAG)
145+
.possible_values(ReachabilityType::VARIANTS)
146+
.required(false)
147+
.default_value(ReachabilityType::None.as_ref())
148+
.help("Selects the type of reachability analysis to perform."),
149+
)
136150
.arg(
137151
Arg::with_name(PRETTY_OUTPUT_FILES)
138152
.long("--pretty-json-files")
@@ -162,14 +176,24 @@ pub fn command_arguments(args: &Vec<String>) -> Vec<String> {
162176
assert!(!args.is_empty(), "Arguments should always include executable name");
163177
let has_kani_flags = args.iter().any(|arg| arg.eq(KANI_ARGS_FLAG));
164178
if has_kani_flags {
179+
let mut filter_args = vec![KANI_ARGS_FLAG];
165180
let mut new_args: Vec<String> = Vec::new();
166181
new_args.push(args[0].clone());
182+
// For cargo kani, --reachability is included as a rustc argument.
183+
let reachability = args.iter().find(|arg| arg.starts_with(REACHABILITY_FLAG));
184+
if let Some(value) = reachability {
185+
new_args.push(value.clone());
186+
filter_args.push(value)
187+
}
188+
// Add all the other kani specific arguments are inside $KANIFLAGS.
167189
let env_flags = env::var(KANIFLAGS_ENV_VAR).unwrap_or_default();
168190
new_args.extend(
169191
shell_words::split(&env_flags)
170192
.expect(&format!("Cannot parse {} value '{}'", KANIFLAGS_ENV_VAR, env_flags)),
171193
);
172-
new_args.extend(args[1..].iter().filter(|&arg| arg.ne(KANI_ARGS_FLAG)).cloned());
194+
// Add the leftover arguments for rustc at the end.
195+
new_args
196+
.extend(args[1..].iter().filter(|&arg| !filter_args.contains(&arg.as_str())).cloned());
173197
new_args
174198
} else {
175199
args.clone()

kani-driver/src/args.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,19 @@ pub struct KaniArgs {
121121
/// Kani will only compile the crate. No verification will be performed
122122
#[structopt(long, hidden_short_help(true))]
123123
pub only_codegen: bool,
124+
/// Enables experimental MIR Linker. This option will affect how Kani prunes the code to be
125+
/// analyzed. Please report any missing function issue found here:
126+
/// <https://github.com/model-checking/kani/issues/new/choose>
127+
#[structopt(long, hidden = true, requires("enable-unstable"))]
128+
pub mir_linker: bool,
124129
/// Compiles Kani harnesses in all features of all packages selected on the command-line.
125130
#[structopt(long)]
126131
pub all_features: bool,
127132
/// Run Kani on all packages in the workspace.
128133
#[structopt(long)]
129134
pub workspace: bool,
130135
/// Run Kani on the specified packages.
131-
#[structopt(long, short)]
136+
#[structopt(long, short, conflicts_with("workspace"))]
132137
pub package: Vec<String>,
133138

134139
/// Specify the value used for loop unwinding in CBMC

kani-driver/src/call_cargo.rs

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4+
use crate::args::KaniArgs;
5+
use crate::session::KaniSession;
46
use anyhow::{Context, Result};
5-
use cargo_metadata::MetadataCommand;
7+
use cargo_metadata::{Metadata, MetadataCommand};
68
use std::ffi::OsString;
79
use std::path::{Path, PathBuf};
810
use std::process::Command;
911

10-
use crate::session::KaniSession;
11-
1212
/// The outputs of kani-compiler being invoked via cargo on a project.
1313
pub struct CargoOutputs {
1414
/// The directory where compiler outputs should be directed.
@@ -22,66 +22,67 @@ pub struct CargoOutputs {
2222
pub metadata: Vec<PathBuf>,
2323
}
2424

25-
/// Finds the "target" directory while considering workspaces,
26-
fn find_target_dir() -> PathBuf {
27-
fn maybe_get_target() -> Option<PathBuf> {
28-
Some(MetadataCommand::new().exec().ok()?.target_directory.into())
29-
}
30-
31-
maybe_get_target().unwrap_or(PathBuf::from("target"))
32-
}
33-
3425
impl KaniSession {
3526
/// Calls `cargo_build` to generate `*.symtab.json` files in `target_dir`
3627
pub fn cargo_build(&self) -> Result<CargoOutputs> {
3728
let build_target = env!("TARGET"); // see build.rs
38-
let target_dir = self.args.target_dir.as_ref().unwrap_or(&find_target_dir()).clone();
29+
let metadata = MetadataCommand::new().exec().expect("Failed to get cargo metadata.");
30+
let target_dir = self
31+
.args
32+
.target_dir
33+
.as_ref()
34+
.unwrap_or(&metadata.target_directory.clone().into())
35+
.clone();
3936
let outdir = target_dir.join(build_target).join("debug/deps");
4037

41-
let flag_env = {
42-
let rustc_args = self.kani_rustc_flags();
43-
crate::util::join_osstring(&rustc_args, " ")
44-
};
45-
46-
let mut args: Vec<OsString> = Vec::new();
38+
let mut kani_args = self.kani_specific_flags();
39+
let rustc_args = self.kani_rustc_flags();
4740

41+
let mut cargo_args: Vec<OsString> = vec!["rustc".into()];
4842
if self.args.tests {
49-
args.push("test".into());
50-
args.push("--no-run".into());
51-
} else {
52-
args.push("build".into());
53-
}
54-
55-
for package in self.args.package.iter() {
56-
args.push("--package".into());
57-
args.push(package.into());
43+
cargo_args.push("--tests".into());
5844
}
5945

6046
if self.args.all_features {
61-
args.push("--all-features".into());
47+
cargo_args.push("--all-features".into());
6248
}
6349

64-
if self.args.workspace {
65-
args.push("--workspace".into());
66-
}
67-
68-
args.push("--target".into());
69-
args.push(build_target.into());
50+
cargo_args.push("--target".into());
51+
cargo_args.push(build_target.into());
7052

71-
args.push("--target-dir".into());
72-
args.push(target_dir.into());
53+
cargo_args.push("--target-dir".into());
54+
cargo_args.push(target_dir.into());
7355

7456
if self.args.verbose {
75-
args.push("-v".into());
57+
cargo_args.push("-v".into());
58+
}
59+
60+
// Arguments that will only be passed to the target package.
61+
let mut pkg_args: Vec<OsString> = vec![];
62+
if self.args.mir_linker {
63+
// Only provide reachability flag to the target package.
64+
pkg_args.push("--".into());
65+
pkg_args.push("--reachability=harnesses".into());
66+
} else {
67+
// Pass legacy reachability to the target package and its dependencies.
68+
kani_args.push("--reachability=legacy".into());
7669
}
7770

78-
let mut cmd = Command::new("cargo");
79-
cmd.args(args)
80-
.env("RUSTC", &self.kani_compiler)
81-
.env("RUSTFLAGS", "--kani-flags")
82-
.env("KANIFLAGS", flag_env);
71+
// Only joing them at the end. All kani flags must come first.
72+
kani_args.extend_from_slice(&rustc_args);
8373

84-
self.run_terminal(cmd)?;
74+
let members = project_members(&self.args, &metadata);
75+
for member in members {
76+
let mut cmd = Command::new("cargo");
77+
cmd.args(&cargo_args)
78+
.args(vec!["-p", &member])
79+
.args(&pkg_args)
80+
.env("RUSTC", &self.kani_compiler)
81+
.env("RUSTFLAGS", "--kani-flags")
82+
.env("KANIFLAGS", &crate::util::join_osstring(&kani_args, " "));
83+
84+
self.run_terminal(cmd)?;
85+
}
8586

8687
if self.args.dry_run {
8788
// mock an answer: mostly the same except we don't/can't expand the globs
@@ -110,3 +111,23 @@ fn glob(path: &Path) -> Result<Vec<PathBuf>> {
110111
let v: core::result::Result<Vec<PathBuf>, glob::GlobError> = results.collect();
111112
Ok(v?)
112113
}
114+
115+
/// Extract the packages that should be verified.
116+
/// If --package <pkg> is given, return the list of packages selected.
117+
/// If --workspace is given, return the list of workspace members.
118+
/// If no argument provided, return the root package if there's one or all members.
119+
/// - I.e.: Do whatever cargo does when there's no default_members.
120+
/// - This is because `default_members` is not available in cargo metadata.
121+
/// See <https://github.com/rust-lang/cargo/issues/8033>.
122+
fn project_members(args: &KaniArgs, metadata: &Metadata) -> Vec<String> {
123+
if !args.package.is_empty() {
124+
args.package.clone()
125+
} else {
126+
match (args.workspace, metadata.root_package()) {
127+
(true, _) | (_, None) => {
128+
metadata.workspace_members.iter().map(|id| metadata[id].name.clone()).collect()
129+
}
130+
(_, Some(root_pkg)) => vec![root_pkg.name.clone()],
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)