use crate::layout::{ext_var_is_empty_record, ext_var_is_empty_tag_union}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; use roc_target::TargetInfo; use roc_types::subs::{self, Content, FlatType, Subs, Variable}; use roc_types::types::RecordField; use std::collections::hash_map::Entry; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Index { index: u32, _marker: std::marker::PhantomData, } impl Index { pub const fn new(index: u32) -> Self { Self { index, _marker: std::marker::PhantomData, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Slice { start: u32, length: u16, _marker: std::marker::PhantomData, } impl Slice { pub const fn new(start: u32, length: u16) -> Self { Self { start, length, _marker: std::marker::PhantomData, } } pub const fn len(&self) -> usize { self.length as _ } pub const fn is_empty(&self) -> bool { self.length == 0 } pub const fn indices(&self) -> std::ops::Range { self.start as usize..(self.start as usize + self.length as usize) } pub fn into_iter(&self) -> impl Iterator> { self.indices().map(|i| Index::new(i as _)) } } trait Reserve { fn reserve(layouts: &mut Layouts, length: usize) -> Self; } impl Reserve for Slice { fn reserve(layouts: &mut Layouts, length: usize) -> Self { let start = layouts.layouts.len() as u32; let it = std::iter::repeat(Layout::Reserved).take(length); layouts.layouts.extend(it); Self { start, length: length as u16, _marker: Default::default(), } } } impl Reserve for Slice> { fn reserve(layouts: &mut Layouts, length: usize) -> Self { let start = layouts.layout_slices.len() as u32; let empty: Slice = Slice::new(0, 0); let it = std::iter::repeat(empty).take(length); layouts.layout_slices.extend(it); Self { start, length: length as u16, _marker: Default::default(), } } } static_assertions::assert_eq_size!([u8; 12], Layout); pub struct Layouts { layouts: Vec, layout_slices: Vec>, // function_layouts: Vec<(Slice, Index)>, lambda_sets: Vec, symbols: Vec, recursion_variable_to_structure_variable_map: MutMap>, target_info: TargetInfo, } pub struct FunctionLayout { /// last element is the result, prior elements the arguments arguments_and_result: Slice, pub lambda_set: Index, } impl FunctionLayout { pub fn from_var( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { // so we can set some things/clean up Self::from_var_help(layouts, subs, var) } fn from_var_help( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { let content = &subs.get_content_without_compacting(var); Self::from_content(layouts, subs, var, content) } fn from_content( layouts: &mut Layouts, subs: &Subs, var: Variable, content: &Content, ) -> Result { use LayoutError::*; match content { Content::FlexVar(_) | Content::RigidVar(_) | Content::FlexAbleVar(_, _) | Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)), Content::RecursionVar { .. } => Err(TypeError(())), Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual), Content::RangedNumber(_) => todo!(), Content::Error => Err(TypeError(())), } } fn from_lambda_set( _layouts: &mut Layouts, _subs: &Subs, _lset: subs::LambdaSet, ) -> Result { todo!(); } fn from_flat_type( layouts: &mut Layouts, subs: &Subs, flat_type: &FlatType, ) -> Result { use LayoutError::*; match flat_type { FlatType::Func(arguments, lambda_set, result) => { let slice = Slice::reserve(layouts, arguments.len() + 1); let variable_slice = &subs.variables[arguments.indices()]; let it = slice.indices().zip(variable_slice); for (target_index, var) in it { let layout = Layout::from_var_help(layouts, subs, *var)?; layouts.layouts[target_index] = layout; } let result_layout = Layout::from_var_help(layouts, subs, *result)?; let result_index: Index = Index::new(slice.start + slice.len() as u32 - 1); layouts.layouts[result_index.index as usize] = result_layout; let lambda_set = LambdaSet::from_var(layouts, subs, *lambda_set)?; let lambda_set_index = Index::new(layouts.lambda_sets.len() as u32); layouts.lambda_sets.push(lambda_set); Ok(Self { arguments_and_result: slice, lambda_set: lambda_set_index, }) } FlatType::Erroneous(_) => Err(TypeError(())), _ => todo!(), } } pub fn argument_slice(&self) -> Slice { let mut result = self.arguments_and_result; result.length -= 1; result } pub fn result_index(&self) -> Index { Index::new(self.arguments_and_result.start + self.arguments_and_result.length as u32 - 1) } } /// Idea: don't include the symbols for the first 3 cases in --optimize mode pub enum LambdaSet { Empty { symbol: Index, }, Single { symbol: Index, layout: Index, }, Struct { symbol: Index, layouts: Slice, }, Union { symbols: Slice, layouts: Slice>, }, } impl LambdaSet { pub fn from_var( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { // so we can set some things/clean up Self::from_var_help(layouts, subs, var) } fn from_var_help( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { let content = &subs.get_content_without_compacting(var); Self::from_content(layouts, subs, var, content) } fn from_content( layouts: &mut Layouts, subs: &Subs, var: Variable, content: &Content, ) -> Result { use LayoutError::*; match content { Content::FlexVar(_) | Content::RigidVar(_) | Content::FlexAbleVar(_, _) | Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)), Content::RecursionVar { .. } => { unreachable!("lambda sets cannot currently be recursive") } Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset), Content::Structure(_flat_type) => unreachable!(), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual), Content::RangedNumber(_) => todo!(), Content::Error => Err(TypeError(())), } } fn from_lambda_set( layouts: &mut Layouts, subs: &Subs, lset: subs::LambdaSet, ) -> Result { let subs::LambdaSet { solved, recursion_var: _, unspecialized: _, ambient_function: _, } = lset; // TODO: handle unspecialized debug_assert!( !solved.is_empty(), "lambda set must contain atleast the function itself" ); let lambda_names = solved.labels(); let closure_names = Self::get_closure_names(layouts, subs, lambda_names); let variables = solved.variables(); if variables.len() == 1 { let symbol = subs.closure_names[lambda_names.start as usize]; let symbol_index = Index::new(layouts.symbols.len() as u32); layouts.symbols.push(symbol); let variable_slice = subs.variable_slices[variables.start as usize]; match variable_slice.len() { 0 => Ok(LambdaSet::Empty { symbol: symbol_index, }), 1 => { let var = subs.variables[variable_slice.start as usize]; let layout = Layout::from_var(layouts, subs, var)?; let index = Index::new(layouts.layouts.len() as u32); layouts.layouts.push(layout); Ok(LambdaSet::Single { symbol: symbol_index, layout: index, }) } _ => { let slice = Layout::from_variable_slice(layouts, subs, variable_slice)?; Ok(LambdaSet::Struct { symbol: symbol_index, layouts: slice, }) } } } else { let layouts = Layout::from_slice_variable_slice(layouts, subs, solved.variables())?; Ok(LambdaSet::Union { symbols: closure_names, layouts, }) } } fn get_closure_names( layouts: &mut Layouts, subs: &Subs, subs_slice: roc_types::subs::SubsSlice, ) -> Slice { let slice = Slice::new(layouts.symbols.len() as u32, subs_slice.len() as u16); let symbols = &subs.closure_names[subs_slice.indices()]; for symbol in symbols { layouts.symbols.push(*symbol); } slice } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum Layout { // theory: we can zero out memory to reserve space for many layouts Reserved, // Question: where to store signedness information? Int(IntWidth), Float(FloatWidth), Decimal, Str, Dict(Index<(Layout, Layout)>), Set(Index), List(Index), Struct(Slice), UnionNonRecursive(Slice>), Boxed(Index), UnionRecursive(Slice>), // UnionNonNullableUnwrapped(Slice), // UnionNullableWrapper { // data: NullableUnionIndex, // tag_id: u16, // }, // // UnionNullableUnwrappedTrue(Slice), // UnionNullableUnwrappedFalse(Slice), // RecursivePointer, } fn round_up_to_alignment(unaligned: u16, alignment_bytes: u16) -> u16 { let unaligned = unaligned as i32; let alignment_bytes = alignment_bytes as i32; if alignment_bytes <= 1 { return unaligned as u16; } if alignment_bytes.count_ones() != 1 { panic!( "Cannot align to {} bytes. Not a power of 2.", alignment_bytes ); } let mut aligned = unaligned; aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 aligned as u16 } impl Layouts { const VOID_INDEX: Index = Index::new(0); const VOID_TUPLE: Index<(Layout, Layout)> = Index::new(0); const UNIT_INDEX: Index = Index::new(2); pub fn new(target_info: TargetInfo) -> Self { let mut layouts = Vec::with_capacity(64); layouts.push(Layout::VOID); layouts.push(Layout::VOID); layouts.push(Layout::UNIT); // sanity check debug_assert_eq!(layouts[Self::VOID_INDEX.index as usize], Layout::VOID); debug_assert_eq!(layouts[Self::VOID_TUPLE.index as usize + 1], Layout::VOID); debug_assert_eq!(layouts[Self::UNIT_INDEX.index as usize], Layout::UNIT); Layouts { layouts: Vec::default(), layout_slices: Vec::default(), lambda_sets: Vec::default(), symbols: Vec::default(), recursion_variable_to_structure_variable_map: MutMap::default(), target_info, } } /// sort a slice according to elements' alignment fn sort_slice_by_alignment(&mut self, layout_slice: Slice) { let slice = &mut self.layouts[layout_slice.indices()]; // SAFETY: the align_of function does not mutate the layouts vector // this unsafety is required to circumvent the borrow checker let sneaky_slice = unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr(), slice.len()) }; sneaky_slice.sort_by(|layout1, layout2| { let align1 = self.align_of_layout(*layout1); let align2 = self.align_of_layout(*layout2); // we want the biggest alignment first align2.cmp(&align1) }); } fn usize(&self) -> Layout { let usize_int_width = match self.target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => IntWidth::U32, roc_target::PtrWidth::Bytes8 => IntWidth::U64, }; Layout::Int(usize_int_width) } fn align_of_layout_index(&self, index: Index) -> u16 { let layout = self.layouts[index.index as usize]; self.align_of_layout(layout) } fn align_of_layout(&self, layout: Layout) -> u16 { let usize_int_width = match self.target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => IntWidth::U32, roc_target::PtrWidth::Bytes8 => IntWidth::U64, }; let ptr_alignment = usize_int_width.alignment_bytes(self.target_info) as u16; match layout { Layout::Reserved => unreachable!(), Layout::Int(int_width) => int_width.alignment_bytes(self.target_info) as u16, Layout::Float(float_width) => float_width.alignment_bytes(self.target_info) as u16, Layout::Decimal => IntWidth::U128.alignment_bytes(self.target_info) as u16, Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => ptr_alignment, Layout::Struct(slice) => self.align_of_layout_slice(slice), Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_alignment, Layout::UnionNonRecursive(slices) => { let tag_id_align = IntWidth::I64.alignment_bytes(self.target_info) as u16; self.align_of_layout_slices(slices).max(tag_id_align) } // Layout::UnionNonNullableUnwrapped(_) => todo!(), // Layout::UnionNullableWrapper { data, tag_id } => todo!(), // Layout::UnionNullableUnwrappedTrue(_) => todo!(), // Layout::UnionNullableUnwrappedFalse(_) => todo!(), // Layout::RecursivePointer => todo!(), } } /// Invariant: the layouts are sorted from biggest to smallest alignment fn align_of_layout_slice(&self, slice: Slice) -> u16 { match slice.into_iter().next() { None => 0, Some(first_index) => self.align_of_layout_index(first_index), } } fn align_of_layout_slices(&self, slice: Slice>) -> u16 { slice .into_iter() .map(|index| self.layout_slices[index.index as usize]) .map(|slice| self.align_of_layout_slice(slice)) .max() .unwrap_or_default() } /// Invariant: the layouts are sorted from biggest to smallest alignment fn size_of_layout_slice(&self, slice: Slice) -> u16 { match slice.into_iter().next() { None => 0, Some(first_index) => { let alignment = self.align_of_layout_index(first_index); let mut sum = 0; for index in slice.into_iter() { sum += self.size_of_layout_index(index); } round_up_to_alignment(sum, alignment) } } } pub fn size_of_layout_index(&self, index: Index) -> u16 { let layout = self.layouts[index.index as usize]; self.size_of_layout(layout) } pub fn size_of_layout(&self, layout: Layout) -> u16 { let usize_int_width = match self.target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => IntWidth::U32, roc_target::PtrWidth::Bytes8 => IntWidth::U64, }; let ptr_width = usize_int_width.stack_size() as u16; match layout { Layout::Reserved => unreachable!(), Layout::Int(int_width) => int_width.stack_size() as _, Layout::Float(float_width) => float_width as _, Layout::Decimal => (std::mem::size_of::()) as _, Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => 2 * ptr_width, Layout::Struct(slice) => self.size_of_layout_slice(slice), Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_width, Layout::UnionNonRecursive(slices) if slices.is_empty() => 0, Layout::UnionNonRecursive(slices) => { let tag_id = IntWidth::I64; let max_slice_size = slices .into_iter() .map(|index| self.layout_slices[index.index as usize]) .map(|slice| self.align_of_layout_slice(slice)) .max() .unwrap_or_default(); tag_id.stack_size() as u16 + max_slice_size } // Layout::UnionNonNullableUnwrapped(_) => todo!(), // Layout::UnionNullableWrapper { data, tag_id } => todo!(), // Layout::UnionNullableUnwrappedTrue(_) => todo!(), // Layout::UnionNullableUnwrappedFalse(_) => todo!(), // Layout::RecursivePointer => todo!(), } } } pub enum LayoutError { UnresolvedVariable(Variable), TypeError(()), } impl Layout { pub const UNIT: Self = Self::Struct(Slice::new(0, 0)); pub const VOID: Self = Self::UnionNonRecursive(Slice::new(0, 0)); pub const EMPTY_LIST: Self = Self::List(Layouts::VOID_INDEX); pub const EMPTY_DICT: Self = Self::Dict(Layouts::VOID_TUPLE); pub const EMPTY_SET: Self = Self::Set(Layouts::VOID_INDEX); pub fn from_var( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { // so we can set some things/clean up Self::from_var_help(layouts, subs, var) } fn from_var_help( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { let content = &subs.get_content_without_compacting(var); Self::from_content(layouts, subs, var, content) } /// Used in situations where an unspecialized variable is not a problem, /// and we can substitute with `[]`, the empty tag union. /// e.g. an empty list literal has type `List *`. We can still generate code /// in those cases by just picking any concrete type for the list element, /// and we pick the empty tag union in practice. fn from_var_help_or_void( layouts: &mut Layouts, subs: &Subs, var: Variable, ) -> Result { let content = &subs.get_content_without_compacting(var); match content { Content::FlexVar(_) | Content::RigidVar(_) => Ok(Layout::VOID), _ => Self::from_content(layouts, subs, var, content), } } fn from_content( layouts: &mut Layouts, subs: &Subs, var: Variable, content: &Content, ) -> Result { use LayoutError::*; match content { Content::FlexVar(_) | Content::RigidVar(_) | Content::FlexAbleVar(_, _) | Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)), Content::RecursionVar { structure, opt_name: _, } => { let structure = subs.get_root_key_without_compacting(*structure); let entry = layouts .recursion_variable_to_structure_variable_map .entry(structure); match entry { Entry::Vacant(vacant) => { let reserved = Index::new(layouts.layouts.len() as _); layouts.layouts.push(Layout::Reserved); vacant.insert(reserved); let layout = Layout::from_var(layouts, subs, structure)?; layouts.layouts[reserved.index as usize] = layout; Ok(Layout::Boxed(reserved)) } Entry::Occupied(occupied) => { let index = occupied.get(); Ok(Layout::Boxed(*index)) } } } // Lambda set layout is same as tag union Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(symbol, _, actual, _) => { let symbol = *symbol; if let Some(int_width) = IntWidth::try_from_symbol(symbol) { return Ok(Layout::Int(int_width)); } if let Some(float_width) = FloatWidth::try_from_symbol(symbol) { return Ok(Layout::Float(float_width)); } match symbol { Symbol::NUM_DECIMAL => Ok(Layout::Decimal), Symbol::NUM_NAT | Symbol::NUM_NATURAL => Ok(layouts.usize()), _ => { // at this point we throw away alias information Self::from_var_help(layouts, subs, *actual) } } } Content::RangedNumber(_) => todo!(), Content::Error => Err(TypeError(())), } } fn from_lambda_set( layouts: &mut Layouts, subs: &Subs, lset: subs::LambdaSet, ) -> Result { let subs::LambdaSet { solved, recursion_var, unspecialized: _, ambient_function: _, } = lset; // TODO: handle unspecialized lambda set match recursion_var.into_variable() { Some(rec_var) => { let rec_var = subs.get_root_key_without_compacting(rec_var); let cached = layouts .recursion_variable_to_structure_variable_map .get(&rec_var); if let Some(layout_index) = cached { match layouts.layouts[layout_index.index as usize] { Layout::Reserved => { // we have to do the work here to fill this reserved variable in } other => { return Ok(other); } } } let slices = Self::from_slice_variable_slice(layouts, subs, solved.variables())?; Ok(Layout::UnionRecursive(slices)) } None => { let slices = Self::from_slice_variable_slice(layouts, subs, solved.variables())?; Ok(Layout::UnionNonRecursive(slices)) } } } fn from_flat_type( layouts: &mut Layouts, subs: &Subs, flat_type: &FlatType, ) -> Result { use LayoutError::*; match flat_type { FlatType::Apply(Symbol::LIST_LIST, arguments) => { debug_assert_eq!(arguments.len(), 1); let element_var = subs.variables[arguments.start as usize]; let element_layout = Self::from_var_help_or_void(layouts, subs, element_var)?; let element_index = Index::new(layouts.layouts.len() as _); layouts.layouts.push(element_layout); Ok(Layout::List(element_index)) } FlatType::Apply(Symbol::DICT_DICT, arguments) => { debug_assert_eq!(arguments.len(), 2); let key_var = subs.variables[arguments.start as usize]; let value_var = subs.variables[arguments.start as usize + 1]; let key_layout = Self::from_var_help_or_void(layouts, subs, key_var)?; let value_layout = Self::from_var_help_or_void(layouts, subs, value_var)?; let index = Index::new(layouts.layouts.len() as _); layouts.layouts.push(key_layout); layouts.layouts.push(value_layout); Ok(Layout::Dict(index)) } FlatType::Apply(Symbol::SET_SET, arguments) => { debug_assert_eq!(arguments.len(), 1); let element_var = subs.variables[arguments.start as usize]; let element_layout = Self::from_var_help_or_void(layouts, subs, element_var)?; let element_index = Index::new(layouts.layouts.len() as _); layouts.layouts.push(element_layout); Ok(Layout::Set(element_index)) } FlatType::Apply(symbol, _) => { unreachable!("Symbol {:?} does not have a layout", symbol) } FlatType::Func(_arguments, lambda_set, _result) => { // in this case, a function (pointer) is represented by the environment it // captures: the lambda set Self::from_var_help(layouts, subs, *lambda_set) } FlatType::Record(fields, ext) => { debug_assert!(ext_var_is_empty_record(subs, *ext)); let mut slice = Slice::reserve(layouts, fields.len()); let mut non_optional_fields = 0; let it = slice.indices().zip(fields.iter_all()); for (target_index, (_, field_index, var_index)) in it { match subs.record_fields[field_index.index as usize] { RecordField::Optional(_) => { // do nothing } RecordField::Required(_) | RecordField::Demanded(_) => { let var = subs.variables[var_index.index as usize]; let layout = Layout::from_var_help(layouts, subs, var)?; layouts.layouts[target_index] = layout; non_optional_fields += 1; } } } // we have some wasted space in the case of optional fields; so be it slice.length = non_optional_fields; layouts.sort_slice_by_alignment(slice); Ok(Layout::Struct(slice)) } FlatType::TagUnion(union_tags, ext) => { debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); let slices = Self::from_slice_variable_slice(layouts, subs, union_tags.variables())?; Ok(Layout::UnionNonRecursive(slices)) } FlatType::FunctionOrTagUnion(_, _, ext) => { debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); // at this point we know this is a tag Ok(Layout::UNIT) } FlatType::RecursiveTagUnion(rec_var, union_tags, ext) => { debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); let rec_var = subs.get_root_key_without_compacting(*rec_var); let cached = layouts .recursion_variable_to_structure_variable_map .get(&rec_var); if let Some(layout_index) = cached { match layouts.layouts[layout_index.index as usize] { Layout::Reserved => { // we have to do the work here to fill this reserved variable in } other => { return Ok(other); } } } let slices = Self::from_slice_variable_slice(layouts, subs, union_tags.variables())?; Ok(Layout::UnionRecursive(slices)) } FlatType::Erroneous(_) => Err(TypeError(())), FlatType::EmptyRecord => Ok(Layout::UNIT), FlatType::EmptyTagUnion => Ok(Layout::VOID), } } fn from_slice_variable_slice( layouts: &mut Layouts, subs: &Subs, slice_variable_slice: roc_types::subs::SubsSlice, ) -> Result>, LayoutError> { let slice = Slice::reserve(layouts, slice_variable_slice.len()); let variable_slices = &subs.variable_slices[slice_variable_slice.indices()]; let it = slice.indices().zip(variable_slices); for (target_index, variable_slice) in it { let layout_slice = Layout::from_variable_slice(layouts, subs, *variable_slice)?; layouts.layout_slices[target_index] = layout_slice; } Ok(slice) } fn from_variable_slice( layouts: &mut Layouts, subs: &Subs, variable_subs_slice: roc_types::subs::VariableSubsSlice, ) -> Result, LayoutError> { let slice = Slice::reserve(layouts, variable_subs_slice.len()); let variable_slice = &subs.variables[variable_subs_slice.indices()]; let it = slice.indices().zip(variable_slice); for (target_index, var) in it { let layout = Layout::from_var_help(layouts, subs, *var)?; layouts.layouts[target_index] = layout; } layouts.sort_slice_by_alignment(slice); Ok(slice) } }