diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 5fec0d286f..6149bff14c 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -228,6 +228,7 @@ impl CallConv for AArch64Call { fn load_args<'a>( _symbol_map: &mut MutMap>, _args: &'a [(Layout<'a>, Symbol)], + _ret_layout: &Layout<'a>, ) -> Result<(), String> { Err("Loading args not yet implemented for AArch64".to_string()) } @@ -242,6 +243,20 @@ impl CallConv for AArch64Call { ) -> Result { Err("Storing args not yet implemented for AArch64".to_string()) } + + fn return_struct<'a>( + _buf: &mut Vec<'a, u8>, + _struct_offset: i32, + _struct_size: u32, + _field_layouts: &[Layout<'a>], + _ret_reg: Option, + ) -> Result<(), String> { + Err("Returning structs not yet implemented for AArch64".to_string()) + } + + fn returns_via_arg_pointer(_ret_layout: &Layout) -> Result { + Err("Returning via arg pointer not yet implemented for AArch64".to_string()) + } } impl Assembler for AArch64Assembler { diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index bf394204da..b3462bd18c 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -10,6 +10,8 @@ use target_lexicon::Triple; pub mod aarch64; pub mod x86_64; +const PTR_SIZE: u32 = 64; + pub trait CallConv { const GENERAL_PARAM_REGS: &'static [GeneralReg]; const GENERAL_RETURN_REGS: &'static [GeneralReg]; @@ -49,6 +51,8 @@ pub trait CallConv { fn load_args<'a>( symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], + // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. + ret_layout: &Layout<'a>, ) -> Result<(), String>; // store_args stores the args in registers and on the stack for function calling. @@ -61,6 +65,19 @@ pub trait CallConv { // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. ret_layout: &Layout<'a>, ) -> Result; + + // return_struct returns a struct currently on the stack at `struct_offset`. + // It does so using registers and stack as necessary. + fn return_struct<'a>( + buf: &mut Vec<'a, u8>, + struct_offset: i32, + struct_size: u32, + field_layouts: &[Layout<'a>], + ret_reg: Option, + ) -> Result<(), String>; + + // returns true if the layout should be returned via an argument pointer. + fn returns_via_arg_pointer(ret_layout: &Layout) -> Result; } /// Assembler contains calls to the backend assembly generator. @@ -160,8 +177,6 @@ pub trait Assembler { #[derive(Clone, Debug, PartialEq)] #[allow(dead_code)] pub enum SymbolStorage { - // These may need layout, but I am not sure. - // I think whenever a symbol would be used, we specify layout anyways. GeneralReg(GeneralReg), FloatReg(FloatReg), Base(i32), @@ -186,7 +201,7 @@ pub struct Backend64Bit< last_seen_map: MutMap>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, - symbols_map: MutMap>, + symbol_storage_map: MutMap>, literal_map: MutMap>, // This should probably be smarter than a vec. @@ -226,7 +241,7 @@ impl< relocs: bumpalo::vec!(in env.arena), last_seen_map: MutMap::default(), free_map: MutMap::default(), - symbols_map: MutMap::default(), + symbol_storage_map: MutMap::default(), literal_map: MutMap::default(), general_free_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena], @@ -248,7 +263,7 @@ impl< self.fn_call_stack_size = 0; self.last_seen_map.clear(); self.free_map.clear(); - self.symbols_map.clear(); + self.symbol_storage_map.clear(); self.buf.clear(); self.general_used_callee_saved_regs.clear(); self.general_free_regs.clear(); @@ -324,10 +339,14 @@ impl< Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) } - fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String> { - CC::load_args(&mut self.symbols_map, args)?; + fn load_args( + &mut self, + args: &'a [(Layout<'a>, Symbol)], + ret_layout: &Layout<'a>, + ) -> Result<(), String> { + CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?; // Update used and free regs. - for (sym, storage) in &self.symbols_map { + for (sym, storage) in &self.symbol_storage_map { match storage { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => { self.general_free_regs.retain(|r| *r != *reg); @@ -386,7 +405,7 @@ impl< // Put values in param regs or on top of the stack. let tmp_stack_size = CC::store_args( &mut self.buf, - &self.symbols_map, + &self.symbol_storage_map, args, arg_layouts, ret_layout, @@ -421,7 +440,7 @@ impl< _cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations. branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), - _ret_layout: &Layout<'a>, + ret_layout: &Layout<'a>, ) -> Result<(), String> { // Switches are a little complex due to keeping track of jumps. // In general I am trying to not have to loop over things multiple times or waste memory. @@ -439,7 +458,7 @@ impl< let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); // Build all statements in this branch. - self.build_stmt(stmt)?; + self.build_stmt(stmt, ret_layout)?; // Build unconditional jump to the end of this switch. // Since we don't know the offset yet, set it to 0 and overwrite later. @@ -463,7 +482,7 @@ impl< } let (branch_info, stmt) = default_branch; if let BranchInfo::None = branch_info { - self.build_stmt(stmt)?; + self.build_stmt(stmt, ret_layout)?; // Update all return jumps to jump past the default case. let ret_offset = self.buf.len(); @@ -560,6 +579,61 @@ impl< Ok(()) } + fn create_struct( + &mut self, + sym: &Symbol, + layout: &Layout<'a>, + fields: &'a [Symbol], + ) -> Result<(), String> { + if let Layout::Struct(field_layouts) = layout { + let struct_size = layout.stack_size(PTR_SIZE); + if struct_size > 0 { + let offset = self.increase_stack_size(struct_size)?; + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); + + let mut current_offset = offset; + for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { + self.copy_symbol_to_stack_offset(current_offset, field, field_layout)?; + let field_size = field_layout.stack_size(PTR_SIZE); + current_offset += field_size as i32; + } + } else { + self.symbol_storage_map.insert(*sym, SymbolStorage::Base(0)); + } + Ok(()) + } else { + // This is a single element struct. Just copy the single field to the stack. + let struct_size = layout.stack_size(PTR_SIZE); + let offset = self.increase_stack_size(struct_size)?; + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); + self.copy_symbol_to_stack_offset(offset, &fields[0], layout)?; + Ok(()) + } + } + + fn load_struct_at_index( + &mut self, + sym: &Symbol, + structure: &Symbol, + index: u64, + field_layouts: &'a [Layout<'a>], + ) -> Result<(), String> { + if let Some(SymbolStorage::Base(struct_offset)) = self.symbol_storage_map.get(structure) { + let mut data_offset = *struct_offset; + for i in 0..index { + let field_size = field_layouts[i as usize].stack_size(PTR_SIZE); + data_offset += field_size as i32; + } + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(data_offset)); + Ok(()) + } else { + Err(format!("unknown struct: {:?}", structure)) + } + } + fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> { match lit { Literal::Int(x) => { @@ -579,7 +653,7 @@ impl< } fn free_symbol(&mut self, sym: &Symbol) { - self.symbols_map.remove(sym); + self.symbol_storage_map.remove(sym); for i in 0..self.general_used_regs.len() { let (reg, saved_sym) = self.general_used_regs[i]; if saved_sym == *sym { @@ -590,8 +664,8 @@ impl< } } - fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> { - let val = self.symbols_map.get(sym); + fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> { + let val = self.symbol_storage_map.get(sym); match val { Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()), Some(SymbolStorage::GeneralReg(reg)) => { @@ -605,6 +679,48 @@ impl< ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); Ok(()) } + Some(SymbolStorage::Base(offset)) => match layout { + Layout::Builtin(Builtin::Int64) => { + ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); + Ok(()) + } + Layout::Builtin(Builtin::Float64) => { + ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); + Ok(()) + } + Layout::Struct(field_layouts) => { + let struct_size = layout.stack_size(PTR_SIZE); + if struct_size > 0 { + let struct_offset = if let Some(SymbolStorage::Base(offset)) = + self.symbol_storage_map.get(sym) + { + Ok(*offset) + } else { + Err(format!("unknown struct: {:?}", sym)) + }?; + let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) + { + Some(self.load_to_general_reg(&Symbol::RET_POINTER)?) + } else { + None + }; + CC::return_struct( + &mut self.buf, + struct_offset, + struct_size, + field_layouts, + ret_reg, + ) + } else { + // Nothing to do for empty struct + Ok(()) + } + } + x => Err(format!( + "returning symbol with layout, {:?}, is not yet implemented", + x + )), + }, Some(x) => Err(format!( "returning symbol storage, {:?}, is not yet implemented", x @@ -624,6 +740,25 @@ impl< CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { + fn get_tmp_general_reg(&mut self) -> Result { + if !self.general_free_regs.is_empty() { + let free_reg = *self + .general_free_regs + .get(self.general_free_regs.len() - 1) + .unwrap(); + if CC::general_callee_saved(&free_reg) { + self.general_used_callee_saved_regs.insert(free_reg); + } + Ok(free_reg) + } else if !self.general_used_regs.is_empty() { + let (reg, sym) = self.general_used_regs.remove(0); + self.free_to_stack(&sym)?; + Ok(reg) + } else { + Err("completely out of general purpose registers".to_string()) + } + } + fn claim_general_reg(&mut self, sym: &Symbol) -> Result { let reg = if !self.general_free_regs.is_empty() { let free_reg = self.general_free_regs.pop().unwrap(); @@ -640,7 +775,7 @@ impl< }?; self.general_used_regs.push((reg, *sym)); - self.symbols_map + self.symbol_storage_map .insert(*sym, SymbolStorage::GeneralReg(reg)); Ok(reg) } @@ -661,27 +796,28 @@ impl< }?; self.float_used_regs.push((reg, *sym)); - self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg)); + self.symbol_storage_map + .insert(*sym, SymbolStorage::FloatReg(reg)); Ok(reg) } fn load_to_general_reg(&mut self, sym: &Symbol) -> Result { - let val = self.symbols_map.remove(sym); + let val = self.symbol_storage_map.remove(sym); match val { Some(SymbolStorage::GeneralReg(reg)) => { - self.symbols_map + self.symbol_storage_map .insert(*sym, SymbolStorage::GeneralReg(reg)); Ok(reg) } Some(SymbolStorage::Base(offset)) => { let reg = self.claim_general_reg(sym)?; - self.symbols_map + self.symbol_storage_map .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset)); ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32); Ok(reg) } Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => { - self.symbols_map + self.symbol_storage_map .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset)); Ok(reg) } @@ -693,21 +829,22 @@ impl< } fn load_to_float_reg(&mut self, sym: &Symbol) -> Result { - let val = self.symbols_map.remove(sym); + let val = self.symbol_storage_map.remove(sym); match val { Some(SymbolStorage::FloatReg(reg)) => { - self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg)); + self.symbol_storage_map + .insert(*sym, SymbolStorage::FloatReg(reg)); Ok(reg) } Some(SymbolStorage::Base(offset)) => { let reg = self.claim_float_reg(sym)?; - self.symbols_map + self.symbol_storage_map .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset)); ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32); Ok(reg) } Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => { - self.symbols_map + self.symbol_storage_map .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset)); Ok(reg) } @@ -719,54 +856,94 @@ impl< } fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> { - let val = self.symbols_map.remove(sym); + let val = self.symbol_storage_map.remove(sym); match val { Some(SymbolStorage::GeneralReg(reg)) => { - let offset = self.increase_stack_size(8)? as i32; + let offset = self.increase_stack_size(8)?; // For base addresssing, use the negative offset - 8. - ASM::mov_base32_reg64(&mut self.buf, -offset - 8, reg); - self.symbols_map - .insert(*sym, SymbolStorage::Base(-offset - 8)); + ASM::mov_base32_reg64(&mut self.buf, offset, reg); + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); Ok(()) } Some(SymbolStorage::FloatReg(reg)) => { - let offset = self.increase_stack_size(8)? as i32; + let offset = self.increase_stack_size(8)?; // For base addresssing, use the negative offset. - ASM::mov_base32_freg64(&mut self.buf, -offset - 8, reg); - self.symbols_map - .insert(*sym, SymbolStorage::Base(-offset - 8)); + ASM::mov_base32_freg64(&mut self.buf, offset, reg); + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); Ok(()) } Some(SymbolStorage::Base(offset)) => { - self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); Ok(()) } Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => { - self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); Ok(()) } Some(SymbolStorage::BaseAndFloatReg(_, offset)) => { - self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); + self.symbol_storage_map + .insert(*sym, SymbolStorage::Base(offset)); Ok(()) } None => Err(format!("Unknown symbol: {}", sym)), } } - /// increase_stack_size increase the current stack size and returns the offset of the stack. - fn increase_stack_size(&mut self, amount: u32) -> Result { + /// increase_stack_size increase the current stack size `amount` bytes. + /// It returns base pointer relative offset of the new data. + fn increase_stack_size(&mut self, amount: u32) -> Result { debug_assert!(amount > 0); - let offset = self.stack_size; if let Some(new_size) = self.stack_size.checked_add(amount) { // Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed. if new_size > i32::MAX as u32 { Err("Ran out of stack space".to_string()) } else { self.stack_size = new_size; + let offset = -(self.stack_size as i32); Ok(offset) } } else { Err("Ran out of stack space".to_string()) } } + + fn copy_symbol_to_stack_offset( + &mut self, + to_offset: i32, + sym: &Symbol, + layout: &Layout<'a>, + ) -> Result<(), String> { + match layout { + Layout::Builtin(Builtin::Int64) => { + let reg = self.load_to_general_reg(sym)?; + ASM::mov_base32_reg64(&mut self.buf, to_offset, reg); + Ok(()) + } + Layout::Builtin(Builtin::Float64) => { + let reg = self.load_to_float_reg(sym)?; + ASM::mov_base32_freg64(&mut self.buf, to_offset, reg); + Ok(()) + } + Layout::Struct(_) if layout.safe_to_memcpy() => { + let tmp_reg = self.get_tmp_general_reg()?; + if let Some(SymbolStorage::Base(from_offset)) = self.symbol_storage_map.get(sym) { + for i in 0..layout.stack_size(PTR_SIZE) as i32 { + ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); + ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); + } + Ok(()) + } else { + Err(format!("unknown struct: {:?}", sym)) + } + } + x => Err(format!( + "copying data to the stack with layout, {:?}, not implemented yet", + x + )), + } + } } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 67ae107b5a..a1b2f27887 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,4 +1,4 @@ -use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage}; +use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; use crate::Relocation; use bumpalo::collections::Vec; use roc_collections::all::MutMap; @@ -177,10 +177,18 @@ impl CallConv for X86_64SystemV { fn load_args<'a>( symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], + ret_layout: &Layout<'a>, ) -> Result<(), String> { let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. let mut general_i = 0; let mut float_i = 0; + if X86_64SystemV::returns_via_arg_pointer(ret_layout)? { + symbol_map.insert( + Symbol::RET_POINTER, + SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]), + ); + general_i += 1; + } for (layout, sym) in args.iter() { match layout { Layout::Builtin(Builtin::Int64) => { @@ -359,6 +367,22 @@ impl CallConv for X86_64SystemV { } Ok(stack_offset as u32) } + + fn return_struct<'a>( + _buf: &mut Vec<'a, u8>, + _struct_offset: i32, + _struct_size: u32, + _field_layouts: &[Layout<'a>], + _ret_reg: Option, + ) -> Result<(), String> { + Err("Returning structs not yet implemented for X86_64".to_string()) + } + + fn returns_via_arg_pointer(ret_layout: &Layout) -> Result { + // TODO: This may need to be more complex/extended to fully support the calling convention. + // details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf + Ok(ret_layout.stack_size(PTR_SIZE) > 16) + } } impl CallConv for X86_64WindowsFastcall { @@ -477,9 +501,18 @@ impl CallConv for X86_64WindowsFastcall { fn load_args<'a>( symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], + ret_layout: &Layout<'a>, ) -> Result<(), String> { let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. - for (i, (layout, sym)) in args.iter().enumerate() { + let mut i = 0; + if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout)? { + symbol_map.insert( + Symbol::RET_POINTER, + SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]), + ); + i += 1; + } + for (layout, sym) in args.iter() { if i < Self::GENERAL_PARAM_REGS.len() { match layout { Layout::Builtin(Builtin::Int64) => { @@ -496,6 +529,7 @@ impl CallConv for X86_64WindowsFastcall { )); } } + i += 1; } else { base_offset += match layout { Layout::Builtin(Builtin::Int64) => 8, @@ -653,6 +687,22 @@ impl CallConv for X86_64WindowsFastcall { } Ok(stack_offset as u32) } + + fn return_struct<'a>( + _buf: &mut Vec<'a, u8>, + _struct_offset: i32, + _struct_size: u32, + _field_layouts: &[Layout<'a>], + _ret_reg: Option, + ) -> Result<(), String> { + Err("Returning structs not yet implemented for X86_64WindowsFastCall".to_string()) + } + + fn returns_via_arg_pointer(ret_layout: &Layout) -> Result { + // TODO: This is not fully correct there are some exceptions for "vector" types. + // details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values + Ok(ret_layout.stack_size(PTR_SIZE) > 8) + } } #[inline(always)] diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index a0d3c38187..1adac8e1a8 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -66,7 +66,11 @@ where // load_args is used to let the backend know what the args are. // The backend should track these args so it can use them as needed. - fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String>; + fn load_args( + &mut self, + args: &'a [(Layout<'a>, Symbol)], + ret_layout: &Layout<'a>, + ) -> Result<(), String>; /// Used for generating wrappers for malloc/realloc/free fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>; @@ -74,29 +78,29 @@ where /// build_proc creates a procedure and outputs it to the wrapped object writer. fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { self.reset(); - self.load_args(proc.args)?; + self.load_args(proc.args, &proc.ret_layout)?; // let start = std::time::Instant::now(); self.scan_ast(&proc.body); self.create_free_map(); // let duration = start.elapsed(); // println!("Time to calculate lifetimes: {:?}", duration); // println!("{:?}", self.last_seen_map()); - self.build_stmt(&proc.body)?; + self.build_stmt(&proc.body, &proc.ret_layout)?; self.finalize() } /// build_stmt builds a statement and outputs at the end of the buffer. - fn build_stmt(&mut self, stmt: &Stmt<'a>) -> Result<(), String> { + fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { match stmt { Stmt::Let(sym, expr, layout, following) => { self.build_expr(sym, expr, layout)?; self.free_symbols(stmt); - self.build_stmt(following)?; + self.build_stmt(following, ret_layout)?; Ok(()) } Stmt::Ret(sym) => { self.load_literal_symbols(&[*sym])?; - self.return_symbol(sym)?; + self.return_symbol(sym, ret_layout)?; self.free_symbols(stmt); Ok(()) } @@ -218,6 +222,15 @@ where x => Err(format!("the call type, {:?}, is not yet implemented", x)), } } + Expr::Struct(fields) => { + self.load_literal_symbols(fields)?; + self.create_struct(sym, layout, fields) + } + Expr::StructAtIndex { + index, + field_layouts, + structure, + } => self.load_struct_at_index(sym, structure, *index, field_layouts), x => Err(format!("the expression, {:?}, is not yet implemented", x)), } } @@ -377,11 +390,28 @@ where Ok(()) } + /// create_struct creates a struct with the elements specified loaded into it as data. + fn create_struct( + &mut self, + sym: &Symbol, + layout: &Layout<'a>, + fields: &'a [Symbol], + ) -> Result<(), String>; + + /// load_struct_at_index loads into `sym` the value at `index` in `structure`. + fn load_struct_at_index( + &mut self, + sym: &Symbol, + structure: &Symbol, + index: u64, + field_layouts: &'a [Layout<'a>], + ) -> Result<(), String>; + /// load_literal sets a symbol to be equal to a literal. fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; /// return_symbol moves a symbol to the correct return location for the backend. - fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>; + fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>; /// free_symbols will free all symbols for the given statement. fn free_symbols(&mut self, stmt: &Stmt<'a>) { diff --git a/compiler/gen_dev/tests/dev_records.rs b/compiler/gen_dev/tests/dev_records.rs new file mode 100644 index 0000000000..2fcebe8c97 --- /dev/null +++ b/compiler/gen_dev/tests/dev_records.rs @@ -0,0 +1,937 @@ +#[macro_use] +extern crate indoc; + +#[macro_use] +mod helpers; + +#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +mod dev_records { + #[test] + fn basic_record() { + assert_evals_to!( + indoc!( + r#" + { y: 17, x: 15, z: 19 }.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn nested_record() { + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a + "# + ), + 12, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c + "# + ), + 2, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn f64_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.x + "# + ), + 15.1, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.y + "# + ), + 17.2, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.z + "# + ), + 19.3, + f64 + ); + } + + // #[test] + // fn fn_record() { + // assert_evals_to!( + // indoc!( + // r#" + // getRec = \x -> { y: 17, x, z: 19 } + + // (getRec 15).x + // "# + // ), + // 15, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // rec = { x: 15, y: 17, z: 19 } + + // rec.y + // "# + // ), + // 17, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // rec = { x: 15, y: 17, z: 19 } + + // rec.z + // "# + // ), + // 19, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // rec = { x: 15, y: 17, z: 19 } + + // rec.z + rec.x + // "# + // ), + // 34, + // i64 + // ); + // } + + #[test] + fn def_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17, x: 15, z: 19 } + + rec.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn when_on_record() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2 } is + { x } -> x + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn when_record_with_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2, y: 3.14 } is + { x: var } -> var + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn let_with_record_pattern() { + assert_evals_to!( + indoc!( + r#" + { x } = { x: 0x2, y: 3.14 } + + x + "# + ), + 2, + i64 + ); + } + + #[test] + fn record_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2, y: 3.14 } is + { x: 0x4 } -> 5 + { x } -> x + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn twice_record_access() { + assert_evals_to!( + indoc!( + r#" + x = {a: 0x2, b: 0x3 } + + x.a + x.b + "# + ), + 5, + i64 + ); + } + #[test] + fn empty_record() { + assert_evals_to!( + indoc!( + r#" + v = {} + + v + "# + ), + (), + () + ); + } + + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); + } + + // #[test] + // fn i64_record2_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5 } + // "# + // ), + // (3, 5), + // (i64, i64) + // ); + // } + + // // #[test] + // // fn i64_record3_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { x: 3, y: 5, z: 17 } + // // "# + // // ), + // // (3, 5, 17), + // // (i64, i64, i64) + // // ); + // // } + + // #[test] + // fn f64_record2_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3.1, y: 5.1 } + // "# + // ), + // (3.1, 5.1), + // (f64, f64) + // ); + // } + + // // #[test] + // // fn f64_record3_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { x: 3.1, y: 5.1, z: 17.1 } + // // "# + // // ), + // // (3.1, 5.1, 17.1), + // // (f64, f64, f64) + // // ); + // // } + + // // #[test] + // // fn bool_record4_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // record : { a : Bool, b : Bool, c : Bool, d : Bool } + // // record = { a: True, b: True, c : True, d : Bool } + + // // record + // // "# + // // ), + // // (true, false, false, true), + // // (bool, bool, bool, bool) + // // ); + // // } + + // #[test] + // fn i64_record1_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3 } + // "# + // ), + // 3, + // i64 + // ); + // } + + // // #[test] + // // fn i64_record9_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + // // "# + // // ), + // // (3, 5, 17, 1, 9, 12, 13, 14, 15), + // // (i64, i64, i64, i64, i64, i64, i64, i64, i64) + // // ); + // // } + + // // #[test] + // // fn f64_record3_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { x: 3.1, y: 5.1, z: 17.1 } + // // "# + // // ), + // // (3.1, 5.1, 17.1), + // // (f64, f64, f64) + // // ); + // // } + + // #[test] + // fn bool_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // x : Bool + // x = True + + // x + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn optional_field_when_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // main = + // a = f { x: Blue, y: 7 } + // b = f { x: Blue } + // c = f { x: Red, y: 11 } + // d = f { x: Red } + + // a * b * c * d + // "# + // ), + // 3 * 5 * 7 * 11, + // i64 + // ); + // } + + // #[test] + // fn optional_field_when_use_default_nested() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // a = f { x: Blue, y: 7 } + // b = f { x: Blue } + // c = f { x: Red, y: 11 } + // d = f { x: Red } + + // a * b * c * d + // "# + // ), + // 3 * 5 * 7 * 11, + // i64 + // ); + // } + + // #[test] + // fn optional_field_when_no_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // { x ? 10, y } = r + // x + y + + // main = + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_when_no_use_default_nested() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // { x ? 10, y } = r + // x + y + + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_let_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // { x ? 10, y } = r + // x + y + + // main = + // f { y: 9 } + // "# + // ), + // 19, + // i64 + // ); + // } + + // #[test] + // fn optional_field_let_no_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // { x ? 10, y } = r + // x + y + + // main = + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_let_no_use_default_nested() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // { x ? 10, y } = r + // x + y + + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_function_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \{ x ? 10, y } -> x + y + + // f { y: 9 } + // "# + // ), + // 19, + // i64 + // ); + // } + + // #[test] + // #[ignore] + // fn optional_field_function_no_use_default() { + // // blocked on https://github.com/rtfeldman/roc/issues/786 + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \{ x ? 10, y } -> x + y + + // main = + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // #[ignore] + // fn optional_field_function_no_use_default_nested() { + // // blocked on https://github.com/rtfeldman/roc/issues/786 + // assert_evals_to!( + // indoc!( + // r#" + // f = \{ x ? 10, y } -> x + y + + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_singleton_record() { + // assert_evals_to!( + // indoc!( + // r#" + // when { x : 4 } is + // { x ? 3 } -> x + // "# + // ), + // 4, + // i64 + // ); + // } + + // #[test] + // fn optional_field_empty_record() { + // assert_evals_to!( + // indoc!( + // r#" + // when { } is + // { x ? 3 } -> x + // "# + // ), + // 3, + // i64 + // ); + // } + + // #[test] + // fn return_record_2() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5 } + // "# + // ), + // [3, 5], + // [i64; 2] + // ); + // } + + // #[test] + // fn return_record_3() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5, z: 4 } + // "# + // ), + // (3, 5, 4), + // (i64, i64, i64) + // ); + // } + + // #[test] + // fn return_record_4() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2 } + // "# + // ), + // [3, 5, 4, 2], + // [i64; 4] + // ); + // } + + // #[test] + // fn return_record_5() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2, e: 1 } + // "# + // ), + // [3, 5, 4, 2, 1], + // [i64; 5] + // ); + // } + + // #[test] + // fn return_record_6() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + // "# + // ), + // [3, 5, 4, 2, 1, 7], + // [i64; 6] + // ); + // } + + // #[test] + // fn return_record_7() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + // "# + // ), + // [3, 5, 4, 2, 1, 7, 8], + // [i64; 7] + // ); + // } + + // #[test] + // fn return_record_float_int() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3.14, b: 0x1 } + // "# + // ), + // (3.14, 0x1), + // (f64, i64) + // ); + // } + + // #[test] + // fn return_record_int_float() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 0x1, b: 3.14 } + // "# + // ), + // (0x1, 3.14), + // (i64, f64) + // ); + // } + + // #[test] + // fn return_record_float_float() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 6.28, b: 3.14 } + // "# + // ), + // (6.28, 3.14), + // (f64, f64) + // ); + // } + + // #[test] + // fn return_record_float_float_float() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 6.28, b: 3.14, c: 0.1 } + // "# + // ), + // (6.28, 3.14, 0.1), + // (f64, f64, f64) + // ); + // } + + // #[test] + // fn return_nested_record() { + // assert_evals_to!( + // indoc!( + // r#" + // { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } } + // "# + // ), + // (0x0, (6.28, 3.14, 0.1)), + // (i64, (f64, f64, f64)) + // ); + // } + + // #[test] + // fn accessor() { + // assert_evals_to!( + // indoc!( + // r#" + // .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 } + // "# + // ), + // 7, + // i64 + // ); + // } + + // #[test] + // fn accessor_single_element_record() { + // assert_evals_to!( + // indoc!( + // r#" + // .foo { foo: 4 } + // "# + // ), + // 4, + // i64 + // ); + // } + + // #[test] + // fn update_record() { + // assert_evals_to!( + // indoc!( + // r#" + // rec = { foo: 42, bar: 6 } + + // { rec & foo: rec.foo + 1 } + // "# + // ), + // (6, 43), + // (i64, i64) + // ); + // } + + #[test] + fn update_single_element_record() { + assert_evals_to!( + indoc!( + r#" + rec = { foo: 42} + + { rec & foo: rec.foo + 1 } + "# + ), + 43, + i64 + ); + } + + // #[test] + // fn booleans_in_record() { + // assert_evals_to!( + // indoc!("{ x: 1 == 1, y: 1 == 1 }"), + // (true, true), + // (bool, bool) + // ); + // assert_evals_to!( + // indoc!("{ x: 1 != 1, y: 1 == 1 }"), + // (false, true), + // (bool, bool) + // ); + // assert_evals_to!( + // indoc!("{ x: 1 == 1, y: 1 != 1 }"), + // (true, false), + // (bool, bool) + // ); + // assert_evals_to!( + // indoc!("{ x: 1 != 1, y: 1 != 1 }"), + // (false, false), + // (bool, bool) + // ); + // } + + // #[test] + // fn alignment_in_record() { + // assert_evals_to!( + // indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"), + // (32i64, true, 2u8), + // (i64, bool, u8) + // ); + // } + + // #[test] + // fn blue_and_present() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // f { x: Blue, y: 7 } + // "# + // ), + // 7, + // i64 + // ); + // } + + // #[test] + // fn blue_and_absent() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // f { x: Blue } + // "# + // ), + // 3, + // i64 + // ); + // } +} diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index dfbc95f3a7..aaa9ce321d 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -786,6 +786,9 @@ define_builtins! { // used to initialize parameters in borrow.rs 22 EMPTY_PARAM: "#empty_param" + + // used by the dev backend to store the pointer to where to store large return types + 23 RET_POINTER: "#ret_pointer" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias