use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::MutMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::{Content, FlatType, Subs, Variable}; /// Types for code gen must be monomorphic. No type variables allowed! #[derive(Clone, Debug, PartialEq, Eq)] pub enum Layout<'a> { Builtin(Builtin<'a>), Struct(&'a [(Lowercase, Layout<'a>)]), Tag(&'a [Layout<'a>]), Pointer(&'a Layout<'a>), /// A function. The types of its arguments, then the type of its return value. FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Builtin<'a> { Int64, Float64, Bool(TagName, TagName), Byte(MutMap), Str, Map(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), List(&'a Layout<'a>), EmptyStr, EmptyList, EmptyMap, EmptySet, } impl<'a> Layout<'a> { /// 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! pub fn from_var( arena: &'a Bump, var: Variable, subs: &Subs, pointer_size: u32, ) -> Result { let content = subs.get_without_compacting(var).content; Self::from_content(arena, content, subs, pointer_size) } pub fn from_content( arena: &'a Bump, content: Content, subs: &Subs, pointer_size: u32, ) -> Result { use roc_types::subs::Content::*; match content { var @ FlexVar(_) | var @ RigidVar(_) => { panic!("Layout::from_content encountered an unresolved {:?}", var); } Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs, pointer_size), Alias(Symbol::INT_INT, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } Alias(Symbol::FLOAT_FLOAT, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } Alias(_, _, var) => Self::from_content( arena, subs.get_without_compacting(var).content, subs, pointer_size, ), Error => Err(()), } } pub fn stack_size(&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 } Tag(fields) => { // the symbol is a 64-bit value, so 8 bytes let mut sum = 8; for field_layout in *fields { sum += field_layout.stack_size(pointer_size); } sum } Pointer(_) | FunctionPointer(_, _) => pointer_size, } } } impl<'a> Builtin<'a> { const I64_SIZE: u32 = std::mem::size_of::() as u32; const F64_SIZE: u32 = std::mem::size_of::() as u32; const BOOL_SIZE: u32 = std::mem::size_of::() as u32; const BYTE_SIZE: u32 = std::mem::size_of::() as u32; /// Number of machine words in an empty one of these pub const STR_WORDS: u32 = 2; pub const MAP_WORDS: u32 = 6; pub const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value pub const LIST_WORDS: u32 = 2; /// Layout of collection wrapper - a struct of (pointer, length, capacity) pub const WRAPPER_PTR: u32 = 0; pub const WRAPPER_LEN: u32 = 1; pub const WRAPPER_CAPACITY: u32 = 2; pub fn stack_size(&self, pointer_size: u32) -> u32 { use Builtin::*; match self { Int64 => Builtin::I64_SIZE, Float64 => Builtin::F64_SIZE, Bool(_, _) => Builtin::BOOL_SIZE, Byte(_) => Builtin::BYTE_SIZE, Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size, } } } #[allow(clippy::cognitive_complexity)] fn layout_from_flat_type<'a>( arena: &'a Bump, flat_type: FlatType, subs: &Subs, pointer_size: u32, ) -> Result, ()> { use roc_types::subs::FlatType::*; match flat_type { Apply(symbol, args) => { match symbol { Symbol::INT_INT => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } Symbol::FLOAT_FLOAT => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } Symbol::NUM_NUM => { // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer debug_assert!(args.len() == 1); let var = args.iter().next().unwrap(); let content = subs.get_without_compacting(*var).content; layout_from_num_content(content) } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::LIST_LIST => { use roc_types::subs::Content::*; match subs.get_without_compacting(args[0]).content { FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)), content => { let elem_layout = Layout::from_content(arena, content, subs, pointer_size)?; Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) } } } Symbol::ATTR_ATTR => { debug_assert!(args.len() == 2); // The first argument is the uniqueness info; // that doesn't affect layout, so we don't need it here. let wrapped_var = args[1]; // For now, layout is unaffected by uniqueness. // (Incorporating refcounting may change this.) // Unwrap and continue Layout::from_var(arena, wrapped_var, subs, pointer_size) } _ => { panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args)); } } } Func(args, ret_var) => { let mut fn_args = Vec::with_capacity_in(args.len(), arena); for arg_var in args { let arg_content = subs.get_without_compacting(arg_var).content; fn_args.push(Layout::from_content( arena, arg_content, subs, pointer_size, )?); } let ret_content = subs.get_without_compacting(ret_var).content; let ret = Layout::from_content(arena, ret_content, subs, pointer_size)?; Ok(Layout::FunctionPointer( fn_args.into_bump_slice(), arena.alloc(ret), )) } Record(fields, ext_var) => { debug_assert!(ext_var_is_empty_record(subs, ext_var)); let ext_content = subs.get_without_compacting(ext_var).content; let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid record! panic!("TODO gracefully handle record with invalid ext_var"); } }; let mut field_layouts; match ext_layout { Layout::Struct(more_fields) => { field_layouts = Vec::with_capacity_in(fields.len() + more_fields.len(), arena); for (label, field) in more_fields { field_layouts.push((label.clone(), field.clone())); } } _ => { panic!( "TODO handle Layout for invalid record extension, specifically {:?}", ext_layout ); } } for (label, field_var) in fields { let field_content = subs.get_without_compacting(field_var).content; let field_layout = match Layout::from_content(arena, field_content, subs, pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! panic!("TODO gracefully handle record with invalid field.var"); } }; field_layouts.push((label.clone(), field_layout)); } Ok(Layout::Struct(field_layouts.into_bump_slice())) } TagUnion(tags, ext_var) => { debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); match tags.len() { 0 => { panic!("TODO gracefully handle trying to instantiate Never"); } // We can only unwrap a wrapper if it never becomes part of a bigger union // therefore, the ext_var must be the literal empty tag union 1 => { // This is a wrapper. Unwrap it! let (tag, args) = tags.into_iter().next().unwrap(); match tag { TagName::Private(Symbol::NUM_AT_NUM) => { debug_assert!(args.len() == 1); let var = args.into_iter().next().unwrap(); unwrap_num_tag(subs, var) } TagName::Private(symbol) => { panic!("TODO emit wrapped private tag for {:?} {:?}", symbol, args); } TagName::Global(ident) => { panic!("TODO emit wrapped global tag for {:?} {:?}", ident, args); } } } _ => { // Check if we can turn this tag union into an enum // The arguments of all tags must have size 0. // That is trivially the case when there are no arguments // // [ Orange, Apple, Banana ] // // But when one-tag tag unions are optimized away, we can also use an enum for // // [ Foo [ Unit ], Bar [ Unit ] ] let arguments_have_size_0 = || { tags.iter().all(|(_, args)| { args.iter().all(|var| { Layout::from_var(arena, *var, subs, pointer_size) .map(|v| v.stack_size(pointer_size)) == Ok(0) }) }) }; // up to 256 enum keys can be stored in a byte if tags.len() <= std::u8::MAX as usize + 1 && arguments_have_size_0() { if tags.len() <= 2 { // Up to 2 enum tags can be stored (in theory) in one bit let mut it = tags.keys(); let a: TagName = it.next().unwrap().clone(); let b: TagName = it.next().unwrap().clone(); if a < b { Ok(Layout::Builtin(Builtin::Bool(a, b))) } else { Ok(Layout::Builtin(Builtin::Bool(b, a))) } } else { // up to 256 enum tags can be stored in a byte let mut tag_to_u8 = MutMap::default(); for (counter, (name, _)) in tags.into_iter().enumerate() { tag_to_u8.insert(name, counter as u8); } Ok(Layout::Builtin(Builtin::Byte(tag_to_u8))) } } else { // panic!("TODO handle a tag union with mutliple tags: {:?}", tags); Ok(Layout::Tag(&[])) } } } } RecursiveTagUnion(_, _, _) => { panic!("TODO make Layout for non-empty Tag Union"); } EmptyTagUnion => { panic!("TODO make Layout for empty Tag Union"); } Boolean(_) => { panic!("TODO make Layout for Boolean"); } Erroneous(_) => Err(()), EmptyRecord => Ok(Layout::Struct(&[])), } } 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), } } fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = MutMap::default(); match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) { Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), Err((_, content)) => panic!("invalid content in ext_var: {:?}", content), } } fn layout_from_num_content<'a>(content: Content) -> Result, ()> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; match content { var @ FlexVar(_) | var @ RigidVar(_) => { panic!("Layout::from_content encountered an unresolved {:?}", var); } Structure(Apply(symbol, args)) => match symbol { Symbol::INT_INTEGER => Ok(Layout::Builtin(Builtin::Int64)), Symbol::FLOAT_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), _ => { panic!( "Invalid Num.Num type application: {:?}", Apply(symbol, args) ); } }, Structure(_) => { panic!("Invalid Num.Num type application: {:?}", content); } Alias(_, _, _) => { panic!("TODO recursively resolve type aliases in num_from_content"); } Error => Err(()), } } fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, ()> { match subs.get_without_compacting(var).content { Content::Structure(flat_type) => match flat_type { FlatType::Apply(Symbol::ATTR_ATTR, args) => { debug_assert!(args.len() == 2); let arg_var = args.get(1).unwrap(); unwrap_num_tag(subs, *arg_var) } _ => { panic!("TODO handle Num.@Num flat_type {:?}", flat_type); } }, Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } Content::FlexVar(_) => { // If this was still a (Num *) then default to compiling it to i64 Ok(Layout::Builtin(Builtin::Int64)) } other => { panic!("TODO non structure Num.@Num flat_type {:?}", other); } } }