mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 13:59:08 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into dict
This commit is contained in:
commit
609bb21fad
71 changed files with 4904 additions and 1190 deletions
|
@ -8,6 +8,7 @@ use roc_types::types::RecordField;
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
|
||||
const GENERATE_NULLABLE: bool = true;
|
||||
|
||||
/// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to.
|
||||
const DEFAULT_NUM_BUILTIN: Builtin<'_> = Builtin::Int64;
|
||||
|
@ -28,8 +29,7 @@ pub enum Layout<'a> {
|
|||
/// this is important for closures that capture zero-sized values
|
||||
PhantomEmptyStruct,
|
||||
Struct(&'a [Layout<'a>]),
|
||||
Union(&'a [&'a [Layout<'a>]]),
|
||||
RecursiveUnion(&'a [&'a [Layout<'a>]]),
|
||||
Union(UnionLayout<'a>),
|
||||
RecursivePointer,
|
||||
/// A function. The types of its arguments, then the type of its return value.
|
||||
FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>),
|
||||
|
@ -37,6 +37,29 @@ pub enum Layout<'a> {
|
|||
Pointer(&'a Layout<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum UnionLayout<'a> {
|
||||
/// A non-recursive tag union
|
||||
/// e.g. `Result a e : [ Ok a, Err e ]`
|
||||
NonRecursive(&'a [&'a [Layout<'a>]]),
|
||||
/// A recursive tag union
|
||||
/// e.g. `RoseTree a : [ Tree a (List (RoseTree a)) ]`
|
||||
Recursive(&'a [&'a [Layout<'a>]]),
|
||||
/// A recursive tag union where the non-nullable variant(s) store the tag id
|
||||
/// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]`
|
||||
/// see also: https://youtu.be/ip92VMpf_-A?t=164
|
||||
NullableWrapped {
|
||||
nullable_id: i64,
|
||||
other_tags: &'a [&'a [Layout<'a>]],
|
||||
},
|
||||
/// A recursive tag union where the non-nullable variant does NOT store the tag id
|
||||
/// e.g. `ConsList a : [ Nil, Cons a (ConsList a) ]`
|
||||
NullableUnwrapped {
|
||||
nullable_id: bool,
|
||||
other_fields: &'a [Layout<'a>],
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ClosureLayout<'a> {
|
||||
/// the layout that this specific closure captures
|
||||
|
@ -98,7 +121,9 @@ impl<'a> ClosureLayout<'a> {
|
|||
|
||||
ClosureLayout {
|
||||
captured: tags,
|
||||
layout: arena.alloc(Layout::Union(tag_arguments.into_bump_slice())),
|
||||
layout: arena.alloc(Layout::Union(UnionLayout::NonRecursive(
|
||||
tag_arguments.into_bump_slice(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,17 +183,20 @@ impl<'a> ClosureLayout<'a> {
|
|||
|
||||
Ok(Some(closure_layout))
|
||||
}
|
||||
Wrapped {
|
||||
sorted_tag_layouts: tags,
|
||||
is_recursive,
|
||||
} => {
|
||||
// TODO handle recursive closures
|
||||
debug_assert!(!is_recursive);
|
||||
Wrapped(variant) => {
|
||||
use WrappedVariant::*;
|
||||
|
||||
let closure_layout =
|
||||
ClosureLayout::from_tag_union(arena, tags.into_bump_slice());
|
||||
match variant {
|
||||
NonRecursive {
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let closure_layout =
|
||||
ClosureLayout::from_tag_union(arena, tags.into_bump_slice());
|
||||
Ok(Some(closure_layout))
|
||||
}
|
||||
|
||||
Ok(Some(closure_layout))
|
||||
_ => panic!("handle recursive layouts"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +292,7 @@ impl<'a> ClosureLayout<'a> {
|
|||
|
||||
Ok(Expr::Struct(symbols))
|
||||
}
|
||||
Layout::Union(tags) => {
|
||||
Layout::Union(UnionLayout::NonRecursive(tags)) => {
|
||||
// NOTE it's very important that this Union consists of Closure tags
|
||||
// and is not an unpacked 1-element record
|
||||
|
||||
|
@ -275,7 +303,7 @@ impl<'a> ClosureLayout<'a> {
|
|||
.unwrap() as _;
|
||||
|
||||
let expr = Expr::Tag {
|
||||
tag_layout: Layout::Union(tags),
|
||||
tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)),
|
||||
tag_name: TagName::Closure(original),
|
||||
tag_id,
|
||||
union_size: tags.len() as u8,
|
||||
|
@ -449,12 +477,18 @@ impl<'a> Layout<'a> {
|
|||
Struct(fields) => fields
|
||||
.iter()
|
||||
.all(|field_layout| field_layout.safe_to_memcpy()),
|
||||
Union(tags) => tags
|
||||
.iter()
|
||||
.all(|tag_layout| tag_layout.iter().all(|field| field.safe_to_memcpy())),
|
||||
RecursiveUnion(_) => {
|
||||
// a recursive union will always contain a pointer, and are thus not safe to memcpy
|
||||
false
|
||||
Union(variant) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
match variant {
|
||||
NonRecursive(tags) => tags
|
||||
.iter()
|
||||
.all(|tag_layout| tag_layout.iter().all(|field| field.safe_to_memcpy())),
|
||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
||||
// a recursive union will always contain a pointer, and is thus not safe to memcpy
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
FunctionPointer(_, _) => {
|
||||
// Function pointers are immutable and can always be safely copied
|
||||
|
@ -499,26 +533,26 @@ impl<'a> Layout<'a> {
|
|||
|
||||
sum
|
||||
}
|
||||
Union(fields) => fields
|
||||
.iter()
|
||||
.map(|tag_layout| {
|
||||
tag_layout
|
||||
Union(variant) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
match variant {
|
||||
NonRecursive(fields) => fields
|
||||
.iter()
|
||||
.map(|field| field.stack_size(pointer_size))
|
||||
.sum()
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default(),
|
||||
RecursiveUnion(fields) => fields
|
||||
.iter()
|
||||
.map(|tag_layout| {
|
||||
tag_layout
|
||||
.iter()
|
||||
.map(|field| field.stack_size(pointer_size))
|
||||
.sum()
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default(),
|
||||
.map(|tag_layout| {
|
||||
tag_layout
|
||||
.iter()
|
||||
.map(|field| field.stack_size(pointer_size))
|
||||
.sum()
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default(),
|
||||
|
||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
||||
pointer_size
|
||||
}
|
||||
}
|
||||
}
|
||||
Closure(_, closure_layout, _) => pointer_size + closure_layout.stack_size(pointer_size),
|
||||
FunctionPointer(_, _) => pointer_size,
|
||||
RecursivePointer => pointer_size,
|
||||
|
@ -533,13 +567,23 @@ impl<'a> Layout<'a> {
|
|||
.map(|x| x.alignment_bytes(pointer_size))
|
||||
.max()
|
||||
.unwrap_or(0),
|
||||
Layout::Union(tags) | Layout::RecursiveUnion(tags) => tags
|
||||
.iter()
|
||||
.map(|x| x.iter())
|
||||
.flatten()
|
||||
.map(|x| x.alignment_bytes(pointer_size))
|
||||
.max()
|
||||
.unwrap_or(0),
|
||||
|
||||
Layout::Union(variant) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
match variant {
|
||||
NonRecursive(tags) => tags
|
||||
.iter()
|
||||
.map(|x| x.iter())
|
||||
.flatten()
|
||||
.map(|x| x.alignment_bytes(pointer_size))
|
||||
.max()
|
||||
.unwrap_or(0),
|
||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
||||
pointer_size
|
||||
}
|
||||
}
|
||||
}
|
||||
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
|
||||
Layout::PhantomEmptyStruct => 0,
|
||||
Layout::RecursivePointer => pointer_size,
|
||||
|
@ -552,7 +596,22 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
|
||||
pub fn is_refcounted(&self) -> bool {
|
||||
matches!(self, Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) | Layout::Builtin(Builtin::Str) | Layout::RecursiveUnion(_) | Layout::RecursivePointer)
|
||||
use self::Builtin::*;
|
||||
use Layout::*;
|
||||
|
||||
match self {
|
||||
Union(variant) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
matches!(variant, Recursive(_)| NullableWrapped { .. } | NullableUnwrapped { .. })
|
||||
}
|
||||
|
||||
RecursivePointer => true,
|
||||
|
||||
Builtin(List(MemoryMode::Refcounted, _)) | Builtin(Str) => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Even if a value (say, a record) is not itself reference counted,
|
||||
|
@ -565,12 +624,18 @@ impl<'a> Layout<'a> {
|
|||
Builtin(builtin) => builtin.is_refcounted(),
|
||||
PhantomEmptyStruct => false,
|
||||
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
|
||||
Union(fields) => fields
|
||||
.iter()
|
||||
.map(|ls| ls.iter())
|
||||
.flatten()
|
||||
.any(|f| f.contains_refcounted()),
|
||||
RecursiveUnion(_) => true,
|
||||
Union(variant) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
match variant {
|
||||
NonRecursive(fields) => fields
|
||||
.iter()
|
||||
.map(|ls| ls.iter())
|
||||
.flatten()
|
||||
.any(|f| f.contains_refcounted()),
|
||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => true,
|
||||
}
|
||||
}
|
||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,
|
||||
}
|
||||
|
@ -992,7 +1057,23 @@ fn layout_from_flat_type<'a>(
|
|||
let mut tags_vec: std::vec::Vec<_> = tags.into_iter().collect();
|
||||
tags_vec.sort();
|
||||
|
||||
for (_name, variables) in tags_vec {
|
||||
let mut nullable = None;
|
||||
|
||||
if GENERATE_NULLABLE {
|
||||
for (index, (_name, variables)) in tags_vec.iter().enumerate() {
|
||||
if variables.is_empty() {
|
||||
nullable = Some(index as i64);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, (_name, variables)) in tags_vec.into_iter().enumerate() {
|
||||
if matches!(nullable, Some(i) if i == index as i64) {
|
||||
// don't add the
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena);
|
||||
|
||||
// store the discriminant
|
||||
|
@ -1020,7 +1101,26 @@ fn layout_from_flat_type<'a>(
|
|||
tag_layouts.push(tag_layout.into_bump_slice());
|
||||
}
|
||||
|
||||
Ok(Layout::RecursiveUnion(tag_layouts.into_bump_slice()))
|
||||
let union_layout = if let Some(tag_id) = nullable {
|
||||
match tag_layouts.into_bump_slice() {
|
||||
[one] => {
|
||||
let nullable_id = tag_id != 0;
|
||||
|
||||
UnionLayout::NullableUnwrapped {
|
||||
nullable_id,
|
||||
other_fields: one,
|
||||
}
|
||||
}
|
||||
many => UnionLayout::NullableWrapped {
|
||||
nullable_id: tag_id,
|
||||
other_tags: many,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
UnionLayout::Recursive(tag_layouts.into_bump_slice())
|
||||
};
|
||||
|
||||
Ok(Layout::Union(union_layout))
|
||||
}
|
||||
EmptyTagUnion => {
|
||||
panic!("TODO make Layout for empty Tag Union");
|
||||
|
@ -1101,16 +1201,106 @@ pub enum UnionVariant<'a> {
|
|||
Never,
|
||||
Unit,
|
||||
UnitWithArguments,
|
||||
BoolUnion {
|
||||
ttrue: TagName,
|
||||
ffalse: TagName,
|
||||
},
|
||||
BoolUnion { ttrue: TagName, ffalse: TagName },
|
||||
ByteUnion(Vec<'a, TagName>),
|
||||
Unwrapped(Vec<'a, Layout<'a>>),
|
||||
Wrapped {
|
||||
Wrapped(WrappedVariant<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum WrappedVariant<'a> {
|
||||
Recursive {
|
||||
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
||||
is_recursive: bool,
|
||||
},
|
||||
NonRecursive {
|
||||
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
||||
},
|
||||
NullableWrapped {
|
||||
nullable_id: i64,
|
||||
nullable_name: TagName,
|
||||
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
||||
},
|
||||
NullableUnwrapped {
|
||||
nullable_id: bool,
|
||||
nullable_name: TagName,
|
||||
other_name: TagName,
|
||||
other_fields: &'a [Layout<'a>],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> WrappedVariant<'a> {
|
||||
pub fn tag_name_to_id(&self, tag_name: &TagName) -> (u8, &'a [Layout<'a>]) {
|
||||
use WrappedVariant::*;
|
||||
|
||||
match self {
|
||||
Recursive { sorted_tag_layouts } | NonRecursive { sorted_tag_layouts } => {
|
||||
let (tag_id, (_, argument_layouts)) = sorted_tag_layouts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, (key, _))| key == tag_name)
|
||||
.expect("tag name is not in its own type");
|
||||
|
||||
debug_assert!(tag_id < 256);
|
||||
(tag_id as u8, *argument_layouts)
|
||||
}
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
nullable_name,
|
||||
sorted_tag_layouts,
|
||||
} => {
|
||||
// assumption: the nullable_name is not included in sorted_tag_layouts
|
||||
|
||||
if tag_name == nullable_name {
|
||||
(*nullable_id as u8, &[] as &[_])
|
||||
} else {
|
||||
let (mut tag_id, (_, argument_layouts)) = sorted_tag_layouts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, (key, _))| key == tag_name)
|
||||
.expect("tag name is not in its own type");
|
||||
|
||||
if tag_id >= *nullable_id as usize {
|
||||
tag_id += 1;
|
||||
}
|
||||
|
||||
debug_assert!(tag_id < 256);
|
||||
(tag_id as u8, *argument_layouts)
|
||||
}
|
||||
}
|
||||
NullableUnwrapped {
|
||||
nullable_id,
|
||||
nullable_name,
|
||||
other_name,
|
||||
other_fields,
|
||||
} => {
|
||||
if tag_name == nullable_name {
|
||||
(*nullable_id as u8, &[] as &[_])
|
||||
} else {
|
||||
debug_assert_eq!(other_name, tag_name);
|
||||
|
||||
(!*nullable_id as u8, *other_fields)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number_of_tags(&'a self) -> usize {
|
||||
use WrappedVariant::*;
|
||||
|
||||
match self {
|
||||
Recursive { sorted_tag_layouts } | NonRecursive { sorted_tag_layouts } => {
|
||||
sorted_tag_layouts.len()
|
||||
}
|
||||
NullableWrapped {
|
||||
sorted_tag_layouts, ..
|
||||
} => {
|
||||
// assumption: the nullable_name is not included in sorted_tag_layouts
|
||||
|
||||
sorted_tag_layouts.len() + 1
|
||||
}
|
||||
NullableUnwrapped { .. } => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union_sorted_tags<'a>(arena: &'a Bump, var: Variable, subs: &Subs) -> UnionVariant<'a> {
|
||||
|
@ -1228,8 +1418,26 @@ pub fn union_sorted_tags_help<'a>(
|
|||
let mut answer = Vec::with_capacity_in(tags_vec.len(), arena);
|
||||
let mut has_any_arguments = false;
|
||||
|
||||
for (tag_name, arguments) in tags_vec {
|
||||
let mut nullable = None;
|
||||
|
||||
// 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() {
|
||||
if variables.is_empty() {
|
||||
nullable = Some((index as i64, name.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, (tag_name, arguments)) in tags_vec.into_iter().enumerate() {
|
||||
// reserve space for the tag discriminant
|
||||
if matches!(nullable, Some((i, _)) if i as usize == index) {
|
||||
debug_assert!(arguments.is_empty());
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
|
||||
|
||||
// add the tag discriminant (size currently always hardcoded to i64)
|
||||
|
@ -1289,10 +1497,37 @@ pub fn union_sorted_tags_help<'a>(
|
|||
|
||||
UnionVariant::ByteUnion(tag_names)
|
||||
}
|
||||
_ => UnionVariant::Wrapped {
|
||||
sorted_tag_layouts: answer,
|
||||
is_recursive: opt_rec_var.is_some(),
|
||||
},
|
||||
_ => {
|
||||
let variant = if let Some((nullable_id, nullable_name)) = nullable {
|
||||
if answer.len() == 1 {
|
||||
let (other_name, other_arguments) = answer.drain(..).next().unwrap();
|
||||
let nullable_id = nullable_id != 0;
|
||||
|
||||
WrappedVariant::NullableUnwrapped {
|
||||
nullable_id,
|
||||
nullable_name,
|
||||
other_name,
|
||||
other_fields: other_arguments,
|
||||
}
|
||||
} else {
|
||||
WrappedVariant::NullableWrapped {
|
||||
nullable_id,
|
||||
nullable_name,
|
||||
sorted_tag_layouts: answer,
|
||||
}
|
||||
}
|
||||
} else if is_recursive {
|
||||
WrappedVariant::Recursive {
|
||||
sorted_tag_layouts: answer,
|
||||
}
|
||||
} else {
|
||||
WrappedVariant::NonRecursive {
|
||||
sorted_tag_layouts: answer,
|
||||
}
|
||||
};
|
||||
|
||||
UnionVariant::Wrapped(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1320,7 +1555,7 @@ pub fn layout_from_tag_union<'a>(
|
|||
let variant = union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs);
|
||||
|
||||
match variant {
|
||||
Never => Layout::Union(&[]),
|
||||
Never => Layout::Union(UnionLayout::NonRecursive(&[])),
|
||||
Unit | UnitWithArguments => Layout::Struct(&[]),
|
||||
BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
|
||||
ByteUnion(_) => Layout::Builtin(Builtin::Int8),
|
||||
|
@ -1331,20 +1566,43 @@ pub fn layout_from_tag_union<'a>(
|
|||
Layout::Struct(field_layouts.into_bump_slice())
|
||||
}
|
||||
}
|
||||
Wrapped {
|
||||
sorted_tag_layouts: tags,
|
||||
is_recursive,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
Wrapped(variant) => {
|
||||
use WrappedVariant::*;
|
||||
|
||||
for (_, tag_layout) in tags {
|
||||
tag_layouts.push(tag_layout);
|
||||
}
|
||||
match variant {
|
||||
NonRecursive {
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||
|
||||
if is_recursive {
|
||||
Layout::RecursiveUnion(tag_layouts.into_bump_slice())
|
||||
} else {
|
||||
Layout::Union(tag_layouts.into_bump_slice())
|
||||
Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice()))
|
||||
}
|
||||
|
||||
Recursive {
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||
|
||||
Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice()))
|
||||
}
|
||||
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
nullable_name: _,
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||
|
||||
Layout::Union(UnionLayout::NullableWrapped {
|
||||
nullable_id,
|
||||
other_tags: tag_layouts.into_bump_slice(),
|
||||
})
|
||||
}
|
||||
|
||||
NullableUnwrapped { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue