From 79b65347fa8327ea0b536f62e1cc6984c969d6e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Mar 2022 23:13:56 +0000 Subject: [PATCH 01/10] repl_www: Improved UI for multiline input --- repl_www/public/repl.css | 2 -- repl_www/public/repl.js | 8 +++--- repl_www/public/repl/index.html | 48 ++++++++++++++++++--------------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/repl_www/public/repl.css b/repl_www/public/repl.css index f39bccbff6..554921b009 100644 --- a/repl_www/public/repl.css +++ b/repl_www/public/repl.css @@ -64,9 +64,7 @@ section.source { display: flex; flex-direction: column; } - section.source textarea { - height: 32px; padding: 8px; margin-bottom: 16px; } diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 008a08c8e7..7411cc1705 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -82,7 +82,9 @@ function onInputKeyup(event) { break; case ENTER: - onInputChange({ target: repl.elemSourceInput }); + if (!event.shiftKey) { + onInputChange({ target: repl.elemSourceInput }); + } break; default: @@ -168,8 +170,8 @@ function createHistoryEntry(inputText) { const historyIndex = repl.inputHistory.length; repl.inputHistory.push(inputText); - const inputElem = document.createElement("div"); - inputElem.textContent = "> " + inputText; + const inputElem = document.createElement("pre"); + inputElem.textContent = inputText; inputElem.classList.add("input"); const historyItem = document.createElement("div"); diff --git a/repl_www/public/repl/index.html b/repl_www/public/repl/index.html index 5bc4ac7e76..e294e24c28 100644 --- a/repl_www/public/repl/index.html +++ b/repl_www/public/repl/index.html @@ -1,29 +1,33 @@ + + Roc REPL + + - - Roc REPL - - + +
+
+

The rockin' Roc REPL

+
- -
-
-

The rockin' Roc REPL

-
- -
-
-
-
-
- -
- -
-
- - +
+
+
+
+
+
+ +
+
+ + From f8e23d93cc309daba0edf8e3f134f5c000b05af3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Mar 2022 08:14:50 +0000 Subject: [PATCH 02/10] repl_www: nicer line spacing --- repl_www/public/repl.css | 14 +++++++++----- repl_www/public/repl.js | 3 ++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/repl_www/public/repl.css b/repl_www/public/repl.css index 554921b009..2c39fc8a3d 100644 --- a/repl_www/public/repl.css +++ b/repl_www/public/repl.css @@ -43,16 +43,20 @@ section.history { margin: 16px 0; padding: 8px; } -#history-text .input { +.history-item { + margin-bottom: 24px; +} +.history-item .input { + margin: 0; margin-bottom: 8px; } -#history-text .output { - margin-bottom: 16px; +.history-item .output { + margin: 0; } -#history-text .output-ok { +.history-item .output-ok { color: #0f8; } -#history-text .output-error { +.history-item .output-error { color: #f00; } .code { diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 7411cc1705..4ad0323e15 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -176,6 +176,7 @@ function createHistoryEntry(inputText) { const historyItem = document.createElement("div"); historyItem.appendChild(inputElem); + historyItem.classList.add("history-item"); repl.elemHistory.appendChild(historyItem); repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; @@ -184,7 +185,7 @@ function createHistoryEntry(inputText) { } function updateHistoryEntry(index, ok, outputText) { - const outputElem = document.createElement("div"); + const outputElem = document.createElement("pre"); outputElem.textContent = outputText; outputElem.classList.add("output"); outputElem.classList.add(ok ? "output-ok" : "output-error"); From 3867b2da0119b5cd8576e0afbf534cc6e81c8f75 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Mar 2022 13:12:56 +0000 Subject: [PATCH 03/10] www: Change download URL for pre-built web REPL I'm making changes more frequently to the REPL than we are to the source code. I want to be able to deploy it from a script rather than opening new GitHub issues, so I decided to just commit the assets into a branch of a GitHub repo. The repo was only used as a prototype for the web REPL so I don't use it anymore. --- www/build.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/www/build.sh b/www/build.sh index ae07359997..86db1d0134 100755 --- a/www/build.sh +++ b/www/build.sh @@ -12,13 +12,18 @@ rm -rf build/ cp -r public/ build/ pushd build + # grab the source code and copy it to Netlify's server; if it's not there, fail the build. wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip # grab the pre-compiled REPL and copy it to Netlify's server; if it's not there, fail the build. -wget https://github.com/brian-carroll/mock-repl/files/8167902/roc_repl_wasm.tar.gz -tar xzvf roc_repl_wasm.tar.gz -rm roc_repl_wasm.tar.gz +wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip +unzip deploy.zip +mv mock-repl-deploy/* . +rmdir mock-repl-deploy +rm deploy.zip + +# Copy REPL webpage source files cp -r ../../repl_www/public/* . popd From 4252f093ad97c1b65cab388c587471728a751937 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Mar 2022 13:26:49 +0000 Subject: [PATCH 04/10] Instructions about Shift+Enter --- repl_www/public/repl/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repl_www/public/repl/index.html b/repl_www/public/repl/index.html index e294e24c28..4c71ebc6ed 100644 --- a/repl_www/public/repl/index.html +++ b/repl_www/public/repl/index.html @@ -24,7 +24,7 @@ autofocus id="source-input" class="code" - placeholder="Type some Roc code and press Enter" + placeholder="Type some Roc code and press Enter. (Use Shift+Enter for multi-line input)" > From 7ad55d67e277772881d63f577aa0ee7cc0260560 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 12:57:01 +0100 Subject: [PATCH 05/10] Make type_to_variable manually tail-recursive by using a work stack and reserving variables. fun stuff --- compiler/solve/src/solve.rs | 558 ++++++++++++++++++++---------------- 1 file changed, 318 insertions(+), 240 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 7a4efb1e48..2db56dbad6 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -865,14 +865,65 @@ fn type_to_var( _: &mut MutMap, typ: &Type, ) -> Variable { - let mut arena = take_scratchpad(); + if let Type::Variable(var) = typ { + *var + } else { + let mut arena = take_scratchpad(); - let var = type_to_variable(subs, rank, pools, &arena, typ); + // let var = type_to_variable(subs, rank, pools, &arena, typ); + let var = type_to_variable(subs, rank, pools, &arena, typ); - arena.reset(); - put_scratchpad(arena); + arena.reset(); + put_scratchpad(arena); - var + var + } +} + +enum RegisterVariable { + /// Based on the Type, we already know what variable this will be + Direct(Variable), + /// This Type needs more complicated Content. We reserve a Variable + /// for it, but put a placeholder Content in subs + Deferred, +} + +impl RegisterVariable { + fn from_type( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &'_ bumpalo::Bump, + typ: &Type, + ) -> Self { + use RegisterVariable::*; + + match typ { + Variable(var) => Direct(*var), + EmptyRec => Direct(Variable::EMPTY_RECORD), + EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), + Type::Alias { symbol, .. } => { + if let Some(reserved) = Variable::get_reserved(*symbol) { + if rank.is_none() { + // reserved variables are stored with rank NONE + return Direct(reserved); + } else { + // for any other rank, we need to copy; it takes care of adjusting the rank + let copied = deep_copy_var_in(subs, rank, pools, reserved, arena); + return Direct(copied); + } + } + + Deferred + } + _ => Deferred, + } + } +} + +#[derive(Debug)] +enum TypeToVar<'a> { + Defer(&'a Type, Variable), } fn type_to_variable<'a>( @@ -884,255 +935,280 @@ fn type_to_variable<'a>( ) -> Variable { use bumpalo::collections::Vec; - match typ { - Variable(var) => *var, - RangedNumber(typ, vars) => { - let ty_var = type_to_variable(subs, rank, pools, arena, typ); - let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); - let content = Content::RangedNumber(ty_var, vars); + let mut stack = Vec::with_capacity_in(8, arena); - register(subs, rank, pools, content) - } - Apply(symbol, arguments, _) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = type_to_variable(subs, rank, pools, arena, var_index); - subs.variables[target_index] = var; - } - - let flat_type = FlatType::Apply(*symbol, new_arguments); - let content = Content::Structure(flat_type); - - register(subs, rank, pools, content) - } - EmptyRec => Variable::EMPTY_RECORD, - EmptyTagUnion => Variable::EMPTY_TAG_UNION, - - ClosureTag { name, ext } => { - let tag_name = TagName::Closure(*name); - let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1); - - subs.tag_names.push(tag_name); - - // the first VariableSubsSlice in the array is a zero-length slice - let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1)); - - let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); - - register(subs, rank, pools, content) - } - - // This case is important for the rank of boolean variables - Function(arguments, closure_type, ret_type) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = type_to_variable(subs, rank, pools, arena, var_index); - subs.variables[target_index] = var; - } - - let ret_var = type_to_variable(subs, rank, pools, arena, ret_type); - let closure_var = type_to_variable(subs, rank, pools, arena, closure_type); - let content = Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); - - register(subs, rank, pools, content) - } - Record(fields, ext) => { - // An empty fields is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyRecord in canonicalization - debug_assert!(!fields.is_empty() || !ext.is_empty_record()); - - let mut field_vars = Vec::with_capacity_in(fields.len(), arena); - - for (field, field_type) in fields { - let field_var = - field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ)); - - field_vars.push((field.clone(), field_var)); - } - - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); - - let (it, new_ext_var) = - gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) - .expect("Something ended up weird in this record type"); - - let it = it - .into_iter() - .map(|(field, field_type)| (field.clone(), field_type)); - - field_vars.extend(it); - insertion_sort_by(&mut field_vars, RecordFields::compare); - - let record_fields = RecordFields::insert_into_subs(subs, field_vars); - - let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); - - register(subs, rank, pools, content) - } - TagUnion(tags, ext) => { - // An empty tags is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - register(subs, rank, pools, content) - } - FunctionOrTagUnion(tag_name, symbol, ext) => { - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); - - let (it, ext) = roc_types::types::gather_tags_unsorted_iter( - subs, - UnionTags::default(), - temp_ext_var, - ); - - for _ in it { - unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); - } - - let slice = SubsIndex::new(subs.tag_names.len() as u32); - subs.tag_names.push(tag_name.clone()); - - let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); - - register(subs, rank, pools, content) - } - RecursiveTagUnion(rec_var, tags, ext) => { - // An empty tags is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); - let content = - Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); - - let tag_union_var = register(subs, rank, pools, content); - - register_with_known_var( - subs, - *rec_var, - rank, - pools, - Content::RecursionVar { - opt_name: None, - structure: tag_union_var, - }, - ); - - tag_union_var - } - - Type::Alias { - symbol, - type_arguments, - actual, - lambda_set_variables, - kind, - } => { - if let Some(reserved) = Variable::get_reserved(*symbol) { - if rank.is_none() { - // reserved variables are stored with rank NONE - return reserved; - } else { - // for any other rank, we need to copy; it takes care of adjusting the rank - return deep_copy_var_in(subs, rank, pools, reserved, arena); + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var } } + }}; + } - let alias_variables = alias_to_var( - subs, - rank, - pools, - arena, - type_arguments, - lambda_set_variables, - ); + let result = helper!(typ); - let alias_variable = if let Symbol::RESULT_RESULT = *symbol { - roc_result_to_var(subs, rank, pools, arena, actual) - } else { - type_to_variable(subs, rank, pools, arena, actual) - }; - let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); + while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() { + match typ { + Variable(_) | EmptyRec | EmptyTagUnion => { + unreachable!("This variant should never be deferred!") + } + RangedNumber(typ, vars) => { + let ty_var = helper!(typ); + let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); + let content = Content::RangedNumber(ty_var, vars); - register(subs, rank, pools, content) - } - HostExposedAlias { - name: symbol, - type_arguments, - actual: alias_type, - actual_var, - lambda_set_variables, - .. - } => { - let alias_variables = alias_to_var( - subs, - rank, - pools, - arena, - type_arguments, - lambda_set_variables, - ); + register_with_known_var(subs, destination, rank, pools, content) + } + Apply(symbol, arguments, _) => { + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = helper!(var_index); + subs.variables[target_index] = var; + } - let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); - // TODO(opaques): I think host-exposed aliases should always be structural - // (when does it make sense to give a host an opaque type?) - let content = Content::Alias( - *symbol, - alias_variables, - alias_variable, - AliasKind::Structural, - ); - let result = register(subs, rank, pools, content); + let flat_type = FlatType::Apply(*symbol, new_arguments); + let content = Content::Structure(flat_type); - // We only want to unify the actual_var with the alias once - // if it's already redirected (and therefore, redundant) - // don't do it again - if !subs.redundant(*actual_var) { - let descriptor = subs.get(result); - subs.union(result, *actual_var, descriptor); + register_with_known_var(subs, destination, rank, pools, content) } - result - } - Erroneous(problem) => { - let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + ClosureTag { name, ext } => { + let tag_name = TagName::Closure(*name); + let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1); - register(subs, rank, pools, content) - } - } -} + subs.tag_names.push(tag_name); -#[inline(always)] -fn alias_to_var<'a>( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'a bumpalo::Bump, - type_arguments: &[(roc_module::ident::Lowercase, Type)], - lambda_set_variables: &[roc_types::types::LambdaSet], -) -> AliasVariables { - let length = type_arguments.len() + lambda_set_variables.len(); - let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + // the first VariableSubsSlice in the array is a zero-length slice + let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1)); - for (target_index, (_, arg_type)) in (new_variables.indices()).zip(type_arguments) { - let copy_var = type_to_variable(subs, rank, pools, arena, arg_type); - subs.variables[target_index] = copy_var; + let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + // This case is important for the rank of boolean variables + Function(arguments, closure_type, ret_type) => { + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = helper!(var_index); + subs.variables[target_index] = var; + } + + let ret_var = helper!(ret_type); + let closure_var = helper!(closure_type); + let content = + Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); + + register_with_known_var(subs, destination, rank, pools, content) + } + Record(fields, ext) => { + // An empty fields is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyRecord in canonicalization + debug_assert!(!fields.is_empty() || !ext.is_empty_record()); + + let mut field_vars = Vec::with_capacity_in(fields.len(), arena); + + for (field, field_type) in fields { + let field_var = { + use roc_types::types::RecordField::*; + match &field_type { + Optional(t) => Optional(helper!(t)), + Required(t) => Required(helper!(t)), + Demanded(t) => Demanded(helper!(t)), + } + }; + + field_vars.push((field.clone(), field_var)); + } + + let temp_ext_var = helper!(ext); + + let (it, new_ext_var) = + gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) + .expect("Something ended up weird in this record type"); + + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + insertion_sort_by(&mut field_vars, RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(subs, field_vars); + + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); + + register_with_known_var(subs, destination, rank, pools, content) + } + + TagUnion(tags, ext) => { + // An empty tags is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyTagUnion in canonicalization + debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + FunctionOrTagUnion(tag_name, symbol, ext) => { + let temp_ext_var = helper!(ext); + + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + subs, + UnionTags::default(), + temp_ext_var, + ); + + for _ in it { + unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); + } + + let slice = SubsIndex::new(subs.tag_names.len() as u32); + subs.tag_names.push(tag_name.clone()); + + let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + RecursiveTagUnion(rec_var, tags, ext) => { + // An empty tags is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyTagUnion in canonicalization + debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let content = + Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); + + let tag_union_var = destination; + register_with_known_var(subs, tag_union_var, rank, pools, content); + + register_with_known_var( + subs, + *rec_var, + rank, + pools, + Content::RecursionVar { + opt_name: None, + structure: tag_union_var, + }, + ); + + tag_union_var + } + + Type::Alias { + symbol, + type_arguments, + actual, + lambda_set_variables, + kind, + } => { + debug_assert!(Variable::get_reserved(*symbol).is_none()); + + let alias_variables = { + let length = type_arguments.len() + lambda_set_variables.len(); + let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + + for (target_index, (_, arg_type)) in + (new_variables.indices()).zip(type_arguments) + { + let copy_var = helper!(arg_type); + subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(type_arguments.len())) + .zip(lambda_set_variables); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + all_variables_len: length as _, + } + }; + + let alias_variable = if let Symbol::RESULT_RESULT = *symbol { + roc_result_to_var(subs, rank, pools, arena, actual) + } else { + helper!(actual) + }; + let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); + + register_with_known_var(subs, destination, rank, pools, content) + } + HostExposedAlias { + name: symbol, + type_arguments, + actual: alias_type, + actual_var, + lambda_set_variables, + .. + } => { + let alias_variables = { + let length = type_arguments.len() + lambda_set_variables.len(); + let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + + for (target_index, (_, arg_type)) in + (new_variables.indices()).zip(type_arguments) + { + let copy_var = helper!(arg_type); + subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(type_arguments.len())) + .zip(lambda_set_variables); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + all_variables_len: length as _, + } + }; + + // cannot use helper! here because this variable may be involved in unification below + let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); + // TODO(opaques): I think host-exposed aliases should always be structural + // (when does it make sense to give a host an opaque type?) + let content = Content::Alias( + *symbol, + alias_variables, + alias_variable, + AliasKind::Structural, + ); + // let result = register(subs, rank, pools, content); + let result = register_with_known_var(subs, destination, rank, pools, content); + + // We only want to unify the actual_var with the alias once + // if it's already redirected (and therefore, redundant) + // don't do it again + if !subs.redundant(*actual_var) { + let descriptor = subs.get(result); + subs.union(result, *actual_var, descriptor); + } + + result + } + Erroneous(problem) => { + let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + + register_with_known_var(subs, destination, rank, pools, content) + } + }; } - let it = (new_variables.indices().skip(type_arguments.len())).zip(lambda_set_variables); - for (target_index, ls) in it { - let copy_var = type_to_variable(subs, rank, pools, arena, &ls.0); - subs.variables[target_index] = copy_var; - } - - AliasVariables { - variables_start: new_variables.start, - type_variables_len: type_arguments.len() as _, - all_variables_len: length as _, - } + result } #[inline(always)] @@ -2167,7 +2243,7 @@ fn register_with_known_var( rank: Rank, pools: &mut Pools, content: Content, -) { +) -> Variable { let descriptor = Descriptor { content, rank, @@ -2178,4 +2254,6 @@ fn register_with_known_var( subs.set(var, descriptor); pools.get_mut(rank).push(var); + + var } From a37a895016d3629b86651cbee6f41e2777851659 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:11:33 +0100 Subject: [PATCH 06/10] optimize tag name cache --- compiler/solve/src/solve.rs | 14 +++++------ compiler/types/src/subs.rs | 46 +++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2db56dbad6..2227dc41bf 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1321,16 +1321,16 @@ fn sort_and_deduplicate(tag_vars: &mut bumpalo::collections::Vec<(TagName, T) fn find_tag_name_run(slice: &[(TagName, T)], subs: &mut Subs) -> Option> { use std::cmp::Ordering; - let tag_name = slice.get(0)?.0.clone(); + let tag_name = &slice.get(0)?.0; let mut result = None; // the `SubsSlice` that inserting `slice` into subs would give let bigger_slice = SubsSlice::new(subs.tag_names.len() as _, slice.len() as _); - match subs.tag_name_cache.entry(tag_name) { - Entry::Occupied(mut occupied) => { - let subs_slice = *occupied.get(); + match subs.tag_name_cache.get_mut(tag_name) { + Some(occupied) => { + let subs_slice = *occupied; let prefix_slice = SubsSlice::new(subs_slice.start, slice.len() as _); @@ -1364,12 +1364,12 @@ fn find_tag_name_run(slice: &[(TagName, T)], subs: &mut Subs) -> Option { // switch to the bigger slice that is not inserted yet, but will be soon - occupied.insert(bigger_slice); + *occupied = bigger_slice; } } } - Entry::Vacant(vacant) => { - vacant.insert(bigger_slice); + None => { + subs.tag_name_cache.push(tag_name, bigger_slice); } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b636c1efa8..048d251e2f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -68,7 +68,49 @@ pub struct Subs { pub field_names: Vec, pub record_fields: Vec>, pub variable_slices: Vec, - pub tag_name_cache: MutMap>, + pub tag_name_cache: TagNameCache, +} + +#[derive(Debug, Clone, Default)] +pub struct TagNameCache { + globals: Vec, + globals_slices: Vec>, + /// Currently private tags and closure tags; in the future just closure tags + symbols: Vec, + symbols_slices: Vec>, +} + +impl TagNameCache { + pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice> { + match tag_name { + TagName::Global(uppercase) => { + // force into block + match self.globals.iter().position(|u| u == uppercase) { + Some(index) => Some(&mut self.globals_slices[index]), + None => None, + } + } + TagName::Private(symbol) | TagName::Closure(symbol) => { + match self.symbols.iter().position(|s| s == symbol) { + Some(index) => Some(&mut self.symbols_slices[index]), + None => None, + } + } + } + } + + pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice) { + match tag_name { + TagName::Global(uppercase) => { + self.globals.push(uppercase.clone()); + self.globals_slices.push(slice); + } + TagName::Private(symbol) | TagName::Closure(symbol) => { + self.symbols.push(*symbol); + self.symbols_slices.push(slice); + } + } + } } impl Default for Subs { @@ -1248,7 +1290,7 @@ impl Subs { // store an empty slice at the first position // used for "TagOrFunction" variable_slices: vec![VariableSubsSlice::default()], - tag_name_cache: MutMap::default(), + tag_name_cache: TagNameCache::default(), }; // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing From 592a5ace193c5ed30846747e1ac836051d4fd91c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:14:47 +0100 Subject: [PATCH 07/10] optimize recursive call --- compiler/solve/src/solve.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2227dc41bf..0056bc68a8 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1466,8 +1466,7 @@ fn type_to_union_tags<'a>( let sorted = tags.len() == 1 || sorted_no_duplicates(tags); if ext.is_empty_tag_union() { - let ext = type_to_variable(subs, rank, pools, arena, &Type::EmptyTagUnion); - // let ext = Variable::EMPTY_TAG_UNION; + let ext = Variable::EMPTY_TAG_UNION; let union_tags = if sorted { insert_tags_fast_path(subs, rank, pools, arena, tags) From 8b526e4f58feb72ef09072a6074c2a22a27fce97 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:54:19 +0100 Subject: [PATCH 08/10] clippy --- compiler/solve/src/solve.rs | 1 - compiler/types/src/subs.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 0056bc68a8..5b446d1435 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -17,7 +17,6 @@ use roc_types::types::{ gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory, }; use roc_unify::unify::{unify, Mode, Unified::*}; -use std::collections::hash_map::Entry; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 048d251e2f..52f9177e8f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1,5 +1,5 @@ use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt}; -use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_collections::all::{ImMap, ImSet, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::symbol::Symbol; use std::fmt; From eec92204f1d980e809f1e190ee9d8f88fc5ec689 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 15:13:39 +0100 Subject: [PATCH 09/10] optimize type_to_union_tags --- compiler/solve/src/solve.rs | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 5b446d1435..8574ccabc3 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1049,7 +1049,8 @@ fn type_to_variable<'a>( // If hit, try to turn the value into an EmptyTagUnion in canonicalization debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let (union_tags, ext) = + type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); register_with_known_var(subs, destination, rank, pools, content) @@ -1079,7 +1080,8 @@ fn type_to_variable<'a>( // If hit, try to turn the value into an EmptyTagUnion in canonicalization debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let (union_tags, ext) = + type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); let content = Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); @@ -1381,10 +1383,24 @@ fn insert_tags_fast_path<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> UnionTags { let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var + } + } + }}; + } + match find_tag_name_run(tags, subs) { Some(new_tag_names) => { let it = (new_variable_slices.indices()).zip(tags); @@ -1394,7 +1410,7 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = type_to_variable(subs, rank, pools, arena, argument); + let var = helper!(argument); subs.variables[target_index] = var; } @@ -1415,7 +1431,7 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = type_to_variable(subs, rank, pools, arena, argument); + let var = helper!(argument); subs.variables[target_index] = var; } @@ -1433,14 +1449,28 @@ fn insert_tags_slow_path<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> UnionTags { + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var + } + } + }}; + } + for (tag, tag_argument_types) in tags { let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); for (i, arg) in (new_slice.indices()).zip(tag_argument_types) { - let var = type_to_variable(subs, rank, pools, arena, arg); + let var = helper!(arg); subs.variables[i] = var; } @@ -1457,8 +1487,9 @@ fn type_to_union_tags<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], ext: &Type, + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; @@ -1468,10 +1499,10 @@ fn type_to_union_tags<'a>( let ext = Variable::EMPTY_TAG_UNION; let union_tags = if sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags) + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) } else { let tag_vars = Vec::with_capacity_in(tags.len(), arena); - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars) + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) }; (union_tags, ext) @@ -1485,9 +1516,9 @@ fn type_to_union_tags<'a>( tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); let union_tags = if tag_vars.is_empty() && sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags) + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) } else { - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars) + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) }; (union_tags, ext) From 0f5c339b4f5bd685e9a6ca7cfa21d30632a64fc5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 15:29:23 +0100 Subject: [PATCH 10/10] and roc_result_to_var --- compiler/solve/src/solve.rs | 82 ++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 8574ccabc3..b5550412e4 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -918,6 +918,25 @@ impl RegisterVariable { _ => Deferred, } } + + #[inline(always)] + fn with_stack<'a>( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &'_ bumpalo::Bump, + typ: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, + ) -> Variable { + match Self::from_type(subs, rank, pools, arena, typ) { + Self::Direct(var) => var, + Self::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer(typ, var)); + var + } + } + } } #[derive(Debug)] @@ -1137,7 +1156,7 @@ fn type_to_variable<'a>( }; let alias_variable = if let Symbol::RESULT_RESULT = *symbol { - roc_result_to_var(subs, rank, pools, arena, actual) + roc_result_to_var(subs, rank, pools, arena, actual, &mut stack) } else { helper!(actual) }; @@ -1217,8 +1236,9 @@ fn roc_result_to_var<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, - result_type: &Type, + arena: &'_ bumpalo::Bump, + result_type: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> Variable { match result_type { Type::TagUnion(tags, ext) => { @@ -1230,8 +1250,10 @@ fn roc_result_to_var<'a>( debug_assert_eq!(ok, &subs.tag_names[1]); if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) { - let err_var = type_to_variable(subs, rank, pools, arena, err_type); - let ok_var = type_to_variable(subs, rank, pools, arena, ok_type); + let err_var = + RegisterVariable::with_stack(subs, rank, pools, arena, err_type, stack); + let ok_var = + RegisterVariable::with_stack(subs, rank, pools, arena, ok_type, stack); let start = subs.variables.len() as u32; let err_slice = SubsSlice::new(start, 1); @@ -1382,25 +1404,12 @@ fn insert_tags_fast_path<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); - macro_rules! helper { - ($typ:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { - RegisterVariable::Direct(var) => var, - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - stack.push(TypeToVar::Defer($typ, var)); - var - } - } - }}; - } - match find_tag_name_run(tags, subs) { Some(new_tag_names) => { let it = (new_variable_slices.indices()).zip(tags); @@ -1410,7 +1419,8 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = helper!(argument); + let var = + RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; } @@ -1431,7 +1441,8 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = helper!(argument); + let var = + RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; } @@ -1448,29 +1459,16 @@ fn insert_tags_slow_path<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { - macro_rules! helper { - ($typ:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { - RegisterVariable::Direct(var) => var, - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - stack.push(TypeToVar::Defer($typ, var)); - var - } - } - }}; - } - for (tag, tag_argument_types) in tags { let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); for (i, arg) in (new_slice.indices()).zip(tag_argument_types) { - let var = helper!(arg); + let var = RegisterVariable::with_stack(subs, rank, pools, arena, arg, stack); subs.variables[i] = var; } @@ -1486,10 +1484,10 @@ fn type_to_union_tags<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - ext: &Type, - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + ext: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; @@ -1509,7 +1507,7 @@ fn type_to_union_tags<'a>( } else { let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); + let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); let (it, ext) = roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var);