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,41 +5118,42 @@ fn register_capturing_closure<'a>(
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive); let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// does this function capture any local values? let captured_symbols = match *env.subs.get_content_without_compacting(function_type) {
let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs); Content::Structure(FlatType::Func(_, closure_var, _)) => {
match LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes) {
let captured_symbols = match function_layout { Ok(lambda_set) => {
Ok(RawFunctionLayout::Function(_, lambda_set, _)) => { if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() { CapturedSymbols::None
CapturedSymbols::None } else {
} else { let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
let mut temp = Vec::from_iter_in(captured_symbols, env.arena); temp.sort();
temp.sort(); CapturedSymbols::Captured(temp.into_bump_slice())
CapturedSymbols::Captured(temp.into_bump_slice()) }
}
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
} }
} }
Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => { _ => {
// top-level thunks cannot capture any variables // This is a value (zero-argument thunk); it cannot capture any variables.
debug_assert!( debug_assert!(
captured_symbols.is_empty(), captured_symbols.is_empty(),
"{:?} with layout {:?} {:?} {:?}", "{:?} with layout {:?} {:?} {:?}",
&captured_symbols, &captured_symbols,
function_layout, layout_cache.raw_from_var(env.arena, function_type, env.subs),
env.subs, env.subs,
(function_type, closure_type, closure_ext_var), (function_type, closure_type, closure_ext_var),
); );
CapturedSymbols::None CapturedSymbols::None
} }
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
}; };
let partial_proc = PartialProc::from_named_function( 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_module::symbol::{Interns, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::RuntimeError;
use roc_types::subs::{ 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 roc_types::types::{gather_fields_unsorted_iter, RecordField};
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
@ -1571,18 +1571,26 @@ fn layout_from_flat_type<'a>(
} }
} }
TagUnion(tags, ext_var) => { 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)); 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) => { 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) => { 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)); debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
// some observations // 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 // 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 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; let mut nullable = None;
@ -1610,7 +1618,7 @@ fn layout_from_flat_type<'a>(
} }
env.insert_seen(rec_var); 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) { if matches!(nullable, Some(i) if i == index as TagIdIntType) {
// don't add the nullable case // don't add the nullable case
continue; continue;
@ -1618,8 +1626,7 @@ fn layout_from_flat_type<'a>(
let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena); let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena);
for var_index in variables { for &var in variables {
let var = subs[var_index];
// TODO does this cause problems with mutually recursive unions? // TODO does this cause problems with mutually recursive unions?
if rec_var == subs.get_root_key_without_compacting(var) { if rec_var == subs.get_root_key_without_compacting(var) {
tag_layout.push(Layout::RecursivePointer); tag_layout.push(Layout::RecursivePointer);
@ -1902,13 +1909,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool {
fn union_sorted_tags_help_new<'a>( fn union_sorted_tags_help_new<'a>(
arena: &'a Bump, arena: &'a Bump,
mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>, tags_list: &[(&'_ TagName, &[Variable])],
opt_rec_var: Option<Variable>, opt_rec_var: Option<Variable>,
subs: &Subs, subs: &Subs,
ptr_bytes: u32, ptr_bytes: u32,
) -> UnionVariant<'a> { ) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact! // 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 { let mut env = Env {
arena, arena,
@ -1917,27 +1925,26 @@ fn union_sorted_tags_help_new<'a>(
ptr_bytes, ptr_bytes,
}; };
match tags_vec.len() { match tags_list.len() {
0 => { 0 => {
// trying to instantiate a type with no values // trying to instantiate a type with no values
UnionVariant::Never UnionVariant::Never
} }
1 => { 1 => {
let (tag_name, arguments) = tags_vec.remove(0); let &(tag_name, arguments) = tags_list.remove(0);
let tag_name = tag_name.clone(); let tag_name = tag_name.clone();
// just one tag in the union (but with arguments) can be a struct // 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 // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int
match tag_name { match tag_name {
TagName::Private(Symbol::NUM_AT_NUM) => { 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")); layouts.push(unwrap_num_tag(subs, var, ptr_bytes).expect("invalid num layout"));
} }
_ => { _ => {
for var_index in arguments { for &var in arguments {
let var = subs[var_index];
match Layout::from_var(&mut env, var) { match Layout::from_var(&mut env, var) {
Ok(layout) => { Ok(layout) => {
layouts.push(layout); layouts.push(layout);
@ -1980,7 +1987,7 @@ fn union_sorted_tags_help_new<'a>(
} }
num_tags => { num_tags => {
// default path // 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 has_any_arguments = false;
let mut nullable: Option<(TagIdIntType, TagName)> = None; 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 // only recursive tag unions can be nullable
let is_recursive = opt_rec_var.is_some(); let is_recursive = opt_rec_var.is_some();
if is_recursive && GENERATE_NULLABLE { 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() { if variables.is_empty() {
nullable = Some((index as TagIdIntType, (*name).clone())); nullable = Some((index as TagIdIntType, (*name).clone()));
break; 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 // reserve space for the tag discriminant
if matches!(nullable, Some((i, _)) if i as usize == index) { if matches!(nullable, Some((i, _)) if i as usize == index) {
debug_assert!(arguments.is_empty()); 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); let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
for var_index in arguments { for &var in arguments {
let var = subs[var_index];
match Layout::from_var(&mut env, var) { match Layout::from_var(&mut env, var) {
Ok(layout) => { Ok(layout) => {
has_any_arguments = true; has_any_arguments = true;
@ -2317,38 +2323,19 @@ pub fn union_sorted_tags_help<'a>(
} }
} }
fn cheap_sort_tags<'a, 'b>( fn cheap_sort_tags<'a>(tags: &'a UnsortedUnionTags) -> &'a [(&'a TagName, &'a [Variable])] {
arena: &'a Bump, &tags.tags
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 layout_from_newtype<'a>( fn layout_from_newtype<'a>(
arena: &'a Bump, arena: &'a Bump,
tags: UnionTags, tags: &UnsortedUnionTags,
subs: &Subs, subs: &Subs,
ptr_bytes: u32, ptr_bytes: u32,
) -> Layout<'a> { ) -> Layout<'a> {
debug_assert!(tags.is_newtype_wrapper(subs)); debug_assert!(tags.is_newtype_wrapper(subs));
let slice_index = tags.variables().into_iter().next().unwrap(); let (tag_name, var) = tags.get_newtype(subs);
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];
if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) {
unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") 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>( fn layout_from_tag_union<'a>(
arena: &'a Bump, arena: &'a Bump,
tags: UnionTags, tags: &UnsortedUnionTags,
subs: &Subs, subs: &Subs,
ptr_bytes: u32, ptr_bytes: u32,
) -> Layout<'a> { ) -> Layout<'a> {
@ -2389,14 +2376,13 @@ fn layout_from_tag_union<'a>(
return layout_from_newtype(arena, tags, subs, ptr_bytes); 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) { match tags_vec.get(0) {
Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => {
debug_assert_eq!(arguments.len(), 1); debug_assert_eq!(arguments.len(), 1);
let var_index = arguments.into_iter().next().unwrap(); let &var = arguments.iter().next().unwrap();
let var = subs[var_index];
unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") 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 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, ext_var: Variable,
) -> ExtContent<'a> { ) -> ExtContent<'a> {
// Sort the fields so they always end up in the same order. // 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 (tags, new_ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
let mut sorted_fields: Vec<_> = it.collect(); let mut sorted_fields = tags.tags;
let interns = &env.interns; let interns = &env.interns;
let home = env.home; let home = env.home;

View file

@ -830,7 +830,10 @@ fn integer_type(
) { ) {
// define the type Signed64 (which is an alias for [ @Signed64 ]) // 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, { subs.set_content(at_signed64, {
Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))
@ -1082,7 +1085,7 @@ impl Subs {
Content::Structure(FlatType::EmptyTagUnion), Content::Structure(FlatType::EmptyTagUnion),
); );
let bool_union_tags = UnionTags::insert_into_subs( let bool_union_tags = UnionTags::insert_into_subs::<Variable, _, _>(
&mut subs, &mut subs,
[ [
(TagName::Global("False".into()), []), (TagName::Global("False".into()), []),
@ -1724,10 +1727,11 @@ impl UnionTags {
pub fn compare<T>(x: &(TagName, T), y: &(TagName, T)) -> std::cmp::Ordering { pub fn compare<T>(x: &(TagName, T), y: &(TagName, T)) -> std::cmp::Ordering {
first(x, y) 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 where
V: Into<Variable>,
I: IntoIterator<Item = (TagName, I2)>, I: IntoIterator<Item = (TagName, I2)>,
I2: IntoIterator<Item = Variable>, I2: IntoIterator<Item = V>,
{ {
let tag_names_start = subs.tag_names.len() as u32; let tag_names_start = subs.tag_names.len() as u32;
let variables_start = subs.variable_slices.len() as u32; let variables_start = subs.variable_slices.len() as u32;
@ -1740,7 +1744,8 @@ impl UnionTags {
let mut length = 0; let mut length = 0;
for (k, v) in it { 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.tag_names.push(k);
subs.variable_slices.push(variables); subs.variable_slices.push(variables);
@ -1813,16 +1818,17 @@ impl UnionTags {
it.map(f) it.map(f)
} }
pub fn unsorted_iterator_and_ext<'a>( #[inline(always)]
pub fn unsorted_tags_and_ext<'a>(
&'a self, &'a self,
subs: &'a Subs, subs: &'a Subs,
ext: Variable, 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 (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 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)] #[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 SortedTagsIterator<'a> = Box<dyn Iterator<Item = (TagName, &'a [Variable])> + 'a>;
pub type SortedTagsSlicesIterator<'a> = Box<dyn Iterator<Item = (TagName, VariableSubsSlice)> + 'a>; pub type SortedTagsSlicesIterator<'a> = Box<dyn Iterator<Item = (TagName, VariableSubsSlice)> + 'a>;
@ -2025,6 +2050,21 @@ impl RecordFields {
it 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 /// 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 /// Implementation: When the record has an `ext` variable that is the empty record, then