Get parallel loading working

This commit is contained in:
Richard Feldman 2019-11-30 17:33:09 -05:00
parent 997b6ec4ad
commit 787d76b36a
3 changed files with 307 additions and 177 deletions

View file

@ -13,6 +13,7 @@ use self::problem::RuntimeError::*;
use self::procedure::References; use self::procedure::References;
use self::scope::Scope; use self::scope::Scope;
use self::symbol::Symbol; use self::symbol::Symbol;
use crate::can::pattern::PatternType;
use crate::collections::{ImMap, ImSet, MutMap, MutSet}; use crate::collections::{ImMap, ImSet, MutMap, MutSet};
use crate::constrain::{self, exists}; use crate::constrain::{self, exists};
use crate::graph::{strongly_connected_component, topological_sort}; use crate::graph::{strongly_connected_component, topological_sort};
@ -26,10 +27,12 @@ use crate::types::Expected::{self, *};
use crate::types::Type::{self, *}; use crate::types::Type::{self, *};
use crate::types::{LetConstraint, PExpected, PReason, Reason}; use crate::types::{LetConstraint, PExpected, PReason, Reason};
use bumpalo::Bump; use bumpalo::Bump;
use im::Vector;
use std::fmt::Debug; use std::fmt::Debug;
pub mod env; pub mod env;
pub mod expr; pub mod expr;
pub mod module;
pub mod num; pub mod num;
pub mod operator; pub mod operator;
pub mod pattern; pub mod pattern;
@ -44,6 +47,117 @@ pub mod symbol;
/// map so that expressions within that annotation can share these vars. /// map so that expressions within that annotation can share these vars.
type Rigids = ImMap<Box<str>, Type>; type Rigids = ImMap<Box<str>, Type>;
pub fn canonicalize_module_defs<'a>(
arena: &Bump,
loc_defs: bumpalo::collections::Vec<'a, Located<Def<'a>>>,
home: Box<str>,
scope: &mut ImMap<Box<str>, (Symbol, Region)>,
) -> Vector<(Located<Pattern>, Located<Expr>)> {
// TODO FIXME need to remove Subs from this - distribute Variables but don't make Subs yet!
let mut subs = Subs::new();
let mut buf = Vector::new();
for loc_def in loc_defs {
buf.push_back(canonicalize_def(
arena,
loc_def.value,
loc_def.region,
home.clone(),
scope,
&mut subs,
));
}
buf
}
fn canonicalize_def<'a>(
arena: &Bump,
def: Def<'a>,
region: Region,
home: Box<str>,
scope: &mut ImMap<Box<str>, (Symbol, Region)>,
// TODO FIXME need to remove Subs from this - distribute Variables but don't make Subs yet!
subs: &mut Subs,
) -> (Located<Pattern>, Located<Expr>) {
match def {
Def::Annotation(_loc_pattern, _loc_ann) => {
panic!("TODO canonicalize top-level annotations");
}
Def::Body(loc_pattern, loc_expr) => {
let variable = subs.mk_flex_var();
let expected = Expected::NoExpectation(Type::Variable(variable));
let declared_idents = ImMap::default(); // TODO FIXME infer this from scope arg
let declared_variants = ImMap::default(); // TODO get rid of this
let name: Box<str> = "TODOfixme".into();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar(arena, &loc_expr);
// If we're canonicalizing the declaration `foo = ...` inside the `Main` module,
// scope_prefix will be "Main.foo$" and its first closure will be named "Main.foo$0"
let scope_prefix = format!("{}.{}$", home, name).into();
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
let mut env = Env::new(home, declared_variants.clone());
let (loc_expr, _) = canonicalize_expr(
&ImMap::default(),
&mut env,
subs,
&mut scope,
region,
&loc_expr.value,
expected,
);
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
// (However, still include it in scope, because you *can* recursively refer to yourself.)
let mut shadowable_idents = scope.idents.clone();
remove_idents(&loc_pattern.value, &mut shadowable_idents);
let pattern_var = subs.mk_flex_var();
let pattern_type = Type::Variable(pattern_var);
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
let mut pattern_state = PatternState {
headers: ImMap::default(),
vars: Vec::with_capacity(1),
constraints: Vec::with_capacity(1),
};
let loc_pattern = canonicalize_pattern(
&mut env,
&mut pattern_state,
subs,
&mut scope,
PatternType::TopLevelDef,
&loc_pattern.value,
loc_pattern.region,
&mut shadowable_idents,
pattern_expected,
);
(loc_pattern, loc_expr)
}
Def::CustomType(_, _) => {
panic!("TODO remove CustomType syntax");
}
Def::TypeAlias(_, _) => {
panic!("TODO remove TypeAlias syntax");
}
// Ignore spaces
Def::SpaceBefore(def, _) | Def::SpaceAfter(def, _) => {
// TODO FIXME performance disaster!!!
canonicalize_def(arena, def.clone(), region, home, scope, subs)
}
}
}
// TODO trim down these arguments // TODO trim down these arguments
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn canonicalize_declaration<'a>( pub fn canonicalize_declaration<'a>(

View file

@ -1,25 +1,28 @@
use crate::can::symbol::Symbol; use crate::can::symbol::Symbol;
use crate::collections::{SendMap, SendSet}; use crate::can::expr::Expr;
use crate::ident::UnqualifiedIdent; use crate::can::pattern::Pattern;
use crate::can::canonicalize_module_defs;
use crate::collections::{SendSet, ImMap};
use crate::module::ModuleName; use crate::module::ModuleName;
use crate::parse::ast::{Attempting, Def, ExposesEntry, ImportsEntry, Module}; use crate::parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
use crate::parse::module; use crate::parse::module::{self, module_defs};
use crate::parse::parser::{Fail, Parser, State}; use crate::parse::parser::{Fail, Parser, State};
use crate::region::{Located, Region}; use crate::region::{Located, Region};
use im::Vector; use im::Vector;
use bumpalo::Bump; use bumpalo::Bump;
use tokio::fs::read_to_string; use tokio::fs::read_to_string;
use tokio::sync::mpsc::{self, Sender, Receiver};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tokio::prelude::*; use crate::can::module::Module;
use futures::future::join_all;
pub struct Loaded {
pub struct Loaded<'a> { pub requested_module: LoadedModule,
pub requested_header: LoadedHeader, pub deps: Deps,
pub dependent_headers: SendMap<ModuleName<'a>, LoadedHeader>,
pub defs: SendMap<ModuleName<'a>, Result<Vector<Located<Def<'a>>>, Fail>>,
} }
#[derive(Debug, Clone)]
struct Env { struct Env {
pub src_dir: PathBuf pub src_dir: PathBuf
} }
@ -29,54 +32,62 @@ pub enum BuildProblem<'a> {
FileNotFound(&'a Path), FileNotFound(&'a Path),
} }
#[derive(Debug, PartialEq, Eq)] type Deps = SendSet<Box<str>>;
pub enum LoadedHeader {
Valid { #[derive(Debug, PartialEq)]
declared_name: Option<Box<str>>, pub enum LoadedModule {
deps: SendSet<Box<str>>, Valid(Module),
scope: SendMap<Box<str>, (Symbol, Region)>,
bytes_parsed: usize
},
FileProblem(io::ErrorKind), FileProblem(io::ErrorKind),
ParsingFailed(Fail), ParsingFailed(Fail),
} }
pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded<'a> { pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded {
let handle = tokio::spawn(async move { let env = Env { src_dir: src_dir.clone() };
let mut env = Env { let (tx, mut rx): (Sender<Deps>, Receiver<Deps>) = mpsc::channel(1024);
src_dir
};
load_filename(&mut env, &filename).await let main_tx = tx.clone();
let handle = tokio::spawn(async move {
load_filename(&env, &filename, main_tx).await
}); });
let requested_header = handle.await; let requested_module = handle.await.expect("Unable to load requested module.");
let mut other_modules = Vec::new();
let mut all_deps = SendSet::default();
panic!("TODO"); // Get a fresh env, since the previous one has been consumed
let env = Env { src_dir };
// At first, 1 module is pending (namely the `filename` one).
let mut pending = 1;
while let Some(module_deps) = rx.recv().await {
let deps_to_load = module_deps.relative_complement(all_deps.clone());
// // TODO parse defs on a different thread, and parse them // We just loaded 1 module, and gained deps_to_load more
// // directly into a Vector rather than into a Vec, so pending = pending + deps_to_load.len() - 1;
// // we don't have to reallocate here.
// let defs = match module::module_defs().parse(&arena, state) {
// Ok((defs, _)) => {
// let mut send_vec = Vector::new();
// for def in defs { // Record that these are loaded *before* spawning threads to load them.
// send_vec.push_back(def); all_deps = all_deps.union(deps_to_load.clone());
// }
// Ok(send_vec) let loaded_modules = join_all(deps_to_load.into_iter().map(|dep|{
// }, let env = env.clone();
// Err((fail, _)) => Err(fail), let tx = tx.clone();
// };
// Loaded { tokio::spawn(async move {
// requested_header, load_module(&env, dep, tx).await
// dependent_headers: env.loaded_headers, })
// defs, })).await;
// problems: env.problems,
// } for module in loaded_modules {
other_modules.push(module.expect("Unable to load dependent module"));
}
// Once we've run out of pending modules to process, we're done!
if pending == 0 {
break;
}
}
Loaded { requested_module, deps: all_deps }
} }
/// The long-term plan is for the loading process to work like this, starting from main.roc: /// The long-term plan is for the loading process to work like this, starting from main.roc:
@ -124,31 +135,31 @@ pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded<'a> {
/// module's canonicalization. /// module's canonicalization.
/// ///
/// If a given import has not been loaded yet, load it too. /// If a given import has not been loaded yet, load it too.
// fn load_module<'a, 'p>(env: &mut Env<'a, 'p>, module_name: &ModuleName<'a>) -> LoadedHeader<'a> { async fn load_module(env: &Env, module_name: Box<str>, tx: Sender<Deps>) -> LoadedModule {
// // 1. Convert module_name to filename, using src_dir. // 1. Convert module_name to filename, using src_dir.
// // 2. Open that file for reading. (If there's a problem, record it and bail.) // 2. Open that file for reading. (If there's a problem, record it and bail.)
// // 3. Read the whole file into a string. (In the future, we can read just the header.) // 3. Read the whole file into a string. (In the future, we can read just the header.)
// // 4. Parse the header. // 4. Parse the header.
// // 5. Use the parsed header to load more modules as necessary. // 5. Use the parsed header to load more modules as necessary.
// // 6. Now that all the headers have been parsed, parse the bodies too. // 6. Now that all the headers have been parsed, parse the bodies too.
// // 7. Once all the bodies have been parsed, canonicalize beginning with the leaves. // 7. Once all the bodies have been parsed, canonicalize beginning with the leaves.
// let mut filename = PathBuf::new(); let mut filename = PathBuf::new();
// filename.push(env.src_dir); filename.push(env.src_dir.clone());
// // Convert dots in module name to directories // Convert dots in module name to directories
// for part in module_name.as_str().split('.') { for part in module_name.split('.') {
// filename.push(part); filename.push(part);
// } }
// // End with .roc // End with .roc
// filename.set_extension("roc"); filename.set_extension("roc");
// load_filename(env, &filename) load_filename(env, &filename, tx).await
// } }
async fn load_filename(env: &mut Env, filename: &Path) -> LoadedHeader { async fn load_filename(env: &Env, filename: &Path, tx: Sender<Deps>) -> LoadedModule {
match read_to_string(filename).await { match read_to_string(filename).await {
Ok(src) => { Ok(src) => {
let arena = Bump::new(); let arena = Bump::new();
@ -161,49 +172,74 @@ async fn load_filename(env: &mut Env, filename: &Path) -> LoadedHeader {
let state = State::new(&src, Attempting::Module); let state = State::new(&src, Attempting::Module);
let answer = match module::module().parse(&arena, state) { let answer = match module::module().parse(&arena, state) {
Ok((Module::Interface { header }, state)) => { Ok((ast::Module::Interface { header }, state)) => {
let declared_name = Some(header.name.value.as_str().into()); let declared_name: Box<str> = header.name.value.as_str().into();
let mut scope = SendMap::default(); // TODO check to see if declared_name is consistent with filename.
// If it isn't, report a problem!
let mut scope = ImMap::default();
let mut deps = SendSet::default(); let mut deps = SendSet::default();
for loc_entry in header.imports { for loc_entry in header.imports {
deps.insert(load_import(env, loc_entry.region, &loc_entry.value, &mut scope)); deps.insert(load_import(env, loc_entry.region, &loc_entry.value, &mut scope));
} }
let bytes_parsed = state.bytes_consumed(); tokio::spawn(async move {
let mut tx = tx;
LoadedHeader::Valid { scope, declared_name, deps, bytes_parsed } // Send the deps to the main thread for processing,
// then continue on to parsing and canonicalizing defs.
tx.send(deps).await.unwrap();
});
let defs = parse_and_canonicalize_defs(&arena, state, declared_name.clone(), &mut scope);
let module = Module { name: Some(declared_name), defs };
LoadedModule::Valid(module)
} }
Ok((Module::App { header }, state)) => { Ok((ast::Module::App { header }, state)) => {
// The app module has no declared name. let mut scope = ImMap::default();
let declared_name = None;
let mut scope = SendMap::default();
let mut deps = SendSet::default(); let mut deps = SendSet::default();
for loc_entry in header.imports { for loc_entry in header.imports {
deps.insert(load_import(env, loc_entry.region, &loc_entry.value, &mut scope)); deps.insert(load_import(env, loc_entry.region, &loc_entry.value, &mut scope));
} }
let bytes_parsed = state.bytes_consumed(); tokio::spawn(async move {
let mut tx = tx;
LoadedHeader::Valid { scope, declared_name, deps, bytes_parsed } // Send the deps to the main thread for processing,
// then continue on to parsing and canonicalizing defs.
tx.send(deps).await.unwrap();
});
// The app module has no declared name. Pass it as "".
let defs = parse_and_canonicalize_defs(&arena, state, "".into(), &mut scope);
let module = Module { name: None, defs };
LoadedModule::Valid(module)
} }
Err((fail, _)) => LoadedHeader::ParsingFailed(fail), Err((fail, _)) => LoadedModule::ParsingFailed(fail),
}; };
answer answer
} }
Err(err) => LoadedHeader::FileProblem(err.kind()), Err(err) => LoadedModule::FileProblem(err.kind()),
} }
} }
fn load_import<'a, 'p, 'out>( fn parse_and_canonicalize_defs(arena: &Bump, state: State<'_>, home: Box<str>, scope: &mut ImMap<Box<str>, (Symbol, Region)>) -> Vector<(Located<Pattern>, Located<Expr>)> {
env: &mut Env, let (parsed_defs, _) = module_defs().parse(arena, state).expect("TODO gracefully handle parse error on module defs");
canonicalize_module_defs(arena, parsed_defs, home, scope)
}
fn load_import(
env: &Env,
region: Region, region: Region,
entry: &ImportsEntry<'_>, entry: &ImportsEntry<'_>,
scope: &mut SendMap<Box<str>, (Symbol, Region)>, scope: &mut ImMap<Box<str>, (Symbol, Region)>,
) -> Box<str> { ) -> Box<str> {
use crate::parse::ast::ImportsEntry::*; use crate::parse::ast::ImportsEntry::*;
@ -225,7 +261,7 @@ fn load_import<'a, 'p, 'out>(
} }
} }
fn expose<'out>( fn expose(
module_name: ModuleName<'_>, module_name: ModuleName<'_>,
entry: &ExposesEntry<'_>, entry: &ExposesEntry<'_>,
region: Region, region: Region,
@ -245,30 +281,3 @@ fn expose<'out>(
} }
} }
} }
#[test]
fn test_tokio() {
test_async(async {
let handle = tokio::spawn(async {
println!("doing some work, asynchronously");
// Return a value for the example
"result of the computation"
});
// Wait for the spawned task to finish
let res = handle.await;
println!("got {:?}", res);
})
}
fn test_async<F: std::future::Future>(future: F) -> F::Output {
use tokio::runtime::Runtime;
// Create the runtime
let mut rt = Runtime::new().expect("Error initializing Tokio runtime.");
// Spawn the root task
rt.block_on(future)
}

View file

@ -12,86 +12,93 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_load { mod test_load {
use crate::helpers::{fixtures_dir, im_map_from_pairs, mut_map_from_pairs}; use crate::helpers::{fixtures_dir, im_map_from_pairs, mut_map_from_pairs};
use bumpalo::Bump; use roc::load::load;
use roc::can::symbol::Symbol; // use roc::region::Region;
use roc::ident::UnqualifiedIdent;
use roc::load::LoadedHeader::*; fn test_async<F: std::future::Future>(future: F) -> F::Output {
use roc::load::{load, LoadedHeader}; use tokio::runtime::Runtime;
use roc::module::ModuleName;
use roc::region::Region; // Create the runtime
let mut rt = Runtime::new().expect("Error initializing Tokio runtime.");
// Spawn the root task
rt.block_on(future)
}
#[test] #[test]
fn interface_with_deps() { fn interface_with_deps() {
let src_dir = fixtures_dir().join("interface_with_deps"); let src_dir = fixtures_dir().join("interface_with_deps");
let filename = src_dir.join("Primary.roc"); let filename = src_dir.join("Primary.roc");
let arena = Bump::new();
let loaded = load(&arena, &src_dir, &filename);
assert!(loaded.problems.is_empty()); test_async(async {
let loaded = load(src_dir, filename).await;
});
let dep1_scope = im_map_from_pairs(vec![( // assert!(loaded.problems.is_empty());
UnqualifiedIdent::new("foo"),
(Symbol::new("Dep3.Blah.", "foo"), Region::new(2, 2, 26, 29)),
)]);
let dep2_scope = im_map_from_pairs(vec![
(
UnqualifiedIdent::new("bar"),
(Symbol::new("Dep3.Blah.", "bar"), Region::new(2, 2, 31, 34)),
),
(
UnqualifiedIdent::new("foo"),
(Symbol::new("Dep3.Blah.", "foo"), Region::new(2, 2, 26, 29)),
),
]);
let dep3_scope = im_map_from_pairs(vec![]);
assert_eq!( // let dep1_scope = im_map_from_pairs(vec![(
loaded.dependent_headers, // UnqualifiedIdent::new("foo"),
mut_map_from_pairs(vec![ // (Symbol::new("Dep3.Blah.", "foo"), Region::new(2, 2, 26, 29)),
(ModuleName::new("Dep1"), Valid { scope: dep1_scope }), // )]);
(ModuleName::new("Dep3.Blah"), Valid { scope: dep3_scope }), // let dep2_scope = im_map_from_pairs(vec![
(ModuleName::new("Dep2"), Valid { scope: dep2_scope }), // (
]) // UnqualifiedIdent::new("bar"),
); // (Symbol::new("Dep3.Blah.", "bar"), Region::new(2, 2, 31, 34)),
// ),
// (
// UnqualifiedIdent::new("foo"),
// (Symbol::new("Dep3.Blah.", "foo"), Region::new(2, 2, 26, 29)),
// ),
// ]);
// let dep3_scope = im_map_from_pairs(vec![]);
assert_eq!(loaded.defs.len(), 4); // assert_eq!(
// loaded.dependent_headers,
// mut_map_from_pairs(vec![
// (ModuleName::new("Dep1"), Valid { scope: dep1_scope }),
// (ModuleName::new("Dep3.Blah"), Valid { scope: dep3_scope }),
// (ModuleName::new("Dep2"), Valid { scope: dep2_scope }),
// ])
// );
let defs = loaded // assert_eq!(loaded.defs.len(), 4);
.defs
.get(&ModuleName::new("Primary"))
.expect("No defs found for `Primary` module")
.clone()
.expect("Defs failed to parse for `Primary` module");
assert_eq!( // let defs = loaded
dbg!(/* problem: module_defs() only parses 1 module - TODO add parsing unit test for it!*/ defs) // .defs
.len(), // .get(&ModuleName::new("Primary"))
6 // .expect("No defs found for `Primary` module")
); // .clone()
// .expect("Defs failed to parse for `Primary` module");
match loaded.requested_header { // assert_eq!(
LoadedHeader::Valid { scope } => assert_eq!( // dbg!(/* problem: module_defs() only parses 1 module - TODO add parsing unit test for it!*/ defs)
scope, // .len(),
im_map_from_pairs(vec![ // 6
( // );
UnqualifiedIdent::new("bar"),
(Symbol::new("Dep3.Blah.", "bar"), Region::new(2, 2, 51, 54)),
),
(
UnqualifiedIdent::new("foo"),
(Symbol::new("Dep2.", "foo"), Region::new(2, 2, 32, 35)),
),
(
UnqualifiedIdent::new("two"),
(Symbol::new("Dep2.", "two"), Region::new(2, 2, 27, 30)),
),
])
),
other => panic!( // match loaded.requested_header {
"app_header should have been Valid, but instead was: {:?}", // LoadedHeader::Valid { scope } => assert_eq!(
other // scope,
), // im_map_from_pairs(vec![
}; // (
// UnqualifiedIdent::new("bar"),
// (Symbol::new("Dep3.Blah.", "bar"), Region::new(2, 2, 51, 54)),
// ),
// (
// UnqualifiedIdent::new("foo"),
// (Symbol::new("Dep2.", "foo"), Region::new(2, 2, 32, 35)),
// ),
// (
// UnqualifiedIdent::new("two"),
// (Symbol::new("Dep2.", "two"), Region::new(2, 2, 27, 30)),
// ),
// ])
// ),
// other => panic!(
// "app_header should have been Valid, but instead was: {:?}",
// other
// ),
// };
} }
} }