Skip to content

Add initial implementation of the reachability algorithm #1683

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

Merged
merged 12 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
63 changes: 51 additions & 12 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use cbmc::InternString;
use kani_metadata::HarnessMetadata;
use rustc_ast::ast;
use rustc_ast::{Attribute, LitKind};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::{HasLocalDecls, Local};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, Instance};
Expand Down Expand Up @@ -262,14 +264,47 @@ impl<'tcx> GotocCtx<'tcx> {
self.reset_current_fn();
}

/// This updates the goto context with any information that should be accumulated from a function's
/// attributes.
///
/// Handle all attributes i.e. `#[kani::x]` (which kani_macros translates to `#[kanitool::x]` for us to handle here)
fn handle_kanitool_attributes(&mut self) {
let all_attributes = self.tcx.get_attrs_unchecked(self.current_fn().instance().def_id());
pub fn is_proof_harness(&self, def_id: DefId) -> bool {
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
let (proof_attributes, _) = partition_kanitool_attributes(all_attributes);
if !proof_attributes.is_empty() {
let span = proof_attributes.first().unwrap().span;
if self.tcx.def_kind(def_id) != DefKind::Fn {
self.tcx
.sess
.span_err(span, "The kani::proof attribute can only be applied to functions.");
} else if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
self.tcx
.sess
.span_err(span, "The proof attribute cannot be applied to generic functions.");
}
self.tcx.sess.abort_if_errors();
true
} else {
false
}
}

// Check that all attributes assigned to an item is valid.
pub fn check_attributes(&self, def_id: DefId) {
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
let (proof_attributes, other_attributes) = partition_kanitool_attributes(all_attributes);
if proof_attributes.is_empty() && !other_attributes.is_empty() {
if !proof_attributes.is_empty() {
let span = proof_attributes.first().unwrap().span;
if self.tcx.def_kind(def_id) != DefKind::Fn {
self.tcx
.sess
.span_err(span, "The kani::proof attribute can only be applied to functions.");
} else if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
self.tcx
.sess
.span_err(span, "The proof attribute cannot be applied to generic functions.");
} else if proof_attributes.len() > 1 {
self.tcx
.sess
.span_warn(proof_attributes[0].span, "Only one '#[kani::proof]' allowed");
}
} else if !other_attributes.is_empty() {
self.tcx.sess.span_err(
other_attributes[0].1.span,
format!(
Expand All @@ -278,12 +313,16 @@ impl<'tcx> GotocCtx<'tcx> {
)
.as_str(),
);
return;
}
if proof_attributes.len() > 1 {
// No return because this only requires a warning
self.tcx.sess.span_warn(proof_attributes[0].span, "Only one '#[kani::proof]' allowed");
}
}

/// This updates the goto context with any information that should be accumulated from a function's
/// attributes.
///
/// Handle all attributes i.e. `#[kani::x]` (which kani_macros translates to `#[kanitool::x]` for us to handle here)
fn handle_kanitool_attributes(&mut self) {
let all_attributes = self.tcx.get_attrs_unchecked(self.current_fn().instance().def_id());
let (proof_attributes, other_attributes) = partition_kanitool_attributes(all_attributes);
if !proof_attributes.is_empty() {
self.create_proof_harness(other_attributes);
}
Expand Down
228 changes: 140 additions & 88 deletions kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

//! This file contains the code necessary to interface with the compiler backend

use crate::codegen_cprover_gotoc::reachability::{collect_reachable_items, filter_crate_items};
use crate::codegen_cprover_gotoc::GotocCtx;
use bitflags::_core::any::Any;
use cbmc::goto_program::{symtab_transformer, Location};
use cbmc::InternedString;
use cbmc::{InternedString, MachineModel};
use kani_metadata::KaniMetadata;
use kani_queries::{QueryDb, ReachabilityType, UserInput};
use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_codegen_ssa::{CodegenResults, CrateInfo};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_metadata::EncodedMetadata;
use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
use rustc_middle::mir::mono::{CodegenUnit, MonoItem};
Expand Down Expand Up @@ -51,104 +53,92 @@ impl CodegenBackend for GotocCodegenBackend {

fn provide_extern(&self, _providers: &mut ty::query::ExternProviders) {}

fn codegen_crate<'tcx>(
fn codegen_crate(
&self,
tcx: TyCtxt<'tcx>,
tcx: TyCtxt,
rustc_metadata: EncodedMetadata,
need_metadata_module: bool,
) -> Box<dyn Any> {
super::utils::init();

// Follow rustc naming convention (cx is abbrev for context).
// https://rustc-dev-guide.rust-lang.org/conventions.html#naming-conventions
let mut gcx = GotocCtx::new(tcx, self.queries.clone());
check_target(tcx.sess);
check_options(tcx.sess, need_metadata_module, self.queries.clone());
check_options(tcx.sess, need_metadata_module);
check_crate_items(&gcx);

let codegen_units: &'tcx [CodegenUnit<'_>] = tcx.collect_and_partition_mono_items(()).1;
let mut c = GotocCtx::new(tcx, self.queries.clone());
let items = collect_codegen_items(&gcx);
if items.is_empty() {
// There's nothing to do.
return codegen_results(tcx, rustc_metadata, gcx.symbol_table.machine_model());
}

// we first declare all functions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declare all "items"

for cgu in codegen_units {
let items = cgu.items_in_deterministic_order(tcx);
for (item, _) in items {
match item {
MonoItem::Fn(instance) => {
c.call_with_panic_debug_info(
|ctx| ctx.declare_function(instance),
format!("declare_function: {}", c.readable_instance_name(instance)),
instance.def_id(),
);
}
MonoItem::Static(def_id) => {
c.call_with_panic_debug_info(
|ctx| ctx.declare_static(def_id, item),
format!("declare_static: {:?}", def_id),
def_id,
);
}
MonoItem::GlobalAsm(_) => {
if !self.queries.get_ignore_global_asm() {
let error_msg = format!(
"Crate {} contains global ASM, which is not supported by Kani. Rerun with `--enable-unstable --ignore-global-asm` to suppress this error (**Verification results may be impacted**).",
c.short_crate_name()
);
tcx.sess.err(&error_msg);
} else {
warn!(
"Ignoring global ASM in crate {}. Verification results may be impacted.",
c.short_crate_name()
);
}
}
for item in &items {
match *item {
MonoItem::Fn(instance) => {
gcx.call_with_panic_debug_info(
|ctx| ctx.declare_function(instance),
format!("declare_function: {}", gcx.readable_instance_name(instance)),
instance.def_id(),
);
}
MonoItem::Static(def_id) => {
gcx.call_with_panic_debug_info(
|ctx| ctx.declare_static(def_id, *item),
format!("declare_static: {:?}", def_id),
def_id,
);
}
MonoItem::GlobalAsm(_) => {} // Ignore this. We have already warned about it.
}
}

// then we move on to codegen
for cgu in codegen_units {
let items = cgu.items_in_deterministic_order(tcx);
for (item, _) in items {
match item {
MonoItem::Fn(instance) => {
c.call_with_panic_debug_info(
|ctx| ctx.codegen_function(instance),
format!(
"codegen_function: {}\n{}",
c.readable_instance_name(instance),
c.symbol_name(instance)
),
instance.def_id(),
);
}
MonoItem::Static(def_id) => {
c.call_with_panic_debug_info(
|ctx| ctx.codegen_static(def_id, item),
format!("codegen_static: {:?}", def_id),
def_id,
);
}
MonoItem::GlobalAsm(_) => {} // We have already warned above
for item in items {
match item {
MonoItem::Fn(instance) => {
gcx.call_with_panic_debug_info(
|ctx| ctx.codegen_function(instance),
format!(
"codegen_function: {}\n{}",
gcx.readable_instance_name(instance),
gcx.symbol_name(instance)
),
instance.def_id(),
);
}
MonoItem::Static(def_id) => {
gcx.call_with_panic_debug_info(
|ctx| ctx.codegen_static(def_id, item),
format!("codegen_static: {:?}", def_id),
def_id,
);
}
MonoItem::GlobalAsm(_) => {} // We have already warned above
}
}

// Print compilation report.
print_report(&c, tcx);
print_report(&gcx, tcx);

// perform post-processing symbol table passes
let passes = self.queries.get_symbol_table_passes();
let symtab = symtab_transformer::do_passes(c.symbol_table, &passes);
let symtab = symtab_transformer::do_passes(gcx.symbol_table, &passes);

// Map MIR types to GotoC types
let type_map: BTreeMap<InternedString, InternedString> =
BTreeMap::from_iter(c.type_map.into_iter().map(|(k, v)| (k, v.to_string().into())));
BTreeMap::from_iter(gcx.type_map.into_iter().map(|(k, v)| (k, v.to_string().into())));

// Get the vtable function pointer restrictions if requested
let vtable_restrictions = if c.vtable_ctx.emit_vtable_restrictions {
Some(c.vtable_ctx.get_virtual_function_restrictions())
let vtable_restrictions = if gcx.vtable_ctx.emit_vtable_restrictions {
Some(gcx.vtable_ctx.get_virtual_function_restrictions())
} else {
None
};

let metadata = KaniMetadata { proof_harnesses: c.proof_harnesses };
let metadata = KaniMetadata { proof_harnesses: gcx.proof_harnesses };

// No output should be generated if user selected no_codegen.
if !tcx.sess.opts.unstable_opts.no_codegen && tcx.sess.opts.output_types.should_codegen() {
Expand All @@ -163,18 +153,7 @@ impl CodegenBackend for GotocCodegenBackend {
write_file(&base_filename, "restrictions.json", &restrictions, pretty);
}
}

let work_products = FxHashMap::<WorkProductId, WorkProduct>::default();
Box::new((
CodegenResults {
modules: vec![],
allocator_module: None,
metadata_module: None,
metadata: rustc_metadata,
crate_info: CrateInfo::new(tcx, symtab.machine_model().architecture.clone()),
},
work_products,
))
codegen_results(tcx, rustc_metadata, symtab.machine_model())
}

fn join_codegen(
Expand Down Expand Up @@ -246,7 +225,7 @@ fn check_target(session: &Session) {
session.abort_if_errors();
}

fn check_options(session: &Session, need_metadata_module: bool, queries: Rc<QueryDb>) {
fn check_options(session: &Session, need_metadata_module: bool) {
// The requirements for `min_global_align` and `endian` are needed to build
// a valid CBMC machine model in function `machine_model_from_session` from
// src/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs
Expand Down Expand Up @@ -281,17 +260,37 @@ fn check_options(session: &Session, need_metadata_module: bool, queries: Rc<Quer
session.err("Kani cannot generate metadata module.");
}

if queries.get_reachability_analysis() != ReachabilityType::Legacy {
let err_msg = format!(
"Using {} reachability mode is still unsupported.",
queries.get_reachability_analysis().as_ref()
);
session.err(&err_msg);
}

session.abort_if_errors();
}

/// Check that all crate items are supported and there's no misconfiguration.
/// This method will exhaustively print any error / warning and it will abort at the end if any
/// error was found.
fn check_crate_items(gcx: &GotocCtx) {
let tcx = gcx.tcx;
for item in tcx.hir_crate_items(()).items() {
let def_id = item.def_id.to_def_id();
gcx.check_attributes(def_id);
if tcx.def_kind(def_id) == DefKind::GlobalAsm {
if !gcx.queries.get_ignore_global_asm() {
let error_msg = format!(
"Crate {} contains global ASM, which is not supported by Kani. Rerun with \
`--enable-unstable --ignore-global-asm` to suppress this error \
(**Verification results may be impacted**).",
gcx.short_crate_name()
);
tcx.sess.err(&error_msg);
} else {
warn!(
"Ignoring global ASM in crate {}. Verification results may be impacted.",
gcx.short_crate_name()
);
}
}
}
tcx.sess.abort_if_errors();
}

fn write_file<T>(base_filename: &Path, extension: &str, source: &T, pretty: bool)
where
T: serde::Serialize,
Expand Down Expand Up @@ -327,3 +326,56 @@ fn print_report<'tcx>(ctx: &GotocCtx, tcx: TyCtxt<'tcx>) {
tcx.sess.warn(&msg);
}
}

/// Return a struct that contains information about the codegen results as expected by `rustc`.
fn codegen_results(
tcx: TyCtxt,
rustc_metadata: EncodedMetadata,
machine: &MachineModel,
) -> Box<dyn Any> {
let work_products = FxHashMap::<WorkProductId, WorkProduct>::default();
Box::new((
CodegenResults {
modules: vec![],
allocator_module: None,
metadata_module: None,
metadata: rustc_metadata,
crate_info: CrateInfo::new(tcx, machine.architecture.clone()),
},
work_products,
))
}

/// Retrieve all items that need to be processed.
fn collect_codegen_items<'tcx>(gcx: &GotocCtx<'tcx>) -> Vec<MonoItem<'tcx>> {
let tcx = gcx.tcx;
let reach = gcx.queries.get_reachability_analysis();
debug!(?reach, "starting_points");
match reach {
ReachabilityType::Legacy => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this does no slicing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well.. this slice on the public items and it is not cross crate.

// Use rustc monomorphizer to retrieve items to codegen.
let codegen_units: &'tcx [CodegenUnit<'_>] = tcx.collect_and_partition_mono_items(()).1;
codegen_units
.iter()
.flat_map(|cgu| cgu.items_in_deterministic_order(tcx))
.map(|(item, _)| item)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to run uniq or its equivalent on this? I recall that we can get the same item more than once in this approach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our offline conversation, I'll use uniq here and remove this code:

if old_sym.is_function_definition() {
tracing::info!("Double codegen of {:?}", old_sym);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find any std method to do this. Is it OK if I leave this as is for now, since the new mir_linker doesn't have this issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

.collect()
}
ReachabilityType::Harnesses => {
// Cross-crate collecting of all items that are reachable from the crate harnesses.
let harnesses = filter_crate_items(tcx, |_, def_id| gcx.is_proof_harness(def_id));
collect_reachable_items(tcx, &harnesses).into_iter().collect()
}
ReachabilityType::None => Vec::new(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope... this is needed to build dependencies. You can see more details here: https://model-checking.github.io/kani/rfc/rfcs/0001-mir-linker.html#cross-crate-reachability-analysis

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment that?

ReachabilityType::PubFns => {
// TODO: https://github.com/model-checking/kani/issues/1674
let err_msg = format!(
"Using {} reachability mode is still unsupported.",
ReachabilityType::PubFns.as_ref()
);
tcx.sess.err(&err_msg);
tcx.sess.abort_if_errors();
unreachable!("Session should've been aborted")
}
}
}
Loading