diff --git a/src/librustc/dep_graph/README.md b/src/librustc/dep_graph/README.md new file mode 100644 index 00000000000..21742d9935d --- /dev/null +++ b/src/librustc/dep_graph/README.md @@ -0,0 +1,390 @@ +# Dependency graph for incremental compilation + +This module contains the infrastructure for managing the incremental +compilation dependency graph. This README aims to explain how it ought +to be used. In this document, we'll first explain the overall +strategy, and then share some tips for handling specific scenarios. + +The high-level idea is that we want to instrument the compiler to +track which parts of the AST and other IR are read/written by what. +This way, when we come back later, we can look at this graph and +determine what work needs to be redone. + +### The dependency graph + +The nodes of the graph are defined by the enum `DepNode`. They represent +one of three things: + +1. HIR nodes (like `Hir(DefId)`) represent the HIR input itself. +2. Data nodes (like `ItemSignature(DefId)`) represent some computed + information about a particular item. +3. Procedure notes (like `CoherenceCheckImpl(DefId)`) represent some + procedure that is executing. Usually this procedure is + performing some kind of check for errors. You can think of them as + computed values where the value being computed is `()` (and the + value may fail to be computed, if an error results). + +An edge `N1 -> N2` is added between two nodes if either: + +- the value of `N1` is used to compute `N2`; +- `N1` is read by the procedure `N2`; +- the procedure `N1` writes the value `N2`. + +The latter two conditions are equivalent to the first one if you think +of procedures as values. + +### Basic tracking + +There is a very general strategy to ensure that you have a correct, if +sometimes overconservative, dependency graph. The two main things you have +to do are (a) identify shared state and (b) identify the current tasks. + +### Identifying shared state + +Identify "shared state" that will be written by one pass and read by +another. In particular, we need to identify shared state that will be +read "across items" -- that is, anything where changes in one item +could invalidate work done for other items. So, for example: + +1. The signature for a function is "shared state". +2. The computed type of some expression in the body of a function is + not shared state, because if it changes it does not itself + invalidate other functions (though it may be that it causes new + monomorphizations to occur, but that's handled independently). + +Put another way: if the HIR for an item changes, we are going to +recompile that item for sure. But we need the dep tracking map to tell +us what *else* we have to recompile. Shared state is anything that is +used to communicate results from one item to another. + +### Identifying the current task + +The dep graph always tracks a current task: this is basically the +`DepNode` that the compiler is computing right now. Typically it would +be a procedure node, but it can also be a data node (as noted above, +the two are kind of equivalent). + +You set the current task by calling `dep_graph.in_task(node)`. For example: + +```rust +let _task = tcx.dep_graph.in_task(DepNode::Privacy); +``` + +Now all the code until `_task` goes out of scope will be considered +part of the "privacy task". + +The tasks are maintained in a stack, so it is perfectly fine to nest +one task within another. Because pushing a task is considered to be +computing a value, when you nest a task `N2` inside of a task `N1`, we +automatically add an edge `N2 -> N1` (since `N1` presumably needed the +result of `N2` to complete): + +```rust +let _n1 = tcx.dep_graph.in_task(DepNode::N1); +let _n2 = tcx.dep_graph.in_task(DepNode::N2); +// this will result in an edge N1 -> n2 +``` + +### Ignore tasks + +Although it is rarely needed, you can also push a special "ignore" +task: + +```rust +let _ignore = tc.dep_graph.in_ignore(); +``` + +This will cause all read/write edges to be ignored until it goes out +of scope or until something else is pushed. For example, we could +suppress the edge between nested tasks like so: + +```rust +let _n1 = tcx.dep_graph.in_task(DepNode::N1); +let _ignore = tcx.dep_graph.in_ignore(); +let _n2 = tcx.dep_graph.in_task(DepNode::N2); +// now no edge is added +``` + +### Tracking reads and writes + +We need to identify what shared state is read/written by the current +task as it executes. The most fundamental way of doing that is to invoke +the `read` and `write` methods on `DepGraph`: + +```rust +// Adds an edge from DepNode::Hir(some_def_id) to the current task +tcx.dep_graph.read(DepNode::Hir(some_def_id)) + +// Adds an edge from the current task to DepNode::ItemSignature(some_def_id) +tcx.dep_graph.write(DepNode::ItemSignature(some_def_id)) +``` + +However, you should rarely need to invoke those methods directly. +Instead, the idea is to *encapsulate* shared state into some API that +will invoke `read` and `write` automatically. The most common way to +do this is to use a `DepTrackingMap`, described in the next section, +but any sort of abstraction barrier will do. In general, the strategy +is that getting access to information implicitly adds an appropriate +`read`. So, for example, when you use the +`dep_graph::visit_all_items_in_krate` helper method, it will visit +each item `X`, start a task `Foo(X)` for that item, and automatically +add an edge `Hir(X) -> Foo(X)`. This edge is added because the code is +being given access to the HIR node for `X`, and hence it is expected +to read from it. Similarly, reading from the `tcache` map for item `X` +(which is a `DepTrackingMap`, described below) automatically invokes +`dep_graph.read(ItemSignature(X))`. + +To make this strategy work, a certain amount of indirection is +required. For example, modules in the HIR do not have direct pointers +to the items that they contain. Rather, they contain node-ids -- one +can then ask the HIR map for the item with a given node-id. This gives +us an opportunity to add an appropriate read edge. + +#### Explicit calls to read and write when starting a new subtask + +One time when you *may* need to call `read` and `write` directly is +when you push a new task onto the stack, either by calling `in_task` +as shown above or indirectly, such as with the `memoize` pattern +described below. In that case, any data that the task has access to +from the surrounding environment must be explicitly "read". For +example, in `librustc_typeck`, the collection code visits all items +and, among other things, starts a subtask producing its signature +(what follows is simplified pseudocode, of course): + +```rust +fn visit_item(item: &hir::Item) { + // Here, current subtask is "Collect(X)", and an edge Hir(X) -> Collect(X) + // has automatically been added by `visit_all_items_in_krate`. + let sig = signature_of_item(item); +} + +fn signature_of_item(item: &hir::Item) { + let def_id = tcx.map.local_def_id(item.id); + let task = tcx.dep_graph.in_task(DepNode::ItemSignature(def_id)); + tcx.dep_graph.read(DepNode::Hir(def_id)); // <-- the interesting line + ... +} +``` + +Here you can see that, in `signature_of_item`, we started a subtask +corresponding to producing the `ItemSignature`. This subtask will read from +`item` -- but it gained access to `item` implicitly. This means that if it just +reads from `item`, there would be missing edges in the graph: + + Hir(X) --+ // added by the explicit call to `read` + | | + | +---> ItemSignature(X) -> Collect(X) + | ^ + | | + +---------------------------------+ // added by `visit_all_items_in_krate` + +In particular, the edge from `Hir(X)` to `ItemSignature(X)` is only +present because we called `read` ourselves when entering the `ItemSignature(X)` +task. + +So, the rule of thumb: when entering a new task yourself, register +reads on any shared state that you inherit. (This actually comes up +fairly infrequently though: the main place you need caution is around +memoization.) + +#### Dependency tracking map + +`DepTrackingMap` is a particularly convenient way to correctly store +shared state. A `DepTrackingMap` is a special hashmap that will add +edges automatically when `get` and `insert` are called. The idea is +that, when you get/insert a value for the key `K`, we will add an edge +from/to the node `DepNode::Variant(K)` (for some variant specific to +the map). + +Each `DepTrackingMap` is parameterized by a special type `M` that +implements `DepTrackingMapConfig`; this trait defines the key and value +types of the map, and also defines a fn for converting from the key to +a `DepNode` label. You don't usually have to muck about with this by +hand, there is a macro for creating it. You can see the complete set +of `DepTrackingMap` definitions in `librustc/middle/ty/maps.rs`. + +As an example, let's look at the `adt_defs` map. The `adt_defs` map +maps from the def-id of a struct/enum to its `AdtDef`. It is defined +using this macro: + +```rust +dep_map_ty! { AdtDefs: ItemSignature(DefId) -> ty::AdtDefMaster<'tcx> } +// ~~~~~~~ ~~~~~~~~~~~~~ ~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ +// | | Key type Value type +// | DepNode variant +// Name of map id type +``` + +this indicates that a map id type `AdtDefs` will be created. The key +of the map will be a `DefId` and value will be +`ty::AdtDefMaster<'tcx>`. The `DepNode` will be created by +`DepNode::ItemSignature(K)` for a given key. + +Once that is done, you can just use the `DepTrackingMap` like any +other map: + +```rust +let mut map: DepTrackingMap = DepTrackingMap::new(dep_graph); +map.insert(key, value); // registers dep_graph.write +map.get(key; // registers dep_graph.read +``` + +#### Memoization + +One particularly interesting case is memoization. If you have some +shared state that you compute in a memoized fashion, the correct thing +to do is to define a `RefCell` for it and use the +`memoize` helper: + +```rust +map.memoize(key, || /* compute value */) +``` + +This will create a graph that looks like + + ... -> MapVariant(key) -> CurrentTask + +where `MapVariant` is the `DepNode` variant that the map is associated with, +and `...` are whatever edges the `/* compute value */` closure creates. + +In particular, using the memoize helper is much better than writing +the obvious code yourself: + +``` +if let Some(result) = map.get(key) { + return result; +} +let value = /* compute value */; +map.insert(key, value); +``` + +If you write that code manually, the dependency graph you get will +include artificial edges that are not necessary. For example, imagine that +two tasks, A and B, both invoke the manual memoization code, but A happens +to go first. The resulting graph will be: + + ... -> A -> MapVariant(key) -> B + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ // caused by A writing to MapVariant(key) + ~~~~~~~~~~~~~~~~~~~~ // caused by B reading from MapVariant(key) + +This graph is not *wrong*, but it encodes a path from A to B that +should not exist. In contrast, using the memoized helper, you get: + + ... -> MapVariant(key) -> A + | + +----------> B + +which is much cleaner. + +**Be aware though that the closure is executed with `MapVariant(key)` +pushed onto the stack as the current task!** That means that you must +add explicit `read` calls for any shared state that it accesses +implicitly from its environment. See the section on "explicit calls to +read and write when starting a new subtask" above for more details. + +### How to decide where to introduce a new task + +Certainly, you need at least one task on the stack: any attempt to +`read` or `write` shared state will panic if there is no current +task. But where does it make sense to introduce subtasks? The basic +rule is that a subtask makes sense for any discrete unit of work you +may want to skip in the future. Adding a subtask separates out the +reads/writes from *that particular subtask* versus the larger +context. An example: you might have a 'meta' task for all of borrow +checking, and then subtasks for borrow checking individual fns. (Seen +in this light, memoized computations are just a special case where we +may want to avoid redoing the work even within the context of one +compilation.) + +The other case where you might want a subtask is to help with refining +the reads/writes for some later bit of work that needs to be memoized. +For example, we create a subtask for type-checking the body of each +fn. However, in the initial version of incr. comp. at least, we do +not expect to actually *SKIP* type-checking -- we only expect to skip +trans. However, it's still useful to create subtasks for type-checking +individual items, because, otherwise, if a fn sig changes, we won't +know which callers are affected -- in fact, because the graph would be +so coarse, we'd just have to retrans everything, since we can't +distinguish which fns used which fn sigs. + +### Testing the dependency graph + +There are various ways to write tests against the dependency graph. +The simplest mechanism are the +`#[rustc_if_this_changed]` and `#[rustc_then_this_would_need]` +annotations. These are used in compile-fail tests to test whether the +expected set of paths exist in the dependency graph. As an example, +see `src/test/compile-fail/dep-graph-caller-callee.rs`. + +The idea is that you can annotate a test like: + +```rust +#[rustc_if_this_changed] +fn foo() { } + +#[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR OK +fn bar() { foo(); } + +#[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path +fn baz() { } +``` + +This will check whether there is a path in the dependency graph from +`Hir(foo)` to `TypeckItemBody(bar)`. An error is reported for each +`#[rustc_then_this_would_need]` annotation that indicates whether a +path exists. `//~ ERROR` annotations can then be used to test if a +path is found (as demonstrated above). + +### Debugging the dependency graph + +The compiler is also capable of dumping the dependency graph for your +debugging pleasure. To do so, pass the `-Z dump-dep-graph` flag. The +graph will be dumped to `dep_graph.{txt,dot}` in the current +directory. You can override the filename with the `RUST_DEP_GRAPH` +environment variable. + +Frequently, though, the full dep graph is quite overwhelming and not +particularly helpful. Therefore, the compiler also allows you to filter +the graph. You can filter in three ways: + +1. All edges originating in a particular set of nodes (usually a single node). +2. All edges reaching a particular set of nodes. +3. All edges that lie between given start and end nodes. + +To filter, use the `RUST_DEP_GRAPH_FILTER` environment variable, which should +look like one of the following: + +``` +source_filter // nodes originating from source_filter +-> target_filter // nodes that can reach target_filter +source_filter -> target_filter // nodes in between source_filter and target_filter +``` + +`source_filter` and `target_filter` are a `&`-separated list of strings. +A node is considered to match a filter if all of those strings appear in its +label. So, for example: + +``` +RUST_DEP_GRAPH_FILTER='-> TypeckItemBody' +``` + +would select the predecessors of all `TypeckItemBody` nodes. Usually though you +want the `TypeckItemBody` node for some particular fn, so you might write: + +``` +RUST_DEP_GRAPH_FILTER='-> TypeckItemBody & bar' +``` + +This will select only the `TypeckItemBody` nodes for fns with `bar` in their name. + +Perhaps you are finding that when you change `foo` you need to re-type-check `bar`, +but you don't think you should have to. In that case, you might do: + +``` +RUST_DEP_GRAPH_FILTER='Hir&foo -> TypeckItemBody & bar' +``` + +This will dump out all the nodes that lead from `Hir(foo)` to +`TypeckItemBody(bar)`, from which you can (hopefully) see the source +of the erroneous edge. + diff --git a/src/librustc/dep_graph/dep_tracking_map.rs b/src/librustc/dep_graph/dep_tracking_map.rs new file mode 100644 index 00000000000..c49e64f0f54 --- /dev/null +++ b/src/librustc/dep_graph/dep_tracking_map.rs @@ -0,0 +1,137 @@ +// Copyright 2012-2015 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. + +use rustc_data_structures::fnv::FnvHashMap; +use std::cell::RefCell; +use std::ops::Index; +use std::hash::Hash; +use std::marker::PhantomData; +use util::common::MemoizationMap; + +use super::{DepNode, DepGraph}; + +/// A DepTrackingMap offers a subset of the `Map` API and ensures that +/// we make calls to `read` and `write` as appropriate. We key the +/// maps with a unique type for brevity. +pub struct DepTrackingMap { + phantom: PhantomData, + graph: DepGraph, + map: FnvHashMap, +} + +pub trait DepTrackingMapConfig { + type Key: Eq + Hash + Clone; + type Value: Clone; + fn to_dep_node(key: &Self::Key) -> DepNode; +} + +impl DepTrackingMap { + pub fn new(graph: DepGraph) -> DepTrackingMap { + DepTrackingMap { + phantom: PhantomData, + graph: graph, + map: FnvHashMap() + } + } + + /// Registers a (synthetic) read from the key `k`. Usually this + /// is invoked automatically by `get`. + fn read(&self, k: &M::Key) { + let dep_node = M::to_dep_node(k); + self.graph.read(dep_node); + } + + /// Registers a (synthetic) write to the key `k`. Usually this is + /// invoked automatically by `insert`. + fn write(&self, k: &M::Key) { + let dep_node = M::to_dep_node(k); + self.graph.write(dep_node); + } + + pub fn get(&self, k: &M::Key) -> Option<&M::Value> { + self.read(k); + self.map.get(k) + } + + pub fn insert(&mut self, k: M::Key, v: M::Value) -> Option { + self.write(&k); + self.map.insert(k, v) + } + + pub fn contains_key(&self, k: &M::Key) -> bool { + self.read(k); + self.map.contains_key(k) + } +} + +impl MemoizationMap for RefCell> { + type Key = M::Key; + type Value = M::Value; + + /// Memoizes an entry in the dep-tracking-map. If the entry is not + /// already present, then `op` will be executed to compute its value. + /// The resulting dependency graph looks like this: + /// + /// [op] -> Map(key) -> CurrentTask + /// + /// Here, `[op]` represents whatever nodes `op` reads in the + /// course of execution; `Map(key)` represents the node for this + /// map; and `CurrentTask` represents the current task when + /// `memoize` is invoked. + /// + /// **Important:* when `op` is invoked, the current task will be + /// switched to `Map(key)`. Therefore, if `op` makes use of any + /// HIR nodes or shared state accessed through its closure + /// environment, it must explicitly register a read of that + /// state. As an example, see `type_scheme_of_item` in `collect`, + /// which looks something like this: + /// + /// ``` + /// fn type_scheme_of_item(..., item: &hir::Item) -> ty::TypeScheme<'tcx> { + /// let item_def_id = ccx.tcx.map.local_def_id(it.id); + /// ccx.tcx.tcache.memoized(item_def_id, || { + /// ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // (*) + /// compute_type_scheme_of_item(ccx, item) + /// }); + /// } + /// ``` + /// + /// The key is the line marked `(*)`: the closure implicitly + /// accesses the body of the item `item`, so we register a read + /// from `Hir(item_def_id)`. + fn memoize(&self, key: M::Key, op: OP) -> M::Value + where OP: FnOnce() -> M::Value + { + let graph; + { + let this = self.borrow(); + if let Some(result) = this.map.get(&key) { + this.read(&key); + return result.clone(); + } + graph = this.graph.clone(); + } + + let _task = graph.in_task(M::to_dep_node(&key)); + let result = op(); + self.borrow_mut().map.insert(key, result.clone()); + result + } +} + +impl<'k, M: DepTrackingMapConfig> Index<&'k M::Key> for DepTrackingMap { + type Output = M::Value; + + #[inline] + fn index(&self, k: &'k M::Key) -> &M::Value { + self.get(k).unwrap() + } +} + diff --git a/src/librustc/dep_graph/edges.rs b/src/librustc/dep_graph/edges.rs new file mode 100644 index 00000000000..4b25285c476 --- /dev/null +++ b/src/librustc/dep_graph/edges.rs @@ -0,0 +1,162 @@ +// Copyright 2012-2015 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. + +use rustc_data_structures::fnv::{FnvHashMap, FnvHashSet}; +use super::{DepGraphQuery, DepNode}; + +pub struct DepGraphEdges { + nodes: Vec, + indices: FnvHashMap, + edges: FnvHashSet<(IdIndex, IdIndex)>, + open_nodes: Vec, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +struct IdIndex { + index: u32 +} + +impl IdIndex { + fn new(v: usize) -> IdIndex { + assert!((v & 0xFFFF_FFFF) == v); + IdIndex { index: v as u32 } + } + + fn index(self) -> usize { + self.index as usize + } +} + +#[derive(Clone, Debug, PartialEq)] +enum OpenNode { + Node(IdIndex), + Ignore, +} + +impl DepGraphEdges { + pub fn new() -> DepGraphEdges { + DepGraphEdges { + nodes: vec![], + indices: FnvHashMap(), + edges: FnvHashSet(), + open_nodes: Vec::new() + } + } + + fn id(&self, index: IdIndex) -> DepNode { + self.nodes[index.index()] + } + + /// Creates a node for `id` in the graph. + fn make_node(&mut self, id: DepNode) -> IdIndex { + if let Some(&i) = self.indices.get(&id) { + return i; + } + + let index = IdIndex::new(self.nodes.len()); + self.nodes.push(id.clone()); + self.indices.insert(id, index); + index + } + + /// Top of the stack of open nodes. + fn current_node(&self) -> Option { + self.open_nodes.last().cloned() + } + + pub fn push_ignore(&mut self) { + self.open_nodes.push(OpenNode::Ignore); + } + + pub fn pop_ignore(&mut self) { + let popped_node = self.open_nodes.pop().unwrap(); + assert_eq!(popped_node, OpenNode::Ignore); + } + + pub fn push_task(&mut self, key: DepNode) { + let top_node = self.current_node(); + + let new_node = self.make_node(key); + self.open_nodes.push(OpenNode::Node(new_node)); + + // if we are in the midst of doing task T, then this new task + // N is a subtask of T, so add an edge N -> T. + if let Some(top_node) = top_node { + self.add_edge_from_open_node(top_node, |t| (new_node, t)); + } + } + + pub fn pop_task(&mut self, key: DepNode) { + let popped_node = self.open_nodes.pop().unwrap(); + assert_eq!(OpenNode::Node(self.indices[&key]), popped_node); + } + + /// Indicates that the current task `C` reads `v` by adding an + /// edge from `v` to `C`. If there is no current task, panics. If + /// you want to suppress this edge, use `ignore`. + pub fn read(&mut self, v: DepNode) { + let source = self.make_node(v); + self.add_edge_from_current_node(|current| (source, current)) + } + + /// Indicates that the current task `C` writes `v` by adding an + /// edge from `C` to `v`. If there is no current task, panics. If + /// you want to suppress this edge, use `ignore`. + pub fn write(&mut self, v: DepNode) { + let target = self.make_node(v); + self.add_edge_from_current_node(|current| (current, target)) + } + + /// Invoke `add_edge_from_open_node` with the top of the stack, or + /// panic if stack is empty. + fn add_edge_from_current_node(&mut self, + op: OP) + where OP: FnOnce(IdIndex) -> (IdIndex, IdIndex) + { + match self.current_node() { + Some(open_node) => self.add_edge_from_open_node(open_node, op), + None => panic!("no current node, cannot add edge into dependency graph") + } + } + + /// Adds an edge to or from the `open_node`, assuming `open_node` + /// is not `Ignore`. The direction of the edge is determined by + /// the closure `op` --- we pass as argument the open node `n`, + /// and the closure returns a (source, target) tuple, which should + /// include `n` in one spot or another. + fn add_edge_from_open_node(&mut self, + open_node: OpenNode, + op: OP) + where OP: FnOnce(IdIndex) -> (IdIndex, IdIndex) + { + let (source, target) = match open_node { + OpenNode::Node(n) => op(n), + OpenNode::Ignore => { return; } + }; + + // ignore trivial self edges, which are not very interesting + if source == target { + return; + } + + if self.edges.insert((source, target)) { + debug!("adding edge from {:?} to {:?}", + self.id(source), + self.id(target)); + } + } + + pub fn query(&self) -> DepGraphQuery { + let edges: Vec<_> = self.edges.iter() + .map(|&(i, j)| (self.id(i), self.id(j))) + .collect(); + DepGraphQuery::new(&self.nodes, &edges) + } +} diff --git a/src/librustc/dep_graph/mod.rs b/src/librustc/dep_graph/mod.rs new file mode 100644 index 00000000000..9bf0a79115e --- /dev/null +++ b/src/librustc/dep_graph/mod.rs @@ -0,0 +1,196 @@ +// Copyright 2012-2015 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. + +use self::thread::{DepGraphThreadData, DepMessage}; +use middle::def_id::DefId; +use middle::ty; +use middle::ty::fast_reject::SimplifiedType; +use rustc_front::hir; +use rustc_front::intravisit::Visitor; +use std::rc::Rc; + +mod dep_tracking_map; +mod edges; +mod query; +mod raii; +mod thread; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum DepNode { + // Represents the `Krate` as a whole (the `hir::Krate` value) (as + // distinct from the krate module). This is basically a hash of + // the entire krate, so if you read from `Krate` (e.g., by calling + // `tcx.map.krate()`), we will have to assume that any change + // means that you need to be recompiled. This is because the + // `Krate` value gives you access to all other items. To avoid + // this fate, do not call `tcx.map.krate()`; instead, prefer + // wrappers like `tcx.visit_all_items_in_krate()`. If there is no + // suitable wrapper, you can use `tcx.dep_graph.ignore()` to gain + // access to the krate, but you must remember to add suitable + // edges yourself for the individual items that you read. + Krate, + + // Represents the HIR node with the given node-id + Hir(DefId), + + // Represents different phases in the compiler. + CollectItem(DefId), + Coherence, + CoherenceCheckImpl(DefId), + CoherenceOverlapCheck(DefId), + CoherenceOverlapCheckSpecial(DefId), + CoherenceOrphanCheck(DefId), + Variance, + WfCheck(DefId), + TypeckItemType(DefId), + TypeckItemBody(DefId), + Dropck, + DropckImpl(DefId), + CheckConst(DefId), + Privacy, + IntrinsicCheck(DefId), + MatchCheck(DefId), + MirMapConstruction(DefId), + BorrowCheck(DefId), + RvalueCheck(DefId), + Reachability, + DeadCheck, + StabilityCheck, + LateLintCheck, + IntrinsicUseCheck, + TransCrate, + TransCrateItem(DefId), + TransInlinedItem(DefId), + TransWriteMetadata, + + // Nodes representing bits of computed IR in the tcx. Each shared + // table in the tcx (or elsewhere) maps to one of these + // nodes. Often we map multiple tables to the same node if there + // is no point in distinguishing them (e.g., both the type and + // predicates for an item wind up in `ItemSignature`). Other + // times, such as `ImplItems` vs `TraitItemDefIds`, tables which + // might be mergable are kept distinct because the sets of def-ids + // to which they apply are disjoint, and hence we might as well + // have distinct labels for easier debugging. + ImplOrTraitItems(DefId), + ItemSignature(DefId), + FieldTy(DefId), + TraitItemDefIds(DefId), + InherentImpls(DefId), + ImplItems(DefId), + + // The set of impls for a given trait. Ultimately, it would be + // nice to get more fine-grained here (e.g., to include a + // simplified type), but we can't do that until we restructure the + // HIR to distinguish the *header* of an impl from its body. This + // is because changes to the header may change the self-type of + // the impl and hence would require us to be more conservative + // than changes in the impl body. + TraitImpls(DefId), + + // Nodes representing caches. To properly handle a true cache, we + // don't use a DepTrackingMap, but rather we push a task node. + // Otherwise the write into the map would be incorrectly + // attributed to the first task that happened to fill the cache, + // which would yield an overly conservative dep-graph. + TraitItems(DefId), + ReprHints(DefId), + TraitSelect(DefId, Option), +} + +#[derive(Clone)] +pub struct DepGraph { + data: Rc +} + +impl DepGraph { + pub fn new(enabled: bool) -> DepGraph { + DepGraph { + data: Rc::new(DepGraphThreadData::new(enabled)) + } + } + + pub fn query(&self) -> DepGraphQuery { + self.data.query() + } + + pub fn in_ignore<'graph>(&'graph self) -> raii::IgnoreTask<'graph> { + raii::IgnoreTask::new(&self.data) + } + + pub fn in_task<'graph>(&'graph self, key: DepNode) -> raii::DepTask<'graph> { + raii::DepTask::new(&self.data, key) + } + + pub fn with_ignore(&self, op: OP) -> R + where OP: FnOnce() -> R + { + let _task = self.in_ignore(); + op() + } + + pub fn with_task(&self, key: DepNode, op: OP) -> R + where OP: FnOnce() -> R + { + let _task = self.in_task(key); + op() + } + + pub fn read(&self, v: DepNode) { + self.data.enqueue(DepMessage::Read(v)); + } + + pub fn write(&self, v: DepNode) { + self.data.enqueue(DepMessage::Write(v)); + } +} + +pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapConfig}; + +pub use self::query::DepGraphQuery; + +/// Visit all the items in the krate in some order. When visiting a +/// particular item, first create a dep-node by calling `dep_node_fn` +/// and push that onto the dep-graph stack of tasks, and also create a +/// read edge from the corresponding AST node. This is used in +/// compiler passes to automatically record the item that they are +/// working on. +pub fn visit_all_items_in_krate<'tcx,V,F>(tcx: &ty::ctxt<'tcx>, + mut dep_node_fn: F, + visitor: &mut V) + where F: FnMut(DefId) -> DepNode, V: Visitor<'tcx> +{ + struct TrackingVisitor<'visit, 'tcx: 'visit, F: 'visit, V: 'visit> { + tcx: &'visit ty::ctxt<'tcx>, + dep_node_fn: &'visit mut F, + visitor: &'visit mut V + } + + impl<'visit, 'tcx, F, V> Visitor<'tcx> for TrackingVisitor<'visit, 'tcx, F, V> + where F: FnMut(DefId) -> DepNode, V: Visitor<'tcx> + { + fn visit_item(&mut self, i: &'tcx hir::Item) { + let item_def_id = self.tcx.map.local_def_id(i.id); + let task_id = (self.dep_node_fn)(item_def_id); + debug!("About to start task {:?}", task_id); + let _task = self.tcx.dep_graph.in_task(task_id); + self.tcx.dep_graph.read(DepNode::Hir(item_def_id)); + self.visitor.visit_item(i) + } + } + + let krate = tcx.dep_graph.with_ignore(|| tcx.map.krate()); + let mut tracking_visitor = TrackingVisitor { + tcx: tcx, + dep_node_fn: &mut dep_node_fn, + visitor: visitor + }; + krate.visit_all_items(&mut tracking_visitor) +} diff --git a/src/librustc/dep_graph/query.rs b/src/librustc/dep_graph/query.rs new file mode 100644 index 00000000000..74a054acb4f --- /dev/null +++ b/src/librustc/dep_graph/query.rs @@ -0,0 +1,68 @@ +// Copyright 2012-2015 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. + +use rustc_data_structures::fnv::FnvHashMap; +use rustc_data_structures::graph::{Graph, NodeIndex}; + +use super::DepNode; + +pub struct DepGraphQuery { + pub graph: Graph, + pub indices: FnvHashMap, +} + +impl DepGraphQuery { + pub fn new(nodes: &[DepNode], edges: &[(DepNode, DepNode)]) -> DepGraphQuery { + let mut graph = Graph::new(); + let mut indices = FnvHashMap(); + for node in nodes { + indices.insert(node.clone(), graph.next_node_index()); + graph.add_node(node.clone()); + } + + for &(ref source, ref target) in edges { + let source = indices[source]; + let target = indices[target]; + graph.add_edge(source, target, ()); + } + + DepGraphQuery { + graph: graph, + indices: indices + } + } + + pub fn nodes(&self) -> Vec { + self.graph.all_nodes() + .iter() + .map(|n| n.data.clone()) + .collect() + } + + pub fn edges(&self) -> Vec<(DepNode,DepNode)> { + self.graph.all_edges() + .iter() + .map(|edge| (edge.source(), edge.target())) + .map(|(s, t)| (self.graph.node_data(s).clone(), self.graph.node_data(t).clone())) + .collect() + } + + /// All nodes reachable from `node`. In other words, things that + /// will have to be recomputed if `node` changes. + pub fn dependents(&self, node: DepNode) -> Vec { + if let Some(&index) = self.indices.get(&node) { + self.graph.depth_traverse(index) + .map(|dependent_node| self.graph.node_data(dependent_node).clone()) + .collect() + } else { + vec![] + } + } +} diff --git a/src/librustc/dep_graph/raii.rs b/src/librustc/dep_graph/raii.rs new file mode 100644 index 00000000000..dd7ff92f9c3 --- /dev/null +++ b/src/librustc/dep_graph/raii.rs @@ -0,0 +1,47 @@ +// Copyright 2012-2015 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. + +use super::DepNode; +use super::thread::{DepGraphThreadData, DepMessage}; + +pub struct DepTask<'graph> { + data: &'graph DepGraphThreadData, + key: DepNode, +} + +impl<'graph> DepTask<'graph> { + pub fn new(data: &'graph DepGraphThreadData, key: DepNode) -> DepTask<'graph> { + data.enqueue(DepMessage::PushTask(key)); + DepTask { data: data, key: key } + } +} + +impl<'graph> Drop for DepTask<'graph> { + fn drop(&mut self) { + self.data.enqueue(DepMessage::PopTask(self.key)); + } +} + +pub struct IgnoreTask<'graph> { + data: &'graph DepGraphThreadData +} + +impl<'graph> IgnoreTask<'graph> { + pub fn new(data: &'graph DepGraphThreadData) -> IgnoreTask<'graph> { + data.enqueue(DepMessage::PushIgnore); + IgnoreTask { data: data } + } +} + +impl<'graph> Drop for IgnoreTask<'graph> { + fn drop(&mut self) { + self.data.enqueue(DepMessage::PopIgnore); + } +} diff --git a/src/librustc/dep_graph/thread.rs b/src/librustc/dep_graph/thread.rs new file mode 100644 index 00000000000..dbc57605d71 --- /dev/null +++ b/src/librustc/dep_graph/thread.rs @@ -0,0 +1,137 @@ +// Copyright 2012-2015 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. + +//! Manages the communication between the compiler's main thread and +//! the thread that constructs the dependency graph. The basic idea is +//! to use double buffering to lower the cost of producing a message. +//! In the compiler thread, we accumulate messages in a vector until +//! the vector is full, or until we want to query the graph, and then +//! we send that vector over to the depgraph thread. At the same time, +//! we receive an empty vector from the depgraph thread that we can use +//! to accumulate more messages. This way we only ever have two vectors +//! allocated (and both have a fairly large capacity). + +use rustc_data_structures::veccell::VecCell; +use std::sync::mpsc::{self, Sender, Receiver}; +use std::thread; + +use super::DepGraphQuery; +use super::DepNode; +use super::edges::DepGraphEdges; + +pub enum DepMessage { + Read(DepNode), + Write(DepNode), + PushTask(DepNode), + PopTask(DepNode), + PushIgnore, + PopIgnore, + Query, +} + +pub struct DepGraphThreadData { + enabled: bool, + + // current buffer, where we accumulate messages + messages: VecCell, + + // whence to receive new buffer when full + swap_in: Receiver>, + + // where to send buffer when full + swap_out: Sender>, + + // where to receive query results + query_in: Receiver, +} + +const INITIAL_CAPACITY: usize = 2048; + +impl DepGraphThreadData { + pub fn new(enabled: bool) -> DepGraphThreadData { + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (txq, rxq) = mpsc::channel(); + if enabled { + thread::spawn(move || main(rx1, tx2, txq)); + } + DepGraphThreadData { + enabled: enabled, + messages: VecCell::with_capacity(INITIAL_CAPACITY), + swap_in: rx2, + swap_out: tx1, + query_in: rxq, + } + } + + /// Sends the current batch of messages to the thread. Installs a + /// new vector of messages. + fn swap(&self) { + assert!(self.enabled, "should never swap if not enabled"); + + // should be a buffer waiting for us (though of course we may + // have to wait for depgraph thread to finish processing the + // old messages) + let new_messages = self.swap_in.recv().unwrap(); + assert!(new_messages.is_empty()); + + // swap in the empty buffer and extract the full one + let old_messages = self.messages.swap(new_messages); + + // send full buffer to depgraph thread to be processed + self.swap_out.send(old_messages).unwrap(); + } + + pub fn query(&self) -> DepGraphQuery { + assert!(self.enabled, "cannot query if dep graph construction not enabled"); + self.enqueue(DepMessage::Query); + self.swap(); + self.query_in.recv().unwrap() + } + + /// Enqueue a message to be sent when things are next swapped. (If + /// the buffer is full, this may swap.) + #[inline] + pub fn enqueue(&self, message: DepMessage) { + if self.enabled { + let len = self.messages.push(message); + if len == INITIAL_CAPACITY { + self.swap(); + } + } + } +} + +/// Definition of the depgraph thread. +pub fn main(swap_in: Receiver>, + swap_out: Sender>, + query_out: Sender) { + let mut edges = DepGraphEdges::new(); + + // the compiler thread always expects a fresh buffer to be + // waiting, so queue one up + swap_out.send(Vec::with_capacity(INITIAL_CAPACITY)).unwrap(); + + // process the buffers from compiler thread as we receive them + for mut messages in swap_in { + for msg in messages.drain(..) { + match msg { + DepMessage::Read(node) => edges.read(node), + DepMessage::Write(node) => edges.write(node), + DepMessage::PushTask(node) => edges.push_task(node), + DepMessage::PopTask(node) => edges.pop_task(node), + DepMessage::PushIgnore => edges.push_ignore(), + DepMessage::PopIgnore => edges.pop_ignore(), + DepMessage::Query => query_out.send(edges.query()).unwrap(), + } + } + swap_out.send(messages).unwrap(); + } +} diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index 00d6237b855..f84d5fbaf81 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -87,6 +87,8 @@ pub mod back { pub use rustc_back::svh; } +pub mod dep_graph; + pub mod front { pub mod check_attr; pub mod map; diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 464f29a3393..0ac5160c29e 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -25,6 +25,7 @@ //! for all lint attributes. use self::TargetLint::*; +use dep_graph::DepNode; use middle::privacy::AccessLevels; use middle::ty; use session::{early_error, Session}; @@ -1071,6 +1072,8 @@ impl LateLintPass for GatherNodeLevels { /// /// Consumes the `lint_store` field of the `Session`. pub fn check_crate(tcx: &ty::ctxt, access_levels: &AccessLevels) { + let _task = tcx.dep_graph.in_task(DepNode::LateLintCheck); + let krate = tcx.map.krate(); let mut cx = LateContext::new(tcx, krate, access_levels); diff --git a/src/librustc/middle/check_const.rs b/src/librustc/middle/check_const.rs index c2acd0e4795..a9b3043e090 100644 --- a/src/librustc/middle/check_const.rs +++ b/src/librustc/middle/check_const.rs @@ -24,6 +24,7 @@ // - It's not possible to take the address of a static item with unsafe interior. This is enforced // by borrowck::gather_loans +use dep_graph::DepNode; use middle::ty::cast::{CastKind}; use middle::const_eval::{self, ConstEvalErr}; use middle::const_eval::ErrKind::IndexOpFeatureGated; @@ -842,13 +843,12 @@ fn check_adjustments<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Exp } pub fn check_crate(tcx: &ty::ctxt) { - tcx.map.krate().visit_all_items(&mut CheckCrateVisitor { + tcx.visit_all_items_in_krate(DepNode::CheckConst, &mut CheckCrateVisitor { tcx: tcx, mode: Mode::Var, qualif: ConstQualif::NOT_CONST, rvalue_borrows: NodeMap() }); - tcx.sess.abort_if_errors(); } diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs index 8439b439d76..972f9e2c64d 100644 --- a/src/librustc/middle/check_match.rs +++ b/src/librustc/middle/check_match.rs @@ -12,6 +12,7 @@ pub use self::Constructor::*; use self::Usefulness::*; use self::WitnessPreference::*; +use dep_graph::DepNode; use middle::const_eval::{compare_const_vals, ConstVal}; use middle::const_eval::{eval_const_expr, eval_const_expr_partial}; use middle::const_eval::{const_expr_to_pat, lookup_const_by_id}; @@ -155,7 +156,7 @@ impl<'a, 'tcx, 'v> Visitor<'v> for MatchCheckCtxt<'a, 'tcx> { } pub fn check_crate(tcx: &ty::ctxt) { - tcx.map.krate().visit_all_items(&mut MatchCheckCtxt { + tcx.visit_all_items_in_krate(DepNode::MatchCheck, &mut MatchCheckCtxt { tcx: tcx, param_env: tcx.empty_parameter_environment(), }); diff --git a/src/librustc/middle/check_rvalues.rs b/src/librustc/middle/check_rvalues.rs index 35adeae3e61..8a3e039ac6e 100644 --- a/src/librustc/middle/check_rvalues.rs +++ b/src/librustc/middle/check_rvalues.rs @@ -11,21 +11,21 @@ // Checks that all rvalues in a crate have statically known size. check_crate // is the public starting point. +use dep_graph::DepNode; use middle::expr_use_visitor as euv; use middle::infer; use middle::mem_categorization as mc; use middle::ty::ParameterEnvironment; use middle::ty; -use syntax::ast; use rustc_front::hir; -use syntax::codemap::Span; use rustc_front::intravisit; +use syntax::ast; +use syntax::codemap::Span; -pub fn check_crate(tcx: &ty::ctxt, - krate: &hir::Crate) { +pub fn check_crate(tcx: &ty::ctxt) { let mut rvcx = RvalueContext { tcx: tcx }; - krate.visit_all_items(&mut rvcx); + tcx.visit_all_items_in_krate(DepNode::RvalueCheck, &mut rvcx); } struct RvalueContext<'a, 'tcx: 'a> { diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs index ec1b447d711..1386ef91c70 100644 --- a/src/librustc/middle/dead.rs +++ b/src/librustc/middle/dead.rs @@ -12,6 +12,7 @@ // closely. The idea is that all reachable symbols are live, codes called // from live codes are live, and everything else is dead. +use dep_graph::DepNode; use front::map as ast_map; use rustc_front::hir; use rustc_front::intravisit::{self, Visitor}; @@ -590,6 +591,7 @@ impl<'a, 'tcx, 'v> Visitor<'v> for DeadVisitor<'a, 'tcx> { } pub fn check_crate(tcx: &ty::ctxt, access_levels: &privacy::AccessLevels) { + let _task = tcx.dep_graph.in_task(DepNode::DeadCheck); let krate = tcx.map.krate(); let live_symbols = find_live(tcx, access_levels, krate); let mut visitor = DeadVisitor { tcx: tcx, live_symbols: live_symbols }; diff --git a/src/librustc/middle/expr_use_visitor.rs b/src/librustc/middle/expr_use_visitor.rs index 0273d1a76e9..23d1617e5c6 100644 --- a/src/librustc/middle/expr_use_visitor.rs +++ b/src/librustc/middle/expr_use_visitor.rs @@ -274,7 +274,7 @@ enum PassArgs { } impl<'d,'t,'a,'tcx> ExprUseVisitor<'d,'t,'a,'tcx> { - pub fn new(delegate: &'d mut (Delegate<'tcx>), + pub fn new(delegate: &'d mut (Delegate<'tcx>+'d), typer: &'t infer::InferCtxt<'a, 'tcx>) -> ExprUseVisitor<'d,'t,'a,'tcx> where 'tcx:'a+'d { diff --git a/src/librustc/middle/intrinsicck.rs b/src/librustc/middle/intrinsicck.rs index 48d7f44063e..f1eed256dd1 100644 --- a/src/librustc/middle/intrinsicck.rs +++ b/src/librustc/middle/intrinsicck.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use dep_graph::DepNode; use middle::def::DefFn; use middle::def_id::DefId; use middle::subst::{Subst, Substs, EnumeratedItems}; @@ -29,7 +30,7 @@ pub fn check_crate(tcx: &ctxt) { dummy_sized_ty: tcx.types.isize, dummy_unsized_ty: tcx.mk_slice(tcx.types.isize), }; - tcx.map.krate().visit_all_items(&mut visitor); + tcx.visit_all_items_in_krate(DepNode::IntrinsicCheck, &mut visitor); } struct IntrinsicCheckingVisitor<'a, 'tcx: 'a> { diff --git a/src/librustc/middle/reachable.rs b/src/librustc/middle/reachable.rs index d146ad2d800..738440adf41 100644 --- a/src/librustc/middle/reachable.rs +++ b/src/librustc/middle/reachable.rs @@ -15,6 +15,7 @@ // makes all other generics or inline functions that it references // reachable as well. +use dep_graph::DepNode; use front::map as ast_map; use middle::def; use middle::def_id::DefId; @@ -349,6 +350,7 @@ impl<'a, 'v> Visitor<'v> for CollectPrivateImplItemsVisitor<'a> { pub fn find_reachable(tcx: &ty::ctxt, access_levels: &privacy::AccessLevels) -> NodeSet { + let _task = tcx.dep_graph.in_task(DepNode::Reachability); let mut reachable_context = ReachableContext::new(tcx); diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs index f6af680d441..8d5c0c98885 100644 --- a/src/librustc/middle/stability.rs +++ b/src/librustc/middle/stability.rs @@ -13,6 +13,7 @@ pub use self::StabilityLevel::*; +use dep_graph::DepNode; use session::Session; use lint; use middle::cstore::{CrateStore, LOCAL_CRATE}; @@ -328,6 +329,7 @@ impl<'tcx> Index<'tcx> { /// features used. pub fn check_unstable_api_usage(tcx: &ty::ctxt) -> FnvHashMap { + let _task = tcx.dep_graph.in_task(DepNode::StabilityCheck); let ref active_lib_features = tcx.sess.features.borrow().declared_lib_features; // Put the active features into a map for quick lookup @@ -341,8 +343,7 @@ pub fn check_unstable_api_usage(tcx: &ty::ctxt) }; intravisit::walk_crate(&mut checker, tcx.map.krate()); - let used_features = checker.used_features; - return used_features; + checker.used_features } struct Checker<'a, 'tcx: 'a> { diff --git a/src/librustc/middle/traits/mod.rs b/src/librustc/middle/traits/mod.rs index 255680465ca..dddd6f8bc85 100644 --- a/src/librustc/middle/traits/mod.rs +++ b/src/librustc/middle/traits/mod.rs @@ -15,10 +15,12 @@ pub use self::FulfillmentErrorCode::*; pub use self::Vtable::*; pub use self::ObligationCauseCode::*; +use dep_graph::DepNode; use middle::def_id::DefId; use middle::free_region::FreeRegionMap; use middle::subst; use middle::ty::{self, HasTypeFlags, Ty}; +use middle::ty::fast_reject; use middle::ty::fold::TypeFoldable; use middle::infer::{self, fixup_err_to_string, InferCtxt}; @@ -599,6 +601,18 @@ impl<'tcx> FulfillmentError<'tcx> { } impl<'tcx> TraitObligation<'tcx> { + /// Creates the dep-node for selecting/evaluating this trait reference. + fn dep_node(&self, tcx: &ty::ctxt<'tcx>) -> DepNode { + let simplified_ty = + fast_reject::simplify_type(tcx, + self.predicate.skip_binder().self_ty(), // (*) + true); + + // (*) skip_binder is ok because `simplify_type` doesn't care about regions + + DepNode::TraitSelect(self.predicate.def_id(), simplified_ty) + } + fn self_ty(&self) -> ty::Binder> { ty::Binder(self.predicate.skip_binder().self_ty()) } diff --git a/src/librustc/middle/traits/select.rs b/src/librustc/middle/traits/select.rs index b0215675fca..bd92f974866 100644 --- a/src/librustc/middle/traits/select.rs +++ b/src/librustc/middle/traits/select.rs @@ -310,6 +310,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { debug!("select({:?})", obligation); assert!(!obligation.predicate.has_escaping_regions()); + let dep_node = obligation.dep_node(self.tcx()); + let _task = self.tcx().dep_graph.in_task(dep_node); + let stack = self.push_stack(TraitObligationStackList::empty(), obligation); match try!(self.candidate_from_obligation(&stack)) { None => { @@ -411,7 +414,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// accurate if inference variables are involved. pub fn evaluate_obligation_conservatively(&mut self, obligation: &PredicateObligation<'tcx>) - -> bool + -> bool { debug!("evaluate_obligation_conservatively({:?})", obligation); diff --git a/src/librustc/middle/ty/contents.rs b/src/librustc/middle/ty/contents.rs index afe88f70d94..619201a4a9f 100644 --- a/src/librustc/middle/ty/contents.rs +++ b/src/librustc/middle/ty/contents.rs @@ -10,7 +10,7 @@ use middle::def_id::{DefId}; use middle::ty::{self, Ty}; -use util::common::{memoized}; +use util::common::MemoizationMap; use util::nodemap::FnvHashMap; use std::fmt; @@ -141,9 +141,7 @@ impl fmt::Debug for TypeContents { impl<'tcx> ty::TyS<'tcx> { pub fn type_contents(&'tcx self, cx: &ty::ctxt<'tcx>) -> TypeContents { - return memoized(&cx.tc_cache, self, |ty| { - tc_ty(cx, ty, &mut FnvHashMap()) - }); + return cx.tc_cache.memoize(self, || tc_ty(cx, self, &mut FnvHashMap())); fn tc_ty<'tcx>(cx: &ty::ctxt<'tcx>, ty: Ty<'tcx>, diff --git a/src/librustc/middle/ty/context.rs b/src/librustc/middle/ty/context.rs index cee651743ca..d1504d25288 100644 --- a/src/librustc/middle/ty/context.rs +++ b/src/librustc/middle/ty/context.rs @@ -13,6 +13,7 @@ // FIXME: (@jroesch) @eddyb should remove this when he renames ctxt #![allow(non_camel_case_types)] +use dep_graph::{DepGraph, DepTrackingMap}; use front::map as ast_map; use session::Session; use lint; @@ -29,10 +30,12 @@ use middle::traits; use middle::ty::{self, TraitRef, Ty, TypeAndMut}; use middle::ty::{TyS, TypeVariants}; use middle::ty::{AdtDef, ClosureSubsts, ExistentialBounds, Region}; -use middle::ty::{FreevarMap, GenericPredicates}; +use middle::ty::{FreevarMap}; use middle::ty::{BareFnTy, InferTy, ParamTy, ProjectionTy, TraitTy}; use middle::ty::{TyVar, TyVid, IntVar, IntVid, FloatVar, FloatVid}; use middle::ty::TypeVariants::*; +use middle::ty::maps; +use util::common::MemoizationMap; use util::nodemap::{NodeMap, NodeSet, DefIdMap, DefIdSet}; use util::nodemap::FnvHashMap; @@ -224,6 +227,8 @@ pub struct ctxt<'tcx> { region_interner: RefCell>, stability_interner: RefCell>, + pub dep_graph: DepGraph, + /// Common types, pre-interned for your convenience. pub types: CommonTypes<'tcx>, @@ -245,21 +250,22 @@ pub struct ctxt<'tcx> { pub tables: RefCell>, /// Maps from a trait item to the trait item "descriptor" - pub impl_or_trait_items: RefCell>>, + pub impl_or_trait_items: RefCell>>, /// Maps from a trait def-id to a list of the def-ids of its trait items - pub trait_item_def_ids: RefCell>>>, + pub trait_item_def_ids: RefCell>>, - /// A cache for the trait_items() routine - pub trait_items_cache: RefCell>>>>, + /// A cache for the trait_items() routine; note that the routine + /// itself pushes the `TraitItems` dependency node. + trait_items_cache: RefCell>>, - pub impl_trait_refs: RefCell>>>, - pub trait_defs: RefCell>>, - pub adt_defs: RefCell>>, + pub impl_trait_refs: RefCell>>, + pub trait_defs: RefCell>>, + pub adt_defs: RefCell>>, /// Maps from the def-id of an item (trait/struct/enum/fn) to its /// associated predicates. - pub predicates: RefCell>>, + pub predicates: RefCell>>, /// Maps from the def-id of a trait to the list of /// super-predicates. This is a subset of the full list of @@ -267,21 +273,40 @@ pub struct ctxt<'tcx> { /// evaluate them even during type conversion, often before the /// full predicates are available (note that supertraits have /// additional acyclicity requirements). - pub super_predicates: RefCell>>, + pub super_predicates: RefCell>>, pub map: ast_map::Map<'tcx>, + + // Records the free variables refrenced by every closure + // expression. Do not track deps for this, just recompute it from + // scratch every time. pub freevars: RefCell, - pub tcache: RefCell>>, + + // Records the type of every item. + pub tcache: RefCell>>, + + // Internal cache for metadata decoding. No need to track deps on this. pub rcache: RefCell>>, + + // Cache for the type-contents routine. FIXME -- track deps? pub tc_cache: RefCell, ty::contents::TypeContents>>, + + // Cache for various types within a method body and so forth. + // + // FIXME this should be made local to typeck, but it is currently used by one lint pub ast_ty_to_ty_cache: RefCell>>, + + // FIXME no dep tracking, but we should be able to remove this pub ty_param_defs: RefCell>>, + + // FIXME dep tracking -- should be harmless enough pub normalized_cache: RefCell, Ty<'tcx>>>, + pub lang_items: middle::lang_items::LanguageItems, /// Maps from def-id of a type or region parameter to its /// (inferred) variance. - pub item_variance_map: RefCell>>, + pub item_variance_map: RefCell>>, /// True if the variance has been computed yet; false otherwise. pub variance_computed: Cell, @@ -289,13 +314,13 @@ pub struct ctxt<'tcx> { /// Maps a DefId of a type to a list of its inherent impls. /// Contains implementations of methods that are inherent to a type. /// Methods in these implementations don't need to be exported. - pub inherent_impls: RefCell>>>, + pub inherent_impls: RefCell>>, /// Maps a DefId of an impl to a list of its items. /// Note that this contains all of the impls that we know about, /// including ones in other crates. It's not clear that this is the best /// way to do it. - pub impl_items: RefCell>>, + pub impl_items: RefCell>>, /// Set of used unsafe nodes (functions or blocks). Unsafe nodes not /// present in this set can be warned about. @@ -309,6 +334,7 @@ pub struct ctxt<'tcx> { /// The set of external nominal types whose implementations have been read. /// This is used for lazy resolution of methods. pub populated_external_types: RefCell, + /// The set of external primitive types whose implementations have been read. /// FIXME(arielb1): why is this separate from populated_external_types? pub populated_external_primitive_impls: RefCell, @@ -344,7 +370,7 @@ pub struct ctxt<'tcx> { pub fulfilled_predicates: RefCell>, /// Caches the representation hints for struct definitions. - pub repr_hint_cache: RefCell>>>, + repr_hint_cache: RefCell>>, /// Maps Expr NodeId's to their constant qualification. pub const_qualif_map: RefCell>, @@ -483,7 +509,7 @@ impl<'tcx> ctxt<'tcx> { { let interner = RefCell::new(FnvHashMap()); let common_types = CommonTypes::new(&arenas.type_, &interner); - + let dep_graph = DepGraph::new(s.opts.incremental_compilation); tls::enter(ctxt { arenas: arenas, interner: interner, @@ -491,35 +517,36 @@ impl<'tcx> ctxt<'tcx> { bare_fn_interner: RefCell::new(FnvHashMap()), region_interner: RefCell::new(FnvHashMap()), stability_interner: RefCell::new(FnvHashMap()), + dep_graph: dep_graph.clone(), types: common_types, named_region_map: named_region_map, region_maps: region_maps, free_region_maps: RefCell::new(FnvHashMap()), - item_variance_map: RefCell::new(DefIdMap()), + item_variance_map: RefCell::new(DepTrackingMap::new(dep_graph.clone())), variance_computed: Cell::new(false), sess: s, def_map: def_map, tables: RefCell::new(Tables::empty()), - impl_trait_refs: RefCell::new(DefIdMap()), - trait_defs: RefCell::new(DefIdMap()), - adt_defs: RefCell::new(DefIdMap()), - predicates: RefCell::new(DefIdMap()), - super_predicates: RefCell::new(DefIdMap()), + impl_trait_refs: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + trait_defs: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + adt_defs: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + predicates: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + super_predicates: RefCell::new(DepTrackingMap::new(dep_graph.clone())), fulfilled_predicates: RefCell::new(traits::FulfilledPredicates::new()), map: map, freevars: RefCell::new(freevars), - tcache: RefCell::new(DefIdMap()), + tcache: RefCell::new(DepTrackingMap::new(dep_graph.clone())), rcache: RefCell::new(FnvHashMap()), tc_cache: RefCell::new(FnvHashMap()), ast_ty_to_ty_cache: RefCell::new(NodeMap()), - impl_or_trait_items: RefCell::new(DefIdMap()), - trait_item_def_ids: RefCell::new(DefIdMap()), - trait_items_cache: RefCell::new(DefIdMap()), + impl_or_trait_items: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + trait_item_def_ids: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + trait_items_cache: RefCell::new(DepTrackingMap::new(dep_graph.clone())), ty_param_defs: RefCell::new(NodeMap()), normalized_cache: RefCell::new(FnvHashMap()), lang_items: lang_items, - inherent_impls: RefCell::new(DefIdMap()), - impl_items: RefCell::new(DefIdMap()), + inherent_impls: RefCell::new(DepTrackingMap::new(dep_graph.clone())), + impl_items: RefCell::new(DepTrackingMap::new(dep_graph.clone())), used_unsafe: RefCell::new(NodeSet()), used_mut_nodes: RefCell::new(NodeSet()), populated_external_types: RefCell::new(DefIdSet()), @@ -531,7 +558,7 @@ impl<'tcx> ctxt<'tcx> { stability: RefCell::new(stability), selection_cache: traits::SelectionCache::new(), evaluation_cache: traits::EvaluationCache::new(), - repr_hint_cache: RefCell::new(DefIdMap()), + repr_hint_cache: RefCell::new(DepTrackingMap::new(dep_graph.clone())), const_qualif_map: RefCell::new(NodeMap()), custom_coerce_unsized_kinds: RefCell::new(DefIdMap()), cast_kinds: RefCell::new(NodeMap()), @@ -1000,4 +1027,26 @@ impl<'tcx> ctxt<'tcx> { pub fn mk_param_from_def(&self, def: &ty::TypeParameterDef) -> Ty<'tcx> { self.mk_param(def.space, def.index, def.name) } + + pub fn trait_items(&self, trait_did: DefId) -> Rc>> { + self.trait_items_cache.memoize(trait_did, || { + let def_ids = self.trait_item_def_ids(trait_did); + Rc::new(def_ids.iter() + .map(|d| self.impl_or_trait_item(d.def_id())) + .collect()) + }) + } + + /// Obtain the representation annotation for a struct definition. + pub fn lookup_repr_hints(&self, did: DefId) -> Rc> { + self.repr_hint_cache.memoize(did, || { + Rc::new(if did.is_local() { + self.get_attrs(did).iter().flat_map(|meta| { + attr::find_repr_attrs(self.sess.diagnostic(), meta).into_iter() + }).collect() + } else { + self.sess.cstore.repr_attrs(did) + }) + }) + } } diff --git a/src/librustc/middle/ty/fast_reject.rs b/src/librustc/middle/ty/fast_reject.rs index 77608f40128..a06e8a72c44 100644 --- a/src/librustc/middle/ty/fast_reject.rs +++ b/src/librustc/middle/ty/fast_reject.rs @@ -15,7 +15,7 @@ use syntax::ast; use self::SimplifiedType::*; /// See `simplify_type -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum SimplifiedType { BoolSimplifiedType, CharSimplifiedType, diff --git a/src/librustc/middle/ty/ivar.rs b/src/librustc/middle/ty/ivar.rs index 73d567d0acf..ffc12aa5aea 100644 --- a/src/librustc/middle/ty/ivar.rs +++ b/src/librustc/middle/ty/ivar.rs @@ -8,7 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use dep_graph::DepNode; use middle::ty::{Ty, TyS}; +use middle::ty::tls; use rustc_data_structures::ivar; @@ -27,6 +29,10 @@ use core::nonzero::NonZero; /// (B) no aliases to this value with a 'tcx longer than this /// value's 'lt exist /// +/// Dependency tracking: each ivar does not know what node in the +/// dependency graph it is associated with, so when you get/fulfill +/// you must supply a `DepNode` id. This should always be the same id! +/// /// NonZero is used rather than Unique because Unique isn't Copy. pub struct TyIVar<'tcx, 'lt: 'tcx>(ivar::Ivar>>, PhantomData)->TyS<'tcx>>); @@ -40,19 +46,28 @@ impl<'tcx, 'lt> TyIVar<'tcx, 'lt> { } #[inline] - pub fn get(&self) -> Option> { + pub fn get(&self, dep_node: DepNode) -> Option> { + tls::with(|tcx| tcx.dep_graph.read(dep_node)); + self.untracked_get() + } + + #[inline] + fn untracked_get(&self) -> Option> { match self.0.get() { None => None, // valid because of invariant (A) Some(v) => Some(unsafe { &*(*v as *const TyS<'tcx>) }) } } + #[inline] - pub fn unwrap(&self) -> Ty<'tcx> { - self.get().unwrap() + pub fn unwrap(&self, dep_node: DepNode) -> Ty<'tcx> { + self.get(dep_node).unwrap() } - pub fn fulfill(&self, value: Ty<'lt>) { + pub fn fulfill(&self, dep_node: DepNode, value: Ty<'lt>) { + tls::with(|tcx| tcx.dep_graph.write(dep_node)); + // Invariant (A) is fulfilled, because by (B), every alias // of this has a 'tcx longer than 'lt. let value: *const TyS<'lt> = value; @@ -64,7 +79,7 @@ impl<'tcx, 'lt> TyIVar<'tcx, 'lt> { impl<'tcx, 'lt> fmt::Debug for TyIVar<'tcx, 'lt> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.get() { + match self.untracked_get() { Some(val) => write!(f, "TyIVar({:?})", val), None => f.write_str("TyIVar()") } diff --git a/src/librustc/middle/ty/maps.rs b/src/librustc/middle/ty/maps.rs new file mode 100644 index 00000000000..7d5276f379f --- /dev/null +++ b/src/librustc/middle/ty/maps.rs @@ -0,0 +1,44 @@ +// Copyright 2012-2015 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. + +use dep_graph::{DepNode, DepTrackingMapConfig}; +use middle::def_id::DefId; +use middle::ty; +use std::marker::PhantomData; +use std::rc::Rc; +use syntax::attr; + +macro_rules! dep_map_ty { + ($ty_name:ident : $node_name:ident ($key:ty) -> $value:ty) => { + pub struct $ty_name<'tcx> { + data: PhantomData<&'tcx ()> + } + + impl<'tcx> DepTrackingMapConfig for $ty_name<'tcx> { + type Key = $key; + type Value = $value; + fn to_dep_node(key: &$key) -> DepNode { DepNode::$node_name(*key) } + } + } +} + +dep_map_ty! { ImplOrTraitItems: ImplOrTraitItems(DefId) -> ty::ImplOrTraitItem<'tcx> } +dep_map_ty! { Tcache: ItemSignature(DefId) -> ty::TypeScheme<'tcx> } +dep_map_ty! { Predicates: ItemSignature(DefId) -> ty::GenericPredicates<'tcx> } +dep_map_ty! { SuperPredicates: ItemSignature(DefId) -> ty::GenericPredicates<'tcx> } +dep_map_ty! { TraitItemDefIds: TraitItemDefIds(DefId) -> Rc> } +dep_map_ty! { ImplTraitRefs: ItemSignature(DefId) -> Option> } +dep_map_ty! { TraitDefs: ItemSignature(DefId) -> &'tcx ty::TraitDef<'tcx> } +dep_map_ty! { AdtDefs: ItemSignature(DefId) -> ty::AdtDefMaster<'tcx> } +dep_map_ty! { ItemVariances: ItemSignature(DefId) -> Rc } +dep_map_ty! { InherentImpls: InherentImpls(DefId) -> Rc> } +dep_map_ty! { ImplItems: ImplItems(DefId) -> Vec } +dep_map_ty! { TraitItems: TraitItems(DefId) -> Rc>> } +dep_map_ty! { ReprHints: ReprHints(DefId) -> Rc> } diff --git a/src/librustc/middle/ty/mod.rs b/src/librustc/middle/ty/mod.rs index 47e7eee4696..2baf5c5145f 100644 --- a/src/librustc/middle/ty/mod.rs +++ b/src/librustc/middle/ty/mod.rs @@ -18,6 +18,7 @@ pub use self::ImplOrTraitItem::*; pub use self::IntVarValue::*; pub use self::LvaluePreference::*; +use dep_graph::{self, DepNode}; use front::map as ast_map; use front::map::LinkedPath; use middle; @@ -31,13 +32,13 @@ use middle::traits; use middle::ty; use middle::ty::fold::TypeFolder; use middle::ty::walk::TypeWalker; -use util::common::memoized; -use util::nodemap::{NodeMap, NodeSet, DefIdMap}; +use util::common::MemoizationMap; +use util::nodemap::{NodeMap, NodeSet}; use util::nodemap::FnvHashMap; use serialize::{Encodable, Encoder, Decodable, Decoder}; use std::borrow::{Borrow, Cow}; -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::hash::{Hash, Hasher}; use std::iter; use std::rc::Rc; @@ -51,6 +52,7 @@ use syntax::parse::token::{InternedString, special_idents}; use rustc_front::hir; use rustc_front::hir::{ItemImpl, ItemTrait}; +use rustc_front::intravisit::Visitor; pub use self::sty::{Binder, DebruijnIndex}; pub use self::sty::{BuiltinBound, BuiltinBounds, ExistentialBounds}; @@ -75,14 +77,18 @@ pub use self::contents::TypeContents; pub use self::context::{ctxt, tls}; pub use self::context::{CtxtArenas, Lift, Tables}; +pub use self::trait_def::{TraitDef, TraitFlags}; + pub mod adjustment; pub mod cast; pub mod error; pub mod fast_reject; pub mod fold; pub mod _match; +pub mod maps; pub mod outlives; pub mod relate; +pub mod trait_def; pub mod walk; pub mod wf; pub mod util; @@ -1317,161 +1323,6 @@ pub struct TypeScheme<'tcx> { pub ty: Ty<'tcx>, } -bitflags! { - flags TraitFlags: u32 { - const NO_TRAIT_FLAGS = 0, - const HAS_DEFAULT_IMPL = 1 << 0, - const IS_OBJECT_SAFE = 1 << 1, - const OBJECT_SAFETY_VALID = 1 << 2, - const IMPLS_VALID = 1 << 3, - } -} - -/// As `TypeScheme` but for a trait ref. -pub struct TraitDef<'tcx> { - pub unsafety: hir::Unsafety, - - /// If `true`, then this trait had the `#[rustc_paren_sugar]` - /// attribute, indicating that it should be used with `Foo()` - /// sugar. This is a temporary thing -- eventually any trait wil - /// be usable with the sugar (or without it). - pub paren_sugar: bool, - - /// Generic type definitions. Note that `Self` is listed in here - /// as having a single bound, the trait itself (e.g., in the trait - /// `Eq`, there is a single bound `Self : Eq`). This is so that - /// default methods get to assume that the `Self` parameters - /// implements the trait. - pub generics: Generics<'tcx>, - - pub trait_ref: TraitRef<'tcx>, - - /// A list of the associated types defined in this trait. Useful - /// for resolving `X::Foo` type markers. - pub associated_type_names: Vec, - - // Impls of this trait. To allow for quicker lookup, the impls are indexed - // by a simplified version of their Self type: impls with a simplifiable - // Self are stored in nonblanket_impls keyed by it, while all other impls - // are stored in blanket_impls. - - /// Impls of the trait. - pub nonblanket_impls: RefCell< - FnvHashMap> - >, - - /// Blanket impls associated with the trait. - pub blanket_impls: RefCell>, - - /// Various flags - pub flags: Cell -} - -impl<'tcx> TraitDef<'tcx> { - // returns None if not yet calculated - pub fn object_safety(&self) -> Option { - if self.flags.get().intersects(TraitFlags::OBJECT_SAFETY_VALID) { - Some(self.flags.get().intersects(TraitFlags::IS_OBJECT_SAFE)) - } else { - None - } - } - - pub fn set_object_safety(&self, is_safe: bool) { - assert!(self.object_safety().map(|cs| cs == is_safe).unwrap_or(true)); - self.flags.set( - self.flags.get() | if is_safe { - TraitFlags::OBJECT_SAFETY_VALID | TraitFlags::IS_OBJECT_SAFE - } else { - TraitFlags::OBJECT_SAFETY_VALID - } - ); - } - - /// Records a trait-to-implementation mapping. - pub fn record_impl(&self, - tcx: &ctxt<'tcx>, - impl_def_id: DefId, - impl_trait_ref: TraitRef<'tcx>) { - debug!("TraitDef::record_impl for {:?}, from {:?}", - self, impl_trait_ref); - - // We don't want to borrow_mut after we already populated all impls, - // so check if an impl is present with an immutable borrow first. - if let Some(sty) = fast_reject::simplify_type(tcx, - impl_trait_ref.self_ty(), false) { - if let Some(is) = self.nonblanket_impls.borrow().get(&sty) { - if is.contains(&impl_def_id) { - return // duplicate - skip - } - } - - self.nonblanket_impls.borrow_mut().entry(sty).or_insert(vec![]).push(impl_def_id) - } else { - if self.blanket_impls.borrow().contains(&impl_def_id) { - return // duplicate - skip - } - self.blanket_impls.borrow_mut().push(impl_def_id) - } - } - - - pub fn for_each_impl(&self, tcx: &ctxt<'tcx>, mut f: F) { - tcx.populate_implementations_for_trait_if_necessary(self.trait_ref.def_id); - - for &impl_def_id in self.blanket_impls.borrow().iter() { - f(impl_def_id); - } - - for v in self.nonblanket_impls.borrow().values() { - for &impl_def_id in v { - f(impl_def_id); - } - } - } - - /// Iterate over every impl that could possibly match the - /// self-type `self_ty`. - pub fn for_each_relevant_impl(&self, - tcx: &ctxt<'tcx>, - self_ty: Ty<'tcx>, - mut f: F) - { - tcx.populate_implementations_for_trait_if_necessary(self.trait_ref.def_id); - - for &impl_def_id in self.blanket_impls.borrow().iter() { - f(impl_def_id); - } - - // simplify_type(.., false) basically replaces type parameters and - // projections with infer-variables. This is, of course, done on - // the impl trait-ref when it is instantiated, but not on the - // predicate trait-ref which is passed here. - // - // for example, if we match `S: Copy` against an impl like - // `impl Copy for Option`, we replace the type variable - // in `Option` with an infer variable, to `Option<_>` (this - // doesn't actually change fast_reject output), but we don't - // replace `S` with anything - this impl of course can't be - // selected, and as there are hundreds of similar impls, - // considering them would significantly harm performance. - if let Some(simp) = fast_reject::simplify_type(tcx, self_ty, true) { - if let Some(impls) = self.nonblanket_impls.borrow().get(&simp) { - for &impl_def_id in impls { - f(impl_def_id); - } - } - } else { - for v in self.nonblanket_impls.borrow().values() { - for &impl_def_id in v { - f(impl_def_id); - } - } - } - } - -} - bitflags! { flags AdtFlags: u32 { const NO_ADT_FLAGS = 0, @@ -1513,6 +1364,8 @@ pub struct FieldDefData<'tcx, 'container: 'tcx> { pub vis: hir::Visibility, /// TyIVar is used here to allow for variance (see the doc at /// AdtDefData). + /// + /// Note: direct accesses to `ty` must also add dep edges. ty: ivar::TyIVar<'tcx, 'container> } @@ -1803,11 +1656,11 @@ impl<'tcx, 'container> FieldDefData<'tcx, 'container> { } pub fn unsubst_ty(&self) -> Ty<'tcx> { - self.ty.unwrap() + self.ty.unwrap(DepNode::FieldTy(self.did)) } pub fn fulfill_ty(&self, ty: Ty<'container>) { - self.ty.fulfill(ty); + self.ty.fulfill(DepNode::FieldTy(self.did), ty); } } @@ -1930,24 +1783,20 @@ impl LvaluePreference { /// into the map by the `typeck::collect` phase. If the def-id is external, /// then we have to go consult the crate loading code (and cache the result for /// the future). -fn lookup_locally_or_in_crate_store(descr: &str, +fn lookup_locally_or_in_crate_store(descr: &str, def_id: DefId, - map: &RefCell>, - load_external: F) -> V where - V: Clone, - F: FnOnce() -> V, + map: &M, + load_external: F) + -> M::Value where + M: MemoizationMap, + F: FnOnce() -> M::Value, { - match map.borrow().get(&def_id).cloned() { - Some(v) => { return v; } - None => { } - } - - if def_id.is_local() { - panic!("No def'n found for {:?} in tcx.{}", def_id, descr); - } - let v = load_external(); - map.borrow_mut().insert(def_id, v.clone()); - v + map.memoize(def_id, || { + if def_id.is_local() { + panic!("No def'n found for {:?} in tcx.{}", def_id, descr); + } + load_external() + }) } impl BorrowKind { @@ -2223,22 +2072,6 @@ impl<'tcx> ctxt<'tcx> { } } - pub fn trait_items(&self, trait_did: DefId) -> Rc>> { - let mut trait_items = self.trait_items_cache.borrow_mut(); - match trait_items.get(&trait_did).cloned() { - Some(trait_items) => trait_items, - None => { - let def_ids = self.trait_item_def_ids(trait_did); - let items: Rc> = - Rc::new(def_ids.iter() - .map(|d| self.impl_or_trait_item(d.def_id())) - .collect()); - trait_items.insert(trait_did, items.clone()); - items - } - } - } - pub fn trait_impl_polarity(&self, id: DefId) -> Option { if let Some(id) = self.map.as_local_node_id(id) { match self.map.find(id) { @@ -2256,7 +2089,7 @@ impl<'tcx> ctxt<'tcx> { } pub fn custom_coerce_unsized_kind(&self, did: DefId) -> adjustment::CustomCoerceUnsized { - memoized(&self.custom_coerce_unsized_kinds, did, |did: DefId| { + self.custom_coerce_unsized_kinds.memoize(did, || { let (kind, src) = if did.krate != LOCAL_CRATE { (self.sess.cstore.custom_coerce_unsized_kind(did), "external") } else { @@ -2452,19 +2285,6 @@ impl<'tcx> ctxt<'tcx> { || self.lookup_repr_hints(did).contains(&attr::ReprSimd) } - /// Obtain the representation annotation for a struct definition. - pub fn lookup_repr_hints(&self, did: DefId) -> Rc> { - memoized(&self.repr_hint_cache, did, |did: DefId| { - Rc::new(if did.is_local() { - self.get_attrs(did).iter().flat_map(|meta| { - attr::find_repr_attrs(self.sess.diagnostic(), meta).into_iter() - }).collect() - } else { - self.sess.cstore.repr_attrs(did) - }) - }) - } - pub fn item_variances(&self, item_id: DefId) -> Rc { lookup_locally_or_in_crate_store( "item_variance_map", item_id, &self.item_variance_map, @@ -2491,6 +2311,10 @@ impl<'tcx> ctxt<'tcx> { return } + // The primitive is not local, hence we are reading this out + // of metadata. + let _ignore = self.dep_graph.in_ignore(); + if self.populated_external_primitive_impls.borrow().contains(&primitive_def_id) { return } @@ -2513,6 +2337,10 @@ impl<'tcx> ctxt<'tcx> { return } + // The type is not local, hence we are reading this out of + // metadata and don't need to track edges. + let _ignore = self.dep_graph.in_ignore(); + if self.populated_external_types.borrow().contains(&type_id) { return } @@ -2538,6 +2366,10 @@ impl<'tcx> ctxt<'tcx> { return } + // The type is not local, hence we are reading this out of + // metadata and don't need to track edges. + let _ignore = self.dep_graph.in_ignore(); + let def = self.lookup_trait_def(trait_id); if def.flags.get().intersects(TraitFlags::IMPLS_VALID) { return; @@ -2760,6 +2592,15 @@ impl<'tcx> ctxt<'tcx> { pub fn upvar_capture(&self, upvar_id: ty::UpvarId) -> Option { Some(self.tables.borrow().upvar_capture_map.get(&upvar_id).unwrap().clone()) } + + + pub fn visit_all_items_in_krate(&self, + dep_node_fn: F, + visitor: &mut V) + where F: FnMut(DefId) -> DepNode, V: Visitor<'tcx> + { + dep_graph::visit_all_items_in_krate(self, dep_node_fn, visitor); + } } /// The category of explicit self. diff --git a/src/librustc/middle/ty/trait_def.rs b/src/librustc/middle/ty/trait_def.rs new file mode 100644 index 00000000000..db001ce2c44 --- /dev/null +++ b/src/librustc/middle/ty/trait_def.rs @@ -0,0 +1,226 @@ +// Copyright 2012-2015 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. + +use dep_graph::DepNode; +use middle::def_id::DefId; +use middle::ty; +use middle::ty::fast_reject; +use middle::ty::Ty; +use std::borrow::{Borrow}; +use std::cell::{Cell, Ref, RefCell}; +use syntax::ast::Name; +use rustc_front::hir; +use util::nodemap::FnvHashMap; + +/// As `TypeScheme` but for a trait ref. +pub struct TraitDef<'tcx> { + pub unsafety: hir::Unsafety, + + /// If `true`, then this trait had the `#[rustc_paren_sugar]` + /// attribute, indicating that it should be used with `Foo()` + /// sugar. This is a temporary thing -- eventually any trait wil + /// be usable with the sugar (or without it). + pub paren_sugar: bool, + + /// Generic type definitions. Note that `Self` is listed in here + /// as having a single bound, the trait itself (e.g., in the trait + /// `Eq`, there is a single bound `Self : Eq`). This is so that + /// default methods get to assume that the `Self` parameters + /// implements the trait. + pub generics: ty::Generics<'tcx>, + + pub trait_ref: ty::TraitRef<'tcx>, + + /// A list of the associated types defined in this trait. Useful + /// for resolving `X::Foo` type markers. + pub associated_type_names: Vec, + + // Impls of this trait. To allow for quicker lookup, the impls are indexed + // by a simplified version of their Self type: impls with a simplifiable + // Self are stored in nonblanket_impls keyed by it, while all other impls + // are stored in blanket_impls. + // + // These lists are tracked by `DepNode::TraitImpls`; we don't use + // a DepTrackingMap but instead have the `TraitDef` insert the + // required reads/writes. + + /// Impls of the trait. + nonblanket_impls: RefCell< + FnvHashMap> + >, + + /// Blanket impls associated with the trait. + blanket_impls: RefCell>, + + /// Various flags + pub flags: Cell +} + +impl<'tcx> TraitDef<'tcx> { + pub fn new(unsafety: hir::Unsafety, + paren_sugar: bool, + generics: ty::Generics<'tcx>, + trait_ref: ty::TraitRef<'tcx>, + associated_type_names: Vec) + -> TraitDef<'tcx> { + TraitDef { + paren_sugar: paren_sugar, + unsafety: unsafety, + generics: generics, + trait_ref: trait_ref, + associated_type_names: associated_type_names, + nonblanket_impls: RefCell::new(FnvHashMap()), + blanket_impls: RefCell::new(vec![]), + flags: Cell::new(ty::TraitFlags::NO_TRAIT_FLAGS) + } + } + + pub fn def_id(&self) -> DefId { + self.trait_ref.def_id + } + + // returns None if not yet calculated + pub fn object_safety(&self) -> Option { + if self.flags.get().intersects(TraitFlags::OBJECT_SAFETY_VALID) { + Some(self.flags.get().intersects(TraitFlags::IS_OBJECT_SAFE)) + } else { + None + } + } + + pub fn set_object_safety(&self, is_safe: bool) { + assert!(self.object_safety().map(|cs| cs == is_safe).unwrap_or(true)); + self.flags.set( + self.flags.get() | if is_safe { + TraitFlags::OBJECT_SAFETY_VALID | TraitFlags::IS_OBJECT_SAFE + } else { + TraitFlags::OBJECT_SAFETY_VALID + } + ); + } + + fn write_trait_impls(&self, tcx: &ty::ctxt<'tcx>) { + tcx.dep_graph.write(DepNode::TraitImpls(self.trait_ref.def_id)); + } + + fn read_trait_impls(&self, tcx: &ty::ctxt<'tcx>) { + tcx.dep_graph.read(DepNode::TraitImpls(self.trait_ref.def_id)); + } + + /// Records a trait-to-implementation mapping. + pub fn record_impl(&self, + tcx: &ty::ctxt<'tcx>, + impl_def_id: DefId, + impl_trait_ref: ty::TraitRef<'tcx>) { + debug!("TraitDef::record_impl for {:?}, from {:?}", + self, impl_trait_ref); + + // Record the write into the impl set, but only for local + // impls: external impls are handled differently. + if impl_def_id.is_local() { + self.write_trait_impls(tcx); + } + + // We don't want to borrow_mut after we already populated all impls, + // so check if an impl is present with an immutable borrow first. + if let Some(sty) = fast_reject::simplify_type(tcx, + impl_trait_ref.self_ty(), false) { + if let Some(is) = self.nonblanket_impls.borrow().get(&sty) { + if is.contains(&impl_def_id) { + return // duplicate - skip + } + } + + self.nonblanket_impls.borrow_mut().entry(sty).or_insert(vec![]).push(impl_def_id) + } else { + if self.blanket_impls.borrow().contains(&impl_def_id) { + return // duplicate - skip + } + self.blanket_impls.borrow_mut().push(impl_def_id) + } + } + + pub fn for_each_impl(&self, tcx: &ty::ctxt<'tcx>, mut f: F) { + self.read_trait_impls(tcx); + + tcx.populate_implementations_for_trait_if_necessary(self.trait_ref.def_id); + + for &impl_def_id in self.blanket_impls.borrow().iter() { + f(impl_def_id); + } + + for v in self.nonblanket_impls.borrow().values() { + for &impl_def_id in v { + f(impl_def_id); + } + } + } + + /// Iterate over every impl that could possibly match the + /// self-type `self_ty`. + pub fn for_each_relevant_impl(&self, + tcx: &ty::ctxt<'tcx>, + self_ty: Ty<'tcx>, + mut f: F) + { + self.read_trait_impls(tcx); + + tcx.populate_implementations_for_trait_if_necessary(self.trait_ref.def_id); + + for &impl_def_id in self.blanket_impls.borrow().iter() { + f(impl_def_id); + } + + // simplify_type(.., false) basically replaces type parameters and + // projections with infer-variables. This is, of course, done on + // the impl trait-ref when it is instantiated, but not on the + // predicate trait-ref which is passed here. + // + // for example, if we match `S: Copy` against an impl like + // `impl Copy for Option`, we replace the type variable + // in `Option` with an infer variable, to `Option<_>` (this + // doesn't actually change fast_reject output), but we don't + // replace `S` with anything - this impl of course can't be + // selected, and as there are hundreds of similar impls, + // considering them would significantly harm performance. + if let Some(simp) = fast_reject::simplify_type(tcx, self_ty, true) { + if let Some(impls) = self.nonblanket_impls.borrow().get(&simp) { + for &impl_def_id in impls { + f(impl_def_id); + } + } + } else { + for v in self.nonblanket_impls.borrow().values() { + for &impl_def_id in v { + f(impl_def_id); + } + } + } + } + + pub fn borrow_impl_lists<'s>(&'s self, tcx: &ty::ctxt<'tcx>) + -> (Ref<'s, Vec>, + Ref<'s, FnvHashMap>>) { + self.read_trait_impls(tcx); + (self.blanket_impls.borrow(), self.nonblanket_impls.borrow()) + } + +} + +bitflags! { + flags TraitFlags: u32 { + const NO_TRAIT_FLAGS = 0, + const HAS_DEFAULT_IMPL = 1 << 0, + const IS_OBJECT_SAFE = 1 << 1, + const OBJECT_SAFETY_VALID = 1 << 2, + const IMPLS_VALID = 1 << 3, + } +} + diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 745be426676..0134bcdf175 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -125,6 +125,8 @@ pub struct Options { pub parse_only: bool, pub no_trans: bool, pub treat_err_as_bug: bool, + pub incremental_compilation: bool, + pub dump_dep_graph: bool, pub no_analysis: bool, pub debugging_opts: DebuggingOptions, pub prints: Vec, @@ -234,6 +236,8 @@ pub fn basic_options() -> Options { parse_only: false, no_trans: false, treat_err_as_bug: false, + incremental_compilation: false, + dump_dep_graph: false, no_analysis: false, debugging_opts: basic_debugging_options(), prints: Vec::new(), @@ -606,6 +610,10 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "run all passes except translation; no output"), treat_err_as_bug: bool = (false, parse_bool, "treat all errors that occur as bugs"), + incr_comp: bool = (false, parse_bool, + "enable incremental compilation (experimental)"), + dump_dep_graph: bool = (false, parse_bool, + "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv)"), no_analysis: bool = (false, parse_bool, "parse and expand the source, but run no analysis"), extra_plugins: Vec = (Vec::new(), parse_list, @@ -932,6 +940,8 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { let parse_only = debugging_opts.parse_only; let no_trans = debugging_opts.no_trans; let treat_err_as_bug = debugging_opts.treat_err_as_bug; + let incremental_compilation = debugging_opts.incr_comp; + let dump_dep_graph = debugging_opts.dump_dep_graph; let no_analysis = debugging_opts.no_analysis; if debugging_opts.debug_llvm { @@ -1106,6 +1116,8 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { parse_only: parse_only, no_trans: no_trans, treat_err_as_bug: treat_err_as_bug, + incremental_compilation: incremental_compilation || dump_dep_graph, + dump_dep_graph: dump_dep_graph, no_analysis: no_analysis, debugging_opts: debugging_opts, prints: prints, diff --git a/src/librustc/util/common.rs b/src/librustc/util/common.rs index b5d259d9ac9..2481cab78b4 100644 --- a/src/librustc/util/common.rs +++ b/src/librustc/util/common.rs @@ -201,46 +201,38 @@ pub fn block_query

(b: &hir::Block, p: P) -> bool where P: FnMut(&hir::Expr) - return v.flag; } -/// Memoizes a one-argument closure using the given RefCell containing -/// a type implementing MutableMap to serve as a cache. -/// -/// In the future the signature of this function is expected to be: -/// ``` -/// pub fn memoized>( -/// cache: &RefCell, -/// f: &|T| -> U -/// ) -> impl |T| -> U { -/// ``` -/// but currently it is not possible. -/// -/// # Examples -/// ``` -/// struct Context { -/// cache: RefCell> -/// } -/// -/// fn factorial(ctxt: &Context, n: usize) -> usize { -/// memoized(&ctxt.cache, n, |n| match n { -/// 0 | 1 => n, -/// _ => factorial(ctxt, n - 2) + factorial(ctxt, n - 1) -/// }) -/// } -/// ``` -#[inline(always)] -pub fn memoized(cache: &RefCell>, arg: T, f: F) -> U - where T: Clone + Hash + Eq, - U: Clone, - S: HashState, - F: FnOnce(T) -> U, +pub trait MemoizationMap { + type Key: Clone; + type Value: Clone; + + /// If `key` is present in the map, return the valuee, + /// otherwise invoke `op` and store the value in the map. + /// + /// NB: if the receiver is a `DepTrackingMap`, special care is + /// needed in the `op` to ensure that the correct edges are + /// added into the dep graph. See the `DepTrackingMap` impl for + /// more details! + fn memoize(&self, key: Self::Key, op: OP) -> Self::Value + where OP: FnOnce() -> Self::Value; +} + +impl MemoizationMap for RefCell> + where K: Hash+Eq+Clone, V: Clone, S: HashState { - let key = arg.clone(); - let result = cache.borrow().get(&key).cloned(); - match result { - Some(result) => result, - None => { - let result = f(arg); - cache.borrow_mut().insert(key, result.clone()); - result + type Key = K; + type Value = V; + + fn memoize(&self, key: K, op: OP) -> V + where OP: FnOnce() -> V + { + let result = self.borrow().get(&key).cloned(); + match result { + Some(result) => result, + None => { + let result = op(); + self.borrow_mut().insert(key, result.clone()); + result + } } } } diff --git a/src/librustc_borrowck/borrowck/mod.rs b/src/librustc_borrowck/borrowck/mod.rs index 0a2586755ce..631149e69d7 100644 --- a/src/librustc_borrowck/borrowck/mod.rs +++ b/src/librustc_borrowck/borrowck/mod.rs @@ -20,6 +20,7 @@ pub use self::MovedValueUseKind::*; use self::InteriorKind::*; +use rustc::dep_graph::DepNode; use rustc::front::map as hir_map; use rustc::front::map::blocks::FnParts; use rustc::middle::cfg; @@ -109,7 +110,7 @@ pub fn check_crate(tcx: &ty::ctxt) { } }; - tcx.map.krate().visit_all_items(&mut bccx); + tcx.visit_all_items_in_krate(DepNode::BorrowCheck, &mut bccx); if tcx.sess.borrowck_stats() { println!("--- borrowck stats ---"); diff --git a/src/librustc_data_structures/graph/mod.rs b/src/librustc_data_structures/graph/mod.rs index 4a3810c822b..1ea09490aed 100644 --- a/src/librustc_data_structures/graph/mod.rs +++ b/src/librustc_data_structures/graph/mod.rs @@ -77,16 +77,16 @@ impl Debug for Edge { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct NodeIndex(pub usize); -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct EdgeIndex(pub usize); pub const INVALID_EDGE_INDEX: EdgeIndex = EdgeIndex(usize::MAX); // Use a private field here to guarantee no more instances are created: -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct Direction { repr: usize } pub const OUTGOING: Direction = Direction { repr: 0 }; @@ -410,4 +410,12 @@ impl Edge { pub fn target(&self) -> NodeIndex { self.target } + + pub fn source_or_target(&self, direction: Direction) -> NodeIndex { + if direction == OUTGOING { + self.target + } else { + self.source + } + } } diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs index 3bba7d651ad..ef64d7dde09 100644 --- a/src/librustc_data_structures/lib.rs +++ b/src/librustc_data_structures/lib.rs @@ -40,6 +40,7 @@ pub mod transitive_relation; pub mod unify; pub mod fnv; pub mod tuple_slice; +pub mod veccell; // See comments in src/librustc/lib.rs #[doc(hidden)] diff --git a/src/librustc_data_structures/veccell/mod.rs b/src/librustc_data_structures/veccell/mod.rs new file mode 100644 index 00000000000..008642d9d65 --- /dev/null +++ b/src/librustc_data_structures/veccell/mod.rs @@ -0,0 +1,47 @@ +// Copyright 2012-2014 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. + +use std::cell::UnsafeCell; +use std::mem; + +pub struct VecCell { + data: UnsafeCell> +} + +impl VecCell { + pub fn with_capacity(capacity: usize) -> VecCell{ + VecCell { data: UnsafeCell::new(Vec::with_capacity(capacity)) } + } + + #[inline] + pub fn push(&self, data: T) -> usize { + // The logic here, and in `swap` below, is that the `push` + // method on the vector will not recursively access this + // `VecCell`. Therefore, we can temporarily obtain mutable + // access, secure in the knowledge that even if aliases exist + // -- indeed, even if aliases are reachable from within the + // vector -- they will not be used for the duration of this + // particular fn call. (Note that we also are relying on the + // fact that `VecCell` is not `Sync`.) + unsafe { + let v = self.data.get(); + (*v).push(data); + (*v).len() + } + } + + pub fn swap(&self, mut data: Vec) -> Vec { + unsafe { + let v = self.data.get(); + mem::swap(&mut *v, &mut data); + } + data + } +} diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 27740b8fc5c..d172bfb4413 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -801,7 +801,7 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session, time(time_passes, "rvalue checking", - || middle::check_rvalues::check_crate(tcx, krate)); + || middle::check_rvalues::check_crate(tcx)); // Avoid overwhelming user with errors if type checking failed. // I'm not sure how helpful this is, to be honest, but it avoids diff --git a/src/librustc_driver/pretty.rs b/src/librustc_driver/pretty.rs index 47b89e3be5d..ba5ecc22e74 100644 --- a/src/librustc_driver/pretty.rs +++ b/src/librustc_driver/pretty.rs @@ -204,6 +204,7 @@ impl PpSourceMode { let annotation = TypedAnnotation { tcx: tcx, }; + let _ignore = tcx.dep_graph.in_ignore(); f(&annotation, payload, &ast_map.forest.krate) diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs index 54cccf087eb..0b48cad36ba 100644 --- a/src/librustc_metadata/decoder.rs +++ b/src/librustc_metadata/decoder.rs @@ -38,7 +38,7 @@ use middle::ty::{self, RegionEscape, Ty}; use rustc::mir; use rustc::mir::visit::MutVisitor; -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::io::prelude::*; use std::io; use std::rc::Rc; @@ -353,16 +353,11 @@ pub fn get_trait_def<'tcx>(cdata: Cmd, let associated_type_names = parse_associated_type_names(item_doc); let paren_sugar = parse_paren_sugar(item_doc); - ty::TraitDef { - paren_sugar: paren_sugar, - unsafety: unsafety, - generics: generics, - trait_ref: item_trait_ref(item_doc, tcx, cdata), - associated_type_names: associated_type_names, - nonblanket_impls: RefCell::new(FnvHashMap()), - blanket_impls: RefCell::new(vec![]), - flags: Cell::new(ty::TraitFlags::NO_TRAIT_FLAGS) - } + ty::TraitDef::new(unsafety, + paren_sugar, + generics, + item_trait_ref(item_doc, tcx, cdata), + associated_type_names) } pub fn get_adt_def<'tcx>(intr: &IdentInterner, diff --git a/src/librustc_mir/mir_map.rs b/src/librustc_mir/mir_map.rs index a3ca4c05456..ac15878dc51 100644 --- a/src/librustc_mir/mir_map.rs +++ b/src/librustc_mir/mir_map.rs @@ -24,6 +24,7 @@ use build; use graphviz; use pretty; use transform::*; +use rustc::dep_graph::DepNode; use rustc::mir::repr::Mir; use hair::cx::Cx; use std::fs::File; @@ -48,7 +49,7 @@ pub fn build_mir_for_crate<'tcx>(tcx: &ty::ctxt<'tcx>) -> MirMap<'tcx> { tcx: tcx, map: &mut map, }; - tcx.map.krate().visit_all_items(&mut dump); + tcx.visit_all_items_in_krate(DepNode::MirMapConstruction, &mut dump); } map } diff --git a/src/librustc_privacy/lib.rs b/src/librustc_privacy/lib.rs index 1cc2482a39e..955e68be0b0 100644 --- a/src/librustc_privacy/lib.rs +++ b/src/librustc_privacy/lib.rs @@ -35,6 +35,7 @@ use std::mem::replace; use rustc_front::hir; use rustc_front::intravisit::{self, Visitor}; +use rustc::dep_graph::DepNode; use rustc::lint; use rustc::middle::def; use rustc::middle::def_id::DefId; @@ -1674,6 +1675,8 @@ pub fn check_crate(tcx: &ty::ctxt, export_map: &def::ExportMap, external_exports: ExternalExports) -> AccessLevels { + let _task = tcx.dep_graph.in_task(DepNode::Privacy); + let krate = tcx.map.krate(); // Sanity check to make sure that all privacy usage and controls are diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 8446db65a4c..e1edbf4a127 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -182,8 +182,10 @@ pub fn find_crate_name(sess: Option<&Session>, "rust_out".to_string() } -pub fn build_link_meta(sess: &Session, krate: &hir::Crate, - name: &str) -> LinkMeta { +pub fn build_link_meta(sess: &Session, + krate: &hir::Crate, + name: &str) + -> LinkMeta { let r = LinkMeta { crate_name: name.to_owned(), crate_hash: Svh::calculate(&sess.opts.cg.metadata, krate), diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs index a17e0a4ccd7..0892cf1b5d3 100644 --- a/src/librustc_trans/lib.rs +++ b/src/librustc_trans/lib.rs @@ -27,6 +27,7 @@ #![feature(const_fn)] #![feature(custom_attribute)] #![allow(unused_attributes)] +#![feature(into_cow)] #![feature(iter_arith)] #![feature(libc)] #![feature(path_relative_from)] diff --git a/src/librustc_trans/save/mod.rs b/src/librustc_trans/save/mod.rs index 501ab566f1c..e1343c73acf 100644 --- a/src/librustc_trans/save/mod.rs +++ b/src/librustc_trans/save/mod.rs @@ -716,6 +716,8 @@ pub fn process_crate<'l, 'tcx>(tcx: &'l ty::ctxt<'tcx>, analysis: &ty::CrateAnalysis, cratename: &str, odir: Option<&Path>) { + let _ignore = tcx.dep_graph.in_ignore(); + if generated_code(krate.span) { return; } diff --git a/src/librustc_trans/trans/assert_dep_graph.rs b/src/librustc_trans/trans/assert_dep_graph.rs new file mode 100644 index 00000000000..924700f0ae5 --- /dev/null +++ b/src/librustc_trans/trans/assert_dep_graph.rs @@ -0,0 +1,430 @@ +// Copyright 2012-2015 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. + +//! This pass is only used for the UNIT TESTS and DEBUGGING NEEDS +//! around dependency graph construction. It serves two purposes; it +//! will dump graphs in graphviz form to disk, and it searches for +//! `#[rustc_if_this_changed]` and `#[rustc_then_this_would_need]` +//! annotations. These annotations can be used to test whether paths +//! exist in the graph. We report errors on each +//! `rustc_if_this_changed` annotation. If a path exists in all +//! cases, then we would report "all path(s) exist". Otherwise, we +//! report: "no path to `foo`" for each case where no path exists. +//! `compile-fail` tests can then be used to check when paths exist or +//! do not. +//! +//! The full form of the `rustc_if_this_changed` annotation is +//! `#[rustc_if_this_changed(id)]`. The `"id"` is optional and +//! defaults to `"id"` if omitted. +//! +//! Example: +//! +//! ``` +//! #[rustc_if_this_changed] +//! fn foo() { } +//! +//! #[rustc_then_this_would_need("trans")] //~ ERROR no path from `foo` +//! fn bar() { } +//! +//! #[rustc_then_this_would_need("trans")] //~ ERROR OK +//! fn baz() { foo(); } +//! ``` + +use graphviz as dot; +use rustc::dep_graph::{DepGraphQuery, DepNode}; +use rustc::middle::def_id::DefId; +use rustc::middle::ty; +use rustc_data_structures::fnv::{FnvHashMap, FnvHashSet}; +use rustc_data_structures::graph::{Direction, INCOMING, OUTGOING, NodeIndex}; +use rustc_front::hir; +use rustc_front::intravisit::Visitor; +use std::borrow::IntoCow; +use std::env; +use std::fs::File; +use std::io::Write; +use syntax::ast; +use syntax::attr::AttrMetaMethods; +use syntax::codemap::Span; +use syntax::parse::token::InternedString; + +const IF_THIS_CHANGED: &'static str = "rustc_if_this_changed"; +const THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need"; +const ID: &'static str = "id"; + +pub fn assert_dep_graph(tcx: &ty::ctxt) { + let _ignore = tcx.dep_graph.in_ignore(); + + if tcx.sess.opts.dump_dep_graph { + dump_graph(tcx); + } + + // Find annotations supplied by user (if any). + let (if_this_changed, then_this_would_need) = { + let mut visitor = IfThisChanged { tcx: tcx, + if_this_changed: FnvHashMap(), + then_this_would_need: FnvHashMap() }; + tcx.map.krate().visit_all_items(&mut visitor); + (visitor.if_this_changed, visitor.then_this_would_need) + }; + + // Check paths. + check_paths(tcx, &if_this_changed, &then_this_would_need); +} + +type SourceHashMap = FnvHashMap>; +type TargetHashMap = FnvHashMap>; + +struct IfThisChanged<'a, 'tcx:'a> { + tcx: &'a ty::ctxt<'tcx>, + if_this_changed: SourceHashMap, + then_this_would_need: TargetHashMap, +} + +impl<'a, 'tcx> IfThisChanged<'a, 'tcx> { + fn process_attrs(&mut self, node_id: ast::NodeId, def_id: DefId) { + for attr in self.tcx.get_attrs(def_id).iter() { + if attr.check_name(IF_THIS_CHANGED) { + let mut id = None; + for meta_item in attr.meta_item_list().unwrap_or_default() { + match meta_item.node { + ast::MetaWord(ref s) if id.is_none() => id = Some(s.clone()), + _ => { + self.tcx.sess.span_err( + meta_item.span, + &format!("unexpected meta-item {:?}", meta_item.node)); + } + } + } + let id = id.unwrap_or(InternedString::new(ID)); + self.if_this_changed.entry(id) + .or_insert(FnvHashSet()) + .insert((attr.span, def_id, DepNode::Hir(def_id))); + } else if attr.check_name(THEN_THIS_WOULD_NEED) { + let mut dep_node_interned = None; + let mut id = None; + for meta_item in attr.meta_item_list().unwrap_or_default() { + match meta_item.node { + ast::MetaWord(ref s) if dep_node_interned.is_none() => + dep_node_interned = Some(s.clone()), + ast::MetaWord(ref s) if id.is_none() => + id = Some(s.clone()), + _ => { + self.tcx.sess.span_err( + meta_item.span, + &format!("unexpected meta-item {:?}", meta_item.node)); + } + } + } + let dep_node_str = dep_node_interned.as_ref().map(|s| &**s); + macro_rules! match_depnode_name { + ($input:expr, $def_id:expr, match { $($variant:ident,)* } else $y:expr) => { + match $input { + $(Some(stringify!($variant)) => DepNode::$variant($def_id),)* + _ => $y + } + } + } + let dep_node = match_depnode_name! { + dep_node_str, def_id, match { + CollectItem, + BorrowCheck, + TransCrateItem, + TypeckItemType, + TypeckItemBody, + ImplOrTraitItems, + ItemSignature, + FieldTy, + TraitItemDefIds, + InherentImpls, + ImplItems, + TraitImpls, + ReprHints, + } else { + self.tcx.sess.span_fatal( + attr.span, + &format!("unrecognized DepNode variant {:?}", dep_node_str)); + } + }; + let id = id.unwrap_or(InternedString::new(ID)); + self.then_this_would_need + .entry(id) + .or_insert(FnvHashSet()) + .insert((attr.span, dep_node_interned.clone().unwrap(), node_id, dep_node)); + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for IfThisChanged<'a, 'tcx> { + fn visit_item(&mut self, item: &'tcx hir::Item) { + let def_id = self.tcx.map.local_def_id(item.id); + self.process_attrs(item.id, def_id); + } +} + +fn check_paths(tcx: &ty::ctxt, + if_this_changed: &SourceHashMap, + then_this_would_need: &TargetHashMap) +{ + // Return early here so as not to construct the query, which is not cheap. + if if_this_changed.is_empty() { + return; + } + let query = tcx.dep_graph.query(); + for (id, sources) in if_this_changed { + let targets = match then_this_would_need.get(id) { + Some(targets) => targets, + None => { + for &(source_span, _, _) in sources.iter().take(1) { + tcx.sess.span_err( + source_span, + &format!("no targets for id `{}`", id)); + } + continue; + } + }; + + for &(_, source_def_id, source_dep_node) in sources { + let dependents = query.dependents(source_dep_node); + for &(target_span, ref target_pass, _, ref target_dep_node) in targets { + if !dependents.contains(&target_dep_node) { + tcx.sess.span_err( + target_span, + &format!("no path from `{}` to `{}`", + tcx.item_path_str(source_def_id), + target_pass)); + } else { + tcx.sess.span_err( + target_span, + &format!("OK")); + } + } + } + } +} + +fn dump_graph(tcx: &ty::ctxt) { + let path: String = env::var("RUST_DEP_GRAPH").unwrap_or_else(|_| format!("dep_graph")); + let query = tcx.dep_graph.query(); + + let nodes = match env::var("RUST_DEP_GRAPH_FILTER") { + Ok(string) => { + // Expect one of: "-> target", "source -> target", or "source ->". + let parts: Vec<_> = string.split("->").collect(); + if parts.len() > 2 { + panic!("Invalid RUST_DEP_GRAPH_FILTER: expected '[source] -> [target]'"); + } + let sources = node_set(&query, &parts[0]); + let targets = node_set(&query, &parts[1]); + filter_nodes(&query, &sources, &targets) + } + Err(_) => { + query.nodes() + .into_iter() + .collect() + } + }; + let edges = filter_edges(&query, &nodes); + + { // dump a .txt file with just the edges: + let txt_path = format!("{}.txt", path); + let mut file = File::create(&txt_path).unwrap(); + for &(source, target) in &edges { + write!(file, "{:?} -> {:?}\n", source, target).unwrap(); + } + } + + { // dump a .dot file in graphviz format: + let dot_path = format!("{}.dot", path); + let mut v = Vec::new(); + dot::render(&GraphvizDepGraph(nodes, edges), &mut v).unwrap(); + File::create(&dot_path).and_then(|mut f| f.write_all(&v)).unwrap(); + } +} + +pub struct GraphvizDepGraph(FnvHashSet, Vec<(DepNode, DepNode)>); + +impl<'a, 'tcx> dot::GraphWalk<'a, DepNode, (DepNode, DepNode)> for GraphvizDepGraph { + fn nodes(&self) -> dot::Nodes { + let nodes: Vec<_> = self.0.iter().cloned().collect(); + nodes.into_cow() + } + fn edges(&self) -> dot::Edges<(DepNode, DepNode)> { + self.1[..].into_cow() + } + fn source(&self, edge: &(DepNode, DepNode)) -> DepNode { + edge.0 + } + fn target(&self, edge: &(DepNode, DepNode)) -> DepNode { + edge.1 + } +} + +impl<'a, 'tcx> dot::Labeller<'a, DepNode, (DepNode, DepNode)> for GraphvizDepGraph { + fn graph_id(&self) -> dot::Id { + dot::Id::new("DependencyGraph").unwrap() + } + fn node_id(&self, n: &DepNode) -> dot::Id { + let s: String = + format!("{:?}", n).chars() + .map(|c| if c == '_' || c.is_alphanumeric() { c } else { '_' }) + .collect(); + debug!("n={:?} s={:?}", n, s); + dot::Id::new(s).unwrap() + } + fn node_label(&self, n: &DepNode) -> dot::LabelText { + dot::LabelText::label(format!("{:?}", n)) + } +} + +// Given an optional filter like `"x,y,z"`, returns either `None` (no +// filter) or the set of nodes whose labels contain all of those +// substrings. +fn node_set(query: &DepGraphQuery, filter: &str) -> Option> { + debug!("node_set(filter={:?})", filter); + + if filter.trim().is_empty() { + return None; + } + + let filters: Vec<&str> = filter.split("&").map(|s| s.trim()).collect(); + + debug!("node_set: filters={:?}", filters); + + Some(query.nodes() + .into_iter() + .filter(|n| { + let s = format!("{:?}", n); + filters.iter().all(|f| s.contains(f)) + }) + .collect()) +} + +fn filter_nodes(query: &DepGraphQuery, + sources: &Option>, + targets: &Option>) + -> FnvHashSet +{ + if let &Some(ref sources) = sources { + if let &Some(ref targets) = targets { + walk_between(query, sources, targets) + } else { + walk_nodes(query, sources, OUTGOING) + } + } else if let &Some(ref targets) = targets { + walk_nodes(query, targets, INCOMING) + } else { + query.nodes().into_iter().collect() + } +} + +fn walk_nodes(query: &DepGraphQuery, + starts: &FnvHashSet, + direction: Direction) + -> FnvHashSet +{ + let mut set = FnvHashSet(); + for start in starts { + debug!("walk_nodes: start={:?} outgoing?={:?}", start, direction == OUTGOING); + if set.insert(*start) { + let mut stack = vec![query.indices[start]]; + while let Some(index) = stack.pop() { + for (_, edge) in query.graph.adjacent_edges(index, direction) { + let neighbor_index = edge.source_or_target(direction); + let neighbor = query.graph.node_data(neighbor_index); + if set.insert(*neighbor) { + stack.push(neighbor_index); + } + } + } + } + } + set +} + +fn walk_between(query: &DepGraphQuery, + sources: &FnvHashSet, + targets: &FnvHashSet) + -> FnvHashSet +{ + // This is a bit tricky. We want to include a node only if it is: + // (a) reachable from a source and (b) will reach a target. And we + // have to be careful about cycles etc. Luckily efficiency is not + // a big concern! + + #[derive(Copy, Clone, PartialEq)] + enum State { Undecided, Deciding, Included, Excluded } + + let mut node_states = vec![State::Undecided; query.graph.len_nodes()]; + + for &target in targets { + node_states[query.indices[&target].0] = State::Included; + } + + for source in sources.iter().map(|n| query.indices[n]) { + recurse(query, &mut node_states, source); + } + + return query.nodes() + .into_iter() + .filter(|n| { + let index = query.indices[n]; + node_states[index.0] == State::Included + }) + .collect(); + + fn recurse(query: &DepGraphQuery, + node_states: &mut [State], + node: NodeIndex) + -> bool + { + match node_states[node.0] { + // known to reach a target + State::Included => return true, + + // known not to reach a target + State::Excluded => return false, + + // backedge, not yet known, say false + State::Deciding => return false, + + State::Undecided => { } + } + + node_states[node.0] = State::Deciding; + + for neighbor_index in query.graph.successor_nodes(node) { + if recurse(query, node_states, neighbor_index) { + node_states[node.0] = State::Included; + } + } + + // if we didn't find a path to target, then set to excluded + if node_states[node.0] == State::Deciding { + node_states[node.0] = State::Excluded; + false + } else { + assert!(node_states[node.0] == State::Included); + true + } + } +} + +fn filter_edges(query: &DepGraphQuery, + nodes: &FnvHashSet) + -> Vec<(DepNode, DepNode)> +{ + query.edges() + .into_iter() + .filter(|&(source, target)| nodes.contains(&source) && nodes.contains(&target)) + .collect() +} diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index a6e6d304220..5a40ff76252 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -43,6 +43,7 @@ use middle::weak_lang_items; use middle::pat_util::simple_name; use middle::subst::Substs; use middle::ty::{self, Ty, HasTypeFlags}; +use rustc::dep_graph::DepNode; use rustc::front::map as hir_map; use rustc::util::common::time; use rustc_mir::mir_map::MirMap; @@ -50,6 +51,7 @@ use session::config::{self, NoDebugInfo, FullDebugInfo}; use session::Session; use trans::_match; use trans::adt; +use trans::assert_dep_graph; use trans::attributes; use trans::build::*; use trans::builder::{Builder, noname}; @@ -2984,9 +2986,16 @@ pub fn trans_crate<'tcx>(tcx: &ty::ctxt<'tcx>, mir_map: &MirMap<'tcx>, analysis: ty::CrateAnalysis) -> CrateTranslation { - let ty::CrateAnalysis { export_map, reachable, name, .. } = analysis; + let _task = tcx.dep_graph.in_task(DepNode::TransCrate); + + // Be careful with this krate: obviously it gives access to the + // entire contents of the krate. So if you push any subtasks of + // `TransCrate`, you need to be careful to register "reads" of the + // particular items that will be processed. let krate = tcx.map.krate(); + let ty::CrateAnalysis { export_map, reachable, name, .. } = analysis; + let check_overflow = if let Some(v) = tcx.sess.opts.debugging_opts.force_overflow_checks { v } else { @@ -3140,6 +3149,8 @@ pub fn trans_crate<'tcx>(tcx: &ty::ctxt<'tcx>, }; let no_builtins = attr::contains_name(&krate.attrs, "no_builtins"); + assert_dep_graph::assert_dep_graph(tcx); + CrateTranslation { modules: modules, metadata_module: metadata_module, @@ -3192,7 +3203,16 @@ impl<'a, 'tcx, 'v> Visitor<'v> for TransItemsWithinModVisitor<'a, 'tcx> { // skip modules, they will be uncovered by the TransModVisitor } _ => { - trans_item(self.ccx, i); + let def_id = self.ccx.tcx().map.local_def_id(i.id); + let tcx = self.ccx.tcx(); + + // Create a subtask for trans'ing a particular item. We are + // giving `trans_item` access to this item, so also record a read. + tcx.dep_graph.with_task(DepNode::TransCrateItem(def_id), || { + tcx.dep_graph.read(DepNode::Hir(def_id)); + trans_item(self.ccx, i); + }); + intravisit::walk_item(self, i); } } diff --git a/src/librustc_trans/trans/inline.rs b/src/librustc_trans/trans/inline.rs index 5968daf349f..baf244c2e79 100644 --- a/src/librustc_trans/trans/inline.rs +++ b/src/librustc_trans/trans/inline.rs @@ -15,12 +15,13 @@ use middle::subst::Substs; use trans::base::{push_ctxt, trans_item, get_item_val, trans_fn}; use trans::common::*; +use rustc::dep_graph::DepNode; use rustc_front::hir; -fn instantiate_inline(ccx: &CrateContext, fn_id: DefId) - -> Option { +fn instantiate_inline(ccx: &CrateContext, fn_id: DefId) -> Option { debug!("instantiate_inline({:?})", fn_id); let _icx = push_ctxt("instantiate_inline"); + let _task = ccx.tcx().dep_graph.in_task(DepNode::TransInlinedItem(fn_id)); match ccx.external().borrow().get(&fn_id) { Some(&Some(node_id)) => { diff --git a/src/librustc_trans/trans/intrinsic.rs b/src/librustc_trans/trans/intrinsic.rs index 66f53deffa9..d8a3cc50ff4 100644 --- a/src/librustc_trans/trans/intrinsic.rs +++ b/src/librustc_trans/trans/intrinsic.rs @@ -37,6 +37,7 @@ use trans::machine; use trans::type_::Type; use middle::ty::{self, Ty, HasTypeFlags}; use middle::subst::Substs; +use rustc::dep_graph::DepNode; use rustc_front::hir; use syntax::abi::{self, RustIntrinsic}; use syntax::ast; @@ -101,6 +102,7 @@ pub fn span_transmute_size_error(a: &Session, b: Span, msg: &str) { /// Performs late verification that intrinsics are used correctly. At present, /// the only intrinsic that needs such verification is `transmute`. pub fn check_intrinsics(ccx: &CrateContext) { + let _task = ccx.tcx().dep_graph.in_task(DepNode::IntrinsicUseCheck); let mut last_failing_id = None; for transmute_restriction in ccx.tcx().transmute_restrictions.borrow().iter() { // Sometimes, a single call to transmute will push multiple diff --git a/src/librustc_trans/trans/mod.rs b/src/librustc_trans/trans/mod.rs index b102e96af20..1fbc0d5c015 100644 --- a/src/librustc_trans/trans/mod.rs +++ b/src/librustc_trans/trans/mod.rs @@ -20,6 +20,7 @@ mod macros; mod adt; mod asm; +mod assert_dep_graph; mod attributes; mod base; mod basic_block; diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index ca2db8c3def..14adc84f701 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -82,6 +82,7 @@ use self::TupleArgumentsFlag::*; use astconv::{self, ast_region_to_region, ast_ty_to_ty, AstConv, PathParamMode}; use check::_match::pat_ctxt; +use dep_graph::DepNode; use fmt_macros::{Parser, Piece, Position}; use middle::astconv_util::prohibit_type_params; use middle::cstore::LOCAL_CRATE; @@ -384,34 +385,33 @@ impl<'a, 'tcx> Visitor<'tcx> for CheckItemBodiesVisitor<'a, 'tcx> { pub fn check_wf_new(ccx: &CrateCtxt) { ccx.tcx.sess.abort_if_new_errors(|| { - let krate = ccx.tcx.map.krate(); let mut visit = wfcheck::CheckTypeWellFormedVisitor::new(ccx); - krate.visit_all_items(&mut visit); + ccx.tcx.visit_all_items_in_krate(DepNode::WfCheck, &mut visit); }); } pub fn check_item_types(ccx: &CrateCtxt) { ccx.tcx.sess.abort_if_new_errors(|| { - let krate = ccx.tcx.map.krate(); let mut visit = CheckItemTypesVisitor { ccx: ccx }; - krate.visit_all_items(&mut visit); + ccx.tcx.visit_all_items_in_krate(DepNode::TypeckItemType, &mut visit); }); } pub fn check_item_bodies(ccx: &CrateCtxt) { ccx.tcx.sess.abort_if_new_errors(|| { - let krate = ccx.tcx.map.krate(); let mut visit = CheckItemBodiesVisitor { ccx: ccx }; - krate.visit_all_items(&mut visit); + ccx.tcx.visit_all_items_in_krate(DepNode::TypeckItemBody, &mut visit); }); } pub fn check_drop_impls(ccx: &CrateCtxt) { ccx.tcx.sess.abort_if_new_errors(|| { + let _task = ccx.tcx.dep_graph.in_task(DepNode::Dropck); let drop_trait = match ccx.tcx.lang_items.drop_trait() { Some(id) => ccx.tcx.lookup_trait_def(id), None => { return } }; drop_trait.for_each_impl(ccx.tcx, |drop_impl_did| { + let _task = ccx.tcx.dep_graph.in_task(DepNode::DropckImpl(drop_impl_did)); if drop_impl_did.is_local() { match dropck::check_drop_impl(ccx.tcx, drop_impl_did) { Ok(()) => {} diff --git a/src/librustc_typeck/coherence/mod.rs b/src/librustc_typeck/coherence/mod.rs index 02be74a5906..07c920829d9 100644 --- a/src/librustc_typeck/coherence/mod.rs +++ b/src/librustc_typeck/coherence/mod.rs @@ -39,9 +39,10 @@ use std::rc::Rc; use syntax::codemap::Span; use syntax::parse::token; use util::nodemap::{DefIdMap, FnvHashMap}; +use rustc::dep_graph::DepNode; use rustc::front::map as hir_map; use rustc_front::intravisit; -use rustc_front::hir::{Item, ItemImpl,Crate}; +use rustc_front::hir::{Item, ItemImpl}; use rustc_front::hir; mod orphan; @@ -104,11 +105,13 @@ impl<'a, 'tcx, 'v> intravisit::Visitor<'v> for CoherenceCheckVisitor<'a, 'tcx> { } impl<'a, 'tcx> CoherenceChecker<'a, 'tcx> { - fn check(&self, krate: &Crate) { + fn check(&self) { // Check implementations and traits. This populates the tables // containing the inherent methods and extension methods. It also // builds up the trait inheritance table. - krate.visit_all_items(&mut CoherenceCheckVisitor { cc: self }); + self.crate_context.tcx.visit_all_items_in_krate( + DepNode::CoherenceCheckImpl, + &mut CoherenceCheckVisitor { cc: self }); // Copy over the inherent impls we gathered up during the walk into // the tcx. @@ -513,11 +516,13 @@ fn enforce_trait_manually_implementable(tcx: &ty::ctxt, sp: Span, trait_def_id: } pub fn check_coherence(crate_context: &CrateCtxt) { + let _task = crate_context.tcx.dep_graph.in_task(DepNode::Coherence); + let infcx = new_infer_ctxt(crate_context.tcx, &crate_context.tcx.tables, None, true); CoherenceChecker { crate_context: crate_context, - inference_context: new_infer_ctxt(crate_context.tcx, &crate_context.tcx.tables, None, true), + inference_context: infcx, inherent_impls: RefCell::new(FnvHashMap()), - }.check(crate_context.tcx.map.krate()); + }.check(); unsafety::check(crate_context.tcx); orphan::check(crate_context.tcx); overlap::check(crate_context.tcx); diff --git a/src/librustc_typeck/coherence/orphan.rs b/src/librustc_typeck/coherence/orphan.rs index 76be04bb174..69eb7f51f37 100644 --- a/src/librustc_typeck/coherence/orphan.rs +++ b/src/librustc_typeck/coherence/orphan.rs @@ -17,12 +17,13 @@ use middle::traits; use middle::ty; use syntax::ast; use syntax::codemap::Span; +use rustc::dep_graph::DepNode; use rustc_front::intravisit; use rustc_front::hir; pub fn check(tcx: &ty::ctxt) { let mut orphan = OrphanChecker { tcx: tcx }; - tcx.map.krate().visit_all_items(&mut orphan); + tcx.visit_all_items_in_krate(DepNode::CoherenceOrphanCheck, &mut orphan); } struct OrphanChecker<'cx, 'tcx:'cx> { @@ -234,10 +235,10 @@ impl<'cx, 'tcx> OrphanChecker<'cx, 'tcx> { } Err(traits::OrphanCheckErr::UncoveredTy(param_ty)) => { span_err!(self.tcx.sess, item.span, E0210, - "type parameter `{}` must be used as the type parameter for \ - some local type (e.g. `MyStruct`); only traits defined in \ - the current crate can be implemented for a type parameter", - param_ty); + "type parameter `{}` must be used as the type parameter for \ + some local type (e.g. `MyStruct`); only traits defined in \ + the current crate can be implemented for a type parameter", + param_ty); return; } } diff --git a/src/librustc_typeck/coherence/overlap.rs b/src/librustc_typeck/coherence/overlap.rs index beb409f7f0f..71c6fc1fd08 100644 --- a/src/librustc_typeck/coherence/overlap.rs +++ b/src/librustc_typeck/coherence/overlap.rs @@ -18,53 +18,53 @@ use middle::ty; use middle::infer; use syntax::ast; use syntax::codemap::Span; +use rustc::dep_graph::DepNode; use rustc_front::hir; use rustc_front::intravisit; -use util::nodemap::DefIdMap; +use util::nodemap::{DefIdMap, DefIdSet}; pub fn check(tcx: &ty::ctxt) { - let mut overlap = OverlapChecker { tcx: tcx, default_impls: DefIdMap() }; - overlap.check_for_overlapping_impls(); + let mut overlap = OverlapChecker { tcx: tcx, + traits_checked: DefIdSet(), + default_impls: DefIdMap() }; // this secondary walk specifically checks for some other cases, // like defaulted traits, for which additional overlap rules exist - tcx.map.krate().visit_all_items(&mut overlap); + tcx.visit_all_items_in_krate(DepNode::CoherenceOverlapCheckSpecial, &mut overlap); } struct OverlapChecker<'cx, 'tcx:'cx> { tcx: &'cx ty::ctxt<'tcx>, + // The set of traits where we have checked for overlap. This is + // used to avoid checking the same trait twice. + // + // NB. It's ok to skip tracking this set because we fully + // encapsulate it, and we always create a task + // (`CoherenceOverlapCheck`) corresponding to each entry. + traits_checked: DefIdSet, + // maps from a trait def-id to an impl id default_impls: DefIdMap, } impl<'cx, 'tcx> OverlapChecker<'cx, 'tcx> { - fn check_for_overlapping_impls(&self) { - debug!("check_for_overlapping_impls"); + fn check_for_overlapping_impls_of_trait(&mut self, trait_def_id: DefId) { + debug!("check_for_overlapping_impls_of_trait(trait_def_id={:?})", + trait_def_id); - // Collect this into a vector to avoid holding the - // refcell-lock during the - // check_for_overlapping_impls_of_trait() check, since that - // check can populate this table further with impls from other - // crates. - let trait_defs: Vec<_> = self.tcx.trait_defs.borrow().values().cloned().collect(); - - for trait_def in trait_defs { - self.tcx.populate_implementations_for_trait_if_necessary(trait_def.trait_ref.def_id); - self.check_for_overlapping_impls_of_trait(trait_def); + let _task = self.tcx.dep_graph.in_task(DepNode::CoherenceOverlapCheck(trait_def_id)); + if !self.traits_checked.insert(trait_def_id) { + return; } - } - fn check_for_overlapping_impls_of_trait(&self, - trait_def: &'tcx ty::TraitDef<'tcx>) - { - debug!("check_for_overlapping_impls_of_trait(trait_def={:?})", - trait_def); + let trait_def = self.tcx.lookup_trait_def(trait_def_id); + self.tcx.populate_implementations_for_trait_if_necessary( + trait_def.trait_ref.def_id); // We should already know all impls of this trait, so these // borrows are safe. - let blanket_impls = trait_def.blanket_impls.borrow(); - let nonblanket_impls = trait_def.nonblanket_impls.borrow(); + let (blanket_impls, nonblanket_impls) = trait_def.borrow_impl_lists(self.tcx); // Conflicts can only occur between a blanket impl and another impl, // or between 2 non-blanket impls of the same kind. @@ -175,12 +175,20 @@ impl<'cx, 'tcx> OverlapChecker<'cx, 'tcx> { impl<'cx, 'tcx,'v> intravisit::Visitor<'v> for OverlapChecker<'cx, 'tcx> { fn visit_item(&mut self, item: &'v hir::Item) { match item.node { - hir::ItemDefaultImpl(_, _) => { + hir::ItemTrait(..) => { + let trait_def_id = self.tcx.map.local_def_id(item.id); + self.check_for_overlapping_impls_of_trait(trait_def_id); + } + + hir::ItemDefaultImpl(..) => { // look for another default impl; note that due to the // general orphan/coherence rules, it must always be // in this crate. let impl_def_id = self.tcx.map.local_def_id(item.id); let trait_ref = self.tcx.impl_trait_ref(impl_def_id).unwrap(); + + self.check_for_overlapping_impls_of_trait(trait_ref.def_id); + let prev_default_impl = self.default_impls.insert(trait_ref.def_id, item.id); match prev_default_impl { Some(prev_id) => { @@ -195,6 +203,7 @@ impl<'cx, 'tcx,'v> intravisit::Visitor<'v> for OverlapChecker<'cx, 'tcx> { let impl_def_id = self.tcx.map.local_def_id(item.id); let trait_ref = self.tcx.impl_trait_ref(impl_def_id).unwrap(); let trait_def_id = trait_ref.def_id; + self.check_for_overlapping_impls_of_trait(trait_def_id); match trait_ref.self_ty().sty { ty::TyTrait(ref data) => { // This is something like impl Trait1 for Trait2. Illegal diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index eaaa2c77379..5a9b8991758 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -34,13 +34,12 @@ lazilly and on demand, and include logic that checks for cycles. Demand is driven by calls to `AstConv::get_item_type_scheme` or `AstConv::lookup_trait_def`. -Currently, we "convert" types and traits in three phases (note that +Currently, we "convert" types and traits in two phases (note that conversion only affects the types of items / enum variants / methods; it does not e.g. compute the types of individual expressions): 0. Intrinsics -1. Trait definitions -2. Type definitions +1. Trait/Type definitions Conversion itself is done by simply walking each of the items in turn and invoking an appropriate function (e.g., `trait_def_of_item` or @@ -56,11 +55,6 @@ There are some shortcomings in this design: - Because the type scheme includes defaults, cycles through type parameter defaults are illegal even if those defaults are never employed. This is not necessarily a bug. -- The phasing of trait definitions before type definitions does not - seem to be necessary, sufficient, or particularly helpful, given that - processing a trait definition can trigger processing a type def and - vice versa. However, if I remove it, I get ICEs, so some more work is - needed in that area. -nmatsakis */ @@ -79,12 +73,13 @@ use middle::ty::{VariantKind}; use middle::ty::fold::{TypeFolder}; use middle::ty::util::IntTypeExt; use rscope::*; +use rustc::dep_graph::DepNode; use rustc::front::map as hir_map; -use util::common::{ErrorReported, memoized}; +use util::common::{ErrorReported, MemoizationMap}; use util::nodemap::{FnvHashMap, FnvHashSet}; use write_ty_to_tcx; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; @@ -104,9 +99,6 @@ use rustc_front::print::pprust; pub fn collect_item_types(tcx: &ty::ctxt) { let ccx = &CrateCtxt { tcx: tcx, stack: RefCell::new(Vec::new()) }; - let mut visitor = CollectTraitDefVisitor{ ccx: ccx }; - ccx.tcx.map.krate().visit_all_items(&mut visitor); - let mut visitor = CollectItemTypesVisitor{ ccx: ccx }; ccx.tcx.map.krate().visit_all_items(&mut visitor); } @@ -146,41 +138,17 @@ enum AstConvRequest { } /////////////////////////////////////////////////////////////////////////// -// First phase: just collect *trait definitions* -- basically, the set -// of type parameters and supertraits. This is information we need to -// know later when parsing field defs. - -struct CollectTraitDefVisitor<'a, 'tcx: 'a> { - ccx: &'a CrateCtxt<'a, 'tcx> -} - -impl<'a, 'tcx, 'v> intravisit::Visitor<'v> for CollectTraitDefVisitor<'a, 'tcx> { - fn visit_item(&mut self, i: &hir::Item) { - match i.node { - hir::ItemTrait(..) => { - // computing the trait def also fills in the table - let _ = trait_def_of_item(self.ccx, i); - } - _ => { } - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Second phase: collection proper. struct CollectItemTypesVisitor<'a, 'tcx: 'a> { ccx: &'a CrateCtxt<'a, 'tcx> } impl<'a, 'tcx, 'v> intravisit::Visitor<'v> for CollectItemTypesVisitor<'a, 'tcx> { - fn visit_item(&mut self, i: &hir::Item) { - convert_item(self.ccx, i); - intravisit::walk_item(self, i); - } - fn visit_foreign_item(&mut self, i: &hir::ForeignItem) { - convert_foreign_item(self.ccx, i); - intravisit::walk_foreign_item(self, i); + fn visit_item(&mut self, item: &hir::Item) { + let tcx = self.ccx.tcx; + let item_def_id = tcx.map.local_def_id(item.id); + let _task = tcx.dep_graph.in_task(DepNode::CollectItem(item_def_id)); + convert_item(self.ccx, item); } } @@ -703,8 +671,12 @@ fn convert_item(ccx: &CrateCtxt, it: &hir::Item) { debug!("convert: item {} with id {}", it.name, it.id); match it.node { // These don't define types. - hir::ItemExternCrate(_) | hir::ItemUse(_) | - hir::ItemForeignMod(_) | hir::ItemMod(_) => { + hir::ItemExternCrate(_) | hir::ItemUse(_) | hir::ItemMod(_) => { + } + hir::ItemForeignMod(ref foreign_mod) => { + for item in &foreign_mod.items { + convert_foreign_item(ccx, item); + } } hir::ItemEnum(ref enum_definition, _) => { let (scheme, predicates) = convert_typed_item(ccx, it); @@ -1283,16 +1255,11 @@ fn trait_def_of_item<'a, 'tcx>(ccx: &CrateCtxt<'a, 'tcx>, substs: substs, }; - let trait_def = ty::TraitDef { - paren_sugar: paren_sugar, - unsafety: unsafety, - generics: ty_generics, - trait_ref: trait_ref, - associated_type_names: associated_type_names, - nonblanket_impls: RefCell::new(FnvHashMap()), - blanket_impls: RefCell::new(vec![]), - flags: Cell::new(ty::TraitFlags::NO_TRAIT_FLAGS) - }; + let trait_def = ty::TraitDef::new(unsafety, + paren_sugar, + ty_generics, + trait_ref, + associated_type_names); return tcx.intern_trait_def(trait_def); @@ -1452,12 +1419,17 @@ fn type_scheme_of_def_id<'a,'tcx>(ccx: &CrateCtxt<'a,'tcx>, } fn type_scheme_of_item<'a,'tcx>(ccx: &CrateCtxt<'a,'tcx>, - it: &hir::Item) + item: &hir::Item) -> ty::TypeScheme<'tcx> { - memoized(&ccx.tcx.tcache, - ccx.tcx.map.local_def_id(it.id), - |_| compute_type_scheme_of_item(ccx, it)) + let item_def_id = ccx.tcx.map.local_def_id(item.id); + ccx.tcx.tcache.memoize(item_def_id, || { + // NB. Since the `memoized` function enters a new task, and we + // are giving this task access to the item `item`, we must + // register a read. + ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); + compute_type_scheme_of_item(ccx, item) + }) } fn compute_type_scheme_of_item<'a,'tcx>(ccx: &CrateCtxt<'a,'tcx>, @@ -1571,13 +1543,18 @@ fn convert_typed_item<'a, 'tcx>(ccx: &CrateCtxt<'a, 'tcx>, fn type_scheme_of_foreign_item<'a, 'tcx>( ccx: &CrateCtxt<'a, 'tcx>, - it: &hir::ForeignItem, + item: &hir::ForeignItem, abi: abi::Abi) -> ty::TypeScheme<'tcx> { - memoized(&ccx.tcx.tcache, - ccx.tcx.map.local_def_id(it.id), - |_| compute_type_scheme_of_foreign_item(ccx, it, abi)) + let item_def_id = ccx.tcx.map.local_def_id(item.id); + ccx.tcx.tcache.memoize(item_def_id, || { + // NB. Since the `memoized` function enters a new task, and we + // are giving this task access to the item `item`, we must + // register a read. + ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); + compute_type_scheme_of_foreign_item(ccx, item, abi) + }) } fn compute_type_scheme_of_foreign_item<'a, 'tcx>( diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs index bf890f3e507..580d200eb73 100644 --- a/src/librustc_typeck/lib.rs +++ b/src/librustc_typeck/lib.rs @@ -92,6 +92,7 @@ extern crate rustc_platform_intrinsics as intrinsics; extern crate rustc_front; extern crate rustc_back; +pub use rustc::dep_graph; pub use rustc::front; pub use rustc::lint; pub use rustc::middle; diff --git a/src/librustc_typeck/variance.rs b/src/librustc_typeck/variance.rs index 8c7967c7078..ce0e9e14035 100644 --- a/src/librustc_typeck/variance.rs +++ b/src/librustc_typeck/variance.rs @@ -266,6 +266,7 @@ use self::ParamKind::*; use arena; use arena::TypedArena; +use dep_graph::DepNode; use middle::def_id::DefId; use middle::resolve_lifetime as rl; use middle::subst; @@ -280,6 +281,7 @@ use rustc_front::intravisit::Visitor; use util::nodemap::NodeMap; pub fn infer_variance(tcx: &ty::ctxt) { + let _task = tcx.dep_graph.in_task(DepNode::Variance); let krate = tcx.map.krate(); let mut arena = arena::TypedArena::new(); let terms_cx = determine_parameters_to_be_inferred(tcx, &mut arena, krate); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index d7190a4bea9..d57d1bcd92d 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -154,6 +154,7 @@ pub fn run_core(search_paths: SearchPaths, cfgs: Vec, externs: Externs, &name, resolve::MakeGlobMap::No, |tcx, _, analysis| { + let _ignore = tcx.dep_graph.in_ignore(); let ty::CrateAnalysis { access_levels, .. } = analysis; // Convert from a NodeId set to a DefId set since we don't always have easy access diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index a23dc3b8871..c281571305b 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -326,6 +326,14 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat "the `#[rustc_error]` attribute \ is just used for rustc unit tests \ and will never be stable")), + ("rustc_if_this_changed", Whitelisted, Gated("rustc_attrs", + "the `#[rustc_if_this_changed]` attribute \ + is just used for rustc unit tests \ + and will never be stable")), + ("rustc_then_this_would_need", Whitelisted, Gated("rustc_attrs", + "the `#[rustc_if_this_changed]` attribute \ + is just used for rustc unit tests \ + and will never be stable")), ("rustc_move_fragments", Normal, Gated("rustc_attrs", "the `#[rustc_move_fragments]` attribute \ is just used for rustc unit tests \ diff --git a/src/test/compile-fail/dep-graph-caller-callee.rs b/src/test/compile-fail/dep-graph-caller-callee.rs new file mode 100644 index 00000000000..acd6091cbdd --- /dev/null +++ b/src/test/compile-fail/dep-graph-caller-callee.rs @@ -0,0 +1,47 @@ +// Copyright 2012-2014 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. + +// Test that immediate callers have to change when callee changes, but +// not callers' callers. + +// compile-flags: -Z incr-comp + +#![feature(rustc_attrs)] +#![allow(dead_code)] + +fn main() { } + +mod x { + #[rustc_if_this_changed] + pub fn x() { } +} + +mod y { + use x; + + // These dependencies SHOULD exist: + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR OK + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR OK + pub fn y() { + x::x(); + } +} + +mod z { + use y; + + // These are expected to yield errors, because changes to `x` + // affect the BODY of `y`, but not its signature. + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR no path + pub fn z() { + y::y(); + } +} diff --git a/src/test/compile-fail/dep-graph-struct-signature.rs b/src/test/compile-fail/dep-graph-struct-signature.rs new file mode 100644 index 00000000000..5cfb748b6f4 --- /dev/null +++ b/src/test/compile-fail/dep-graph-struct-signature.rs @@ -0,0 +1,100 @@ +// Copyright 2012-2014 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. + +// Test cases where a changing struct appears in the signature of fns +// and methods. + +// compile-flags: -Z incr-comp + +#![feature(rustc_attrs)] +#![allow(dead_code)] +#![allow(unused_variables)] + +fn main() { } + +#[rustc_if_this_changed] +struct WillChange { + x: u32, + y: u32 +} + +struct WontChange { + x: u32, + y: u32 +} + +// these are valid dependencies +mod signatures { + use WillChange; + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + trait Bar { + fn do_something(x: WillChange); + } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + fn some_fn(x: WillChange) { } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + fn new_foo(x: u32, y: u32) -> WillChange { + WillChange { x: x, y: y } + } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + impl WillChange { + fn new(x: u32, y: u32) -> WillChange { loop { } } + } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + impl WillChange { + fn method(&self, x: u32) { } + } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + struct WillChanges { + x: WillChange, + y: WillChange + } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + fn indirect(x: WillChanges) { } +} + +// these are invalid dependencies, though sometimes we create edges +// anyway. +mod invalid_signatures { + use WontChange; + + // FIXME due to the variance pass having overly conservative edges, + // we incorrectly think changes are needed here + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + trait A { + fn do_something_else_twice(x: WontChange); + } + + // FIXME due to the variance pass having overly conservative edges, + // we incorrectly think changes are needed here + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK + #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK + fn b(x: WontChange) { } + + #[rustc_then_this_would_need(ItemSignature)] //~ ERROR no path from `WillChange` + #[rustc_then_this_would_need(CollectItem)] //~ ERROR no path from `WillChange` + fn c(x: u32) { } +} + diff --git a/src/test/compile-fail/dep-graph-trait-impl-two-traits-same-method.rs b/src/test/compile-fail/dep-graph-trait-impl-two-traits-same-method.rs new file mode 100644 index 00000000000..57e83586d8d --- /dev/null +++ b/src/test/compile-fail/dep-graph-trait-impl-two-traits-same-method.rs @@ -0,0 +1,54 @@ +// Copyright 2012-2014 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. + +// Test that adding an impl to a trait `Foo` DOES affect functions +// that only use `Bar` if they have methods in common. + +// compile-flags: -Z incr-comp + +#![feature(rustc_attrs)] +#![allow(dead_code)] + +fn main() { } + +pub trait Foo: Sized { + fn method(self) { } +} + +pub trait Bar: Sized { + fn method(self) { } +} + +mod x { + use {Foo, Bar}; + + #[rustc_if_this_changed] + impl Foo for u32 { } + + impl Bar for char { } +} + +mod y { + use {Foo, Bar}; + + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR OK + pub fn with_char() { + char::method('a'); + } +} + +mod z { + use y; + + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + pub fn z() { + y::with_char(); + } +} diff --git a/src/test/compile-fail/dep-graph-trait-impl-two-traits.rs b/src/test/compile-fail/dep-graph-trait-impl-two-traits.rs new file mode 100644 index 00000000000..ba54a056209 --- /dev/null +++ b/src/test/compile-fail/dep-graph-trait-impl-two-traits.rs @@ -0,0 +1,54 @@ +// Copyright 2012-2014 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. + +// Test that adding an impl to a trait `Foo` does not affect functions +// that only use `Bar`, so long as they do not have methods in common. + +// compile-flags: -Z incr-comp + +#![feature(rustc_attrs)] +#![allow(warnings)] + +fn main() { } + +pub trait Foo: Sized { + fn foo(self) { } +} + +pub trait Bar: Sized { + fn bar(self) { } +} + +mod x { + use {Foo, Bar}; + + #[rustc_if_this_changed] + impl Foo for char { } + + impl Bar for char { } +} + +mod y { + use {Foo, Bar}; + + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + pub fn call_bar() { + char::bar('a'); + } +} + +mod z { + use y; + + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + pub fn z() { + y::call_bar(); + } +} diff --git a/src/test/compile-fail/dep-graph-trait-impl.rs b/src/test/compile-fail/dep-graph-trait-impl.rs new file mode 100644 index 00000000000..83e924fe06d --- /dev/null +++ b/src/test/compile-fail/dep-graph-trait-impl.rs @@ -0,0 +1,77 @@ +// Copyright 2012-2014 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. + +// Test that when a trait impl changes, fns whose body uses that trait +// must also be recompiled. + +// compile-flags: -Z incr-comp + +#![feature(rustc_attrs)] +#![allow(warnings)] + +fn main() { } + +pub trait Foo: Sized { + fn method(self) { } +} + +mod x { + use Foo; + + #[rustc_if_this_changed] + impl Foo for char { } + + impl Foo for u32 { } +} + +mod y { + use Foo; + + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR OK + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR OK + pub fn with_char() { + char::method('a'); + } + + // FIXME(#30741) tcx fulfillment cache not tracked + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR no path + pub fn take_foo_with_char() { + take_foo::('a'); + } + + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR OK + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR OK + pub fn with_u32() { + u32::method(22); + } + + // FIXME(#30741) tcx fulfillment cache not tracked + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR no path + pub fn take_foo_with_u32() { + take_foo::(22); + } + + pub fn take_foo(t: T) { } +} + +mod z { + use y; + + // These are expected to yield errors, because changes to `x` + // affect the BODY of `y`, but not its signature. + #[rustc_then_this_would_need(TypeckItemBody)] //~ ERROR no path + #[rustc_then_this_would_need(TransCrateItem)] //~ ERROR no path + pub fn z() { + y::with_char(); + y::with_u32(); + } +} diff --git a/src/test/compile-fail/dep-graph-unrelated.rs b/src/test/compile-fail/dep-graph-unrelated.rs new file mode 100644 index 00000000000..8feec12a2f7 --- /dev/null +++ b/src/test/compile-fail/dep-graph-unrelated.rs @@ -0,0 +1,22 @@ +// Copyright 2012-2014 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. + +// Test that two unrelated functions have no trans dependency. + +// compile-flags: -Z incr-comp + +#![feature(rustc_attrs)] +#![allow(dead_code)] + +#[rustc_if_this_changed] +fn main() { } + +#[rustc_then_this_would_need(TransCrateItem)] //~ ERROR no path from `main` +fn bar() { }