// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Finds items that are externally reachable, to determine which items // need to have their metadata (and possibly their AST) serialized. // All items that can be referred to through an exported name are // reachable, and when a reachable thing is inline or generic, it // makes all other generics or inline functions that it references // reachable as well. use middle::ty; use middle::typeck; use std::hashmap::HashSet; use syntax::ast::*; use syntax::ast_map; use syntax::ast_util::def_id_of_def; use syntax::attr; use syntax::parse::token; use syntax::visit::Visitor; use syntax::visit; // Returns true if the given set of attributes contains the `#[inline]` // attribute. fn attributes_specify_inlining(attrs: &[Attribute]) -> bool { attr::contains_name(attrs, "inline") } // Returns true if the given set of generics implies that the item it's // associated with must be inlined. fn generics_require_inlining(generics: &Generics) -> bool { !generics.ty_params.is_empty() } // Returns true if the given item must be inlined because it may be // monomorphized or it was marked with `#[inline]`. This will only return // true for functions. fn item_might_be_inlined(item: @item) -> bool { if attributes_specify_inlining(item.attrs) { return true } match item.node { item_fn(_, _, _, ref generics, _) => { generics_require_inlining(generics) } _ => false, } } // Returns true if the given type method must be inlined because it may be // monomorphized or it was marked with `#[inline]`. fn ty_method_might_be_inlined(ty_method: &TypeMethod) -> bool { attributes_specify_inlining(ty_method.attrs) || generics_require_inlining(&ty_method.generics) } // Returns true if the given trait method must be inlined because it may be // monomorphized or it was marked with `#[inline]`. fn trait_method_might_be_inlined(trait_method: &trait_method) -> bool { match *trait_method { required(ref ty_method) => ty_method_might_be_inlined(ty_method), provided(_) => true } } // The context we're in. If we're in a public context, then public symbols are // marked reachable. If we're in a private context, then only trait // implementations are marked reachable. #[deriving(Clone, Eq)] enum PrivacyContext { PublicContext, PrivateContext, } // Information needed while computing reachability. struct ReachableContext { // The type context. tcx: ty::ctxt, // The method map, which links node IDs of method call expressions to the // methods they've been resolved to. method_map: typeck::method_map, // The set of items which must be exported in the linkage sense. reachable_symbols: @mut HashSet, // A worklist of item IDs. Each item ID in this worklist will be inlined // and will be scanned for further references. worklist: @mut ~[NodeId], } struct ReachableVisitor { reachable_symbols: @mut HashSet, worklist: @mut ~[NodeId], } impl Visitor for ReachableVisitor { fn visit_item(&mut self, item:@item, privacy_context:PrivacyContext) { match item.node { item_fn(*) => { if privacy_context == PublicContext { self.reachable_symbols.insert(item.id); } if item_might_be_inlined(item) { self.worklist.push(item.id) } } item_struct(ref struct_def, _) => { match struct_def.ctor_id { Some(ctor_id) if privacy_context == PublicContext => { self.reachable_symbols.insert(ctor_id); } Some(_) | None => {} } } item_enum(ref enum_def, _) => { if privacy_context == PublicContext { for variant in enum_def.variants.iter() { self.reachable_symbols.insert(variant.node.id); } } } item_impl(ref generics, ref trait_ref, _, ref methods) => { // XXX(pcwalton): We conservatively assume any methods // on a trait implementation are reachable, when this // is not the case. We could be more precise by only // treating implementations of reachable or cross- // crate traits as reachable. let should_be_considered_public = |method: @method| { (method.vis == public && privacy_context == PublicContext) || trait_ref.is_some() }; // Mark all public methods as reachable. for &method in methods.iter() { if should_be_considered_public(method) { self.reachable_symbols.insert(method.id); } } if generics_require_inlining(generics) { // If the impl itself has generics, add all public // symbols to the worklist. for &method in methods.iter() { if should_be_considered_public(method) { self.worklist.push(method.id) } } } else { // Otherwise, add only public methods that have // generics to the worklist. for method in methods.iter() { let generics = &method.generics; let attrs = &method.attrs; if generics_require_inlining(generics) || attributes_specify_inlining(*attrs) || should_be_considered_public(*method) { self.worklist.push(method.id) } } } } item_trait(_, _, ref trait_methods) => { // Mark all provided methods as reachable. if privacy_context == PublicContext { for trait_method in trait_methods.iter() { match *trait_method { provided(method) => { self.reachable_symbols.insert(method.id); self.worklist.push(method.id) } required(_) => {} } } } } _ => {} } if item.vis == public && privacy_context == PublicContext { visit::walk_item(self, item, PublicContext) } else { visit::walk_item(self, item, PrivateContext) } } } struct MarkSymbolVisitor { worklist: @mut ~[NodeId], method_map: typeck::method_map, tcx: ty::ctxt, reachable_symbols: @mut HashSet, } impl Visitor<()> for MarkSymbolVisitor { fn visit_expr(&mut self, expr:@Expr, _:()) { match expr.node { ExprPath(_) => { let def = match self.tcx.def_map.find(&expr.id) { Some(&def) => def, None => { self.tcx.sess.span_bug(expr.span, "def ID not in def map?!") } }; let def_id = def_id_of_def(def); if ReachableContext:: def_id_represents_local_inlined_item(self.tcx, def_id) { self.worklist.push(def_id.node) } self.reachable_symbols.insert(def_id.node); } ExprMethodCall(*) => { match self.method_map.find(&expr.id) { Some(&typeck::method_map_entry { origin: typeck::method_static(def_id), _ }) => { if ReachableContext:: def_id_represents_local_inlined_item( self.tcx, def_id) { self.worklist.push(def_id.node) } self.reachable_symbols.insert(def_id.node); } Some(_) => {} None => { self.tcx.sess.span_bug(expr.span, "method call expression \ not in method map?!") } } } _ => {} } visit::walk_expr(self, expr, ()) } } impl ReachableContext { // Creates a new reachability computation context. fn new(tcx: ty::ctxt, method_map: typeck::method_map) -> ReachableContext { ReachableContext { tcx: tcx, method_map: method_map, reachable_symbols: @mut HashSet::new(), worklist: @mut ~[], } } // Step 1: Mark all public symbols, and add all public symbols that might // be inlined to a worklist. fn mark_public_symbols(&self, crate: &Crate) { let reachable_symbols = self.reachable_symbols; let worklist = self.worklist; let mut visitor = ReachableVisitor { reachable_symbols: reachable_symbols, worklist: worklist, }; visit::walk_crate(&mut visitor, crate, PublicContext); } // Returns true if the given def ID represents a local item that is // eligible for inlining and false otherwise. fn def_id_represents_local_inlined_item(tcx: ty::ctxt, def_id: DefId) -> bool { if def_id.crate != LOCAL_CRATE { return false } let node_id = def_id.node; match tcx.items.find(&node_id) { Some(&ast_map::node_item(item, _)) => { match item.node { item_fn(*) => item_might_be_inlined(item), _ => false, } } Some(&ast_map::node_trait_method(trait_method, _, _)) => { match *trait_method { required(_) => false, provided(_) => true, } } Some(&ast_map::node_method(method, impl_did, _)) => { if generics_require_inlining(&method.generics) || attributes_specify_inlining(method.attrs) { true } else { // Check the impl. If the generics on the self type of the // impl require inlining, this method does too. assert!(impl_did.crate == LOCAL_CRATE); match tcx.items.find(&impl_did.node) { Some(&ast_map::node_item(item, _)) => { match item.node { item_impl(ref generics, _, _, _) => { generics_require_inlining(generics) } _ => false } } Some(_) => { tcx.sess.span_bug(method.span, "method is not inside an \ impl?!") } None => { tcx.sess.span_bug(method.span, "the impl that this method is \ supposedly inside of doesn't \ exist in the AST map?!") } } } } Some(_) => false, None => false // This will happen for default methods. } } // Helper function to set up a visitor for `propagate()` below. fn init_visitor(&self) -> MarkSymbolVisitor { let (worklist, method_map) = (self.worklist, self.method_map); let (tcx, reachable_symbols) = (self.tcx, self.reachable_symbols); MarkSymbolVisitor { worklist: worklist, method_map: method_map, tcx: tcx, reachable_symbols: reachable_symbols, } } // Step 2: Mark all symbols that the symbols on the worklist touch. fn propagate(&self) { let mut visitor = self.init_visitor(); let mut scanned = HashSet::new(); while self.worklist.len() > 0 { let search_item = self.worklist.pop(); if scanned.contains(&search_item) { loop } scanned.insert(search_item); self.reachable_symbols.insert(search_item); // Find the AST block corresponding to the item and visit it, // marking all path expressions that resolve to something // interesting. match self.tcx.items.find(&search_item) { Some(&ast_map::node_item(item, _)) => { match item.node { item_fn(_, _, _, _, ref search_block) => { visit::walk_block(&mut visitor, search_block, ()) } _ => { self.tcx.sess.span_bug(item.span, "found non-function item \ in worklist?!") } } } Some(&ast_map::node_trait_method(trait_method, _, _)) => { match *trait_method { required(ref ty_method) => { self.tcx.sess.span_bug(ty_method.span, "found required method in \ worklist?!") } provided(ref method) => { visit::walk_block(&mut visitor, &method.body, ()) } } } Some(&ast_map::node_method(ref method, _, _)) => { visit::walk_block(&mut visitor, &method.body, ()) } Some(_) => { let ident_interner = token::get_ident_interner(); let desc = ast_map::node_id_to_str(self.tcx.items, search_item, ident_interner); self.tcx.sess.bug(format!("found unexpected thingy in \ worklist: {}", desc)) } None => { self.tcx.sess.bug(format!("found unmapped ID in worklist: \ {}", search_item)) } } } } // Step 3: Mark all destructors as reachable. // // XXX(pcwalton): This is a conservative overapproximation, but fixing // this properly would result in the necessity of computing *type* // reachability, which might result in a compile time loss. fn mark_destructors_reachable(&self) { for (_, destructor_def_id) in self.tcx.destructor_for_type.iter() { if destructor_def_id.crate == LOCAL_CRATE { self.reachable_symbols.insert(destructor_def_id.node); } } } } pub fn find_reachable(tcx: ty::ctxt, method_map: typeck::method_map, crate: &Crate) -> @mut HashSet { // XXX(pcwalton): We only need to mark symbols that are exported. But this // is more complicated than just looking at whether the symbol is `pub`, // because it might be the target of a `pub use` somewhere. For now, I // think we are fine, because you can't `pub use` something that wasn't // exported due to the bug whereby `use` only looks through public // modules even if you're inside the module the `use` appears in. When // this bug is fixed, however, this code will need to be updated. Probably // the easiest way to fix this (although a conservative overapproximation) // is to have the name resolution pass mark all targets of a `pub use` as // "must be reachable". let reachable_context = ReachableContext::new(tcx, method_map); // Step 1: Mark all public symbols, and add all public symbols that might // be inlined to a worklist. reachable_context.mark_public_symbols(crate); // Step 2: Mark all symbols that the symbols on the worklist touch. reachable_context.propagate(); // Step 3: Mark all destructors as reachable. reachable_context.mark_destructors_reachable(); // Return the set of reachable symbols. reachable_context.reachable_symbols }