Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loop Contracts Annotation for While-Loop #3151

Merged
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
0e49d50
Loop Contracts Annotation for While-Loop
qinheping Apr 19, 2024
a5cc42c
Bump dependencies and Kani's version to 0.50.0 (#3148)
celinval Apr 17, 2024
537eefc
Upgrade toolchain to 2024-04-18 and improve toolchain workflow (#3149)
celinval Apr 18, 2024
4806dac
Automatic toolchain upgrade to nightly-2024-04-19 (#3150)
github-actions[bot] Apr 19, 2024
55e5f0d
Stabilize cover statement and update contracts RFC (#3091)
celinval Apr 19, 2024
0e6c192
Automatic toolchain upgrade to nightly-2024-04-20 (#3154)
github-actions[bot] Apr 22, 2024
dd6e60f
Bump tests/perf/s2n-quic from `2d5e891` to `5f88e54` (#3140)
dependabot[bot] Apr 22, 2024
a542ede
Automatic cargo update to 2024-04-22 (#3157)
github-actions[bot] Apr 22, 2024
3cf110c
Automatic toolchain upgrade to nightly-2024-04-21 (#3158)
github-actions[bot] Apr 22, 2024
4be4214
Bump tests/perf/s2n-quic from `5f88e54` to `9730578` (#3159)
dependabot[bot] Apr 22, 2024
e5b0a2a
Fix cargo audit error (#3160)
jaisnan Apr 22, 2024
b244770
Fix cbmc-update CI job (#3156)
tautschnig Apr 23, 2024
ec29ffd
Automatic cargo update to 2024-04-29 (#3165)
github-actions[bot] Apr 29, 2024
1371195
Bump tests/perf/s2n-quic from `9730578` to `1436af7` (#3166)
dependabot[bot] Apr 29, 2024
5d64679
Do not assume that ZST-typed symbols refer to unique objects (#3134)
tautschnig Apr 30, 2024
7bc0cb8
Fix copyright check for `expected` tests (#3170)
adpaco-aws May 3, 2024
df2c1fb
Remove kani::Arbitrary from the modifies contract instrumentation (#3…
feliperodri May 3, 2024
5edab5d
Annotate loop contracts as statement expression
qinheping May 9, 2024
c8ecefe
Automatic cargo update to 2024-05-06 (#3172)
github-actions[bot] May 6, 2024
bda1f3c
Bump tests/perf/s2n-quic from `1436af7` to `6dd41e0` (#3174)
dependabot[bot] May 6, 2024
eaf5b42
Avoid unnecessary uses of Location::none() (#3173)
tautschnig May 7, 2024
cf5a8f9
Update Rust dependencies (#3175)
karkhaz May 7, 2024
584a3de
Bump Kani version to 0.51.0 (#3176)
karkhaz May 7, 2024
195c453
Merge branch 'main' into features/loop-contracts-annotation
qinheping May 9, 2024
52878c5
Remove leftover code
qinheping May 9, 2024
6547dcd
Remove unused import
qinheping May 9, 2024
319a8b9
Fix format
qinheping May 9, 2024
2569af0
Loop invariants should be operands of named subs
qinheping May 9, 2024
36c7628
Merge branch 'main' into features/loop-contracts-annotation
qinheping Aug 6, 2024
159e6ad
Allow cloned reachability checks
qinheping Aug 6, 2024
fbd17d6
Fix format
qinheping Aug 6, 2024
1a81de2
Fix format
qinheping Aug 7, 2024
3596dbe
Apply Adrian's comments
qinheping Aug 13, 2024
5210107
Use with_loop_contracts
qinheping Aug 13, 2024
54168fd
Fix tests
qinheping Aug 13, 2024
def6b97
Merge branch 'main' into features/loop-contracts-annotation
qinheping Sep 3, 2024
64b66d3
Fix format
qinheping Sep 3, 2024
0b05968
Refactor the loop contracts with closures
qinheping Sep 11, 2024
3624655
Add missing copyright
qinheping Sep 11, 2024
3a481a0
Merge branch 'main' into features/loop-contracts-annotation
qinheping Sep 11, 2024
68554df
Use proc_macro_error2
qinheping Sep 11, 2024
75a82d5
Provide locals to ty()
qinheping Sep 11, 2024
20b8de0
Do loop contracts transformation only for harnesses
qinheping Sep 11, 2024
c82684e
Merge branch 'main' into features/loop-contracts-annotation
qinheping Sep 20, 2024
90c1c5c
Move loop-contracts-hook and add more documentation
qinheping Sep 20, 2024
608baeb
Fix format
qinheping Sep 20, 2024
4617a22
Refactor to avoid violating borrow check
qinheping Oct 8, 2024
4337947
Merge branch 'main' into features/loop-contracts-annotation
qinheping Oct 8, 2024
077985d
Fix format
qinheping Oct 8, 2024
ab4f484
Fix format
qinheping Oct 8, 2024
04492fb
Ignore loop contract macros when loop contracts disabled
qinheping Oct 8, 2024
7920f91
Fix copyright
qinheping Oct 8, 2024
cf52ca9
Merge branch 'main' into features/loop-contracts-annotation
qinheping Oct 9, 2024
52f94a8
Add checkks for unspport invariants
qinheping Oct 10, 2024
c912349
Merge branch 'main' into features/loop-contracts-annotation
qinheping Oct 10, 2024
aa31528
Code simplification
qinheping Oct 11, 2024
faf50e9
Add more tests
qinheping Oct 11, 2024
3c1a64a
Use DFCC
qinheping Oct 14, 2024
5cf2ec1
Merge branch 'main' into features/loop-contracts-annotation
qinheping Oct 14, 2024
f008223
Fix format
qinheping Oct 14, 2024
8b90842
Merge branch 'main' into features/loop-contracts-annotation
qinheping Oct 14, 2024
b1ef46d
Add expected
qinheping Oct 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions cprover_bindings/src/goto_program/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ pub enum StmtBody {
arguments: Vec<Expr>,
},
/// `goto dest;`
Goto(InternedString),
Goto {
dest: InternedString,
loop_invariants: Option<Expr>,
qinheping marked this conversation as resolved.
Show resolved Hide resolved
},
/// `if (i) { t } else { e }`
Ifthenelse {
i: Expr,
Expand Down Expand Up @@ -179,7 +182,7 @@ impl Stmt {
stmt!(Assign { lhs, rhs }, loc)
}

/// `assert(cond, property_class, commment);`
/// `assert(cond, property_class, comment);`
pub fn assert(cond: Expr, property_name: &str, message: &str, loc: Location) -> Self {
assert!(cond.typ().is_bool());
assert!(!property_name.is_empty() && !message.is_empty());
Expand All @@ -188,7 +191,7 @@ impl Stmt {
let loc_with_property =
Location::create_location_with_property(message, property_name, loc);

// Chose InternedString to seperate out codegen from the cprover_bindings logic
// Chose InternedString to separate out codegen from the cprover_bindings logic
let property_class = property_name.intern();
let msg = message.into();

Expand Down Expand Up @@ -283,7 +286,18 @@ impl Stmt {
pub fn goto<T: Into<InternedString>>(dest: T, loc: Location) -> Self {
let dest = dest.into();
assert!(!dest.is_empty());
stmt!(Goto(dest), loc)
stmt!(Goto { dest, loop_invariants: None }, loc)
}

/// `goto dest;` with loop invariant
pub fn goto_with_loop_inv<T: Into<InternedString>>(
qinheping marked this conversation as resolved.
Show resolved Hide resolved
dest: T,
loop_invariants: Expr,
loc: Location,
) -> Self {
let dest = dest.into();
assert!(!dest.is_empty());
stmt!(Goto { dest, loop_invariants: Some(loop_invariants) }, loc)
}

/// `if (i) { t } else { e }` or `if (i) { t }`
Expand Down
14 changes: 12 additions & 2 deletions cprover_bindings/src/irep/to_irep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,18 @@ impl ToIrep for StmtBody {
arguments_irep(arguments.iter(), mm),
],
),
StmtBody::Goto(dest) => code_irep(IrepId::Goto, vec![])
.with_named_sub(IrepId::Destination, Irep::just_string_id(dest.to_string())),
StmtBody::Goto { dest, loop_invariants } => {
let stmt_goto = code_irep(IrepId::Goto, vec![])
.with_named_sub(IrepId::Destination, Irep::just_string_id(dest.to_string()));
if loop_invariants.is_some() {
stmt_goto.with_named_sub(
IrepId::CSpecLoopInvariant,
loop_invariants.clone().unwrap().to_irep(mm),
qinheping marked this conversation as resolved.
Show resolved Hide resolved
)
} else {
stmt_goto
}
}
StmtBody::Ifthenelse { i, t, e } => code_irep(
IrepId::Ifthenelse,
vec![
Expand Down
34 changes: 30 additions & 4 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,23 @@ impl<'tcx> GotocCtx<'tcx> {
debug!(?bb, "codegen_block");
let label = bb_label(bb);
let check_coverage = self.queries.args().check_coverage;

// record the seen bbidx if loop contracts enabled
if self.loop_contracts_ctx.loop_contracts_enabled() {
self.loop_contracts_ctx.add_new_seen_bbidx(bb);
}

// the first statement should be labelled. if there is no statements, then the
// terminator should be labelled.
match bbd.statements.len() {
0 => {
let term = &bbd.terminator;
let tcode = self.codegen_terminator(term);
let tcode = if self.loop_contracts_ctx.loop_contracts_enabled() {
let codegen_result = self.codegen_terminator(term);
self.loop_contracts_ctx.push_onto_block(codegen_result)
} else {
self.codegen_terminator(term)
};
// When checking coverage, the `coverage` check should be
// labelled instead.
if check_coverage {
Expand All @@ -40,7 +51,12 @@ impl<'tcx> GotocCtx<'tcx> {
}
_ => {
let stmt = &bbd.statements[0];
let scode = self.codegen_statement(stmt);
let scode = if self.loop_contracts_ctx.loop_contracts_enabled() {
let codegen_result = self.codegen_statement(stmt);
self.loop_contracts_ctx.push_onto_block(codegen_result)
} else {
self.codegen_statement(stmt)
};
// When checking coverage, the `coverage` check should be
// labelled instead.
if check_coverage {
Expand All @@ -58,7 +74,12 @@ impl<'tcx> GotocCtx<'tcx> {
let cover = self.codegen_coverage(span);
self.current_fn_mut().push_onto_block(cover);
}
let stmt = self.codegen_statement(s);
let stmt = if self.loop_contracts_ctx.loop_contracts_enabled() {
let codegen_result = self.codegen_statement(s);
self.loop_contracts_ctx.push_onto_block(codegen_result)
} else {
self.codegen_statement(s)
};
self.current_fn_mut().push_onto_block(stmt);
}
let term = &bbd.terminator;
Expand All @@ -67,7 +88,12 @@ impl<'tcx> GotocCtx<'tcx> {
let cover = self.codegen_coverage(span);
self.current_fn_mut().push_onto_block(cover);
}
let tcode = self.codegen_terminator(term);
let tcode = if self.loop_contracts_ctx.loop_contracts_enabled() {
let codegen_result = self.codegen_terminator(term);
self.loop_contracts_ctx.push_onto_block(codegen_result)
} else {
self.codegen_terminator(term)
};
self.current_fn_mut().push_onto_block(tcode);
}
}
Expand Down
3 changes: 3 additions & 0 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ impl<'tcx> GotocCtx<'tcx> {
let old_sym = self.symbol_table.lookup(&name).unwrap();

let _trace_span = debug_span!("CodegenFunction", name = instance.name()).entered();
if self.loop_contracts_ctx.loop_contracts_enabled() {
self.loop_contracts_ctx.enter_new_function();
}
if old_sym.is_function_definition() {
debug!("Double codegen of {:?}", old_sym);
} else {
Expand Down
16 changes: 14 additions & 2 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,24 @@ impl<'tcx> GotocCtx<'tcx> {
///
/// See also [`GotocCtx::codegen_statement`] for ordinary [Statement]s.
pub fn codegen_terminator(&mut self, term: &Terminator) -> Stmt {
let loc = self.codegen_span_stable(term.span);
let loc: Location = self.codegen_span_stable(term.span);
let _trace_span = debug_span!("CodegenTerminator", statement = ?term.kind).entered();
debug!("handling terminator {:?}", term);
//TODO: Instead of doing location::none(), and updating, just putit in when we make the stmt.
match &term.kind {
TerminatorKind::Goto { target } => Stmt::goto(bb_label(*target), loc),
TerminatorKind::Goto { target } => {
if self.loop_contracts_ctx.loop_contracts_enabled()
&& self.loop_contracts_ctx.is_loop_latch(target)
{
Stmt::goto_with_loop_inv(
bb_label(*target),
self.loop_contracts_ctx.extract_block(loc),
loc,
)
} else {
Stmt::goto(bb_label(*target), loc)
}
}
TerminatorKind::SwitchInt { discr, targets } => {
self.codegen_switch_int(discr, targets, loc)
}
Expand Down
6 changes: 6 additions & 0 deletions kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! Any MIR specific functionality (e.g. codegen etc) should live in specialized files that use
//! this structure as input.
use super::current_fn::CurrentFnCtx;
use super::loop_contracts_ctx::LoopContractsCtx;
use super::vtable_ctx::VtableCtx;
use crate::codegen_cprover_gotoc::overrides::{fn_hooks, GotocHooks};
use crate::codegen_cprover_gotoc::utils::full_crate_name;
Expand Down Expand Up @@ -74,6 +75,8 @@ pub struct GotocCtx<'tcx> {
pub concurrent_constructs: UnsupportedConstructs,
/// The body transformation agent.
pub transformer: BodyTransformation,
/// The context for loop contracts code generation.
pub loop_contracts_ctx: LoopContractsCtx,
}

/// Constructor
Expand All @@ -87,6 +90,8 @@ impl<'tcx> GotocCtx<'tcx> {
let fhks = fn_hooks();
let symbol_table = SymbolTable::new(machine_model.clone());
let emit_vtable_restrictions = queries.args().emit_vtable_restrictions;
let loop_contracts_enabled =
queries.args().unstable_features.contains(&"loop-contracts".to_string());
GotocCtx {
tcx,
queries,
Expand All @@ -103,6 +108,7 @@ impl<'tcx> GotocCtx<'tcx> {
unsupported_constructs: FxHashMap::default(),
concurrent_constructs: FxHashMap::default(),
transformer,
loop_contracts_ctx: LoopContractsCtx::new(loop_contracts_enabled),
}
}
}
Expand Down
140 changes: 140 additions & 0 deletions kani-compiler/src/codegen_cprover_gotoc/context/loop_contracts_ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::codegen_cprover_gotoc::codegen::bb_label;
use cbmc::goto_program::{CIntType, Expr, Location, Stmt, StmtBody, Type};
use stable_mir::mir::BasicBlockIdx;
use std::collections::HashSet;

pub struct LoopContractsCtx {
/// the GOTO block compiled from the corresponding loop invariants
invariants_block: Vec<Stmt>,
/// Which codegen state
stage: LoopContractsStage,
/// If enable loop contracts
loop_contracts_enabled: bool,
/// Seen basic block indexes. Used to decide if a jump is backward
seen_bbidx: HashSet<BasicBlockIdx>,
/// Current unused bbidx label
current_bbidx_label: Option<String>,
/// The lhs of evaluation of the loop invariant
loop_invariant_lhs: Option<Stmt>,
}

/// We define two states:
/// 1. loop invariants block
/// In this state, we push all codegen stmts into the invariant block.
/// We enter this state when codegen for `KaniLoopInvariantBegin`.
/// We exit this state when codegen for `KaniLoopInvariantEnd`.
qinheping marked this conversation as resolved.
Show resolved Hide resolved
/// 2. loop latch block
/// In this state, we codegen a statement expression from the
/// invariant_block annotate the statement expression to the named sub
/// of the next backward jumping we codegen.
/// We enter this state when codegen for `KaniLoopInvariantEnd`.
/// We exit this state when codegen for the first backward jumping.
#[allow(dead_code)]
qinheping marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, PartialEq)]
enum LoopContractsStage {
/// Codegen for user code as usual
UserCode,
/// Codegen for loop invariants
InvariantBlock,
/// Codegen for loop latch node
FindingLatchNode,
}

/// Constructor
impl LoopContractsCtx {
pub fn new(loop_contracts_enabled: bool) -> Self {
Self {
invariants_block: Vec::new(),
stage: LoopContractsStage::UserCode,
loop_contracts_enabled,
seen_bbidx: HashSet::new(),
current_bbidx_label: None,
loop_invariant_lhs: None,
}
}
}

/// Getters
impl LoopContractsCtx {
pub fn loop_contracts_enabled(&self) -> bool {
self.loop_contracts_enabled
}

/// decide if a GOTO with `target` is backward jump
pub fn is_loop_latch(&self, target: &BasicBlockIdx) -> bool {
self.stage == LoopContractsStage::FindingLatchNode && self.seen_bbidx.contains(target)
}
}

/// Setters
impl LoopContractsCtx {
/// Returns the current block as a statement expression.
/// Exit loop latch block.
pub fn extract_block(&mut self, loc: Location) -> Expr {
assert!(self.loop_invariant_lhs.is_some());
self.stage = LoopContractsStage::UserCode;
self.invariants_block.push(self.loop_invariant_lhs.as_ref().unwrap().clone());

// The first statement is the GOTO in the rhs of __kani_loop_invariant_begin()
// Ignore it
self.invariants_block.remove(0);

Expr::statement_expression(
std::mem::take(&mut self.invariants_block),
Type::CInteger(CIntType::Bool),
loc,
)
.cast_to(Type::bool())
.and(Expr::bool_true())
}

/// Push the `s` onto the block if it is in the loop invariant block
/// and return `skip`. Otherwise, do nothing and return `s`.
pub fn push_onto_block(&mut self, s: Stmt) -> Stmt {
if self.stage == LoopContractsStage::InvariantBlock {
// Attach the lable to the first Stmt in that block and reset it.
qinheping marked this conversation as resolved.
Show resolved Hide resolved
let to_push = if self.current_bbidx_label.is_none() {
s.clone()
} else {
s.clone().with_label(self.current_bbidx_label.clone().unwrap())
};
self.current_bbidx_label = None;

match s.body() {
StmtBody::Assign { lhs, rhs: _ } => {
let lhs_stmt = lhs.clone().as_stmt(*s.location());
self.loop_invariant_lhs = Some(lhs_stmt.clone());
self.invariants_block.push(to_push);
}
_ => {
self.invariants_block.push(to_push);
}
};
Stmt::skip(*s.location())
} else {
s
}
}

pub fn enter_loop_invariant_block(&mut self) {
assert!(self.invariants_block.is_empty());
self.stage = LoopContractsStage::InvariantBlock;
}

pub fn exit_loop_invariant_block(&mut self) {
self.stage = LoopContractsStage::FindingLatchNode;
}

/// Enter a new function, reset the seen_bbidx set
pub fn enter_new_function(&mut self) {
self.seen_bbidx = HashSet::new()
}

pub fn add_new_seen_bbidx(&mut self, bbidx: BasicBlockIdx) {
self.seen_bbidx.insert(bbidx);
self.current_bbidx_label = Some(bb_label(bbidx));
}
}
1 change: 1 addition & 0 deletions kani-compiler/src/codegen_cprover_gotoc/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

mod current_fn;
mod goto_ctx;
mod loop_contracts_ctx;
mod vtable_ctx;

pub use goto_ctx::GotocCtx;
Expand Down
3 changes: 3 additions & 0 deletions kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! It would be too nasty if we spread around these sort of undocumented hooks in place, so
//! this module addresses this issue.

use super::loop_contracts_hooks::{LoopInvariantBegin, LoopInvariantEnd};
use crate::codegen_cprover_gotoc::codegen::{bb_label, PropertyClass};
use crate::codegen_cprover_gotoc::GotocCtx;
use crate::kani_middle::attributes::matches_diagnostic as matches_function;
Expand Down Expand Up @@ -555,6 +556,8 @@ pub fn fn_hooks() -> GotocHooks {
Rc::new(MemCmp),
Rc::new(UntrackedDeref),
Rc::new(InitContracts),
Rc::new(LoopInvariantBegin),
Rc::new(LoopInvariantEnd),
],
}
}
Expand Down
Loading
Loading