From e5fbbf5e6d59aa5885ddc6c4d8d27a986d6ab2e1 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 27 Oct 2023 19:49:04 -0700 Subject: [PATCH 1/8] Add initial reachability based on SMIR Keep this under a feature for now until we can assess perfomance. This is still not working: - Stub error handling --- kani-compiler/Cargo.toml | 1 + kani-compiler/src/kani_middle/mod.rs | 6 + kani-compiler/src/kani_middle/reachability.rs | 4 +- .../src/kani_middle/reachability_smir.rs | 603 ++++++++++++++++++ 4 files changed, 612 insertions(+), 2 deletions(-) create mode 100644 kani-compiler/src/kani_middle/reachability_smir.rs diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 912976218e83..61226d89b702 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -30,6 +30,7 @@ tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt" default = ['cprover'] cprover = ['cbmc', 'num', 'serde'] write_json_symtab = [] +stable_mir = [] [package.metadata.rust-analyzer] # This package uses rustc crates. diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index 0aec0c448219..99a7cec47fdc 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -33,10 +33,16 @@ pub mod coercion; mod intrinsics; pub mod metadata; pub mod provide; +#[cfg(not(feature = "stable_mir"))] pub mod reachability; +#[cfg(feature = "stable_mir")] +pub mod reachability_smir; pub mod resolve; pub mod stubbing; +#[cfg(feature = "stable_mir")] +pub use reachability_smir as reachability; + /// 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. diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 35ba3d6229f4..1d0c1c2e1eaf 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -216,8 +216,8 @@ impl<'tcx> MonoItemsCollector<'tcx> { // Collect initialization. let alloc = self.tcx.eval_static_initializer(def_id).unwrap(); - for id in alloc.inner().provenance().provenances() { - next_items.extend(collect_alloc_items(self.tcx, id).iter()); + for id in alloc.inner().provenance().ptrs().values() { + next_items.extend(collect_alloc_items(self.tcx, *id).iter()); } next_items diff --git a/kani-compiler/src/kani_middle/reachability_smir.rs b/kani-compiler/src/kani_middle/reachability_smir.rs new file mode 100644 index 000000000000..cbdf0908a11e --- /dev/null +++ b/kani-compiler/src/kani_middle/reachability_smir.rs @@ -0,0 +1,603 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module implements a cross-crate collector that allow us to find all items that +//! should be included in order to verify one or more proof harness. +//! +//! This module works as following: +//! - Traverse all reachable items starting at the given starting points. +//! - For every function, traverse its body and collect the following: +//! - Constants / Static objects. +//! - Functions that are called or have their address taken. +//! - VTable methods for types that are coerced as unsized types. +//! - For every static, collect initializer and drop functions. +//! +//! We have kept this module agnostic of any Kani code in case we can contribute this back to rustc. +//! +//! Note that this is a copy of `reachability.rs` that uses StableMIR but the public APIs are still +//! kept with internal APIs. +use tracing::{debug, debug_span, trace}; + +use rustc_data_structures::fingerprint::Fingerprint; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_hir::def_id::DefId; +use rustc_middle::mir::mono::MonoItem as InternalMonoItem; +use rustc_middle::ty::{TyCtxt, VtblEntry}; +use rustc_smir::rustc_internal; +use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; +use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; +use stable_mir::mir::{ + visit::Location, Body, CastKind, Constant, MirVisitor, PointerCoercion, Rvalue, Terminator, + TerminatorKind, +}; +use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; +use stable_mir::{self, CrateItem}; + +use crate::kani_middle::coercion; +use crate::kani_middle::coercion::CoercionBase; + +/// Collect all reachable items starting from the given starting points. +pub fn collect_reachable_items<'tcx>( + tcx: TyCtxt<'tcx>, + starting_points: &[InternalMonoItem<'tcx>], +) -> Vec> { + rustc_smir::rustc_internal::run(tcx, || { + // For each harness, collect items using the same collector. + // I.e.: This will return any item that is reachable from one or more of the starting points. + let mut collector = MonoItemsCollector::new(tcx); + for item in starting_points { + collector.collect(rustc_internal::stable(item)); + } + + #[cfg(disable_debug_assertions)] + collector + .call_graph + .dump_dot(tcx) + .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); + + tcx.sess.abort_if_errors(); + + // Sort the result so code generation follows deterministic order. + // This helps us to debug the code, but it also provides the user a good experience since the + // order of the errors and warnings is stable. + let mut sorted_items: Vec<_> = + collector.collected.iter().map(rustc_internal::internal).collect(); + sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); + sorted_items + }) + .unwrap() +} + +/// Collect all (top-level) items in the crate that matches the given predicate. +/// An item can only be a root if they are: non-generic Fn / Static / GlobalASM +pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec +where + F: Fn(TyCtxt, DefId) -> bool, +{ + rustc_smir::rustc_internal::run(tcx, || { + let crate_items = stable_mir::all_local_items(); + // Filter regular items. + crate_items + .iter() + .filter_map(|item| { + // Only collect monomorphic items. + Instance::try_from(*item) + .ok() + .map(|instance| { + let def_id = rustc_internal::internal(item); + predicate(tcx, def_id) + .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) + }) + .flatten() + }) + .collect::>() + }) + .unwrap() +} + +/// Use a predicate to find `const` declarations, then extract all items reachable from them. +/// +/// Probably only specifically useful with a predicate to find `TestDescAndFn` const declarations from +/// tests and extract the closures from them. +pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec +where + F: FnMut(TyCtxt, DefId) -> bool, +{ + rustc_smir::rustc_internal::run(tcx, || { + let crate_items = stable_mir::all_local_items(); + let mut roots = Vec::new(); + // Filter regular items. + for item in crate_items { + // Only collect monomorphic items. + if let Ok(instance) = Instance::try_from(item) { + let def_id = rustc_internal::internal(&item); + if predicate(tcx, def_id) { + let body = instance.body().unwrap(); + let mut collector = + MonoItemsFnCollector { tcx, body: &body, collected: FxHashSet::default() }; + collector.visit_body(&body); + roots.extend(collector.collected.iter().map(rustc_internal::internal)); + } + } + } + roots + }) + .unwrap() +} + +struct MonoItemsCollector<'tcx> { + /// The compiler context. + tcx: TyCtxt<'tcx>, + /// Set of collected items used to avoid entering recursion loops. + collected: FxHashSet, + /// Items enqueued for visiting. + queue: Vec, + #[cfg(disable_debug_assertions)] + call_graph: debug::CallGraph, +} + +// TODO: Need to figure out how to handle stubbing errors. +impl<'tcx> MonoItemsCollector<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>) -> Self { + MonoItemsCollector { + tcx, + collected: FxHashSet::default(), + queue: vec![], + #[cfg(disable_debug_assertions)] + call_graph: debug::CallGraph::default(), + } + } + + /// Collects all reachable items starting from the given root. + pub fn collect(&mut self, root: MonoItem) { + debug!(?root, "collect"); + self.queue.push(root); + self.reachable_items(); + } + + /// Traverses the call graph starting from the given root. For every function, we visit all + /// instruction looking for the items that should be included in the compilation. + fn reachable_items(&mut self) { + while let Some(to_visit) = self.queue.pop() { + if !self.collected.contains(&to_visit) { + self.collected.insert(to_visit.clone()); + let next_items = match &to_visit { + MonoItem::Fn(instance) => self.visit_fn(*instance), + MonoItem::Static(static_def) => self.visit_static(*static_def), + MonoItem::GlobalAsm(_) => { + self.visit_asm(to_visit); + vec![] + } + }; + #[cfg(disable_debug_assertions)] + self.call_graph.add_edges(to_visit, &next_items); + + self.queue + .extend(next_items.into_iter().filter(|item| !self.collected.contains(item))); + } + } + } + + /// Visit a function and collect all mono-items reachable from its instructions. + fn visit_fn(&mut self, instance: Instance) -> Vec { + let _guard = debug_span!("visit_fn", function=?instance).entered(); + let body = instance.body().unwrap(); + let mut collector = + MonoItemsFnCollector { tcx: self.tcx, collected: FxHashSet::default(), body: &body }; + collector.visit_body(&body); + collector.collected.into_iter().collect() + } + + /// Visit a static object and collect drop / initialization functions. + fn visit_static(&mut self, def: StaticDef) -> Vec { + let _guard = debug_span!("visit_static", ?def).entered(); + let mut next_items = vec![]; + + // Collect drop function. + let static_ty = def.ty(); + let instance = Instance::resolve_drop_in_place(static_ty); + next_items.push(instance.into()); + + // Collect initialization. + let alloc = def.eval_initializer().unwrap(); + for (_, prov) in alloc.provenance.ptrs { + next_items.extend(collect_alloc_items(prov.0).into_iter()); + } + + next_items + } + + /// Visit global assembly and collect its item. + fn visit_asm(&mut self, item: MonoItem) { + debug!(?item, "visit_asm"); + } +} + +struct MonoItemsFnCollector<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + collected: FxHashSet, + body: &'a Body, +} + +impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { + /// Collect the implementation of all trait methods and its supertrait methods for the given + /// concrete type. + fn collect_vtable_methods(&mut self, concrete_ty: Ty, trait_ty: Ty) { + trace!(?concrete_ty, ?trait_ty, "collect_vtable_methods"); + let concrete_kind = concrete_ty.kind(); + let trait_kind = trait_ty.kind(); + + assert!(!concrete_kind.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); + assert!(trait_kind.is_trait(), "Expected a trait: {trait_ty:?}"); + if let Some(principal) = trait_kind.trait_principal() { + // A trait object type can have multiple trait bounds but up to one non-auto-trait + // bound. This non-auto-trait, named principal, is the only one that can have methods. + // https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits + let poly_trait_ref = principal.with_self_ty(concrete_ty); + + // Walk all methods of the trait, including those of its supertraits + let entries = self.tcx.vtable_entries(rustc_internal::internal(&poly_trait_ref)); + let methods = entries.iter().filter_map(|entry| match entry { + VtblEntry::MetadataAlign + | VtblEntry::MetadataDropInPlace + | VtblEntry::MetadataSize + | VtblEntry::Vacant => None, + VtblEntry::TraitVPtr(_) => { + // all super trait items already covered, so skip them. + None + } + VtblEntry::Method(instance) => { + let instance = rustc_internal::stable(instance); + should_codegen_locally(&instance).then_some(MonoItem::Fn(instance)) + } + }); + trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); + self.collected.extend(methods); + } + + // Add the destructor for the concrete type. + let instance = Instance::resolve_drop_in_place(concrete_ty); + self.collect_instance(instance, false); + } + + /// Collect an instance depending on how it is used (invoked directly or via fn_ptr). + fn collect_instance(&mut self, instance: Instance, is_direct_call: bool) { + let should_collect = match instance.kind { + InstanceKind::Virtual | InstanceKind::Intrinsic => { + // Instance definition has no body. + assert!(is_direct_call, "Expected direct call {instance:?}"); + false + } + InstanceKind::Shim | InstanceKind::Item => true, + }; + if should_collect && should_codegen_locally(&instance) { + trace!(?instance, "collect_instance"); + self.collected.insert(instance.into()); + } + } + + /// Collect constant values represented by static variables. + fn collect_allocation(&mut self, alloc: &Allocation) { + debug!(?alloc, "collect_allocation"); + for (_, id) in &alloc.provenance.ptrs { + self.collected.extend(collect_alloc_items(id.0).into_iter()) + } + } +} + +/// Visit every instruction in a function and collect the following: +/// 1. Every function / method / closures that may be directly invoked. +/// 2. Every function / method / closures that may have their address taken. +/// 3. Every method that compose the impl of a trait for a given type when there's a conversion +/// from the type to the trait. +/// - I.e.: If we visit the following code: +/// ``` +/// let var = MyType::new(); +/// let ptr : &dyn MyTrait = &var; +/// ``` +/// We collect the entire implementation of `MyTrait` for `MyType`. +/// 4. Every Static variable that is referenced in the function or constant used in the function. +/// 5. Drop glue. +/// 6. Static Initialization +/// This code has been mostly taken from `rustc_monomorphize::collector::MirNeighborCollector`. +impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { + /// Collect the following: + /// - Trait implementations when casting from concrete to dyn Trait. + /// - Functions / Closures that have their address taken. + /// - Thread Local. + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { + trace!(rvalue=?*rvalue, "visit_rvalue"); + + match *rvalue { + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::Unsize), + ref operand, + target, + ) => { + // Check if the conversion include casting a concrete type to a trait type. + // If so, collect items from the impl `Trait for Concrete {}`. + let target_ty = target; + let source_ty = operand.ty(self.body.locals()).unwrap(); + let (src_ty, dst_ty) = extract_unsize_coercion(self.tcx, source_ty, target_ty); + if !src_ty.kind().is_trait() && dst_ty.kind().is_trait() { + debug!(?src_ty, ?dst_ty, "collect_vtable_methods"); + self.collect_vtable_methods(src_ty, dst_ty); + } + } + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer), + ref operand, + _, + ) => { + let fn_kind = operand.ty(self.body.locals()).unwrap().kind(); + if let RigidTy::FnDef(fn_def, args) = fn_kind.rigid().unwrap() { + let instance = Instance::resolve_for_fn_ptr(*fn_def, args).unwrap(); + self.collect_instance(instance, false); + } else { + unreachable!("Expected FnDef type, but got: {:?}", fn_kind); + } + } + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)), + ref operand, + _, + ) => { + let source_ty = operand.ty(self.body.locals()).unwrap(); + match source_ty.kind().rigid().unwrap() { + RigidTy::Closure(def_id, args) => { + let instance = + Instance::resolve_closure(*def_id, args, ClosureKind::FnOnce) + .expect("failed to normalize and resolve closure during codegen"); + self.collect_instance(instance, false); + } + _ => unreachable!("Unexpected type: {:?}", source_ty), + } + } + Rvalue::ThreadLocalRef(item) => { + trace!(?item, "visit_rvalue thread_local"); + self.collected.insert(MonoItem::Static(StaticDef::try_from(item).unwrap())); + } + _ => { /* not interesting */ } + } + + self.super_rvalue(rvalue, location); + } + + /// Collect constants that are represented as static variables. + fn visit_constant(&mut self, constant: &Constant, location: Location) { + debug!(?constant, ?location, literal=?constant.literal, "visit_constant"); + let allocation = match constant.literal.kind() { + ConstantKind::Allocated(allocation) => allocation, + ConstantKind::Unevaluated(_) => { + unreachable!("Instance with polymorphic constant: `{constant:?}`") + } + ConstantKind::Param(_) => unreachable!("Unexpected parameter constant: {constant:?}"), + ConstantKind::ZeroSized => { + // Nothing to do here. + return; + } + }; + self.collect_allocation(&allocation); + } + + /// Collect function calls. + fn visit_terminator(&mut self, terminator: &Terminator, location: Location) { + trace!(?terminator, ?location, "visit_terminator"); + + match terminator.kind { + TerminatorKind::Call { ref func, args: ref _outer_args, .. } => { + let fn_ty = func.ty(self.body.locals()).unwrap(); + if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { + let instance_opt = Instance::resolve(fn_def, &args).ok(); + match instance_opt { + None => { + todo!("stubbing error. see original.."); + } + Some(instance) => self.collect_instance(instance, true), + }; + } else { + assert!( + matches!(fn_ty.kind().rigid(), Some(RigidTy::FnPtr(..))), + "Unexpected type: {fn_ty:?}" + ); + } + } + TerminatorKind::Drop { ref place, .. } => { + let place_ty = place.ty(self.body.locals()).unwrap(); + let instance = Instance::resolve_drop_in_place(place_ty); + self.collect_instance(instance, true); + } + TerminatorKind::InlineAsm { .. } => { + // We don't support inline assembly. This shall be replaced by an unsupported + // construct during codegen. + } + TerminatorKind::Abort { .. } | TerminatorKind::Assert { .. } => { + // We generate code for this without invoking any lang item. + } + TerminatorKind::CoroutineDrop { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Resume + | TerminatorKind::Return + | TerminatorKind::Unreachable => {} + } + + self.super_terminator(terminator, location); + } +} + +fn extract_unsize_coercion(tcx: TyCtxt, orig_ty: Ty, dst_trait: Ty) -> (Ty, Ty) { + let CoercionBase { src_ty, dst_ty } = coercion::extract_unsize_casting( + tcx, + rustc_internal::internal(orig_ty), + rustc_internal::internal(dst_trait), + ); + (rustc_internal::stable(src_ty), rustc_internal::stable(dst_ty)) +} + +/// Convert a `MonoItem` into a stable `Fingerprint` which can be used as a stable hash across +/// compilation sessions. This allow us to provide a stable deterministic order to codegen. +fn to_fingerprint(tcx: TyCtxt, item: &InternalMonoItem) -> Fingerprint { + tcx.with_stable_hashing_context(|mut hcx| { + let mut hasher = StableHasher::new(); + item.hash_stable(&mut hcx, &mut hasher); + hasher.finish() + }) +} + +/// Return whether we should include the item into codegen. +fn should_codegen_locally<'tcx>(instance: &Instance) -> bool { + !instance.is_foreign_item() +} + +fn collect_alloc_items(alloc_id: AllocId) -> Vec { + trace!(?alloc_id, "collect_alloc_items"); + let mut items = vec![]; + match GlobalAlloc::from(alloc_id) { + GlobalAlloc::Static(def) => { + // This differ from rustc's collector since rustc does not include static from + // upstream crates. + let instance = Instance::try_from(CrateItem::from(def)).unwrap(); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def))); + } + GlobalAlloc::Function(instance) => { + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(instance))); + } + GlobalAlloc::Memory(alloc) => { + items.extend( + alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(prov.0)), + ); + } + vtable_alloc @ GlobalAlloc::VTable(..) => { + let vtable_id = vtable_alloc.vtable_allocation().unwrap(); + items = collect_alloc_items(vtable_id); + } + }; + items +} + +#[cfg(debug_assertions)] +mod debug { + #![allow(dead_code)] + + use std::fmt::{Display, Formatter}; + use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::{BufWriter, Write}, + }; + + use rustc_session::config::OutputType; + + use super::*; + + #[derive(Debug, Default)] + pub struct CallGraph { + // Nodes of the graph. + nodes: HashSet, + edges: HashMap>, + back_edges: HashMap>, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash)] + struct Node(pub MonoItem); + + impl CallGraph { + pub fn add_node(&mut self, item: MonoItem) { + let node = Node(item); + self.nodes.insert(node.clone()); + self.edges.entry(node.clone()).or_default(); + self.back_edges.entry(node).or_default(); + } + + /// Add a new edge "from" -> "to". + pub fn add_edge(&mut self, from: MonoItem, to: MonoItem) { + let from_node = Node(from.clone()); + let to_node = Node(to.clone()); + self.add_node(from); + self.add_node(to); + self.edges.get_mut(&from_node).unwrap().push(to_node.clone()); + self.back_edges.get_mut(&to_node).unwrap().push(from_node); + } + + /// Add multiple new edges for the "from" node. + pub fn add_edges(&mut self, from: MonoItem, to: &[MonoItem]) { + self.add_node(from.clone()); + for item in to { + self.add_edge(from.clone(), item.clone()); + } + } + + /// Print the graph in DOT format to a file. + /// See for more information. + pub fn dump_dot(&self, tcx: TyCtxt) -> std::io::Result<()> { + if let Ok(target) = std::env::var("KANI_REACH_DEBUG") { + debug!(?target, "dump_dot"); + let outputs = tcx.output_filenames(()); + let path = outputs.output_path(OutputType::Metadata).with_extension("dot"); + let out_file = File::create(path)?; + let mut writer = BufWriter::new(out_file); + writeln!(writer, "digraph ReachabilityGraph {{")?; + if target.is_empty() { + self.dump_all(&mut writer)?; + } else { + // Only dump nodes that led the reachability analysis to the target node. + self.dump_reason(&mut writer, &target)?; + } + writeln!(writer, "}}")?; + } + + Ok(()) + } + + /// Write all notes to the given writer. + fn dump_all(&self, writer: &mut W) -> std::io::Result<()> { + tracing::info!(nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_all"); + for node in &self.nodes { + writeln!(writer, r#""{node}""#)?; + for succ in self.edges.get(node).unwrap() { + writeln!(writer, r#""{node}" -> "{succ}" "#)?; + } + } + Ok(()) + } + + /// Write all notes that may have led to the discovery of the given target. + fn dump_reason(&self, writer: &mut W, target: &str) -> std::io::Result<()> { + let mut queue = self + .nodes + .iter() + .filter(|item| item.to_string().contains(target)) + .collect::>(); + let mut visited: HashSet<&Node> = HashSet::default(); + tracing::info!(target=?queue, nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_reason"); + while let Some(to_visit) = queue.pop() { + if !visited.contains(to_visit) { + visited.insert(to_visit); + queue.extend(self.back_edges.get(to_visit).unwrap()); + } + } + + for node in &visited { + writeln!(writer, r#""{node}""#)?; + for succ in + self.edges.get(node).unwrap().iter().filter(|item| visited.contains(item)) + { + writeln!(writer, r#""{node}" -> "{succ}" "#)?; + } + } + Ok(()) + } + } + + impl Display for Node { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + MonoItem::Fn(instance) => write!(f, "{}", instance.mangled_name()), + MonoItem::Static(def) => write!(f, "{}", CrateItem::from(*def).name()), + MonoItem::GlobalAsm(asm) => write!(f, "{asm:?}"), + } + } + } +} From 062386ccdbda72a584ecdd9b8c13c4a64f6a1e10 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 24 Nov 2023 18:47:41 -0800 Subject: [PATCH 2/8] Handle stubbing error with associated constant --- .../src/kani_middle/reachability_smir.rs | 71 ++++++++++++-- kani-compiler/src/kani_middle/stubbing/mod.rs | 94 ++++++++++++++++++- 2 files changed, 154 insertions(+), 11 deletions(-) diff --git a/kani-compiler/src/kani_middle/reachability_smir.rs b/kani-compiler/src/kani_middle/reachability_smir.rs index cbdf0908a11e..777b29e2542e 100644 --- a/kani-compiler/src/kani_middle/reachability_smir.rs +++ b/kani-compiler/src/kani_middle/reachability_smir.rs @@ -27,15 +27,18 @@ use rustc_middle::ty::{TyCtxt, VtblEntry}; use rustc_smir::rustc_internal; use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; +use stable_mir::mir::pretty::pretty_ty; use stable_mir::mir::{ visit::Location, Body, CastKind, Constant, MirVisitor, PointerCoercion, Rvalue, Terminator, TerminatorKind, }; use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; +use stable_mir::CrateDef; use stable_mir::{self, CrateItem}; use crate::kani_middle::coercion; use crate::kani_middle::coercion::CoercionBase; +use crate::kani_middle::stubbing::{get_stub, validate_instance}; /// Collect all reachable items starting from the given starting points. pub fn collect_reachable_items<'tcx>( @@ -114,8 +117,12 @@ where let def_id = rustc_internal::internal(&item); if predicate(tcx, def_id) { let body = instance.body().unwrap(); - let mut collector = - MonoItemsFnCollector { tcx, body: &body, collected: FxHashSet::default() }; + let mut collector = MonoItemsFnCollector { + tcx, + body: &body, + collected: FxHashSet::default(), + instance: &instance, + }; collector.visit_body(&body); roots.extend(collector.collected.iter().map(rustc_internal::internal)); } @@ -182,11 +189,19 @@ impl<'tcx> MonoItemsCollector<'tcx> { /// Visit a function and collect all mono-items reachable from its instructions. fn visit_fn(&mut self, instance: Instance) -> Vec { let _guard = debug_span!("visit_fn", function=?instance).entered(); - let body = instance.body().unwrap(); - let mut collector = - MonoItemsFnCollector { tcx: self.tcx, collected: FxHashSet::default(), body: &body }; - collector.visit_body(&body); - collector.collected.into_iter().collect() + if validate_instance(self.tcx, instance) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx: self.tcx, + collected: FxHashSet::default(), + body: &body, + instance: &instance, + }; + collector.visit_body(&body); + collector.collected.into_iter().collect() + } else { + vec![] + } } /// Visit a static object and collect drop / initialization functions. @@ -218,6 +233,7 @@ struct MonoItemsFnCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, collected: FxHashSet, body: &'a Body, + instance: &'a Instance, } impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { @@ -386,13 +402,50 @@ impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { trace!(?terminator, ?location, "visit_terminator"); match terminator.kind { - TerminatorKind::Call { ref func, args: ref _outer_args, .. } => { + TerminatorKind::Call { ref func, .. } => { let fn_ty = func.ty(self.body.locals()).unwrap(); if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { let instance_opt = Instance::resolve(fn_def, &args).ok(); match instance_opt { None => { - todo!("stubbing error. see original.."); + let caller = CrateItem::try_from(*self.instance).unwrap().name(); + let callee = fn_def.name(); + // Check if the current function has been stubbed. + if let Some(stub) = + get_stub(self.tcx, rustc_internal::internal(self.instance).def_id()) + { + // During the MIR stubbing transformation, we do not + // force type variables in the stub's signature to + // implement the same traits as those in the + // original function/method. A trait mismatch shows + // up here, when we try to resolve a trait method + + // FIXME: This assumes the type resolving the + // trait is the first argument, but that isn't + // necessarily true. It could be any argument or + // even the return type, for instance for a + // trait like `FromIterator`. + let receiver_ty = args.0[0].expect_ty(); + trace!(?receiver_ty, ?callee, "**** receiver:"); + let sep = callee.rfind("::").unwrap(); + let trait_ = &callee[..sep]; + self.tcx.sess.span_err( + rustc_internal::internal(terminator.span), + format!( + "`{}` doesn't implement \ + `{}`. The function `{}` \ + cannot be stubbed by `{}` due to \ + generic bounds not being met. Callee: {}", + pretty_ty(receiver_ty.kind()), + trait_, + caller, + self.tcx.def_path_str(stub), + callee, + ), + ); + } else { + panic!("unable to resolve call to `{callee}` in `{caller}`") + } } Some(instance) => self.collect_instance(instance, true), }; diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index d4a00f44cc01..21c381598b92 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -6,12 +6,19 @@ mod annotations; mod transform; use std::collections::BTreeMap; +use tracing::{debug, trace}; +pub use self::transform::*; use kani_metadata::HarnessMetadata; use rustc_hir::def_id::DefId; use rustc_hir::definitions::DefPathHash; -use rustc_middle::ty::TyCtxt; -pub use transform::*; +use rustc_middle::mir::Const; +use rustc_middle::ty::{self, EarlyBinder, ParamEnv, TyCtxt, TypeFoldable}; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::visit::{Location, MirVisitor}; +use stable_mir::mir::Constant; +use stable_mir::{CrateDef, CrateItem}; use self::annotations::update_stub_mapping; @@ -28,3 +35,86 @@ pub fn harness_stub_map( } stub_pairs } + +/// Validate that an instance body can be instantiated. +/// +/// Stubbing may cause an instance to not be correctly instantiated since we delay checking its +/// generic bounds. +/// +/// In stable mir, trying to retrieve an Instance::body() will ICE if we cannot evaluate a +/// constant as expected. For now, use internal APIs to anticipate this issue. +pub fn validate_instance(tcx: TyCtxt, instance: Instance) -> bool { + let internal_instance = rustc_internal::internal(instance); + if get_stub(tcx, internal_instance.def_id()).is_some() { + let item = CrateItem::try_from(instance).unwrap(); + let mut checker = StubConstChecker::new(tcx, internal_instance, item); + checker.visit_body(&item.body()); + checker.is_valid() + } else { + true + } +} + +struct StubConstChecker<'tcx> { + tcx: TyCtxt<'tcx>, + instance: ty::Instance<'tcx>, + source: CrateItem, + is_valid: bool, +} + +impl<'tcx> StubConstChecker<'tcx> { + fn new(tcx: TyCtxt<'tcx>, instance: ty::Instance<'tcx>, source: CrateItem) -> Self { + StubConstChecker { tcx, instance, is_valid: true, source } + } + fn monomorphize(&self, value: T) -> T + where + T: TypeFoldable>, + { + trace!(instance=?self.instance, ?value, "monomorphize"); + self.instance.instantiate_mir_and_normalize_erasing_regions( + self.tcx, + ParamEnv::reveal_all(), + EarlyBinder::bind(value), + ) + } + + fn is_valid(&self) -> bool { + self.is_valid + } +} + +impl<'tcx> MirVisitor for StubConstChecker<'tcx> { + /// Collect constants that are represented as static variables. + fn visit_constant(&mut self, constant: &Constant, location: Location) { + let const_ = self.monomorphize(rustc_internal::internal(&constant.literal)); + debug!(?constant, ?location, ?const_, "visit_constant"); + match const_ { + Const::Val(..) | Const::Ty(..) => {} + Const::Unevaluated(un_eval, _) => { + // Thread local fall into this category. + match self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None) { + // The `monomorphize` call should have evaluated that constant already. + Err(_) => { + let tcx = self.tcx; + let mono_const = &un_eval; + let implementor = match mono_const.args.as_slice() { + [one] => one.as_type().unwrap(), + _ => unreachable!(), + }; + let trait_ = tcx.trait_of_item(mono_const.def).unwrap(); + let msg = format!( + "Type `{implementor}` does not implement trait `{}`. \ + This is likely because `{}` is used as a stub but its \ + generic bounds are not being met.", + tcx.def_path_str(trait_), + self.source.name() + ); + tcx.sess.span_err(rustc_internal::internal(location.span()), msg); + self.is_valid = false; + } + _ => {} + } + } + }; + } +} From 7bdafad677318a7f3918b6916d0583c9fd0db532 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 24 Nov 2023 18:50:32 -0800 Subject: [PATCH 3/8] Make stable mir reachability default --- kani-compiler/Cargo.toml | 1 - kani-compiler/src/kani_middle/mod.rs | 6 - kani-compiler/src/kani_middle/reachability.rs | 680 +++++++----------- .../src/kani_middle/reachability_smir.rs | 656 ----------------- 4 files changed, 277 insertions(+), 1066 deletions(-) delete mode 100644 kani-compiler/src/kani_middle/reachability_smir.rs diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 61226d89b702..912976218e83 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -30,7 +30,6 @@ tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt" default = ['cprover'] cprover = ['cbmc', 'num', 'serde'] write_json_symtab = [] -stable_mir = [] [package.metadata.rust-analyzer] # This package uses rustc crates. diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index 99a7cec47fdc..0aec0c448219 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -33,16 +33,10 @@ pub mod coercion; mod intrinsics; pub mod metadata; pub mod provide; -#[cfg(not(feature = "stable_mir"))] pub mod reachability; -#[cfg(feature = "stable_mir")] -pub mod reachability_smir; pub mod resolve; pub mod stubbing; -#[cfg(feature = "stable_mir")] -pub use reachability_smir as reachability; - /// 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. diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 1d0c1c2e1eaf..36932eb67e66 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -13,146 +13,138 @@ //! - For every static, collect initializer and drop functions. //! //! We have kept this module agnostic of any Kani code in case we can contribute this back to rustc. -use rustc_span::ErrorGuaranteed; -use tracing::{debug, debug_span, trace, warn}; +//! +//! Note that this is a copy of `reachability.rs` that uses StableMIR but the public APIs are still +//! kept with internal APIs. +use tracing::{debug, debug_span, trace}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; -use rustc_hir::ItemId; -use rustc_middle::mir::interpret::{AllocId, ErrorHandled, GlobalAlloc, Scalar}; -use rustc_middle::mir::mono::MonoItem; -use rustc_middle::mir::visit::Visitor as MirVisitor; -use rustc_middle::mir::{ - Body, CastKind, Const, ConstOperand, ConstValue, Location, Rvalue, Terminator, TerminatorKind, - UnevaluatedConst, -}; -use rustc_middle::span_bug; -use rustc_middle::ty::adjustment::PointerCoercion; -use rustc_middle::ty::{ - Closure, ClosureKind, ConstKind, EarlyBinder, Instance, InstanceDef, ParamEnv, Ty, TyCtxt, - TyKind, TypeFoldable, VtblEntry, +use rustc_middle::mir::mono::MonoItem as InternalMonoItem; +use rustc_middle::ty::{TyCtxt, VtblEntry}; +use rustc_smir::rustc_internal; +use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; +use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; +use stable_mir::mir::pretty::pretty_ty; +use stable_mir::mir::{ + visit::Location, Body, CastKind, Constant, MirVisitor, PointerCoercion, Rvalue, Terminator, + TerminatorKind, }; +use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; +use stable_mir::CrateDef; +use stable_mir::{self, CrateItem}; use crate::kani_middle::coercion; -use crate::kani_middle::stubbing::get_stub; +use crate::kani_middle::coercion::CoercionBase; +use crate::kani_middle::stubbing::{get_stub, validate_instance}; /// Collect all reachable items starting from the given starting points. pub fn collect_reachable_items<'tcx>( tcx: TyCtxt<'tcx>, - starting_points: &[MonoItem<'tcx>], -) -> Vec> { - // For each harness, collect items using the same collector. - // I.e.: This will return any item that is reachable from one or more of the starting points. - let mut collector = MonoItemsCollector::new(tcx); - for item in starting_points { - collector.collect(*item); - } + starting_points: &[InternalMonoItem<'tcx>], +) -> Vec> { + rustc_smir::rustc_internal::run(tcx, || { + // For each harness, collect items using the same collector. + // I.e.: This will return any item that is reachable from one or more of the starting points. + let mut collector = MonoItemsCollector::new(tcx); + for item in starting_points { + collector.collect(rustc_internal::stable(item)); + } - #[cfg(debug_assertions)] - collector - .call_graph - .dump_dot(tcx) - .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); - - tcx.sess.abort_if_errors(); - - // Sort the result so code generation follows deterministic order. - // This helps us to debug the code, but it also provides the user a good experience since the - // order of the errors and warnings is stable. - let mut sorted_items: Vec<_> = collector.collected.into_iter().collect(); - sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); - sorted_items + #[cfg(debug_assertions)] + collector + .call_graph + .dump_dot(tcx) + .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); + + tcx.sess.abort_if_errors(); + + // Sort the result so code generation follows deterministic order. + // This helps us to debug the code, but it also provides the user a good experience since the + // order of the errors and warnings is stable. + let mut sorted_items: Vec<_> = + collector.collected.iter().map(rustc_internal::internal).collect(); + sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); + sorted_items + }) + .unwrap() } /// Collect all (top-level) items in the crate that matches the given predicate. /// An item can only be a root if they are: non-generic Fn / Static / GlobalASM -pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec +pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec where F: Fn(TyCtxt, DefId) -> bool, { - let crate_items = tcx.hir_crate_items(()); - // Filter regular items. - let root_items = crate_items.items().filter_map(|item| { - let def_id = item.owner_id.def_id.to_def_id(); - if !is_generic(tcx, def_id) && predicate(tcx, def_id) { - to_mono_root(tcx, item, def_id) - } else { - None - } - }); - - // Filter items from implementation blocks. - let impl_items = crate_items.impl_items().filter_map(|impl_item| { - let def_id = impl_item.owner_id.def_id.to_def_id(); - if matches!(tcx.def_kind(def_id), DefKind::AssocFn) - && !is_generic(tcx, def_id) - && predicate(tcx, def_id) - { - Some(MonoItem::Fn(Instance::mono(tcx, def_id))) - } else { - None - } - }); - root_items.chain(impl_items).collect() + rustc_smir::rustc_internal::run(tcx, || { + let crate_items = stable_mir::all_local_items(); + // Filter regular items. + crate_items + .iter() + .filter_map(|item| { + // Only collect monomorphic items. + Instance::try_from(*item) + .ok() + .map(|instance| { + let def_id = rustc_internal::internal(item); + predicate(tcx, def_id) + .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) + }) + .flatten() + }) + .collect::>() + }) + .unwrap() } /// Use a predicate to find `const` declarations, then extract all items reachable from them. /// /// Probably only specifically useful with a predicate to find `TestDescAndFn` const declarations from /// tests and extract the closures from them. -pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec +pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec where F: FnMut(TyCtxt, DefId) -> bool, { - let mut roots = Vec::new(); - for hir_id in tcx.hir_crate_items(()).items() { - let def_id = hir_id.owner_id.def_id.to_def_id(); - let def_kind = tcx.def_kind(def_id); - if matches!(def_kind, DefKind::Const) && predicate(tcx, def_id) { - let instance = Instance::mono(tcx, def_id); - let body = tcx.instance_mir(InstanceDef::Item(def_id)); - let mut collector = - MonoItemsFnCollector { tcx, body, instance, collected: FxHashSet::default() }; - collector.visit_body(body); - - roots.extend(collector.collected); - } - } - roots -} - -fn is_generic(tcx: TyCtxt, def_id: DefId) -> bool { - let generics = tcx.generics_of(def_id); - generics.requires_monomorphization(tcx) -} - -fn to_mono_root(tcx: TyCtxt, item_id: ItemId, def_id: DefId) -> Option { - let kind = tcx.def_kind(def_id); - match kind { - DefKind::Static(..) => Some(MonoItem::Static(def_id)), - DefKind::Fn => Some(MonoItem::Fn(Instance::mono(tcx, def_id))), - DefKind::GlobalAsm => Some(MonoItem::GlobalAsm(item_id)), - _ => { - debug!(?def_id, ?kind, "Ignored item. Not a root type."); - None + rustc_smir::rustc_internal::run(tcx, || { + let crate_items = stable_mir::all_local_items(); + let mut roots = Vec::new(); + // Filter regular items. + for item in crate_items { + // Only collect monomorphic items. + if let Ok(instance) = Instance::try_from(item) { + let def_id = rustc_internal::internal(&item); + if predicate(tcx, def_id) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx, + body: &body, + collected: FxHashSet::default(), + instance: &instance, + }; + collector.visit_body(&body); + roots.extend(collector.collected.iter().map(rustc_internal::internal)); + } + } } - } + roots + }) + .unwrap() } struct MonoItemsCollector<'tcx> { /// The compiler context. tcx: TyCtxt<'tcx>, /// Set of collected items used to avoid entering recursion loops. - collected: FxHashSet>, + collected: FxHashSet, /// Items enqueued for visiting. - queue: Vec>, + queue: Vec, #[cfg(debug_assertions)] - call_graph: debug::CallGraph<'tcx>, + call_graph: debug::CallGraph, } +// TODO: Need to figure out how to handle stubbing errors. impl<'tcx> MonoItemsCollector<'tcx> { pub fn new(tcx: TyCtxt<'tcx>) -> Self { MonoItemsCollector { @@ -163,8 +155,9 @@ impl<'tcx> MonoItemsCollector<'tcx> { call_graph: debug::CallGraph::default(), } } + /// Collects all reachable items starting from the given root. - pub fn collect(&mut self, root: MonoItem<'tcx>) { + pub fn collect(&mut self, root: MonoItem) { debug!(?root, "collect"); self.queue.push(root); self.reachable_items(); @@ -175,12 +168,12 @@ impl<'tcx> MonoItemsCollector<'tcx> { fn reachable_items(&mut self) { while let Some(to_visit) = self.queue.pop() { if !self.collected.contains(&to_visit) { - self.collected.insert(to_visit); - let next_items = match to_visit { - MonoItem::Fn(instance) => self.visit_fn(instance), - MonoItem::Static(def_id) => self.visit_static(def_id), + self.collected.insert(to_visit.clone()); + let next_items = match &to_visit { + MonoItem::Fn(instance) => self.visit_fn(*instance), + MonoItem::Static(static_def) => self.visit_static(*static_def), MonoItem::GlobalAsm(_) => { - self.visit_asm(to_visit); + self.visit_asm(&to_visit); vec![] } }; @@ -194,146 +187,117 @@ impl<'tcx> MonoItemsCollector<'tcx> { } /// Visit a function and collect all mono-items reachable from its instructions. - fn visit_fn(&mut self, instance: Instance<'tcx>) -> Vec> { + fn visit_fn(&mut self, instance: Instance) -> Vec { let _guard = debug_span!("visit_fn", function=?instance).entered(); - let body = self.tcx.instance_mir(instance.def); - let mut collector = - MonoItemsFnCollector { tcx: self.tcx, collected: FxHashSet::default(), instance, body }; - collector.visit_body(body); - collector.collected.into_iter().collect() + if validate_instance(self.tcx, instance) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx: self.tcx, + collected: FxHashSet::default(), + body: &body, + instance: &instance, + }; + collector.visit_body(&body); + collector.collected.into_iter().collect() + } else { + vec![] + } } /// Visit a static object and collect drop / initialization functions. - fn visit_static(&mut self, def_id: DefId) -> Vec> { - let _guard = debug_span!("visit_static", ?def_id).entered(); - let instance = Instance::mono(self.tcx, def_id); + fn visit_static(&mut self, def: StaticDef) -> Vec { + let _guard = debug_span!("visit_static", ?def).entered(); let mut next_items = vec![]; // Collect drop function. - let static_ty = instance.ty(self.tcx, ParamEnv::reveal_all()); - let instance = Instance::resolve_drop_in_place(self.tcx, static_ty); - next_items.push(MonoItem::Fn(instance.polymorphize(self.tcx))); + let static_ty = def.ty(); + let instance = Instance::resolve_drop_in_place(static_ty); + next_items.push(instance.into()); // Collect initialization. - let alloc = self.tcx.eval_static_initializer(def_id).unwrap(); - for id in alloc.inner().provenance().ptrs().values() { - next_items.extend(collect_alloc_items(self.tcx, *id).iter()); + let alloc = def.eval_initializer().unwrap(); + for (_, prov) in alloc.provenance.ptrs { + next_items.extend(collect_alloc_items(prov.0).into_iter()); } next_items } /// Visit global assembly and collect its item. - fn visit_asm(&mut self, item: MonoItem<'tcx>) { + fn visit_asm(&mut self, item: &MonoItem) { debug!(?item, "visit_asm"); } } struct MonoItemsFnCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, - collected: FxHashSet>, - instance: Instance<'tcx>, - body: &'a Body<'tcx>, + collected: FxHashSet, + body: &'a Body, + instance: &'a Instance, } impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { - fn monomorphize(&self, value: T) -> T - where - T: TypeFoldable>, - { - trace!(instance=?self.instance, ?value, "monomorphize"); - self.instance.instantiate_mir_and_normalize_erasing_regions( - self.tcx, - ParamEnv::reveal_all(), - EarlyBinder::bind(value), - ) - } - /// Collect the implementation of all trait methods and its supertrait methods for the given /// concrete type. - fn collect_vtable_methods(&mut self, concrete_ty: Ty<'tcx>, trait_ty: Ty<'tcx>) { + fn collect_vtable_methods(&mut self, concrete_ty: Ty, trait_ty: Ty) { trace!(?concrete_ty, ?trait_ty, "collect_vtable_methods"); - assert!(!concrete_ty.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); - assert!(trait_ty.is_trait(), "Expected a trait: {trait_ty:?}"); - if let TyKind::Dynamic(trait_list, ..) = trait_ty.kind() { + let concrete_kind = concrete_ty.kind(); + let trait_kind = trait_ty.kind(); + + assert!(!concrete_kind.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); + assert!(trait_kind.is_trait(), "Expected a trait: {trait_ty:?}"); + if let Some(principal) = trait_kind.trait_principal() { // A trait object type can have multiple trait bounds but up to one non-auto-trait // bound. This non-auto-trait, named principal, is the only one that can have methods. // https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits - if let Some(principal) = trait_list.principal() { - let poly_trait_ref = principal.with_self_ty(self.tcx, concrete_ty); - - // Walk all methods of the trait, including those of its supertraits - let entries = self.tcx.vtable_entries(poly_trait_ref); - let methods = entries.iter().filter_map(|entry| match entry { - VtblEntry::MetadataAlign - | VtblEntry::MetadataDropInPlace - | VtblEntry::MetadataSize - | VtblEntry::Vacant => None, - VtblEntry::TraitVPtr(_) => { - // all super trait items already covered, so skip them. - None - } - VtblEntry::Method(instance) if should_codegen_locally(self.tcx, instance) => { - Some(MonoItem::Fn(instance.polymorphize(self.tcx))) - } - VtblEntry::Method(..) => None, - }); - trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); - self.collected.extend(methods); - } + let poly_trait_ref = principal.with_self_ty(concrete_ty); + + // Walk all methods of the trait, including those of its supertraits + let entries = self.tcx.vtable_entries(rustc_internal::internal(&poly_trait_ref)); + let methods = entries.iter().filter_map(|entry| match entry { + VtblEntry::MetadataAlign + | VtblEntry::MetadataDropInPlace + | VtblEntry::MetadataSize + | VtblEntry::Vacant => None, + VtblEntry::TraitVPtr(_) => { + // all super trait items already covered, so skip them. + None + } + VtblEntry::Method(instance) => { + let instance = rustc_internal::stable(instance); + should_codegen_locally(&instance).then_some(MonoItem::Fn(instance)) + } + }); + trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); + self.collected.extend(methods); } // Add the destructor for the concrete type. - let instance = Instance::resolve_drop_in_place(self.tcx, concrete_ty); + let instance = Instance::resolve_drop_in_place(concrete_ty); self.collect_instance(instance, false); } /// Collect an instance depending on how it is used (invoked directly or via fn_ptr). - fn collect_instance(&mut self, instance: Instance<'tcx>, is_direct_call: bool) { - let should_collect = match instance.def { - InstanceDef::Virtual(..) | InstanceDef::Intrinsic(_) => { + fn collect_instance(&mut self, instance: Instance, is_direct_call: bool) { + let should_collect = match instance.kind { + InstanceKind::Virtual | InstanceKind::Intrinsic => { // Instance definition has no body. assert!(is_direct_call, "Expected direct call {instance:?}"); false } - InstanceDef::DropGlue(_, None) => { - // Only need the glue if we are not calling it directly. - !is_direct_call - } - InstanceDef::CloneShim(..) - | InstanceDef::ClosureOnceShim { .. } - | InstanceDef::DropGlue(_, Some(_)) - | InstanceDef::FnPtrShim(..) - | InstanceDef::Item(..) - | InstanceDef::ReifyShim(..) - | InstanceDef::VTableShim(..) => true, - InstanceDef::ThreadLocalShim(_) | InstanceDef::FnPtrAddrShim(_, _) => true, + InstanceKind::Shim | InstanceKind::Item => true, }; - if should_collect && should_codegen_locally(self.tcx, &instance) { + if should_collect && should_codegen_locally(&instance) { trace!(?instance, "collect_instance"); - self.collected.insert(MonoItem::Fn(instance.polymorphize(self.tcx))); + self.collected.insert(instance.into()); } } /// Collect constant values represented by static variables. - fn collect_const_value(&mut self, value: ConstValue<'tcx>) { - debug!(?value, "collect_const_value"); - match value { - ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => { - self.collected.extend(collect_alloc_items(self.tcx, ptr.provenance).iter()); - } - ConstValue::Slice { data: alloc, .. } => { - for id in alloc.inner().provenance().provenances() { - self.collected.extend(collect_alloc_items(self.tcx, id).iter()) - } - } - ConstValue::Indirect { alloc_id, .. } => { - let alloc = self.tcx.global_alloc(alloc_id).unwrap_memory(); - for id in alloc.inner().provenance().provenances() { - self.collected.extend(collect_alloc_items(self.tcx, id).iter()) - } - } - _ => {} + fn collect_allocation(&mut self, alloc: &Allocation) { + debug!(?alloc, "collect_allocation"); + for (_, id) in &alloc.provenance.ptrs { + self.collected.extend(collect_alloc_items(id.0).into_iter()) } } } @@ -353,12 +317,12 @@ impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { /// 5. Drop glue. /// 6. Static Initialization /// This code has been mostly taken from `rustc_monomorphize::collector::MirNeighborCollector`. -impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { +impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { /// Collect the following: /// - Trait implementations when casting from concrete to dyn Trait. /// - Functions / Closures that have their address taken. /// - Thread Local. - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { trace!(rvalue=?*rvalue, "visit_rvalue"); match *rvalue { @@ -369,13 +333,12 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { ) => { // Check if the conversion include casting a concrete type to a trait type. // If so, collect items from the impl `Trait for Concrete {}`. - let target_ty = self.monomorphize(target); - let source_ty = self.monomorphize(operand.ty(self.body, self.tcx)); - let base_coercion = - coercion::extract_unsize_casting(self.tcx, source_ty, target_ty); - if !base_coercion.src_ty.is_trait() && base_coercion.dst_ty.is_trait() { - debug!(?base_coercion, "collect_vtable_methods"); - self.collect_vtable_methods(base_coercion.src_ty, base_coercion.dst_ty); + let target_ty = target; + let source_ty = operand.ty(self.body.locals()).unwrap(); + let (src_ty, dst_ty) = extract_unsize_coercion(self.tcx, source_ty, target_ty); + if !src_ty.kind().is_trait() && dst_ty.kind().is_trait() { + debug!(?src_ty, ?dst_ty, "collect_vtable_methods"); + self.collect_vtable_methods(src_ty, dst_ty); } } Rvalue::Cast( @@ -383,19 +346,12 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { ref operand, _, ) => { - let fn_ty = operand.ty(self.body, self.tcx); - let fn_ty = self.monomorphize(fn_ty); - if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() { - let instance = Instance::resolve_for_fn_ptr( - self.tcx, - ParamEnv::reveal_all(), - def_id, - substs, - ) - .unwrap(); + let fn_kind = operand.ty(self.body.locals()).unwrap().kind(); + if let RigidTy::FnDef(fn_def, args) = fn_kind.rigid().unwrap() { + let instance = Instance::resolve_for_fn_ptr(*fn_def, args).unwrap(); self.collect_instance(instance, false); } else { - unreachable!("Expected FnDef type, but got: {:?}", fn_ty); + unreachable!("Expected FnDef type, but got: {:?}", fn_kind); } } Rvalue::Cast( @@ -403,30 +359,20 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { ref operand, _, ) => { - let source_ty = operand.ty(self.body, self.tcx); - let source_ty = self.monomorphize(source_ty); - match *source_ty.kind() { - Closure(def_id, substs) => { - let instance = Instance::resolve_closure( - self.tcx, - def_id, - substs, - ClosureKind::FnOnce, - ) - .expect("failed to normalize and resolve closure during codegen"); + let source_ty = operand.ty(self.body.locals()).unwrap(); + match source_ty.kind().rigid().unwrap() { + RigidTy::Closure(def_id, args) => { + let instance = + Instance::resolve_closure(*def_id, args, ClosureKind::FnOnce) + .expect("failed to normalize and resolve closure during codegen"); self.collect_instance(instance, false); } _ => unreachable!("Unexpected type: {:?}", source_ty), } } - Rvalue::ThreadLocalRef(def_id) => { - assert!(self.tcx.is_thread_local_static(def_id)); - trace!(?def_id, "visit_rvalue thread_local"); - let instance = Instance::mono(self.tcx, def_id); - if should_codegen_locally(self.tcx, &instance) { - trace!("collecting thread-local static {:?}", def_id); - self.collected.insert(MonoItem::Static(def_id)); - } + Rvalue::ThreadLocalRef(item) => { + trace!(?item, "visit_rvalue thread_local"); + self.collected.insert(MonoItem::Static(StaticDef::try_from(item).unwrap())); } _ => { /* not interesting */ } } @@ -435,78 +381,39 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { } /// Collect constants that are represented as static variables. - fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, location: Location) { - let const_ = self.monomorphize(constant.const_); - debug!(?constant, ?location, ?const_, "visit_constant"); - let val = match const_ { - Const::Val(const_val, _) => const_val, - Const::Ty(ct) => match ct.kind() { - ConstKind::Value(v) => self.tcx.valtree_to_const_val((ct.ty(), v)), - ConstKind::Unevaluated(_) => unreachable!(), - // Nothing to do - ConstKind::Param(..) - | ConstKind::Infer(..) - | ConstKind::Error(..) - | ConstKind::Expr(..) => return, - - // Shouldn't happen - ConstKind::Placeholder(..) | ConstKind::Bound(..) => { - unreachable!("Unexpected constant type {:?} ({:?})", ct, ct.kind()) - } - }, - Const::Unevaluated(un_eval, _) => { - // Thread local fall into this category. - match self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None) { - // The `monomorphize` call should have evaluated that constant already. - Ok(const_val) => const_val, - Err(ErrorHandled::TooGeneric(span)) => { - if graceful_const_resolution_err( - self.tcx, - &un_eval, - span, - self.instance.def_id(), - ) - .is_some() - { - return; - } else { - span_bug!( - span, - "Unexpected polymorphic constant: {:?} {:?}", - const_, - constant.const_ - ) - } - } - Err(error) => { - warn!(?error, "Error already reported"); - return; - } - } + fn visit_constant(&mut self, constant: &Constant, location: Location) { + debug!(?constant, ?location, literal=?constant.literal, "visit_constant"); + let allocation = match constant.literal.kind() { + ConstantKind::Allocated(allocation) => allocation, + ConstantKind::Unevaluated(_) => { + unreachable!("Instance with polymorphic constant: `{constant:?}`") + } + ConstantKind::Param(_) => unreachable!("Unexpected parameter constant: {constant:?}"), + ConstantKind::ZeroSized => { + // Nothing to do here. + return; } }; - self.collect_const_value(val); + self.collect_allocation(&allocation); } /// Collect function calls. - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + fn visit_terminator(&mut self, terminator: &Terminator, location: Location) { trace!(?terminator, ?location, "visit_terminator"); - let tcx = self.tcx; match terminator.kind { - TerminatorKind::Call { ref func, args: ref outer_args, .. } => { - let callee_ty = func.ty(self.body, tcx); - let fn_ty = self.monomorphize(callee_ty); - if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() { - let instance_opt = - Instance::resolve(self.tcx, ParamEnv::reveal_all(), def_id, substs) - .unwrap(); + TerminatorKind::Call { ref func, .. } => { + let fn_ty = func.ty(self.body.locals()).unwrap(); + if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { + let instance_opt = Instance::resolve(fn_def, &args).ok(); match instance_opt { None => { - let caller = tcx.def_path_str(self.instance.def_id()); - let callee = tcx.def_path_str(def_id); + let caller = CrateItem::try_from(*self.instance).unwrap().name(); + let callee = fn_def.name(); // Check if the current function has been stubbed. - if let Some(stub) = get_stub(tcx, self.instance.def_id()) { + if let Some(stub) = + get_stub(self.tcx, rustc_internal::internal(self.instance).def_id()) + { // During the MIR stubbing transformation, we do not // force type variables in the stub's signature to // implement the same traits as those in the @@ -518,22 +425,22 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { // necessarily true. It could be any argument or // even the return type, for instance for a // trait like `FromIterator`. - let generic_ty = outer_args[0].ty(self.body, tcx).peel_refs(); - let receiver_ty = tcx.instantiate_and_normalize_erasing_regions( - substs, - ParamEnv::reveal_all(), - EarlyBinder::bind(generic_ty), - ); + let receiver_ty = args.0[0].expect_ty(); + trace!(?receiver_ty, ?callee, "**** receiver:"); let sep = callee.rfind("::").unwrap(); let trait_ = &callee[..sep]; - tcx.sess.span_err( - terminator.source_info.span, + self.tcx.sess.span_err( + rustc_internal::internal(terminator.span), format!( - "`{receiver_ty}` doesn't implement \ - `{trait_}`. The function `{caller}` \ + "`{}` doesn't implement \ + `{}`. The function `{}` \ cannot be stubbed by `{}` due to \ - generic bounds not being met. Callee: {callee}", - tcx.def_path_str(stub) + generic bounds not being met. Callee: {}", + pretty_ty(receiver_ty.kind()), + trait_, + caller, + self.tcx.def_path_str(stub), + callee, ), ); } else { @@ -544,74 +451,47 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { }; } else { assert!( - matches!(fn_ty.kind(), TyKind::FnPtr(..)), + matches!(fn_ty.kind().rigid(), Some(RigidTy::FnPtr(..))), "Unexpected type: {fn_ty:?}" ); } } TerminatorKind::Drop { ref place, .. } => { - let place_ty = place.ty(self.body, self.tcx).ty; - let place_mono_ty = self.monomorphize(place_ty); - let instance = Instance::resolve_drop_in_place(self.tcx, place_mono_ty); + let place_ty = place.ty(self.body.locals()).unwrap(); + let instance = Instance::resolve_drop_in_place(place_ty); self.collect_instance(instance, true); } TerminatorKind::InlineAsm { .. } => { // We don't support inline assembly. This shall be replaced by an unsupported // construct during codegen. } - TerminatorKind::UnwindTerminate { .. } | TerminatorKind::Assert { .. } => { + TerminatorKind::Abort { .. } | TerminatorKind::Assert { .. } => { // We generate code for this without invoking any lang item. } - TerminatorKind::Goto { .. } + TerminatorKind::CoroutineDrop { .. } + | TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::UnwindResume + | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::Unreachable => {} - TerminatorKind::CoroutineDrop - | TerminatorKind::Yield { .. } - | TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } => { - unreachable!("Unexpected at this MIR level") - } } self.super_terminator(terminator, location); } } -/// Try to construct a nice error message when const evaluation fails. -/// -/// This function handles the `Trt::CNST` case where there is one trait (`Trt`) -/// which defined a constant `CNST` that we failed to resolve. As such we expect -/// that the trait can be resolved from the constant and that only one generic -/// parameter, the instantiation of `Trt`, is present. -/// -/// If these expectations are not met we return `None`. We do not know in what -/// situation that would be the case and if they are even possible. -fn graceful_const_resolution_err<'tcx>( - tcx: TyCtxt<'tcx>, - mono_const: &UnevaluatedConst<'tcx>, - span: rustc_span::Span, - parent_fn: DefId, -) -> Option { - let implementor = match mono_const.args.as_slice() { - [one] => one.as_type(), - _ => None, - }?; - let trait_ = tcx.trait_of_item(mono_const.def)?; - let msg = format!( - "Type `{implementor}` does not implement trait `{}`. \ - This is likely because `{}` is used as a stub but its \ - generic bounds are not being met.", - tcx.def_path_str(trait_), - tcx.def_path_str(parent_fn) +fn extract_unsize_coercion(tcx: TyCtxt, orig_ty: Ty, dst_trait: Ty) -> (Ty, Ty) { + let CoercionBase { src_ty, dst_ty } = coercion::extract_unsize_casting( + tcx, + rustc_internal::internal(orig_ty), + rustc_internal::internal(dst_trait), ); - Some(tcx.sess.span_err(span, msg)) + (rustc_internal::stable(src_ty), rustc_internal::stable(dst_ty)) } /// Convert a `MonoItem` into a stable `Fingerprint` which can be used as a stable hash across /// compilation sessions. This allow us to provide a stable deterministic order to codegen. -fn to_fingerprint(tcx: TyCtxt, item: &MonoItem) -> Fingerprint { +fn to_fingerprint(tcx: TyCtxt, item: &InternalMonoItem) -> Fingerprint { tcx.with_stable_hashing_context(|mut hcx| { let mut hasher = StableHasher::new(); item.hash_stable(&mut hcx, &mut hasher); @@ -620,52 +500,31 @@ fn to_fingerprint(tcx: TyCtxt, item: &MonoItem) -> Fingerprint { } /// Return whether we should include the item into codegen. -/// - We only skip foreign items. -/// -/// Note: Ideally, we should be able to assert that the MIR for non-foreign items are available via -/// call to `tcx.is_mir_available (def_id)`. -/// However, we found an issue where this function was returning `false` for a mutable static -/// item with constant initializer from an upstream crate. -/// See for an example. -fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) -> bool { - if let Some(def_id) = instance.def.def_id_if_not_guaranteed_local_codegen() { - // We cannot codegen foreign items. - !tcx.is_foreign_item(def_id) - } else { - // This will include things like VTableShim and other stuff. See the method - // def_id_if_not_guaranteed_local_codegen for the full list. - true - } +fn should_codegen_locally<'tcx>(instance: &Instance) -> bool { + !instance.is_foreign_item() } -/// Scans the allocation type and collect static objects. -fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec { - trace!(alloc=?tcx.global_alloc(alloc_id), ?alloc_id, "collect_alloc_items"); +fn collect_alloc_items(alloc_id: AllocId) -> Vec { + trace!(?alloc_id, "collect_alloc_items"); let mut items = vec![]; - match tcx.global_alloc(alloc_id) { - GlobalAlloc::Static(def_id) => { + match GlobalAlloc::from(alloc_id) { + GlobalAlloc::Static(def) => { // This differ from rustc's collector since rustc does not include static from // upstream crates. - assert!(!tcx.is_thread_local_static(def_id)); - let instance = Instance::mono(tcx, def_id); - should_codegen_locally(tcx, &instance).then(|| items.push(MonoItem::Static(def_id))); + let instance = Instance::try_from(CrateItem::from(def)).unwrap(); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def))); } GlobalAlloc::Function(instance) => { - should_codegen_locally(tcx, &instance) - .then(|| items.push(MonoItem::Fn(instance.polymorphize(tcx)))); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(instance))); } GlobalAlloc::Memory(alloc) => { items.extend( - alloc - .inner() - .provenance() - .provenances() - .flat_map(|id| collect_alloc_items(tcx, id)), + alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(prov.0)), ); } - GlobalAlloc::VTable(ty, trait_ref) => { - let vtable_id = tcx.vtable_allocation((ty, trait_ref)); - items.append(&mut collect_alloc_items(tcx, vtable_id)); + vtable_alloc @ GlobalAlloc::VTable(..) => { + let vtable_id = vtable_alloc.vtable_allocation().unwrap(); + items = collect_alloc_items(vtable_id); } }; items @@ -675,6 +534,7 @@ fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec { mod debug { #![allow(dead_code)] + use std::fmt::{Display, Formatter}; use std::{ collections::{HashMap, HashSet}, fs::File, @@ -686,35 +546,39 @@ mod debug { use super::*; #[derive(Debug, Default)] - pub struct CallGraph<'tcx> { + pub struct CallGraph { // Nodes of the graph. - nodes: HashSet>, - edges: HashMap, Vec>>, - back_edges: HashMap, Vec>>, + nodes: HashSet, + edges: HashMap>, + back_edges: HashMap>, } - type Node<'tcx> = MonoItem<'tcx>; + #[derive(Clone, Debug, Eq, PartialEq, Hash)] + struct Node(pub MonoItem); - impl<'tcx> CallGraph<'tcx> { - pub fn add_node(&mut self, item: Node<'tcx>) { - self.nodes.insert(item); - self.edges.entry(item).or_default(); - self.back_edges.entry(item).or_default(); + impl CallGraph { + pub fn add_node(&mut self, item: MonoItem) { + let node = Node(item); + self.nodes.insert(node.clone()); + self.edges.entry(node.clone()).or_default(); + self.back_edges.entry(node).or_default(); } /// Add a new edge "from" -> "to". - pub fn add_edge(&mut self, from: Node<'tcx>, to: Node<'tcx>) { + pub fn add_edge(&mut self, from: MonoItem, to: MonoItem) { + let from_node = Node(from.clone()); + let to_node = Node(to.clone()); self.add_node(from); self.add_node(to); - self.edges.get_mut(&from).unwrap().push(to); - self.back_edges.get_mut(&to).unwrap().push(from); + self.edges.get_mut(&from_node).unwrap().push(to_node.clone()); + self.back_edges.get_mut(&to_node).unwrap().push(from_node); } /// Add multiple new edges for the "from" node. - pub fn add_edges(&mut self, from: Node<'tcx>, to: &[Node<'tcx>]) { - self.add_node(from); + pub fn add_edges(&mut self, from: MonoItem, to: &[MonoItem]) { + self.add_node(from.clone()); for item in to { - self.add_edge(from, *item); + self.add_edge(from.clone(), item.clone()); } } @@ -759,7 +623,7 @@ mod debug { .iter() .filter(|item| item.to_string().contains(target)) .collect::>(); - let mut visited: HashSet<&MonoItem> = HashSet::default(); + let mut visited: HashSet<&Node> = HashSet::default(); tracing::info!(target=?queue, nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_reason"); while let Some(to_visit) = queue.pop() { if !visited.contains(to_visit) { @@ -779,4 +643,14 @@ mod debug { Ok(()) } } + + impl Display for Node { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + MonoItem::Fn(instance) => write!(f, "{}", instance.mangled_name()), + MonoItem::Static(def) => write!(f, "{}", CrateItem::from(*def).name()), + MonoItem::GlobalAsm(asm) => write!(f, "{asm:?}"), + } + } + } } diff --git a/kani-compiler/src/kani_middle/reachability_smir.rs b/kani-compiler/src/kani_middle/reachability_smir.rs deleted file mode 100644 index 777b29e2542e..000000000000 --- a/kani-compiler/src/kani_middle/reachability_smir.rs +++ /dev/null @@ -1,656 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! This module implements a cross-crate collector that allow us to find all items that -//! should be included in order to verify one or more proof harness. -//! -//! This module works as following: -//! - Traverse all reachable items starting at the given starting points. -//! - For every function, traverse its body and collect the following: -//! - Constants / Static objects. -//! - Functions that are called or have their address taken. -//! - VTable methods for types that are coerced as unsized types. -//! - For every static, collect initializer and drop functions. -//! -//! We have kept this module agnostic of any Kani code in case we can contribute this back to rustc. -//! -//! Note that this is a copy of `reachability.rs` that uses StableMIR but the public APIs are still -//! kept with internal APIs. -use tracing::{debug, debug_span, trace}; - -use rustc_data_structures::fingerprint::Fingerprint; -use rustc_data_structures::fx::FxHashSet; -use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::mono::MonoItem as InternalMonoItem; -use rustc_middle::ty::{TyCtxt, VtblEntry}; -use rustc_smir::rustc_internal; -use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; -use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; -use stable_mir::mir::pretty::pretty_ty; -use stable_mir::mir::{ - visit::Location, Body, CastKind, Constant, MirVisitor, PointerCoercion, Rvalue, Terminator, - TerminatorKind, -}; -use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; -use stable_mir::CrateDef; -use stable_mir::{self, CrateItem}; - -use crate::kani_middle::coercion; -use crate::kani_middle::coercion::CoercionBase; -use crate::kani_middle::stubbing::{get_stub, validate_instance}; - -/// Collect all reachable items starting from the given starting points. -pub fn collect_reachable_items<'tcx>( - tcx: TyCtxt<'tcx>, - starting_points: &[InternalMonoItem<'tcx>], -) -> Vec> { - rustc_smir::rustc_internal::run(tcx, || { - // For each harness, collect items using the same collector. - // I.e.: This will return any item that is reachable from one or more of the starting points. - let mut collector = MonoItemsCollector::new(tcx); - for item in starting_points { - collector.collect(rustc_internal::stable(item)); - } - - #[cfg(disable_debug_assertions)] - collector - .call_graph - .dump_dot(tcx) - .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); - - tcx.sess.abort_if_errors(); - - // Sort the result so code generation follows deterministic order. - // This helps us to debug the code, but it also provides the user a good experience since the - // order of the errors and warnings is stable. - let mut sorted_items: Vec<_> = - collector.collected.iter().map(rustc_internal::internal).collect(); - sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); - sorted_items - }) - .unwrap() -} - -/// Collect all (top-level) items in the crate that matches the given predicate. -/// An item can only be a root if they are: non-generic Fn / Static / GlobalASM -pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec -where - F: Fn(TyCtxt, DefId) -> bool, -{ - rustc_smir::rustc_internal::run(tcx, || { - let crate_items = stable_mir::all_local_items(); - // Filter regular items. - crate_items - .iter() - .filter_map(|item| { - // Only collect monomorphic items. - Instance::try_from(*item) - .ok() - .map(|instance| { - let def_id = rustc_internal::internal(item); - predicate(tcx, def_id) - .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) - }) - .flatten() - }) - .collect::>() - }) - .unwrap() -} - -/// Use a predicate to find `const` declarations, then extract all items reachable from them. -/// -/// Probably only specifically useful with a predicate to find `TestDescAndFn` const declarations from -/// tests and extract the closures from them. -pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec -where - F: FnMut(TyCtxt, DefId) -> bool, -{ - rustc_smir::rustc_internal::run(tcx, || { - let crate_items = stable_mir::all_local_items(); - let mut roots = Vec::new(); - // Filter regular items. - for item in crate_items { - // Only collect monomorphic items. - if let Ok(instance) = Instance::try_from(item) { - let def_id = rustc_internal::internal(&item); - if predicate(tcx, def_id) { - let body = instance.body().unwrap(); - let mut collector = MonoItemsFnCollector { - tcx, - body: &body, - collected: FxHashSet::default(), - instance: &instance, - }; - collector.visit_body(&body); - roots.extend(collector.collected.iter().map(rustc_internal::internal)); - } - } - } - roots - }) - .unwrap() -} - -struct MonoItemsCollector<'tcx> { - /// The compiler context. - tcx: TyCtxt<'tcx>, - /// Set of collected items used to avoid entering recursion loops. - collected: FxHashSet, - /// Items enqueued for visiting. - queue: Vec, - #[cfg(disable_debug_assertions)] - call_graph: debug::CallGraph, -} - -// TODO: Need to figure out how to handle stubbing errors. -impl<'tcx> MonoItemsCollector<'tcx> { - pub fn new(tcx: TyCtxt<'tcx>) -> Self { - MonoItemsCollector { - tcx, - collected: FxHashSet::default(), - queue: vec![], - #[cfg(disable_debug_assertions)] - call_graph: debug::CallGraph::default(), - } - } - - /// Collects all reachable items starting from the given root. - pub fn collect(&mut self, root: MonoItem) { - debug!(?root, "collect"); - self.queue.push(root); - self.reachable_items(); - } - - /// Traverses the call graph starting from the given root. For every function, we visit all - /// instruction looking for the items that should be included in the compilation. - fn reachable_items(&mut self) { - while let Some(to_visit) = self.queue.pop() { - if !self.collected.contains(&to_visit) { - self.collected.insert(to_visit.clone()); - let next_items = match &to_visit { - MonoItem::Fn(instance) => self.visit_fn(*instance), - MonoItem::Static(static_def) => self.visit_static(*static_def), - MonoItem::GlobalAsm(_) => { - self.visit_asm(to_visit); - vec![] - } - }; - #[cfg(disable_debug_assertions)] - self.call_graph.add_edges(to_visit, &next_items); - - self.queue - .extend(next_items.into_iter().filter(|item| !self.collected.contains(item))); - } - } - } - - /// Visit a function and collect all mono-items reachable from its instructions. - fn visit_fn(&mut self, instance: Instance) -> Vec { - let _guard = debug_span!("visit_fn", function=?instance).entered(); - if validate_instance(self.tcx, instance) { - let body = instance.body().unwrap(); - let mut collector = MonoItemsFnCollector { - tcx: self.tcx, - collected: FxHashSet::default(), - body: &body, - instance: &instance, - }; - collector.visit_body(&body); - collector.collected.into_iter().collect() - } else { - vec![] - } - } - - /// Visit a static object and collect drop / initialization functions. - fn visit_static(&mut self, def: StaticDef) -> Vec { - let _guard = debug_span!("visit_static", ?def).entered(); - let mut next_items = vec![]; - - // Collect drop function. - let static_ty = def.ty(); - let instance = Instance::resolve_drop_in_place(static_ty); - next_items.push(instance.into()); - - // Collect initialization. - let alloc = def.eval_initializer().unwrap(); - for (_, prov) in alloc.provenance.ptrs { - next_items.extend(collect_alloc_items(prov.0).into_iter()); - } - - next_items - } - - /// Visit global assembly and collect its item. - fn visit_asm(&mut self, item: MonoItem) { - debug!(?item, "visit_asm"); - } -} - -struct MonoItemsFnCollector<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - collected: FxHashSet, - body: &'a Body, - instance: &'a Instance, -} - -impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { - /// Collect the implementation of all trait methods and its supertrait methods for the given - /// concrete type. - fn collect_vtable_methods(&mut self, concrete_ty: Ty, trait_ty: Ty) { - trace!(?concrete_ty, ?trait_ty, "collect_vtable_methods"); - let concrete_kind = concrete_ty.kind(); - let trait_kind = trait_ty.kind(); - - assert!(!concrete_kind.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); - assert!(trait_kind.is_trait(), "Expected a trait: {trait_ty:?}"); - if let Some(principal) = trait_kind.trait_principal() { - // A trait object type can have multiple trait bounds but up to one non-auto-trait - // bound. This non-auto-trait, named principal, is the only one that can have methods. - // https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits - let poly_trait_ref = principal.with_self_ty(concrete_ty); - - // Walk all methods of the trait, including those of its supertraits - let entries = self.tcx.vtable_entries(rustc_internal::internal(&poly_trait_ref)); - let methods = entries.iter().filter_map(|entry| match entry { - VtblEntry::MetadataAlign - | VtblEntry::MetadataDropInPlace - | VtblEntry::MetadataSize - | VtblEntry::Vacant => None, - VtblEntry::TraitVPtr(_) => { - // all super trait items already covered, so skip them. - None - } - VtblEntry::Method(instance) => { - let instance = rustc_internal::stable(instance); - should_codegen_locally(&instance).then_some(MonoItem::Fn(instance)) - } - }); - trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); - self.collected.extend(methods); - } - - // Add the destructor for the concrete type. - let instance = Instance::resolve_drop_in_place(concrete_ty); - self.collect_instance(instance, false); - } - - /// Collect an instance depending on how it is used (invoked directly or via fn_ptr). - fn collect_instance(&mut self, instance: Instance, is_direct_call: bool) { - let should_collect = match instance.kind { - InstanceKind::Virtual | InstanceKind::Intrinsic => { - // Instance definition has no body. - assert!(is_direct_call, "Expected direct call {instance:?}"); - false - } - InstanceKind::Shim | InstanceKind::Item => true, - }; - if should_collect && should_codegen_locally(&instance) { - trace!(?instance, "collect_instance"); - self.collected.insert(instance.into()); - } - } - - /// Collect constant values represented by static variables. - fn collect_allocation(&mut self, alloc: &Allocation) { - debug!(?alloc, "collect_allocation"); - for (_, id) in &alloc.provenance.ptrs { - self.collected.extend(collect_alloc_items(id.0).into_iter()) - } - } -} - -/// Visit every instruction in a function and collect the following: -/// 1. Every function / method / closures that may be directly invoked. -/// 2. Every function / method / closures that may have their address taken. -/// 3. Every method that compose the impl of a trait for a given type when there's a conversion -/// from the type to the trait. -/// - I.e.: If we visit the following code: -/// ``` -/// let var = MyType::new(); -/// let ptr : &dyn MyTrait = &var; -/// ``` -/// We collect the entire implementation of `MyTrait` for `MyType`. -/// 4. Every Static variable that is referenced in the function or constant used in the function. -/// 5. Drop glue. -/// 6. Static Initialization -/// This code has been mostly taken from `rustc_monomorphize::collector::MirNeighborCollector`. -impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { - /// Collect the following: - /// - Trait implementations when casting from concrete to dyn Trait. - /// - Functions / Closures that have their address taken. - /// - Thread Local. - fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { - trace!(rvalue=?*rvalue, "visit_rvalue"); - - match *rvalue { - Rvalue::Cast( - CastKind::PointerCoercion(PointerCoercion::Unsize), - ref operand, - target, - ) => { - // Check if the conversion include casting a concrete type to a trait type. - // If so, collect items from the impl `Trait for Concrete {}`. - let target_ty = target; - let source_ty = operand.ty(self.body.locals()).unwrap(); - let (src_ty, dst_ty) = extract_unsize_coercion(self.tcx, source_ty, target_ty); - if !src_ty.kind().is_trait() && dst_ty.kind().is_trait() { - debug!(?src_ty, ?dst_ty, "collect_vtable_methods"); - self.collect_vtable_methods(src_ty, dst_ty); - } - } - Rvalue::Cast( - CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer), - ref operand, - _, - ) => { - let fn_kind = operand.ty(self.body.locals()).unwrap().kind(); - if let RigidTy::FnDef(fn_def, args) = fn_kind.rigid().unwrap() { - let instance = Instance::resolve_for_fn_ptr(*fn_def, args).unwrap(); - self.collect_instance(instance, false); - } else { - unreachable!("Expected FnDef type, but got: {:?}", fn_kind); - } - } - Rvalue::Cast( - CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)), - ref operand, - _, - ) => { - let source_ty = operand.ty(self.body.locals()).unwrap(); - match source_ty.kind().rigid().unwrap() { - RigidTy::Closure(def_id, args) => { - let instance = - Instance::resolve_closure(*def_id, args, ClosureKind::FnOnce) - .expect("failed to normalize and resolve closure during codegen"); - self.collect_instance(instance, false); - } - _ => unreachable!("Unexpected type: {:?}", source_ty), - } - } - Rvalue::ThreadLocalRef(item) => { - trace!(?item, "visit_rvalue thread_local"); - self.collected.insert(MonoItem::Static(StaticDef::try_from(item).unwrap())); - } - _ => { /* not interesting */ } - } - - self.super_rvalue(rvalue, location); - } - - /// Collect constants that are represented as static variables. - fn visit_constant(&mut self, constant: &Constant, location: Location) { - debug!(?constant, ?location, literal=?constant.literal, "visit_constant"); - let allocation = match constant.literal.kind() { - ConstantKind::Allocated(allocation) => allocation, - ConstantKind::Unevaluated(_) => { - unreachable!("Instance with polymorphic constant: `{constant:?}`") - } - ConstantKind::Param(_) => unreachable!("Unexpected parameter constant: {constant:?}"), - ConstantKind::ZeroSized => { - // Nothing to do here. - return; - } - }; - self.collect_allocation(&allocation); - } - - /// Collect function calls. - fn visit_terminator(&mut self, terminator: &Terminator, location: Location) { - trace!(?terminator, ?location, "visit_terminator"); - - match terminator.kind { - TerminatorKind::Call { ref func, .. } => { - let fn_ty = func.ty(self.body.locals()).unwrap(); - if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { - let instance_opt = Instance::resolve(fn_def, &args).ok(); - match instance_opt { - None => { - let caller = CrateItem::try_from(*self.instance).unwrap().name(); - let callee = fn_def.name(); - // Check if the current function has been stubbed. - if let Some(stub) = - get_stub(self.tcx, rustc_internal::internal(self.instance).def_id()) - { - // During the MIR stubbing transformation, we do not - // force type variables in the stub's signature to - // implement the same traits as those in the - // original function/method. A trait mismatch shows - // up here, when we try to resolve a trait method - - // FIXME: This assumes the type resolving the - // trait is the first argument, but that isn't - // necessarily true. It could be any argument or - // even the return type, for instance for a - // trait like `FromIterator`. - let receiver_ty = args.0[0].expect_ty(); - trace!(?receiver_ty, ?callee, "**** receiver:"); - let sep = callee.rfind("::").unwrap(); - let trait_ = &callee[..sep]; - self.tcx.sess.span_err( - rustc_internal::internal(terminator.span), - format!( - "`{}` doesn't implement \ - `{}`. The function `{}` \ - cannot be stubbed by `{}` due to \ - generic bounds not being met. Callee: {}", - pretty_ty(receiver_ty.kind()), - trait_, - caller, - self.tcx.def_path_str(stub), - callee, - ), - ); - } else { - panic!("unable to resolve call to `{callee}` in `{caller}`") - } - } - Some(instance) => self.collect_instance(instance, true), - }; - } else { - assert!( - matches!(fn_ty.kind().rigid(), Some(RigidTy::FnPtr(..))), - "Unexpected type: {fn_ty:?}" - ); - } - } - TerminatorKind::Drop { ref place, .. } => { - let place_ty = place.ty(self.body.locals()).unwrap(); - let instance = Instance::resolve_drop_in_place(place_ty); - self.collect_instance(instance, true); - } - TerminatorKind::InlineAsm { .. } => { - // We don't support inline assembly. This shall be replaced by an unsupported - // construct during codegen. - } - TerminatorKind::Abort { .. } | TerminatorKind::Assert { .. } => { - // We generate code for this without invoking any lang item. - } - TerminatorKind::CoroutineDrop { .. } - | TerminatorKind::Goto { .. } - | TerminatorKind::SwitchInt { .. } - | TerminatorKind::Resume - | TerminatorKind::Return - | TerminatorKind::Unreachable => {} - } - - self.super_terminator(terminator, location); - } -} - -fn extract_unsize_coercion(tcx: TyCtxt, orig_ty: Ty, dst_trait: Ty) -> (Ty, Ty) { - let CoercionBase { src_ty, dst_ty } = coercion::extract_unsize_casting( - tcx, - rustc_internal::internal(orig_ty), - rustc_internal::internal(dst_trait), - ); - (rustc_internal::stable(src_ty), rustc_internal::stable(dst_ty)) -} - -/// Convert a `MonoItem` into a stable `Fingerprint` which can be used as a stable hash across -/// compilation sessions. This allow us to provide a stable deterministic order to codegen. -fn to_fingerprint(tcx: TyCtxt, item: &InternalMonoItem) -> Fingerprint { - tcx.with_stable_hashing_context(|mut hcx| { - let mut hasher = StableHasher::new(); - item.hash_stable(&mut hcx, &mut hasher); - hasher.finish() - }) -} - -/// Return whether we should include the item into codegen. -fn should_codegen_locally<'tcx>(instance: &Instance) -> bool { - !instance.is_foreign_item() -} - -fn collect_alloc_items(alloc_id: AllocId) -> Vec { - trace!(?alloc_id, "collect_alloc_items"); - let mut items = vec![]; - match GlobalAlloc::from(alloc_id) { - GlobalAlloc::Static(def) => { - // This differ from rustc's collector since rustc does not include static from - // upstream crates. - let instance = Instance::try_from(CrateItem::from(def)).unwrap(); - should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def))); - } - GlobalAlloc::Function(instance) => { - should_codegen_locally(&instance).then(|| items.push(MonoItem::from(instance))); - } - GlobalAlloc::Memory(alloc) => { - items.extend( - alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(prov.0)), - ); - } - vtable_alloc @ GlobalAlloc::VTable(..) => { - let vtable_id = vtable_alloc.vtable_allocation().unwrap(); - items = collect_alloc_items(vtable_id); - } - }; - items -} - -#[cfg(debug_assertions)] -mod debug { - #![allow(dead_code)] - - use std::fmt::{Display, Formatter}; - use std::{ - collections::{HashMap, HashSet}, - fs::File, - io::{BufWriter, Write}, - }; - - use rustc_session::config::OutputType; - - use super::*; - - #[derive(Debug, Default)] - pub struct CallGraph { - // Nodes of the graph. - nodes: HashSet, - edges: HashMap>, - back_edges: HashMap>, - } - - #[derive(Clone, Debug, Eq, PartialEq, Hash)] - struct Node(pub MonoItem); - - impl CallGraph { - pub fn add_node(&mut self, item: MonoItem) { - let node = Node(item); - self.nodes.insert(node.clone()); - self.edges.entry(node.clone()).or_default(); - self.back_edges.entry(node).or_default(); - } - - /// Add a new edge "from" -> "to". - pub fn add_edge(&mut self, from: MonoItem, to: MonoItem) { - let from_node = Node(from.clone()); - let to_node = Node(to.clone()); - self.add_node(from); - self.add_node(to); - self.edges.get_mut(&from_node).unwrap().push(to_node.clone()); - self.back_edges.get_mut(&to_node).unwrap().push(from_node); - } - - /// Add multiple new edges for the "from" node. - pub fn add_edges(&mut self, from: MonoItem, to: &[MonoItem]) { - self.add_node(from.clone()); - for item in to { - self.add_edge(from.clone(), item.clone()); - } - } - - /// Print the graph in DOT format to a file. - /// See for more information. - pub fn dump_dot(&self, tcx: TyCtxt) -> std::io::Result<()> { - if let Ok(target) = std::env::var("KANI_REACH_DEBUG") { - debug!(?target, "dump_dot"); - let outputs = tcx.output_filenames(()); - let path = outputs.output_path(OutputType::Metadata).with_extension("dot"); - let out_file = File::create(path)?; - let mut writer = BufWriter::new(out_file); - writeln!(writer, "digraph ReachabilityGraph {{")?; - if target.is_empty() { - self.dump_all(&mut writer)?; - } else { - // Only dump nodes that led the reachability analysis to the target node. - self.dump_reason(&mut writer, &target)?; - } - writeln!(writer, "}}")?; - } - - Ok(()) - } - - /// Write all notes to the given writer. - fn dump_all(&self, writer: &mut W) -> std::io::Result<()> { - tracing::info!(nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_all"); - for node in &self.nodes { - writeln!(writer, r#""{node}""#)?; - for succ in self.edges.get(node).unwrap() { - writeln!(writer, r#""{node}" -> "{succ}" "#)?; - } - } - Ok(()) - } - - /// Write all notes that may have led to the discovery of the given target. - fn dump_reason(&self, writer: &mut W, target: &str) -> std::io::Result<()> { - let mut queue = self - .nodes - .iter() - .filter(|item| item.to_string().contains(target)) - .collect::>(); - let mut visited: HashSet<&Node> = HashSet::default(); - tracing::info!(target=?queue, nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_reason"); - while let Some(to_visit) = queue.pop() { - if !visited.contains(to_visit) { - visited.insert(to_visit); - queue.extend(self.back_edges.get(to_visit).unwrap()); - } - } - - for node in &visited { - writeln!(writer, r#""{node}""#)?; - for succ in - self.edges.get(node).unwrap().iter().filter(|item| visited.contains(item)) - { - writeln!(writer, r#""{node}" -> "{succ}" "#)?; - } - } - Ok(()) - } - } - - impl Display for Node { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match &self.0 { - MonoItem::Fn(instance) => write!(f, "{}", instance.mangled_name()), - MonoItem::Static(def) => write!(f, "{}", CrateItem::from(*def).name()), - MonoItem::GlobalAsm(asm) => write!(f, "{asm:?}"), - } - } - } -} From 63795a0cc687060def6a65bd54fed46fe5bcb3a1 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 24 Nov 2023 19:03:03 -0800 Subject: [PATCH 4/8] Update the rust toolchain to nightly-2023-11-27 --- kani-compiler/src/kani_middle/reachability.rs | 15 ++++----- kani-compiler/src/kani_middle/resolve.rs | 4 +-- kani-compiler/src/kani_middle/stubbing/mod.rs | 33 +++++++++---------- rust-toolchain.toml | 2 +- .../harness/expected | 2 +- tests/expected/intrinsics/ctpop-ice/expected | 2 +- .../expected | 2 +- .../simd-extract-wrong-type/expected | 2 +- .../simd-insert-wrong-type/expected | 2 +- .../simd-result-type-is-float/expected | 2 +- .../expected | 2 +- .../expected | 2 +- tests/expected/panic/arg-error/expected | 2 +- .../multiple-attrs/expected | 2 +- .../should-panic-attribute/with-args/expected | 2 +- tests/ui/solver-attribute/invalid/expected | 2 +- .../solver-attribute/multiple-args/expected | 2 +- .../solver-attribute/multiple-attrs/expected | 2 +- tests/ui/solver-attribute/no-arg/expected | 2 +- tests/ui/solver-attribute/unknown/expected | 2 +- 20 files changed, 40 insertions(+), 46 deletions(-) diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 36932eb67e66..159905d9bf85 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -85,14 +85,11 @@ where .iter() .filter_map(|item| { // Only collect monomorphic items. - Instance::try_from(*item) - .ok() - .map(|instance| { - let def_id = rustc_internal::internal(item); - predicate(tcx, def_id) - .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) - }) - .flatten() + Instance::try_from(*item).ok().and_then(|instance| { + let def_id = rustc_internal::internal(item); + predicate(tcx, def_id) + .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) + }) }) .collect::>() }) @@ -500,7 +497,7 @@ fn to_fingerprint(tcx: TyCtxt, item: &InternalMonoItem) -> Fingerprint { } /// Return whether we should include the item into codegen. -fn should_codegen_locally<'tcx>(instance: &Instance) -> bool { +fn should_codegen_locally(instance: &Instance) -> bool { !instance.is_foreign_item() } diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index 0cc3ee0b7f3a..1d3b1a2c2f06 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -190,7 +190,7 @@ fn resolve_prefix<'tcx>( CRATE => { segments.next(); // Find the module at the root of the crate. - let current_module_hir_id = tcx.hir().local_def_id_to_hir_id(current_module); + let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let crate_root = match tcx.hir().parent_iter(current_module_hir_id).last() { None => current_module, Some((hir_id, _)) => hir_id.owner.def_id, @@ -229,7 +229,7 @@ fn resolve_super<'tcx, I>( where I: Iterator, { - let current_module_hir_id = tcx.hir().local_def_id_to_hir_id(current_module); + let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let mut parents = tcx.hir().parent_iter(current_module_hir_id); let mut base_module = current_module; while segments.next_if(|segment| segment == SUPER).is_some() { diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index 21c381598b92..748365a6d653 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -92,27 +92,24 @@ impl<'tcx> MirVisitor for StubConstChecker<'tcx> { Const::Val(..) | Const::Ty(..) => {} Const::Unevaluated(un_eval, _) => { // Thread local fall into this category. - match self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None) { + if self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None).is_err() { // The `monomorphize` call should have evaluated that constant already. - Err(_) => { - let tcx = self.tcx; - let mono_const = &un_eval; - let implementor = match mono_const.args.as_slice() { - [one] => one.as_type().unwrap(), - _ => unreachable!(), - }; - let trait_ = tcx.trait_of_item(mono_const.def).unwrap(); - let msg = format!( - "Type `{implementor}` does not implement trait `{}`. \ + let tcx = self.tcx; + let mono_const = &un_eval; + let implementor = match mono_const.args.as_slice() { + [one] => one.as_type().unwrap(), + _ => unreachable!(), + }; + let trait_ = tcx.trait_of_item(mono_const.def).unwrap(); + let msg = format!( + "Type `{implementor}` does not implement trait `{}`. \ This is likely because `{}` is used as a stub but its \ generic bounds are not being met.", - tcx.def_path_str(trait_), - self.source.name() - ); - tcx.sess.span_err(rustc_internal::internal(location.span()), msg); - self.is_valid = false; - } - _ => {} + tcx.def_path_str(trait_), + self.source.name() + ); + tcx.sess.span_err(rustc_internal::internal(location.span()), msg); + self.is_valid = false; } } }; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c759e2625603..3957d9e04ee1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2023-11-22" +channel = "nightly-2023-11-27" components = ["llvm-tools-preview", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tests/cargo-kani/stubbing-double-extern-path/harness/expected b/tests/cargo-kani/stubbing-double-extern-path/harness/expected index 178d9ab00302..adbbf31d49bd 100644 --- a/tests/cargo-kani/stubbing-double-extern-path/harness/expected +++ b/tests/cargo-kani/stubbing-double-extern-path/harness/expected @@ -1 +1 @@ -error[E0391]: cycle detected when optimizing MIR for `crate_a::assert_true` +error[E0391]: cycle detected when optimizing MIR for `crate_b::assert_false` diff --git a/tests/expected/intrinsics/ctpop-ice/expected b/tests/expected/intrinsics/ctpop-ice/expected index 25fbb79d9551..1ac989525f36 100644 --- a/tests/expected/intrinsics/ctpop-ice/expected +++ b/tests/expected/intrinsics/ctpop-ice/expected @@ -3,4 +3,4 @@ error: Type check failed for intrinsic `ctpop`: Expected integer type, found () 12 | let n = ctpop(()); | ^^^^^^^^^ -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected b/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected index f6bf97116db3..b535e4699346 100644 --- a/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected +++ b/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected @@ -1,2 +1,2 @@ expected return type with length 2 (same as input type `u64x2`), found `u32x4` with length 4 -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-extract-wrong-type/expected b/tests/expected/intrinsics/simd-extract-wrong-type/expected index 5d6a42b0542d..eb2c6e932803 100644 --- a/tests/expected/intrinsics/simd-extract-wrong-type/expected +++ b/tests/expected/intrinsics/simd-extract-wrong-type/expected @@ -1,2 +1,2 @@ expected return type `i64` (element of input `i64x2`), found `i32` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-insert-wrong-type/expected b/tests/expected/intrinsics/simd-insert-wrong-type/expected index 8e2271a5ca57..c86b42bc89e4 100644 --- a/tests/expected/intrinsics/simd-insert-wrong-type/expected +++ b/tests/expected/intrinsics/simd-insert-wrong-type/expected @@ -1,2 +1,2 @@ expected inserted type `i64` (element of input `i64x2`), found `i32` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-result-type-is-float/expected b/tests/expected/intrinsics/simd-result-type-is-float/expected index 26145ffed72c..991b6f37d384 100644 --- a/tests/expected/intrinsics/simd-result-type-is-float/expected +++ b/tests/expected/intrinsics/simd-result-type-is-float/expected @@ -1,2 +1,2 @@ expected return type with integer elements, found `f32x2` with non-integer `f32` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected index bc85467ee413..6b6c4ad781fd 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected @@ -1,2 +1,2 @@ expected return type of length 4, found `i64x2` with length 2 -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected index 4bb75c754afb..9982de236965 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected @@ -1,2 +1,2 @@ expected return element type `i64` (element of input `i64x2`), found `f64x2` with element type `f64` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/panic/arg-error/expected b/tests/expected/panic/arg-error/expected index 95567d5f7efb..655a016bc38e 100644 --- a/tests/expected/panic/arg-error/expected +++ b/tests/expected/panic/arg-error/expected @@ -1,2 +1,2 @@ error: 1 positional argument in format string, but no arguments were given -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/should-panic-attribute/multiple-attrs/expected b/tests/ui/should-panic-attribute/multiple-attrs/expected index 5dd8c6a61430..ecc7c6fc91c8 100644 --- a/tests/ui/should-panic-attribute/multiple-attrs/expected +++ b/tests/ui/should-panic-attribute/multiple-attrs/expected @@ -1,2 +1,2 @@ error: only one '#[kani::should_panic]' attribute is allowed per harness -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/should-panic-attribute/with-args/expected b/tests/ui/should-panic-attribute/with-args/expected index 3ba218b82a69..2ab549629004 100644 --- a/tests/ui/should-panic-attribute/with-args/expected +++ b/tests/ui/should-panic-attribute/with-args/expected @@ -1,3 +1,3 @@ error: custom attribute panicked help: message: `#[kani::should_panic]` does not take any arguments currently -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/invalid/expected b/tests/ui/solver-attribute/invalid/expected index 53f6b87bf547..fe4f1da36d68 100644 --- a/tests/ui/solver-attribute/invalid/expected +++ b/tests/ui/solver-attribute/invalid/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(123)]\ | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/multiple-args/expected b/tests/ui/solver-attribute/multiple-args/expected index 64e1a5468fc3..9bd942eaafa3 100644 --- a/tests/ui/solver-attribute/multiple-args/expected +++ b/tests/ui/solver-attribute/multiple-args/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(kissat, minisat)]\ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/multiple-attrs/expected b/tests/ui/solver-attribute/multiple-attrs/expected index 1287dedaaab1..5a6b5cb298b1 100644 --- a/tests/ui/solver-attribute/multiple-attrs/expected +++ b/tests/ui/solver-attribute/multiple-attrs/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(kissat)]\ | ^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/no-arg/expected b/tests/ui/solver-attribute/no-arg/expected index 42cadb93b477..c4946d27dafd 100644 --- a/tests/ui/solver-attribute/no-arg/expected +++ b/tests/ui/solver-attribute/no-arg/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver]\ | ^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/unknown/expected b/tests/ui/solver-attribute/unknown/expected index 7d3bf6d61ef3..7825d4e8c93c 100644 --- a/tests/ui/solver-attribute/unknown/expected +++ b/tests/ui/solver-attribute/unknown/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(foo)]\ | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error From 930380e07287fba2acbad071307e3d0f413eaab3 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 28 Nov 2023 13:53:05 -0800 Subject: [PATCH 5/8] Apply suggestions from code review Co-authored-by: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> --- kani-compiler/src/kani_middle/reachability.rs | 4 ++-- kani-compiler/src/kani_middle/stubbing/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 159905d9bf85..1293aff13e43 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -241,8 +241,8 @@ impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { let concrete_kind = concrete_ty.kind(); let trait_kind = trait_ty.kind(); - assert!(!concrete_kind.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); - assert!(trait_kind.is_trait(), "Expected a trait: {trait_ty:?}"); + assert!(!concrete_kind.is_trait(), "expected a concrete type, but found `{concrete_ty:?}`"); + assert!(trait_kind.is_trait(), "expected a trait `{trait_ty:?}`"); if let Some(principal) = trait_kind.trait_principal() { // A trait object type can have multiple trait bounds but up to one non-auto-trait // bound. This non-auto-trait, named principal, is the only one that can have methods. diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index 748365a6d653..03298456c522 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -41,7 +41,7 @@ pub fn harness_stub_map( /// Stubbing may cause an instance to not be correctly instantiated since we delay checking its /// generic bounds. /// -/// In stable mir, trying to retrieve an Instance::body() will ICE if we cannot evaluate a +/// In stable MIR, trying to retrieve an `Instance::body()` will ICE if we cannot evaluate a /// constant as expected. For now, use internal APIs to anticipate this issue. pub fn validate_instance(tcx: TyCtxt, instance: Instance) -> bool { let internal_instance = rustc_internal::internal(instance); From 98058c254f344fa94f08b7532c178707aa6b8645 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 29 Nov 2023 17:39:39 -0800 Subject: [PATCH 6/8] Add dev documentation on the StableMIR migration --- docs/src/dev-documentation.md | 1 + docs/src/stable_mir.md | 94 +++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 docs/src/stable_mir.md diff --git a/docs/src/dev-documentation.md b/docs/src/dev-documentation.md index 639b7125ae78..173588586a3a 100644 --- a/docs/src/dev-documentation.md +++ b/docs/src/dev-documentation.md @@ -14,6 +14,7 @@ developers (including external contributors): 3. [Development setup recommendations for working with `cbmc`](./cbmc-hacks.md). 4. [Development setup recommendations for working with `rustc`](./rustc-hacks.md). 5. [Guide for testing in Kani](./testing.md). + 6. [Transition to StableMIR](./stable_mir.md). > **NOTE**: The developer documentation is intended for Kani developers and not users. At present, the project is under heavy development and some items diff --git a/docs/src/stable_mir.md b/docs/src/stable_mir.md new file mode 100644 index 000000000000..e21926ba6141 --- /dev/null +++ b/docs/src/stable_mir.md @@ -0,0 +1,94 @@ +# Transition to StableMIR + +We have partnered with the Rust compiler team in the initiative to introduce stable +APIs to the compiler that can be used by third-party tools, which is known as the +[Stable MIR Project](https://github.com/rust-lang/project-stable-mir), or just StableMIR. +This means that we are starting to use the new APIs introduced by this project as is, +despite them not being stable yet. + +### StableMIR APIs + +For now, the StableMIR APIs are exposed as a crate in the compiler named `stable_mir`. +This crate includes the definition of structures and methods to be stabilized, +which are expected to become the stable APIs in the compiler. +To reduce the migration burden, these APIs are somewhat close to the original compiler interfaces. +However, some changes have been made to make these APIs cleaner and easier to use. + +For example: +1. The usage of the compiler context (aka `TyCtxt`) is transparent to the user. + The StableMIR implementation caches this context in a thread local variable, + and retrieves it whenever necessary. + - Because of that, code that uses the StableMIR has to be invoked inside a `run` call. +2. The `DefId` has been specialized into multiple types, + making its usage less error prone. E.g.: + `FnDef` represents the definition of a function, + while `StaticDef` is the definition of a static variable. + - Note that the same `DefId` may be mapped to different definitions according to its context. + For example, an `InstanceDef` and a `FnDef` may represent the same function definition. +3. Methods that were used to be exposed as part of `TyCtxt` are now part of a type. + Example, the function `TyCtxt.instance_mir` is now `Instance::body`. +4. There is no need for explicit instantiation (monomorphization) of items from an`Instance::body`. + This method already instantiate all types and resolve all constants before converting + it to stable APIs. + + +### Performance + +Since the new APIs require converting internal data to a stable representation, +the APIs were also designed to avoid needless conversions, +and to allow extra information to be retrieved by demand. + +For example, `Ty` is just an identifier, while `TyKind` is a structure that can be retrieved via `Ty::kind` method. +The `TyKind` is a more structured object, thus, +it is only generated when the `kind` method is invoked. +Since this translation is not cached, +many of the functions that the rust compiler used to expose in `Ty`, +is now only part of `TyKind`. +The reason being that there is no cache for the `TyKind`, +and users should do the caching themselves to avoid needless translations. + +From our initial experiments with the transition of the reachability algorithm to use StableMIR, +there is a small penalty of using StableMIR over internal rust compiler APIs. +However, they are still fairly efficient and it did not impact the overall compilation time. + +### Interface with internal APIs + +To reduce the burden of migrating to StableMIR, +and to allow StableMIR to be used together with internal APIs, +there are two helpful methods to convert StableMIR constructs to internal rustc and back: + - `rustc_internal::internal()`: Convert a Stable item into an internal one. + - `rustc_internal::stable()`: Convert an internal item into a Stable one. + +Both of these methods are inside `rustc_smir` crate in the `rustc_internal` +module inside the compiler. +Note that there is no plan to stabilize any of these methods, +and there's also no guarantee on its support and coverage. + +The conversion is not implemented for all items, and some conversions may be incomplete. +Please proceed with caution when using these methods. + +Besides that, do not invoke any other `rustc_smir` methods, except for `run`. +This crate methods are not meant to be invoked externally. +Note that, the method `run` will also eventually be replaced by a Stable driver. + +### Creating and modifying StableMIR items + +For now, StableMIR should only be used to get information from the compiler. +Do not try to create or modify items directly, as it may not work. +This may result in incorrect behavior or an internal compiler error (ICE). + +## Naming conventions in Kani + +As we adopt StableMIR, we would like to introduce a few conventions to make it easier to maintain the code. +Whenever there is a name conflict, for example, `Ty` or `codegen_ty`, +use a suffix to indicate which API you are using. +`Stable` for StableMIR and `Internal` for `rustc` internal APIs. + +A module should either default its naming to Stable APIs or Internal APIs. +I.e.: Modules that have been migrated to StableMIR don't need to add the `Stable` suffix to stable items. +While those that haven't been migrated, should add `Stable`, but no `Internal` is needed. + +For example, the `codegen::typ` module will likely include methods: + +`codegen_ty(&mut self, Ty)` and `codegen_ty_stable(&mut, TyStable)` to handle +internal and stable APIs. From 78e09825582b16d2c862ef2e22ad09f75d75883c Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 29 Nov 2023 17:46:51 -0800 Subject: [PATCH 7/8] Address feedback + merge with main --- kani-compiler/src/kani_compiler.rs | 10 +- kani-compiler/src/kani_middle/provide.rs | 15 ++- kani-compiler/src/kani_middle/reachability.rs | 114 ++++++++---------- 3 files changed, 67 insertions(+), 72 deletions(-) diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 56cd02ce542d..fc04b68ec747 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -34,6 +34,7 @@ use rustc_hir::definitions::DefPathHash; use rustc_interface::Config; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ErrorOutputType, OutputType}; +use rustc_smir::rustc_internal; use rustc_span::ErrorGuaranteed; use std::collections::{BTreeMap, HashMap}; use std::fs::File; @@ -400,9 +401,12 @@ impl Callbacks for KaniCompiler { ) -> Compilation { if self.stage.is_init() { self.stage = rustc_queries.global_ctxt().unwrap().enter(|tcx| { - check_crate_items(tcx, self.queries.lock().unwrap().args().ignore_global_asm); - self.process_harnesses(tcx) - }); + rustc_internal::run(tcx, || { + check_crate_items(tcx, self.queries.lock().unwrap().args().ignore_global_asm); + self.process_harnesses(tcx) + }) + .unwrap() + }) } self.prepare_codegen() diff --git a/kani-compiler/src/kani_middle/provide.rs b/kani-compiler/src/kani_middle/provide.rs index 033eae3b460f..3c7664d076d6 100644 --- a/kani-compiler/src/kani_middle/provide.rs +++ b/kani-compiler/src/kani_middle/provide.rs @@ -76,11 +76,14 @@ fn collect_and_partition_mono_items( tcx: TyCtxt, key: (), ) -> queries::collect_and_partition_mono_items::ProvidedValue { - let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); - let local_reachable = filter_crate_items(tcx, |_, def_id| { - tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id) - }); - // We do not actually need the value returned here. - collect_reachable_items(tcx, &local_reachable); + rustc_smir::rustc_internal::run(tcx, || { + let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); + let local_reachable = filter_crate_items(tcx, |_, def_id| { + tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id) + }); + // We do not actually need the value returned here. + collect_reachable_items(tcx, &local_reachable); + }) + .unwrap(); (rustc_interface::DEFAULT_QUERY_PROVIDERS.collect_and_partition_mono_items)(tcx, key) } diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 1293aff13e43..d56b7b5db65d 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -45,31 +45,27 @@ pub fn collect_reachable_items<'tcx>( tcx: TyCtxt<'tcx>, starting_points: &[InternalMonoItem<'tcx>], ) -> Vec> { - rustc_smir::rustc_internal::run(tcx, || { - // For each harness, collect items using the same collector. - // I.e.: This will return any item that is reachable from one or more of the starting points. - let mut collector = MonoItemsCollector::new(tcx); - for item in starting_points { - collector.collect(rustc_internal::stable(item)); - } + // For each harness, collect items using the same collector. + // I.e.: This will return any item that is reachable from one or more of the starting points. + let mut collector = MonoItemsCollector::new(tcx); + for item in starting_points { + collector.collect(rustc_internal::stable(item)); + } - #[cfg(debug_assertions)] - collector - .call_graph - .dump_dot(tcx) - .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); - - tcx.sess.abort_if_errors(); - - // Sort the result so code generation follows deterministic order. - // This helps us to debug the code, but it also provides the user a good experience since the - // order of the errors and warnings is stable. - let mut sorted_items: Vec<_> = - collector.collected.iter().map(rustc_internal::internal).collect(); - sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); - sorted_items - }) - .unwrap() + #[cfg(debug_assertions)] + collector + .call_graph + .dump_dot(tcx) + .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); + + tcx.sess.abort_if_errors(); + // Sort the result so code generation follows deterministic order. + // This helps us to debug the code, but it also provides the user a good experience since the + // order of the errors and warnings is stable. + let mut sorted_items: Vec<_> = + collector.collected.iter().map(rustc_internal::internal).collect(); + sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); + sorted_items } /// Collect all (top-level) items in the crate that matches the given predicate. @@ -78,22 +74,19 @@ pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec where F: Fn(TyCtxt, DefId) -> bool, { - rustc_smir::rustc_internal::run(tcx, || { - let crate_items = stable_mir::all_local_items(); - // Filter regular items. - crate_items - .iter() - .filter_map(|item| { - // Only collect monomorphic items. - Instance::try_from(*item).ok().and_then(|instance| { - let def_id = rustc_internal::internal(item); - predicate(tcx, def_id) - .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) - }) + let crate_items = stable_mir::all_local_items(); + // Filter regular items. + crate_items + .iter() + .filter_map(|item| { + // Only collect monomorphic items. + Instance::try_from(*item).ok().and_then(|instance| { + let def_id = rustc_internal::internal(item); + predicate(tcx, def_id) + .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) }) - .collect::>() - }) - .unwrap() + }) + .collect::>() } /// Use a predicate to find `const` declarations, then extract all items reachable from them. @@ -104,30 +97,27 @@ pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec bool, { - rustc_smir::rustc_internal::run(tcx, || { - let crate_items = stable_mir::all_local_items(); - let mut roots = Vec::new(); - // Filter regular items. - for item in crate_items { - // Only collect monomorphic items. - if let Ok(instance) = Instance::try_from(item) { - let def_id = rustc_internal::internal(&item); - if predicate(tcx, def_id) { - let body = instance.body().unwrap(); - let mut collector = MonoItemsFnCollector { - tcx, - body: &body, - collected: FxHashSet::default(), - instance: &instance, - }; - collector.visit_body(&body); - roots.extend(collector.collected.iter().map(rustc_internal::internal)); - } + let crate_items = stable_mir::all_local_items(); + let mut roots = Vec::new(); + // Filter regular items. + for item in crate_items { + // Only collect monomorphic items. + if let Ok(instance) = Instance::try_from(item) { + let def_id = rustc_internal::internal(&item); + if predicate(tcx, def_id) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx, + body: &body, + collected: FxHashSet::default(), + instance: &instance, + }; + collector.visit_body(&body); + roots.extend(collector.collected.iter().map(rustc_internal::internal)); } } - roots - }) - .unwrap() + } + roots } struct MonoItemsCollector<'tcx> { @@ -141,7 +131,6 @@ struct MonoItemsCollector<'tcx> { call_graph: debug::CallGraph, } -// TODO: Need to figure out how to handle stubbing errors. impl<'tcx> MonoItemsCollector<'tcx> { pub fn new(tcx: TyCtxt<'tcx>) -> Self { MonoItemsCollector { @@ -423,7 +412,6 @@ impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { // even the return type, for instance for a // trait like `FromIterator`. let receiver_ty = args.0[0].expect_ty(); - trace!(?receiver_ty, ?callee, "**** receiver:"); let sep = callee.rfind("::").unwrap(); let trait_ = &callee[..sep]; self.tcx.sess.span_err( From f33a537d0e1db7f3c614cfd8d225619da43103f3 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 30 Nov 2023 10:44:30 -0800 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- docs/src/stable_mir.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/stable_mir.md b/docs/src/stable_mir.md index e21926ba6141..79e63a98cba1 100644 --- a/docs/src/stable_mir.md +++ b/docs/src/stable_mir.md @@ -25,10 +25,10 @@ For example: while `StaticDef` is the definition of a static variable. - Note that the same `DefId` may be mapped to different definitions according to its context. For example, an `InstanceDef` and a `FnDef` may represent the same function definition. -3. Methods that were used to be exposed as part of `TyCtxt` are now part of a type. +3. Methods that used to be exposed as part of `TyCtxt` are now part of a type. Example, the function `TyCtxt.instance_mir` is now `Instance::body`. 4. There is no need for explicit instantiation (monomorphization) of items from an`Instance::body`. - This method already instantiate all types and resolve all constants before converting + This method already instantiates all types and resolves all constants before converting it to stable APIs. @@ -36,7 +36,7 @@ For example: Since the new APIs require converting internal data to a stable representation, the APIs were also designed to avoid needless conversions, -and to allow extra information to be retrieved by demand. +and to allow extra information to be retrieved on demand. For example, `Ty` is just an identifier, while `TyKind` is a structure that can be retrieved via `Ty::kind` method. The `TyKind` is a more structured object, thus, @@ -68,7 +68,7 @@ The conversion is not implemented for all items, and some conversions may be inc Please proceed with caution when using these methods. Besides that, do not invoke any other `rustc_smir` methods, except for `run`. -This crate methods are not meant to be invoked externally. +This crate's methods are not meant to be invoked externally. Note that, the method `run` will also eventually be replaced by a Stable driver. ### Creating and modifying StableMIR items