Consolidate WorldAbilities and solve Phase

This commit is contained in:
Ayaz Hafiz 2022-06-13 10:23:00 -04:00
parent f5c4528919
commit f21d68c9fb
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
8 changed files with 244 additions and 172 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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<RwLock<MutMap<ModuleId, (AbilitiesStore, ExposedTypesStorageSubs)>>>,
}
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<T, F>(&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<T, F>(&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

View file

@ -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" }

View file

@ -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,
),
);
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<ExternalSpecializations>,
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<ExternalSpecializations>,
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

View file

@ -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,7 +3775,9 @@ 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| {
let specialization_symbol =
env.abilities
.with_module_abilities_store(env.home, |store| {
store
.get_resolved(specialization_id)
.expect("Specialization was never made!")
@ -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,7 +5192,9 @@ 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| {
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!")
});
@ -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

View file

@ -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<RwLock<MutMap<ModuleId, (AbilitiesStore, ExposedTypesStorageSubs)>>>;
pub enum WorldAbilities<'a> {
BigWorld(AllModuleAbilities),
TinyWorld(&'a AbilitiesStore),
}
impl WorldAbilities<'_> {
pub fn with_module_store<T, F>(&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

View file

@ -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<ErrorType>),
@ -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<T, F>(&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<T, F>(&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<P: Phase>(
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<P: Phase>(
subs: &mut Subs,
arena: &Bump,
pools: &mut Pools,
abilities: &WorldAbilities,
this_lambda_set: Variable,
phase: Phase,
phase: &P,
) {
let LambdaSet {
solved,
@ -1865,19 +1898,19 @@ fn compact_lambda_set(
}
let opaque_home = opaque.module_id();
let specialized_lambda_set = abilities.with_module_store(opaque_home, |abilities_store| {
let specialized_lambda_set =
phase.with_module_abilities_store(opaque_home, |abilities_store| {
let opt_specialization = abilities_store.get_specialization(member, *opaque);
match (phase, opt_specialization) {
(Phase::Solve, None) => {
match (P::IS_LATE, opt_specialization) {
(false, None) => {
// doesn't specialize, we'll have reported an error for this
Spec::Skip
}
(Phase::Late { home }, None) => {
(true, None) => {
internal_error!(
"expected to know a specialization for {:?}#{:?}, but it wasn't found (from module {:?})",
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
opaque,
member,
home
);
}
(_, Some(specialization)) => {
@ -1891,18 +1924,13 @@ fn compact_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