Collect awaited lambda set specializations to be solved when a specialization is known

Despite our best efforts, sometimes we still can't specialize lambda
sets on the fly, if a specialization lambda set's specialization type
isn't yet well-known! This commit adds an `AwaitingSpecializations`
data structure to keep track of the lambda sets blocked for
specialization behind a specialization's full resolution in the module.
After the specialization is resolved, its blocked lambda sets can be
eagerly compacted.
This commit is contained in:
Ayaz Hafiz 2022-07-26 12:45:37 -04:00
parent 350d9cd59b
commit f2d4bf20ba
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
2 changed files with 328 additions and 77 deletions

View file

@ -3,7 +3,9 @@ use crate::ability::{
CheckedDerives, ObligationCache, PendingDerivesTable, Resolved,
};
use crate::module::Solved;
use crate::specialize::{compact_lambda_sets_of_vars, DerivedEnv, SolvePhase};
use crate::specialize::{
compact_lambda_sets_of_vars, AwaitingSpecializations, CompactionResult, DerivedEnv, SolvePhase,
};
use bumpalo::Bump;
use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo};
use roc_can::constraint::Constraint::{self, *};
@ -20,7 +22,7 @@ use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::{ModuleId, Symbol};
use roc_problem::can::CycleEntry;
use roc_region::all::{Loc, Region};
use roc_region::all::Loc;
use roc_solve_problem::TypeError;
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, LambdaSet, Mark,
@ -558,6 +560,7 @@ fn run_in_place(
let arena = Bump::new();
let mut obligation_cache = ObligationCache::default();
let mut awaiting_specializations = AwaitingSpecializations::default();
let pending_derives = PendingDerivesTable::new(subs, aliases, pending_derives);
let CheckedDerives {
@ -566,9 +569,10 @@ fn run_in_place(
} = obligation_cache.check_derives(subs, abilities_store, pending_derives);
problems.extend(derives_problems);
// Because we don't know what ability specializations are available until the entire module is
// solved, we must wait to solve unspecialized lambda sets then.
let mut deferred_uls_to_resolve = UlsOfVar::default();
let derived_env = DerivedEnv {
derived_module: &derived_module,
exposed_types: exposed_by_module,
};
let state = solve(
&arena,
@ -582,30 +586,9 @@ fn run_in_place(
constraint,
abilities_store,
&mut obligation_cache,
&mut deferred_uls_to_resolve,
);
// Now that the module has been solved, we can run through and check all
// types claimed to implement abilities. This will also tell us what derives
// are legal, which we need to register.
let derived_env = DerivedEnv {
derived_module: &derived_module,
exposed_types: exposed_by_module,
};
let new_must_implement = compact_lambda_sets_of_vars(
subs,
&mut awaiting_specializations,
&derived_env,
&arena,
&mut pools,
deferred_uls_to_resolve,
&SolvePhase { abilities_store },
);
problems.extend(obligation_cache.check_obligations(
subs,
abilities_store,
new_must_implement,
AbilityImplError::DoesNotImplement,
));
state.env
}
@ -659,7 +642,8 @@ fn solve(
constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
obligation_cache: &mut ObligationCache,
deferred_uls_to_resolve: &mut UlsOfVar,
awaiting_specializations: &mut AwaitingSpecializations,
derived_env: &DerivedEnv,
) -> State {
let initial = Work::Constraint {
env: &Env::default(),
@ -715,11 +699,13 @@ fn solve(
check_ability_specialization(
arena,
subs,
derived_env,
pools,
rank,
abilities_store,
obligation_cache,
awaiting_specializations,
problems,
deferred_uls_to_resolve,
*symbol,
*loc_var,
);
@ -822,11 +808,13 @@ fn solve(
check_ability_specialization(
arena,
subs,
derived_env,
pools,
rank,
abilities_store,
obligation_cache,
awaiting_specializations,
problems,
deferred_uls_to_resolve,
*symbol,
*loc_var,
);
@ -892,7 +880,17 @@ fn solve(
);
problems.extend(new_problems);
}
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
lambda_sets_to_specialize,
);
state
}
@ -942,7 +940,21 @@ fn solve(
} => {
introduce(subs, rank, pools, &vars);
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
let CompactionResult {
obligations,
awaiting_specialization,
} = compact_lambda_sets_of_vars(
subs,
&derived_env,
&arena,
pools,
lambda_sets_to_specialize,
&SolvePhase { abilities_store },
);
// implement obligations not reported
_ = obligations;
// but awaited specializations must be recorded
awaiting_specializations.union(awaiting_specialization);
state
}
@ -1014,7 +1026,17 @@ fn solve(
);
problems.extend(new_problems);
}
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
lambda_sets_to_specialize,
);
state
}
@ -1094,7 +1116,17 @@ fn solve(
);
problems.extend(new_problems);
}
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
lambda_sets_to_specialize,
);
state
}
@ -1267,7 +1299,17 @@ fn solve(
);
problems.extend(new_problems);
}
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
lambda_sets_to_specialize,
);
state
}
@ -1373,7 +1415,17 @@ fn solve(
must_implement_ability,
AbilityImplError::DoesNotImplement,
));
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
lambda_sets_to_specialize,
);
// Case 1: unify error types, but don't check exhaustiveness.
// Case 2: run exhaustiveness to check for redundant branches.
@ -1543,6 +1595,37 @@ fn solve(
state
}
fn compact_lambdas_and_check_obligations(
arena: &Bump,
pools: &mut Pools,
problems: &mut Vec<TypeError>,
subs: &mut Subs,
abilities_store: &mut AbilitiesStore,
obligation_cache: &mut ObligationCache,
awaiting_specialization: &mut AwaitingSpecializations,
derived_env: &DerivedEnv,
lambda_sets_to_specialize: UlsOfVar,
) {
let CompactionResult {
obligations,
awaiting_specialization: new_awaiting,
} = compact_lambda_sets_of_vars(
subs,
&derived_env,
&arena,
pools,
lambda_sets_to_specialize,
&SolvePhase { abilities_store },
);
problems.extend(obligation_cache.check_obligations(
subs,
abilities_store,
obligations,
AbilityImplError::DoesNotImplement,
));
awaiting_specialization.union(new_awaiting);
}
fn open_tag_union(subs: &mut Subs, var: Variable) {
let mut stack = vec![var];
while let Some(var) = stack.pop() {
@ -1589,11 +1672,13 @@ fn open_tag_union(subs: &mut Subs, var: Variable) {
fn check_ability_specialization(
arena: &Bump,
subs: &mut Subs,
derived_env: &DerivedEnv,
pools: &mut Pools,
rank: Rank,
abilities_store: &mut AbilitiesStore,
obligation_cache: &mut ObligationCache,
awaiting_specializations: &mut AwaitingSpecializations,
problems: &mut Vec<TypeError>,
deferred_uls_to_resolve: &mut UlsOfVar,
symbol: Symbol,
symbol_loc_var: Loc<Variable>,
) {
@ -1626,7 +1711,7 @@ fn check_ability_specialization(
Success {
vars,
must_implement_ability,
lambda_sets_to_specialize: other_lambda_sets_to_specialize,
lambda_sets_to_specialize,
extra_metadata: SpecializationLsetCollector(specialization_lambda_sets),
} => {
let specialization_type =
@ -1650,7 +1735,17 @@ fn check_ability_specialization(
})
.collect();
deferred_uls_to_resolve.union(other_lambda_sets_to_specialize);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
lambda_sets_to_specialize,
);
let specialization =
MemberSpecializationInfo::new(symbol, specialization_lambda_sets);
@ -1761,6 +1856,27 @@ fn check_ability_specialization(
abilities_store
.mark_implementation(impl_key, resolved_mark)
.expect("marked as a custom implementation, but not recorded as such");
// Get the lambda sets that are ready for specialization because this ability member
// specializaiton was resolved, and compact them.
let new_lambda_sets_to_specialize =
awaiting_specializations.remove_for_specialized(subs, impl_key);
compact_lambdas_and_check_obligations(
arena,
pools,
problems,
subs,
abilities_store,
obligation_cache,
awaiting_specializations,
derived_env,
new_lambda_sets_to_specialize,
);
debug_assert!(
!awaiting_specializations.waiting_for(impl_key),
"still have lambda sets waiting for {:?}, but it was just resolved",
impl_key
);
}
}

View file

@ -3,7 +3,11 @@
use std::collections::VecDeque;
use bumpalo::Bump;
use roc_can::{abilities::AbilitiesStore, module::ExposedByModule};
use roc_can::{
abilities::{AbilitiesStore, ImplKey},
module::ExposedByModule,
};
use roc_collections::{VecMap, VecSet};
use roc_debug_flags::{dbg_do, ROC_TRACE_COMPACTION};
use roc_derive::SharedDerivedModule;
use roc_derive_key::{DeriveError, DeriveKey};
@ -118,6 +122,62 @@ pub struct DerivedEnv<'a> {
pub exposed_types: &'a ExposedByModule,
}
#[derive(Default)]
pub struct AwaitingSpecializations {
// What variables' specialized lambda sets in `uls_of_var` will be unlocked for specialization
// when an implementation key's specialization is resolved?
waiting: VecMap<ImplKey, VecSet<Variable>>,
uls_of_var: UlsOfVar,
}
impl AwaitingSpecializations {
pub fn remove_for_specialized(&mut self, subs: &Subs, impl_key: ImplKey) -> UlsOfVar {
let spec_variables = self
.waiting
.remove(&impl_key)
.map(|(_, set)| set)
.unwrap_or_default();
let mut result = UlsOfVar::default();
for var in spec_variables {
let target_lambda_sets = self
.uls_of_var
.remove_dependent_unspecialized_lambda_sets(subs, var);
result.extend(var, target_lambda_sets);
}
result
}
pub fn add(
&mut self,
impl_key: ImplKey,
var: Variable,
lambda_sets: impl IntoIterator<Item = Variable>,
) {
self.uls_of_var.extend(var, lambda_sets);
let waiting = self.waiting.get_or_insert(impl_key, Default::default);
waiting.insert(var);
}
pub fn union(&mut self, other: Self) {
for (impl_key, waiting_vars) in other.waiting {
let waiting = self.waiting.get_or_insert(impl_key, Default::default);
waiting.extend(waiting_vars);
}
self.uls_of_var.union(other.uls_of_var);
}
pub fn waiting_for(&self, impl_key: ImplKey) -> bool {
self.waiting.contains_key(&impl_key)
}
}
pub struct CompactionResult {
pub obligations: MustImplementConstraints,
pub awaiting_specialization: AwaitingSpecializations,
}
#[cfg(debug_assertions)]
fn trace_compaction_step_1(subs: &Subs, c_a: Variable, uls_a: &[Variable]) {
let c_a = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(c_a), subs);
@ -246,9 +306,9 @@ pub fn compact_lambda_sets_of_vars<P: Phase>(
pools: &mut Pools,
uls_of_var: UlsOfVar,
phase: &P,
) -> MustImplementConstraints {
// let mut seen = VecSet::default();
) -> CompactionResult {
let mut must_implement = MustImplementConstraints::default();
let mut awaiting_specialization = AwaitingSpecializations::default();
let mut uls_of_var_queue = VecDeque::with_capacity(uls_of_var.len());
uls_of_var_queue.extend(uls_of_var.drain());
@ -374,22 +434,36 @@ pub fn compact_lambda_sets_of_vars<P: Phase>(
// 3. Unify `t_f1 ~ t_f2`.
trace_compact!(3start.);
for l in uls_a {
// let root_lset = subs.get_root_key_without_compacting(l);
// if seen.contains(&root_lset) {
// continue;
// }
let (new_must_implement, new_uls_of_var) =
let compaction_result =
compact_lambda_set(subs, derived_env, arena, pools, c_a, l, phase);
must_implement.extend(new_must_implement);
uls_of_var_queue.extend(new_uls_of_var.drain());
// seen.insert(root_lset);
match compaction_result {
OneCompactionResult::Compacted {
new_obligations,
new_lambda_sets_to_specialize,
} => {
must_implement.extend(new_obligations);
uls_of_var_queue.extend(new_lambda_sets_to_specialize.drain());
}
OneCompactionResult::MustWaitForSpecialization(impl_key) => {
awaiting_specialization.add(impl_key, c_a, [l])
}
}
}
}
must_implement
CompactionResult {
obligations: must_implement,
awaiting_specialization,
}
}
enum OneCompactionResult {
Compacted {
new_obligations: MustImplementConstraints,
new_lambda_sets_to_specialize: UlsOfVar,
},
MustWaitForSpecialization(ImplKey),
}
#[must_use]
@ -402,7 +476,7 @@ fn compact_lambda_set<P: Phase>(
resolved_concrete: Variable,
this_lambda_set: Variable,
phase: &P,
) -> (MustImplementConstraints, UlsOfVar) {
) -> OneCompactionResult {
// 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`:
// 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set.
// - For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`.
@ -426,6 +500,20 @@ fn compact_lambda_set<P: Phase>(
debug_assert!(subs.equivalent_without_compacting(c, resolved_concrete));
// Now decide: do we
// - proceed with specialization
// - simply drop the specialization lambda set (due to an error)
// - or do we need to wait, because we don't know enough information for the specialization yet?
let specialization_decision = make_specialization_decision(subs, phase, c, f);
let specialization_key_or_drop = match specialization_decision {
SpecializeDecision::Specialize(key) => Ok(key),
SpecializeDecision::Drop => Err(()),
SpecializeDecision::PendingSpecialization(impl_key) => {
// Bail, we need to wait for the specialization to be known.
return OneCompactionResult::MustWaitForSpecialization(impl_key);
}
};
// 1b. Remove `C:f:r` from `t_f1`'s lambda set.
let new_unspecialized: Vec<_> = unspecialized
.iter()
@ -447,15 +535,16 @@ fn compact_lambda_set<P: Phase>(
Content::LambdaSet(t_f1_lambda_set_without_concrete),
);
let specialization_decision = make_specialization_decision(subs, c);
let specialization_key = match specialization_decision {
SpecializeDecision::Specialize(key) => key,
SpecializeDecision::Drop => {
let specialization_key = match specialization_key_or_drop {
Ok(specialization_key) => specialization_key,
Err(()) => {
// Do nothing other than to remove the concrete lambda to drop from the lambda set,
// which we already did in 1b above.
trace_compact!(3iter_end_skipped. subs, t_f1);
return (Default::default(), Default::default());
return OneCompactionResult::Compacted {
new_obligations: Default::default(),
new_lambda_sets_to_specialize: Default::default(),
};
}
};
@ -475,7 +564,10 @@ fn compact_lambda_set<P: Phase>(
// Do nothing other than to remove the concrete lambda to drop from the lambda set,
// which we already did in 1b above.
trace_compact!(3iter_end_skipped. subs, t_f1);
return (Default::default(), Default::default());
return OneCompactionResult::Compacted {
new_obligations: Default::default(),
new_lambda_sets_to_specialize: Default::default(),
};
}
};
@ -485,14 +577,17 @@ fn compact_lambda_set<P: Phase>(
// 3. Unify `t_f1 ~ t_f2`.
trace_compact!(3iter_start. subs, this_lambda_set, t_f1, t_f2);
let (vars, new_must_implement_ability, new_lambda_sets_to_specialize, _meta) =
let (vars, new_obligations, new_lambda_sets_to_specialize, _meta) =
unify(&mut UEnv::new(subs), t_f1, t_f2, Mode::EQ)
.expect_success("ambient functions don't unify");
trace_compact!(3iter_end. subs, t_f1);
introduce(subs, target_rank, pools, &vars);
(new_must_implement_ability, new_lambda_sets_to_specialize)
OneCompactionResult::Compacted {
new_obligations,
new_lambda_sets_to_specialize,
}
}
#[derive(Debug)]
@ -505,14 +600,53 @@ enum SpecializationTypeKey {
enum SpecializeDecision {
Specialize(SpecializationTypeKey),
Drop,
/// Only relevant during module solving of recursive defs - we don't yet know the
/// specialization type for a declared ability implementation, so we must hold off on
/// specialization.
PendingSpecialization(ImplKey),
}
fn make_specialization_decision(subs: &Subs, var: Variable) -> SpecializeDecision {
fn make_specialization_decision<P: Phase>(
subs: &Subs,
phase: &P,
var: Variable,
ability_member: Symbol,
) -> SpecializeDecision {
use Content::*;
use SpecializationTypeKey::*;
match subs.get_content_without_compacting(var) {
Alias(opaque, _, _, AliasKind::Opaque) if opaque.module_id() != ModuleId::NUM => {
SpecializeDecision::Specialize(Opaque(*opaque))
if P::IS_LATE {
SpecializeDecision::Specialize(Opaque(*opaque))
} else {
// Solving within a module.
phase.with_module_abilities_store(opaque.module_id(), |abilities_store| {
let impl_key = ImplKey {
opaque: *opaque,
ability_member,
};
match abilities_store.get_implementation(impl_key) {
None => {
// Doesn't specialize; an error will already be reported for this.
SpecializeDecision::Drop
}
Some(MemberImpl::Error | MemberImpl::Derived) => {
// TODO: probably not right, we may want to choose a derive decision!
SpecializeDecision::Specialize(Opaque(*opaque))
}
Some(MemberImpl::Impl(specialization_symbol)) => {
match abilities_store.specialization_info(*specialization_symbol) {
Some(_) => SpecializeDecision::Specialize(Opaque(*opaque)),
// If we expect a specialization impl but don't yet know it, we must hold off
// compacting the lambda set until the specialization is well-known.
None => SpecializeDecision::PendingSpecialization(impl_key),
}
}
}
})
}
}
Structure(_) | Alias(_, _, _, _) => {
// This is a structural type, find the name of the derived ability function it
@ -573,19 +707,20 @@ fn get_specialization_lambda_set_ambient_function<P: Phase>(
};
let opt_specialization =
abilities_store.get_implementation(impl_key);
match (P::IS_LATE, opt_specialization) {
(false, None) => {
// doesn't specialize, we'll have reported an error for this
Err(())
match opt_specialization {
None => {
if P::IS_LATE {
internal_error!(
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
opaque,
ability_member
);
} else {
// doesn't specialize, we'll have reported an error for this
Err(())
}
}
(true, None) => {
internal_error!(
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
opaque,
ability_member,
);
}
(_, Some(member_impl)) => match member_impl {
Some(member_impl) => match member_impl {
MemberImpl::Impl(spec_symbol) => {
let specialization =
abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point");