diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 251d3aba44..bf80304c0b 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,16 @@ 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()) + } } impl Assembler for AArch64Assembler { diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index e88cf49d09..9dd48a154a 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -51,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. @@ -63,6 +65,16 @@ 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>; } /// Assembler contains calls to the backend assembly generator. @@ -309,8 +321,12 @@ 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.symbol_storage_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.symbol_storage_map { match storage { @@ -625,36 +641,29 @@ impl< ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); Ok(()) } - Layout::Struct( - &[Layout::Builtin(Builtin::Int64), Layout::Builtin(Builtin::Int64)], - ) => { - if let Some(SymbolStorage::Base(struct_offset)) = - self.symbol_storage_map.get(sym) - { - ASM::mov_reg64_base32( - &mut self.buf, - CC::GENERAL_RETURN_REGS[0], - *struct_offset, - ); - ASM::mov_reg64_base32( - &mut self.buf, - CC::GENERAL_RETURN_REGS[1], - *struct_offset + 8, - ); - Ok(()) - } else { - Err(format!("unknown struct: {:?}", sym)) - } - } - Layout::Struct(_field_layouts) => { + Layout::Struct(field_layouts) => { let struct_size = layout.stack_size(PTR_SIZE); if struct_size > 0 { - // Need at actually dispatch to call conv here since struct return is specific to that. - // CC::return_struct() - Err(format!( - "Returning struct with layout, {:?}, is not yet implemented", - layout - )) + 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(()) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 7a26456f59..921576f7ab 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; @@ -55,6 +55,22 @@ pub struct X86_64SystemV {} const STACK_ALIGNMENT: u8 = 16; +impl X86_64SystemV { + fn returns_via_arg_pointer<'a>(ret_layout: &Layout<'a>) -> 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 + return Ok(ret_layout.stack_size(PTR_SIZE) > 16); + } +} + +impl X86_64WindowsFastcall { + fn returns_via_arg_pointer<'a>(ret_layout: &Layout<'a>) -> 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 + return Ok(ret_layout.stack_size(PTR_SIZE) > 8); + } +} + impl CallConv for X86_64SystemV { const GENERAL_PARAM_REGS: &'static [X86_64GeneralReg] = &[ X86_64GeneralReg::RDI, @@ -177,10 +193,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 +383,16 @@ 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()) + } } impl CallConv for X86_64WindowsFastcall { @@ -477,9 +511,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 +539,7 @@ impl CallConv for X86_64WindowsFastcall { )); } } + i += 1; } else { base_offset += match layout { Layout::Builtin(Builtin::Int64) => 8, @@ -653,6 +697,16 @@ 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()) + } } #[inline(always)] diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 105619e274..4a4ec09825 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -65,12 +65,16 @@ 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>; /// 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(); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index ad79d30e7d..a334b5312c 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -759,6 +759,9 @@ define_builtins! { // a caller (wrapper) for comparison 21 GENERIC_COMPARE_REF: "#generic_compare_ref" + + // used by the dev backend to store the pointer to where to store large return types + 22 RET_POINTER: "#ret_pointer" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias