diff --git a/Cargo.lock b/Cargo.lock index ebbca7c3e8..1260b2fec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3554,6 +3554,7 @@ version = "0.1.0" name = "roc_types" version = "0.1.0" dependencies = [ + "bumpalo", "roc_collections", "roc_module", "roc_region", diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index c046e8bd18..092153bb5f 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -19,8 +19,7 @@ use roc_module::symbol::{ Symbol, }; use roc_mono::ir::{ - CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, - ProcLayout, Procs, + CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, StrLiteral, TypeAnnotation}; @@ -356,7 +355,7 @@ struct ModuleCache<'a> { constrained: MutMap, typechecked: MutMap>, found_specializations: MutMap>, - external_specializations_requested: MutMap>>, + external_specializations_requested: MutMap>, /// Various information imports: MutMap>, @@ -831,7 +830,7 @@ enum Msg<'a> { module_id: ModuleId, ident_ids: IdentIds, layout_cache: LayoutCache<'a>, - external_specializations_requested: BumpMap>, + external_specializations_requested: BumpMap, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, problems: Vec, module_timing: ModuleTiming, @@ -911,9 +910,6 @@ struct State<'a> { /// pending specializations in the same thread. pub needs_specialization: MutSet, - pub all_pending_specializations: - MutMap, PendingSpecialization<'a>>>, - pub specializations_in_flight: u32, pub timings: MutMap, @@ -1054,7 +1050,7 @@ enum BuildTask<'a> { subs: Subs, procs_base: ProcsBase<'a>, layout_cache: LayoutCache<'a>, - specializations_we_must_make: Vec>, + specializations_we_must_make: Vec, module_timing: ModuleTiming, }, } @@ -1538,7 +1534,6 @@ where unsolved_modules: MutMap::default(), timings: MutMap::default(), needs_specialization: MutSet::default(), - all_pending_specializations: MutMap::default(), specializations_in_flight: 0, layout_caches: std::vec::Vec::with_capacity(num_cpus::get()), procs: Procs::new_in(arena), @@ -2067,17 +2062,6 @@ fn update<'a>( log!("found specializations for {:?}", module_id); let subs = solved_subs.into_inner(); - for (symbol, specs) in &procs_base.specializations_for_host { - let existing = match state.all_pending_specializations.entry(*symbol) { - Vacant(entry) => entry.insert(MutMap::default()), - Occupied(entry) => entry.into_mut(), - }; - - for (layout, pend) in specs { - existing.insert(*layout, pend.clone()); - } - } - state .module_cache .top_level_thunks @@ -3936,7 +3920,7 @@ fn make_specializations<'a>( mut subs: Subs, procs_base: ProcsBase<'a>, mut layout_cache: LayoutCache<'a>, - specializations_we_must_make: Vec>, + specializations_we_must_make: Vec, mut module_timing: ModuleTiming, ptr_bytes: u32, ) -> Msg<'a> { @@ -3972,7 +3956,7 @@ fn make_specializations<'a>( &mut mono_env, procs, specializations_we_must_make, - procs_base.specializations_for_host, + procs_base.host_specializations, &mut layout_cache, ); @@ -4004,27 +3988,11 @@ struct ProcsBase<'a> { partial_procs: BumpMap>, module_thunks: &'a [Symbol], /// A host-exposed function must be specialized; it's a seed for subsequent specializations - specializations_for_host: BumpMap, PendingSpecialization<'a>>>, + host_specializations: roc_mono::ir::HostSpecializations, runtime_errors: BumpMap, imported_module_thunks: &'a [Symbol], } -impl<'a> ProcsBase<'a> { - fn add_specialization_for_host( - &mut self, - symbol: Symbol, - layout: ProcLayout<'a>, - pending: PendingSpecialization<'a>, - ) { - let all_pending = self - .specializations_for_host - .entry(symbol) - .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher())); - - all_pending.insert(layout, pending); - } -} - #[allow(clippy::too_many_arguments)] fn build_pending_specializations<'a>( arena: &'a Bump, @@ -4046,7 +4014,7 @@ fn build_pending_specializations<'a>( let mut procs_base = ProcsBase { partial_procs: BumpMap::default(), module_thunks: &[], - specializations_for_host: BumpMap::default(), + host_specializations: roc_mono::ir::HostSpecializations::new(), runtime_errors: BumpMap::default(), imported_module_thunks, }; @@ -4134,7 +4102,7 @@ fn add_def_to_module<'a>( match def.loc_pattern.value { Identifier(symbol) => { - let is_exposed = exposed_to_host.contains_key(&symbol); + let is_host_exposed = exposed_to_host.contains_key(&symbol); match def.loc_expr.value { Closure(ClosureData { @@ -4152,42 +4120,37 @@ fn add_def_to_module<'a>( // register it as such. Otherwise, since it // never gets called by Roc code, it will never // get specialized! - if is_exposed { - let layout = match layout_cache.raw_from_var( - mono_env.arena, - annotation, - mono_env.subs, - ) { - Ok(l) => l, - Err(LayoutProblem::Erroneous) => { - let message = "top level function has erroneous type"; - procs.runtime_errors.insert(symbol, message); - return; - } - Err(LayoutProblem::UnresolvedTypeVar(v)) => { - let message = format!( - "top level function has unresolved type variable {:?}", - v - ); - procs - .runtime_errors - .insert(symbol, mono_env.arena.alloc(message)); - return; - } - }; + if is_host_exposed { + let layout_result = + layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs); - let pending = PendingSpecialization::from_exposed_function( - mono_env.arena, + // cannot specialize when e.g. main's type contains type variables + if let Err(e) = layout_result { + match e { + LayoutProblem::Erroneous => { + let message = "top level function has erroneous type"; + procs.runtime_errors.insert(symbol, message); + return; + } + LayoutProblem::UnresolvedTypeVar(v) => { + let message = format!( + "top level function has unresolved type variable {:?}", + v + ); + procs + .runtime_errors + .insert(symbol, mono_env.arena.alloc(message)); + return; + } + } + } + + procs.host_specializations.insert_host_exposed( mono_env.subs, + symbol, def.annotation, annotation, ); - - procs.add_specialization_for_host( - symbol, - ProcLayout::from_raw(mono_env.arena, layout), - pending, - ); } let partial_proc = PartialProc::from_named_function( @@ -4207,51 +4170,47 @@ fn add_def_to_module<'a>( // mark this symbols as a top-level thunk before any other work on the procs module_thunks.push(symbol); + let annotation = def.expr_var; + // If this is an exposed symbol, we need to // register it as such. Otherwise, since it // never gets called by Roc code, it will never // get specialized! - if is_exposed { - let annotation = def.expr_var; + if is_host_exposed { + let layout_result = + layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs); - let top_level = match layout_cache.from_var( - mono_env.arena, - annotation, - mono_env.subs, - ) { - Ok(l) => { - // remember, this is a 0-argument thunk - ProcLayout::new(mono_env.arena, &[], l) + // cannot specialize when e.g. main's type contains type variables + if let Err(e) = layout_result { + match e { + LayoutProblem::Erroneous => { + let message = "top level function has erroneous type"; + procs.runtime_errors.insert(symbol, message); + return; + } + LayoutProblem::UnresolvedTypeVar(v) => { + let message = format!( + "top level function has unresolved type variable {:?}", + v + ); + procs + .runtime_errors + .insert(symbol, mono_env.arena.alloc(message)); + return; + } } - Err(LayoutProblem::Erroneous) => { - let message = "top level function has erroneous type"; - procs.runtime_errors.insert(symbol, message); - return; - } - Err(LayoutProblem::UnresolvedTypeVar(v)) => { - let message = format!( - "top level function has unresolved type variable {:?}", - v - ); - procs - .runtime_errors - .insert(symbol, mono_env.arena.alloc(message)); - return; - } - }; + } - let pending = PendingSpecialization::from_exposed_function( - mono_env.arena, + procs.host_specializations.insert_host_exposed( mono_env.subs, + symbol, def.annotation, annotation, ); - - procs.add_specialization_for_host(symbol, top_level, pending); } let proc = PartialProc { - annotation: def.expr_var, + annotation, // This is a 0-arity thunk, so it has no arguments. pattern_symbols: &[], // This is a top-level definition, so it cannot capture anything diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index bb319a4370..7dfb141170 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -15,7 +15,6 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_std::RocDec; -use roc_types::solved_types::SolvedType; use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSubsSlice}; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; @@ -222,65 +221,6 @@ impl<'a> Default for CapturedSymbols<'a> { } } -#[derive(Clone, Debug)] -pub struct PendingSpecialization<'a> { - solved_type: SolvedType, - host_exposed_aliases: BumpMap, - _lifetime: std::marker::PhantomData<&'a u8>, -} - -impl<'a> PendingSpecialization<'a> { - pub fn from_var(arena: &'a Bump, subs: &Subs, var: Variable) -> Self { - let solved_type = SolvedType::from_var(subs, var); - PendingSpecialization { - solved_type, - host_exposed_aliases: BumpMap::new_in(arena), - _lifetime: std::marker::PhantomData, - } - } - - pub fn from_var_host_exposed( - arena: &'a Bump, - subs: &Subs, - var: Variable, - exposed: &MutMap, - ) -> Self { - let solved_type = SolvedType::from_var(subs, var); - - let mut host_exposed_aliases = BumpMap::with_capacity_in(exposed.len(), arena); - - host_exposed_aliases.extend( - exposed - .iter() - .map(|(symbol, variable)| (*symbol, SolvedType::from_var(subs, *variable))), - ); - - PendingSpecialization { - solved_type, - host_exposed_aliases, - _lifetime: std::marker::PhantomData, - } - } - - /// Add a named function that will be publicly exposed to the host - pub fn from_exposed_function( - arena: &'a Bump, - subs: &Subs, - opt_annotation: Option, - fn_var: Variable, - ) -> Self { - match opt_annotation { - None => PendingSpecialization::from_var(arena, subs, fn_var), - Some(annotation) => PendingSpecialization::from_var_host_exposed( - arena, - subs, - fn_var, - &annotation.introduced_variables.host_exposed_aliases, - ), - } - } -} - #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { pub name: Symbol, @@ -414,24 +354,108 @@ impl<'a> Proc<'a> { } } +/// A host-exposed function must be specialized; it's a seed for subsequent specializations #[derive(Clone, Debug)] -pub struct ExternalSpecializations<'a> { +pub struct HostSpecializations { + /// Not a bumpalo vec because bumpalo is not thread safe + /// Separate array so we can search for membership quickly + symbols: std::vec::Vec, + storage_subs: StorageSubs, + /// For each symbol, what types to specialize it for, points into the storage_subs + types_to_specialize: std::vec::Vec, + /// Variables for an exposed alias + exposed_aliases: std::vec::Vec>, +} + +impl Default for HostSpecializations { + fn default() -> Self { + Self::new() + } +} + +impl HostSpecializations { + pub fn new() -> Self { + Self { + symbols: std::vec::Vec::new(), + storage_subs: StorageSubs::new(Subs::default()), + types_to_specialize: std::vec::Vec::new(), + exposed_aliases: std::vec::Vec::new(), + } + } + + pub fn insert_host_exposed( + &mut self, + env_subs: &mut Subs, + symbol: Symbol, + opt_annotation: Option, + variable: Variable, + ) { + let variable = self.storage_subs.extend_with_variable(env_subs, variable); + + let mut host_exposed_aliases = std::vec::Vec::new(); + + if let Some(annotation) = opt_annotation { + host_exposed_aliases.extend(annotation.introduced_variables.host_exposed_aliases); + } + + match self.symbols.iter().position(|s| *s == symbol) { + None => { + self.symbols.push(symbol); + self.types_to_specialize.push(variable); + self.exposed_aliases.push(host_exposed_aliases); + } + Some(_) => { + // we assume that only one specialization of a function is directly exposed to the + // host. Other host-exposed symbols may (transitively) specialize this symbol, + // but then the existing specialization mechanism will find those specializations + panic!("A host-exposed symbol can only be exposed once"); + } + } + + debug_assert_eq!(self.types_to_specialize.len(), self.exposed_aliases.len()); + } + + fn decompose( + self, + ) -> ( + StorageSubs, + impl Iterator)>, + ) { + let it1 = self.symbols.into_iter(); + + let it2 = self.types_to_specialize.into_iter(); + let it3 = self.exposed_aliases.into_iter(); + + ( + self.storage_subs, + it1.zip(it2).zip(it3).map(|((a, b), c)| (a, b, c)), + ) + } +} + +/// Specializations of this module's symbols that other modules need +#[derive(Clone, Debug)] +pub struct ExternalSpecializations { /// Not a bumpalo vec because bumpalo is not thread safe /// Separate array so we can search for membership quickly symbols: std::vec::Vec, storage_subs: StorageSubs, /// For each symbol, what types to specialize it for, points into the storage_subs types_to_specialize: std::vec::Vec>, - _lifetime: std::marker::PhantomData<&'a u8>, } -impl<'a> ExternalSpecializations<'a> { - pub fn new_in(_arena: &'a Bump) -> Self { +impl Default for ExternalSpecializations { + fn default() -> Self { + Self::new() + } +} + +impl ExternalSpecializations { + pub fn new() -> Self { Self { symbols: std::vec::Vec::new(), storage_subs: StorageSubs::new(Subs::default()), types_to_specialize: std::vec::Vec::new(), - _lifetime: std::marker::PhantomData, } } @@ -636,7 +660,7 @@ pub struct Procs<'a> { pending_specializations: PendingSpecializations<'a>, specialized: Specialized<'a>, pub runtime_errors: BumpMap, - pub externals_we_need: BumpMap>, + pub externals_we_need: BumpMap, } impl<'a> Procs<'a> { @@ -802,7 +826,7 @@ impl<'a> Procs<'a> { symbol, layout_cache, annotation, - BumpMap::new_in(env.arena), + &[], partial_proc_id, ) { Ok((proc, layout)) => { @@ -1889,14 +1913,87 @@ fn pattern_to_when<'a>( } } +fn specialize_suspended<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + suspended: Suspended<'a>, +) { + let offset_variable = StorageSubs::merge_into(suspended.store, env.subs); + + for (i, (symbol, var)) in suspended + .symbols + .iter() + .zip(suspended.variables.iter()) + .enumerate() + { + let name = *symbol; + let outside_layout = suspended.layouts[i]; + + let var = offset_variable(*var); + + // TODO define our own Entry for Specialized? + let partial_proc = if procs.specialized.is_specialized(name, &outside_layout) { + // already specialized, just continue + continue; + } else { + match procs.partial_procs.symbol_to_id(name) { + Some(v) => { + // Mark this proc as in-progress, so if we're dealing with + // mutually recursive functions, we don't loop forever. + // (We had a bug around this before this system existed!) + procs.specialized.mark_in_progress(name, outside_layout); + + v + } + None => { + // TODO this assumes the specialization is done by another module + // make sure this does not become a problem down the road! + continue; + } + } + }; + + match specialize_variable(env, procs, name, layout_cache, var, &[], partial_proc) { + Ok((proc, layout)) => { + // TODO thiscode is duplicated elsewhere + let top_level = ProcLayout::from_raw(env.arena, layout); + + if procs.is_module_thunk(proc.name) { + debug_assert!( + top_level.arguments.is_empty(), + "{:?} from {:?}", + name, + layout + ); + } + + debug_assert_eq!(outside_layout, top_level, " in {:?}", name); + procs.specialized.insert_specialized(name, top_level, proc); + } + Err(SpecializeFailure { + attempted_layout, .. + }) => { + let proc = generate_runtime_error_function(env, name, attempted_layout); + + let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + + procs.specialized.insert_specialized(name, top_level, proc); + } + } + } +} + pub fn specialize_all<'a>( env: &mut Env<'a, '_>, mut procs: Procs<'a>, - externals_others_need: std::vec::Vec>, - specializations_for_host: BumpMap, PendingSpecialization<'a>>>, + externals_others_need: std::vec::Vec, + specializations_for_host: HostSpecializations, layout_cache: &mut LayoutCache<'a>, ) -> Procs<'a> { - specialize_externals_others_need(env, &mut procs, externals_others_need, layout_cache); + for externals in externals_others_need { + specialize_external_specializations(env, &mut procs, layout_cache, externals); + } // When calling from_can, pending_specializations should be unavailable. // This must be a single pass, and we must not add any more entries to it! @@ -1908,200 +2005,110 @@ pub fn specialize_all<'a>( match pending_specializations { PendingSpecializations::Making => {} PendingSpecializations::Finding(suspended) => { - let offset_variable = StorageSubs::merge_into(suspended.store, env.subs); - - for (i, (symbol, var)) in suspended - .symbols - .iter() - .zip(suspended.variables.iter()) - .enumerate() - { - let name = *symbol; - let outside_layout = suspended.layouts[i]; - - let var = offset_variable(*var); - - // TODO define our own Entry for Specialized? - let partial_proc = if procs.specialized.is_specialized(name, &outside_layout) { - // already specialized, just continue - continue; - } else { - match procs.partial_procs.symbol_to_id(name) { - Some(v) => { - // Mark this proc as in-progress, so if we're dealing with - // mutually recursive functions, we don't loop forever. - // (We had a bug around this before this system existed!) - procs.specialized.mark_in_progress(name, outside_layout); - - v - } - None => { - // TODO this assumes the specialization is done by another module - // make sure this does not become a problem down the road! - continue; - } - } - }; - - match specialize_variable( - env, - &mut procs, - name, - layout_cache, - var, - BumpMap::new_in(env.arena), - partial_proc, - ) { - Ok((proc, layout)) => { - // TODO thiscode is duplicated elsewhere - let top_level = ProcLayout::from_raw(env.arena, layout); - - if procs.is_module_thunk(proc.name) { - debug_assert!( - top_level.arguments.is_empty(), - "{:?} from {:?}", - name, - layout - ); - } - - debug_assert_eq!(outside_layout, top_level, " in {:?}", name); - procs.specialized.insert_specialized(name, top_level, proc); - } - Err(SpecializeFailure { - attempted_layout, .. - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); - - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); - - procs.specialized.insert_specialized(name, top_level, proc); - } - } - } + specialize_suspended(env, &mut procs, layout_cache, suspended) } } - let it = specializations_for_host.into_iter(); - - for (name, by_layout) in it { - for (outside_layout, pending) in by_layout.into_iter() { - // If we've already seen this (Symbol, Layout) combination before, - // don't try to specialize it again. If we do, we'll loop forever! - - let partial_proc = if procs.specialized.is_specialized(name, &outside_layout) { - // already specialized, just continue - continue; - } else { - match procs.partial_procs.symbol_to_id(name) { - Some(v) => { - // Mark this proc as in-progress, so if we're dealing with - // mutually recursive functions, we don't loop forever. - // (We had a bug around this before this system existed!) - procs.specialized.mark_in_progress(name, outside_layout); - - v - } - None => { - // TODO this assumes the specialization is done by another module - // make sure this does not become a problem down the road! - continue; - } - } - }; - - match specialize(env, &mut procs, name, layout_cache, pending, partial_proc) { - Ok((proc, layout)) => { - // TODO thiscode is duplicated elsewhere - let top_level = ProcLayout::from_raw(env.arena, layout); - - if procs.is_module_thunk(proc.name) { - debug_assert!( - top_level.arguments.is_empty(), - "{:?} from {:?}", - name, - layout - ); - } - - debug_assert_eq!(outside_layout, top_level, " in {:?}", name); - procs.specialized.insert_specialized(name, top_level, proc); - } - Err(SpecializeFailure { - attempted_layout, .. - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); - - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); - - procs.specialized.insert_specialized(name, top_level, proc); - } - } - } - } + specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host); procs } -fn specialize_externals_others_need<'a>( +fn specialize_host_specializations<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - all_externals_others_need: std::vec::Vec>, layout_cache: &mut LayoutCache<'a>, + host_specializations: HostSpecializations, ) { - for externals_others_need in all_externals_others_need { - let (store, it) = externals_others_need.decompose(); + let (store, it) = host_specializations.decompose(); - let offset_variable = StorageSubs::merge_into(store, env.subs); + let offset_variable = StorageSubs::merge_into(store, env.subs); - for (symbol, solved_types) in it { - for store_variable in solved_types { - let variable = offset_variable(store_variable); - // historical note: we used to deduplicate with a hash here, - // but the cost of that hash is very high. So for now we make - // duplicate specializations, and the insertion into a hash map - // below will deduplicate them. + for (symbol, variable, host_exposed_aliases) in it { + specialize_external_help( + env, + procs, + layout_cache, + symbol, + offset_variable(variable), + &host_exposed_aliases, + ) + } +} - let name = symbol; +fn specialize_external_specializations<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + externals_others_need: ExternalSpecializations, +) { + let (store, it) = externals_others_need.decompose(); - let partial_proc_id = match procs.partial_procs.symbol_to_id(name) { - Some(v) => v, - None => { - panic!("Cannot find a partial proc for {:?}", name); - } - }; + let offset_variable = StorageSubs::merge_into(store, env.subs); - // TODO I believe this is also duplicated - match specialize_variable( - env, - procs, - name, - layout_cache, - variable, - BumpMap::new_in(env.arena), - partial_proc_id, - ) { - Ok((proc, layout)) => { - let top_level = ProcLayout::from_raw(env.arena, layout); + for (symbol, solved_types) in it { + for store_variable in solved_types { + // historical note: we used to deduplicate with a hash here, + // but the cost of that hash is very high. So for now we make + // duplicate specializations, and the insertion into a hash map + // below will deduplicate them. - if procs.is_module_thunk(name) { - debug_assert!(top_level.arguments.is_empty()); - } + specialize_external_help( + env, + procs, + layout_cache, + symbol, + offset_variable(store_variable), + &[], + ) + } + } +} - procs.specialized.insert_specialized(name, top_level, proc); - } - Err(SpecializeFailure { - problem: _, - attempted_layout, - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); +fn specialize_external_help<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + name: Symbol, + variable: Variable, + host_exposed_aliases: &[(Symbol, Variable)], +) { + let partial_proc_id = match procs.partial_procs.symbol_to_id(name) { + Some(v) => v, + None => { + panic!("Cannot find a partial proc for {:?}", name); + } + }; - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + let specialization_result = specialize_variable( + env, + procs, + name, + layout_cache, + variable, + host_exposed_aliases, + partial_proc_id, + ); - procs.specialized.insert_specialized(name, top_level, proc); - } - } + match specialization_result { + Ok((proc, layout)) => { + let top_level = ProcLayout::from_raw(env.arena, layout); + + if procs.is_module_thunk(name) { + debug_assert!(top_level.arguments.is_empty()); } + + procs.specialized.insert_specialized(name, top_level, proc); + } + Err(SpecializeFailure { + problem: _, + attempted_layout, + }) => { + let proc = generate_runtime_error_function(env, name, attempted_layout); + + let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + + procs.specialized.insert_specialized(name, top_level, proc); } } } @@ -2671,77 +2678,13 @@ struct SpecializeFailure<'a> { type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>); -fn specialize<'a, 'b>( - env: &mut Env<'a, '_>, - procs: &'b mut Procs<'a>, - proc_name: Symbol, - layout_cache: &mut LayoutCache<'a>, - pending: PendingSpecialization, - partial_proc_id: PartialProcId, -) -> Result, SpecializeFailure<'a>> { - let PendingSpecialization { - solved_type, - host_exposed_aliases, - .. - } = pending; - - specialize_solved_type( - env, - procs, - proc_name, - layout_cache, - &solved_type, - host_exposed_aliases, - partial_proc_id, - ) -} - -fn introduce_solved_type_to_subs<'a>(env: &mut Env<'a, '_>, solved_type: &SolvedType) -> Variable { - use roc_solve::solve::insert_type_into_subs; - use roc_types::solved_types::{to_type, FreeVars}; - use roc_types::subs::VarStore; - let mut free_vars = FreeVars::default(); - let mut var_store = VarStore::new_from_subs(env.subs); - - let before = var_store.peek(); - - let normal_type = to_type(solved_type, &mut free_vars, &mut var_store); - - let after = var_store.peek(); - let variables_introduced = after - before; - - env.subs.extend_by(variables_introduced as usize); - - insert_type_into_subs(env.subs, &normal_type) -} - -fn specialize_solved_type<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - proc_name: Symbol, - layout_cache: &mut LayoutCache<'a>, - solved_type: &SolvedType, - host_exposed_aliases: BumpMap, - partial_proc_id: PartialProcId, -) -> Result, SpecializeFailure<'a>> { - specialize_variable_help( - env, - procs, - proc_name, - layout_cache, - |env| introduce_solved_type_to_subs(env, solved_type), - host_exposed_aliases, - partial_proc_id, - ) -} - fn specialize_variable<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, fn_var: Variable, - host_exposed_aliases: BumpMap, + host_exposed_aliases: &[(Symbol, Variable)], partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> { specialize_variable_help( @@ -2761,7 +2704,7 @@ fn specialize_variable_help<'a, F>( proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, fn_var_thunk: F, - host_exposed_aliases: BumpMap, + host_exposed_variables: &[(Symbol, Variable)], partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> where @@ -2796,21 +2739,13 @@ where let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation; instantiate_rigids(env.subs, annotation_var); - let mut host_exposed_variables = Vec::with_capacity_in(host_exposed_aliases.len(), env.arena); - - for (symbol, solved_type) in host_exposed_aliases { - let alias_var = introduce_solved_type_to_subs(env, &solved_type); - - host_exposed_variables.push((symbol, alias_var)); - } - let specialized = specialize_external( env, procs, proc_name, layout_cache, fn_var, - &host_exposed_variables, + host_exposed_variables, partial_proc_id, ); @@ -6564,7 +6499,7 @@ fn add_needed_external<'a>( use hashbrown::hash_map::Entry::{Occupied, Vacant}; let existing = match procs.externals_we_need.entry(name.module_id()) { - Vacant(entry) => entry.insert(ExternalSpecializations::new_in(env.arena)), + Vacant(entry) => entry.insert(ExternalSpecializations::new()), Occupied(entry) => entry.into_mut(), }; @@ -6927,7 +6862,7 @@ fn call_by_name_help<'a>( proc_name, layout_cache, fn_var, - BumpMap::new_in(env.arena), + &[], partial_proc, ) { Ok((proc, layout)) => { @@ -7049,7 +6984,7 @@ fn call_by_name_module_thunk<'a>( proc_name, layout_cache, fn_var, - BumpMap::new_in(env.arena), + &[], partial_proc, ) { Ok((proc, raw_layout)) => { diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index 4b24c7b470..de9d320d28 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -12,6 +12,7 @@ roc_module = { path = "../module" } roc_types = { path = "../types" } roc_can = { path = "../can" } roc_unify = { path = "../unify" } +bumpalo = { version = "3.8.0", features = ["collections"] } [dev-dependencies] roc_load = { path = "../load" } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index ccf739a2d8..854b853bae 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -619,10 +619,13 @@ fn type_to_var( subs: &mut Subs, rank: Rank, pools: &mut Pools, - cached: &mut MutMap, + _: &mut MutMap, typ: &Type, ) -> Variable { - type_to_variable(subs, rank, pools, cached, typ) + // capacity based on the false hello world program + let arena = bumpalo::Bump::with_capacity(4 * 1024); + + type_to_variable(subs, rank, pools, &arena, typ) } /// Abusing existing functions for our purposes @@ -630,25 +633,29 @@ fn type_to_var( pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type) -> Variable { let rank = Rank::NONE; let mut pools = Pools::default(); - let mut cached = MutMap::default(); - type_to_variable(subs, rank, &mut pools, &mut cached, typ) + // capacity based on the false hello world program + let arena = bumpalo::Bump::with_capacity(4 * 1024); + + type_to_variable(subs, rank, &mut pools, &arena, typ) } -fn type_to_variable( +fn type_to_variable<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - cached: &mut MutMap, + arena: &'a bumpalo::Bump, typ: &Type, ) -> Variable { + use bumpalo::collections::Vec; + match typ { Variable(var) => *var, Apply(symbol, args) => { - let mut new_arg_vars = Vec::with_capacity(args.len()); + let mut new_arg_vars = Vec::with_capacity_in(args.len(), arena); for arg in args { - let var = type_to_variable(subs, rank, pools, cached, arg); + let var = type_to_variable(subs, rank, pools, arena, arg); new_arg_vars.push(var); } @@ -663,32 +670,32 @@ fn type_to_variable( // This case is important for the rank of boolean variables Function(arg_vars, closure_type, ret_type) => { - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + let mut new_arg_vars = Vec::with_capacity_in(arg_vars.len(), arena); for arg in arg_vars { - let var = type_to_variable(subs, rank, pools, cached, arg); + let var = type_to_variable(subs, rank, pools, arena, arg); new_arg_vars.push(var); } let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - let ret_var = type_to_variable(subs, rank, pools, cached, ret_type); - let closure_var = type_to_variable(subs, rank, pools, cached, closure_type); + 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(arg_vars, closure_var, ret_var)); register(subs, rank, pools, content) } Record(fields, ext) => { - let mut field_vars = Vec::with_capacity(fields.len()); + 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, cached, typ)); + 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, cached, ext); + 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); @@ -707,14 +714,14 @@ fn type_to_variable( register(subs, rank, pools, content) } TagUnion(tags, ext) => { - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); + 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, cached, ext); - let mut ext_tag_vec = Vec::new(); + let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); + let mut ext_tag_vec = std::vec::Vec::new(); let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( subs, temp_ext_var, @@ -735,7 +742,7 @@ fn type_to_variable( register(subs, rank, pools, content) } RecursiveTagUnion(rec_var, tags, ext) => { - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); + 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)); @@ -780,22 +787,22 @@ fn type_to_variable( } } - let mut arg_vars = Vec::with_capacity(args.len()); + let mut arg_vars = Vec::with_capacity_in(args.len(), arena); for (arg, arg_type) in args { - let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + let arg_var = type_to_variable(subs, rank, pools, arena, arg_type); arg_vars.push((arg.clone(), arg_var)); } - let lambda_set_variables: Vec<_> = lambda_set_variables + let lambda_set_variables_it = lambda_set_variables .iter() - .map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) - .collect(); + .map(|ls| type_to_variable(subs, rank, pools, arena, &ls.0)); + let lambda_set_variables = Vec::from_iter_in(lambda_set_variables_it, arena); let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); - let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + let alias_var = type_to_variable(subs, rank, pools, arena, alias_type); let content = Content::Alias(*symbol, arg_vars, alias_var); register(subs, rank, pools, content) @@ -808,22 +815,22 @@ fn type_to_variable( lambda_set_variables, .. } => { - let mut arg_vars = Vec::with_capacity(args.len()); + let mut arg_vars = Vec::with_capacity_in(args.len(), arena); for (arg, arg_type) in args { - let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + let arg_var = type_to_variable(subs, rank, pools, arena, arg_type); arg_vars.push((arg.clone(), arg_var)); } - let lambda_set_variables: Vec<_> = lambda_set_variables + let lambda_set_variables_it = lambda_set_variables .iter() - .map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) - .collect(); + .map(|ls| type_to_variable(subs, rank, pools, arena, &ls.0)); + let lambda_set_variables = Vec::from_iter_in(lambda_set_variables_it, arena); let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); - let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + let alias_var = type_to_variable(subs, rank, pools, arena, alias_type); // unify the actual_var with the result var // this can be used to access the type of the actual_var @@ -853,20 +860,22 @@ fn type_to_variable( } } -fn type_to_union_tags( +fn type_to_union_tags<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - cached: &mut MutMap, + arena: &'a bumpalo::Bump, tags: &[(TagName, Vec)], ext: &Type, ) -> (UnionTags, Variable) { - let mut tag_vars = Vec::with_capacity(tags.len()); + use bumpalo::collections::Vec; - let mut tag_argument_vars = Vec::new(); + let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); + + let mut tag_argument_vars = Vec::with_capacity_in(tags.len(), arena); for (tag, tag_argument_types) in tags { for arg_type in tag_argument_types { - let new_var = type_to_variable(subs, rank, pools, cached, arg_type); + let new_var = type_to_variable(subs, rank, pools, arena, arg_type); tag_argument_vars.push(new_var); } @@ -875,7 +884,7 @@ fn type_to_union_tags( tag_vars.push((tag.clone(), new_slice)); } - let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); let ext = { let (it, ext) = @@ -1031,8 +1040,7 @@ fn pool_to_rank_table( // Sort the variables into buckets by rank. for &var in young_vars.iter() { - let rank = subs.get_rank(var); - subs.set_mark(var, young_mark); + let rank = subs.get_rank_set_mark(var, young_mark); debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); pools.get_mut(rank).push(var); @@ -1248,125 +1256,131 @@ pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { instantiate_rigids_help(subs, rank, var); - subs.restore(var); + // NOTE subs.restore(var) is done at the end of instantiate_rigids_help } -fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, var: Variable) { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; +fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { + let mut visited = vec![]; + let mut stack = vec![initial]; - let desc = subs.get_without_compacting(var); - - if desc.copy.is_some() { - return; + macro_rules! var_slice { + ($variable_subs_slice:expr) => {{ + let slice = $variable_subs_slice; + &subs.variables[slice.slice.start as usize..][..slice.slice.length as usize] + }}; } - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: desc.content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: var.into(), - }, - ); + while let Some(var) = stack.pop() { + visited.push(var); - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match desc.content { - Structure(flat_type) => { - match flat_type { + let desc = subs.get_ref_mut(var); + if desc.copy.is_some() { + continue; + } + + desc.rank = Rank::NONE; + desc.mark = Mark::NONE; + desc.copy = OptVariable::from(var); + + use Content::*; + use FlatType::*; + + match &desc.content { + RigidVar(name) => { + // what it's all about: convert the rigid var into a flex var + let name = name.clone(); + + // NOTE: we must write to the mutually borrowed `desc` value here + // using `subs.set` does not work (unclear why, really) + // but get_ref_mut approach saves a lookup, so the weirdness is worth it + desc.content = FlexVar(Some(name)); + desc.rank = max_rank; + desc.mark = Mark::NONE; + desc.copy = OptVariable::NONE; + } + FlexVar(_) | Error => (), + + RecursionVar { structure, .. } => { + stack.push(*structure); + } + + Structure(flat_type) => match flat_type { Apply(_, args) => { - for var_index in args.into_iter() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, var); - } + stack.extend(var_slice!(*args)); } Func(arg_vars, closure_var, ret_var) => { - instantiate_rigids_help(subs, max_rank, ret_var); - instantiate_rigids_help(subs, max_rank, closure_var); + let arg_vars = *arg_vars; + let ret_var = *ret_var; + let closure_var = *closure_var; - for index in arg_vars.into_iter() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, var); - } + stack.extend(var_slice!(arg_vars)); + + stack.push(ret_var); + stack.push(closure_var); } - EmptyRecord | EmptyTagUnion | Erroneous(_) => {} + EmptyRecord => (), + EmptyTagUnion => (), Record(fields, ext_var) => { - for index in fields.iter_variables() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, var); - } + let fields = *fields; + let ext_var = *ext_var; + stack.extend(var_slice!(fields.variables())); - instantiate_rigids_help(subs, max_rank, ext_var); + stack.push(ext_var); } - TagUnion(tags, ext_var) => { - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, var); - } + let tags = *tags; + let ext_var = *ext_var; + + for slice_index in tags.variables() { + let slice = subs.variable_slices[slice_index.start as usize]; + stack.extend(var_slice!(slice)); } - instantiate_rigids_help(subs, max_rank, ext_var); + stack.push(ext_var); } - - FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { - instantiate_rigids_help(subs, max_rank, ext_var); + FunctionOrTagUnion(_, _, ext_var) => { + stack.push(*ext_var); } RecursiveTagUnion(rec_var, tags, ext_var) => { - instantiate_rigids_help(subs, max_rank, rec_var); + let tags = *tags; + let ext_var = *ext_var; + let rec_var = *rec_var; - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, var); - } + for slice_index in tags.variables() { + let slice = subs.variable_slices[slice_index.start as usize]; + stack.extend(var_slice!(slice)); } - instantiate_rigids_help(subs, max_rank, ext_var); + stack.push(ext_var); + stack.push(rec_var); } - }; - } - FlexVar(_) | Error => {} + Erroneous(_) => (), + }, + Alias(_, args, var) => { + let var = *var; + let args = *args; - RecursionVar { structure, .. } => { - instantiate_rigids_help(subs, max_rank, structure); - } + stack.extend(var_slice!(args.variables())); - RigidVar(name) => { - // what it's all about: convert the rigid var into a flex var - subs.set( - var, - Descriptor { - content: FlexVar(Some(name)), - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }, - ); - } - - Alias(_symbol, args, real_type_var) => { - for var_index in args.variables().into_iter() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, var); + stack.push(var); } + } + } - instantiate_rigids_help(subs, max_rank, real_type_var); + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + for var in visited { + let descriptor = subs.get_ref_mut(var); + + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; } } } diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index 36630718ca..1bb0f10960 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,4 +1,4 @@ -procedure Bool.5 (#Attr.2, #Attr.3): +procedure Bool.7 (#Attr.2, #Attr.3): let Test.11 = lowlevel Eq #Attr.2 #Attr.3; ret Test.11; @@ -13,7 +13,7 @@ procedure Test.1 (Test.3): ret Test.12; in let Test.10 = 5i64; - let Test.9 = CallByName Bool.5 Test.6 Test.10; + let Test.9 = CallByName Bool.7 Test.6 Test.10; jump Test.8 Test.9; procedure Test.0 (): diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index a47245bf95..97cd23ac45 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -10,4 +10,5 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } ven_ena = { path = "../../vendor/ena" } +bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 4aaaf28abb..7d42f424f7 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -79,7 +79,7 @@ pub struct SubsSlice { /// An index into the Vec of subs pub struct SubsIndex { - start: u32, + pub start: u32, _marker: std::marker::PhantomData, } @@ -1164,6 +1164,26 @@ impl Subs { }); } + pub fn modify(&mut self, key: Variable, mapper: F) + where + F: Fn(&mut Descriptor), + { + mapper(self.get_ref_mut(key)); + } + + pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { + let l_key = self.utable.get_root_key(key); + + let mut rank = Rank::NONE; + + self.utable.update_value(l_key, |node| { + node.value.mark = mark; + rank = node.value.rank; + }); + + rank + } + pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool { self.utable.unioned(left, right) } @@ -1501,7 +1521,7 @@ pub enum Builtin { #[derive(Clone, Copy, Debug, Default)] pub struct VariableSubsSlice { - slice: SubsSlice, + pub slice: SubsSlice, } impl VariableSubsSlice { @@ -1825,7 +1845,7 @@ impl RecordFields { } } - fn variables(&self) -> VariableSubsSlice { + pub const fn variables(&self) -> VariableSubsSlice { let slice = SubsSlice::new(self.variables_start, self.length); VariableSubsSlice { slice } @@ -2837,7 +2857,7 @@ impl StorageSubs { }; let offsets = StorageSubsOffsets { - utable: target.utable.len() as u32, + utable: (target.utable.len() - Variable::NUM_RESERVED_VARS) as u32, variables: target.variables.len() as u32, tag_names: target.tag_names.len() as u32, field_names: target.field_names.len() as u32, @@ -2845,7 +2865,9 @@ impl StorageSubs { variable_slices: target.variable_slices.len() as u32, }; - let range = 0..self.subs.utable.len(); + // The first Variable::NUM_RESERVED_VARS are the same in every subs, + // so we can skip copying them! + let range = Variable::NUM_RESERVED_VARS..self.subs.utable.len(); // fill new slots with empty values target.extend_by(range.len()); @@ -2860,8 +2882,6 @@ impl StorageSubs { let new_descriptor = Descriptor { rank: descriptor.rank, mark: descriptor.mark, - // rank: Rank::NONE, - // mark: Mark::NONE, copy: OptVariable::NONE, content: new_content, }; @@ -2998,8 +3018,12 @@ impl StorageSubs { } fn offset_variable(offsets: &StorageSubsOffsets, variable: Variable) -> Variable { - let new_index = variable.0 + offsets.utable; - Variable(new_index) + if variable.index() < Variable::FIRST_USER_SPACE_VAR.index() { + variable + } else { + let new_index = variable.0 + offsets.utable; + Variable(new_index) + } } fn offset_variable_slice( @@ -3019,20 +3043,37 @@ pub fn deep_copy_var_to( ) -> Variable { let rank = Rank::toplevel(); - let copy = deep_copy_var_to_help(source, target, rank, var); + // capacity based on the false hello world program + let arena = bumpalo::Bump::with_capacity(4 * 1024); - source.restore(var); + let mut visited = bumpalo::collections::Vec::with_capacity_in(256, &arena); + + let copy = deep_copy_var_to_help(&arena, &mut visited, source, target, rank, var); + + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + for var in visited { + let descriptor = source.get_ref_mut(var); + + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + } copy } -fn deep_copy_var_to_help( - // source: &mut Subs, // mut to set the copy +fn deep_copy_var_to_help<'a>( + arena: &'a bumpalo::Bump, + visited: &mut bumpalo::collections::Vec<'a, Variable>, source: &mut Subs, target: &mut Subs, max_rank: Rank, var: Variable, ) -> Variable { + use bumpalo::collections::Vec; use Content::*; use FlatType::*; @@ -3042,7 +3083,7 @@ fn deep_copy_var_to_help( debug_assert!(target.contains(copy)); return copy; } else if desc.rank != Rank::NONE { - // DO NOTHING + // DO NOTHING, Fall through // // The original deep_copy_var can do // return var; @@ -3051,6 +3092,8 @@ fn deep_copy_var_to_help( // should only return variables in the target } + visited.push(var); + let make_descriptor = |content| Descriptor { content, rank: max_rank, @@ -3058,58 +3101,57 @@ fn deep_copy_var_to_help( copy: OptVariable::NONE, }; - let content = desc.content; - // let copy = target.fresh(make_descriptor(content.clone())); let copy = target.fresh_unnamed_flex_var(); - // pools.get_mut(max_rank).push(copy); - // Link the original variable to the new variable. This lets us // avoid making multiple copies of the variable we are instantiating. // // Need to do this before recursively copying to avoid looping. - - source.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); + source.modify(var, |descriptor| { + descriptor.mark = Mark::NONE; + descriptor.copy = copy.into(); + }); // Now we recursively copy the content of the variable. // We have already marked the variable as copied, so we // will not repeat this work or crawl this variable again. - match content { + match desc.content { Structure(flat_type) => { let new_flat_type = match flat_type { Apply(symbol, args) => { - let mut new_arg_vars = Vec::with_capacity(args.len()); + let mut new_args = Vec::with_capacity_in(args.len(), arena); for index in args.into_iter() { let var = source[index]; - let copy_var = deep_copy_var_to_help(source, target, max_rank, var); - new_arg_vars.push(copy_var); + new_args.push(deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + )); } - let arg_vars = VariableSubsSlice::insert_into_subs(target, new_arg_vars); + let arg_vars = VariableSubsSlice::insert_into_subs(target, new_args); Apply(symbol, arg_vars) } Func(arg_vars, closure_var, ret_var) => { - let new_ret_var = deep_copy_var_to_help(source, target, max_rank, ret_var); + let new_ret_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, ret_var); - let new_closure_var = - deep_copy_var_to_help(source, target, max_rank, closure_var); + let new_closure_var = deep_copy_var_to_help( + arena, + visited, + source, + target, + max_rank, + closure_var, + ); - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + let mut new_arg_vars = Vec::with_capacity_in(arg_vars.len(), arena); for index in arg_vars.into_iter() { let var = source[index]; - let copy_var = deep_copy_var_to_help(source, target, max_rank, var); + let copy_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, var); new_arg_vars.push(copy_var); } @@ -3122,11 +3164,13 @@ fn deep_copy_var_to_help( Record(fields, ext_var) => { let record_fields = { - let mut new_vars = Vec::with_capacity(fields.len()); + let mut new_vars = Vec::with_capacity_in(fields.len(), arena); for index in fields.iter_variables() { let var = source[index]; - let copy_var = deep_copy_var_to_help(source, target, max_rank, var); + let copy_var = deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + ); new_vars.push(copy_var); } @@ -3157,21 +3201,24 @@ fn deep_copy_var_to_help( Record( record_fields, - deep_copy_var_to_help(source, target, max_rank, ext_var), + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var), ) } TagUnion(tags, ext_var) => { - let new_ext = deep_copy_var_to_help(source, target, max_rank, ext_var); + let new_ext = + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var); - let mut new_variable_slices = Vec::with_capacity(tags.len()); + let mut new_variable_slices = Vec::with_capacity_in(tags.len(), arena); - let mut new_variables = Vec::new(); + let mut new_variables = Vec::with_capacity_in(tags.len(), arena); for index in tags.variables() { let slice = source[index]; for var_index in slice { let var = source[var_index]; - let new_var = deep_copy_var_to_help(source, target, max_rank, var); + let new_var = deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + ); new_variables.push(new_var); } @@ -3215,19 +3262,21 @@ fn deep_copy_var_to_help( FunctionOrTagUnion( new_tag_name, symbol, - deep_copy_var_to_help(source, target, max_rank, ext_var), + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var), ) } RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut new_variable_slices = Vec::with_capacity(tags.len()); + let mut new_variable_slices = Vec::with_capacity_in(tags.len(), arena); - let mut new_variables = Vec::new(); + let mut new_variables = Vec::with_capacity_in(tags.len(), arena); for index in tags.variables() { let slice = source[index]; for var_index in slice { let var = source[var_index]; - let new_var = deep_copy_var_to_help(source, target, max_rank, var); + let new_var = deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + ); new_variables.push(new_var); } @@ -3260,8 +3309,10 @@ fn deep_copy_var_to_help( let union_tags = UnionTags::from_slices(new_tag_names, new_variables); - let new_ext = deep_copy_var_to_help(source, target, max_rank, ext_var); - let new_rec_var = deep_copy_var_to_help(source, target, max_rank, rec_var); + let new_ext = + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var); + let new_rec_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, rec_var); RecursiveTagUnion(new_rec_var, union_tags, new_ext) } @@ -3278,7 +3329,8 @@ fn deep_copy_var_to_help( opt_name, structure, } => { - let new_structure = deep_copy_var_to_help(source, target, max_rank, structure); + let new_structure = + deep_copy_var_to_help(arena, visited, source, target, max_rank, structure); debug_assert!((new_structure.index() as usize) < target.len()); @@ -3300,11 +3352,11 @@ fn deep_copy_var_to_help( } Alias(symbol, mut args, real_type_var) => { - let mut new_vars = Vec::with_capacity(args.variables().len()); + let mut new_vars = Vec::with_capacity_in(args.variables().len(), arena); for var_index in args.variables() { let var = source[var_index]; - let new_var = deep_copy_var_to_help(source, target, max_rank, var); + let new_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); new_vars.push(new_var); } @@ -3317,7 +3369,8 @@ fn deep_copy_var_to_help( args.lowercases_start = target.field_names.len() as u32; target.field_names.extend(lowercases.iter().cloned()); - let new_real_type_var = deep_copy_var_to_help(source, target, max_rank, real_type_var); + let new_real_type_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, real_type_var); let new_content = Alias(symbol, args, new_real_type_var); target.set(copy, make_descriptor(new_content));