From c77cd480cf2105afcbb92de4f514f1f9637912c5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 5 Jan 2016 13:02:57 -0500 Subject: [PATCH 01/12] Introduce the DepGraph and DepTracking map abstractions, along with a README explaining how they are to be used --- src/librustc/dep_graph/README.md | 384 ++++++++++++++++++++ src/librustc/dep_graph/dep_tracking_map.rs | 81 +++++ src/librustc/dep_graph/edges.rs | 162 +++++++++ src/librustc/dep_graph/mod.rs | 196 ++++++++++ src/librustc/dep_graph/query.rs | 68 ++++ src/librustc/dep_graph/raii.rs | 47 +++ src/librustc/dep_graph/thread.rs | 137 +++++++ src/librustc/lib.rs | 2 + src/librustc/middle/ty/fast_reject.rs | 2 +- src/librustc_data_structures/graph/mod.rs | 14 +- src/librustc_data_structures/lib.rs | 1 + src/librustc_data_structures/veccell/mod.rs | 37 ++ 12 files changed, 1127 insertions(+), 4 deletions(-) create mode 100644 src/librustc/dep_graph/README.md create mode 100644 src/librustc/dep_graph/dep_tracking_map.rs create mode 100644 src/librustc/dep_graph/edges.rs create mode 100644 src/librustc/dep_graph/mod.rs create mode 100644 src/librustc/dep_graph/query.rs create mode 100644 src/librustc/dep_graph/raii.rs create mode 100644 src/librustc/dep_graph/thread.rs create mode 100644 src/librustc_data_structures/veccell/mod.rs diff --git a/src/librustc/dep_graph/README.md b/src/librustc/dep_graph/README.md new file mode 100644 index 00000000000..c607894c5e3 --- /dev/null +++ b/src/librustc/dep_graph/README.md @@ -0,0 +1,384 @@ +# 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 brarier 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 `DepTrackingMapId`; 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. + +#### 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 comma-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` nod 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..4f1063eae50 --- /dev/null +++ b/src/librustc/dep_graph/dep_tracking_map.rs @@ -0,0 +1,81 @@ +// 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 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 DepTrackingMapId { + 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<'k, M: DepTrackingMapId> 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..07b18944aa1 --- /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 { + ids: 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 { + ids: vec![], + indices: FnvHashMap(), + edges: FnvHashSet(), + open_nodes: Vec::new() + } + } + + fn id(&self, index: IdIndex) -> &DepNode { + &self.ids[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.ids.len()); + self.ids.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.clone()); + 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).clone(), self.id(j).clone())) + .collect(); + DepGraphQuery::new(&self.ids, &edges) + } +} diff --git a/src/librustc/dep_graph/mod.rs b/src/librustc/dep_graph/mod.rs new file mode 100644 index 00000000000..c3d195811f2 --- /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, DepTrackingMapId}; + +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..f2a5b1c9ef9 --- /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 receiver 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/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_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..1842d0a4958 --- /dev/null +++ b/src/librustc_data_structures/veccell/mod.rs @@ -0,0 +1,37 @@ +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 + } +} From aa265869baf55d59d310edf76fef50026d7c70e3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 5 Jan 2016 13:07:45 -0500 Subject: [PATCH 02/12] Add DepGraph to tcx. --- src/librustc/middle/ty/context.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/librustc/middle/ty/context.rs b/src/librustc/middle/ty/context.rs index cee651743ca..e700abf9db1 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, DepNode, DepTrackingMap}; use front::map as ast_map; use session::Session; use lint; @@ -224,6 +225,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>, @@ -483,7 +486,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,6 +494,7 @@ 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, From 005fa14358d78bc2da3c68933fce0aa58159d944 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Dec 2015 16:35:02 -0500 Subject: [PATCH 03/12] Annotate the compiler with information about what it is doing when. --- src/librustc/lint/context.rs | 3 ++ src/librustc/middle/check_const.rs | 4 +-- src/librustc/middle/check_match.rs | 3 +- src/librustc/middle/check_rvalues.rs | 10 +++---- src/librustc/middle/dead.rs | 2 ++ src/librustc/middle/intrinsicck.rs | 3 +- src/librustc/middle/reachable.rs | 2 ++ src/librustc/middle/stability.rs | 5 ++-- src/librustc/middle/traits/mod.rs | 14 ++++++++++ src/librustc/middle/traits/select.rs | 5 +++- src/librustc/middle/ty/mod.rs | 31 ++++++++++++++++++++- src/librustc/session/config.rs | 12 ++++++++ src/librustc_borrowck/borrowck/mod.rs | 3 +- src/librustc_driver/driver.rs | 2 +- src/librustc_driver/pretty.rs | 1 + src/librustc_mir/mir_map.rs | 3 +- src/librustc_privacy/lib.rs | 3 ++ src/librustc_trans/back/link.rs | 6 ++-- src/librustc_trans/save/mod.rs | 2 ++ src/librustc_trans/trans/base.rs | 21 ++++++++++++-- src/librustc_trans/trans/inline.rs | 5 ++-- src/librustc_trans/trans/intrinsic.rs | 2 ++ src/librustc_typeck/check/mod.rs | 12 ++++---- src/librustc_typeck/coherence/mod.rs | 15 ++++++---- src/librustc_typeck/coherence/orphan.rs | 11 ++++---- src/librustc_typeck/collect.rs | 37 +++++++++++++++++-------- src/librustc_typeck/lib.rs | 1 + src/librustc_typeck/variance.rs | 2 ++ src/librustdoc/core.rs | 1 + 29 files changed, 171 insertions(+), 50 deletions(-) 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/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/mod.rs b/src/librustc/middle/ty/mod.rs index 5daa9bcd0d1..f9d18e99297 100644 --- a/src/librustc/middle/ty/mod.rs +++ b/src/librustc/middle/ty/mod.rs @@ -51,6 +51,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}; @@ -1946,7 +1947,14 @@ fn lookup_locally_or_in_crate_store(descr: &str, panic!("No def'n found for {:?} in tcx.{}", def_id, descr); } let v = load_external(); - map.borrow_mut().insert(def_id, v.clone()); + + // Don't consider this a write from the current task, since we are + // loading from another crate. (Note that the current task will + // already have registered a read in the call to `get` above.) + dep_graph.with_ignore(|| { + map.borrow_mut().insert(def_id, v.clone()); + }); + v } @@ -2458,6 +2466,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 } @@ -2480,6 +2492,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 } @@ -2505,6 +2521,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; @@ -2727,6 +2747,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/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_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_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_mir/mir_map.rs b/src/librustc_mir/mir_map.rs index 5c9399ebdad..08174272a9b 100644 --- a/src/librustc_mir/mir_map.rs +++ b/src/librustc_mir/mir_map.rs @@ -23,6 +23,7 @@ extern crate rustc_front; use build; use graphviz; use transform::*; +use rustc::dep_graph::DepNode; use rustc::mir::repr::Mir; use hair::cx::Cx; use std::fs::File; @@ -47,7 +48,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/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/base.rs b/src/librustc_trans/trans/base.rs index 4197f80cb5e..6dffdf74140 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; @@ -2978,9 +2979,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 { @@ -3186,7 +3194,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 29965755eac..89399043c96 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_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/collect.rs b/src/librustc_typeck/collect.rs index eaaa2c77379..6135cf1d32a 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -79,6 +79,7 @@ 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::nodemap::{FnvHashMap, FnvHashSet}; @@ -174,13 +175,11 @@ struct CollectItemTypesVisitor<'a, 'tcx: 'a> { } 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 +702,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); @@ -1455,6 +1458,11 @@ fn type_scheme_of_item<'a,'tcx>(ccx: &CrateCtxt<'a,'tcx>, it: &hir::Item) -> ty::TypeScheme<'tcx> { + // Computing the type scheme of an item is a discrete task: + let item_def_id = ccx.tcx.map.local_def_id(it.id); + let _task = ccx.tcx.dep_graph.in_task(DepNode::TypeScheme(item_def_id)); + ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // we have access to `it` + memoized(&ccx.tcx.tcache, ccx.tcx.map.local_def_id(it.id), |_| compute_type_scheme_of_item(ccx, it)) @@ -1571,13 +1579,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> { + // Computing the type scheme of a foreign item is a discrete task: + let item_def_id = ccx.tcx.map.local_def_id(item.id); + let _task = ccx.tcx.dep_graph.in_task(DepNode::TypeScheme(item_def_id)); + ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // we have access to `item` + memoized(&ccx.tcx.tcache, - ccx.tcx.map.local_def_id(it.id), - |_| compute_type_scheme_of_foreign_item(ccx, it, abi)) + ccx.tcx.map.local_def_id(item.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 From 75c4f395acc22f7dc3fddda7cf7a09a3400870b3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Dec 2015 17:08:45 -0500 Subject: [PATCH 04/12] Strip the trait-def phase from collect, which has no function. --- src/librustc_typeck/collect.rs | 50 +++++----------------------------- 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 6135cf1d32a..d161324f564 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 */ @@ -105,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); } @@ -147,28 +138,6 @@ 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> @@ -1286,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); From 5d9dd7cf33dc252b3b914125a2025435783df096 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Dec 2015 17:17:57 -0500 Subject: [PATCH 05/12] Refactor overlap checker so that it walks the HIR instead of poking into random tables. The old code was weird anyway because it would potentially walk traits from other crates etc. The new code fits seamlessly with the dependency tracking. --- src/librustc_typeck/coherence/overlap.rs | 59 ++++++++++++++---------- 1 file changed, 34 insertions(+), 25 deletions(-) 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 From d48f48f61f996478e972e2dd6f3070eaf3ed45d7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Dec 2015 16:39:33 -0500 Subject: [PATCH 06/12] Refactor compiler to make use of dep-tracking-maps. Also, in cases where we were using interior mutability (RefCells, TyIvar), add some reads/writes. --- src/librustc/dep_graph/dep_tracking_map.rs | 56 +++++ src/librustc/middle/ty/contents.rs | 6 +- src/librustc/middle/ty/context.rs | 112 +++++++--- src/librustc/middle/ty/ivar.rs | 25 ++- src/librustc/middle/ty/maps.rs | 41 ++++ src/librustc/middle/ty/mod.rs | 238 +++------------------ src/librustc/middle/ty/trait_def.rs | 226 +++++++++++++++++++ src/librustc/util/common.rs | 70 +++--- src/librustc_metadata/decoder.rs | 17 +- src/librustc_typeck/collect.rs | 36 ++-- 10 files changed, 511 insertions(+), 316 deletions(-) create mode 100644 src/librustc/middle/ty/maps.rs create mode 100644 src/librustc/middle/ty/trait_def.rs diff --git a/src/librustc/dep_graph/dep_tracking_map.rs b/src/librustc/dep_graph/dep_tracking_map.rs index 4f1063eae50..501c1aff320 100644 --- a/src/librustc/dep_graph/dep_tracking_map.rs +++ b/src/librustc/dep_graph/dep_tracking_map.rs @@ -13,6 +13,7 @@ use std::cell::RefCell; use std::ops::Index; use std::hash::Hash; use std::marker::PhantomData; +use util::common::MemoizationMap; use super::{DepNode, DepGraph}; @@ -70,6 +71,61 @@ impl DepTrackingMap { } } +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 read 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: DepTrackingMapId> Index<&'k M::Key> for DepTrackingMap { type Output = M::Value; 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 e700abf9db1..8f313120c9d 100644 --- a/src/librustc/middle/ty/context.rs +++ b/src/librustc/middle/ty/context.rs @@ -30,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; @@ -248,21 +250,23 @@ 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. This cache is + /// "encapsulated" and thus does not need to be itself tracked. + 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 @@ -270,21 +274,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, @@ -292,13 +315,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. @@ -312,6 +335,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, @@ -347,7 +371,9 @@ pub struct ctxt<'tcx> { pub fulfilled_predicates: RefCell>, /// Caches the representation hints for struct definitions. - pub repr_hint_cache: RefCell>>>, + /// + /// This is encapsulated by the `ReprHints` task and hence is not tracked. + repr_hint_cache: RefCell>>>, /// Maps Expr NodeId's to their constant qualification. pub const_qualif_map: RefCell>, @@ -499,31 +525,31 @@ impl<'tcx> ctxt<'tcx> { 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()), + 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(DefIdMap()), 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()), @@ -1004,4 +1030,38 @@ 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>> { + // since this is cached, pushing a dep-node for the + // computation yields the correct dependencies. + let _task = self.dep_graph.in_task(DepNode::TraitItems(trait_did)); + + 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 + } + } + } + + /// Obtain the representation annotation for a struct definition. + pub fn lookup_repr_hints(&self, did: DefId) -> Rc> { + let _task = self.dep_graph.in_task(DepNode::ReprHints(did)); + 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/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..e1fea5bba1d --- /dev/null +++ b/src/librustc/middle/ty/maps.rs @@ -0,0 +1,41 @@ +// 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, DepTrackingMapId}; +use middle::def_id::DefId; +use middle::ty; +use std::marker::PhantomData; +use std::rc::Rc; + +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> DepTrackingMapId 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 } diff --git a/src/librustc/middle/ty/mod.rs b/src/librustc/middle/ty/mod.rs index f9d18e99297..10682655e63 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; @@ -76,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; @@ -1318,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, @@ -1514,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> } @@ -1804,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); } } @@ -1931,31 +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(); - - // Don't consider this a write from the current task, since we are - // loading from another crate. (Note that the current task will - // already have registered a read in the call to `get` above.) - dep_graph.with_ignore(|| { - 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 { @@ -2231,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) { @@ -2264,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 { @@ -2427,19 +2252,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, 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/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_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_typeck/collect.rs b/src/librustc_typeck/collect.rs index d161324f564..5a9b8991758 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -75,11 +75,11 @@ 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; @@ -1419,17 +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> { - // Computing the type scheme of an item is a discrete task: - let item_def_id = ccx.tcx.map.local_def_id(it.id); - let _task = ccx.tcx.dep_graph.in_task(DepNode::TypeScheme(item_def_id)); - ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // we have access to `it` - - 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>, @@ -1547,14 +1547,14 @@ fn type_scheme_of_foreign_item<'a, 'tcx>( abi: abi::Abi) -> ty::TypeScheme<'tcx> { - // Computing the type scheme of a foreign item is a discrete task: let item_def_id = ccx.tcx.map.local_def_id(item.id); - let _task = ccx.tcx.dep_graph.in_task(DepNode::TypeScheme(item_def_id)); - ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // we have access to `item` - - memoized(&ccx.tcx.tcache, - ccx.tcx.map.local_def_id(item.id), - |_| compute_type_scheme_of_foreign_item(ccx, item, abi)) + 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>( From 8b22ed86518ebc001f84f9c0debe8cf5aaddb9a3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Dec 2015 17:17:27 -0500 Subject: [PATCH 07/12] Add assert-dep-graph testing mechanism and tests --- src/librustc_trans/lib.rs | 1 + src/librustc_trans/trans/assert_dep_graph.rs | 430 ++++++++++++++++++ src/librustc_trans/trans/base.rs | 3 + src/librustc_trans/trans/mod.rs | 1 + src/libsyntax/feature_gate.rs | 8 + .../compile-fail/dep-graph-caller-callee.rs | 44 ++ .../dep-graph-struct-signature.rs | 97 ++++ ...graph-trait-impl-two-traits-same-method.rs | 51 +++ .../dep-graph-trait-impl-two-traits.rs | 52 +++ src/test/compile-fail/dep-graph-trait-impl.rs | 75 +++ src/test/compile-fail/dep-graph-unrelated.rs | 20 + 11 files changed, 782 insertions(+) create mode 100644 src/librustc_trans/trans/assert_dep_graph.rs create mode 100644 src/test/compile-fail/dep-graph-caller-callee.rs create mode 100644 src/test/compile-fail/dep-graph-struct-signature.rs create mode 100644 src/test/compile-fail/dep-graph-trait-impl-two-traits-same-method.rs create mode 100644 src/test/compile-fail/dep-graph-trait-impl-two-traits.rs create mode 100644 src/test/compile-fail/dep-graph-trait-impl.rs create mode 100644 src/test/compile-fail/dep-graph-unrelated.rs 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/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 6dffdf74140..810803762a8 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -51,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}; @@ -3142,6 +3143,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, 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/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..8e46603711f --- /dev/null +++ b/src/test/compile-fail/dep-graph-caller-callee.rs @@ -0,0 +1,44 @@ +// 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. + +#![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..2c4fe5c96b8 --- /dev/null +++ b/src/test/compile-fail/dep-graph-struct-signature.rs @@ -0,0 +1,97 @@ +// 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. + +#![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: WillChange) { } +} + +// 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..1003f92f161 --- /dev/null +++ b/src/test/compile-fail/dep-graph-trait-impl-two-traits-same-method.rs @@ -0,0 +1,51 @@ +// 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. + +#![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..34a9dbfd62a --- /dev/null +++ b/src/test/compile-fail/dep-graph-trait-impl-two-traits.rs @@ -0,0 +1,52 @@ +// 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`. + +#![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..9ad782a0a63 --- /dev/null +++ b/src/test/compile-fail/dep-graph-trait-impl.rs @@ -0,0 +1,75 @@ +// 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. + +#![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'); + } + + // TODO open an issue on this, problem is that we fail to track + // the tcx fulfillment cache + #[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); + } + + // TODO same issue as above + #[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..fa204a5c76c --- /dev/null +++ b/src/test/compile-fail/dep-graph-unrelated.rs @@ -0,0 +1,20 @@ +// 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. + +#![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() { } From 11c671b59ce0161be5835b5195729f0df5e024f5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Dec 2015 17:24:26 -0500 Subject: [PATCH 08/12] Workaround stage0 bug --- src/librustc/middle/expr_use_visitor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From a9d7e366681c0157a557ff0083aa56fae9469fbf Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 6 Jan 2016 09:19:19 -0500 Subject: [PATCH 09/12] Fix numerous typos, renamings, and minor nits raised by mw. --- src/librustc/dep_graph/README.md | 22 ++++++++++++++-------- src/librustc/dep_graph/dep_tracking_map.rs | 16 ++++++++-------- src/librustc/dep_graph/edges.rs | 18 +++++++++--------- src/librustc/dep_graph/mod.rs | 2 +- src/librustc/dep_graph/thread.rs | 2 +- src/librustc/middle/ty/maps.rs | 4 ++-- 6 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/librustc/dep_graph/README.md b/src/librustc/dep_graph/README.md index c607894c5e3..21742d9935d 100644 --- a/src/librustc/dep_graph/README.md +++ b/src/librustc/dep_graph/README.md @@ -123,7 +123,7 @@ 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 brarier will do. In general, the strategy +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 @@ -197,7 +197,7 @@ 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 `DepTrackingMapId`; this trait defines the key and value +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 @@ -221,7 +221,13 @@ of the map will be a `DefId` and value will be `DepNode::ItemSignature(K)` for a given key. Once that is done, you can just use the `DepTrackingMap` like any -other map. +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 @@ -305,7 +311,7 @@ distinguish which fns used which fn sigs. 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]` +`#[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`. @@ -354,7 +360,7 @@ source_filter // nodes originating from source_filter source_filter -> target_filter // nodes in between source_filter and target_filter ``` -`source_filter` and `target_filter` are a comma-separated list of strings. +`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: @@ -363,10 +369,10 @@ RUST_DEP_GRAPH_FILTER='-> TypeckItemBody' ``` would select the predecessors of all `TypeckItemBody` nodes. Usually though you -want the `TypeckItemBody` nod for some particular fn, so you might write: +want the `TypeckItemBody` node for some particular fn, so you might write: ``` -RUST_DEP_GRAPH_FILTER='-> TypeckItemBody,bar' +RUST_DEP_GRAPH_FILTER='-> TypeckItemBody & bar' ``` This will select only the `TypeckItemBody` nodes for fns with `bar` in their name. @@ -375,7 +381,7 @@ Perhaps you are finding that when you change `foo` you need to re-type-check `ba but you don't think you should have to. In that case, you might do: ``` -RUST_DEP_GRAPH_FILTER='Hir,foo -> TypeckItemBody,bar' +RUST_DEP_GRAPH_FILTER='Hir&foo -> TypeckItemBody & bar' ``` This will dump out all the nodes that lead from `Hir(foo)` to diff --git a/src/librustc/dep_graph/dep_tracking_map.rs b/src/librustc/dep_graph/dep_tracking_map.rs index 501c1aff320..c49e64f0f54 100644 --- a/src/librustc/dep_graph/dep_tracking_map.rs +++ b/src/librustc/dep_graph/dep_tracking_map.rs @@ -20,19 +20,19 @@ 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 { +pub struct DepTrackingMap { phantom: PhantomData, graph: DepGraph, map: FnvHashMap, } -pub trait DepTrackingMapId { +pub trait DepTrackingMapConfig { type Key: Eq + Hash + Clone; type Value: Clone; fn to_dep_node(key: &Self::Key) -> DepNode; } -impl DepTrackingMap { +impl DepTrackingMap { pub fn new(graph: DepGraph) -> DepTrackingMap { DepTrackingMap { phantom: PhantomData, @@ -71,7 +71,7 @@ impl DepTrackingMap { } } -impl MemoizationMap for RefCell> { +impl MemoizationMap for RefCell> { type Key = M::Key; type Value = M::Value; @@ -89,9 +89,9 @@ impl MemoizationMap for RefCell> { /// **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 read that state. As an - /// example, see `type_scheme_of_item` in `collect`, which looks - /// something like this: + /// 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> { @@ -126,7 +126,7 @@ impl MemoizationMap for RefCell> { } } -impl<'k, M: DepTrackingMapId> Index<&'k M::Key> for DepTrackingMap { +impl<'k, M: DepTrackingMapConfig> Index<&'k M::Key> for DepTrackingMap { type Output = M::Value; #[inline] diff --git a/src/librustc/dep_graph/edges.rs b/src/librustc/dep_graph/edges.rs index 07b18944aa1..4b25285c476 100644 --- a/src/librustc/dep_graph/edges.rs +++ b/src/librustc/dep_graph/edges.rs @@ -12,7 +12,7 @@ use rustc_data_structures::fnv::{FnvHashMap, FnvHashSet}; use super::{DepGraphQuery, DepNode}; pub struct DepGraphEdges { - ids: Vec, + nodes: Vec, indices: FnvHashMap, edges: FnvHashSet<(IdIndex, IdIndex)>, open_nodes: Vec, @@ -43,15 +43,15 @@ enum OpenNode { impl DepGraphEdges { pub fn new() -> DepGraphEdges { DepGraphEdges { - ids: vec![], + nodes: vec![], indices: FnvHashMap(), edges: FnvHashSet(), open_nodes: Vec::new() } } - fn id(&self, index: IdIndex) -> &DepNode { - &self.ids[index.index()] + fn id(&self, index: IdIndex) -> DepNode { + self.nodes[index.index()] } /// Creates a node for `id` in the graph. @@ -60,8 +60,8 @@ impl DepGraphEdges { return i; } - let index = IdIndex::new(self.ids.len()); - self.ids.push(id.clone()); + let index = IdIndex::new(self.nodes.len()); + self.nodes.push(id.clone()); self.indices.insert(id, index); index } @@ -83,7 +83,7 @@ impl DepGraphEdges { pub fn push_task(&mut self, key: DepNode) { let top_node = self.current_node(); - let new_node = self.make_node(key.clone()); + 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 @@ -155,8 +155,8 @@ impl DepGraphEdges { pub fn query(&self) -> DepGraphQuery { let edges: Vec<_> = self.edges.iter() - .map(|&(i, j)| (self.id(i).clone(), self.id(j).clone())) + .map(|&(i, j)| (self.id(i), self.id(j))) .collect(); - DepGraphQuery::new(&self.ids, &edges) + DepGraphQuery::new(&self.nodes, &edges) } } diff --git a/src/librustc/dep_graph/mod.rs b/src/librustc/dep_graph/mod.rs index c3d195811f2..9bf0a79115e 100644 --- a/src/librustc/dep_graph/mod.rs +++ b/src/librustc/dep_graph/mod.rs @@ -152,7 +152,7 @@ impl DepGraph { } } -pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapId}; +pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapConfig}; pub use self::query::DepGraphQuery; diff --git a/src/librustc/dep_graph/thread.rs b/src/librustc/dep_graph/thread.rs index f2a5b1c9ef9..dbc57605d71 100644 --- a/src/librustc/dep_graph/thread.rs +++ b/src/librustc/dep_graph/thread.rs @@ -48,7 +48,7 @@ pub struct DepGraphThreadData { // where to send buffer when full swap_out: Sender>, - // where to receiver query results + // where to receive query results query_in: Receiver, } diff --git a/src/librustc/middle/ty/maps.rs b/src/librustc/middle/ty/maps.rs index e1fea5bba1d..924d420613c 100644 --- a/src/librustc/middle/ty/maps.rs +++ b/src/librustc/middle/ty/maps.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use dep_graph::{DepNode, DepTrackingMapId}; +use dep_graph::{DepNode, DepTrackingMapConfig}; use middle::def_id::DefId; use middle::ty; use std::marker::PhantomData; @@ -20,7 +20,7 @@ macro_rules! dep_map_ty { data: PhantomData<&'tcx ()> } - impl<'tcx> DepTrackingMapId for $ty_name<'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) } From 0c8ee65020a9d6ef60506741d9db1582dc8f6834 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 6 Jan 2016 10:16:58 -0500 Subject: [PATCH 10/12] Use `memoized` helper more often. --- src/librustc/middle/ty/context.rs | 39 ++++++++++--------------------- src/librustc/middle/ty/maps.rs | 3 +++ 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/librustc/middle/ty/context.rs b/src/librustc/middle/ty/context.rs index 8f313120c9d..d1504d25288 100644 --- a/src/librustc/middle/ty/context.rs +++ b/src/librustc/middle/ty/context.rs @@ -13,7 +13,7 @@ // FIXME: (@jroesch) @eddyb should remove this when he renames ctxt #![allow(non_camel_case_types)] -use dep_graph::{DepGraph, DepNode, DepTrackingMap}; +use dep_graph::{DepGraph, DepTrackingMap}; use front::map as ast_map; use session::Session; use lint; @@ -256,9 +256,8 @@ pub struct ctxt<'tcx> { pub trait_item_def_ids: RefCell>>, /// A cache for the trait_items() routine; note that the routine - /// itself pushes the `TraitItems` dependency node. This cache is - /// "encapsulated" and thus does not need to be itself tracked. - trait_items_cache: RefCell>>>>, + /// itself pushes the `TraitItems` dependency node. + trait_items_cache: RefCell>>, pub impl_trait_refs: RefCell>>, pub trait_defs: RefCell>>, @@ -371,9 +370,7 @@ pub struct ctxt<'tcx> { pub fulfilled_predicates: RefCell>, /// Caches the representation hints for struct definitions. - /// - /// This is encapsulated by the `ReprHints` task and hence is not tracked. - repr_hint_cache: RefCell>>>, + repr_hint_cache: RefCell>>, /// Maps Expr NodeId's to their constant qualification. pub const_qualif_map: RefCell>, @@ -544,7 +541,7 @@ impl<'tcx> ctxt<'tcx> { ast_ty_to_ty_cache: RefCell::new(NodeMap()), 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(DefIdMap()), + 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, @@ -561,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()), @@ -1032,28 +1029,16 @@ impl<'tcx> ctxt<'tcx> { } pub fn trait_items(&self, trait_did: DefId) -> Rc>> { - // since this is cached, pushing a dep-node for the - // computation yields the correct dependencies. - let _task = self.dep_graph.in_task(DepNode::TraitItems(trait_did)); - - 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 - } - } + 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> { - let _task = self.dep_graph.in_task(DepNode::ReprHints(did)); self.repr_hint_cache.memoize(did, || { Rc::new(if did.is_local() { self.get_attrs(did).iter().flat_map(|meta| { diff --git a/src/librustc/middle/ty/maps.rs b/src/librustc/middle/ty/maps.rs index 924d420613c..7d5276f379f 100644 --- a/src/librustc/middle/ty/maps.rs +++ b/src/librustc/middle/ty/maps.rs @@ -13,6 +13,7 @@ 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) => { @@ -39,3 +40,5 @@ 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> } From 876de6e4956e45d4a335b41af953b6cc05c8a36b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 6 Jan 2016 11:28:53 -0500 Subject: [PATCH 11/12] Fix tidy errors --- src/librustc_data_structures/veccell/mod.rs | 10 ++++++++++ src/test/compile-fail/dep-graph-trait-impl.rs | 5 ++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/librustc_data_structures/veccell/mod.rs b/src/librustc_data_structures/veccell/mod.rs index 1842d0a4958..008642d9d65 100644 --- a/src/librustc_data_structures/veccell/mod.rs +++ b/src/librustc_data_structures/veccell/mod.rs @@ -1,3 +1,13 @@ +// 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; diff --git a/src/test/compile-fail/dep-graph-trait-impl.rs b/src/test/compile-fail/dep-graph-trait-impl.rs index 9ad782a0a63..da0c77c26d1 100644 --- a/src/test/compile-fail/dep-graph-trait-impl.rs +++ b/src/test/compile-fail/dep-graph-trait-impl.rs @@ -37,8 +37,7 @@ mod y { char::method('a'); } - // TODO open an issue on this, problem is that we fail to track - // the tcx fulfillment cache + // 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() { @@ -51,7 +50,7 @@ mod y { u32::method(22); } - // TODO same issue as above + // 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() { From 93996b160ce319f864fbab6c9243048c4d65704c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 6 Jan 2016 11:29:00 -0500 Subject: [PATCH 12/12] Fix dependency graph test cases to have correct commments and use -Z incr-comp --- src/test/compile-fail/dep-graph-caller-callee.rs | 5 ++++- src/test/compile-fail/dep-graph-struct-signature.rs | 7 +++++-- .../dep-graph-trait-impl-two-traits-same-method.rs | 5 ++++- src/test/compile-fail/dep-graph-trait-impl-two-traits.rs | 4 +++- src/test/compile-fail/dep-graph-trait-impl.rs | 5 ++++- src/test/compile-fail/dep-graph-unrelated.rs | 2 ++ 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/test/compile-fail/dep-graph-caller-callee.rs b/src/test/compile-fail/dep-graph-caller-callee.rs index 8e46603711f..acd6091cbdd 100644 --- a/src/test/compile-fail/dep-graph-caller-callee.rs +++ b/src/test/compile-fail/dep-graph-caller-callee.rs @@ -8,7 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// Test that two unrelated functions have no trans dependency. +// 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)] diff --git a/src/test/compile-fail/dep-graph-struct-signature.rs b/src/test/compile-fail/dep-graph-struct-signature.rs index 2c4fe5c96b8..5cfb748b6f4 100644 --- a/src/test/compile-fail/dep-graph-struct-signature.rs +++ b/src/test/compile-fail/dep-graph-struct-signature.rs @@ -8,7 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// Test that two unrelated functions have no trans dependency. +// 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)] @@ -68,7 +71,7 @@ mod signatures { #[rustc_then_this_would_need(ItemSignature)] //~ ERROR OK #[rustc_then_this_would_need(CollectItem)] //~ ERROR OK - fn indirect(x: WillChange) { } + fn indirect(x: WillChanges) { } } // these are invalid dependencies, though sometimes we create edges 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 index 1003f92f161..57e83586d8d 100644 --- 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 @@ -8,7 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// Test that two unrelated functions have no trans dependency. +// 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)] 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 index 34a9dbfd62a..ba54a056209 100644 --- a/src/test/compile-fail/dep-graph-trait-impl-two-traits.rs +++ b/src/test/compile-fail/dep-graph-trait-impl-two-traits.rs @@ -9,7 +9,9 @@ // except according to those terms. // Test that adding an impl to a trait `Foo` does not affect functions -// that only use `Bar`. +// that only use `Bar`, so long as they do not have methods in common. + +// compile-flags: -Z incr-comp #![feature(rustc_attrs)] #![allow(warnings)] diff --git a/src/test/compile-fail/dep-graph-trait-impl.rs b/src/test/compile-fail/dep-graph-trait-impl.rs index da0c77c26d1..83e924fe06d 100644 --- a/src/test/compile-fail/dep-graph-trait-impl.rs +++ b/src/test/compile-fail/dep-graph-trait-impl.rs @@ -8,7 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// Test that two unrelated functions have no trans dependency. +// 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)] diff --git a/src/test/compile-fail/dep-graph-unrelated.rs b/src/test/compile-fail/dep-graph-unrelated.rs index fa204a5c76c..8feec12a2f7 100644 --- a/src/test/compile-fail/dep-graph-unrelated.rs +++ b/src/test/compile-fail/dep-graph-unrelated.rs @@ -10,6 +10,8 @@ // Test that two unrelated functions have no trans dependency. +// compile-flags: -Z incr-comp + #![feature(rustc_attrs)] #![allow(dead_code)]