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 + } +}