Do not drop uninhabited captures from lambda sets

Previously, we would drop uninhabited captures from lambda sets' runtime
representations, which meant sets like

```
[L1 {a: Str}, L2 {a: []}]
```

had runtime representation

```
{Str}
```

rather than

```
Union({Str}, {[]})
```

if we drop unreachable lambdas from the representation, then the
reachable lambdas are somewhat more efficient to compile (as there are
less material tag options), but the compiler complexity increases
because we must represent voided capture sets in the lambda set.

Even if a lambda has voided captures, we must specialize it, because
failing to do so opens us up to losing relevant specializations needed
later on. See 2f7020aa31 for a
previous occurence of that.

As such, simply keep voided layouts in place during lambda set
compilation. The optimizer should elide them anyway.
This commit is contained in:
Ayaz Hafiz 2023-06-29 17:32:09 -05:00
parent e27a06849d
commit a5e1558a6e
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
3 changed files with 253 additions and 10 deletions

View file

@ -1914,6 +1914,11 @@ impl<'a> LambdaSet<'a> {
) -> Cacheable<InLayout<'a>> {
let union_labels = UnsortedUnionLabels { tags: set };
// Even if a variant in the lambda set has uninhabitable captures (and is hence
// unreachable as a function), we want to keep it in the representation. Failing to do so
// risks dropping relevant specializations needed during monomorphization.
let drop_uninhabited_variants = DropUninhabitedVariants(false);
match opt_rec_var {
Some(rec_var) => {
let Cacheable(result, criteria) =
@ -1922,7 +1927,7 @@ impl<'a> LambdaSet<'a> {
Cacheable(result, criteria)
}
None => layout_from_non_recursive_union(env, &union_labels),
None => layout_from_non_recursive_union(env, &union_labels, drop_uninhabited_variants),
}
}
@ -3330,7 +3335,7 @@ fn layout_from_flat_type<'a>(
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
layout_from_non_recursive_union(env, &tags).map(Ok)
layout_from_non_recursive_union(env, &tags, DropUninhabitedVariants(true)).map(Ok)
}
FunctionOrTagUnion(tag_names, _, ext_var) => {
debug_assert!(
@ -3343,7 +3348,8 @@ fn layout_from_flat_type<'a>(
tags: tag_names.iter().map(|t| (t, &[] as &[Variable])).collect(),
};
layout_from_non_recursive_union(env, &unsorted_tags).map(Ok)
layout_from_non_recursive_union(env, &unsorted_tags, DropUninhabitedVariants(true))
.map(Ok)
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
@ -3621,11 +3627,14 @@ pub fn union_sorted_tags<'a>(
var
};
let drop_uninhabited_variants = DropUninhabitedVariants(true);
let mut tags_vec = std::vec::Vec::new();
let result = match roc_types::pretty_print::chase_ext_tag_union(env.subs, var, &mut tags_vec) {
ChasedExt::Empty => {
let opt_rec_var = get_recursion_var(env.subs, var);
let Cacheable(result, _) = union_sorted_tags_help(env, tags_vec, opt_rec_var);
let Cacheable(result, _) =
union_sorted_tags_help(env, tags_vec, opt_rec_var, drop_uninhabited_variants);
result
}
ChasedExt::NonEmpty { content, .. } => {
@ -3638,12 +3647,22 @@ pub fn union_sorted_tags<'a>(
// x
// In such cases it's fine to drop the variable. We may be proven wrong in the future...
let opt_rec_var = get_recursion_var(env.subs, var);
let Cacheable(result, _) = union_sorted_tags_help(env, tags_vec, opt_rec_var);
let Cacheable(result, _) = union_sorted_tags_help(
env,
tags_vec,
opt_rec_var,
drop_uninhabited_variants,
);
result
}
RecursionVar { .. } => {
let opt_rec_var = get_recursion_var(env.subs, var);
let Cacheable(result, _) = union_sorted_tags_help(env, tags_vec, opt_rec_var);
let Cacheable(result, _) = union_sorted_tags_help(
env,
tags_vec,
opt_rec_var,
drop_uninhabited_variants,
);
result
}
@ -3694,9 +3713,12 @@ impl Label for Symbol {
}
}
struct DropUninhabitedVariants(bool);
fn union_sorted_non_recursive_tags_help<'a, L>(
env: &mut Env<'a, '_>,
tags_list: &mut Vec<'_, &'_ (&'_ L, &[Variable])>,
drop_uninhabited_variants: DropUninhabitedVariants,
) -> Cacheable<UnionVariant<'a>>
where
L: Label + Ord + Clone + Into<TagOrClosure>,
@ -3816,7 +3838,7 @@ where
answer.push((tag_name.clone().into(), arg_layouts.into_bump_slice()));
}
if inhabited_tag_ids.count_ones() == 1 {
if inhabited_tag_ids.count_ones() == 1 && drop_uninhabited_variants.0 {
let kept_tag_id = inhabited_tag_ids.first_one().unwrap();
let kept = answer.get(kept_tag_id).unwrap();
@ -3869,13 +3891,14 @@ pub fn union_sorted_tags_pub<'a, L>(
where
L: Into<TagOrClosure> + Ord + Clone,
{
union_sorted_tags_help(env, tags_vec, opt_rec_var).value()
union_sorted_tags_help(env, tags_vec, opt_rec_var, DropUninhabitedVariants(true)).value()
}
fn union_sorted_tags_help<'a, L>(
env: &mut Env<'a, '_>,
mut tags_vec: std::vec::Vec<(L, std::vec::Vec<Variable>)>,
opt_rec_var: Option<Variable>,
drop_uninhabited_variants: DropUninhabitedVariants,
) -> Cacheable<UnionVariant<'a>>
where
L: Into<TagOrClosure> + Ord + Clone,
@ -4028,7 +4051,7 @@ where
answer.push((tag_name.into(), arg_layouts.into_bump_slice()));
}
if inhabited_tag_ids.count_ones() == 1 && !is_recursive {
if inhabited_tag_ids.count_ones() == 1 && !is_recursive && drop_uninhabited_variants.0 {
let kept_tag_id = inhabited_tag_ids.first_one().unwrap();
let kept = answer.get(kept_tag_id).unwrap();
@ -4131,6 +4154,7 @@ fn layout_from_newtype<'a, L: Label>(
fn layout_from_non_recursive_union<'a, L>(
env: &mut Env<'a, '_>,
tags: &UnsortedUnionLabels<L>,
drop_uninhabited_variants: DropUninhabitedVariants,
) -> Cacheable<InLayout<'a>>
where
L: Label + Ord + Into<TagOrClosure>,
@ -4146,7 +4170,8 @@ where
let mut criteria = CACHEABLE;
let variant =
union_sorted_non_recursive_tags_help(env, &mut tags_vec).decompose(&mut criteria, env.subs);
union_sorted_non_recursive_tags_help(env, &mut tags_vec, drop_uninhabited_variants)
.decompose(&mut criteria, env.subs);
let compute_semantic = || L::semantic_repr(env.arena, tags_vec.iter().map(|(l, _)| *l));