Collect tags from extension variables during monomorphization

Fixes #2365
This commit is contained in:
ayazhafiz 2022-01-23 11:12:29 -05:00
parent 2b3f347eec
commit b2f2fcd6a8
5 changed files with 194 additions and 85 deletions

View file

@ -5118,11 +5118,10 @@ fn register_capturing_closure<'a>(
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// does this function capture any local values?
let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs);
let captured_symbols = match function_layout {
Ok(RawFunctionLayout::Function(_, lambda_set, _)) => {
let captured_symbols = match *env.subs.get_content_without_compacting(function_type) {
Content::Structure(FlatType::Func(_, closure_var, _)) => {
match LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes) {
Ok(lambda_set) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
CapturedSymbols::None
} else {
@ -5131,18 +5130,6 @@ fn register_capturing_closure<'a>(
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => {
// top-level thunks cannot capture any variables
debug_assert!(
captured_symbols.is_empty(),
"{:?} with layout {:?} {:?} {:?}",
&captured_symbols,
function_layout,
env.subs,
(function_type, closure_type, closure_ext_var),
);
CapturedSymbols::None
}
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
if captured_symbols.is_empty() {
@ -5153,6 +5140,20 @@ fn register_capturing_closure<'a>(
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
}
}
_ => {
// This is a value (zero-argument thunk); it cannot capture any variables.
debug_assert!(
captured_symbols.is_empty(),
"{:?} with layout {:?} {:?} {:?}",
&captured_symbols,
layout_cache.raw_from_var(env.arena, function_type, env.subs),
env.subs,
(function_type, closure_type, closure_ext_var),
);
CapturedSymbols::None
}
};
let partial_proc = PartialProc::from_named_function(

View file

@ -7,7 +7,7 @@ use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol};
use roc_problem::can::RuntimeError;
use roc_types::subs::{
Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice,
Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable,
};
use roc_types::types::{gather_fields_unsorted_iter, RecordField};
use std::collections::hash_map::Entry;
@ -1571,18 +1571,26 @@ fn layout_from_flat_type<'a>(
}
}
TagUnion(tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes))
Ok(layout_from_tag_union(arena, &tags, subs, env.ptr_bytes))
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
debug_assert!(
ext_var_is_empty_tag_union(subs, ext_var),
"If ext_var wasn't empty, this wouldn't be a FunctionOrTagUnion!"
);
let tags = UnionTags::from_tag_name_index(tag_name);
let union_tags = UnionTags::from_tag_name_index(tag_name);
let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var);
Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes))
Ok(layout_from_tag_union(arena, &tags, subs, env.ptr_bytes))
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
// some observations
@ -1594,9 +1602,9 @@ fn layout_from_flat_type<'a>(
// That means none of the optimizations for enums or single tag tag unions apply
let rec_var = subs.get_root_key_without_compacting(rec_var);
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
let mut tag_layouts = Vec::with_capacity_in(tags.tags.len(), arena);
let tags_vec = cheap_sort_tags(arena, tags, subs);
let tags_vec = cheap_sort_tags(&tags);
let mut nullable = None;
@ -1610,7 +1618,7 @@ fn layout_from_flat_type<'a>(
}
env.insert_seen(rec_var);
for (index, (_name, variables)) in tags_vec.into_iter().enumerate() {
for (index, &(_name, variables)) in tags_vec.iter().enumerate() {
if matches!(nullable, Some(i) if i == index as TagIdIntType) {
// don't add the nullable case
continue;
@ -1618,8 +1626,7 @@ fn layout_from_flat_type<'a>(
let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena);
for var_index in variables {
let var = subs[var_index];
for &var in variables {
// TODO does this cause problems with mutually recursive unions?
if rec_var == subs.get_root_key_without_compacting(var) {
tag_layout.push(Layout::RecursivePointer);
@ -1902,13 +1909,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool {
fn union_sorted_tags_help_new<'a>(
arena: &'a Bump,
mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>,
tags_list: &[(&'_ TagName, &[Variable])],
opt_rec_var: Option<Variable>,
subs: &Subs,
ptr_bytes: u32,
) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact!
tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena);
tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
let mut env = Env {
arena,
@ -1917,27 +1925,26 @@ fn union_sorted_tags_help_new<'a>(
ptr_bytes,
};
match tags_vec.len() {
match tags_list.len() {
0 => {
// trying to instantiate a type with no values
UnionVariant::Never
}
1 => {
let (tag_name, arguments) = tags_vec.remove(0);
let &(tag_name, arguments) = tags_list.remove(0);
let tag_name = tag_name.clone();
// just one tag in the union (but with arguments) can be a struct
let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena);
let mut layouts = Vec::with_capacity_in(tags_list.len(), arena);
// special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int
match tag_name {
TagName::Private(Symbol::NUM_AT_NUM) => {
let var = subs[arguments.into_iter().next().unwrap()];
let var = arguments[0];
layouts.push(unwrap_num_tag(subs, var, ptr_bytes).expect("invalid num layout"));
}
_ => {
for var_index in arguments {
let var = subs[var_index];
for &var in arguments {
match Layout::from_var(&mut env, var) {
Ok(layout) => {
layouts.push(layout);
@ -1980,7 +1987,7 @@ fn union_sorted_tags_help_new<'a>(
}
num_tags => {
// default path
let mut answer = Vec::with_capacity_in(tags_vec.len(), arena);
let mut answer = Vec::with_capacity_in(tags_list.len(), arena);
let mut has_any_arguments = false;
let mut nullable: Option<(TagIdIntType, TagName)> = None;
@ -1988,7 +1995,7 @@ fn union_sorted_tags_help_new<'a>(
// only recursive tag unions can be nullable
let is_recursive = opt_rec_var.is_some();
if is_recursive && GENERATE_NULLABLE {
for (index, (name, variables)) in tags_vec.iter().enumerate() {
for (index, (name, variables)) in tags_list.iter().enumerate() {
if variables.is_empty() {
nullable = Some((index as TagIdIntType, (*name).clone()));
break;
@ -1996,7 +2003,7 @@ fn union_sorted_tags_help_new<'a>(
}
}
for (index, (tag_name, arguments)) in tags_vec.into_iter().enumerate() {
for (index, &(tag_name, arguments)) in tags_list.into_iter().enumerate() {
// reserve space for the tag discriminant
if matches!(nullable, Some((i, _)) if i as usize == index) {
debug_assert!(arguments.is_empty());
@ -2005,8 +2012,7 @@ fn union_sorted_tags_help_new<'a>(
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
for var_index in arguments {
let var = subs[var_index];
for &var in arguments {
match Layout::from_var(&mut env, var) {
Ok(layout) => {
has_any_arguments = true;
@ -2317,38 +2323,19 @@ pub fn union_sorted_tags_help<'a>(
}
}
fn cheap_sort_tags<'a, 'b>(
arena: &'a Bump,
tags: UnionTags,
subs: &'b Subs,
) -> Vec<'a, (&'b TagName, VariableSubsSlice)> {
let mut tags_vec = Vec::with_capacity_in(tags.len(), arena);
for (tag_index, index) in tags.iter_all() {
let tag = &subs[tag_index];
let slice = subs[index];
tags_vec.push((tag, slice));
}
tags_vec
fn cheap_sort_tags<'a>(tags: &'a UnsortedUnionTags) -> &'a [(&'a TagName, &'a [Variable])] {
&tags.tags
}
fn layout_from_newtype<'a>(
arena: &'a Bump,
tags: UnionTags,
tags: &UnsortedUnionTags,
subs: &Subs,
ptr_bytes: u32,
) -> Layout<'a> {
debug_assert!(tags.is_newtype_wrapper(subs));
let slice_index = tags.variables().into_iter().next().unwrap();
let slice = subs[slice_index];
let var_index = slice.into_iter().next().unwrap();
let var = subs[var_index];
let tag_name_index = tags.tag_names().into_iter().next().unwrap();
let tag_name = &subs[tag_name_index];
let (tag_name, var) = tags.get_newtype(subs);
if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) {
unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument")
@ -2379,7 +2366,7 @@ fn layout_from_newtype<'a>(
fn layout_from_tag_union<'a>(
arena: &'a Bump,
tags: UnionTags,
tags: &UnsortedUnionTags,
subs: &Subs,
ptr_bytes: u32,
) -> Layout<'a> {
@ -2389,14 +2376,13 @@ fn layout_from_tag_union<'a>(
return layout_from_newtype(arena, tags, subs, ptr_bytes);
}
let tags_vec = cheap_sort_tags(arena, tags, subs);
let tags_vec = cheap_sort_tags(tags);
match tags_vec.get(0) {
Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => {
debug_assert_eq!(arguments.len(), 1);
let var_index = arguments.into_iter().next().unwrap();
let var = subs[var_index];
let &var = arguments.iter().next().unwrap();
unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument")
}

View file

@ -1367,3 +1367,85 @@ fn monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg() {
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_2365_monomorphize_tag_with_non_empty_ext_var() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Single a : [A, B, C]a
Compound a : Single [D, E, F]a
single : {} -> Single *
single = \{} -> C
compound : {} -> Compound *
compound = \{} -> single {}
main = compound {}
"#
),
2, // C
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Single a : [A, B, C]a
Compound a : Single [D, E, F]a
single : {} -> Result Str (Single *)
single = \{} -> Err C
compound : {} -> Result Str (Compound *)
compound = \{} ->
when single {} is
Ok s -> Ok s
Err e -> Err e
main = compound {}
"#
),
2, // C
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Single a : [A, B, C]a
Compound a : Single [D, E, F]a
main =
single : {} -> Result Str (Single *)
single = \{} -> Err C
compound : {} -> Result Str (Compound *)
compound = \{} ->
when single {} is
Ok s -> Ok s
Err e -> Err e
compound {}
"#
),
2, // C
u8
)
}

View file

@ -406,8 +406,8 @@ fn write_sorted_tags2<'a>(
ext_var: Variable,
) -> ExtContent<'a> {
// Sort the fields so they always end up in the same order.
let (it, new_ext_var) = tags.unsorted_iterator_and_ext(subs, ext_var);
let mut sorted_fields: Vec<_> = it.collect();
let (tags, new_ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
let mut sorted_fields = tags.tags;
let interns = &env.interns;
let home = env.home;

View file

@ -830,7 +830,10 @@ fn integer_type(
) {
// define the type Signed64 (which is an alias for [ @Signed64 ])
{
let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_signed64), [])]);
let tags = UnionTags::insert_into_subs::<Variable, _, _>(
subs,
[(TagName::Private(num_at_signed64), [])],
);
subs.set_content(at_signed64, {
Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))
@ -1082,7 +1085,7 @@ impl Subs {
Content::Structure(FlatType::EmptyTagUnion),
);
let bool_union_tags = UnionTags::insert_into_subs(
let bool_union_tags = UnionTags::insert_into_subs::<Variable, _, _>(
&mut subs,
[
(TagName::Global("False".into()), []),
@ -1724,10 +1727,11 @@ impl UnionTags {
pub fn compare<T>(x: &(TagName, T), y: &(TagName, T)) -> std::cmp::Ordering {
first(x, y)
}
pub fn insert_into_subs<I, I2>(subs: &mut Subs, input: I) -> Self
pub fn insert_into_subs<V, I, I2>(subs: &mut Subs, input: I) -> Self
where
V: Into<Variable>,
I: IntoIterator<Item = (TagName, I2)>,
I2: IntoIterator<Item = Variable>,
I2: IntoIterator<Item = V>,
{
let tag_names_start = subs.tag_names.len() as u32;
let variables_start = subs.variable_slices.len() as u32;
@ -1740,7 +1744,8 @@ impl UnionTags {
let mut length = 0;
for (k, v) in it {
let variables = VariableSubsSlice::insert_into_subs(subs, v.into_iter());
let variables =
VariableSubsSlice::insert_into_subs(subs, v.into_iter().map(|v| v.into()));
subs.tag_names.push(k);
subs.variable_slices.push(variables);
@ -1813,16 +1818,17 @@ impl UnionTags {
it.map(f)
}
pub fn unsorted_iterator_and_ext<'a>(
#[inline(always)]
pub fn unsorted_tags_and_ext<'a>(
&'a self,
subs: &'a Subs,
ext: Variable,
) -> (impl Iterator<Item = (&TagName, &[Variable])> + 'a, Variable) {
) -> (UnsortedUnionTags<'a>, Variable) {
let (it, ext) = crate::types::gather_tags_unsorted_iter(subs, *self, ext);
let f = move |(label, slice): (_, SubsSlice<Variable>)| (label, subs.get_subs_slice(slice));
let it = it.map(f);
(it.map(f), ext)
(UnsortedUnionTags { tags: it.collect() }, ext)
}
#[inline(always)]
@ -1877,6 +1883,25 @@ impl UnionTags {
}
}
#[derive(Debug)]
pub struct UnsortedUnionTags<'a> {
pub tags: Vec<(&'a TagName, &'a [Variable])>,
}
impl<'a> UnsortedUnionTags<'a> {
pub fn is_newtype_wrapper(&self, _subs: &Subs) -> bool {
if self.tags.len() != 1 {
return false;
}
self.tags[0].1.len() == 1
}
pub fn get_newtype(&self, _subs: &Subs) -> (&TagName, Variable) {
let (tag_name, vars) = self.tags[0];
(tag_name, vars[0])
}
}
pub type SortedTagsIterator<'a> = Box<dyn Iterator<Item = (TagName, &'a [Variable])> + 'a>;
pub type SortedTagsSlicesIterator<'a> = Box<dyn Iterator<Item = (TagName, VariableSubsSlice)> + 'a>;
@ -2025,6 +2050,21 @@ impl RecordFields {
it
}
#[inline(always)]
pub fn unsorted_iterator_and_ext<'a>(
&'a self,
subs: &'a Subs,
ext: Variable,
) -> (
impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a,
Variable,
) {
let (it, ext) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)
.expect("Something weird ended up in a record type");
(it, ext)
}
/// Get a sorted iterator over the fields of this record type
///
/// Implementation: When the record has an `ext` variable that is the empty record, then