From c2e6debb37282b75955d810f3f430ee7eacf3cb6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 11 May 2018 08:46:04 -0300 Subject: [PATCH 01/14] rename `timely` to `naive` --- src/output/mod.rs | 4 ++-- src/output/{timely.rs => naive.rs} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/output/{timely.rs => naive.rs} (99%) diff --git a/src/output/mod.rs b/src/output/mod.rs index f81be867bdd..cb0f2e88475 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -19,7 +19,7 @@ use std::path::PathBuf; mod dump; mod tracking; -mod timely; +mod naive; #[derive(Clone, Debug)] @@ -38,7 +38,7 @@ crate struct Output { impl Output { crate fn compute(all_facts: AllFacts, algorithm: Algorithm, dump_enabled: bool) -> Self { match algorithm { - Algorithm::Naive => timely::timely_dataflow(dump_enabled, all_facts), + Algorithm::Naive => naive::compute(dump_enabled, all_facts), } } diff --git a/src/output/timely.rs b/src/output/naive.rs similarity index 99% rename from src/output/timely.rs rename to src/output/naive.rs index bbd873aaa38..c303dc5e7cf 100644 --- a/src/output/timely.rs +++ b/src/output/naive.rs @@ -22,7 +22,7 @@ use std::sync::Mutex; use timely; use timely::dataflow::operators::*; -pub(super) fn timely_dataflow(dump_enabled: bool, all_facts: AllFacts) -> Output { +pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { let result = Arc::new(Mutex::new(Output::new(dump_enabled))); // Use a channel to send `all_facts` to one worker (and only one) From db1777e05fb4761f88915b410b121a69727c4cad Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 11 May 2018 09:21:12 -0300 Subject: [PATCH 02/14] squash deprecation warnings --- src/cli.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 76948fc7b56..7c81609a432 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] // arg_enum! uses deprecated stuff + use crate::intern; use crate::output::Output; use crate::tab_delim; From 6612e31a339b745e3a6d0213d3bfb43095c9c092 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 11 May 2018 09:23:31 -0300 Subject: [PATCH 03/14] add a "timely opt" version using differential-dataflow --- src/cli.rs | 1 + src/output/mod.rs | 2 + src/output/timely_opt.rs | 264 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 src/output/timely_opt.rs diff --git a/src/cli.rs b/src/cli.rs index 7c81609a432..d699d7b8712 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,6 +12,7 @@ arg_enum! { #[derive(Debug, Clone, Copy)] pub enum Algorithm { Naive, + TimelyOpt, } } diff --git a/src/output/mod.rs b/src/output/mod.rs index cb0f2e88475..648a096aa28 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -20,6 +20,7 @@ use std::path::PathBuf; mod dump; mod tracking; mod naive; +mod timely_opt; #[derive(Clone, Debug)] @@ -39,6 +40,7 @@ impl Output { crate fn compute(all_facts: AllFacts, algorithm: Algorithm, dump_enabled: bool) -> Self { match algorithm { Algorithm::Naive => naive::compute(dump_enabled, all_facts), + Algorithm::TimelyOpt => timely_opt::compute(dump_enabled, all_facts), } } diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs new file mode 100644 index 00000000000..c303dc5e7cf --- /dev/null +++ b/src/output/timely_opt.rs @@ -0,0 +1,264 @@ +// Copyright 2017 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. + +//! Timely dataflow runs on its own thread. + +use crate::facts::AllFacts; +use crate::output::Output; +use differential_dataflow::collection::Collection; +use differential_dataflow::operators::*; +use std::collections::{BTreeMap, BTreeSet}; +use std::mem; +use std::sync::mpsc; +use std::sync::Arc; +use std::sync::Mutex; +use timely; +use timely::dataflow::operators::*; + +pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { + let result = Arc::new(Mutex::new(Output::new(dump_enabled))); + + // Use a channel to send `all_facts` to one worker (and only one) + let (tx, rx) = mpsc::channel(); + tx.send(all_facts).unwrap(); + mem::drop(tx); + let rx = Mutex::new(rx); + + timely::execute_from_args(vec![].into_iter(), { + let result = result.clone(); + move |worker| { + // First come, first serve: one worker gets all the facts; + // the others get empty vectors. + let my_facts = rx.lock() + .unwrap() + .recv() + .unwrap_or_else(|_| AllFacts::default()); + + worker.dataflow::<(), _, _>(|scope| { + macro_rules! let_collections { + (let ($($facts:ident,)*) = ..$base:expr;) => { + let ($($facts),*) = ( + $(Collection::<_, _, isize>::new( + $base.$facts + .to_stream(scope) + .map(|datum| (datum, Default::default(), 1)), + ),)* + ); + } + } + + let_collections! { + let ( + borrow_region, + universal_region, + cfg_edge, + killed, + outlives, + region_live_at, + ) = ..my_facts; + } + + // .decl subset(Ra, Rb, P) -- at the point P, R1 <= R2 holds + let subset = outlives.iterate(|subset| { + let outlives = outlives.enter(&subset.scope()); + let cfg_edge = cfg_edge.enter(&subset.scope()); + let region_live_at = region_live_at.enter(&subset.scope()); + let universal_region = universal_region.enter(&subset.scope()); + + // subset(R1, R2, P) :- outlives(R1, R2, P). + let subset1 = outlives.clone(); + + // subset(R1, R3, P) :- + // subset(R1, R2, P), + // subset(R2, R3, P). + let subset2 = subset + .map(|(r1, r2, q)| ((r2, q), r1)) + .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) + .map(|((_r2, p), r1, r3)| (r1, r3, p)); + + // subset(R1, R2, Q) :- + // subset(R1, R2, P), + // cfg_edge(P, Q), + // (region_live_at(R1, Q); universal_region(R1)), + // (region_live_at(R2, Q); universal_region(R2)). + let subset3base0 = subset.map(|(r1, r2, p)| (p, (r1, r2))).join(&cfg_edge); + let subset3base1 = subset3base0 + .map(|(_p, (r1, r2), q)| ((r1, q), r2)) + .semijoin(®ion_live_at); + let subset3a = subset3base1 + .map(|((r1, q), r2)| ((r2, q), r1)) + .semijoin(®ion_live_at) + .map(|((r2, q), r1)| (r1, r2, q)); + let subset3b = subset3base1 + .map(|((r1, q), r2)| (r2, (q, r1))) + .semijoin(&universal_region) + .map(|(r2, (q, r1))| (r1, r2, q)); + let subset3base2 = subset3base0 + .map(|(_p, (r1, r2), q)| (r1, (q, r2))) + .semijoin(&universal_region); + let subset3c = subset3base2 + .map(|(r1, (q, r2))| (r2, (q, r1))) + .semijoin(&universal_region) + .map(|(r2, (q, r1))| (r1, r2, q)); + let subset3d = subset3base2 + .map(|(r1, (q, r2))| ((r2, q), r1)) + .semijoin(®ion_live_at) + .map(|((r2, q), r1)| (r1, r2, q)); + + subset1 + .concat(&subset2) + .concat(&subset3a) + .concat(&subset3b) + .concat(&subset3c) + .concat(&subset3d) + .distinct() + }); + + // .decl requires(R, B, P) -- at the point, things with region R + // may depend on data from borrow B + let requires = borrow_region.iterate(|requires| { + let borrow_region = borrow_region.enter(&requires.scope()); + let subset = subset.enter(&requires.scope()); + let killed = killed.enter(&requires.scope()); + let region_live_at = region_live_at.enter(&requires.scope()); + let cfg_edge = cfg_edge.enter(&requires.scope()); + let universal_region = universal_region.enter(&requires.scope()); + + // requires(R, B, P) :- borrow_region(R, B, P). + let requires1 = borrow_region.clone(); + + // requires(R2, B, P) :- + // requires(R1, B, P), + // subset(R1, R2, P). + let requires2 = requires + .map(|(r1, b, p)| ((r1, p), b)) + .join(&subset.map(|(r1, r2, p)| ((r1, p), r2))) + .map(|((_r1, p), b, r2)| (r2, b, p)); + + // requires(R, B, Q) :- + // requires(R, B, P), + // !killed(B, P), + // cfg_edge(P, Q), + // (region_live_at(R, Q); universal_region(R)). + let requires_propagate_base = requires + .map(|(r, b, p)| ((b, p), r)) + .antijoin(&killed) + .map(|((b, p), r)| (p, (r, b))) + .join(&cfg_edge); + let requires3 = requires_propagate_base + .map(|(_p, (r, b), q)| ((r, q), b)) + .semijoin(®ion_live_at) + .map(|((r, q), b)| (r, b, q)); + let requires4 = requires_propagate_base + .map(|(_p, (r, b), q)| (r, (q, b))) + .semijoin(&universal_region) + .map(|(r, (q, b))| (r, b, q)); + + requires1 + .concat(&requires2) + .concat(&requires3) + .concat(&requires4) + .distinct() + }); + + // .decl borrow_live_at(B, P) -- true if the restrictions of the borrow B + // need to be enforced at the point P + let borrow_live_at = { + // borrow_live_at(B, P) :- requires(R, B, P), region_live_at(R, P) + let borrow_live_at1 = requires + .map(|(r, b, p)| ((r, p), b)) + .semijoin(®ion_live_at) + .map(|((_r, p), b)| (b, p)); + + // borrow_live_at(B, P) :- requires(R, B, P), universal_region(R). + let borrow_live_at2 = requires + .map(|(r, b, p)| (r, (p, b))) + .semijoin(&universal_region) + .map(|(_r, (p, b))| (b, p)); + + borrow_live_at1.concat(&borrow_live_at2).distinct() + }; + + if dump_enabled { + region_live_at.inspect_batch({ + let result = result.clone(); + move |_timestamp, facts| { + let mut result = result.lock().unwrap(); + for ((region, location), _timestamp, multiplicity) in facts { + assert_eq!(*multiplicity, 1); + result + .region_live_at + .entry(*location) + .or_insert(vec![]) + .push(*region); + } + } + }); + + subset.inspect_batch({ + let result = result.clone(); + move |_timestamp, facts| { + let mut result = result.lock().unwrap(); + for ((r1, r2, location), _timestamp, multiplicity) in facts { + assert_eq!(*multiplicity, 1); + result + .subset + .entry(*location) + .or_insert(BTreeMap::new()) + .entry(*r1) + .or_insert(BTreeSet::new()) + .insert(*r2); + result.region_degrees.update_degrees(*r1, *r2, *location); + } + } + }); + + requires.inspect_batch({ + let result = result.clone(); + move |_timestamp, facts| { + let mut result = result.lock().unwrap(); + for ((region, borrow, location), _timestamp, multiplicity) in facts { + assert_eq!(*multiplicity, 1); + result + .restricts + .entry(*location) + .or_insert(BTreeMap::new()) + .entry(*region) + .or_insert(BTreeSet::new()) + .insert(*borrow); + } + } + }); + } + + borrow_live_at.inspect_batch({ + let result = result.clone(); + move |_timestamp, facts| { + let mut result = result.lock().unwrap(); + for ((borrow, location), _timestamp, multiplicity) in facts { + assert_eq!(*multiplicity, 1); + result + .borrow_live_at + .entry(*location) + .or_insert(Vec::new()) + .push(*borrow); + } + } + }); + }); + } + }).unwrap(); + + // Remove from the Arc and Mutex, since it is fully populated now. + Arc::try_unwrap(result) + .unwrap_or_else(|_| panic!("somebody still has a handle to this arc")) + .into_inner() + .unwrap() +} From c4189a7b13c351deed96a6f72b4522b62dc91e0a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 11 May 2018 20:06:46 -0300 Subject: [PATCH 04/14] compute the transitive closure only when needed Instead of computing the full transitive closure of the subset relation at each point, we now only compute it for those regions that are "dying". --- src/cli.rs | 2 +- src/facts.rs | 2 +- src/output/mod.rs | 8 +- src/output/timely_opt.rs | 185 ++++++++++++++++++++++++++------------- src/test.rs | 4 +- 5 files changed, 132 insertions(+), 69 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index d699d7b8712..abd9e5c997f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -48,7 +48,7 @@ pub fn main(opt: Opt) -> Result<(), Error> { let verbose = opt.verbose | opt.stats; let algorithm = opt.algorithm; let all_facts = tab_delim::load_tab_delimited_facts(tables, &Path::new(&facts_dir))?; - timed(|| Output::compute(all_facts, algorithm, verbose)) + timed(|| Output::compute(&all_facts, algorithm, verbose)) }; match result { diff --git a/src/facts.rs b/src/facts.rs index ee403e2c87a..d2048f9eeac 100644 --- a/src/facts.rs +++ b/src/facts.rs @@ -1,7 +1,7 @@ use abomonation_derive::Abomonation; /// The "facts" which are the basis of the NLL borrow analysis. -#[derive(Default)] +#[derive(Clone, Default)] crate struct AllFacts { /// `borrow_region(R, B, P)` -- the region R may refer to data /// from borrow B starting at the point P (this is usually the diff --git a/src/output/mod.rs b/src/output/mod.rs index 648a096aa28..92327840aa3 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -25,7 +25,7 @@ mod timely_opt; #[derive(Clone, Debug)] crate struct Output { - borrow_live_at: FxHashMap>, + crate borrow_live_at: FxHashMap>, dump_enabled: bool, @@ -37,10 +37,10 @@ crate struct Output { } impl Output { - crate fn compute(all_facts: AllFacts, algorithm: Algorithm, dump_enabled: bool) -> Self { + crate fn compute(all_facts: &AllFacts, algorithm: Algorithm, dump_enabled: bool) -> Self { match algorithm { - Algorithm::Naive => naive::compute(dump_enabled, all_facts), - Algorithm::TimelyOpt => timely_opt::compute(dump_enabled, all_facts), + Algorithm::Naive => naive::compute(dump_enabled, all_facts.clone()), + Algorithm::TimelyOpt => timely_opt::compute(dump_enabled, all_facts.clone()), } } diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index c303dc5e7cf..fdafd3e467c 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -10,9 +10,10 @@ //! Timely dataflow runs on its own thread. -use crate::facts::AllFacts; +use crate::facts::{AllFacts, Point}; use crate::output::Output; use differential_dataflow::collection::Collection; +use differential_dataflow::operators::iterate::Variable; use differential_dataflow::operators::*; use std::collections::{BTreeMap, BTreeSet}; use std::mem; @@ -21,8 +22,23 @@ use std::sync::Arc; use std::sync::Mutex; use timely; use timely::dataflow::operators::*; +use timely::dataflow::Scope; + +pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { + // Declare that each universal region is live at every point. + let all_points: BTreeSet = all_facts + .cfg_edge + .iter() + .map(|&(p, _)| p) + .chain(all_facts.cfg_edge.iter().map(|&(_, q)| q)) + .collect(); + + for &r in &all_facts.universal_region { + for &p in &all_points { + all_facts.region_live_at.push((r, p)); + } + } -pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { let result = Arc::new(Mutex::new(Output::new(dump_enabled))); // Use a channel to send `all_facts` to one worker (and only one) @@ -57,7 +73,6 @@ pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { let_collections! { let ( borrow_region, - universal_region, cfg_edge, killed, outlives, @@ -65,60 +80,119 @@ pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { ) = ..my_facts; } - // .decl subset(Ra, Rb, P) -- at the point P, R1 <= R2 holds - let subset = outlives.iterate(|subset| { - let outlives = outlives.enter(&subset.scope()); - let cfg_edge = cfg_edge.enter(&subset.scope()); - let region_live_at = region_live_at.enter(&subset.scope()); - let universal_region = universal_region.enter(&subset.scope()); + // .decl subset(R1, R2, P) + // + // At the point P, R1 <= R2. + let subset = scope.scoped(|subset_scope| { + let outlives = outlives.enter(&subset_scope); + let cfg_edge = cfg_edge.enter(&subset_scope); + let region_live_at = region_live_at.enter(&subset_scope); // subset(R1, R2, P) :- outlives(R1, R2, P). - let subset1 = outlives.clone(); + let subset_base = outlives.clone(); + let subset = Variable::from(subset_base.clone()); - // subset(R1, R3, P) :- + // .decl live_to_dead_regions(R1, R2, P, Q) + // + // The regions `R1` and `R2` are "live to dead" + // on the edge `P -> Q` if: + // + // - In P, `R1` <= `R2` + // - In Q, `R1` is live but `R2` is dead. + // + // In that case, `Q` would like to add all the + // live things reachable from `R2` to `R1`. + // + // live_to_dead_regions(R1, R2, P, Q) :- // subset(R1, R2, P), - // subset(R2, R3, P). - let subset2 = subset - .map(|(r1, r2, q)| ((r2, q), r1)) - .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) - .map(|((_r2, p), r1, r3)| (r1, r3, p)); + // cfg_edge(P, Q), + // region_live_at(R1, Q), + // !region_live_at(R2, Q). + let live_to_dead_regions = { + subset + .map(|(r1, r2, p)| (p, (r1, r2))) + .join(&cfg_edge) + .map(|(p, (r1, r2), q)| ((r1, q), (r2, p))) + .semijoin(®ion_live_at) + .map(|((r1, q), (r2, p))| ((r2, q), (r1, p))) + .antijoin(®ion_live_at) + .map(|((r2, q), (r1, p))| (r1, r2, p, q)) + }; + + // .decl dead_can_reach(R1, R2, P, Q) + // + // Indicates that the region `R1`, which is dead + // in `Q`, can reach the region `R2` in P. + // + // This is effectively the transitive subset + // relation, but we try to limit it to regions + // that are dying on the edge P -> Q. + let dead_can_reach = { + // dead_can_reach(R2, R3, P, Q) :- + // live_to_dead_regions(_R1, R2, P, Q), + // subset(R2, R3, P). + let dead_can_reach_base = { + live_to_dead_regions + .map(|(_r1, r2, p, q)| ((r2, p), q)) + .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) + .map(|((r2, p), q, r3)| (r2, r3, p, q)) + }; + + let dead_can_reach = Variable::from(dead_can_reach_base.clone()); + + // dead_can_reach(R1, R3, P, Q) :- + // dead_can_reach(R1, R2, P, Q), + // !region_live_at(R2, Q), + // subset(R2, R3, P). + // + // This is the "transitive closure" rule, but + // note that we only apply it with the + // "intermediate" region R2 is dead at Q. + let dead_can_reach2 = { + dead_can_reach + .map(|(r1, r2, p, q)| ((r2, q), (r1, p))) + .antijoin(®ion_live_at) + .map(|((r2, q), (r1, p))| ((r2, p), (r1, q))) + .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) + .map(|((_r2, p), (r1, q), r3)| (r1, r3, p, q)) + }; + + dead_can_reach.set(&dead_can_reach_base.concat(&dead_can_reach2).distinct()) + }; // subset(R1, R2, Q) :- - // subset(R1, R2, P), + // subset(R1, R2, P) :- // cfg_edge(P, Q), - // (region_live_at(R1, Q); universal_region(R1)), - // (region_live_at(R2, Q); universal_region(R2)). - let subset3base0 = subset.map(|(r1, r2, p)| (p, (r1, r2))).join(&cfg_edge); - let subset3base1 = subset3base0 + // region_live_at(R1, Q), + // region_live_at(R2, Q). + // + // Carry `R1 <= R2` from P into Q if both `R1` and + // `R2` are live in Q. + let subset1 = subset + .map(|(r1, r2, p)| (p, (r1, r2))) + .join(&cfg_edge) .map(|(_p, (r1, r2), q)| ((r1, q), r2)) - .semijoin(®ion_live_at); - let subset3a = subset3base1 - .map(|((r1, q), r2)| ((r2, q), r1)) .semijoin(®ion_live_at) - .map(|((r2, q), r1)| (r1, r2, q)); - let subset3b = subset3base1 - .map(|((r1, q), r2)| (r2, (q, r1))) - .semijoin(&universal_region) - .map(|(r2, (q, r1))| (r1, r2, q)); - let subset3base2 = subset3base0 - .map(|(_p, (r1, r2), q)| (r1, (q, r2))) - .semijoin(&universal_region); - let subset3c = subset3base2 - .map(|(r1, (q, r2))| (r2, (q, r1))) - .semijoin(&universal_region) - .map(|(r2, (q, r1))| (r1, r2, q)); - let subset3d = subset3base2 - .map(|(r1, (q, r2))| ((r2, q), r1)) + .map(|((r1, q), r2)| ((r2, q), r1)) .semijoin(®ion_live_at) .map(|((r2, q), r1)| (r1, r2, q)); - subset1 - .concat(&subset2) - .concat(&subset3a) - .concat(&subset3b) - .concat(&subset3c) - .concat(&subset3d) - .distinct() + // subset(R1, R3, Q) :- + // live_to_dead_regions(R1, R2, P, Q), + // dead_can_reach(R2, R3, P, Q), + // region_live_at(R3, Q). + let subset2 = { + live_to_dead_regions + .map(|(r1, r2, p, q)| ((r2, p, q), r1)) + .join(&dead_can_reach.map(|(r2, r3, p, q)| ((r2, p, q), r3))) + .map(|((_r2, _p, q), r1, r3)| ((r3, q), r1)) + .semijoin(®ion_live_at) + .map(|((r3, q), r1)| (r1, r3, q)) + }; + + subset + .set(&subset_base.concat(&subset1).concat(&subset2).distinct()) + .leave() }); // .decl requires(R, B, P) -- at the point, things with region R @@ -129,7 +203,6 @@ pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { let killed = killed.enter(&requires.scope()); let region_live_at = region_live_at.enter(&requires.scope()); let cfg_edge = cfg_edge.enter(&requires.scope()); - let universal_region = universal_region.enter(&requires.scope()); // requires(R, B, P) :- borrow_region(R, B, P). let requires1 = borrow_region.clone(); @@ -146,25 +219,19 @@ pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { // requires(R, B, P), // !killed(B, P), // cfg_edge(P, Q), - // (region_live_at(R, Q); universal_region(R)). - let requires_propagate_base = requires + // region_live_at(R, Q). + let requires3 = requires .map(|(r, b, p)| ((b, p), r)) .antijoin(&killed) .map(|((b, p), r)| (p, (r, b))) - .join(&cfg_edge); - let requires3 = requires_propagate_base + .join(&cfg_edge) .map(|(_p, (r, b), q)| ((r, q), b)) .semijoin(®ion_live_at) .map(|((r, q), b)| (r, b, q)); - let requires4 = requires_propagate_base - .map(|(_p, (r, b), q)| (r, (q, b))) - .semijoin(&universal_region) - .map(|(r, (q, b))| (r, b, q)); requires1 .concat(&requires2) .concat(&requires3) - .concat(&requires4) .distinct() }); @@ -177,13 +244,7 @@ pub(super) fn compute(dump_enabled: bool, all_facts: AllFacts) -> Output { .semijoin(®ion_live_at) .map(|((_r, p), b)| (b, p)); - // borrow_live_at(B, P) :- requires(R, B, P), universal_region(R). - let borrow_live_at2 = requires - .map(|(r, b, p)| (r, (p, b))) - .semijoin(&universal_region) - .map(|(_r, (p, b))| (b, p)); - - borrow_live_at1.concat(&borrow_live_at2).distinct() + borrow_live_at1.distinct() }; if dump_enabled { diff --git a/src/test.rs b/src/test.rs index ecdc504633b..2c372ccdf35 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,7 +17,9 @@ fn test_fn(dir_name: &str, fn_name: &str) -> Result<(), Error> { println!("facts_dir = {:?}", facts_dir); let tables = &mut intern::InternerTables::new(); let all_facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir)?; - let _result = Output::compute(all_facts, Algorithm::Naive, false); + let naive = Output::compute(&all_facts, Algorithm::Naive, false); + let timely_opt = Output::compute(&all_facts, Algorithm::TimelyOpt, false); + assert_eq!(naive.borrow_live_at, timely_opt.borrow_live_at); // FIXME: check `_result` somehow } } From b3280bbe138fd6582e23db1e6c60d4d02971d4e7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 11 May 2018 20:24:11 -0300 Subject: [PATCH 05/14] add rust-toolchain --- rust-toolchain | 1 + 1 file changed, 1 insertion(+) create mode 100644 rust-toolchain diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 00000000000..bf867e0ae5b --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly From 441cd59534639764756a91136aa764f0c48682de Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 11 May 2018 20:25:29 -0300 Subject: [PATCH 06/14] use `distinct_total`, not `distinct` --- src/output/timely_opt.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index fdafd3e467c..cd61213ef79 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -157,7 +157,9 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|((_r2, p), (r1, q), r3)| (r1, r3, p, q)) }; - dead_can_reach.set(&dead_can_reach_base.concat(&dead_can_reach2).distinct()) + dead_can_reach.set(&dead_can_reach_base + .concat(&dead_can_reach2) + .distinct_total()) }; // subset(R1, R2, Q) :- @@ -191,7 +193,10 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { }; subset - .set(&subset_base.concat(&subset1).concat(&subset2).distinct()) + .set(&subset_base + .concat(&subset1) + .concat(&subset2) + .distinct_total()) .leave() }); @@ -232,7 +237,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { requires1 .concat(&requires2) .concat(&requires3) - .distinct() + .distinct_total() }); // .decl borrow_live_at(B, P) -- true if the restrictions of the borrow B @@ -244,7 +249,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .semijoin(®ion_live_at) .map(|((_r, p), b)| (b, p)); - borrow_live_at1.distinct() + borrow_live_at1.distinct_total() }; if dump_enabled { From 3d4c99cf4eeda2fd4896d6ee3f8d2265d2ca2dc6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 12 May 2018 11:56:44 -0300 Subject: [PATCH 07/14] add a `distinct_total` we first compute each `R1 -> R2` relation where R1 is live but R2 is dead; then we find the distinct R2 values to use as "seeds" for the reachability analysis --- src/output/timely_opt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index cd61213ef79..4ac87a81e80 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -134,6 +134,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let dead_can_reach_base = { live_to_dead_regions .map(|(_r1, r2, p, q)| ((r2, p), q)) + .distinct_total() .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) .map(|((r2, p), q, r3)| (r2, r3, p, q)) }; From 38821b3c319c3d40e5a18cc97c92f5328764606c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 12 May 2018 12:08:22 -0300 Subject: [PATCH 08/14] merge the "borrow" iteration with the "subset" iteration --- src/output/timely_opt.rs | 68 ++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index 4ac87a81e80..7086b6f659d 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -80,17 +80,20 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { ) = ..my_facts; } - // .decl subset(R1, R2, P) - // - // At the point P, R1 <= R2. - let subset = scope.scoped(|subset_scope| { - let outlives = outlives.enter(&subset_scope); - let cfg_edge = cfg_edge.enter(&subset_scope); - let region_live_at = region_live_at.enter(&subset_scope); + let (subset, requires) = scope.scoped(|subscope| { + let outlives = outlives.enter(&subscope); + let cfg_edge = cfg_edge.enter(&subscope); + let region_live_at = region_live_at.enter(&subscope); + let borrow_region = borrow_region.enter(&subscope); + let killed = killed.enter(&subscope); + + // .decl subset(R1, R2, P) + // + // At the point P, R1 <= R2. // subset(R1, R2, P) :- outlives(R1, R2, P). - let subset_base = outlives.clone(); - let subset = Variable::from(subset_base.clone()); + let subset0 = outlives.clone(); + let subset = Variable::from(subset0.clone()); // .decl live_to_dead_regions(R1, R2, P, Q) // @@ -131,7 +134,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // dead_can_reach(R2, R3, P, Q) :- // live_to_dead_regions(_R1, R2, P, Q), // subset(R2, R3, P). - let dead_can_reach_base = { + let dead_can_reach0 = { live_to_dead_regions .map(|(_r1, r2, p, q)| ((r2, p), q)) .distinct_total() @@ -139,7 +142,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|((r2, p), q, r3)| (r2, r3, p, q)) }; - let dead_can_reach = Variable::from(dead_can_reach_base.clone()); + let dead_can_reach = Variable::from(dead_can_reach0.clone()); // dead_can_reach(R1, R3, P, Q) :- // dead_can_reach(R1, R2, P, Q), @@ -149,7 +152,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // This is the "transitive closure" rule, but // note that we only apply it with the // "intermediate" region R2 is dead at Q. - let dead_can_reach2 = { + let dead_can_reach1 = { dead_can_reach .map(|(r1, r2, p, q)| ((r2, q), (r1, p))) .antijoin(®ion_live_at) @@ -158,9 +161,8 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|((_r2, p), (r1, q), r3)| (r1, r3, p, q)) }; - dead_can_reach.set(&dead_can_reach_base - .concat(&dead_can_reach2) - .distinct_total()) + dead_can_reach + .set(&dead_can_reach0.concat(&dead_can_reach1).distinct_total()) }; // subset(R1, R2, Q) :- @@ -193,30 +195,17 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|((r3, q), r1)| (r1, r3, q)) }; - subset - .set(&subset_base - .concat(&subset1) - .concat(&subset2) - .distinct_total()) - .leave() - }); - - // .decl requires(R, B, P) -- at the point, things with region R - // may depend on data from borrow B - let requires = borrow_region.iterate(|requires| { - let borrow_region = borrow_region.enter(&requires.scope()); - let subset = subset.enter(&requires.scope()); - let killed = killed.enter(&requires.scope()); - let region_live_at = region_live_at.enter(&requires.scope()); - let cfg_edge = cfg_edge.enter(&requires.scope()); + // .decl requires(R, B, P) -- at the point, things with region R + // may depend on data from borrow B // requires(R, B, P) :- borrow_region(R, B, P). - let requires1 = borrow_region.clone(); + let requires0 = borrow_region.clone(); + let requires = Variable::from(requires0.clone()); // requires(R2, B, P) :- // requires(R1, B, P), // subset(R1, R2, P). - let requires2 = requires + let requires1 = requires .map(|(r1, b, p)| ((r1, p), b)) .join(&subset.map(|(r1, r2, p)| ((r1, p), r2))) .map(|((_r1, p), b, r2)| (r2, b, p)); @@ -226,7 +215,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // !killed(B, P), // cfg_edge(P, Q), // region_live_at(R, Q). - let requires3 = requires + let requires2 = requires .map(|(r, b, p)| ((b, p), r)) .antijoin(&killed) .map(|((b, p), r)| (p, (r, b))) @@ -235,10 +224,15 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .semijoin(®ion_live_at) .map(|((r, q), b)| (r, b, q)); - requires1 + let requires = requires.set(&requires0 + .concat(&requires1) .concat(&requires2) - .concat(&requires3) - .distinct_total() + .distinct_total()); + + let subset = + subset.set(&subset0.concat(&subset1).concat(&subset2).distinct_total()); + + (subset.leave(), requires.leave()) }); // .decl borrow_live_at(B, P) -- true if the restrictions of the borrow B From c739e3f4bff879854d8ee93939da6c59d17dd650 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 12 May 2018 12:37:06 -0300 Subject: [PATCH 09/14] generalize the `dead_can_reach` computations so they can be shared This does slow us down quite a bit right now though. --- src/output/timely_opt.rs | 90 ++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index 7086b6f659d..f2823120ad1 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -90,11 +90,18 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // .decl subset(R1, R2, P) // // At the point P, R1 <= R2. - + // // subset(R1, R2, P) :- outlives(R1, R2, P). let subset0 = outlives.clone(); let subset = Variable::from(subset0.clone()); + // .decl requires(R, B, P) -- at the point, things with region R + // may depend on data from borrow B + // + // requires(R, B, P) :- borrow_region(R, B, P). + let requires0 = borrow_region.clone(); + let requires = Variable::from(requires0.clone()); + // .decl live_to_dead_regions(R1, R2, P, Q) // // The regions `R1` and `R2` are "live to dead" @@ -122,6 +129,43 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|((r2, q), (r1, p))| (r1, r2, p, q)) }; + // .decl dead_region_requires(R, B, P, Q) + // + // The region `R` requires the borrow `B`, but the + // region ``R goes dead along the edge `P -> Q` + // + // dead_region_requires(R, B, P, Q) :- + // requires(R, B, P), + // cfg_edge(P, Q), + // !region_live_at(R, Q). + let dead_region_requires = { + requires + .map(|(r, b, p)| (p, (r, b))) + .join(&cfg_edge) + .map(|(p, (r, b), q)| ((r, q), (b, p))) + .antijoin(®ion_live_at) + .map(|((r, q), (b, p))| (r, b, p, q)) + }; + + // .decl dead_can_reach_origins(R, P, Q) + // + // Contains dead regions where we are interested + // in computing the transitive closure of things they + // can reach. + let dead_can_reach_origins = { + let dead_can_reach_origins0 = { + live_to_dead_regions + .map(|(_r1, r2, p, q)| ((r2, p), q)) + }; + let dead_can_reach_origins1 = { + dead_region_requires + .map(|(r, _b, p, q)| ((r, p), q)) + }; + dead_can_reach_origins0 + .concat(&dead_can_reach_origins1) + .distinct_total() + }; + // .decl dead_can_reach(R1, R2, P, Q) // // Indicates that the region `R1`, which is dead @@ -131,15 +175,13 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // relation, but we try to limit it to regions // that are dying on the edge P -> Q. let dead_can_reach = { - // dead_can_reach(R2, R3, P, Q) :- - // live_to_dead_regions(_R1, R2, P, Q), - // subset(R2, R3, P). + // dead_can_reach(R1, R2, P, Q) :- + // dead_can_reach_origins(R1, P, Q), + // subset(R1, R2, P). let dead_can_reach0 = { - live_to_dead_regions - .map(|(_r1, r2, p, q)| ((r2, p), q)) - .distinct_total() - .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) - .map(|((r2, p), q, r3)| (r2, r3, p, q)) + dead_can_reach_origins + .join(&subset.map(|(r1, r2, p)| ((r1, p), r2))) + .map(|((r1, p), q, r2)| (r1, r2, p, q)) }; let dead_can_reach = Variable::from(dead_can_reach0.clone()); @@ -165,6 +207,20 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .set(&dead_can_reach0.concat(&dead_can_reach1).distinct_total()) }; + // .decl dead_can_reach_live(R1, R2, P, Q) + // + // Indicates that, along the edge `P -> Q`, the + // dead (in Q) region R1 can reach the live (in Q) + // region R2 via a subset relation. This is a + // subset of the full `dead_can_reach` relation + // where we filter down to those cases where R2 is + // live in Q. + let dead_can_reach_live = { + dead_can_reach.map(|(r1, r2, p, q)| ((r2, q), (r1, p))) + .semijoin(®ion_live_at) + .map(|((r2, q), (r1, p))| (r1, r2, p, q)) + }; + // subset(R1, R2, Q) :- // subset(R1, R2, P) :- // cfg_edge(P, Q), @@ -184,24 +240,14 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // subset(R1, R3, Q) :- // live_to_dead_regions(R1, R2, P, Q), - // dead_can_reach(R2, R3, P, Q), - // region_live_at(R3, Q). + // dead_can_reach_live(R2, R3, P, Q). let subset2 = { live_to_dead_regions .map(|(r1, r2, p, q)| ((r2, p, q), r1)) - .join(&dead_can_reach.map(|(r2, r3, p, q)| ((r2, p, q), r3))) - .map(|((_r2, _p, q), r1, r3)| ((r3, q), r1)) - .semijoin(®ion_live_at) - .map(|((r3, q), r1)| (r1, r3, q)) + .join(&dead_can_reach_live.map(|(r2, r3, p, q)| ((r2, p, q), r3))) + .map(|((_r2, _p, q), r1, r3)| (r1, r3, q)) }; - // .decl requires(R, B, P) -- at the point, things with region R - // may depend on data from borrow B - - // requires(R, B, P) :- borrow_region(R, B, P). - let requires0 = borrow_region.clone(); - let requires = Variable::from(requires0.clone()); - // requires(R2, B, P) :- // requires(R1, B, P), // subset(R1, R2, P). From e5ace538c60bf7aa69c7431d6b1ce46c512363f4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 12 May 2018 11:47:30 -0400 Subject: [PATCH 10/14] avoid transitive computation of the `requires` relationship Instead, propagate `R requires B` to superset regions of R only when R goes dead. Timing now down to 15s from around 120s! --- src/output/timely_opt.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index f2823120ad1..a83c7ae2513 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -132,15 +132,18 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // .decl dead_region_requires(R, B, P, Q) // // The region `R` requires the borrow `B`, but the - // region ``R goes dead along the edge `P -> Q` + // region `R` goes dead along the edge `P -> Q` // // dead_region_requires(R, B, P, Q) :- // requires(R, B, P), + // !killed(B, P), // cfg_edge(P, Q), // !region_live_at(R, Q). let dead_region_requires = { requires - .map(|(r, b, p)| (p, (r, b))) + .map(|(r, b, p)| ((b, p), r)) + .antijoin(&killed) + .map(|((b, p), r)| (p, (r, b))) .join(&cfg_edge) .map(|(p, (r, b), q)| ((r, q), (b, p))) .antijoin(®ion_live_at) @@ -218,7 +221,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let dead_can_reach_live = { dead_can_reach.map(|(r1, r2, p, q)| ((r2, q), (r1, p))) .semijoin(®ion_live_at) - .map(|((r2, q), (r1, p))| (r1, r2, p, q)) + .map(|((r2, q), (r1, p))| ((r1, p, q), r2)) }; // subset(R1, R2, Q) :- @@ -244,17 +247,23 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let subset2 = { live_to_dead_regions .map(|(r1, r2, p, q)| ((r2, p, q), r1)) - .join(&dead_can_reach_live.map(|(r2, r3, p, q)| ((r2, p, q), r3))) + .join(&dead_can_reach_live) .map(|((_r2, _p, q), r1, r3)| (r1, r3, q)) }; - // requires(R2, B, P) :- - // requires(R1, B, P), - // subset(R1, R2, P). - let requires1 = requires - .map(|(r1, b, p)| ((r1, p), b)) - .join(&subset.map(|(r1, r2, p)| ((r1, p), r2))) - .map(|((_r1, p), b, r2)| (r2, b, p)); + // requires(R2, B, Q) :- + // dead_region_requires(R1, B, P, Q), + // dead_can_reach_live(R1, R2, P, Q). + // + // Communicate a `R1 requires B` relation across + // an edge `P -> Q` where `R1` is dead in Q; in + // that case, for each region `R2` live in `Q` + // where `R1 <= R2` in P, we add `R2 requires B` + // to `Q`. + let requires1 = dead_region_requires + .map(|(r1, b, p, q)| ((r1, p, q), b)) + .join(&dead_can_reach_live) + .map(|((_r1, _p, q), b, r2)| (r2, b, q)); // requires(R, B, Q) :- // requires(R, B, P), From 0a9b95d69b2298c49b6e306d9136f3cbb6feade9 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 12 May 2018 12:06:01 -0400 Subject: [PATCH 11/14] eliminate a few maps --- src/output/timely_opt.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index a83c7ae2513..0e803692e61 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -177,6 +177,12 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // This is effectively the transitive subset // relation, but we try to limit it to regions // that are dying on the edge P -> Q. + // + // NB. As a micro-optimization, `dead_can_reach` + // is stored as `dead_can_reach((R2, Q), (R1, + // P))`, since that happens to be the ordering + // that is required for the joins it participates + // in. let dead_can_reach = { // dead_can_reach(R1, R2, P, Q) :- // dead_can_reach_origins(R1, P, Q), @@ -184,7 +190,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let dead_can_reach0 = { dead_can_reach_origins .join(&subset.map(|(r1, r2, p)| ((r1, p), r2))) - .map(|((r1, p), q, r2)| (r1, r2, p, q)) + .map(|((r1, p), q, r2)| ((r2, q), (r1, p))) }; let dead_can_reach = Variable::from(dead_can_reach0.clone()); @@ -199,11 +205,10 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // "intermediate" region R2 is dead at Q. let dead_can_reach1 = { dead_can_reach - .map(|(r1, r2, p, q)| ((r2, q), (r1, p))) .antijoin(®ion_live_at) .map(|((r2, q), (r1, p))| ((r2, p), (r1, q))) .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) - .map(|((_r2, p), (r1, q), r3)| (r1, r3, p, q)) + .map(|((_r2, p), (r1, q), r3)| ((r3, q), (r1, p))) }; dead_can_reach @@ -218,8 +223,13 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // subset of the full `dead_can_reach` relation // where we filter down to those cases where R2 is // live in Q. + // + // NB. As a micro-optimization, + // `dead_can_reach_live` is stored as `((r1, p, + // q), r2)` since that happens to be the ordering + // required for the joins that it participates in. let dead_can_reach_live = { - dead_can_reach.map(|(r1, r2, p, q)| ((r2, q), (r1, p))) + dead_can_reach .semijoin(®ion_live_at) .map(|((r2, q), (r1, p))| ((r1, p, q), r2)) }; From 17e3760a508ae1f26a8eff931f508ce3d5549718 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 12 May 2018 12:45:51 -0400 Subject: [PATCH 12/14] adopt `join_map` --- src/output/timely_opt.rs | 43 +++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index 0e803692e61..3c7c3742f21 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -121,8 +121,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let live_to_dead_regions = { subset .map(|(r1, r2, p)| (p, (r1, r2))) - .join(&cfg_edge) - .map(|(p, (r1, r2), q)| ((r1, q), (r2, p))) + .join_map(&cfg_edge, |&p, &(r1, r2), &q| ((r1, q), (r2, p))) .semijoin(®ion_live_at) .map(|((r1, q), (r2, p))| ((r2, q), (r1, p))) .antijoin(®ion_live_at) @@ -144,8 +143,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|(r, b, p)| ((b, p), r)) .antijoin(&killed) .map(|((b, p), r)| (p, (r, b))) - .join(&cfg_edge) - .map(|(p, (r, b), q)| ((r, q), (b, p))) + .join_map(&cfg_edge, |&p, &(r, b), &q| ((r, q), (b, p))) .antijoin(®ion_live_at) .map(|((r, q), (b, p))| (r, b, p, q)) }; @@ -156,14 +154,10 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // in computing the transitive closure of things they // can reach. let dead_can_reach_origins = { - let dead_can_reach_origins0 = { - live_to_dead_regions - .map(|(_r1, r2, p, q)| ((r2, p), q)) - }; - let dead_can_reach_origins1 = { - dead_region_requires - .map(|(r, _b, p, q)| ((r, p), q)) - }; + let dead_can_reach_origins0 = + { live_to_dead_regions.map(|(_r1, r2, p, q)| ((r2, p), q)) }; + let dead_can_reach_origins1 = + { dead_region_requires.map(|(r, _b, p, q)| ((r, p), q)) }; dead_can_reach_origins0 .concat(&dead_can_reach_origins1) .distinct_total() @@ -188,9 +182,10 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // dead_can_reach_origins(R1, P, Q), // subset(R1, R2, P). let dead_can_reach0 = { - dead_can_reach_origins - .join(&subset.map(|(r1, r2, p)| ((r1, p), r2))) - .map(|((r1, p), q, r2)| ((r2, q), (r1, p))) + dead_can_reach_origins.join_map( + &subset.map(|(r1, r2, p)| ((r1, p), r2)), + |&(r1, p), &q, &r2| ((r2, q), (r1, p)), + ) }; let dead_can_reach = Variable::from(dead_can_reach0.clone()); @@ -207,8 +202,10 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { dead_can_reach .antijoin(®ion_live_at) .map(|((r2, q), (r1, p))| ((r2, p), (r1, q))) - .join(&subset.map(|(r2, r3, p)| ((r2, p), r3))) - .map(|((_r2, p), (r1, q), r3)| ((r3, q), (r1, p))) + .join_map( + &subset.map(|(r2, r3, p)| ((r2, p), r3)), + |&(_r2, p), &(r1, q), &r3| ((r3, q), (r1, p)), + ) }; dead_can_reach @@ -244,8 +241,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // `R2` are live in Q. let subset1 = subset .map(|(r1, r2, p)| (p, (r1, r2))) - .join(&cfg_edge) - .map(|(_p, (r1, r2), q)| ((r1, q), r2)) + .join_map(&cfg_edge, |_p, &(r1, r2), &q| ((r1, q), r2)) .semijoin(®ion_live_at) .map(|((r1, q), r2)| ((r2, q), r1)) .semijoin(®ion_live_at) @@ -257,8 +253,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let subset2 = { live_to_dead_regions .map(|(r1, r2, p, q)| ((r2, p, q), r1)) - .join(&dead_can_reach_live) - .map(|((_r2, _p, q), r1, r3)| (r1, r3, q)) + .join_map(&dead_can_reach_live, |&(_r2, _p, q), &r1, &r3| (r1, r3, q)) }; // requires(R2, B, Q) :- @@ -272,8 +267,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // to `Q`. let requires1 = dead_region_requires .map(|(r1, b, p, q)| ((r1, p, q), b)) - .join(&dead_can_reach_live) - .map(|((_r1, _p, q), b, r2)| (r2, b, q)); + .join_map(&dead_can_reach_live, |&(_r1, _p, q), &b, &r2| (r2, b, q)); // requires(R, B, Q) :- // requires(R, B, P), @@ -284,8 +278,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|(r, b, p)| ((b, p), r)) .antijoin(&killed) .map(|((b, p), r)| (p, (r, b))) - .join(&cfg_edge) - .map(|(_p, (r, b), q)| ((r, q), b)) + .join_map(&cfg_edge, |&_p, &(r, b), &q| ((r, q), b)) .semijoin(®ion_live_at) .map(|((r, q), b)| (r, b, q)); From 06cc8707ca0e4eee9ff486369ab79aec39acc1b7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 14 May 2018 09:48:28 -0400 Subject: [PATCH 13/14] use arrangements and join-core in many cases I am still not using them for `antijoin` because that's a bit more complicated for me to think about. Also, I think we should write some nicer wrapper traits, like `join` and `semijoin`, to make this more readable. Still, results in a net speed win: down to 14.2s. --- src/output/timely_opt.rs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index 3c7c3742f21..4c99912360d 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -13,6 +13,7 @@ use crate::facts::{AllFacts, Point}; use crate::output::Output; use differential_dataflow::collection::Collection; +use differential_dataflow::operators::arrange::{ArrangeByKey, ArrangeBySelf}; use differential_dataflow::operators::iterate::Variable; use differential_dataflow::operators::*; use std::collections::{BTreeMap, BTreeSet}; @@ -80,10 +81,14 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { ) = ..my_facts; } + let cfg_edge = cfg_edge.arrange_by_key(); + let region_live_at_by_self = region_live_at.arrange_by_self(); + let (subset, requires) = scope.scoped(|subscope| { let outlives = outlives.enter(&subscope); let cfg_edge = cfg_edge.enter(&subscope); let region_live_at = region_live_at.enter(&subscope); + let region_live_at_by_self = region_live_at_by_self.enter(&subscope); let borrow_region = borrow_region.enter(&subscope); let killed = killed.enter(&subscope); @@ -121,8 +126,8 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let live_to_dead_regions = { subset .map(|(r1, r2, p)| (p, (r1, r2))) - .join_map(&cfg_edge, |&p, &(r1, r2), &q| ((r1, q), (r2, p))) - .semijoin(®ion_live_at) + .join_core(&cfg_edge, |&p, &(r1, r2), &q| Some(((r1, q), (r2, p)))) + .join_core(®ion_live_at_by_self, |&k, &v, _| Some((k, v))) .map(|((r1, q), (r2, p))| ((r2, q), (r1, p))) .antijoin(®ion_live_at) .map(|((r2, q), (r1, p))| (r1, r2, p, q)) @@ -143,7 +148,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|(r, b, p)| ((b, p), r)) .antijoin(&killed) .map(|((b, p), r)| (p, (r, b))) - .join_map(&cfg_edge, |&p, &(r, b), &q| ((r, q), (b, p))) + .join_core(&cfg_edge, |&p, &(r, b), &q| Some(((r, q), (b, p)))) .antijoin(®ion_live_at) .map(|((r, q), (b, p))| (r, b, p, q)) }; @@ -227,8 +232,9 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // required for the joins that it participates in. let dead_can_reach_live = { dead_can_reach - .semijoin(®ion_live_at) + .join_core(®ion_live_at_by_self, |&k, &v, _| Some((k, v))) .map(|((r2, q), (r1, p))| ((r1, p, q), r2)) + .arrange_by_key() }; // subset(R1, R2, Q) :- @@ -241,10 +247,10 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // `R2` are live in Q. let subset1 = subset .map(|(r1, r2, p)| (p, (r1, r2))) - .join_map(&cfg_edge, |_p, &(r1, r2), &q| ((r1, q), r2)) - .semijoin(®ion_live_at) + .join_core(&cfg_edge, |_p, &(r1, r2), &q| Some(((r1, q), r2))) + .join_core(®ion_live_at_by_self, |&k, &v, _| Some((k, v))) .map(|((r1, q), r2)| ((r2, q), r1)) - .semijoin(®ion_live_at) + .join_core(®ion_live_at_by_self, |&k, &v, _| Some((k, v))) .map(|((r2, q), r1)| (r1, r2, q)); // subset(R1, R3, Q) :- @@ -253,7 +259,9 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { let subset2 = { live_to_dead_regions .map(|(r1, r2, p, q)| ((r2, p, q), r1)) - .join_map(&dead_can_reach_live, |&(_r2, _p, q), &r1, &r3| (r1, r3, q)) + .join_core(&dead_can_reach_live, |&(_r2, _p, q), &r1, &r3| { + Some((r1, r3, q)) + }) }; // requires(R2, B, Q) :- @@ -267,7 +275,9 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // to `Q`. let requires1 = dead_region_requires .map(|(r1, b, p, q)| ((r1, p, q), b)) - .join_map(&dead_can_reach_live, |&(_r1, _p, q), &b, &r2| (r2, b, q)); + .join_core(&dead_can_reach_live, |&(_r1, _p, q), &b, &r2| { + Some((r2, b, q)) + }); // requires(R, B, Q) :- // requires(R, B, P), @@ -278,8 +288,8 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { .map(|(r, b, p)| ((b, p), r)) .antijoin(&killed) .map(|((b, p), r)| (p, (r, b))) - .join_map(&cfg_edge, |&_p, &(r, b), &q| ((r, q), b)) - .semijoin(®ion_live_at) + .join_core(&cfg_edge, |&_p, &(r, b), &q| Some(((r, q), b))) + .join_core(®ion_live_at_by_self, |&k, &v, _| Some((k, v))) .map(|((r, q), b)| (r, b, q)); let requires = requires.set(&requires0 @@ -299,7 +309,7 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // borrow_live_at(B, P) :- requires(R, B, P), region_live_at(R, P) let borrow_live_at1 = requires .map(|(r, b, p)| ((r, p), b)) - .semijoin(®ion_live_at) + .join_core(®ion_live_at_by_self, |&k, &v, _| Some((k, v))) .map(|((_r, p), b)| (b, p)); borrow_live_at1.distinct_total() From 8effcd19894af21dd7561535e3e7c3d926cab51f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 14 May 2018 10:04:06 -0400 Subject: [PATCH 14/14] make variables start out empty --- src/output/timely_opt.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/output/timely_opt.rs b/src/output/timely_opt.rs index 4c99912360d..2000a6660ef 100644 --- a/src/output/timely_opt.rs +++ b/src/output/timely_opt.rs @@ -10,7 +10,7 @@ //! Timely dataflow runs on its own thread. -use crate::facts::{AllFacts, Point}; +use crate::facts::{AllFacts, Loan, Point, Region}; use crate::output::Output; use differential_dataflow::collection::Collection; use differential_dataflow::operators::arrange::{ArrangeByKey, ArrangeBySelf}; @@ -97,15 +97,23 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // At the point P, R1 <= R2. // // subset(R1, R2, P) :- outlives(R1, R2, P). + let subset = Variable::from(Collection::new( + None::<((Region, Region, Point), _, _)> + .into_iter() + .to_stream(subscope), + )); let subset0 = outlives.clone(); - let subset = Variable::from(subset0.clone()); // .decl requires(R, B, P) -- at the point, things with region R // may depend on data from borrow B // // requires(R, B, P) :- borrow_region(R, B, P). + let requires = Variable::from(Collection::new( + None::<((Region, Loan, Point), _, _)> + .into_iter() + .to_stream(subscope), + )); let requires0 = borrow_region.clone(); - let requires = Variable::from(requires0.clone()); // .decl live_to_dead_regions(R1, R2, P, Q) // @@ -183,6 +191,12 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { // that is required for the joins it participates // in. let dead_can_reach = { + let dead_can_reach = Variable::from(Collection::new( + None::<(((Region, Point), (Region, Point)), _, _)> + .into_iter() + .to_stream(subscope), + )); + // dead_can_reach(R1, R2, P, Q) :- // dead_can_reach_origins(R1, P, Q), // subset(R1, R2, P). @@ -193,8 +207,6 @@ pub(super) fn compute(dump_enabled: bool, mut all_facts: AllFacts) -> Output { ) }; - let dead_can_reach = Variable::from(dead_can_reach0.clone()); - // dead_can_reach(R1, R3, P, Q) :- // dead_can_reach(R1, R2, P, Q), // !region_live_at(R2, Q),