Skip to content

Commit ab6a5ec

Browse files
authored
Add zero-init, and put it behind an unsound feature flag (rust-lang#1521)
1 parent d53296a commit ab6a5ec

File tree

24 files changed

+278
-23
lines changed

24 files changed

+278
-23
lines changed

.github/workflows/kani.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ jobs:
3333
- name: Execute Kani regression
3434
run: ./scripts/kani-regression.sh
3535

36+
experimental-features-regression:
37+
runs-on: ubuntu-20.04
38+
env:
39+
KANI_ENABLE_UNSOUND_EXPERIMENTS: 1
40+
steps:
41+
- name: Checkout Kani
42+
uses: actions/checkout@v2
43+
44+
- name: Setup Kani Dependencies
45+
uses: ./.github/actions/setup
46+
with:
47+
os: ubuntu-20.04
48+
49+
- name: Build Kani
50+
run: cargo build
51+
52+
- name: Execute Kani regression
53+
run: ./scripts/kani-regression.sh
54+
3655
perf:
3756
runs-on: ubuntu-20.04
3857
steps:

cprover_bindings/src/goto_program/typ.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,13 @@ impl DatatypeComponent {
185185
}
186186

187187
pub fn field<T: Into<InternedString>>(name: T, typ: Type) -> Self {
188+
let name = name.into();
188189
assert!(
189190
Self::typecheck_datatype_field(&typ),
190191
"Illegal field.\n\tName: {}\n\tType: {:?}",
191-
name.into(),
192+
name,
192193
typ
193194
);
194-
let name = name.into();
195195
Field { name, typ }
196196
}
197197

kani-compiler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ tracing-tree = "0.2.0"
3535
default = ['cprover']
3636
cprover = ['ar', 'bitflags', 'cbmc', 'kani_metadata', 'libc', 'num', 'object', 'rustc-demangle', 'serde',
3737
'serde_json', "strum", "strum_macros"]
38+
unsound_experiments = ["kani_queries/unsound_experiments"]
3839

3940
[package.metadata.rust-analyzer]
4041
# This package uses rustc crates.

kani-compiler/kani_queries/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ edition = "2021"
88
license = "MIT OR Apache-2.0"
99
publish = false
1010

11+
[features]
12+
unsound_experiments = []
13+
1114
[dependencies]
1215
tracing = {version = "0.1"}

kani-compiler/kani_queries/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33

44
use std::sync::atomic::{AtomicBool, Ordering};
55

6+
#[cfg(feature = "unsound_experiments")]
7+
mod unsound_experiments;
8+
9+
#[cfg(feature = "unsound_experiments")]
10+
use {
11+
crate::unsound_experiments::UnsoundExperiments,
12+
std::sync::{Arc, Mutex},
13+
};
14+
615
pub trait UserInput {
716
fn set_symbol_table_passes(&mut self, passes: Vec<String>);
817
fn get_symbol_table_passes(&self) -> Vec<String>;
@@ -18,6 +27,9 @@ pub trait UserInput {
1827

1928
fn set_ignore_global_asm(&mut self, global_asm: bool);
2029
fn get_ignore_global_asm(&self) -> bool;
30+
31+
#[cfg(feature = "unsound_experiments")]
32+
fn get_unsound_experiments(&self) -> Arc<Mutex<UnsoundExperiments>>;
2133
}
2234

2335
#[derive(Debug, Default)]
@@ -27,6 +39,8 @@ pub struct QueryDb {
2739
symbol_table_passes: Vec<String>,
2840
json_pretty_print: AtomicBool,
2941
ignore_global_asm: AtomicBool,
42+
#[cfg(feature = "unsound_experiments")]
43+
unsound_experiments: Arc<Mutex<UnsoundExperiments>>,
3044
}
3145

3246
impl UserInput for QueryDb {
@@ -69,4 +83,9 @@ impl UserInput for QueryDb {
6983
fn get_ignore_global_asm(&self) -> bool {
7084
self.ignore_global_asm.load(Ordering::Relaxed)
7185
}
86+
87+
#[cfg(feature = "unsound_experiments")]
88+
fn get_unsound_experiments(&self) -> Arc<Mutex<UnsoundExperiments>> {
89+
self.unsound_experiments.clone()
90+
}
7291
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
#![cfg(feature = "unsound_experiments")]
5+
6+
#[derive(Debug, Default)]
7+
pub struct UnsoundExperiments {
8+
/// Zero initilize variables.
9+
/// This is useful for experiments to see whether assigning constant values produces better
10+
/// performance by allowing CBMC to do more constant propegation.
11+
/// Unfortunatly, it is unsafe to use for production code, since it may unsoundly hide bugs.
12+
pub zero_init_vars: bool,
13+
}

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ impl<'tcx> GotocCtx<'tcx> {
7777
// Index 0 represents the return value, which does not need to be
7878
// declared in the first block
7979
if lc.index() < 1 || lc.index() > mir.arg_count {
80-
self.current_fn_mut().push_onto_block(Stmt::decl(sym_e, None, loc));
80+
let init = self.codegen_default_initializer(&sym_e);
81+
self.current_fn_mut().push_onto_block(Stmt::decl(sym_e, init, loc));
8182
}
8283
});
8384
}

kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,7 @@ impl<'tcx> GotocCtx<'tcx> {
5656
.assign(self.codegen_rvalue(r, location), location)
5757
}
5858
}
59-
StatementKind::Deinit(place) => {
60-
// From rustc doc: "This writes `uninit` bytes to the entire place."
61-
// Our model of GotoC has a similar statement, which is later lowered
62-
// to assigning a Nondet in CBMC, with a comment specifying that it
63-
// corresponds to a Deinit.
64-
let dst_mir_ty = self.place_ty(place);
65-
let dst_type = self.codegen_ty(dst_mir_ty);
66-
let layout = self.layout_of(dst_mir_ty);
67-
if layout.is_zst() || dst_type.sizeof_in_bits(&self.symbol_table) == 0 {
68-
// We ignore assignment for all zero size types
69-
Stmt::skip(location)
70-
} else {
71-
unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(place))
72-
.goto_expr
73-
.deinit(location)
74-
}
75-
}
59+
StatementKind::Deinit(place) => self.codegen_deinit(place, location),
7660
StatementKind::SetDiscriminant { place, variant_index } => {
7761
// this requires place points to an enum type.
7862
let pt = self.place_ty(place);
@@ -277,6 +261,25 @@ impl<'tcx> GotocCtx<'tcx> {
277261
}
278262
}
279263

264+
/// From rustc doc: "This writes `uninit` bytes to the entire place."
265+
/// Our model of GotoC has a similar statement, which is later lowered
266+
/// to assigning a Nondet in CBMC, with a comment specifying that it
267+
/// corresponds to a Deinit.
268+
#[cfg(not(feature = "unsound_experiments"))]
269+
fn codegen_deinit(&mut self, place: &Place<'tcx>, loc: Location) -> Stmt {
270+
let dst_mir_ty = self.place_ty(place);
271+
let dst_type = self.codegen_ty(dst_mir_ty);
272+
let layout = self.layout_of(dst_mir_ty);
273+
if layout.is_zst() || dst_type.sizeof_in_bits(&self.symbol_table) == 0 {
274+
// We ignore assignment for all zero size types
275+
Stmt::skip(loc)
276+
} else {
277+
unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(place))
278+
.goto_expr
279+
.deinit(loc)
280+
}
281+
}
282+
280283
/// A special case handler to codegen `return ();`
281284
fn codegen_ret_unit(&mut self) -> Stmt {
282285
let is_file_local = false;

kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,15 @@ impl<'tcx> GotocCtx<'tcx> {
676676
Type::struct_tag(name)
677677
}
678678

679+
/// Codegens the an initalizer for variables without one.
680+
/// By default, returns `None` which leaves the variable uninitilized.
681+
/// In CBMC, this translates to a NONDET value.
682+
/// In the future, we might want to replace this with `Poison`.
683+
#[cfg(not(feature = "unsound_experiments"))]
684+
pub fn codegen_default_initializer(&self, _e: &Expr) -> Option<Expr> {
685+
None
686+
}
687+
679688
/// The unit type in Rust is an empty struct in gotoc
680689
pub fn codegen_ty_unit(&mut self) -> Type {
681690
self.ensure_struct(UNIT_TYPE_EMPTY_STRUCT_NAME, "()", |_, _| vec![])

kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ impl<'tcx> GotocCtx<'tcx> {
161161
let c = self.current_fn_mut().get_and_incr_counter();
162162
let var =
163163
self.gen_stack_variable(c, &self.current_fn().name(), "temp", t, loc, false).to_expr();
164+
let value = value.or_else(|| self.codegen_default_initializer(&var));
164165
let decl = Stmt::decl(var.clone(), value, loc);
165166
(var, decl)
166167
}

kani-compiler/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern crate rustc_target;
2929
mod codegen_cprover_gotoc;
3030
mod parser;
3131
mod session;
32+
mod unsound_experiments;
3233

3334
use crate::session::init_session;
3435
use clap::ArgMatches;
@@ -90,6 +91,11 @@ fn main() -> Result<(), &'static str> {
9091
queries.set_check_assertion_reachability(matches.is_present(parser::ASSERTION_REACH_CHECKS));
9192
queries.set_output_pretty_json(matches.is_present(parser::PRETTY_OUTPUT_FILES));
9293
queries.set_ignore_global_asm(matches.is_present(parser::IGNORE_GLOBAL_ASM));
94+
#[cfg(feature = "unsound_experiments")]
95+
crate::unsound_experiments::arg_parser::add_unsound_experiment_args_to_queries(
96+
&mut queries,
97+
&matches,
98+
);
9399

94100
// Generate rustc args.
95101
let rustc_args = generate_rustc_args(&matches);

kani-compiler/src/parser.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const KANI_ARGS_FLAG: &str = "--kani-flags";
5353

5454
/// Configure command options for the Kani compiler.
5555
pub fn parser<'a, 'b>() -> App<'a, 'b> {
56-
app_from_crate!()
56+
let app = app_from_crate!()
5757
.setting(AppSettings::TrailingVarArg) // This allow us to fwd commands to rustc.
5858
.setting(clap::AppSettings::AllowLeadingHyphen)
5959
.version_short("?")
@@ -142,7 +142,11 @@ pub fn parser<'a, 'b>() -> App<'a, 'b> {
142142
Arg::with_name(IGNORE_GLOBAL_ASM)
143143
.long("--ignore-global-asm")
144144
.help("Suppress error due to the existence of global_asm in a crate"),
145-
)
145+
);
146+
#[cfg(feature = "unsound_experiments")]
147+
let app = crate::unsound_experiments::arg_parser::add_unsound_experiments_to_parser(app);
148+
149+
app
146150
}
147151

148152
/// Retrieves the arguments from the command line and process hack to incorporate CARGO arguments.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
#![cfg(feature = "unsound_experiments")]
5+
use clap::{App, Arg, ArgMatches};
6+
use kani_queries::{QueryDb, UserInput};
7+
/// Option used for zero initilizing variables.
8+
const ZERO_INIT_VARS: &str = "unsound-experiment-zero-init-vars";
9+
10+
pub fn add_unsound_experiments_to_parser<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
11+
app.arg(
12+
Arg::with_name(ZERO_INIT_VARS)
13+
.long(ZERO_INIT_VARS)
14+
.help("POTENTIALLY UNSOUND EXPERIMENTAL FEATURE. Zero initialize variables"),
15+
)
16+
}
17+
18+
pub fn add_unsound_experiment_args_to_queries(queries: &mut QueryDb, matches: &ArgMatches) {
19+
queries.get_unsound_experiments().lock().unwrap().zero_init_vars =
20+
matches.is_present(ZERO_INIT_VARS);
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
#![cfg(feature = "unsound_experiments")]
5+
6+
pub mod arg_parser;
7+
mod zero_init;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
#![cfg(feature = "unsound_experiments")]
5+
use crate::codegen_cprover_gotoc::GotocCtx;
6+
use crate::unwrap_or_return_codegen_unimplemented_stmt;
7+
use cbmc::goto_program::{Expr, Location, Stmt};
8+
use kani_queries::UserInput;
9+
use rustc_middle::mir::Place;
10+
use rustc_middle::ty::layout::LayoutOf;
11+
12+
impl<'tcx> GotocCtx<'tcx> {
13+
/// Codegens the an initalizer for variables without one.
14+
/// If the zero initilizer flag is set, does zero init (if possible).
15+
/// Otherwise, returns `None` which leaves the variable uninitilized.
16+
/// In CBMC, this translates to a NONDET value.
17+
pub fn codegen_default_initializer(&mut self, e: &Expr) -> Option<Expr> {
18+
if self.queries.get_unsound_experiments().lock().unwrap().zero_init_vars {
19+
Some(e.typ().zero_initializer(&self.symbol_table))
20+
} else {
21+
None
22+
}
23+
}
24+
25+
/// From rustc doc: "This writes `uninit` bytes to the entire place."
26+
/// Our model of GotoC has a similar statement, which is later lowered
27+
/// to assigning a Nondet in CBMC, with a comment specifying that it
28+
/// corresponds to a Deinit.
29+
pub fn codegen_deinit(&mut self, place: &Place<'tcx>, loc: Location) -> Stmt {
30+
let dst_mir_ty = self.place_ty(place);
31+
let dst_type = self.codegen_ty(dst_mir_ty);
32+
let layout = self.layout_of(dst_mir_ty);
33+
let goto_place =
34+
unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(place)).goto_expr;
35+
if layout.is_zst() || dst_type.sizeof_in_bits(&self.symbol_table) == 0 {
36+
// We ignore assignment for all zero size types
37+
Stmt::skip(loc)
38+
} else if self.queries.get_unsound_experiments().lock().unwrap().zero_init_vars {
39+
let init = goto_place.typ().zero_initializer(&self.symbol_table);
40+
goto_place.assign(init, loc)
41+
} else {
42+
goto_place.deinit(loc)
43+
}
44+
}
45+
}

kani-driver/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ homepage = "https://github.com/model-checking/kani"
1111
repository = "https://github.com/model-checking/kani"
1212
publish = false
1313

14+
[features]
15+
unsound_experiments = []
16+
17+
1418
[dependencies]
1519
kani_metadata = { path = "../kani_metadata" }
1620
cargo_metadata = "0.15.0"

kani-driver/src/args.rs

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

4+
#[cfg(feature = "unsound_experiments")]
5+
use crate::unsound_experiments::UnsoundExperimentArgs;
6+
47
use anyhow::bail;
58
use clap::{arg_enum, Error, ErrorKind};
69
use std::ffi::OsString;
@@ -95,6 +98,10 @@ pub struct KaniArgs {
9598
#[structopt(flatten)]
9699
pub checks: CheckArgs,
97100

101+
#[cfg(feature = "unsound_experiments")]
102+
#[structopt(flatten)]
103+
pub unsound_experiments: UnsoundExperimentArgs,
104+
98105
/// Entry point for verification (symbol name).
99106
/// This is an unstable feature. Consider using --harness instead
100107
#[structopt(long, hidden = true, requires("enable-unstable"), conflicts_with("dry-run"))]

kani-driver/src/call_single_file.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ impl KaniSession {
121121
flags.push("--ignore-global-asm".into());
122122
}
123123

124+
#[cfg(feature = "unsound_experiments")]
125+
flags.extend(self.args.unsound_experiments.process_args());
126+
124127
// Stratification point!
125128
// Above are arguments that should be parsed by kani-compiler
126129
// Below are arguments that should be parsed by the rustc call

kani-driver/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ mod metadata;
2626
mod session;
2727
mod util;
2828

29+
#[cfg(feature = "unsound_experiments")]
30+
mod unsound_experiments;
31+
2932
fn main() -> Result<()> {
3033
match determine_invocation_type(Vec::from_iter(std::env::args_os())) {
3134
InvocationType::CargoKani(args) => cargokani_main(args),
@@ -180,6 +183,9 @@ impl KaniSession {
180183
);
181184
}
182185

186+
#[cfg(feature = "unsound_experiments")]
187+
self.args.unsound_experiments.print_warnings();
188+
183189
if !failed_harnesses.is_empty() {
184190
// Failure exit code without additional error message
185191
drop(self);

0 commit comments

Comments
 (0)