From 2b47c407513891752996fafe2bb591cd94d9ace6 Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Wed, 14 Jun 2017 15:22:33 +0200 Subject: [PATCH 1/3] dep-graph: Remove unused DepMessage::Write --- src/librustc/dep_graph/shadow.rs | 56 -------------------------------- src/librustc/dep_graph/thread.rs | 2 -- 2 files changed, 58 deletions(-) diff --git a/src/librustc/dep_graph/shadow.rs b/src/librustc/dep_graph/shadow.rs index 8808ea5948da8..5afe19722440a 100644 --- a/src/librustc/dep_graph/shadow.rs +++ b/src/librustc/dep_graph/shadow.rs @@ -27,39 +27,21 @@ //! created. See `./README.md` for details. use std::cell::RefCell; -use std::env; use super::DepNode; use super::thread::DepMessage; -use super::debug::EdgeFilter; pub struct ShadowGraph { // if you push None onto the stack, that corresponds to an Ignore stack: RefCell>>, - forbidden_edge: Option, } const ENABLED: bool = cfg!(debug_assertions); impl ShadowGraph { pub fn new() -> Self { - let forbidden_edge = if !ENABLED { - None - } else { - match env::var("RUST_FORBID_DEP_GRAPH_EDGE") { - Ok(s) => { - match EdgeFilter::new(&s) { - Ok(f) => Some(f), - Err(err) => bug!("RUST_FORBID_DEP_GRAPH_EDGE invalid: {}", err), - } - } - Err(_) => None, - } - }; - ShadowGraph { stack: RefCell::new(vec![]), - forbidden_edge: forbidden_edge, } } @@ -86,7 +68,6 @@ impl ShadowGraph { // anyway). What would be bad is WRITING to that // state. DepMessage::Read(_) => { } - DepMessage::Write(ref n) => self.check_edge(top(&stack), Some(Some(n))), DepMessage::PushTask(ref n) => stack.push(Some(n.clone())), DepMessage::PushIgnore => stack.push(None), DepMessage::PopTask(ref n) => { @@ -111,41 +92,4 @@ impl ShadowGraph { } } } - - fn check_edge(&self, - source: Option>, - target: Option>) { - assert!(ENABLED); - match (source, target) { - // cannot happen, one side is always Some(Some(_)) - (None, None) => unreachable!(), - - // nothing on top of the stack - (None, Some(n)) | (Some(n), None) => bug!("write of {:?} but no current task", n), - - // this corresponds to an Ignore being top of the stack - (Some(None), _) | (_, Some(None)) => (), - - // a task is on top of the stack - (Some(Some(source)), Some(Some(target))) => { - if let Some(ref forbidden_edge) = self.forbidden_edge { - if forbidden_edge.test(source, target) { - bug!("forbidden edge {:?} -> {:?} created", source, target) - } - } - } - } - } -} - -// Do a little juggling: we get back a reference to an option at the -// top of the stack, convert it to an optional reference. -fn top<'s>(stack: &'s Vec>) -> Option> { - stack.last() - .map(|n: &'s Option| -> Option<&'s DepNode> { - // (*) - // (*) type annotation just there to clarify what would - // otherwise be some *really* obscure code - n.as_ref() - }) } diff --git a/src/librustc/dep_graph/thread.rs b/src/librustc/dep_graph/thread.rs index ad0abfe26f45f..1ec6b1a7f75fc 100644 --- a/src/librustc/dep_graph/thread.rs +++ b/src/librustc/dep_graph/thread.rs @@ -30,7 +30,6 @@ use super::shadow::ShadowGraph; #[derive(Debug)] pub enum DepMessage { Read(DepNode), - Write(DepNode), PushTask(DepNode), PopTask(DepNode), PushIgnore, @@ -162,7 +161,6 @@ pub fn main(swap_in: Receiver>, 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(), From 73f71a419356810701e7e0874fcad49d6d4afc39 Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Thu, 15 Jun 2017 18:28:30 +0200 Subject: [PATCH 2/3] incr.comp.: Don't manage dep-graph in separate thread. --- src/librustc/dep_graph/edges.rs | 159 ++++++++++---------- src/librustc/dep_graph/graph.rs | 81 +++++++---- src/librustc/dep_graph/mod.rs | 2 - src/librustc/dep_graph/raii.rs | 40 +++--- src/librustc/dep_graph/shadow.rs | 95 ------------ src/librustc/dep_graph/thread.rs | 176 ----------------------- src/librustc_incremental/persist/load.rs | 9 +- 7 files changed, 145 insertions(+), 417 deletions(-) delete mode 100644 src/librustc/dep_graph/shadow.rs delete mode 100644 src/librustc/dep_graph/thread.rs diff --git a/src/librustc/dep_graph/edges.rs b/src/librustc/dep_graph/edges.rs index a323e44d0d427..91ff6bb2ae031 100644 --- a/src/librustc/dep_graph/edges.rs +++ b/src/librustc/dep_graph/edges.rs @@ -15,7 +15,7 @@ pub struct DepGraphEdges { nodes: Vec, indices: FxHashMap, edges: FxHashSet<(IdIndex, IdIndex)>, - open_nodes: Vec, + task_stack: Vec, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -35,8 +35,12 @@ impl IdIndex { } #[derive(Clone, Debug, PartialEq)] -enum OpenNode { - Node(IdIndex), +enum OpenTask { + Regular { + node: DepNode, + reads: Vec, + read_set: FxHashSet, + }, Ignore, } @@ -46,56 +50,50 @@ impl DepGraphEdges { nodes: vec![], indices: FxHashMap(), edges: FxHashSet(), - open_nodes: Vec::new() + task_stack: Vec::new() } } fn id(&self, index: IdIndex) -> DepNode { - self.nodes[index.index()].clone() - } - - /// Creates a node for `id` in the graph. - fn make_node(&mut self, id: DepNode) -> IdIndex { - if let Some(&i) = self.indices.get(&id) { - return i; - } - - let index = IdIndex::new(self.nodes.len()); - self.nodes.push(id.clone()); - self.indices.insert(id, index); - index - } - - /// Top of the stack of open nodes. - fn current_node(&self) -> Option { - self.open_nodes.last().cloned() + self.nodes[index.index()] } pub fn push_ignore(&mut self) { - self.open_nodes.push(OpenNode::Ignore); + self.task_stack.push(OpenTask::Ignore); } pub fn pop_ignore(&mut self) { - let popped_node = self.open_nodes.pop().unwrap(); - assert_eq!(popped_node, OpenNode::Ignore); + let popped_node = self.task_stack.pop().unwrap(); + debug_assert_eq!(popped_node, OpenTask::Ignore); } pub fn push_task(&mut self, key: DepNode) { - let top_node = self.current_node(); - - let new_node = self.make_node(key); - self.open_nodes.push(OpenNode::Node(new_node)); - - // if we are in the midst of doing task T, then this new task - // N is a subtask of T, so add an edge N -> T. - if let Some(top_node) = top_node { - self.add_edge_from_open_node(top_node, |t| (new_node, t)); - } + self.task_stack.push(OpenTask::Regular { + node: key, + reads: Vec::new(), + read_set: FxHashSet(), + }); } 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); + let popped_node = self.task_stack.pop().unwrap(); + + if let OpenTask::Regular { + node, + read_set: _, + reads + } = popped_node { + debug_assert_eq!(node, key); + + let target_id = self.get_or_create_node(node); + + for read in reads.into_iter() { + let source_id = self.get_or_create_node(read); + self.edges.insert((source_id, target_id)); + } + } else { + bug!("pop_task() - Expected regular task to be popped") + } } /// Indicates that the current task `C` reads `v` by adding an @@ -104,63 +102,52 @@ impl DepGraphEdges { /// you are not in a task; what is bad is *writing* to tracked /// state (and leaking data that you read into a tracked task). pub fn read(&mut self, v: DepNode) { - if self.current_node().is_some() { - let source = self.make_node(v); - self.add_edge_from_current_node(|current| (source, current)) + match self.task_stack.last_mut() { + Some(&mut OpenTask::Regular { + node: _, + ref mut reads, + ref mut read_set, + }) => { + if read_set.insert(v) { + reads.push(v); + } + } + _ => { + // ignore + } } } - /// 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)) + pub fn query(&self) -> DepGraphQuery { + let edges: Vec<_> = self.edges.iter() + .map(|&(i, j)| (self.id(i), self.id(j))) + .collect(); + DepGraphQuery::new(&self.nodes, &edges) } - /// 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 => bug!("no current node, cannot add edge into dependency graph") - } + #[inline] + fn get_or_create_node(&mut self, dep_node: DepNode) -> IdIndex { + let DepGraphEdges { + ref mut indices, + ref mut nodes, + .. + } = *self; + + *indices.entry(dep_node).or_insert_with(|| { + let next_id = nodes.len(); + nodes.push(dep_node); + IdIndex::new(next_id) + }) } - /// 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)); - } + #[inline] + pub fn add_edge(&mut self, source: DepNode, target: DepNode) { + let source = self.get_or_create_node(source); + let target = self.get_or_create_node(target); + self.edges.insert((source, target)); } - pub fn query(&self) -> DepGraphQuery { - let edges: Vec<_> = self.edges.iter() - .map(|&(i, j)| (self.id(i), self.id(j))) - .collect(); - DepGraphQuery::new(&self.nodes, &edges) + pub fn add_node(&mut self, node: DepNode) { + self.get_or_create_node(node); } } diff --git a/src/librustc/dep_graph/graph.rs b/src/librustc/dep_graph/graph.rs index e48e61d803510..d4f1b7892a490 100644 --- a/src/librustc/dep_graph/graph.rs +++ b/src/librustc/dep_graph/graph.rs @@ -17,17 +17,16 @@ use super::dep_node::{DepNode, WorkProductId}; use super::query::DepGraphQuery; use super::raii; use super::safe::DepGraphSafe; -use super::thread::{DepGraphThreadData, DepMessage}; +use super::edges::DepGraphEdges; #[derive(Clone)] pub struct DepGraph { - data: Rc + data: Option> } struct DepGraphData { - /// We send messages to the thread to let it build up the dep-graph - /// from the current run. - thread: DepGraphThreadData, + /// The actual graph data. + edges: RefCell, /// When we load, there may be `.o` files, cached mir, or other such /// things available to us. If we find that they are not dirty, we @@ -44,31 +43,35 @@ struct DepGraphData { impl DepGraph { pub fn new(enabled: bool) -> DepGraph { DepGraph { - data: Rc::new(DepGraphData { - thread: DepGraphThreadData::new(enabled), - previous_work_products: RefCell::new(FxHashMap()), - work_products: RefCell::new(FxHashMap()), - dep_node_debug: RefCell::new(FxHashMap()), - }) + data: if enabled { + Some(Rc::new(DepGraphData { + previous_work_products: RefCell::new(FxHashMap()), + work_products: RefCell::new(FxHashMap()), + edges: RefCell::new(DepGraphEdges::new()), + dep_node_debug: RefCell::new(FxHashMap()), + })) + } else { + None + } } } /// True if we are actually building the full dep-graph. #[inline] pub fn is_fully_enabled(&self) -> bool { - self.data.thread.is_fully_enabled() + self.data.is_some() } pub fn query(&self) -> DepGraphQuery { - self.data.thread.query() + self.data.as_ref().unwrap().edges.borrow().query() } pub fn in_ignore<'graph>(&'graph self) -> Option> { - raii::IgnoreTask::new(&self.data.thread) + self.data.as_ref().map(|data| raii::IgnoreTask::new(&data.edges)) } pub fn in_task<'graph>(&'graph self, key: DepNode) -> Option> { - raii::DepTask::new(&self.data.thread, key) + self.data.as_ref().map(|data| raii::DepTask::new(&data.edges, key)) } pub fn with_ignore(&self, op: OP) -> R @@ -112,19 +115,35 @@ impl DepGraph { task(cx, arg) } + #[inline] pub fn read(&self, v: DepNode) { - if self.data.thread.is_enqueue_enabled() { - self.data.thread.enqueue(DepMessage::Read(v)); + if let Some(ref data) = self.data { + data.edges.borrow_mut().read(v); } } + /// Only to be used during graph loading + #[inline] + pub fn add_edge_directly(&self, source: DepNode, target: DepNode) { + self.data.as_ref().unwrap().edges.borrow_mut().add_edge(source, target); + } + + /// Only to be used during graph loading + pub fn add_node_directly(&self, node: DepNode) { + self.data.as_ref().unwrap().edges.borrow_mut().add_node(node); + } + /// Indicates that a previous work product exists for `v`. This is /// invoked during initial start-up based on what nodes are clean /// (and what files exist in the incr. directory). pub fn insert_previous_work_product(&self, v: &WorkProductId, data: WorkProduct) { debug!("insert_previous_work_product({:?}, {:?})", v, data); - self.data.previous_work_products.borrow_mut() - .insert(v.clone(), data); + self.data + .as_ref() + .unwrap() + .previous_work_products + .borrow_mut() + .insert(v.clone(), data); } /// Indicates that we created the given work-product in this run @@ -132,28 +151,34 @@ impl DepGraph { /// run. pub fn insert_work_product(&self, v: &WorkProductId, data: WorkProduct) { debug!("insert_work_product({:?}, {:?})", v, data); - self.data.work_products.borrow_mut() - .insert(v.clone(), data); + self.data + .as_ref() + .unwrap() + .work_products + .borrow_mut() + .insert(v.clone(), data); } /// Check whether a previous work product exists for `v` and, if /// so, return the path that leads to it. Used to skip doing work. pub fn previous_work_product(&self, v: &WorkProductId) -> Option { - self.data.previous_work_products.borrow() - .get(v) - .cloned() + self.data + .as_ref() + .and_then(|data| { + data.previous_work_products.borrow().get(v).cloned() + }) } /// Access the map of work-products created during this run. Only /// used during saving of the dep-graph. pub fn work_products(&self) -> Ref> { - self.data.work_products.borrow() + self.data.as_ref().unwrap().work_products.borrow() } /// Access the map of work-products created during the cached run. Only /// used during saving of the dep-graph. pub fn previous_work_products(&self) -> Ref> { - self.data.previous_work_products.borrow() + self.data.as_ref().unwrap().previous_work_products.borrow() } #[inline(always)] @@ -162,14 +187,14 @@ impl DepGraph { debug_str_gen: F) where F: FnOnce() -> String { - let mut dep_node_debug = self.data.dep_node_debug.borrow_mut(); + let mut dep_node_debug = self.data.as_ref().unwrap().dep_node_debug.borrow_mut(); dep_node_debug.entry(dep_node) .or_insert_with(debug_str_gen); } pub(super) fn dep_node_debug_str(&self, dep_node: DepNode) -> Option { - self.data.dep_node_debug.borrow().get(&dep_node).cloned() + self.data.as_ref().unwrap().dep_node_debug.borrow().get(&dep_node).cloned() } } diff --git a/src/librustc/dep_graph/mod.rs b/src/librustc/dep_graph/mod.rs index 92b05f6a6558e..df8c3ba4ac847 100644 --- a/src/librustc/dep_graph/mod.rs +++ b/src/librustc/dep_graph/mod.rs @@ -16,8 +16,6 @@ mod graph; mod query; mod raii; mod safe; -mod shadow; -mod thread; pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapConfig}; pub use self::dep_node::DepNode; diff --git a/src/librustc/dep_graph/raii.rs b/src/librustc/dep_graph/raii.rs index b45f5de802714..426064e4d8495 100644 --- a/src/librustc/dep_graph/raii.rs +++ b/src/librustc/dep_graph/raii.rs @@ -9,53 +9,47 @@ // except according to those terms. use super::DepNode; -use super::thread::{DepGraphThreadData, DepMessage}; +use super::edges::DepGraphEdges; + +use std::cell::RefCell; pub struct DepTask<'graph> { - data: &'graph DepGraphThreadData, - key: Option, + graph: &'graph RefCell, + key: DepNode, } impl<'graph> DepTask<'graph> { - pub fn new(data: &'graph DepGraphThreadData, key: DepNode) - -> Option> { - if data.is_enqueue_enabled() { - data.enqueue(DepMessage::PushTask(key.clone())); - Some(DepTask { data: data, key: Some(key) }) - } else { - None + pub fn new(graph: &'graph RefCell, key: DepNode) -> DepTask<'graph> { + graph.borrow_mut().push_task(key); + DepTask { + graph, + key } } } impl<'graph> Drop for DepTask<'graph> { fn drop(&mut self) { - if self.data.is_enqueue_enabled() { - self.data.enqueue(DepMessage::PopTask(self.key.take().unwrap())); - } + self.graph.borrow_mut().pop_task(self.key); } } pub struct IgnoreTask<'graph> { - data: &'graph DepGraphThreadData + graph: &'graph RefCell } impl<'graph> IgnoreTask<'graph> { - pub fn new(data: &'graph DepGraphThreadData) -> Option> { - if data.is_enqueue_enabled() { - data.enqueue(DepMessage::PushIgnore); - Some(IgnoreTask { data: data }) - } else { - None + pub fn new(graph: &'graph RefCell) -> IgnoreTask<'graph> { + graph.borrow_mut().push_ignore(); + IgnoreTask { + graph } } } impl<'graph> Drop for IgnoreTask<'graph> { fn drop(&mut self) { - if self.data.is_enqueue_enabled() { - self.data.enqueue(DepMessage::PopIgnore); - } + self.graph.borrow_mut().pop_ignore(); } } diff --git a/src/librustc/dep_graph/shadow.rs b/src/librustc/dep_graph/shadow.rs deleted file mode 100644 index 5afe19722440a..0000000000000 --- a/src/librustc/dep_graph/shadow.rs +++ /dev/null @@ -1,95 +0,0 @@ -// 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. - -//! The "Shadow Graph" is maintained on the main thread and which -//! tracks each message relating to the dep-graph and applies some -//! sanity checks as they go by. If an error results, it means you get -//! a nice stack-trace telling you precisely what caused the error. -//! -//! NOTE: This is a debugging facility which can potentially have non-trivial -//! runtime impact. Therefore, it is largely compiled out if -//! debug-assertions are not enabled. -//! -//! The basic sanity check, enabled if you have debug assertions -//! enabled, is that there is always a task (or ignore) on the stack -//! when you do read/write, and that the tasks are pushed/popped -//! according to a proper stack discipline. -//! -//! Optionally, if you specify RUST_FORBID_DEP_GRAPH_EDGE, you can -//! specify an edge filter to be applied to each edge as it is -//! created. See `./README.md` for details. - -use std::cell::RefCell; - -use super::DepNode; -use super::thread::DepMessage; - -pub struct ShadowGraph { - // if you push None onto the stack, that corresponds to an Ignore - stack: RefCell>>, -} - -const ENABLED: bool = cfg!(debug_assertions); - -impl ShadowGraph { - pub fn new() -> Self { - ShadowGraph { - stack: RefCell::new(vec![]), - } - } - - #[inline] - pub fn enabled(&self) -> bool { - ENABLED - } - - pub fn enqueue(&self, message: &DepMessage) { - if ENABLED { - if self.stack.try_borrow().is_err() { - // When we apply edge filters, that invokes the Debug trait on - // DefIds, which in turn reads from various bits of state and - // creates reads! Ignore those recursive reads. - return; - } - - let mut stack = self.stack.borrow_mut(); - match *message { - // It is ok to READ shared state outside of a - // task. That can't do any harm (at least, the only - // way it can do harm is by leaking that data into a - // query or task, which would be a problem - // anyway). What would be bad is WRITING to that - // state. - DepMessage::Read(_) => { } - DepMessage::PushTask(ref n) => stack.push(Some(n.clone())), - DepMessage::PushIgnore => stack.push(None), - DepMessage::PopTask(ref n) => { - match stack.pop() { - Some(Some(m)) => { - if *n != m { - bug!("stack mismatch: found {:?} expected {:?}", m, n) - } - } - Some(None) => bug!("stack mismatch: found Ignore expected {:?}", n), - None => bug!("stack mismatch: found empty stack, expected {:?}", n), - } - } - DepMessage::PopIgnore => { - match stack.pop() { - Some(Some(m)) => bug!("stack mismatch: found {:?} expected ignore", m), - Some(None) => (), - None => bug!("stack mismatch: found empty stack, expected ignore"), - } - } - DepMessage::Query => (), - } - } - } -} diff --git a/src/librustc/dep_graph/thread.rs b/src/librustc/dep_graph/thread.rs deleted file mode 100644 index 1ec6b1a7f75fc..0000000000000 --- a/src/librustc/dep_graph/thread.rs +++ /dev/null @@ -1,176 +0,0 @@ -// 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; -use super::shadow::ShadowGraph; - -#[derive(Debug)] -pub enum DepMessage { - Read(DepNode), - PushTask(DepNode), - PopTask(DepNode), - PushIgnore, - PopIgnore, - Query, -} - -pub struct DepGraphThreadData { - enabled: bool, - - // The "shadow graph" is a debugging aid. We give it each message - // in real time as it arrives and it checks for various errors - // (for example, a read/write when there is no current task; it - // can also apply user-defined filters; see `shadow` module for - // details). This only occurs if debug-assertions are enabled. - // - // Note that in some cases the same errors will occur when the - // data is processed off the main thread, but that's annoying - // because it lacks precision about the source of the error. - shadow_graph: ShadowGraph, - - // current buffer, where we accumulate messages - messages: VecCell, - - // where to receive new buffer when full - swap_in: Receiver>, - - // where to send buffer when full - swap_out: Sender>, - - // where to receive query results - query_in: Receiver, -} - -const INITIAL_CAPACITY: usize = 2048; - -impl DepGraphThreadData { - pub fn new(enabled: bool) -> DepGraphThreadData { - let (tx1, rx1) = mpsc::channel(); - let (tx2, rx2) = mpsc::channel(); - let (txq, rxq) = mpsc::channel(); - - if enabled { - thread::spawn(move || main(rx1, tx2, txq)); - } - - DepGraphThreadData { - enabled: enabled, - shadow_graph: ShadowGraph::new(), - messages: VecCell::with_capacity(INITIAL_CAPACITY), - swap_in: rx2, - swap_out: tx1, - query_in: rxq, - } - } - - /// True if we are actually building the full dep-graph. - #[inline] - pub fn is_fully_enabled(&self) -> bool { - self.enabled - } - - /// True if (a) we are actually building the full dep-graph, or (b) we are - /// only enqueuing messages in order to sanity-check them (which happens - /// when debug assertions are enabled). - #[inline] - pub fn is_enqueue_enabled(&self) -> bool { - self.is_fully_enabled() || self.shadow_graph.enabled() - } - - /// Sends the current batch of messages to the thread. Installs a - /// new vector of messages. - fn swap(&self) { - assert!(self.is_fully_enabled(), "should never swap if not fully 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.is_fully_enabled(), "should never query if not fully 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) { - assert!(self.is_enqueue_enabled(), "should never enqueue if not enqueue-enabled"); - self.shadow_graph.enqueue(&message); - if self.is_fully_enabled() { - self.enqueue_enabled(message); - } - } - - // Outline this fn since I expect it may want to be inlined - // separately. - fn enqueue_enabled(&self, message: DepMessage) { - 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::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(), - } - } - if let Err(_) = swap_out.send(messages) { - // the receiver must have been dropped already - break; - } - } -} diff --git a/src/librustc_incremental/persist/load.rs b/src/librustc_incremental/persist/load.rs index 28a00bf4aa6c8..12d2d3032d108 100644 --- a/src/librustc_incremental/persist/load.rs +++ b/src/librustc_incremental/persist/load.rs @@ -201,11 +201,7 @@ pub fn decode_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, clean_work_products.insert(wp_id); } - tcx.dep_graph.with_task(*bootstrap_output, (), (), create_node); - - fn create_node((): (), (): ()) { - // just create the node with no inputs - } + tcx.dep_graph.add_node_directly(*bootstrap_output); } // Add in work-products that are still clean, and delete those that are @@ -449,8 +445,7 @@ fn process_edge<'a, 'tcx, 'edges>( if !dirty_raw_nodes.contains_key(&target) { let target = nodes[target]; let source = nodes[source]; - let _task = tcx.dep_graph.in_task(target); - tcx.dep_graph.read(source); + tcx.dep_graph.add_edge_directly(source, target); if let DepKind::WorkProduct = target.kind { let wp_id = WorkProductId::from_fingerprint(target.hash); From 0d4d452d7b3a42c5b9243f043ccf1ac1f8dce69c Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Fri, 16 Jun 2017 16:07:10 +0200 Subject: [PATCH 3/3] incr.comp.: Introduce the concept of anonymous DepNodes and use them for trait selection. --- src/librustc/dep_graph/dep_tracking_map.rs | 87 ++++++---------------- src/librustc/dep_graph/edges.rs | 60 ++++++++++++++- src/librustc/dep_graph/graph.rs | 17 ++++- src/librustc/traits/trans/mod.rs | 59 ++++----------- src/librustc/ty/maps.rs | 19 +++-- src/librustc/util/common.rs | 42 +---------- src/librustc_metadata/cstore_impl.rs | 4 +- 7 files changed, 126 insertions(+), 162 deletions(-) diff --git a/src/librustc/dep_graph/dep_tracking_map.rs b/src/librustc/dep_graph/dep_tracking_map.rs index 43f8d6b938dab..e8fbfafe191c4 100644 --- a/src/librustc/dep_graph/dep_tracking_map.rs +++ b/src/librustc/dep_graph/dep_tracking_map.rs @@ -12,10 +12,7 @@ use rustc_data_structures::fx::FxHashMap; use std::cell::RefCell; use std::hash::Hash; use std::marker::PhantomData; -use ty::TyCtxt; -use util::common::MemoizationMap; - -use super::{DepNode, DepGraph}; +use super::{DepNode, DepGraph, DepKind}; /// A DepTrackingMap offers a subset of the `Map` API and ensures that /// we make calls to `read` and `write` as appropriate. We key the @@ -23,13 +20,16 @@ use super::{DepNode, DepGraph}; pub struct DepTrackingMap { phantom: PhantomData, graph: DepGraph, - map: FxHashMap, + map: RefCell>, } pub trait DepTrackingMapConfig { type Key: Eq + Hash + Clone; type Value: Clone; - fn to_dep_node(tcx: TyCtxt, key: &Self::Key) -> DepNode; + + /// A DepTrackingMap always creates anonymous DepNodes, but the DepKind of + /// the nodes generated by a given map can be specified via this constant. + const DEP_KIND: DepKind; } impl DepTrackingMap { @@ -37,36 +37,10 @@ impl DepTrackingMap { DepTrackingMap { phantom: PhantomData, graph: graph, - map: FxHashMap(), + map: RefCell::new(FxHashMap()), } } - /// Registers a (synthetic) read from the key `k`. Usually this - /// is invoked automatically by `get`. - fn read(&self, tcx: TyCtxt, k: &M::Key) { - let dep_node = M::to_dep_node(tcx, k); - self.graph.read(dep_node); - } - - pub fn get(&self, tcx: TyCtxt, k: &M::Key) -> Option<&M::Value> { - self.read(tcx, k); - self.map.get(k) - } - - pub fn contains_key(&self, tcx: TyCtxt, k: &M::Key) -> bool { - self.read(tcx, k); - self.map.contains_key(k) - } - - pub fn keys(&self) -> Vec { - self.map.keys().cloned().collect() - } -} - -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: @@ -76,44 +50,27 @@ impl MemoizationMap for RefCell> { /// Here, `[op]` represents whatever nodes `op` reads in the /// course of execution; `Map(key)` represents the node for this /// map; and `CurrentTask` represents the current task when - /// `memoize` is invoked. - /// - /// **Important:* when `op` is invoked, the current task will be - /// switched to `Map(key)`. Therefore, if `op` makes use of any - /// HIR nodes or shared state accessed through its closure - /// environment, it must explicitly register a read of that - /// state. As an example, see `type_of_item` in `collect`, - /// which looks something like this: - /// - /// ``` - /// fn type_of_item(..., item: &hir::Item) -> Ty<'tcx> { - /// let item_def_id = ccx.tcx.hir.local_def_id(it.id); - /// ccx.tcx.item_types.memoized(item_def_id, || { - /// ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // (*) - /// compute_type_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, tcx: TyCtxt, key: M::Key, op: OP) -> M::Value - where OP: FnOnce() -> M::Value + /// `memoize` is invoked. The node `Map(key)` is an automatically + /// generated, "anonymous" node with DepKind as configured in the + /// map's `DepTrackingMapConfig`. + pub fn memoize(&self, + key: M::Key, + op: F) + -> M::Value + where F: FnOnce() -> M::Value { - let graph; { - let this = self.borrow(); - if let Some(result) = this.map.get(&key) { - this.read(tcx, &key); + let map = self.map.borrow(); + if let Some(&(ref result, dep_node)) = map.get(&key) { + self.graph.read(dep_node); return result.clone(); } - graph = this.graph.clone(); } - let _task = graph.in_task(M::to_dep_node(tcx, &key)); - let result = op(); - self.borrow_mut().map.insert(key, result.clone()); + let (result, dep_node) = self.graph.with_anon_task(M::DEP_KIND, op); + + self.graph.read(dep_node); + self.map.borrow_mut().insert(key, (result.clone(), dep_node)); result } } diff --git a/src/librustc/dep_graph/edges.rs b/src/librustc/dep_graph/edges.rs index 91ff6bb2ae031..9e77e49fea55c 100644 --- a/src/librustc/dep_graph/edges.rs +++ b/src/librustc/dep_graph/edges.rs @@ -8,8 +8,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use ich::Fingerprint; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use super::{DepGraphQuery, DepNode}; +use rustc_data_structures::stable_hasher::StableHasher; +use std::hash::Hash; +use std::mem; +use super::{DepGraphQuery, DepKind, DepNode}; pub struct DepGraphEdges { nodes: Vec, @@ -41,6 +45,10 @@ enum OpenTask { reads: Vec, read_set: FxHashSet, }, + Anon { + reads: Vec, + read_set: FxHashSet, + }, Ignore, } @@ -75,6 +83,13 @@ impl DepGraphEdges { }); } + pub fn push_anon_task(&mut self) { + self.task_stack.push(OpenTask::Anon { + reads: Vec::new(), + read_set: FxHashSet(), + }); + } + pub fn pop_task(&mut self, key: DepNode) { let popped_node = self.task_stack.pop().unwrap(); @@ -96,6 +111,49 @@ impl DepGraphEdges { } } + pub fn pop_anon_task(&mut self, kind: DepKind) -> DepNode { + let popped_node = self.task_stack.pop().unwrap(); + + if let OpenTask::Anon { + read_set: _, + reads + } = popped_node { + let mut fingerprint = Fingerprint::zero(); + let mut hasher = StableHasher::new(); + + for read in reads.iter() { + mem::discriminant(&read.kind).hash(&mut hasher); + + // Fingerprint::combine() is faster than sending Fingerprint + // through the StableHasher (at least as long as StableHasher + // is so slow). + fingerprint = fingerprint.combine(read.hash); + } + + fingerprint = fingerprint.combine(hasher.finish()); + + let target_dep_node = DepNode { + kind, + hash: fingerprint, + }; + + if self.indices.contains_key(&target_dep_node) { + return target_dep_node; + } + + let target_id = self.get_or_create_node(target_dep_node); + + for read in reads.into_iter() { + let source_id = self.get_or_create_node(read); + self.edges.insert((source_id, target_id)); + } + + target_dep_node + } else { + bug!("pop_anon_task() - Expected anonymous task to be popped") + } + } + /// Indicates that the current task `C` reads `v` by adding an /// edge from `v` to `C`. If there is no current task, has no /// effect. Note that *reading* from tracked state is harmless if diff --git a/src/librustc/dep_graph/graph.rs b/src/librustc/dep_graph/graph.rs index d4f1b7892a490..d7c8887f422cc 100644 --- a/src/librustc/dep_graph/graph.rs +++ b/src/librustc/dep_graph/graph.rs @@ -13,7 +13,7 @@ use session::config::OutputType; use std::cell::{Ref, RefCell}; use std::rc::Rc; -use super::dep_node::{DepNode, WorkProductId}; +use super::dep_node::{DepNode, DepKind, WorkProductId}; use super::query::DepGraphQuery; use super::raii; use super::safe::DepGraphSafe; @@ -115,6 +115,21 @@ impl DepGraph { task(cx, arg) } + /// Execute something within an "anonymous" task, that is, a task the + /// DepNode of which is determined by the list of inputs it read from. + pub fn with_anon_task(&self, dep_kind: DepKind, op: OP) -> (R, DepNode) + where OP: FnOnce() -> R + { + if let Some(ref data) = self.data { + data.edges.borrow_mut().push_anon_task(); + let result = op(); + let dep_node = data.edges.borrow_mut().pop_anon_task(dep_kind); + (result, dep_node) + } else { + (op(), DepNode::new_no_params(DepKind::Krate)) + } + } + #[inline] pub fn read(&self, v: DepNode) { if let Some(ref data) = self.data { diff --git a/src/librustc/traits/trans/mod.rs b/src/librustc/traits/trans/mod.rs index 40bd88d731d3d..eb1801728079d 100644 --- a/src/librustc/traits/trans/mod.rs +++ b/src/librustc/traits/trans/mod.rs @@ -13,11 +13,8 @@ // seems likely that they should eventually be merged into more // general routines. -use dep_graph::{DepGraph, DepNode, DepTrackingMap, DepTrackingMapConfig, - DepConstructor}; -use hir::def_id::DefId; +use dep_graph::{DepGraph, DepKind, DepTrackingMap, DepTrackingMapConfig}; use infer::TransNormalize; -use std::cell::RefCell; use std::marker::PhantomData; use syntax::ast; use syntax_pos::Span; @@ -25,7 +22,6 @@ use traits::{FulfillmentContext, Obligation, ObligationCause, Reveal, SelectionC use ty::{self, Ty, TyCtxt}; use ty::subst::{Subst, Substs}; use ty::fold::{TypeFoldable, TypeFolder}; -use util::common::MemoizationMap; impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> { /// Attempts to resolve an obligation to a vtable.. The result is @@ -41,7 +37,7 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> { // Remove any references to regions; this helps improve caching. let trait_ref = self.erase_regions(&trait_ref); - self.trans_trait_caches.trait_cache.memoize(self, trait_ref, || { + self.trans_trait_caches.trait_cache.memoize(trait_ref, || { debug!("trans::fulfill_obligation(trait_ref={:?}, def_id={:?})", trait_ref, trait_ref.def_id()); @@ -139,7 +135,7 @@ impl<'a, 'gcx> TypeFolder<'gcx, 'gcx> for AssociatedTypeNormalizer<'a, 'gcx> { if !ty.has_projection_types() { ty } else { - self.tcx.trans_trait_caches.project_cache.memoize(self.tcx, ty, || { + self.tcx.trans_trait_caches.project_cache.memoize(ty, || { debug!("AssociatedTypeNormalizer: ty={:?}", ty); self.tcx.normalize_associated_type(&ty) }) @@ -150,30 +146,27 @@ impl<'a, 'gcx> TypeFolder<'gcx, 'gcx> for AssociatedTypeNormalizer<'a, 'gcx> { /// Specializes caches used in trans -- in particular, they assume all /// types are fully monomorphized and that free regions can be erased. pub struct TransTraitCaches<'tcx> { - trait_cache: RefCell>>, - project_cache: RefCell>>, + trait_cache: DepTrackingMap>, + project_cache: DepTrackingMap>, } impl<'tcx> TransTraitCaches<'tcx> { pub fn new(graph: DepGraph) -> Self { TransTraitCaches { - trait_cache: RefCell::new(DepTrackingMap::new(graph.clone())), - project_cache: RefCell::new(DepTrackingMap::new(graph)), + trait_cache: DepTrackingMap::new(graph.clone()), + project_cache: DepTrackingMap::new(graph.clone()), } } } -// Implement DepTrackingMapConfig for `trait_cache` -pub struct TraitSelectionCache<'tcx> { +struct TraitSelectionCache<'tcx> { data: PhantomData<&'tcx ()> } impl<'tcx> DepTrackingMapConfig for TraitSelectionCache<'tcx> { + const DEP_KIND: DepKind = DepKind::TraitSelect; type Key = ty::PolyTraitRef<'tcx>; type Value = Vtable<'tcx, ()>; - fn to_dep_node(tcx: TyCtxt, key: &ty::PolyTraitRef<'tcx>) -> DepNode { - key.to_poly_trait_predicate().dep_node(tcx) - } } // # Global Cache @@ -182,34 +175,8 @@ pub struct ProjectionCache<'gcx> { data: PhantomData<&'gcx ()> } -impl<'gcx> DepTrackingMapConfig for ProjectionCache<'gcx> { - type Key = Ty<'gcx>; - type Value = Ty<'gcx>; - fn to_dep_node(tcx: TyCtxt, key: &Self::Key) -> DepNode { - // Ideally, we'd just put `key` into the dep-node, but we - // can't put full types in there. So just collect up all the - // def-ids of structs/enums as well as any traits that we - // project out of. It doesn't matter so much what we do here, - // except that if we are too coarse, we'll create overly - // coarse edges between impls and the trans. For example, if - // we just used the def-id of things we are projecting out of, - // then the key for `::T` and `::T` would both share a dep-node - // (`TraitSelect(SomeTrait)`), and hence the impls for both - // `Foo` and `Bar` would be considered inputs. So a change to - // `Bar` would affect things that just normalized `Foo`. - // Anyway, this heuristic is not ideal, but better than - // nothing. - let def_ids: Vec = - key.walk() - .filter_map(|t| match t.sty { - ty::TyAdt(adt_def, _) => Some(adt_def.did), - ty::TyProjection(ref proj) => Some(proj.trait_ref.def_id), - _ => None, - }) - .collect(); - - DepNode::new(tcx, DepConstructor::ProjectionCache { def_ids: def_ids }) - } +impl<'tcx> DepTrackingMapConfig for ProjectionCache<'tcx> { + type Key = Ty<'tcx>; + type Value = Ty<'tcx>; + const DEP_KIND: DepKind = DepKind::ProjectionCache; } - diff --git a/src/librustc/ty/maps.rs b/src/librustc/ty/maps.rs index 162a734aa195b..43b128d69af6f 100644 --- a/src/librustc/ty/maps.rs +++ b/src/librustc/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::{DepConstructor, DepNode, DepTrackingMapConfig}; +use dep_graph::{DepConstructor, DepNode}; use hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId, LOCAL_CRATE}; use hir::def::Def; use hir; @@ -260,11 +260,16 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { } } -trait QueryDescription: DepTrackingMapConfig { +pub trait QueryConfig { + type Key: Eq + Hash + Clone; + type Value; +} + +trait QueryDescription: QueryConfig { fn describe(tcx: TyCtxt, key: Self::Key) -> String; } -impl> QueryDescription for M { +impl> QueryDescription for M { default fn describe(tcx: TyCtxt, def_id: DefId) -> String { format!("processing `{}`", tcx.item_path_str(def_id)) } @@ -519,9 +524,12 @@ macro_rules! define_maps { })* } - $(impl<$tcx> DepTrackingMapConfig for queries::$name<$tcx> { + $(impl<$tcx> QueryConfig for queries::$name<$tcx> { type Key = $K; type Value = $V; + } + + impl<'a, $tcx, 'lcx> queries::$name<$tcx> { #[allow(unused)] fn to_dep_node(tcx: TyCtxt, key: &$K) -> DepNode { @@ -529,8 +537,7 @@ macro_rules! define_maps { DepNode::new(tcx, $node(*key)) } - } - impl<'a, $tcx, 'lcx> queries::$name<$tcx> { + fn try_get_with(tcx: TyCtxt<'a, $tcx, 'lcx>, mut span: Span, key: $K, diff --git a/src/librustc/util/common.rs b/src/librustc/util/common.rs index 40ee3cd28f562..25a567bc0bbbc 100644 --- a/src/librustc/util/common.rs +++ b/src/librustc/util/common.rs @@ -10,17 +10,13 @@ #![allow(non_camel_case_types)] -use std::cell::{RefCell, Cell}; -use std::collections::HashMap; +use std::cell::Cell; use std::ffi::CString; use std::fmt::Debug; -use std::hash::{Hash, BuildHasher}; use std::iter::repeat; use std::path::Path; use std::time::{Duration, Instant}; -use ty::TyCtxt; - // The name of the associated type for `Fn` return types pub const FN_OUTPUT_NAME: &'static str = "Output"; @@ -200,42 +196,6 @@ pub fn indenter() -> Indenter { Indenter { _cannot_construct_outside_of_this_module: () } } -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, tcx: TyCtxt, key: Self::Key, op: OP) -> Self::Value - where OP: FnOnce() -> Self::Value; -} - -impl MemoizationMap for RefCell> - where K: Hash+Eq+Clone, V: Clone, S: BuildHasher -{ - type Key = K; - type Value = V; - - fn memoize(&self, _tcx: TyCtxt, 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 - } - } - } -} - #[cfg(unix)] pub fn path2cstr(p: &Path) -> CString { use std::os::unix::prelude::*; diff --git a/src/librustc_metadata/cstore_impl.rs b/src/librustc_metadata/cstore_impl.rs index 34bd309a09ceb..2a28e64347ea4 100644 --- a/src/librustc_metadata/cstore_impl.rs +++ b/src/librustc_metadata/cstore_impl.rs @@ -12,7 +12,7 @@ use cstore; use encoder; use schema; -use rustc::dep_graph::DepTrackingMapConfig; +use rustc::ty::maps::QueryConfig; use rustc::middle::cstore::{CrateStore, CrateSource, LibSource, DepKind, ExternCrate, NativeLibrary, MetadataLoader, LinkMeta, LinkagePreference, LoadedMacro, EncodedMetadata}; @@ -43,7 +43,7 @@ macro_rules! provide { pub fn provide<$lt>(providers: &mut Providers<$lt>) { $(fn $name<'a, $lt:$lt>($tcx: TyCtxt<'a, $lt, $lt>, $def_id: DefId) -> as - DepTrackingMapConfig>::Value { + QueryConfig>::Value { assert!(!$def_id.is_local()); let def_path_hash = $tcx.def_path_hash($def_id);