diff --git a/Cargo.lock b/Cargo.lock index 46dd0c8d3b..e70f253e86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3855,6 +3855,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "roc_can", + "roc_collections", "roc_module", "roc_solve", "roc_types", @@ -3910,6 +3911,7 @@ dependencies = [ "roc_constrain", "roc_debug_flags", "roc_error_macros", + "roc_late_solve", "roc_module", "roc_mono", "roc_parse", diff --git a/compiler/late_solve/Cargo.toml b/compiler/late_solve/Cargo.toml index a23def567d..b4c12f9df1 100644 --- a/compiler/late_solve/Cargo.toml +++ b/compiler/late_solve/Cargo.toml @@ -11,4 +11,5 @@ roc_can = { path = "../can" } roc_module = { path = "../module" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } +roc_collections = { path = "../collections" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/late_solve/src/lib.rs b/compiler/late_solve/src/lib.rs index 0541c1acb5..81b9f70211 100644 --- a/compiler/late_solve/src/lib.rs +++ b/compiler/late_solve/src/lib.rs @@ -1,27 +1,141 @@ //! Crate roc_late_solve exposes type unification and solving primitives from the perspective of //! the compiler backend. +use std::sync::{Arc, RwLock}; + use bumpalo::Bump; +use roc_can::abilities::AbilitiesStore; +use roc_collections::MutMap; use roc_module::symbol::ModuleId; use roc_solve::solve::{compact_lambda_sets_of_vars, Phase, Pools}; -use roc_types::subs::{Subs, Variable}; +use roc_types::subs::Content; +use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable}; use roc_unify::unify::{unify as unify_unify, Mode, Unified}; pub use roc_solve::solve::instantiate_rigids; pub use roc_solve::ability::resolve_ability_specialization; -pub use roc_solve::ability::{Resolved, WorldAbilities}; +pub use roc_solve::ability::Resolved; #[derive(Debug)] pub struct UnificationFailed; +/// A global view of all abilities across all modules in a program under compilation. +/// [WorldAbilities::insert] adds a solved abilities store for a module to the global map. +/// Use [WorldAbilities::clone] to get a thread-safe, reference-counted copy of the global map. +/// Note that this is indeed a map shared amongst everything during a compilation. +#[derive(Default, Debug)] +pub struct WorldAbilities { + world: Arc>>, +} + +impl WorldAbilities { + pub fn insert( + &mut self, + module: ModuleId, + store: AbilitiesStore, + exposed_types: ExposedTypesStorageSubs, + ) { + let old_store = self + .world + .write() + .unwrap() + .insert(module, (store, exposed_types)); + + debug_assert!(old_store.is_none(), "{:?} abilities not new", module); + } + + pub fn clone(&self) -> Self { + Self { + world: Arc::clone(&self.world), + } + } +} + +pub enum AbilitiesView<'a> { + World(WorldAbilities), + Module(&'a AbilitiesStore), +} + +impl AbilitiesView<'_> { + #[inline(always)] + pub fn with_module_abilities_store(&self, module: ModuleId, mut f: F) -> T + where + F: FnMut(&AbilitiesStore) -> T, + { + match self { + AbilitiesView::World(wa) => { + let world = wa.world.read().unwrap(); + let (module_store, _module_types) = world.get(&module).unwrap(); + f(module_store) + } + AbilitiesView::Module(store) => f(store), + } + } +} + +pub struct LatePhase<'a> { + home: ModuleId, + abilities: &'a AbilitiesView<'a>, +} + +impl Phase for LatePhase<'_> { + const IS_LATE: bool = true; + + #[inline(always)] + fn with_module_abilities_store(&self, module: ModuleId, f: F) -> T + where + F: FnMut(&AbilitiesStore) -> T, + { + self.abilities.with_module_abilities_store(module, f) + } + + #[inline(always)] + fn copy_lambda_set_var_to_home_subs( + &self, + external_lambda_set_var: Variable, + external_module_id: ModuleId, + target_subs: &mut Subs, + ) -> Variable { + match (external_module_id == self.home, self.abilities) { + (true, _) | (false, AbilitiesView::Module(_)) => { + debug_assert!(matches!( + target_subs.get_content_without_compacting(external_lambda_set_var), + Content::LambdaSet(..) + )); + external_lambda_set_var + } + (false, AbilitiesView::World(wa)) => { + let mut world = wa.world.write().unwrap(); + let (_module_store, module_types) = world.get_mut(&external_module_id).unwrap(); + + let storage_lambda_set_var = *module_types + .stored_specialization_lambda_set_vars + .get(&external_lambda_set_var) + .unwrap(); + let copied = module_types + .storage_subs + .export_variable_to(target_subs, storage_lambda_set_var); + let our_lambda_set_var = copied.variable; + + debug_assert!(matches!( + target_subs.get_content_without_compacting(our_lambda_set_var), + Content::LambdaSet(..) + )); + + our_lambda_set_var + } + } + } +} + /// Unifies two variables and performs lambda set compaction. /// Ranks and other ability demands are disregarded. pub fn unify( home: ModuleId, arena: &Bump, subs: &mut Subs, - world_abilities: &WorldAbilities, + abilities: &AbilitiesView, left: Variable, right: Variable, ) -> Result<(), UnificationFailed> { @@ -34,13 +148,14 @@ pub fn unify( } => { let mut pools = Pools::default(); + let late_phase = LatePhase { home, abilities }; + compact_lambda_sets_of_vars( subs, arena, &mut pools, - world_abilities, lambda_sets_to_specialize, - Phase::Late { home }, + &late_phase, ); // Pools are only used to keep track of variable ranks for generalization purposes. // Since we break generalization during monomorphization, `pools` is irrelevant diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 1e171f226b..fe6be842cb 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -18,6 +18,7 @@ roc_problem = { path = "../problem" } roc_unify = { path = "../unify" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } +roc_late_solve = { path = "../late_solve" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_reporting = { path = "../../reporting" } diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index a80cee3590..c681683194 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -23,6 +23,7 @@ use roc_debug_flags::{ ROC_PRINT_LOAD_LOG, }; use roc_error_macros::internal_error; +use roc_late_solve::{AbilitiesView, WorldAbilities}; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ IdentIds, IdentIdsByModule, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, @@ -41,7 +42,6 @@ use roc_parse::module::module_defs; use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_region::all::{LineInfo, Loc, Region}; use roc_reporting::report::RenderTarget; -use roc_solve::ability::{AllModuleAbilities, WorldAbilities}; use roc_solve::module::SolvedModule; use roc_solve::solve; use roc_target::TargetInfo; @@ -442,7 +442,6 @@ fn start_phase<'a>( .unwrap(); let FoundSpecializationsModule { - module_id, ident_ids, subs, procs_base, @@ -458,16 +457,13 @@ fn start_phase<'a>( internal_error!("Exposed types for {:?} missing", module_id) }); - let old_store = &state.inverse_world_abilities.write().unwrap().insert( + // Add our abilities to the world. + state.world_abilities.insert( module_id, - ( - abilities_store, - our_exposed_types.exposed_types_storage_subs, - ), + abilities_store, + our_exposed_types.exposed_types_storage_subs, ); - debug_assert!(old_store.is_none(), "{:?} abilities not new", module_id); - (ident_ids, subs, procs_base, layout_cache, module_timing) } else { let LateSpecializationsModule { @@ -493,7 +489,7 @@ fn start_phase<'a>( layout_cache, specializations_we_must_make, module_timing, - world_abilities: Arc::clone(&state.inverse_world_abilities), + world_abilities: state.world_abilities.clone(), } } } @@ -596,7 +592,6 @@ pub struct TypeCheckedModule<'a> { #[derive(Debug)] struct FoundSpecializationsModule<'a> { - module_id: ModuleId, ident_ids: IdentIds, layout_cache: LayoutCache<'a>, procs_base: ProcsBase<'a>, @@ -818,8 +813,8 @@ struct State<'a> { pub render: RenderTarget, - /// At a given point in time, this map contains the abilities stores in reverse order - pub inverse_world_abilities: AllModuleAbilities, + /// All abilities across all modules. + pub world_abilities: WorldAbilities, make_specializations_pass: MakeSpecializationsPass, @@ -867,8 +862,8 @@ impl<'a> State<'a> { layout_caches: std::vec::Vec::with_capacity(number_of_workers), cached_subs: Arc::new(Mutex::new(cached_subs)), render, - inverse_world_abilities: Default::default(), make_specializations_pass: MakeSpecializationsPass::Pass(1), + world_abilities: Default::default(), } } } @@ -996,7 +991,7 @@ enum BuildTask<'a> { layout_cache: LayoutCache<'a>, specializations_we_must_make: Vec, module_timing: ModuleTiming, - world_abilities: AllModuleAbilities, + world_abilities: WorldAbilities, }, } @@ -2287,7 +2282,6 @@ fn update<'a>( .extend(procs_base.module_thunks.iter().copied()); let found_specializations_module = FoundSpecializationsModule { - module_id, ident_ids, layout_cache, procs_base, @@ -4368,7 +4362,7 @@ fn make_specializations<'a>( specializations_we_must_make: Vec, mut module_timing: ModuleTiming, target_info: TargetInfo, - world_abilities: AllModuleAbilities, + world_abilities: WorldAbilities, ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); let mut update_mode_ids = UpdateModeIds::new(); @@ -4382,7 +4376,7 @@ fn make_specializations<'a>( update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, - abilities: WorldAbilities::BigWorld(Arc::clone(&world_abilities)), + abilities: AbilitiesView::World(world_abilities), }; let mut procs = Procs::new_in(arena); @@ -4468,7 +4462,10 @@ fn build_pending_specializations<'a>( update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, - abilities: WorldAbilities::TinyWorld(&abilities_store), + // NB: for getting pending specializations the module view is enough because we only need + // to know the types and abilities in our modules. Only for building *all* specializations + // do we need a global view. + abilities: AbilitiesView::Module(&abilities_store), }; // Add modules' decls to Procs diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 080a13defb..2e7d489660 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -19,7 +19,7 @@ use roc_debug_flags::{ use roc_error_macros::todo_abilities; use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_late_solve::{ - instantiate_rigids, resolve_ability_specialization, Resolved, UnificationFailed, WorldAbilities, + instantiate_rigids, resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed, }; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -1268,7 +1268,7 @@ pub struct Env<'a, 'i> { pub target_info: TargetInfo, pub update_mode_ids: &'i mut UpdateModeIds, pub call_specialization_counter: u32, - pub abilities: WorldAbilities<'i>, + pub abilities: AbilitiesView<'i>, } impl<'a, 'i> Env<'a, 'i> { @@ -3775,11 +3775,13 @@ pub fn with_hole<'a>( specialize_naked_symbol(env, variable, procs, layout_cache, assigned, hole, symbol) } AbilityMember(_member, specialization_id, _) => { - let specialization_symbol = env.abilities.with_module_store(env.home, |store| { - store - .get_resolved(specialization_id) - .expect("Specialization was never made!") - }); + let specialization_symbol = + env.abilities + .with_module_abilities_store(env.home, |store| { + store + .get_resolved(specialization_id) + .expect("Specialization was never made!") + }); specialize_naked_symbol( env, @@ -4786,7 +4788,7 @@ pub fn with_hole<'a>( UnspecializedExpr(symbol) => { match procs.ability_member_aliases.get(symbol).unwrap() { &self::AbilityMember(member) => { - let resolved_proc = env.abilities.with_module_store(env.home, |store| + let resolved_proc = env.abilities.with_module_abilities_store(env.home, |store| resolve_ability_specialization(env.subs, store, member, fn_var) .expect("Recorded as an ability member, but it doesn't have a specialization") ); @@ -5166,7 +5168,7 @@ fn late_resolve_ability_specialization<'a>( ) -> Symbol { let opt_resolved = env .abilities - .with_module_store(env.home, |store| store.get_resolved(specialization_id)); + .with_module_abilities_store(env.home, |store| store.get_resolved(specialization_id)); if let Some(spec_symbol) = opt_resolved { // Fast path: specialization is monomorphic, was found during solving. @@ -5190,10 +5192,12 @@ fn late_resolve_ability_specialization<'a>( env.subs[spec_symbol_index] } else { // Otherwise, resolve by checking the able var. - let specialization = env.abilities.with_module_store(env.home, |store| { - resolve_ability_specialization(env.subs, store, member, specialization_var) - .expect("Ability specialization is unknown - code generation cannot proceed!") - }); + let specialization = env + .abilities + .with_module_abilities_store(env.home, |store| { + resolve_ability_specialization(env.subs, store, member, specialization_var) + .expect("Ability specialization is unknown - code generation cannot proceed!") + }); match specialization { Resolved::Specialization(symbol) => symbol, @@ -7028,7 +7032,7 @@ where { let is_ability_member = env .abilities - .with_module_store(env.home, |store| store.is_ability_member_name(right)); + .with_module_abilities_store(env.home, |store| store.is_ability_member_name(right)); if is_ability_member { procs diff --git a/compiler/solve/src/ability.rs b/compiler/solve/src/ability.rs index 0b3a785c4c..c9abaf2747 100644 --- a/compiler/solve/src/ability.rs +++ b/compiler/solve/src/ability.rs @@ -1,14 +1,10 @@ -use std::sync::{Arc, RwLock}; - use roc_can::abilities::AbilitiesStore; use roc_can::expr::PendingDerives; -use roc_collections::{MutMap, VecMap}; +use roc_collections::VecMap; use roc_error_macros::internal_error; -use roc_module::symbol::{ModuleId, Symbol}; +use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; -use roc_types::subs::{ - Content, ExposedTypesStorageSubs, FlatType, GetSubsSlice, Rank, Subs, Variable, -}; +use roc_types::subs::{Content, FlatType, GetSubsSlice, Rank, Subs, Variable}; use roc_types::types::{AliasKind, Category, ErrorType, PatternCategory}; use roc_unify::unify::MustImplementConstraints; use roc_unify::unify::{MustImplementAbility, Obligated}; @@ -16,78 +12,6 @@ use roc_unify::unify::{MustImplementAbility, Obligated}; use crate::solve::{instantiate_rigids, type_to_var}; use crate::solve::{Aliases, Pools, TypeError}; -pub type AllModuleAbilities = - Arc>>; - -pub enum WorldAbilities<'a> { - BigWorld(AllModuleAbilities), - TinyWorld(&'a AbilitiesStore), -} - -impl WorldAbilities<'_> { - pub fn with_module_store(&self, module: ModuleId, mut f: F) -> T - where - F: FnMut(&AbilitiesStore) -> T, - { - match self { - WorldAbilities::BigWorld(world) => { - let world = world.read().unwrap(); - let (module_store, _module_types) = world.get(&module).unwrap(); - f(module_store) - } - WorldAbilities::TinyWorld(store) => f(store), - } - } - - pub fn copy_lambda_set_var_into( - &self, - external_lambda_set_var: Variable, - external_module_id: ModuleId, - target_subs: &mut Subs, - target_module_id: ModuleId, - ) -> Variable { - match (external_module_id == target_module_id, self) { - (true, _) => { - debug_assert!(matches!( - target_subs.get_content_without_compacting(external_lambda_set_var), - Content::LambdaSet(..) - )); - external_lambda_set_var - } - (false, Self::TinyWorld(_)) => { - // If we're only aware of our module's abilities store, the var must be in our - // module store. Even if the specialization lambda set comes from another module, - // we would have taken care to import it during multi-module solving. - debug_assert!(matches!( - target_subs.get_content_without_compacting(external_lambda_set_var), - Content::LambdaSet(..) - )); - external_lambda_set_var - } - (false, Self::BigWorld(world)) => { - let mut world = world.write().unwrap(); - let (_module_store, module_types) = world.get_mut(&external_module_id).unwrap(); - - let storage_lambda_set_var = *module_types - .stored_specialization_lambda_set_vars - .get(&external_lambda_set_var) - .unwrap(); - let copied = module_types - .storage_subs - .export_variable_to(target_subs, storage_lambda_set_var); - let our_lambda_set_var = copied.variable; - - debug_assert!(matches!( - target_subs.get_content_without_compacting(our_lambda_set_var), - Content::LambdaSet(..) - )); - - our_lambda_set_var - } - } - } -} - #[derive(Debug, Clone)] pub enum AbilityImplError { /// Promote this to an error that the type does not fully implement an ability diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 305a615f73..58550bb4ed 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,6 +1,6 @@ use crate::ability::{ resolve_ability_specialization, type_implementing_specialization, AbilityImplError, - DeferredObligations, DeriveKey, PendingDerivesTable, Resolved, Unfulfilled, WorldAbilities, + DeferredObligations, DeriveKey, PendingDerivesTable, Resolved, Unfulfilled, }; use bumpalo::Bump; use roc_can::abilities::{AbilitiesStore, MemberSpecialization}; @@ -81,21 +81,6 @@ use roc_unify::unify::{unify, Mode, Obligated, Unified::*}; // Ranks are used to limit the number of type variables considered for generalization. Only those inside // of the let (so those used in inferring the type of `\x -> x`) are considered. -/// What phase in the compiler is reaching out to solve types. -/// This is important to distinguish subtle differences in the behavior of the solving algorithm. -#[derive(Clone, Copy)] -pub enum Phase { - /// The regular type-solving phase. - /// Here we can assume that some information is still unknown, and react to that. - Solve, - /// Calls into solve during later phases of compilation, namely monomorphization. - /// Here we expect all information is known. - Late { - /// Module being solved - home: ModuleId, - }, -} - #[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), @@ -497,6 +482,57 @@ impl Pools { } } +/// What phase in the compiler is reaching out to solve types. +/// This is important to distinguish subtle differences in the behavior of the solving algorithm. +pub trait Phase { + /// The regular type-solving phase, or during some later phase of compilation. + /// During the solving phase we must anticipate that some information is still unknown and react to + /// that; during late phases, we expect that all information is resolved. + const IS_LATE: bool; + + fn with_module_abilities_store(&self, module: ModuleId, f: F) -> T + where + F: FnMut(&AbilitiesStore) -> T; + + fn copy_lambda_set_var_to_home_subs( + &self, + external_lambda_set_var: Variable, + external_module_id: ModuleId, + home_subs: &mut Subs, + ) -> Variable; +} + +struct SolvePhase<'a> { + abilities_store: &'a AbilitiesStore, +} +impl Phase for SolvePhase<'_> { + const IS_LATE: bool = false; + + fn with_module_abilities_store(&self, _module: ModuleId, mut f: F) -> T + where + F: FnMut(&AbilitiesStore) -> T, + { + // During solving we're only aware of our module's abilities store. + f(self.abilities_store) + } + + fn copy_lambda_set_var_to_home_subs( + &self, + external_lambda_set_var: Variable, + _external_module_id: ModuleId, + home_subs: &mut Subs, + ) -> Variable { + // During solving we're only aware of our module's abilities store, the var must + // be in our module store. Even if the specialization lambda set comes from another + // module, we should have taken care to import it before starting solving in this module. + debug_assert!(matches!( + home_subs.get_content_without_compacting(external_lambda_set_var), + Content::LambdaSet(..) + )); + external_lambda_set_var + } +} + #[derive(Clone)] struct State { env: Env, @@ -576,9 +612,8 @@ fn run_in_place( subs, &arena, &mut pools, - &WorldAbilities::TinyWorld(abilities_store), deferred_uls_to_resolve, - Phase::Solve, + &SolvePhase { abilities_store }, ); state.env @@ -1779,13 +1814,12 @@ fn find_specialization_lambda_sets( (leftover_uls, specialization_lambda_sets) } -pub fn compact_lambda_sets_of_vars( +pub fn compact_lambda_sets_of_vars( subs: &mut Subs, arena: &Bump, pools: &mut Pools, - abilities: &WorldAbilities, uls_of_var: UlsOfVar, - phase: Phase, + phase: &P, ) { let mut seen = VecSet::default(); for (_, lambda_sets) in uls_of_var.drain() { @@ -1795,20 +1829,19 @@ pub fn compact_lambda_sets_of_vars( continue; } - compact_lambda_set(subs, arena, pools, abilities, root_lset, phase); + compact_lambda_set(subs, arena, pools, root_lset, phase); seen.insert(root_lset); } } } -fn compact_lambda_set( +fn compact_lambda_set( subs: &mut Subs, arena: &Bump, pools: &mut Pools, - abilities: &WorldAbilities, this_lambda_set: Variable, - phase: Phase, + phase: &P, ) { let LambdaSet { solved, @@ -1865,44 +1898,39 @@ fn compact_lambda_set( } let opaque_home = opaque.module_id(); - let specialized_lambda_set = abilities.with_module_store(opaque_home, |abilities_store| { - let opt_specialization = abilities_store.get_specialization(member, *opaque); - match (phase, opt_specialization) { - (Phase::Solve, None) => { - // doesn't specialize, we'll have reported an error for this - Spec::Skip + let specialized_lambda_set = + phase.with_module_abilities_store(opaque_home, |abilities_store| { + let opt_specialization = abilities_store.get_specialization(member, *opaque); + match (P::IS_LATE, opt_specialization) { + (false, None) => { + // doesn't specialize, we'll have reported an error for this + Spec::Skip + } + (true, None) => { + internal_error!( + "expected to know a specialization for {:?}#{:?}, but it wasn't found", + opaque, + member, + ); + } + (_, Some(specialization)) => { + let specialized_lambda_set = *specialization + .specialization_lambda_sets + .get(®ion) + .expect("lambda set region not resolved"); + Spec::Some(specialized_lambda_set) + } } - (Phase::Late { home }, None) => { - internal_error!( - "expected to know a specialization for {:?}#{:?}, but it wasn't found (from module {:?})", - opaque, - member, - home - ); - } - (_, Some(specialization)) => { - let specialized_lambda_set = *specialization - .specialization_lambda_sets - .get(®ion) - .expect("lambda set region not resolved"); - Spec::Some(specialized_lambda_set) - } - } - }); + }); let specialized_lambda_set = match specialized_lambda_set { - Spec::Some(lset) => match phase { - Phase::Solve => lset, - Phase::Late { home } => { - abilities.copy_lambda_set_var_into(lset, opaque_home, subs, home) - } - }, + Spec::Some(lset) => phase.copy_lambda_set_var_to_home_subs(lset, opaque_home, subs), Spec::Skip => continue, }; // Ensure the specialization lambda set is already compacted. if subs.get_root_key(specialized_lambda_set) != subs.get_root_key(this_lambda_set) { - compact_lambda_set(subs, arena, pools, abilities, specialized_lambda_set, phase); + compact_lambda_set(subs, arena, pools, specialized_lambda_set, phase); } // Ensure the specialization lambda set we'll unify with is not a generalized one, but one