From 8fbfae5e620bf6fc6e6912f511a541307fe06745 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 2 May 2020 13:31:05 -0400 Subject: [PATCH 1/4] Expand some docs --- compiler/load/src/file.rs | 25 +++++++++++++++++++++++++ compiler/mono/src/expr.rs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 659bb90e04..f9326eb033 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -144,6 +144,31 @@ type MsgReceiver = mpsc::Receiver; /// The loaded_modules argument specifies which modules have already been loaded. /// It typically contains *at least* the standard modules, but is empty when loading /// the standard modules themselves. +/// +/// If we're just type-checking everything (e.g. running `roc check` at the command line), +/// we can stop there. However, if we're generating code, then there are additional steps. +/// +/// 10. After reporting the completed type annotation, we have all the information necessary +/// to monomorphize. However, since we want to monomorphize in parallel without +/// duplicating work, we do monomorphization in two steps. First, we go through and +/// determine all the specializations this module *wants*. We compute the hashes +/// and report them to the coordinator thread, along with the mono::expr::Expr values of +/// the current function's body. At this point, we have not yet begun to assemble Procs; +/// all we've done is send a list of requetsted specializations to the coordinator. +/// 11. The coordinator works through the specialization requests in parallel, adding them +/// to a global map once they're finished. Performing one specialization may result +/// in requests for others; these are added to the queue and worked through as normal. +/// This process continues until *both* all modules have reported that they've finished +/// adding specialization requests to the queue, *and* the queue is empty (including +/// of any requestss that were added in the course of completing other requests). Now +/// we have a map of specializations, and everything was assembled in parallel with +/// no unique specialization ever getting assembled twice (meanaing no wasted effort). +/// 12. Now that we have our final map of specializations, we can proceed to code gen! +/// As long as the specializations are stored in a per-ModuleId map, we can also +/// parallelize this code gen. (e.g. in dev builds, building separate LLVM modules +/// and then linking them together, and possibly caching them by the hash of their +/// specializations, so if none of their specializations changed, we don't even need +/// to rebuild the module and can link in the cached one directly.) #[allow(clippy::cognitive_complexity)] pub async fn load<'a>( stdlib: &StdLib, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 6d4f583512..d442ff1fb3 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -1350,7 +1350,7 @@ fn call_by_name<'a>( Some(specialization) => { opt_specialize_body = None; - // a specialization with this type hash already exists, use its symbol + // a specialization with this type hash already exists, so use its symbol specialization.0 } None => { From 3d8076dfe8ae0f4f640ff206617357943759a679 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 5 May 2020 20:35:55 -0400 Subject: [PATCH 2/4] Trim trailing spaces --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8a8fddfda..705011fd75 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Every Roc application is built on top of exactly one Roc platform. There is no s The core Roc language and standard library include no I/O operations, which gives platform authors complete control over which effects they want to support. Some of the implications of this include: * A high-performance build tool (or text editor) written in Rust can be a Roc platform with a strong plugin security model. For example, it could expose only operations allowing plugin authors to modify the contents of certain files, rather than allowing plugins arbitrary read/write access to the entire filesystem. -* A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist). +* A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist). * A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction. Each Roc platform gets its own separate package repository, with packages built on top of the API that platform exposes. This means each platform has its own ecosystem where everything is built on top of the same shared set of platform-specific primitives. From ca0b6fde314b6bbd4a6b072a356619e3b8f36118 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 8 May 2020 22:26:45 -0400 Subject: [PATCH 3/4] Drop unused mono test helper --- compiler/mono/tests/helpers/mod.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/compiler/mono/tests/helpers/mod.rs b/compiler/mono/tests/helpers/mod.rs index c2153d683d..d4a6d3b998 100644 --- a/compiler/mono/tests/helpers/mod.rs +++ b/compiler/mono/tests/helpers/mod.rs @@ -51,26 +51,6 @@ pub fn infer_expr( #[allow(dead_code)] const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; -/// Without this, some tests pass in `cargo test --release` but fail without -/// the --release flag because they run out of stack space. This increases -/// stack size for debug builds only, while leaving the stack space at the default -/// amount for release builds. -#[allow(dead_code)] -#[cfg(debug_assertions)] -pub fn with_larger_debug_stack(run_test: F) -where - F: FnOnce() -> (), - F: Send, - F: 'static, -{ - std::thread::Builder::new() - .stack_size(EXPANDED_STACK_SIZE) - .spawn(run_test) - .expect("Error while spawning expanded dev stack size thread") - .join() - .expect("Error while joining expanded dev stack size thread") -} - /// In --release builds, don't increase the stack size. Run the test normally. /// This way, we find out if any of our tests are blowing the stack even after /// optimizations in release builds. From 6a79a75648e8b28285df5a924103a709a420ed8b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 9 May 2020 12:56:55 -0400 Subject: [PATCH 4/4] Don't regenerate anonymous function symbols --- cli/src/main.rs | 4 +- compiler/mono/src/expr.rs | 118 ++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index c864031e5c..3a4635336c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -408,9 +408,9 @@ fn gen( Closure(annotation, _, _, loc_args, boxed_body) => { let (loc_body, ret_var) = *boxed_body; - procs.insert_closure( + procs.insert_named( &mut mono_env, - Some(symbol), + symbol, annotation, loc_args, loc_body, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d442ff1fb3..25f209f17f 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -35,72 +35,56 @@ pub struct Procs<'a> { } impl<'a> Procs<'a> { - fn insert_user_defined(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) { - self.user_defined.insert(symbol, partial_proc); - } - - fn insert_anonymous(&mut self, symbol: Symbol, proc: Option>) { - self.anonymous.insert(symbol, proc); - } - - pub fn insert_closure( + pub fn insert_named( &mut self, env: &mut Env<'a, '_>, - name: Option, + name: Symbol, annotation: Variable, loc_args: std::vec::Vec<(Variable, Located)>, loc_body: Located, ret_var: Variable, - ) -> Symbol { - // turn record/tag patterns into a when expression, e.g. - // - // foo = \{ x } -> body - // - // becomes - // - // foo = \r -> when r is { x } -> body - // - // conversion of one-pattern when expressions will do the most optimal thing + ) { + let (_, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); + // a named closure + self.user_defined.insert( + name, + PartialProc { + annotation, + patterns: arg_symbols, + body: body.value, + }, + ); + } + + pub fn insert_anonymous( + &mut self, + env: &mut Env<'a, '_>, + symbol: Symbol, + annotation: Variable, + loc_args: std::vec::Vec<(Variable, Located)>, + loc_body: Located, + ret_var: Variable, + ) { let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); - match name { - Some(symbol) => { - // a named closure - self.insert_user_defined( - symbol, - PartialProc { - annotation, - patterns: arg_symbols, - body: body.value, - }, - ); + // an anonymous closure. These will always be specialized already + // by the surrounding context - symbol - } - None => { - // an anonymous closure. These will always be specialized already - // by the surrounding context - let symbol = env.unique_symbol(); + let opt_proc = specialize_proc_body( + env, + self, + annotation, + ret_var, + symbol, + &arg_vars, + &arg_symbols, + annotation, + body.value, + ) + .ok(); - let opt_proc = specialize_proc_body( - env, - self, - annotation, - ret_var, - symbol, - &arg_vars, - &arg_symbols, - annotation, - body.value, - ) - .ok(); - - self.insert_anonymous(symbol, opt_proc); - - symbol - } - } + self.anonymous.insert(symbol, opt_proc); } fn insert_specialization( @@ -331,6 +315,15 @@ fn num_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { } } +/// turn record/tag patterns into a when expression, e.g. +/// +/// foo = \{ x } -> body +/// +/// becomes +/// +/// foo = \r -> when r is { x } -> body +/// +/// conversion of one-pattern when expressions will do the most optimal thing fn patterns_to_when<'a>( env: &mut Env<'a, '_>, patterns: std::vec::Vec<(Variable, Located)>, @@ -482,9 +475,20 @@ fn from_can<'a>( LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, procs), LetNonRec(def, ret_expr, _, _) => from_can_defs(env, vec![*def], *ret_expr, procs), - Closure(annotation, _, _, loc_args, boxed_body) => { + Closure(ann, original_name, _, loc_args, boxed_body) => { let (loc_body, ret_var) = *boxed_body; - let symbol = procs.insert_closure(env, name, annotation, loc_args, loc_body, ret_var); + let symbol = match name { + Some(symbol) => { + procs.insert_named(env, symbol, ann, loc_args, loc_body, ret_var); + + symbol + } + None => { + procs.insert_anonymous(env, original_name, ann, loc_args, loc_body, ret_var); + + original_name + } + }; Expr::FunctionPointer(symbol) }