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..777b29e2542e 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -13,158 +13,151 @@ //! - 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); - } - - #[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(); + 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)); + } - // 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(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 +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>, - #[cfg(debug_assertions)] - call_graph: debug::CallGraph<'tcx>, + 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(debug_assertions)] + #[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<'tcx>) { + pub fn collect(&mut self, root: MonoItem) { debug!(?root, "collect"); self.queue.push(root); self.reachable_items(); @@ -175,16 +168,16 @@ 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); vec![] } }; - #[cfg(debug_assertions)] + #[cfg(disable_debug_assertions)] self.call_graph.add_edges(to_visit, &next_items); self.queue @@ -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:?}"), - } - } - } -}