use crate::ir::Parens; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::{default_hasher, MutMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; use roc_types::subs::{ Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField}; use std::collections::HashMap; use ven_pretty::{DocAllocator, DocBuilder}; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::() * 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; #[derive(Debug, Clone)] pub enum LayoutProblem { UnresolvedTypeVar(Variable), Erroneous, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum RawFunctionLayout<'a> { Function(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>), ZeroArgumentThunk(Layout<'a>), } impl RawFunctionLayout<'_> { pub fn is_zero_argument_thunk(&self) -> bool { matches!(self, RawFunctionLayout::ZeroArgumentThunk(_)) } } /// Types for code gen must be monomorphic. No type variables allowed! #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Layout<'a> { Builtin(Builtin<'a>), /// A layout that is empty (turns into the empty struct in LLVM IR /// but for our purposes, not zero-sized, so it does not get dropped from data structures /// this is important for closures that capture zero-sized values Struct(&'a [Layout<'a>]), Union(UnionLayout<'a>), RecursivePointer, /// A function. The types of its arguments, then the type of its return value. Closure(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>), } #[derive(Clone, Copy, 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. `Expr : [ Sym Str, Add Expr Expr ]` Recursive(&'a [&'a [Layout<'a>]]), /// A recursive tag union with just one constructor /// e.g. `RoseTree a : [ Tree a (List (RoseTree a)) ]` NonNullableUnwrapped(&'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>], }, } impl<'a> UnionLayout<'a> { pub fn to_doc(self, alloc: &'a D, _parens: Parens) -> DocBuilder<'a, D, A> where D: DocAllocator<'a, A>, D::Doc: Clone, A: Clone, { use UnionLayout::*; match self { NonRecursive(tags) => { let tags_doc = tags.iter().map(|fields| { alloc.text("C ").append(alloc.intersperse( fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), " ", )) }); alloc .text("[") .append(alloc.intersperse(tags_doc, ", ")) .append(alloc.text("]")) } _ => alloc.text("TODO"), } } pub fn layout_at(self, tag_id: u8, index: usize) -> Layout<'a> { let result = match self { UnionLayout::NonRecursive(tag_layouts) => { let field_layouts = tag_layouts[tag_id as usize]; // this cannot be recursive; return immediately return field_layouts[index]; } UnionLayout::Recursive(tag_layouts) => { let field_layouts = tag_layouts[tag_id as usize]; field_layouts[index] } UnionLayout::NonNullableUnwrapped(field_layouts) => field_layouts[index], UnionLayout::NullableWrapped { nullable_id, other_tags, } => { debug_assert_ne!(nullable_id, tag_id as i64); let tag_index = if (tag_id as i64) < nullable_id { tag_id } else { tag_id - 1 }; let field_layouts = other_tags[tag_index as usize]; field_layouts[index] } UnionLayout::NullableUnwrapped { nullable_id, other_fields, } => { debug_assert_ne!(nullable_id, tag_id != 0); other_fields[index as usize] } }; if let Layout::RecursivePointer = result { Layout::Union(self) } else { result } } pub fn number_of_tags(&'a self) -> usize { match self { UnionLayout::NonRecursive(tags) | UnionLayout::Recursive(tags) => tags.len(), UnionLayout::NullableWrapped { other_tags, .. } => other_tags.len() + 1, UnionLayout::NonNullableUnwrapped(_) => 1, UnionLayout::NullableUnwrapped { .. } => 2, } } fn tag_id_builtin_help(union_size: usize) -> Builtin<'a> { if union_size <= u8::MAX as usize { Builtin::Int8 } else if union_size <= u16::MAX as usize { Builtin::Int16 } else { panic!("tag union is too big") } } pub fn tag_id_builtin(&self) -> Builtin<'a> { match self { UnionLayout::NonRecursive(_tags) => { // let union_size = tags.len(); // Self::tag_id_builtin_help(union_size) // The quicksort-benchmarks version of Quicksort.roc segfaults when // this number is not I64. There must be some dependence on that fact // somewhere in the code, I have not found where that is yet... Builtin::Int64 } UnionLayout::Recursive(tags) => { let union_size = tags.len(); Self::tag_id_builtin_help(union_size) } UnionLayout::NullableWrapped { other_tags, .. } => { Self::tag_id_builtin_help(other_tags.len() + 1) } UnionLayout::NonNullableUnwrapped(_) => Builtin::Int1, UnionLayout::NullableUnwrapped { .. } => Builtin::Int1, } } pub fn tag_id_layout(&self) -> Layout<'a> { Layout::Builtin(self.tag_id_builtin()) } fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], ptr_bytes: u32) -> bool { tags.len() <= ptr_bytes as usize } // i.e. it is not implicit and not stored in the pointer bits pub fn stores_tag_id_as_data(&self, ptr_bytes: u32) -> bool { match self { UnionLayout::NonRecursive(_) => true, UnionLayout::Recursive(tags) | UnionLayout::NullableWrapped { other_tags: tags, .. } => !Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes), UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false, } } pub fn stores_tag_id_in_pointer(&self, ptr_bytes: u32) -> bool { match self { UnionLayout::NonRecursive(_) => false, UnionLayout::Recursive(tags) | UnionLayout::NullableWrapped { other_tags: tags, .. } => Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes), UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false, } } pub fn tag_is_null(&self, tag_id: u8) -> bool { match self { UnionLayout::NonRecursive(_) | UnionLayout::NonNullableUnwrapped(_) | UnionLayout::Recursive(_) => false, UnionLayout::NullableWrapped { nullable_id, .. } => *nullable_id == tag_id as i64, UnionLayout::NullableUnwrapped { nullable_id, .. } => *nullable_id == (tag_id != 0), } } pub fn is_nullable(&self) -> bool { match self { UnionLayout::NonRecursive(_) | UnionLayout::Recursive(_) | UnionLayout::NonNullableUnwrapped { .. } => false, UnionLayout::NullableWrapped { .. } | UnionLayout::NullableUnwrapped { .. } => true, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LambdaSet<'a> { /// collection of function names and their closure arguments pub set: &'a [(Symbol, &'a [Layout<'a>])], /// how the closure will be represented at runtime representation: &'a Layout<'a>, } /// representation of the closure *for a particular function* #[derive(Debug)] pub enum ClosureRepresentation<'a> { /// the closure is represented as a union. Includes the tag ID! Union { tag_layout: &'a [Layout<'a>], tag_name: TagName, tag_id: u8, union_layout: UnionLayout<'a>, }, /// the representation is anything but a union Other(Layout<'a>), } impl<'a> LambdaSet<'a> { pub fn runtime_representation(&self) -> Layout<'a> { *self.representation } pub fn is_represented(&self) -> Option> { if let Layout::Struct(&[]) = self.representation { None } else { Some(*self.representation) } } pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> { debug_assert!( self.set.iter().any(|(s, _)| *s == function_symbol), "function symbol not in set" ); match self.representation { Layout::Union(union) => { // here we rely on the fact that a union in a closure would be stored in a one-element record. // a closure representation that is itself union must be a of the shape `Closure1 ... | Closure2 ...` match union { UnionLayout::NonRecursive(tags) => { let index = self .set .iter() .position(|(s, _)| *s == function_symbol) .unwrap(); ClosureRepresentation::Union { tag_id: index as u8, tag_layout: tags[index], tag_name: TagName::Closure(function_symbol), union_layout: *union, } } UnionLayout::Recursive(_) => todo!("recursive closures"), UnionLayout::NonNullableUnwrapped(_) => todo!("recursive closures"), UnionLayout::NullableWrapped { nullable_id: _, other_tags: _, } => todo!("recursive closures"), UnionLayout::NullableUnwrapped { nullable_id: _, other_fields: _, } => todo!("recursive closures"), } } _ => ClosureRepresentation::Other(*self.representation), } } pub fn extend_argument_list( &self, arena: &'a Bump, argument_layouts: &'a [Layout<'a>], ) -> &'a [Layout<'a>] { if let [] = self.set { // TERRIBLE HACK for builting functions argument_layouts } else { match self.representation { Layout::Struct(&[]) => { // this function does not have anything in its closure, and the lambda set is a // singleton, so we pass no extra argument argument_layouts } Layout::Builtin(Builtin::Int1) | Layout::Builtin(Builtin::Int8) => { // we don't pass this along either argument_layouts } _ => { let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena); arguments.extend(argument_layouts); arguments.push(self.runtime_representation()); arguments.into_bump_slice() } } } } pub fn from_var( arena: &'a Bump, subs: &Subs, closure_var: Variable, ) -> Result { let mut tags = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) { Ok(()) | Err((_, Content::FlexVar(_))) if !tags.is_empty() => { // sort the tags; make sure ordering stays intact! tags.sort(); let mut set = Vec::with_capacity_in(tags.len(), arena); let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; for (tag_name, variables) in tags.iter() { if let TagName::Closure(function_symbol) = tag_name { let mut arguments = Vec::with_capacity_in(variables.len(), arena); for var in variables { arguments.push(Layout::from_var(&mut env, *var)?); } set.push((*function_symbol, arguments.into_bump_slice())); } else { unreachable!("non-closure tag name in lambda set"); } } let representation = arena.alloc(Self::make_representation(arena, subs, tags)); Ok(LambdaSet { set: set.into_bump_slice(), representation, }) } Ok(()) | Err((_, Content::FlexVar(_))) => { // TODO hack for builting functions. Ok(LambdaSet { set: &[], representation: arena.alloc(Layout::Struct(&[])), }) } _ => panic!("called LambdaSet.from_var on invalid input"), } } fn make_representation( arena: &'a Bump, subs: &Subs, tags: std::vec::Vec<(TagName, std::vec::Vec)>, ) -> Layout<'a> { // otherwise, this is a closure with a payload let variant = union_sorted_tags_help(arena, tags, None, subs); use UnionVariant::*; match variant { Never => Layout::Union(UnionLayout::NonRecursive(&[])), Unit | UnitWithArguments | BoolUnion { .. } | ByteUnion(_) => { // no useful information to store Layout::Struct(&[]) } Newtype { arguments: layouts, .. } => Layout::Struct(layouts.into_bump_slice()), Wrapped(variant) => { use WrappedVariant::*; match variant { NonRecursive { sorted_tag_layouts: tags, } => { debug_assert!(tags.len() > 1); // if the closed-over value is actually a layout, it should be wrapped in a 1-element record debug_assert!(matches!(tags[0].0, TagName::Closure(_))); let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena); for (_, tag_args) in tags.iter() { tag_arguments.push(&tag_args[0..]); } Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice())) } _ => panic!("handle recursive layouts"), } } } } pub fn stack_size(&self, pointer_size: u32) -> u32 { self.representation.stack_size(pointer_size) } pub fn contains_refcounted(&self) -> bool { self.representation.contains_refcounted() } pub fn safe_to_memcpy(&self) -> bool { self.representation.safe_to_memcpy() } pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { self.representation.alignment_bytes(pointer_size) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Builtin<'a> { Int128, Int64, Int32, Int16, Int8, Int1, Usize, Decimal, Float128, Float64, Float32, Float16, Str, Dict(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), List(&'a Layout<'a>), EmptyStr, EmptyList, EmptyDict, EmptySet, } pub struct Env<'a, 'b> { arena: &'a Bump, seen: Vec<'a, Variable>, subs: &'b Subs, } impl<'a, 'b> Env<'a, 'b> { fn is_seen(&self, var: Variable) -> bool { let var = self.subs.get_root_key_without_compacting(var); self.seen.iter().rev().any(|x| x == &var) } fn insert_seen(&mut self, var: Variable) { let var = self.subs.get_root_key_without_compacting(var); self.seen.push(var); } fn remove_seen(&mut self, var: Variable) -> bool { let var = self.subs.get_root_key_without_compacting(var); if let Some(index) = self.seen.iter().rposition(|x| x == &var) { self.seen.remove(index); true } else { false } } } impl<'a> Layout<'a> { fn new_help<'b>( env: &mut Env<'a, 'b>, var: Variable, content: Content, ) -> Result { use roc_types::subs::Content::*; match content { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), RecursionVar { structure, .. } => { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, structure_content.clone()) } Structure(flat_type) => layout_from_flat_type(env, flat_type), // Ints Alias(Symbol::NUM_I128, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int128)) } Alias(Symbol::NUM_I64, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } Alias(Symbol::NUM_I32, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int32)) } Alias(Symbol::NUM_I16, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int16)) } Alias(Symbol::NUM_I8, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int8)) } // I think unsigned and signed use the same layout Alias(Symbol::NUM_U128, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int128)) } Alias(Symbol::NUM_U64, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } Alias(Symbol::NUM_U32, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int32)) } Alias(Symbol::NUM_U16, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int16)) } Alias(Symbol::NUM_U8, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int8)) } // Floats Alias(Symbol::NUM_F64, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } Alias(Symbol::NUM_F32, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float32)) } Alias(_, _, var) => Self::from_var(env, var), Error => Err(LayoutProblem::Erroneous), } } /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. /// Panics if given a FlexVar or RigidVar, since those should have been /// monomorphized away already! fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Result { if env.is_seen(var) { Ok(Layout::RecursivePointer) } else { let content = env.subs.get_content_without_compacting(var); Self::new_help(env, var, content.clone()) } } pub fn safe_to_memcpy(&self) -> bool { use Layout::*; match self { Builtin(builtin) => builtin.safe_to_memcpy(), Struct(fields) => fields .iter() .all(|field_layout| field_layout.safe_to_memcpy()), 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 { .. } | NonNullableUnwrapped(_) => { // a recursive union will always contain a pointer, and is thus not safe to memcpy false } } } Closure(_, closure_layout, _) => closure_layout.safe_to_memcpy(), RecursivePointer => { // We cannot memcpy pointers, because then we would have the same pointer in multiple places! false } } } pub fn is_dropped_because_empty(&self) -> bool { // For this calculation, we don't need an accurate // stack size, we just need to know whether it's zero, // so it's fine to use a pointer size of 1. false } pub fn stack_size(&self, pointer_size: u32) -> u32 { let width = self.stack_size_without_alignment(pointer_size); let alignment = self.alignment_bytes(pointer_size); if alignment != 0 && width % alignment > 0 { width + alignment - (width % alignment) } else { width } } fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 { use Layout::*; match self { Builtin(builtin) => builtin.stack_size(pointer_size), Struct(fields) => { let mut sum = 0; for field_layout in *fields { sum += field_layout.stack_size(pointer_size); } sum } Union(variant) => { use UnionLayout::*; match variant { NonRecursive(fields) => { fields .iter() .map(|tag_layout| { tag_layout .iter() .map(|field| field.stack_size(pointer_size)) .sum::() }) .max() .unwrap_or_default() // the size of the tag_id + variant.tag_id_builtin().stack_size(pointer_size) } Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } | NonNullableUnwrapped(_) => pointer_size, } } Closure(_, lambda_set, _) => lambda_set.stack_size(pointer_size), RecursivePointer => pointer_size, } } pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { match self { Layout::Struct(fields) => fields .iter() .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 { .. } | NonNullableUnwrapped(_) => pointer_size, } } Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size), Layout::RecursivePointer => pointer_size, Layout::Closure(_, captured, _) => { pointer_size.max(captured.alignment_bytes(pointer_size)) } } } pub fn is_refcounted(&self) -> bool { use self::Builtin::*; use Layout::*; match self { Union(variant) => { use UnionLayout::*; matches!( variant, Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } ) } RecursivePointer => true, Builtin(List(_)) | Builtin(Str) => true, _ => false, } } /// Even if a value (say, a record) is not itself reference counted, /// it may contains values/fields that are. Therefore when this record /// goes out of scope, the refcount on those values/fields must be decremented. pub fn contains_refcounted(&self) -> bool { use Layout::*; match self { Builtin(builtin) => builtin.is_refcounted(), Struct(fields) => fields.iter().any(|f| f.contains_refcounted()), Union(variant) => { use UnionLayout::*; match variant { NonRecursive(fields) => fields .iter() .map(|ls| ls.iter()) .flatten() .any(|f| f.contains_refcounted()), Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } | NonNullableUnwrapped(_) => true, } } RecursivePointer => true, Closure(_, closure_layout, _) => closure_layout.contains_refcounted(), } } pub fn to_doc(self, alloc: &'a D, parens: Parens) -> DocBuilder<'a, D, A> where D: DocAllocator<'a, A>, D::Doc: Clone, A: Clone, { use Layout::*; match self { Builtin(builtin) => builtin.to_doc(alloc, parens), Struct(fields) => { let fields_doc = fields.iter().map(|x| x.to_doc(alloc, parens)); alloc .text("{") .append(alloc.intersperse(fields_doc, ", ")) .append(alloc.text("}")) } Union(union_layout) => union_layout.to_doc(alloc, parens), RecursivePointer => alloc.text("*self"), Closure(args, closure_layout, result) => { let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction)); let bom = closure_layout .representation .to_doc(alloc, Parens::NotNeeded); alloc .intersperse(args_doc, ", ") .append(alloc.text(" {| ")) .append(bom) .append(" |} -> ") .append(result.to_doc(alloc, Parens::InFunction)) } } } } /// Avoid recomputing Layout from Variable multiple times. /// We use `ena` for easy snapshots and rollbacks of the cache. /// During specialization, a type variable `a` can be specialized to different layouts, /// e.g. `identity : a -> a` could be specialized to `Bool -> Bool` or `Str -> Str`. /// Therefore in general it's invalid to store a map from variables to layouts /// But if we're careful when to invalidate certain keys, we still get some benefit #[derive(Default, Debug)] pub struct LayoutCache<'a> { _marker: std::marker::PhantomData<&'a u8>, } #[derive(Debug, Clone)] pub enum CachedLayout<'a> { Cached(Layout<'a>), NotCached, Problem(LayoutProblem), } impl<'a> LayoutCache<'a> { pub fn from_var( &mut self, arena: &'a Bump, var: Variable, subs: &Subs, ) -> Result, LayoutProblem> { // Store things according to the root Variable, to avoid duplicate work. let var = subs.get_root_key_without_compacting(var); let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; Layout::from_var(&mut env, var) } pub fn raw_from_var( &mut self, arena: &'a Bump, var: Variable, subs: &Subs, ) -> Result, LayoutProblem> { // Store things according to the root Variable, to avoid duplicate work. let var = subs.get_root_key_without_compacting(var); let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; Layout::from_var(&mut env, var).map(|l| match l { Layout::Closure(a, b, c) => RawFunctionLayout::Function(a, b, c), other => RawFunctionLayout::ZeroArgumentThunk(other), }) } pub fn snapshot(&mut self) -> SnapshotKeyPlaceholder { SnapshotKeyPlaceholder } pub fn rollback_to(&mut self, _snapshot: SnapshotKeyPlaceholder) {} } // placeholder for the type ven_ena::unify::Snapshot>> pub struct SnapshotKeyPlaceholder; impl<'a> Builtin<'a> { const I128_SIZE: u32 = std::mem::size_of::() as u32; const I64_SIZE: u32 = std::mem::size_of::() as u32; const I32_SIZE: u32 = std::mem::size_of::() as u32; const I16_SIZE: u32 = std::mem::size_of::() as u32; const I8_SIZE: u32 = std::mem::size_of::() as u32; const I1_SIZE: u32 = std::mem::size_of::() as u32; const USIZE_SIZE: u32 = std::mem::size_of::() as u32; const DECIMAL_SIZE: u32 = std::mem::size_of::() as u32; const F128_SIZE: u32 = 16; const F64_SIZE: u32 = std::mem::size_of::() as u32; const F32_SIZE: u32 = std::mem::size_of::() as u32; const F16_SIZE: u32 = 2; /// Number of machine words in an empty one of these pub const STR_WORDS: u32 = 2; pub const DICT_WORDS: u32 = 3; pub const SET_WORDS: u32 = Builtin::DICT_WORDS; // Set is an alias for Dict with {} for value pub const LIST_WORDS: u32 = 2; /// Layout of collection wrapper for List and Str - a struct of (pointer, length). /// /// We choose this layout (with pointer first) because it's how /// Rust slices are laid out, meaning we can cast to/from them for free. pub const WRAPPER_PTR: u32 = 0; pub const WRAPPER_LEN: u32 = 1; pub fn stack_size(&self, pointer_size: u32) -> u32 { use Builtin::*; match self { Int128 => Builtin::I128_SIZE, Int64 => Builtin::I64_SIZE, Int32 => Builtin::I32_SIZE, Int16 => Builtin::I16_SIZE, Int8 => Builtin::I8_SIZE, Int1 => Builtin::I1_SIZE, Usize => Builtin::USIZE_SIZE, Decimal => Builtin::DECIMAL_SIZE, Float128 => Builtin::F128_SIZE, Float64 => Builtin::F64_SIZE, Float32 => Builtin::F32_SIZE, Float16 => Builtin::F16_SIZE, Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size, } } pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { use std::mem::align_of; use Builtin::*; // for our data structures, what counts is the alignment of the `( ptr, len )` tuple, and // since both of those are one pointer size, the alignment of that structure is a pointer // size match self { Int128 => align_of::() as u32, Int64 => align_of::() as u32, Int32 => align_of::() as u32, Int16 => align_of::() as u32, Int8 => align_of::() as u32, Int1 => align_of::() as u32, Usize => align_of::() as u32, Decimal => align_of::() as u32, Float128 => align_of::() as u32, Float64 => align_of::() as u32, Float32 => align_of::() as u32, Float16 => align_of::() as u32, Str | EmptyStr => pointer_size, Dict(_, _) | EmptyDict => pointer_size, Set(_) | EmptySet => pointer_size, List(_) | EmptyList => pointer_size, } } pub fn safe_to_memcpy(&self) -> bool { use Builtin::*; match self { Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, Str | Dict(_, _) | Set(_) | List(_) => false, } } // Question: does is_refcounted exactly correspond with the "safe to memcpy" property? pub fn is_refcounted(&self) -> bool { use Builtin::*; match self { Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, List(_) => true, Str | Dict(_, _) | Set(_) => true, } } pub fn to_doc(self, alloc: &'a D, _parens: Parens) -> DocBuilder<'a, D, A> where D: DocAllocator<'a, A>, D::Doc: Clone, A: Clone, { use Builtin::*; match self { Int128 => alloc.text("Int128"), Int64 => alloc.text("Int64"), Int32 => alloc.text("Int32"), Int16 => alloc.text("Int16"), Int8 => alloc.text("Int8"), Int1 => alloc.text("Int1"), Usize => alloc.text("Usize"), Decimal => alloc.text("Decimal"), Float128 => alloc.text("Float128"), Float64 => alloc.text("Float64"), Float32 => alloc.text("Float32"), Float16 => alloc.text("Float16"), EmptyStr => alloc.text("EmptyStr"), EmptyList => alloc.text("EmptyList"), EmptyDict => alloc.text("EmptyDict"), EmptySet => alloc.text("EmptySet"), Str => alloc.text("Str"), List(layout) => alloc .text("List ") .append(layout.to_doc(alloc, Parens::InTypeParam)), Set(layout) => alloc .text("Set ") .append(layout.to_doc(alloc, Parens::InTypeParam)), Dict(key_layout, value_layout) => alloc .text("Dict ") .append(key_layout.to_doc(alloc, Parens::InTypeParam)) .append(" ") .append(value_layout.to_doc(alloc, Parens::InTypeParam)), } } } fn layout_from_flat_type<'a>( env: &mut Env<'a, '_>, flat_type: FlatType, ) -> Result, LayoutProblem> { use roc_types::subs::FlatType::*; let arena = env.arena; let subs = env.subs; match flat_type { Apply(symbol, args) => { let args = Vec::from_iter_in(args.into_iter().map(|index| subs[index]), arena); match symbol { // Ints Symbol::NUM_NAT => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Usize)) } Symbol::NUM_I128 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int128)) } Symbol::NUM_I64 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int64)) } Symbol::NUM_I32 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int32)) } Symbol::NUM_I16 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int16)) } Symbol::NUM_I8 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int8)) } Symbol::NUM_U128 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int128)) } Symbol::NUM_U64 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int64)) } Symbol::NUM_U32 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int32)) } Symbol::NUM_U16 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int16)) } Symbol::NUM_U8 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int8)) } // Floats Symbol::NUM_DEC => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Decimal)) } Symbol::NUM_F64 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Float64)) } Symbol::NUM_F32 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Float32)) } Symbol::NUM_NUM | Symbol::NUM_AT_NUM => { // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer debug_assert_eq!(args.len(), 1); let var = args[0]; let content = subs.get_content_without_compacting(var); layout_from_num_content(content) } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::LIST_LIST => list_layout_from_elem(env, args[0]), Symbol::DICT_DICT => dict_layout_from_key_value(env, args[0], args[1]), Symbol::SET_SET => dict_layout_from_key_value(env, args[0], Variable::EMPTY_RECORD), _ => { panic!( "TODO layout_from_flat_type for Apply({:?}, {:?})", symbol, args ); } } } Func(args, closure_var, ret_var) => { let mut fn_args = Vec::with_capacity_in(args.len(), arena); for index in args.into_iter() { let arg_var = env.subs[index]; fn_args.push(Layout::from_var(env, arg_var)?); } let ret = Layout::from_var(env, ret_var)?; let fn_args = fn_args.into_bump_slice(); let ret = arena.alloc(ret); let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var)?; Ok(Layout::Closure(fn_args, lambda_set, ret)) } Record(fields, ext_var) => { // extract any values from the ext_var let pairs_it = fields .unsorted_iterator(subs, ext_var) .filter_map(|(label, field)| { // drop optional fields let var = match field { RecordField::Optional(_) => return None, RecordField::Required(var) => var, RecordField::Demanded(var) => var, }; Some(( label, Layout::from_var(env, var).expect("invalid layout from var"), )) }); let mut pairs = Vec::from_iter_in(pairs_it, arena); pairs.sort_by(|(label1, layout1), (label2, layout2)| { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1).then(label1.cmp(label2)) }); let mut layouts = Vec::from_iter_in(pairs.into_iter().map(|t| t.1), arena); if layouts.len() == 1 { // If the record has only one field that isn't zero-sized, // unwrap it. Ok(layouts.pop().unwrap()) } else { Ok(Layout::Struct(layouts.into_bump_slice())) } } TagUnion(tags, ext_var) => { debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); Ok(layout_from_tag_union(arena, tags, subs)) } FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); let tags = UnionTags::from_tag_name_index(tag_name); Ok(layout_from_tag_union(arena, tags, subs)) } RecursiveTagUnion(rec_var, tags, ext_var) => { debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); // some observations // // * recursive tag unions are always recursive // * therefore at least one tag has a pointer (non-zero sized) field // * they must (to be instantiated) have 2 or more tags // // 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 tags_vec = cheap_sort_tags(arena, tags, subs); 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; } } } env.insert_seen(rec_var); for (index, (_name, variables)) in tags_vec.into_iter().enumerate() { if matches!(nullable, Some(i) if i == index as i64) { // don't add the nullable case continue; } let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena); for var_index in variables { let var = subs[var_index]; // TODO does this cause problems with mutually recursive unions? if rec_var == subs.get_root_key_without_compacting(var) { tag_layout.push(Layout::RecursivePointer); continue; } tag_layout.push(Layout::from_var(env, var)?); } tag_layout.sort_by(|layout1, layout2| { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1) }); tag_layouts.push(tag_layout.into_bump_slice()); } env.remove_seen(rec_var); 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 if tag_layouts.len() == 1 { // drop the tag id UnionLayout::NonNullableUnwrapped(tag_layouts.pop().unwrap()) } else { UnionLayout::Recursive(tag_layouts.into_bump_slice()) }; Ok(Layout::Union(union_layout)) } EmptyTagUnion => { panic!("TODO make Layout for empty Tag Union"); } Erroneous(_) => Err(LayoutProblem::Erroneous), EmptyRecord => Ok(Layout::Struct(&[])), } } pub fn sort_record_fields<'a>( arena: &'a Bump, var: Variable, subs: &Subs, ) -> Vec<'a, (Lowercase, Variable, Result, Layout<'a>>)> { let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; let (it, _) = gather_fields_unsorted_iter(subs, RecordFields::empty(), var); let it = it .into_iter() .map(|(field, field_type)| (field.clone(), field_type)); sort_record_fields_help(&mut env, it) } fn sort_record_fields_help<'a>( env: &mut Env<'a, '_>, fields_map: impl Iterator)>, ) -> Vec<'a, (Lowercase, Variable, Result, Layout<'a>>)> { // Sort the fields by label let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena); for (label, field) in fields_map { let var = match field { RecordField::Demanded(v) => v, RecordField::Required(v) => v, RecordField::Optional(v) => { let layout = Layout::from_var(env, v).expect("invalid layout from var"); sorted_fields.push((label, v, Err(layout))); continue; } }; let layout = Layout::from_var(env, var).expect("invalid layout from var"); sorted_fields.push((label, var, Ok(layout))); } sorted_fields.sort_by( |(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 { Ok(layout1) | Err(layout1) => match res_layout2 { Ok(layout2) | Err(layout2) => { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1).then(label1.cmp(label2)) } }, }, ); sorted_fields } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum UnionVariant<'a> { Never, Unit, UnitWithArguments, BoolUnion { ttrue: TagName, ffalse: TagName, }, ByteUnion(Vec<'a, TagName>), Newtype { tag_name: TagName, arguments: Vec<'a, Layout<'a>>, }, Wrapped(WrappedVariant<'a>), } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum WrappedVariant<'a> { Recursive { sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>, }, 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>])>, }, NonNullableUnwrapped { tag_name: TagName, fields: &'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) } } NonNullableUnwrapped { fields, .. } => (0, 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, NonNullableUnwrapped { .. } => 1, } } } pub fn union_sorted_tags<'a>( arena: &'a Bump, var: Variable, subs: &Subs, ) -> Result, LayoutProblem> { let var = if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(var) { *structure } else { var }; let mut tags_vec = std::vec::Vec::new(); let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) { Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => { let opt_rec_var = get_recursion_var(subs, var); union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs) } Err((_, Content::Error)) => return Err(LayoutProblem::Erroneous), Err(other) => panic!("invalid content in tag union variable: {:?}", other), }; Ok(result) } fn get_recursion_var(subs: &Subs, var: Variable) -> Option { match subs.get_content_without_compacting(var) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(*rec_var), Content::Alias(_, _, actual) => get_recursion_var(subs, *actual), _ => None, } } fn is_recursive_tag_union(layout: &Layout) -> bool { matches!( layout, Layout::Union( UnionLayout::NullableUnwrapped { .. } | UnionLayout::Recursive(_) | UnionLayout::NullableWrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }, ) ) } fn union_sorted_tags_help_new<'a>( arena: &'a Bump, mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>, opt_rec_var: Option, subs: &Subs, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; match tags_vec.len() { 0 => { // trying to instantiate a type with no values UnionVariant::Never } 1 => { let (tag_name, arguments) = tags_vec.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 contains_zero_sized = false; // 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()]; layouts.push(unwrap_num_tag(subs, var).expect("invalid num layout")); } _ => { for var_index in arguments { let var = subs[var_index]; match Layout::from_var(&mut env, var) { Ok(layout) => { // Drop any zero-sized arguments like {} if !layout.is_dropped_because_empty() { layouts.push(layout); } else { contains_zero_sized = true; } } Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) // then it's zero-sized; In the future we may drop this argument // completely, but for now we represent it with the empty struct layouts.push(Layout::Struct(&[])) } Err(LayoutProblem::Erroneous) => { // An erroneous type var will code gen to a runtime // error, so we don't need to store any data for it. } } } } } layouts.sort_by(|layout1, layout2| { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1) }); if layouts.is_empty() { if contains_zero_sized { UnionVariant::UnitWithArguments } else { UnionVariant::Unit } } else if opt_rec_var.is_some() { UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped { tag_name, fields: layouts.into_bump_slice(), }) } else { UnionVariant::Newtype { tag_name, arguments: layouts, } } } num_tags => { // default path let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); let mut has_any_arguments = false; let mut nullable: Option<(i64, TagName)> = 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); for var_index in arguments { let var = subs[var_index]; match Layout::from_var(&mut env, var) { Ok(layout) => { has_any_arguments = true; // make sure to not unroll recursive types! let self_recursion = opt_rec_var.is_some() && subs.get_root_key_without_compacting(var) == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) && is_recursive_tag_union(&layout); if self_recursion { arg_layouts.push(Layout::RecursivePointer); } else { arg_layouts.push(layout); } } Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) // then it's zero-sized; In the future we may drop this argument // completely, but for now we represent it with the empty struct arg_layouts.push(Layout::Struct(&[])); } Err(LayoutProblem::Erroneous) => { // An erroneous type var will code gen to a runtime // error, so we don't need to store any data for it. } } } arg_layouts.sort_by(|layout1, layout2| { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1) }); answer.push((tag_name.clone(), arg_layouts.into_bump_slice())); } match num_tags { 2 if !has_any_arguments => { // type can be stored in a boolean // tags_vec is sorted, and answer is sorted the same way let ttrue = answer.remove(1).0; let ffalse = answer.remove(0).0; UnionVariant::BoolUnion { ffalse, ttrue } } 3..=MAX_ENUM_SIZE if !has_any_arguments => { // type can be stored in a byte // needs the sorted tag names to determine the tag_id let mut tag_names = Vec::with_capacity_in(answer.len(), arena); for (tag_name, _) in answer { tag_names.push(tag_name); } UnionVariant::ByteUnion(tag_names) } _ => { 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 { debug_assert!(answer.len() > 1); WrappedVariant::Recursive { sorted_tag_layouts: answer, } } else { WrappedVariant::NonRecursive { sorted_tag_layouts: answer, } }; UnionVariant::Wrapped(variant) } } } } } pub fn union_sorted_tags_help<'a>( arena: &'a Bump, mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec)>, opt_rec_var: Option, subs: &Subs, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; match tags_vec.len() { 0 => { // trying to instantiate a type with no values UnionVariant::Never } 1 => { let (tag_name, arguments) = tags_vec.remove(0); // 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 contains_zero_sized = false; // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { layouts.push(unwrap_num_tag(subs, arguments[0]).expect("invalid num layout")); } _ => { for var in arguments { match Layout::from_var(&mut env, var) { Ok(layout) => { // Drop any zero-sized arguments like {} if !layout.is_dropped_because_empty() { layouts.push(layout); } else { contains_zero_sized = true; } } Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) // then it's zero-sized; In the future we may drop this argument // completely, but for now we represent it with the empty struct layouts.push(Layout::Struct(&[])) } Err(LayoutProblem::Erroneous) => { // An erroneous type var will code gen to a runtime // error, so we don't need to store any data for it. } } } } } layouts.sort_by(|layout1, layout2| { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1) }); if layouts.is_empty() { if contains_zero_sized { UnionVariant::UnitWithArguments } else { UnionVariant::Unit } } else if opt_rec_var.is_some() { UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped { tag_name, fields: layouts.into_bump_slice(), }) } else { UnionVariant::Newtype { tag_name, arguments: layouts, } } } num_tags => { // default path let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); let mut has_any_arguments = false; 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); for var in arguments { match Layout::from_var(&mut env, var) { Ok(layout) => { has_any_arguments = true; // make sure to not unroll recursive types! let self_recursion = opt_rec_var.is_some() && subs.get_root_key_without_compacting(var) == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) && is_recursive_tag_union(&layout); if self_recursion { arg_layouts.push(Layout::RecursivePointer); } else { arg_layouts.push(layout); } } Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) // then it's zero-sized; In the future we may drop this argument // completely, but for now we represent it with the empty struct arg_layouts.push(Layout::Struct(&[])); } Err(LayoutProblem::Erroneous) => { // An erroneous type var will code gen to a runtime // error, so we don't need to store any data for it. } } } arg_layouts.sort_by(|layout1, layout2| { let ptr_bytes = 8; let size1 = layout1.alignment_bytes(ptr_bytes); let size2 = layout2.alignment_bytes(ptr_bytes); size2.cmp(&size1) }); answer.push((tag_name.clone(), arg_layouts.into_bump_slice())); } match num_tags { 2 if !has_any_arguments => { // type can be stored in a boolean // tags_vec is sorted, and answer is sorted the same way let ttrue = answer.remove(1).0; let ffalse = answer.remove(0).0; UnionVariant::BoolUnion { ffalse, ttrue } } 3..=MAX_ENUM_SIZE if !has_any_arguments => { // type can be stored in a byte // needs the sorted tag names to determine the tag_id let mut tag_names = Vec::with_capacity_in(answer.len(), arena); for (tag_name, _) in answer { tag_names.push(tag_name.clone()); } UnionVariant::ByteUnion(tag_names) } _ => { 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 { debug_assert!(answer.len() > 1); WrappedVariant::Recursive { sorted_tag_layouts: answer, } } else { WrappedVariant::NonRecursive { sorted_tag_layouts: answer, } }; UnionVariant::Wrapped(variant) } } } } } 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 layout_from_newtype<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> 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]; if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { unwrap_num_tag(subs, var).expect("invalid Num argument") } else { let mut env = Env { arena, subs, seen: Vec::new_in(arena), }; match Layout::from_var(&mut env, var) { Ok(layout) => layout, Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) // then it's zero-sized; In the future we may drop this argument // completely, but for now we represent it with the empty struct Layout::Struct(&[]) } Err(LayoutProblem::Erroneous) => { // An erroneous type var will code gen to a runtime // error, so we don't need to store any data for it. todo!() } } } } fn layout_from_tag_union<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Layout<'a> { use UnionVariant::*; if tags.is_newtype_wrapper(subs) { return layout_from_newtype(arena, tags, subs); } let tags_vec = cheap_sort_tags(arena, tags, subs); 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]; unwrap_num_tag(subs, var).expect("invalid Num argument") } _ => { let opt_rec_var = None; let variant = union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs); match variant { Never => Layout::Union(UnionLayout::NonRecursive(&[])), Unit | UnitWithArguments => Layout::Struct(&[]), BoolUnion { .. } => Layout::Builtin(Builtin::Int1), ByteUnion(_) => Layout::Builtin(Builtin::Int8), Newtype { arguments: field_layouts, .. } => { let answer1 = if field_layouts.len() == 1 { field_layouts[0] } else { Layout::Struct(field_layouts.into_bump_slice()) }; answer1 } Wrapped(variant) => { use WrappedVariant::*; 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)); 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)); debug_assert!(tag_layouts.len() > 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!(), NonNullableUnwrapped { .. } => todo!(), } } } } } } #[cfg(debug_assertions)] fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), Err(content) => panic!("invalid content in ext_var: {:?}", content), } } #[cfg(not(debug_assertions))] fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { // This should only ever be used in debug_assert! macros unreachable!(); } fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutProblem> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; match content { RecursionVar { .. } => panic!("recursion var in num"), FlexVar(_) | RigidVar(_) => { // If a Num makes it all the way through type checking with an unbound // type variable, then assume it's a 64-bit integer. // // (e.g. for (5 + 5) assume both 5s are 64-bit integers.) Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) } Structure(Apply(symbol, args)) => match *symbol { // Ints Symbol::NUM_NAT => Ok(Layout::Builtin(Builtin::Usize)), Symbol::NUM_INTEGER => Ok(Layout::Builtin(Builtin::Int64)), Symbol::NUM_I128 => Ok(Layout::Builtin(Builtin::Int128)), Symbol::NUM_I64 => Ok(Layout::Builtin(Builtin::Int64)), Symbol::NUM_I32 => Ok(Layout::Builtin(Builtin::Int32)), Symbol::NUM_I16 => Ok(Layout::Builtin(Builtin::Int16)), Symbol::NUM_I8 => Ok(Layout::Builtin(Builtin::Int8)), Symbol::NUM_U128 => Ok(Layout::Builtin(Builtin::Int128)), Symbol::NUM_U64 => Ok(Layout::Builtin(Builtin::Int64)), Symbol::NUM_U32 => Ok(Layout::Builtin(Builtin::Int32)), Symbol::NUM_U16 => Ok(Layout::Builtin(Builtin::Int16)), Symbol::NUM_U8 => Ok(Layout::Builtin(Builtin::Int8)), // Floats Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), Symbol::NUM_DEC => Ok(Layout::Builtin(Builtin::Decimal)), Symbol::NUM_F64 => Ok(Layout::Builtin(Builtin::Float64)), Symbol::NUM_F32 => Ok(Layout::Builtin(Builtin::Float32)), _ => { panic!( "Invalid Num.Num type application: Apply({:?}, {:?})", symbol, args ); } }, Alias(_, _, _) => { todo!("TODO recursively resolve type aliases in num_from_content"); } Structure(_) => { panic!("Invalid Num.Num type application: {:?}", content); } Error => Err(LayoutProblem::Erroneous), } } fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutProblem> { match subs.get_content_without_compacting(var) { Content::Alias(Symbol::NUM_INTEGER, args, _) => { debug_assert!(args.len() == 1); let precision_var = subs[args.variables().into_iter().next().unwrap()]; let precision = subs.get_content_without_compacting(precision_var); match precision { Content::Alias(symbol, args, _) => { debug_assert!(args.is_empty()); let builtin = match *symbol { Symbol::NUM_SIGNED128 => Builtin::Int128, Symbol::NUM_SIGNED64 => Builtin::Int64, Symbol::NUM_SIGNED32 => Builtin::Int32, Symbol::NUM_SIGNED16 => Builtin::Int16, Symbol::NUM_SIGNED8 => Builtin::Int8, Symbol::NUM_UNSIGNED128 => Builtin::Int128, Symbol::NUM_UNSIGNED64 => Builtin::Int64, Symbol::NUM_UNSIGNED32 => Builtin::Int32, Symbol::NUM_UNSIGNED16 => Builtin::Int16, Symbol::NUM_UNSIGNED8 => Builtin::Int8, Symbol::NUM_NATURAL => Builtin::Usize, _ => unreachable!("not a valid int variant: {:?} {:?}", symbol, args), }; Ok(Layout::Builtin(builtin)) } Content::FlexVar(_) | Content::RigidVar(_) => { // default to i64 Ok(Layout::Builtin(Builtin::Int64)) } _ => unreachable!("not a valid int variant: {:?}", precision), } } Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { debug_assert!(args.len() == 1); let precision_var = subs[args.variables().into_iter().next().unwrap()]; let precision = subs.get_content_without_compacting(precision_var); match precision { Content::Alias(Symbol::NUM_BINARY32, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float32)) } Content::Alias(Symbol::NUM_BINARY64, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } Content::Alias(Symbol::NUM_DECIMAL, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Decimal)) } Content::FlexVar(_) | Content::RigidVar(_) => { // default to f64 Ok(Layout::Builtin(Builtin::Float64)) } _ => unreachable!("not a valid float variant: {:?}", precision), } } Content::FlexVar(_) | Content::RigidVar(_) => { // If this was still a (Num *) then default to compiling it to i64 Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) } other => { todo!("TODO non structure Num.@Num flat_type {:?}", other); } } } fn dict_layout_from_key_value<'a>( env: &mut Env<'a, '_>, key_var: Variable, value_var: Variable, ) -> Result, LayoutProblem> { match env.subs.get_content_without_compacting(key_var) { Content::FlexVar(_) | Content::RigidVar(_) => { // If this was still a (Dict * *) then it must have been an empty dict Ok(Layout::Builtin(Builtin::EmptyDict)) } key_content => { let value_content = env.subs.get_content_without_compacting(value_var); let key_layout = Layout::new_help(env, key_var, key_content.clone())?; let value_layout = Layout::new_help(env, value_var, value_content.clone())?; // This is a normal list. Ok(Layout::Builtin(Builtin::Dict( env.arena.alloc(key_layout), env.arena.alloc(value_layout), ))) } } } pub fn list_layout_from_elem<'a>( env: &mut Env<'a, '_>, elem_var: Variable, ) -> Result, LayoutProblem> { match env.subs.get_content_without_compacting(elem_var) { Content::FlexVar(_) | Content::RigidVar(_) => { // If this was still a (List *) then it must have been an empty list Ok(Layout::Builtin(Builtin::EmptyList)) } _ => { let elem_layout = Layout::from_var(env, elem_var)?; Ok(Layout::Builtin(Builtin::List(env.arena.alloc(elem_layout)))) } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LayoutId(u32); impl LayoutId { // Returns something like "foo#1" when given a symbol that interns to "foo" // and a LayoutId of 1. pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { let ident_string = symbol.ident_str(interns); let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap(); format!("{}_{}_{}", module_string, ident_string, self.0) } } struct IdsByLayout<'a> { by_id: MutMap, u32>, toplevels_by_id: MutMap, u32>, next_id: u32, } #[derive(Default)] pub struct LayoutIds<'a> { by_symbol: MutMap>, } impl<'a> LayoutIds<'a> { /// Returns a LayoutId which is unique for the given symbol and layout. /// If given the same symbol and same layout, returns the same LayoutId. pub fn get<'b>(&mut self, symbol: Symbol, layout: &'b Layout<'a>) -> LayoutId { // Note: this function does some weird stuff to satisfy the borrow checker. // There's probably a nicer way to write it that still works. let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout { by_id: HashMap::with_capacity_and_hasher(1, default_hasher()), toplevels_by_id: Default::default(), next_id: 1, }); // Get the id associated with this layout, or default to next_id. let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id); // If we had to default to next_id, it must not have been found; // store the ID we're going to return and increment next_id. if answer == ids.next_id { ids.by_id.insert(*layout, ids.next_id); ids.next_id += 1; } LayoutId(answer) } /// Returns a LayoutId which is unique for the given symbol and layout. /// If given the same symbol and same layout, returns the same LayoutId. pub fn get_toplevel<'b>( &mut self, symbol: Symbol, layout: &'b crate::ir::ProcLayout<'a>, ) -> LayoutId { // Note: this function does some weird stuff to satisfy the borrow checker. // There's probably a nicer way to write it that still works. let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout { by_id: Default::default(), toplevels_by_id: HashMap::with_capacity_and_hasher(1, default_hasher()), next_id: 1, }); // Get the id associated with this layout, or default to next_id. let answer = ids .toplevels_by_id .get(layout) .copied() .unwrap_or(ids.next_id); // If we had to default to next_id, it must not have been found; // store the ID we're going to return and increment next_id. if answer == ids.next_id { ids.toplevels_by_id.insert(*layout, ids.next_id); ids.next_id += 1; } LayoutId(answer) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ListLayout<'a> { EmptyList, List(&'a Layout<'a>), } impl<'a> std::convert::TryFrom<&Layout<'a>> for ListLayout<'a> { type Error = (); fn try_from(value: &Layout<'a>) -> Result { match value { Layout::Builtin(Builtin::EmptyList) => Ok(ListLayout::EmptyList), Layout::Builtin(Builtin::List(element)) => Ok(ListLayout::List(element)), _ => Err(()), } } }