From d1bcc8d55bce5197a6023d5fa8273576b5ca1fd3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 19:19:35 -0800 Subject: [PATCH 01/56] start storage rewrite --- compiler/gen_dev/src/generic64/mod.rs | 1 + compiler/gen_dev/src/generic64/storage.rs | 87 +++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 compiler/gen_dev/src/generic64/storage.rs diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index f30e98edc3..6ebf85c6ee 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -11,6 +11,7 @@ use roc_target::TargetInfo; use std::marker::PhantomData; pub mod aarch64; +mod storage; pub mod x86_64; const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs new file mode 100644 index 0000000000..148df8d55a --- /dev/null +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -0,0 +1,87 @@ +use crate::generic64::{CallConv, RegTrait}; +use bumpalo::collections::Vec; +use roc_collections::all::{MutMap, MutSet}; +use roc_error_macros::internal_error; +use roc_module::symbol::Symbol; +use std::marker::PhantomData; +use std::rc::Rc; + +#[derive(Clone, Debug, PartialEq)] +enum RegStorage { + General(GeneralReg), + Float(FloatReg), +} + +#[derive(Clone, Debug, PartialEq)] +struct StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { + // The parent of this stack storage. + // If the data is owned, there is none. + // If this is nested in another stack storage, it will have a parent. + parent: Option>>, + // Offset from the base pointer in bytes. + base_offset: i32, + // Size on the stack in bytes. + size: u32, + // Values of this storage currently loaded into registers. + // This is stored in tuples of (offset from start of this storage, register). + // This save on constantly reloading a list pointer for example. + // TODO: add small vec optimization most of the time this should be 0 or 1 items. + refs: Vec<'a, (u32, RegStorage)>, +} + +#[derive(Clone, Debug, PartialEq)] +enum Storage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { + Reg(RegStorage), + Stack(StackStorage<'a, GeneralReg, FloatReg>), +} + +struct StorageManager< + 'a, + GeneralReg: RegTrait, + FloatReg: RegTrait, + CC: CallConv, +> { + phantom_cc: PhantomData, + symbol_storage_map: MutMap>, + + // This should probably be smarter than a vec. + // There are certain registers we should always use first. With pushing and popping, this could get mixed. + general_free_regs: Vec<'a, GeneralReg>, + float_free_regs: Vec<'a, FloatReg>, + + // The last major thing we need is a way to decide what reg to free when all of them are full. + // Theoretically we want a basic lru cache for the currently loaded symbols. + // For now just a vec of used registers and the symbols they contain. + general_used_regs: Vec<'a, (GeneralReg, Symbol)>, + float_used_regs: Vec<'a, (FloatReg, Symbol)>, + + // used callee saved regs must be tracked for pushing and popping at the beginning/end of the function. + general_used_callee_saved_regs: MutSet, + float_used_callee_saved_regs: MutSet, + + free_stack_chunks: Vec<'a, (i32, u32)>, + stack_size: u32, +} + +impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv> + StorageManager<'a, GeneralReg, FloatReg, CC> +{ + // This claims a temporary register and enables is used in the passed in function. + // Temporary registers are not safe across call instructions. + fn with_tmp_general_reg(&mut self, callback: F) { + let reg = if let Some(reg) = self.general_free_regs.pop() { + if CC::general_callee_saved(®) { + self.general_used_callee_saved_regs.insert(reg); + } + reg + } else if !self.general_used_regs.is_empty() { + let (reg, sym) = self.general_used_regs.remove(0); + // self.free_to_stack(&sym); + reg + } else { + internal_error!("completely out of general purpose registers"); + }; + callback(self, reg); + self.general_free_regs.push(reg); + } +} From 1153e0833b4cf85ba54b4f28faaa5f9a6f41b660 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 20:59:51 -0800 Subject: [PATCH 02/56] Make symbols store as RCs --- compiler/gen_dev/src/generic64/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 148df8d55a..8eabc93f6f 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -42,7 +42,7 @@ struct StorageManager< CC: CallConv, > { phantom_cc: PhantomData, - symbol_storage_map: MutMap>, + symbol_storage_map: MutMap>>, // This should probably be smarter than a vec. // There are certain registers we should always use first. With pushing and popping, this could get mixed. From 1926c3e1985461942634cb49d8c1ba847fabab66 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 21:18:52 -0800 Subject: [PATCH 03/56] Add base general reg helpers --- compiler/gen_dev/src/generic64/storage.rs | 30 +++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 8eabc93f6f..de58c55d73 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -6,6 +6,9 @@ use roc_module::symbol::Symbol; use std::marker::PhantomData; use std::rc::Rc; +use RegStorage::*; +use Storage::*; + #[derive(Clone, Debug, PartialEq)] enum RegStorage { General(GeneralReg), @@ -66,10 +69,10 @@ struct StorageManager< impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv> StorageManager<'a, GeneralReg, FloatReg, CC> { - // This claims a temporary register and enables is used in the passed in function. - // Temporary registers are not safe across call instructions. - fn with_tmp_general_reg(&mut self, callback: F) { - let reg = if let Some(reg) = self.general_free_regs.pop() { + // Get a general register from the free list. + // Will free data to the stack if necessary to get the register. + fn get_general_reg(&mut self) -> GeneralReg { + if let Some(reg) = self.general_free_regs.pop() { if CC::general_callee_saved(®) { self.general_used_callee_saved_regs.insert(reg); } @@ -80,7 +83,24 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv GeneralReg { + debug_assert_eq!(self.symbol_storage_map.get(sym), None); + let reg = self.get_general_reg(); + self.general_used_regs.push((reg, *sym)); + self.symbol_storage_map + .insert(*sym, Rc::new(Reg(General(reg)))); + reg + } + + // This claims a temporary register and enables is used in the passed in function. + // Temporary registers are not safe across call instructions. + fn with_tmp_general_reg(&mut self, callback: F) { + let reg = self.get_general_reg(); callback(self, reg); self.general_free_regs.push(reg); } From 6025880e735f71a2632412355cdabaee97060f48 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 22:17:02 -0800 Subject: [PATCH 04/56] Move references out of storage and into own map --- compiler/gen_dev/src/generic64/storage.rs | 103 +++++++++++++++++----- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index de58c55d73..01c0f5861e 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -7,6 +7,7 @@ use std::marker::PhantomData; use std::rc::Rc; use RegStorage::*; +use StackStorage::*; use Storage::*; #[derive(Clone, Debug, PartialEq)] @@ -16,20 +17,29 @@ enum RegStorage { } #[derive(Clone, Debug, PartialEq)] -struct StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { - // The parent of this stack storage. - // If the data is owned, there is none. - // If this is nested in another stack storage, it will have a parent. - parent: Option>>, - // Offset from the base pointer in bytes. - base_offset: i32, - // Size on the stack in bytes. - size: u32, - // Values of this storage currently loaded into registers. - // This is stored in tuples of (offset from start of this storage, register). - // This save on constantly reloading a list pointer for example. - // TODO: add small vec optimization most of the time this should be 0 or 1 items. - refs: Vec<'a, (u32, RegStorage)>, +enum StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { + // Small Values are 8 bytes or less. + // They are used for smaller primitives and values that fit in single registers. + // Their data must always be 8 byte aligned. + SmallValue { + // Offset from the base pointer in bytes. + base_offset: i32, + // Size on the stack in bytes. + size: u8, + // Optional register also holding the value. + reg: Option>, + }, + LargeValue { + // Offset from the base pointer in bytes. + base_offset: i32, + // Size on the stack in bytes. + size: u32, + // Values of this storage currently loaded into registers. + // This is stored in tuples of (offset from start of this storage, register). + // This save on constantly reloading a list pointer for example. + // TODO: add small vec optimization most of the time this should be 0 or 1 items. + refs: Vec<'a, (u32, RegStorage)>, + }, } #[derive(Clone, Debug, PartialEq)] @@ -45,7 +55,15 @@ struct StorageManager< CC: CallConv, > { phantom_cc: PhantomData, - symbol_storage_map: MutMap>>, + // Data about where each symbol is stored. + symbol_storage_map: MutMap>, + + // A map from child to parent storage. + // In the case that subdata is still referenced from an overall structure, + // We can't free the entire structure until the subdata is no longer needed. + // If a symbol has no parent, it points to itself. + // This in only used for data on the stack. + reference_map: MutMap>, // This should probably be smarter than a vec. // There are certain registers we should always use first. With pushing and popping, this could get mixed. @@ -78,9 +96,9 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv GeneralReg { - debug_assert_eq!(self.symbol_storage_map.get(sym), None); + debug_assert_eq!( + self.symbol_storage_map.get(sym), + None, + "unknown symbol: {}", + sym + ); let reg = self.get_general_reg(); self.general_used_regs.push((reg, *sym)); - self.symbol_storage_map - .insert(*sym, Rc::new(Reg(General(reg)))); + self.symbol_storage_map.insert(*sym, Reg(General(reg))); reg } @@ -104,4 +126,45 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv GeneralReg { + let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + Reg(General(reg)) + | Stack(SmallValue { + reg: Some(General(reg)), + .. + }) => reg, + Reg(Float(_)) + | Stack(SmallValue { + reg: Some(Float(_)), + .. + }) => { + internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) + } + Stack(SmallValue { + reg: None, + base_offset, + size, + }) => { + debug_assert_eq!( + base_offset % 8, + 0, + "Small values on the stack must be aligned to 8 bytes" + ); + todo!("loading small value to a reg"); + } + Stack(LargeValue { .. }) => { + internal_error!("Cannot load large values into general registers: {}", sym) + } + } + } } From 9e511486280c7a0a45b1fe8e606e058585a127ab Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 22:25:43 -0800 Subject: [PATCH 05/56] Add assembler and pass buffer through functions --- compiler/gen_dev/src/generic64/storage.rs | 52 ++++++++++++++++------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 01c0f5861e..9e241671ca 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,4 +1,4 @@ -use crate::generic64::{CallConv, RegTrait}; +use crate::generic64::{Assembler, CallConv, RegTrait}; use bumpalo::collections::Vec; use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; @@ -20,12 +20,10 @@ enum RegStorage { enum StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { // Small Values are 8 bytes or less. // They are used for smaller primitives and values that fit in single registers. - // Their data must always be 8 byte aligned. + // Their data must always be 8 byte aligned. An will be moved as a block. SmallValue { // Offset from the base pointer in bytes. base_offset: i32, - // Size on the stack in bytes. - size: u8, // Optional register also holding the value. reg: Option>, }, @@ -52,9 +50,11 @@ struct StorageManager< 'a, GeneralReg: RegTrait, FloatReg: RegTrait, + ASM: Assembler, CC: CallConv, > { phantom_cc: PhantomData, + phantom_asm: PhantomData, // Data about where each symbol is stored. symbol_storage_map: MutMap>, @@ -84,12 +84,17 @@ struct StorageManager< stack_size: u32, } -impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv> - StorageManager<'a, GeneralReg, FloatReg, CC> +impl< + 'a, + FloatReg: RegTrait, + GeneralReg: RegTrait, + ASM: Assembler, + CC: CallConv, + > StorageManager<'a, GeneralReg, FloatReg, ASM, CC> { // Get a general register from the free list. // Will free data to the stack if necessary to get the register. - fn get_general_reg(&mut self) -> GeneralReg { + fn get_general_reg(&mut self, _buf: &mut Vec<'a, u8>) -> GeneralReg { if let Some(reg) = self.general_free_regs.pop() { if CC::general_callee_saved(®) { self.general_used_callee_saved_regs.insert(reg); @@ -106,14 +111,14 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv GeneralReg { + fn claim_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { debug_assert_eq!( self.symbol_storage_map.get(sym), None, "unknown symbol: {}", sym ); - let reg = self.get_general_reg(); + let reg = self.get_general_reg(buf); self.general_used_regs.push((reg, *sym)); self.symbol_storage_map.insert(*sym, Reg(General(reg))); reg @@ -121,9 +126,13 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv(&mut self, callback: F) { - let reg = self.get_general_reg(); - callback(self, reg); + fn with_tmp_general_reg, GeneralReg)>( + &mut self, + buf: &mut Vec<'a, u8>, + callback: F, + ) { + let reg = self.get_general_reg(buf); + callback(self, buf, reg); self.general_free_regs.push(reg); } @@ -131,7 +140,7 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv GeneralReg { + fn to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { storage } else { @@ -142,7 +151,10 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv reg, + }) => { + self.symbol_storage_map.insert(*sym, storage); + reg + } Reg(Float(_)) | Stack(SmallValue { reg: Some(Float(_)), @@ -153,14 +165,22 @@ impl<'a, FloatReg: RegTrait, GeneralReg: RegTrait, CC: CallConv { debug_assert_eq!( base_offset % 8, 0, "Small values on the stack must be aligned to 8 bytes" ); - todo!("loading small value to a reg"); + let reg = self.get_general_reg(buf); + ASM::mov_reg64_base32(buf, reg, base_offset); + self.symbol_storage_map.insert( + *sym, + Stack(SmallValue { + base_offset, + reg: Some(General(reg)), + }), + ); + reg } Stack(LargeValue { .. }) => { internal_error!("Cannot load large values into general registers: {}", sym) From 6e10e005349ab0320f2066732ddc9553218d26d3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 22:56:27 -0800 Subject: [PATCH 06/56] add core stack methods --- compiler/gen_dev/src/generic64/storage.rs | 114 ++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 9e241671ca..070e597f77 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -173,6 +173,7 @@ impl< ); let reg = self.get_general_reg(buf); ASM::mov_reg64_base32(buf, reg, base_offset); + self.general_used_regs.push((reg, *sym)); self.symbol_storage_map.insert( *sym, Stack(SmallValue { @@ -187,4 +188,117 @@ impl< } } } + + // Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. + fn free_to_stack( + &mut self, + buf: &mut Vec<'a, u8>, + sym: &Symbol, + wanted_reg: RegStorage, + ) { + let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + Reg(reg_storage) => { + debug_assert_eq!(reg_storage, wanted_reg); + let base_offset = self.claim_stack_size(8); + match reg_storage { + General(reg) => ASM::mov_base32_reg64(buf, base_offset, reg), + Float(reg) => ASM::mov_base32_freg64(buf, base_offset, reg), + } + self.symbol_storage_map.insert( + *sym, + Stack(SmallValue { + base_offset, + reg: None, + }), + ); + } + Stack(SmallValue { + reg: Some(reg_storage), + base_offset, + }) => { + debug_assert_eq!(reg_storage, wanted_reg); + self.symbol_storage_map.insert( + *sym, + Stack(SmallValue { + base_offset, + reg: None, + }), + ); + } + Stack(SmallValue { reg: None, .. }) => { + internal_error!("Cannot free reg from symbol without a reg: {}", sym) + } + Stack(LargeValue { + base_offset, + size, + mut refs, + }) => { + match refs + .iter() + .position(|(_, reg_storage)| *reg_storage == wanted_reg) + { + Some(pos) => { + refs.remove(pos); + self.symbol_storage_map.insert( + *sym, + Stack(LargeValue { + base_offset, + size, + refs, + }), + ); + } + None => { + internal_error!("Cannot free reg from symbol without a reg: {}", sym) + } + } + } + } + } + + /// claim_stack_size claims `amount` bytes from the stack alignind to 8. + /// This may be free space in the stack or result in increasing the stack size. + /// It returns base pointer relative offset of the new data. + fn claim_stack_size(&mut self, amount: u32) -> i32 { + debug_assert!(amount > 0); + // round value to 8 byte alignment. + let amount = if amount % 8 != 0 { + amount + 8 - (amount % 8) + } else { + amount + }; + if let Some(fitting_chunk) = self + .free_stack_chunks + .iter() + .enumerate() + .filter(|(_, (_, size))| *size >= amount) + .min_by_key(|(_, (_, size))| size) + { + let (pos, (offset, size)) = fitting_chunk; + let (offset, size) = (*offset, *size); + if size == amount { + self.free_stack_chunks.remove(pos); + offset + } else { + let (prev_offset, prev_size) = self.free_stack_chunks[pos]; + self.free_stack_chunks[pos] = (prev_offset + amount as i32, prev_size - amount); + prev_offset + } + } else 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 { + internal_error!("Ran out of stack space"); + } else { + self.stack_size = new_size; + -(self.stack_size as i32) + } + } else { + internal_error!("Ran out of stack space"); + } + } } From 0d70a4f9f863851b1a63528d58afe51c25f6740a Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 23:06:31 -0800 Subject: [PATCH 07/56] add symbol storage into generic64 backend --- compiler/gen_dev/src/generic64/mod.rs | 8 +++- compiler/gen_dev/src/generic64/storage.rs | 51 +++++++++++++++++++++-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 6ebf85c6ee..02ac583cd7 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -14,6 +14,8 @@ pub mod aarch64; mod storage; pub mod x86_64; +use storage::StorageManager; + const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); pub trait CallConv { @@ -269,10 +271,12 @@ pub struct Backend64Bit< layout_map: MutMap>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, - symbol_storage_map: MutMap>, literal_map: MutMap, *const Layout<'a>)>, join_map: MutMap, + storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>, + symbol_storage_map: MutMap>, + // This should probably be smarter than a vec. // There are certain registers we should always use first. With pushing and popping, this could get mixed. general_free_regs: Vec<'a, GeneralReg>, @@ -331,6 +335,7 @@ pub fn new_backend_64bit< free_stack_chunks: bumpalo::vec![in env.arena], stack_size: 0, fn_call_stack_size: 0, + storage_manager: storage::new_storage_manager(env), } } @@ -384,6 +389,7 @@ impl< self.float_free_regs .extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS); self.helper_proc_symbols.clear(); + self.storage_manager.reset(); } fn literal_map(&mut self) -> &mut MutMap, *const Layout<'a>)> { diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 070e597f77..e8e69377ef 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,4 +1,5 @@ use crate::generic64::{Assembler, CallConv, RegTrait}; +use crate::Env; use bumpalo::collections::Vec; use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; @@ -46,7 +47,7 @@ enum Storage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { Stack(StackStorage<'a, GeneralReg, FloatReg>), } -struct StorageManager< +pub struct StorageManager< 'a, GeneralReg: RegTrait, FloatReg: RegTrait, @@ -84,6 +85,31 @@ struct StorageManager< stack_size: u32, } +pub fn new_storage_manager< + 'a, + GeneralReg: RegTrait, + FloatReg: RegTrait, + ASM: Assembler, + CC: CallConv, +>( + env: &'a Env, +) -> StorageManager<'a, GeneralReg, FloatReg, ASM, CC> { + StorageManager { + phantom_asm: PhantomData, + phantom_cc: PhantomData, + symbol_storage_map: MutMap::default(), + reference_map: MutMap::default(), + general_free_regs: bumpalo::vec![in env.arena], + general_used_regs: bumpalo::vec![in env.arena], + general_used_callee_saved_regs: MutSet::default(), + float_free_regs: bumpalo::vec![in env.arena], + float_used_regs: bumpalo::vec![in env.arena], + float_used_callee_saved_regs: MutSet::default(), + free_stack_chunks: bumpalo::vec![in env.arena], + stack_size: 0, + } +} + impl< 'a, FloatReg: RegTrait, @@ -92,6 +118,23 @@ impl< CC: CallConv, > StorageManager<'a, GeneralReg, FloatReg, ASM, CC> { + pub fn reset(&mut self) { + self.symbol_storage_map.clear(); + self.reference_map.clear(); + self.general_used_callee_saved_regs.clear(); + self.general_free_regs.clear(); + self.general_used_regs.clear(); + self.general_free_regs + .extend_from_slice(CC::GENERAL_DEFAULT_FREE_REGS); + self.float_used_callee_saved_regs.clear(); + self.float_free_regs.clear(); + self.float_used_regs.clear(); + self.float_free_regs + .extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS); + self.free_stack_chunks.clear(); + self.stack_size = 0; + } + // Get a general register from the free list. // Will free data to the stack if necessary to get the register. fn get_general_reg(&mut self, _buf: &mut Vec<'a, u8>) -> GeneralReg { @@ -111,7 +154,7 @@ impl< // Claims a general reg for a specific symbol. // They symbol should not already have storage. - fn claim_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { + pub fn claim_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { debug_assert_eq!( self.symbol_storage_map.get(sym), None, @@ -126,7 +169,7 @@ impl< // This claims a temporary register and enables is used in the passed in function. // Temporary registers are not safe across call instructions. - fn with_tmp_general_reg, GeneralReg)>( + pub fn with_tmp_general_reg, GeneralReg)>( &mut self, buf: &mut Vec<'a, u8>, callback: F, @@ -140,7 +183,7 @@ impl< // The symbol must already be stored somewhere. // Will fail on values stored in float regs. // Will fail for values that don't fit in a single register. - fn to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { + pub fn to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { storage } else { From 835d3980d53d42f8fd3601c0efe7c94af7bd9200 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 23:13:56 -0800 Subject: [PATCH 08/56] add float methods to storage manager --- compiler/gen_dev/src/generic64/storage.rs | 111 ++++++++++++++++++---- 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index e8e69377ef..da7044a51f 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -137,16 +137,33 @@ impl< // Get a general register from the free list. // Will free data to the stack if necessary to get the register. - fn get_general_reg(&mut self, _buf: &mut Vec<'a, u8>) -> GeneralReg { + fn get_general_reg(&mut self, buf: &mut Vec<'a, u8>) -> GeneralReg { if let Some(reg) = self.general_free_regs.pop() { if CC::general_callee_saved(®) { self.general_used_callee_saved_regs.insert(reg); } reg } else if !self.general_used_regs.is_empty() { - let (_reg, _sym) = self.general_used_regs.remove(0); - // self.free_to_stack(&sym); - todo!("freeing symbols to the stack"); + let (reg, sym) = self.general_used_regs.remove(0); + self.free_to_stack(buf, &sym, General(reg)); + reg + } else { + internal_error!("completely out of general purpose registers"); + } + } + + // Get a float register from the free list. + // Will free data to the stack if necessary to get the register. + fn get_float_reg(&mut self, buf: &mut Vec<'a, u8>) -> FloatReg { + if let Some(reg) = self.float_free_regs.pop() { + if CC::float_callee_saved(®) { + self.float_used_callee_saved_regs.insert(reg); + } + reg + } else if !self.float_used_regs.is_empty() { + let (reg, sym) = self.float_used_regs.remove(0); + self.free_to_stack(buf, &sym, Float(reg)); + reg } else { internal_error!("completely out of general purpose registers"); } @@ -155,19 +172,24 @@ impl< // Claims a general reg for a specific symbol. // They symbol should not already have storage. pub fn claim_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { - debug_assert_eq!( - self.symbol_storage_map.get(sym), - None, - "unknown symbol: {}", - sym - ); + debug_assert_eq!(self.symbol_storage_map.get(sym), None); let reg = self.get_general_reg(buf); self.general_used_regs.push((reg, *sym)); self.symbol_storage_map.insert(*sym, Reg(General(reg))); reg } - // This claims a temporary register and enables is used in the passed in function. + // Claims a float reg for a specific symbol. + // They symbol should not already have storage. + pub fn claim_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { + debug_assert_eq!(self.symbol_storage_map.get(sym), None); + let reg = self.get_float_reg(buf); + self.float_used_regs.push((reg, *sym)); + self.symbol_storage_map.insert(*sym, Reg(Float(reg))); + reg + } + + // This claims a temporary general register and enables is used in the passed in function. // Temporary registers are not safe across call instructions. pub fn with_tmp_general_reg, GeneralReg)>( &mut self, @@ -179,6 +201,18 @@ impl< self.general_free_regs.push(reg); } + // This claims a temporary float register and enables is used in the passed in function. + // Temporary registers are not safe across call instructions. + pub fn with_tmp_float_reg, FloatReg)>( + &mut self, + buf: &mut Vec<'a, u8>, + callback: F, + ) { + let reg = self.get_float_reg(buf); + callback(self, buf, reg); + self.float_free_regs.push(reg); + } + // Loads a symbol into a general reg and returns that register. // The symbol must already be stored somewhere. // Will fail on values stored in float regs. @@ -209,11 +243,7 @@ impl< reg: None, base_offset, }) => { - debug_assert_eq!( - base_offset % 8, - 0, - "Small values on the stack must be aligned to 8 bytes" - ); + debug_assert_eq!(base_offset % 8, 0); let reg = self.get_general_reg(buf); ASM::mov_reg64_base32(buf, reg, base_offset); self.general_used_regs.push((reg, *sym)); @@ -232,6 +262,55 @@ impl< } } + // Loads a symbol into a float reg and returns that register. + // The symbol must already be stored somewhere. + // Will fail on values stored in general regs. + // Will fail for values that don't fit in a single register. + pub fn to_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { + let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + Reg(Float(reg)) + | Stack(SmallValue { + reg: Some(Float(reg)), + .. + }) => { + self.symbol_storage_map.insert(*sym, storage); + reg + } + Reg(General(_)) + | Stack(SmallValue { + reg: Some(General(_)), + .. + }) => { + internal_error!("Cannot load general symbol into FloatReg: {}", sym) + } + Stack(SmallValue { + reg: None, + base_offset, + }) => { + debug_assert_eq!(base_offset % 8, 0); + let reg = self.get_float_reg(buf); + ASM::mov_freg64_base32(buf, reg, base_offset); + self.float_used_regs.push((reg, *sym)); + self.symbol_storage_map.insert( + *sym, + Stack(SmallValue { + base_offset, + reg: Some(Float(reg)), + }), + ); + reg + } + Stack(LargeValue { .. }) => { + internal_error!("Cannot load large values into float registers: {}", sym) + } + } + } + // Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. fn free_to_stack( &mut self, From b76052c91e46d2ba7cdf66acfa36ab1c3ac45971 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 23:33:26 -0800 Subject: [PATCH 09/56] swap to using storage manager for some core functions --- compiler/gen_dev/src/generic64/mod.rs | 463 +++++----------------- compiler/gen_dev/src/generic64/storage.rs | 25 +- 2 files changed, 128 insertions(+), 360 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 02ac583cd7..4cfaefcaf4 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -550,7 +550,8 @@ impl< } } // Save used caller saved regs. - self.push_used_caller_saved_regs_to_stack(); + self.storage_manager + .push_used_caller_saved_regs_to_stack(&mut self.buf, self.env.arena); // Put values in param regs or on top of the stack. let tmp_stack_size = CC::store_args( @@ -568,11 +569,11 @@ impl< // move return value to dst. match ret_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64) | Builtin::Bool) => { - let dst_reg = self.claim_general_reg(dst); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let dst_reg = self.claim_float_reg(dst); + let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); } Layout::Builtin(Builtin::Str) => { @@ -611,7 +612,9 @@ impl< // 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. // The basic plan is to make jumps to nowhere and then correct them once we know the correct address. - let cond_reg = self.load_to_general_reg(cond_symbol); + let cond_reg = self + .storage_manager + .to_general_reg(&mut self.buf, cond_symbol); let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena]; @@ -760,7 +763,8 @@ impl< ) { // Treat this like a function call, but with a jump instead of a call instruction at the end. - self.push_used_caller_saved_regs_to_stack(); + self.storage_manager + .push_used_caller_saved_regs_to_stack(&mut self.buf, self.env.arena); let tmp_stack_size = CC::store_args( &mut self.buf, @@ -791,13 +795,13 @@ impl< fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src_reg = self.load_to_general_reg(src); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); ASM::abs_reg64_reg64(&mut self.buf, dst_reg, src_reg); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let dst_reg = self.claim_float_reg(dst); - let src_reg = self.load_to_float_reg(src); + let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg); } x => todo!("NumAbs: layout, {:?}", x), @@ -807,15 +811,15 @@ impl< fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let dst_reg = self.claim_float_reg(dst); - let src1_reg = self.load_to_float_reg(src1); - let src2_reg = self.load_to_float_reg(src2); + let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_float_reg(&mut self.buf, src2); ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumAdd: layout, {:?}", x), @@ -825,9 +829,9 @@ impl< fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumMul: layout, {:?}", x), @@ -837,8 +841,8 @@ impl< fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src_reg = self.load_to_general_reg(src); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg); } x => todo!("NumNeg: layout, {:?}", x), @@ -848,9 +852,9 @@ impl< fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumSub: layout, {:?}", x), @@ -860,9 +864,9 @@ impl< fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumEq: layout, {:?}", x), @@ -872,9 +876,9 @@ impl< fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumNeq: layout, {:?}", x), @@ -890,9 +894,9 @@ impl< ) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumLt: layout, {:?}", x), @@ -906,48 +910,48 @@ impl< arg_layout: &Layout<'a>, ret_layout: &Layout<'a>, ) { - let dst_reg = self.claim_float_reg(dst); + let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); match (arg_layout, ret_layout) { ( Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let src_reg = self.load_to_general_reg(src); + let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); ASM::to_float_freg64_reg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let src_reg = self.load_to_general_reg(src); + let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); ASM::to_float_freg32_reg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F64)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let src_reg = self.load_to_float_reg(src); + let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); ASM::to_float_freg32_freg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F32)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let src_reg = self.load_to_float_reg(src); + let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); ASM::to_float_freg64_freg32(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F64)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let src_reg = self.load_to_float_reg(src); + let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F32)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let src_reg = self.load_to_float_reg(src); + let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); } (a, r) => todo!("NumToFloat: layout, arg {:?}, ret {:?}", a, r), @@ -963,9 +967,9 @@ impl< ) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst); - let src1_reg = self.load_to_general_reg(src1); - let src2_reg = self.load_to_general_reg(src2); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumGte: layout, {:?}", x), @@ -976,8 +980,8 @@ impl< // We may not strictly need an instruction here. // What's important is to load the value, and for src and dest to have different Layouts. // This is used for pointer math in refcounting and for pointer equality - let dst_reg = self.claim_general_reg(dst); - let src_reg = self.load_to_general_reg(src); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg); } @@ -1068,48 +1072,48 @@ impl< | IntWidth::I64, )), ) => { - let reg = self.claim_general_reg(sym); + let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); let val = *x; ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64); } (Literal::Float(x), Layout::Builtin(Builtin::Float(FloatWidth::F64))) => { - let reg = self.claim_float_reg(sym); + let reg = self.storage_manager.claim_float_reg(&mut self.buf, sym); let val = *x; ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); } (Literal::Float(x), Layout::Builtin(Builtin::Float(FloatWidth::F32))) => { - let reg = self.claim_float_reg(sym); + let reg = self.storage_manager.claim_float_reg(&mut self.buf, sym); let val = *x as f32; ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val); } - (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 16 => { - // Load small string. - let reg = self.get_tmp_general_reg(); + // (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 16 => { + // Load small string. + // let reg = self.get_tmp_general_reg(); - let offset = self.claim_stack_size(16); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size: 16, - owned: true, - }, - ); - let mut bytes = [0; 16]; - bytes[..x.len()].copy_from_slice(x.as_bytes()); - bytes[15] = (x.len() as u8) | 0b1000_0000; + // let offset = self.claim_stack_size(16); + // self.symbol_storage_map.insert( + // *sym, + // SymbolStorage::Base { + // offset, + // size: 16, + // owned: true, + // }, + // ); + // let mut bytes = [0; 16]; + // bytes[..x.len()].copy_from_slice(x.as_bytes()); + // bytes[15] = (x.len() as u8) | 0b1000_0000; - let mut num_bytes = [0; 8]; - num_bytes.copy_from_slice(&bytes[..8]); - let num = i64::from_ne_bytes(num_bytes); - ASM::mov_reg64_imm64(&mut self.buf, reg, num); - ASM::mov_base32_reg64(&mut self.buf, offset, reg); + // let mut num_bytes = [0; 8]; + // num_bytes.copy_from_slice(&bytes[..8]); + // let num = i64::from_ne_bytes(num_bytes); + // ASM::mov_reg64_imm64(&mut self.buf, reg, num); + // ASM::mov_base32_reg64(&mut self.buf, offset, reg); - num_bytes.copy_from_slice(&bytes[8..]); - let num = i64::from_ne_bytes(num_bytes); - ASM::mov_reg64_imm64(&mut self.buf, reg, num); - ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); - } + // num_bytes.copy_from_slice(&bytes[8..]); + // let num = i64::from_ne_bytes(num_bytes); + // ASM::mov_reg64_imm64(&mut self.buf, reg, num); + // ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); + // } x => todo!("loading literal, {:?}", x), } } @@ -1247,7 +1251,10 @@ impl< if size > 0 { let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { - Some(self.load_to_general_reg(&Symbol::RET_POINTER)) + Some( + self.storage_manager + .to_general_reg(&mut self.buf, &Symbol::RET_POINTER), + ) } else { None }; @@ -1284,244 +1291,6 @@ impl< CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - fn get_tmp_general_reg(&mut self) -> GeneralReg { - 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); - } - free_reg - } else if !self.general_used_regs.is_empty() { - let (reg, sym) = self.general_used_regs.remove(0); - self.free_to_stack(&sym); - reg - } else { - internal_error!("completely out of general purpose registers"); - } - } - - fn claim_general_reg(&mut self, sym: &Symbol) -> GeneralReg { - let reg = if !self.general_free_regs.is_empty() { - let free_reg = self.general_free_regs.pop().unwrap(); - if CC::general_callee_saved(&free_reg) { - self.general_used_callee_saved_regs.insert(free_reg); - } - free_reg - } else if !self.general_used_regs.is_empty() { - let (reg, sym) = self.general_used_regs.remove(0); - self.free_to_stack(&sym); - reg - } else { - internal_error!("completely out of general purpose registers"); - }; - - self.general_used_regs.push((reg, *sym)); - self.symbol_storage_map - .insert(*sym, SymbolStorage::GeneralReg(reg)); - reg - } - - fn claim_float_reg(&mut self, sym: &Symbol) -> FloatReg { - let reg = if !self.float_free_regs.is_empty() { - let free_reg = self.float_free_regs.pop().unwrap(); - if CC::float_callee_saved(&free_reg) { - self.float_used_callee_saved_regs.insert(free_reg); - } - free_reg - } else if !self.float_used_regs.is_empty() { - let (reg, sym) = self.float_used_regs.remove(0); - self.free_to_stack(&sym); - reg - } else { - internal_error!("completely out of floating point registers"); - }; - - self.float_used_regs.push((reg, *sym)); - self.symbol_storage_map - .insert(*sym, SymbolStorage::FloatReg(reg)); - reg - } - - fn load_to_general_reg(&mut self, sym: &Symbol) -> GeneralReg { - let val = self.symbol_storage_map.remove(sym); - match val { - Some(SymbolStorage::GeneralReg(reg)) => { - self.symbol_storage_map - .insert(*sym, SymbolStorage::GeneralReg(reg)); - reg - } - Some(SymbolStorage::Base { - offset, - size, - owned, - }) => { - let reg = self.claim_general_reg(sym); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::BaseAndGeneralReg { - reg, - offset, - size, - owned, - }, - ); - ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32); - reg - } - Some(SymbolStorage::BaseAndGeneralReg { - reg, - offset, - size, - owned, - }) => { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::BaseAndGeneralReg { - reg, - offset, - size, - owned, - }, - ); - reg - } - Some(SymbolStorage::FloatReg(_)) | Some(SymbolStorage::BaseAndFloatReg { .. }) => { - internal_error!("Cannot load floating point symbol into GeneralReg") - } - None => internal_error!("Unknown symbol: {}", sym), - } - } - - fn load_to_float_reg(&mut self, sym: &Symbol) -> FloatReg { - let val = self.symbol_storage_map.remove(sym); - match val { - Some(SymbolStorage::FloatReg(reg)) => { - self.symbol_storage_map - .insert(*sym, SymbolStorage::FloatReg(reg)); - reg - } - Some(SymbolStorage::Base { - offset, - size, - owned, - }) => { - let reg = self.claim_float_reg(sym); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::BaseAndFloatReg { - reg, - offset, - size, - owned, - }, - ); - ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32); - reg - } - Some(SymbolStorage::BaseAndFloatReg { - reg, - offset, - size, - owned, - }) => { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::BaseAndFloatReg { - reg, - offset, - size, - owned, - }, - ); - reg - } - Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg { .. }) => { - internal_error!("Cannot load integer symbol into FloatReg") - } - None => internal_error!("Unknown symbol: {}", sym), - } - } - - fn free_to_stack(&mut self, sym: &Symbol) { - let val = self.symbol_storage_map.remove(sym); - match val { - Some(SymbolStorage::GeneralReg(reg)) => { - let offset = self.claim_stack_size(8); - // For base addressing, use the negative offset - 8. - ASM::mov_base32_reg64(&mut self.buf, offset, reg); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size: 8, - owned: true, - }, - ); - } - Some(SymbolStorage::FloatReg(reg)) => { - let offset = self.claim_stack_size(8); - // For base addressing, use the negative offset. - ASM::mov_base32_freg64(&mut self.buf, offset, reg); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size: 8, - owned: true, - }, - ); - } - Some(SymbolStorage::Base { - offset, - size, - owned, - }) => { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size, - owned, - }, - ); - } - Some(SymbolStorage::BaseAndGeneralReg { - offset, - size, - owned, - .. - }) => { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size, - owned, - }, - ); - } - Some(SymbolStorage::BaseAndFloatReg { - offset, - size, - owned, - .. - }) => { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size, - owned, - }, - ); - } - None => internal_error!("Unknown symbol: {}", sym), - } - } - /// claim_stack_size claims `amount` bytes from the stack. /// This may be free space in the stack or result in increasing the stack size. /// It returns base pointer relative offset of the new data. @@ -1560,63 +1329,39 @@ impl< fn copy_symbol_to_stack_offset(&mut self, to_offset: i32, sym: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let reg = self.load_to_general_reg(sym); + let reg = self.storage_manager.to_general_reg(&mut self.buf, sym); ASM::mov_base32_reg64(&mut self.buf, to_offset, reg); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let reg = self.load_to_float_reg(sym); + let reg = self.storage_manager.to_float_reg(&mut self.buf, sym); ASM::mov_base32_freg64(&mut self.buf, to_offset, reg); } - Layout::Struct(_) if layout.safe_to_memcpy() => { - let tmp_reg = self.get_tmp_general_reg(); - if let Some(SymbolStorage::Base { - offset: from_offset, - size, - .. - }) = self.symbol_storage_map.get(sym) - { - debug_assert_eq!( - *size, - layout.stack_size(TARGET_INFO), - "expected struct to have same size as data being stored in it" - ); - for i in 0..layout.stack_size(TARGET_INFO) 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); - } - } else { - internal_error!("unknown struct: {:?}", sym); - } - } + // Layout::Struct(_) if layout.safe_to_memcpy() => { + // // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) + // // if let Some(SymbolStorage::Base { + // // offset: from_offset, + // // size, + // // .. + // // }) = self.symbol_storage_map.get(sym) + // // { + // // debug_assert_eq!( + // // *size, + // // layout.stack_size(TARGET_INFO), + // // "expected struct to have same size as data being stored in it" + // // ); + // // for i in 0..layout.stack_size(TARGET_INFO) 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); + // // } + // todo!() + // } else { + // internal_error!("unknown struct: {:?}", sym); + // } + // } x => todo!("copying data to the stack with layout, {:?}", x), } } - fn push_used_caller_saved_regs_to_stack(&mut self) { - let old_general_used_regs = std::mem::replace( - &mut self.general_used_regs, - bumpalo::vec![in self.env.arena], - ); - for (reg, saved_sym) in old_general_used_regs.into_iter() { - if CC::general_caller_saved(®) { - self.general_free_regs.push(reg); - self.free_to_stack(&saved_sym); - } else { - self.general_used_regs.push((reg, saved_sym)); - } - } - let old_float_used_regs = - std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]); - for (reg, saved_sym) in old_float_used_regs.into_iter() { - if CC::float_caller_saved(®) { - self.float_free_regs.push(reg); - self.free_to_stack(&saved_sym); - } else { - self.float_used_regs.push((reg, saved_sym)); - } - } - } - // Updates a jump instruction to a new offset and returns the number of bytes written. fn update_jmp_imm32_offset( &mut self, diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index da7044a51f..a9e263991e 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,6 +1,6 @@ use crate::generic64::{Assembler, CallConv, RegTrait}; use crate::Env; -use bumpalo::collections::Vec; +use bumpalo::{collections::Vec, Bump}; use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; @@ -423,4 +423,27 @@ impl< internal_error!("Ran out of stack space"); } } + + pub fn push_used_caller_saved_regs_to_stack(&mut self, buf: &mut Vec<'a, u8>, arena: &'a Bump) { + let old_general_used_regs = + std::mem::replace(&mut self.general_used_regs, bumpalo::vec![in arena]); + for (reg, saved_sym) in old_general_used_regs.into_iter() { + if CC::general_caller_saved(®) { + self.general_free_regs.push(reg); + self.free_to_stack(buf, &saved_sym, General(reg)); + } else { + self.general_used_regs.push((reg, saved_sym)); + } + } + let old_float_used_regs = + std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in arena]); + for (reg, saved_sym) in old_float_used_regs.into_iter() { + if CC::float_caller_saved(®) { + self.float_free_regs.push(reg); + self.free_to_stack(buf, &saved_sym, Float(reg)); + } else { + self.float_used_regs.push((reg, saved_sym)); + } + } + } } From d7cac1a224148bc7a4748a7b3314ea6739f7167f Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 16 Feb 2022 23:37:28 -0800 Subject: [PATCH 10/56] fix naming clippy warning --- compiler/gen_dev/src/generic64/mod.rs | 88 +++++++++++++++-------- compiler/gen_dev/src/generic64/storage.rs | 4 +- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 4cfaefcaf4..791b88fde8 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -614,7 +614,7 @@ impl< // The basic plan is to make jumps to nowhere and then correct them once we know the correct address. let cond_reg = self .storage_manager - .to_general_reg(&mut self.buf, cond_symbol); + .load_to_general_reg(&mut self.buf, cond_symbol); let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena]; @@ -796,12 +796,12 @@ impl< match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); ASM::abs_reg64_reg64(&mut self.buf, dst_reg, src_reg); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); - let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg); } x => todo!("NumAbs: layout, {:?}", x), @@ -812,14 +812,18 @@ impl< match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_float_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_float_reg(&mut self.buf, src2); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumAdd: layout, {:?}", x), @@ -830,8 +834,12 @@ impl< match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumMul: layout, {:?}", x), @@ -842,7 +850,7 @@ impl< match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg); } x => todo!("NumNeg: layout, {:?}", x), @@ -853,8 +861,12 @@ impl< match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumSub: layout, {:?}", x), @@ -865,8 +877,12 @@ impl< match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumEq: layout, {:?}", x), @@ -877,8 +893,12 @@ impl< match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumNeq: layout, {:?}", x), @@ -895,8 +915,12 @@ impl< match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumLt: layout, {:?}", x), @@ -916,42 +940,42 @@ impl< Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); ASM::to_float_freg64_reg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); ASM::to_float_freg32_reg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F64)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); ASM::to_float_freg32_freg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F32)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); ASM::to_float_freg64_freg32(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F64)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); } ( Layout::Builtin(Builtin::Float(FloatWidth::F32)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let src_reg = self.storage_manager.to_float_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); } (a, r) => todo!("NumToFloat: layout, arg {:?}, ret {:?}", a, r), @@ -968,8 +992,12 @@ impl< match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self.storage_manager.to_general_reg(&mut self.buf, src1); - let src2_reg = self.storage_manager.to_general_reg(&mut self.buf, src2); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } x => todo!("NumGte: layout, {:?}", x), @@ -981,7 +1009,7 @@ impl< // What's important is to load the value, and for src and dest to have different Layouts. // This is used for pointer math in refcounting and for pointer equality let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src_reg = self.storage_manager.to_general_reg(&mut self.buf, src); + let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg); } @@ -1253,7 +1281,7 @@ impl< { Some( self.storage_manager - .to_general_reg(&mut self.buf, &Symbol::RET_POINTER), + .load_to_general_reg(&mut self.buf, &Symbol::RET_POINTER), ) } else { None @@ -1329,11 +1357,11 @@ impl< fn copy_symbol_to_stack_offset(&mut self, to_offset: i32, sym: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let reg = self.storage_manager.to_general_reg(&mut self.buf, sym); + let reg = self.storage_manager.load_to_general_reg(&mut self.buf, sym); ASM::mov_base32_reg64(&mut self.buf, to_offset, reg); } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let reg = self.storage_manager.to_float_reg(&mut self.buf, sym); + let reg = self.storage_manager.load_to_float_reg(&mut self.buf, sym); ASM::mov_base32_freg64(&mut self.buf, to_offset, reg); } // Layout::Struct(_) if layout.safe_to_memcpy() => { diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index a9e263991e..f0f7ee1492 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -217,7 +217,7 @@ impl< // The symbol must already be stored somewhere. // Will fail on values stored in float regs. // Will fail for values that don't fit in a single register. - pub fn to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { + pub fn load_to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { storage } else { @@ -266,7 +266,7 @@ impl< // The symbol must already be stored somewhere. // Will fail on values stored in general regs. // Will fail for values that don't fit in a single register. - pub fn to_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { + pub fn load_to_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { storage } else { From dff1255fd3c862e12d25f21466649e71960dde3e Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 14:47:24 -0800 Subject: [PATCH 11/56] properly get target_info and move claim stack fully to storage manager --- compiler/gen_dev/src/generic64/mod.rs | 145 ++++++---------------- compiler/gen_dev/src/generic64/storage.rs | 106 ++++++++++++---- compiler/gen_dev/src/generic64/x86_64.rs | 5 +- compiler/gen_dev/src/object_builder.rs | 9 +- compiler/roc_target/src/lib.rs | 6 + 5 files changed, 132 insertions(+), 139 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 791b88fde8..e2e4ffe825 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -16,8 +16,6 @@ pub mod x86_64; use storage::StorageManager; -const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); - pub trait CallConv { const BASE_PTR_REG: GeneralReg; const STACK_PTR_REG: GeneralReg; @@ -259,6 +257,7 @@ pub struct Backend64Bit< phantom_asm: PhantomData, phantom_cc: PhantomData, env: &'a Env<'a>, + target_info: TargetInfo, interns: &'a mut Interns, helper_proc_gen: CodeGenHelp<'a>, helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>, @@ -307,14 +306,16 @@ pub fn new_backend_64bit< CC: CallConv, >( env: &'a Env, + target_info: TargetInfo, interns: &'a mut Interns, ) -> Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { Backend64Bit { phantom_asm: PhantomData, phantom_cc: PhantomData, env, + target_info, interns, - helper_proc_gen: CodeGenHelp::new(env.arena, TARGET_INFO, env.module_id), + helper_proc_gen: CodeGenHelp::new(env.arena, target_info, env.module_id), helper_proc_symbols: bumpalo::vec![in env.arena], proc_name: None, is_self_recursive: None, @@ -335,7 +336,7 @@ pub fn new_backend_64bit< free_stack_chunks: bumpalo::vec![in env.arena], stack_size: 0, fn_call_stack_size: 0, - storage_manager: storage::new_storage_manager(env), + storage_manager: storage::new_storage_manager(env, target_info), } } @@ -551,7 +552,7 @@ impl< } // Save used caller saved regs. self.storage_manager - .push_used_caller_saved_regs_to_stack(&mut self.buf, self.env.arena); + .push_used_caller_saved_regs_to_stack(&mut self.buf); // Put values in param regs or on top of the stack. let tmp_stack_size = CC::store_args( @@ -576,24 +577,24 @@ impl< let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); } - Layout::Builtin(Builtin::Str) => { - if CC::returns_via_arg_pointer(ret_layout) { - // This will happen on windows, return via pointer here. - todo!("FnCall: Returning strings via pointer"); - } else { - let offset = self.claim_stack_size(16); - self.symbol_storage_map.insert( - *dst, - SymbolStorage::Base { - offset, - size: 16, - owned: true, - }, - ); - ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); - ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); - } - } + // Layout::Builtin(Builtin::Str) => { + // if CC::returns_via_arg_pointer(ret_layout) { + // // This will happen on windows, return via pointer here. + // todo!("FnCall: Returning strings via pointer"); + // } else { + // let offset = self.claim_stack_size(16); + // self.symbol_storage_map.insert( + // *dst, + // SymbolStorage::Base { + // offset, + // size: 16, + // owned: true, + // }, + // ); + // ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); + // ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); + // } + // } Layout::Struct([]) => { // Nothing needs to be done to load a returned empty struct. } @@ -680,8 +681,11 @@ impl< // This section can essentially be seen as a sub function within the main function. // Thus we build using a new backend with some minor extra synchronization. { - let mut sub_backend = - new_backend_64bit::(self.env, self.interns); + let mut sub_backend = new_backend_64bit::( + self.env, + self.target_info, + self.interns, + ); sub_backend.reset( self.proc_name.as_ref().unwrap().clone(), self.is_self_recursive.as_ref().unwrap().clone(), @@ -764,7 +768,7 @@ impl< // Treat this like a function call, but with a jump instead of a call instruction at the end. self.storage_manager - .push_used_caller_saved_regs_to_stack(&mut self.buf, self.env.arena); + .push_used_caller_saved_regs_to_stack(&mut self.buf); let tmp_stack_size = CC::store_args( &mut self.buf, @@ -1014,49 +1018,9 @@ impl< } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { - let struct_size = layout.stack_size(TARGET_INFO); - - if let Layout::Struct(field_layouts) = layout { - if struct_size > 0 { - let offset = self.claim_stack_size(struct_size); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size: struct_size, - owned: true, - }, - ); - - 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(TARGET_INFO); - current_offset += field_size as i32; - } - } else { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset: 0, - size: 0, - owned: false, - }, - ); - } - } else { - // This is a single element struct. Just copy the single field to the stack. - let offset = self.claim_stack_size(struct_size); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset, - size: struct_size, - owned: true, - }, - ); - self.copy_symbol_to_stack_offset(offset, &fields[0], layout); - } + return self + .storage_manager + .create_struct(&mut self.buf, sym, layout, fields); } fn load_struct_at_index( @@ -1069,14 +1033,14 @@ impl< if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) { let mut data_offset = *offset; for i in 0..index { - let field_size = field_layouts[i as usize].stack_size(TARGET_INFO); + let field_size = field_layouts[i as usize].stack_size(self.target_info); data_offset += field_size as i32; } self.symbol_storage_map.insert( *sym, SymbolStorage::Base { offset: data_offset, - size: field_layouts[index as usize].stack_size(TARGET_INFO), + size: field_layouts[index as usize].stack_size(self.target_info), owned: false, }, ); @@ -1319,41 +1283,6 @@ impl< CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - /// claim_stack_size claims `amount` bytes from the stack. - /// This may be free space in the stack or result in increasing the stack size. - /// It returns base pointer relative offset of the new data. - fn claim_stack_size(&mut self, amount: u32) -> i32 { - debug_assert!(amount > 0); - if let Some(fitting_chunk) = self - .free_stack_chunks - .iter() - .enumerate() - .filter(|(_, (_, size))| *size >= amount) - .min_by_key(|(_, (_, size))| size) - { - let (pos, (offset, size)) = fitting_chunk; - let (offset, size) = (*offset, *size); - if size == amount { - self.free_stack_chunks.remove(pos); - offset - } else { - let (prev_offset, prev_size) = self.free_stack_chunks[pos]; - self.free_stack_chunks[pos] = (prev_offset + amount as i32, prev_size - amount); - prev_offset - } - } else 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 { - internal_error!("Ran out of stack space"); - } else { - self.stack_size = new_size; - -(self.stack_size as i32) - } - } else { - internal_error!("Ran out of stack space"); - } - } - fn copy_symbol_to_stack_offset(&mut self, to_offset: i32, sym: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { @@ -1374,10 +1303,10 @@ impl< // // { // // debug_assert_eq!( // // *size, - // // layout.stack_size(TARGET_INFO), + // // layout.stack_size(self.target_info), // // "expected struct to have same size as data being stored in it" // // ); - // // for i in 0..layout.stack_size(TARGET_INFO) as i32 { + // // for i in 0..layout.stack_size(self.target_info) 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); // // } diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index f0f7ee1492..4bf0e8c64e 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,9 +1,11 @@ use crate::generic64::{Assembler, CallConv, RegTrait}; use crate::Env; -use bumpalo::{collections::Vec, Bump}; +use bumpalo::collections::Vec; use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; +use roc_mono::layout::Layout; +use roc_target::TargetInfo; use std::marker::PhantomData; use std::rc::Rc; @@ -19,16 +21,16 @@ enum RegStorage { #[derive(Clone, Debug, PartialEq)] enum StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { - // Small Values are 8 bytes or less. - // They are used for smaller primitives and values that fit in single registers. - // Their data must always be 8 byte aligned. An will be moved as a block. - SmallValue { + // Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. + // Their data must always be 8 byte aligned and will be moved as a block. + // They are never part of a struct, union, or more complex value. + Primitive { // Offset from the base pointer in bytes. base_offset: i32, // Optional register also holding the value. reg: Option>, }, - LargeValue { + Complex { // Offset from the base pointer in bytes. base_offset: i32, // Size on the stack in bytes. @@ -45,6 +47,7 @@ enum StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { enum Storage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { Reg(RegStorage), Stack(StackStorage<'a, GeneralReg, FloatReg>), + NoData, } pub struct StorageManager< @@ -56,6 +59,8 @@ pub struct StorageManager< > { phantom_cc: PhantomData, phantom_asm: PhantomData, + env: &'a Env<'a>, + target_info: TargetInfo, // Data about where each symbol is stored. symbol_storage_map: MutMap>, @@ -93,10 +98,13 @@ pub fn new_storage_manager< CC: CallConv, >( env: &'a Env, + target_info: TargetInfo, ) -> StorageManager<'a, GeneralReg, FloatReg, ASM, CC> { StorageManager { phantom_asm: PhantomData, phantom_cc: PhantomData, + env, + target_info, symbol_storage_map: MutMap::default(), reference_map: MutMap::default(), general_free_regs: bumpalo::vec![in env.arena], @@ -225,7 +233,7 @@ impl< }; match storage { Reg(General(reg)) - | Stack(SmallValue { + | Stack(Primitive { reg: Some(General(reg)), .. }) => { @@ -233,13 +241,13 @@ impl< reg } Reg(Float(_)) - | Stack(SmallValue { + | Stack(Primitive { reg: Some(Float(_)), .. }) => { internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) } - Stack(SmallValue { + Stack(Primitive { reg: None, base_offset, }) => { @@ -249,16 +257,19 @@ impl< self.general_used_regs.push((reg, *sym)); self.symbol_storage_map.insert( *sym, - Stack(SmallValue { + Stack(Primitive { base_offset, reg: Some(General(reg)), }), ); reg } - Stack(LargeValue { .. }) => { + Stack(Complex { .. }) => { internal_error!("Cannot load large values into general registers: {}", sym) } + NoData => { + internal_error!("Cannot load no data into general registers: {}", sym) + } } } @@ -274,7 +285,7 @@ impl< }; match storage { Reg(Float(reg)) - | Stack(SmallValue { + | Stack(Primitive { reg: Some(Float(reg)), .. }) => { @@ -282,13 +293,13 @@ impl< reg } Reg(General(_)) - | Stack(SmallValue { + | Stack(Primitive { reg: Some(General(_)), .. }) => { internal_error!("Cannot load general symbol into FloatReg: {}", sym) } - Stack(SmallValue { + Stack(Primitive { reg: None, base_offset, }) => { @@ -298,16 +309,56 @@ impl< self.float_used_regs.push((reg, *sym)); self.symbol_storage_map.insert( *sym, - Stack(SmallValue { + Stack(Primitive { base_offset, reg: Some(Float(reg)), }), ); reg } - Stack(LargeValue { .. }) => { + Stack(Complex { .. }) => { internal_error!("Cannot load large values into float registers: {}", sym) } + NoData => { + internal_error!("Cannot load no data into general registers: {}", sym) + } + } + } + + // Creates a struct on the stack, moving the data in fields into the struct. + pub fn create_struct( + &mut self, + buf: &mut Vec<'a, u8>, + sym: &Symbol, + layout: &Layout<'a>, + fields: &'a [Symbol], + ) { + let struct_size = layout.stack_size(self.target_info); + if struct_size == 0 { + self.symbol_storage_map.insert(*sym, NoData); + return; + } + let base_offset = self.claim_stack_size(struct_size); + self.symbol_storage_map.insert( + *sym, + Stack(Complex { + base_offset, + size: struct_size, + refs: bumpalo::vec![in self.env.arena], + }), + ); + + if let Layout::Struct(field_layouts) = layout { + let mut current_offset = base_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(self.target_info); + current_offset += field_size as i32; + } + } else { + // This is a single element struct. Just copy the single field to the stack. + debug_assert_eq!(fields.len(), 1); + // self.copy_symbol_to_stack_offset(offset, &fields[0], layout); } } @@ -333,29 +384,29 @@ impl< } self.symbol_storage_map.insert( *sym, - Stack(SmallValue { + Stack(Primitive { base_offset, reg: None, }), ); } - Stack(SmallValue { + Stack(Primitive { reg: Some(reg_storage), base_offset, }) => { debug_assert_eq!(reg_storage, wanted_reg); self.symbol_storage_map.insert( *sym, - Stack(SmallValue { + Stack(Primitive { base_offset, reg: None, }), ); } - Stack(SmallValue { reg: None, .. }) => { + Stack(Primitive { reg: None, .. }) => { internal_error!("Cannot free reg from symbol without a reg: {}", sym) } - Stack(LargeValue { + Stack(Complex { base_offset, size, mut refs, @@ -368,7 +419,7 @@ impl< refs.remove(pos); self.symbol_storage_map.insert( *sym, - Stack(LargeValue { + Stack(Complex { base_offset, size, refs, @@ -380,6 +431,7 @@ impl< } } } + NoData => {} } } @@ -424,9 +476,11 @@ impl< } } - pub fn push_used_caller_saved_regs_to_stack(&mut self, buf: &mut Vec<'a, u8>, arena: &'a Bump) { - let old_general_used_regs = - std::mem::replace(&mut self.general_used_regs, bumpalo::vec![in arena]); + pub fn push_used_caller_saved_regs_to_stack(&mut self, buf: &mut Vec<'a, u8>) { + let old_general_used_regs = std::mem::replace( + &mut self.general_used_regs, + bumpalo::vec![in self.env.arena], + ); for (reg, saved_sym) in old_general_used_regs.into_iter() { if CC::general_caller_saved(®) { self.general_free_regs.push(reg); @@ -436,7 +490,7 @@ impl< } } let old_float_used_regs = - std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in arena]); + std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]); for (reg, saved_sym) in old_float_used_regs.into_iter() { if CC::float_caller_saved(®) { self.float_free_regs.push(reg); diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 359dd4670f..8b43849627 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, TARGET_INFO}; +use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage}; use crate::{ single_register_builtins, single_register_floats, single_register_integers, Relocation, }; @@ -8,6 +8,9 @@ use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; +use roc_target::TargetInfo; + +const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); // Not sure exactly how I want to represent registers. // If we want max speed, we would likely make them structs that impl the same trait to avoid ifs. diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index d8675c880d..eec99ad63f 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -13,6 +13,7 @@ use roc_module::symbol; use roc_module::symbol::Interns; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; +use roc_target::TargetInfo; use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple}; // This is used by some code below which is currently commented out. @@ -38,7 +39,7 @@ pub fn build_module<'a>( x86_64::X86_64FloatReg, x86_64::X86_64Assembler, x86_64::X86_64SystemV, - >(env, interns); + >(env, TargetInfo::default_x86_64(), interns); build_object( procedures, backend, @@ -55,7 +56,7 @@ pub fn build_module<'a>( x86_64::X86_64FloatReg, x86_64::X86_64Assembler, x86_64::X86_64SystemV, - >(env, interns); + >(env, TargetInfo::default_x86_64(), interns); build_object( procedures, backend, @@ -76,7 +77,7 @@ pub fn build_module<'a>( aarch64::AArch64FloatReg, aarch64::AArch64Assembler, aarch64::AArch64Call, - >(env, interns); + >(env, TargetInfo::default_aarch64(), interns); build_object( procedures, backend, @@ -93,7 +94,7 @@ pub fn build_module<'a>( aarch64::AArch64FloatReg, aarch64::AArch64Assembler, aarch64::AArch64Call, - >(env, interns); + >(env, TargetInfo::default_aarch64(), interns); build_object( procedures, backend, diff --git a/compiler/roc_target/src/lib.rs b/compiler/roc_target/src/lib.rs index 68269a3849..5174d4082e 100644 --- a/compiler/roc_target/src/lib.rs +++ b/compiler/roc_target/src/lib.rs @@ -12,6 +12,12 @@ impl TargetInfo { self.architecture.ptr_width() } + pub const fn default_aarch64() -> Self { + TargetInfo { + architecture: Architecture::Aarch64, + } + } + pub const fn default_x86_64() -> Self { TargetInfo { architecture: Architecture::X86_64, From c08b01187508a93d2a2e1919226629129946d622 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 14:55:49 -0800 Subject: [PATCH 12/56] move copy_symbol_to_stack_offset to storage manager --- compiler/gen_dev/src/generic64/mod.rs | 36 --------------- compiler/gen_dev/src/generic64/storage.rs | 55 +++++++++++++++++++++-- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index e2e4ffe825..d48a12fab5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1283,42 +1283,6 @@ impl< CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - fn copy_symbol_to_stack_offset(&mut self, to_offset: i32, sym: &Symbol, layout: &Layout<'a>) { - match layout { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let reg = self.storage_manager.load_to_general_reg(&mut self.buf, sym); - ASM::mov_base32_reg64(&mut self.buf, to_offset, reg); - } - Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let reg = self.storage_manager.load_to_float_reg(&mut self.buf, sym); - ASM::mov_base32_freg64(&mut self.buf, to_offset, reg); - } - // Layout::Struct(_) if layout.safe_to_memcpy() => { - // // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) - // // if let Some(SymbolStorage::Base { - // // offset: from_offset, - // // size, - // // .. - // // }) = self.symbol_storage_map.get(sym) - // // { - // // debug_assert_eq!( - // // *size, - // // layout.stack_size(self.target_info), - // // "expected struct to have same size as data being stored in it" - // // ); - // // for i in 0..layout.stack_size(self.target_info) 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); - // // } - // todo!() - // } else { - // internal_error!("unknown struct: {:?}", sym); - // } - // } - x => todo!("copying data to the stack with layout, {:?}", x), - } - } - // Updates a jump instruction to a new offset and returns the number of bytes written. fn update_jmp_imm32_offset( &mut self, diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 4bf0e8c64e..6cbd288949 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,10 +1,11 @@ use crate::generic64::{Assembler, CallConv, RegTrait}; use crate::Env; use bumpalo::collections::Vec; +use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; -use roc_mono::layout::Layout; +use roc_mono::layout::{Builtin, Layout}; use roc_target::TargetInfo; use std::marker::PhantomData; use std::rc::Rc; @@ -351,14 +352,62 @@ impl< if let Layout::Struct(field_layouts) = layout { let mut current_offset = base_offset; for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { - // self.copy_symbol_to_stack_offset(current_offset, field, field_layout); + self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout); let field_size = field_layout.stack_size(self.target_info); current_offset += field_size as i32; } } else { // This is a single element struct. Just copy the single field to the stack. debug_assert_eq!(fields.len(), 1); - // self.copy_symbol_to_stack_offset(offset, &fields[0], layout); + self.copy_symbol_to_stack_offset(buf, base_offset, &fields[0], layout); + } + } + + // Copies a symbol to the specified stack offset. This is used for things like filling structs. + // The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan. + // This means that, for example 2 I32s might be back to back on the stack. + // Always interact with the stack using aligned 64bit movement. + fn copy_symbol_to_stack_offset( + &mut self, + buf: &mut Vec<'a, u8>, + to_offset: i32, + sym: &Symbol, + layout: &Layout<'a>, + ) { + match layout { + Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg64(buf, to_offset, reg); + } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_float_reg(buf, sym); + ASM::mov_base32_freg64(buf, to_offset, reg); + } + // Layout::Struct(_) if layout.safe_to_memcpy() => { + // // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) + // // if let Some(SymbolStorage::Base { + // // offset: from_offset, + // // size, + // // .. + // // }) = self.symbol_storage_map.get(sym) + // // { + // // debug_assert_eq!( + // // *size, + // // layout.stack_size(self.target_info), + // // "expected struct to have same size as data being stored in it" + // // ); + // // for i in 0..layout.stack_size(self.target_info) 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); + // // } + // todo!() + // } else { + // internal_error!("unknown struct: {:?}", sym); + // } + // } + x => todo!("copying data to the stack with layout, {:?}", x), } } From 895ed17776101b992b10a1f73d8a0acae52434b1 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 14:58:09 -0800 Subject: [PATCH 13/56] add internal error --- compiler/gen_dev/src/generic64/storage.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 6cbd288949..fa38c7f0ed 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -480,7 +480,9 @@ impl< } } } - NoData => {} + NoData => { + internal_error!("Cannot free reg from symbol without a reg: {}", sym) + } } } From 89b6a6cf91de90f8050d5c470ffd34ce6df646b1 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 15:11:24 -0800 Subject: [PATCH 14/56] add todo --- compiler/gen_dev/src/generic64/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index d48a12fab5..a449723ade 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -14,6 +14,15 @@ pub mod aarch64; mod storage; pub mod x86_64; +// TODO: StorageManager is still not fully integrated. +// General pieces needed: +// - loading and Storing args iwht storage manager +// - returning data (note: remove return struct send everything to CC) +// - function call stack? (maybe can stay here) +// - re-enabling some commented out things +// - ensure storage map doesn't leak out of storage, try to make it clean and generic +// - Look into Complex values on the stack and reference. They may not work well. +// - look into fixing join to no longer use multiple backends??? use storage::StorageManager; pub trait CallConv { From fa8d7f78b2c5bc23936c522ded0de622afed61ea Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 15:28:54 -0800 Subject: [PATCH 15/56] move freeing symbols to the storage manager --- compiler/gen_dev/src/generic64/mod.rs | 95 +---------------------- compiler/gen_dev/src/generic64/storage.rs | 93 ++++++++++++++++++++++ 2 files changed, 96 insertions(+), 92 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index a449723ade..0d131334e5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -16,10 +16,11 @@ pub mod x86_64; // TODO: StorageManager is still not fully integrated. // General pieces needed: -// - loading and Storing args iwht storage manager +// - loading and Storing args into storage manager // - returning data (note: remove return struct send everything to CC) // - function call stack? (maybe can stay here) // - re-enabling some commented out things +// - remove data that is duplicated here and in storage manager // - ensure storage map doesn't leak out of storage, try to make it clean and generic // - Look into Complex values on the stack and reference. They may not work well. // - look into fixing join to no longer use multiple backends??? @@ -1120,97 +1121,7 @@ impl< } fn free_symbol(&mut self, sym: &Symbol) { - match self.symbol_storage_map.remove(sym) { - Some( - SymbolStorage::Base { - offset, - size, - owned: true, - } - | SymbolStorage::BaseAndGeneralReg { - offset, - size, - owned: true, - .. - } - | SymbolStorage::BaseAndFloatReg { - offset, - size, - owned: true, - .. - }, - ) => { - let loc = (offset, size); - // Note: this position current points to the offset following the specified location. - // If loc was inserted at this position, it would shift the data at this position over by 1. - let pos = self - .free_stack_chunks - .binary_search(&loc) - .unwrap_or_else(|e| e); - - // Check for overlap with previous and next free chunk. - let merge_with_prev = if pos > 0 { - if let Some((prev_offset, prev_size)) = self.free_stack_chunks.get(pos - 1) { - let prev_end = *prev_offset + *prev_size as i32; - if prev_end > offset { - internal_error!("Double free? A previously freed stack location overlaps with the currently freed stack location."); - } - prev_end == offset - } else { - false - } - } else { - false - }; - let merge_with_next = if let Some((next_offset, _)) = - self.free_stack_chunks.get(pos) - { - let current_end = offset + size as i32; - if current_end > *next_offset { - internal_error!("Double free? A previously freed stack location overlaps with the currently freed stack location."); - } - current_end == *next_offset - } else { - false - }; - - match (merge_with_prev, merge_with_next) { - (true, true) => { - let (prev_offset, prev_size) = self.free_stack_chunks[pos - 1]; - let (_, next_size) = self.free_stack_chunks[pos]; - self.free_stack_chunks[pos - 1] = - (prev_offset, prev_size + size + next_size); - self.free_stack_chunks.remove(pos); - } - (true, false) => { - let (prev_offset, prev_size) = self.free_stack_chunks[pos - 1]; - self.free_stack_chunks[pos - 1] = (prev_offset, prev_size + size); - } - (false, true) => { - let (_, next_size) = self.free_stack_chunks[pos]; - self.free_stack_chunks[pos] = (offset, next_size + size); - } - (false, false) => self.free_stack_chunks.insert(pos, loc), - } - } - Some(_) | None => {} - } - for i in 0..self.general_used_regs.len() { - let (reg, saved_sym) = self.general_used_regs[i]; - if saved_sym == *sym { - self.general_free_regs.push(reg); - self.general_used_regs.remove(i); - break; - } - } - for i in 0..self.float_used_regs.len() { - let (reg, saved_sym) = self.float_used_regs[i]; - if saved_sym == *sym { - self.float_free_regs.push(reg); - self.float_used_regs.remove(i); - break; - } - } + self.storage_manager.free_symbol(sym); } fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) { diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index fa38c7f0ed..8884c2694f 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -527,6 +527,99 @@ impl< } } + pub fn free_symbol(&mut self, sym: &Symbol) { + let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + let rc_sym = if let Some(rc_sym) = self.reference_map.remove(sym) { + rc_sym + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + // Free stack chunck if this is the last reference to the chunk. + Stack(Primitive { base_offset, .. }) if Rc::strong_count(&rc_sym) == 1 => { + self.free_stack_chunk(base_offset, 8); + } + Stack(Complex { + base_offset, size, .. + }) if Rc::strong_count(&rc_sym) == 1 => { + self.free_stack_chunk(base_offset, size); + } + _ => {} + } + for i in 0..self.general_used_regs.len() { + let (reg, saved_sym) = self.general_used_regs[i]; + if saved_sym == *sym { + self.general_free_regs.push(reg); + self.general_used_regs.remove(i); + break; + } + } + for i in 0..self.float_used_regs.len() { + let (reg, saved_sym) = self.float_used_regs[i]; + if saved_sym == *sym { + self.float_free_regs.push(reg); + self.float_used_regs.remove(i); + break; + } + } + } + + fn free_stack_chunk(&mut self, base_offset: i32, size: u32) { + let loc = (base_offset, size); + // Note: this position current points to the offset following the specified location. + // If loc was inserted at this position, it would shift the data at this position over by 1. + let pos = self + .free_stack_chunks + .binary_search(&loc) + .unwrap_or_else(|e| e); + + // Check for overlap with previous and next free chunk. + let merge_with_prev = if pos > 0 { + if let Some((prev_offset, prev_size)) = self.free_stack_chunks.get(pos - 1) { + let prev_end = *prev_offset + *prev_size as i32; + if prev_end > base_offset { + internal_error!("Double free? A previously freed stack location overlaps with the currently freed stack location."); + } + prev_end == base_offset + } else { + false + } + } else { + false + }; + let merge_with_next = if let Some((next_offset, _)) = self.free_stack_chunks.get(pos) { + let current_end = base_offset + size as i32; + if current_end > *next_offset { + internal_error!("Double free? A previously freed stack location overlaps with the currently freed stack location."); + } + current_end == *next_offset + } else { + false + }; + + match (merge_with_prev, merge_with_next) { + (true, true) => { + let (prev_offset, prev_size) = self.free_stack_chunks[pos - 1]; + let (_, next_size) = self.free_stack_chunks[pos]; + self.free_stack_chunks[pos - 1] = (prev_offset, prev_size + size + next_size); + self.free_stack_chunks.remove(pos); + } + (true, false) => { + let (prev_offset, prev_size) = self.free_stack_chunks[pos - 1]; + self.free_stack_chunks[pos - 1] = (prev_offset, prev_size + size); + } + (false, true) => { + let (_, next_size) = self.free_stack_chunks[pos]; + self.free_stack_chunks[pos] = (base_offset, next_size + size); + } + (false, false) => self.free_stack_chunks.insert(pos, loc), + } + } + pub fn push_used_caller_saved_regs_to_stack(&mut self, buf: &mut Vec<'a, u8>) { let old_general_used_regs = std::mem::replace( &mut self.general_used_regs, From f4bb49427d961bbe3115b1a2a0814f6002b160d6 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 15:46:41 -0800 Subject: [PATCH 16/56] remove refs to complex storage with todo to investigate later --- compiler/gen_dev/src/generic64/storage.rs | 48 ++++++----------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 8884c2694f..37c5dd40ab 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -21,7 +21,7 @@ enum RegStorage { } #[derive(Clone, Debug, PartialEq)] -enum StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { +enum StackStorage { // Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. // Their data must always be 8 byte aligned and will be moved as a block. // They are never part of a struct, union, or more complex value. @@ -31,23 +31,25 @@ enum StackStorage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { // Optional register also holding the value. reg: Option>, }, + // Complex data (lists, unions, structs, str) stored on the stack. + // Note, this is also used for referencing a value within a struct/union. + // It has no alignment guarantees. + // When a primitive value is being loaded from this, it should be moved into a register. Complex { // Offset from the base pointer in bytes. base_offset: i32, // Size on the stack in bytes. size: u32, - // Values of this storage currently loaded into registers. - // This is stored in tuples of (offset from start of this storage, register). - // This save on constantly reloading a list pointer for example. - // TODO: add small vec optimization most of the time this should be 0 or 1 items. - refs: Vec<'a, (u32, RegStorage)>, + // TODO: investigate if storing a reg here for special values is worth it. + // For example, the ptr in list.get/list.set + // Instead, it would probably be better to change the incoming IR to load the pointer once and then use it multiple times. }, } #[derive(Clone, Debug, PartialEq)] -enum Storage<'a, GeneralReg: RegTrait, FloatReg: RegTrait> { +enum Storage { Reg(RegStorage), - Stack(StackStorage<'a, GeneralReg, FloatReg>), + Stack(StackStorage), NoData, } @@ -63,7 +65,7 @@ pub struct StorageManager< env: &'a Env<'a>, target_info: TargetInfo, // Data about where each symbol is stored. - symbol_storage_map: MutMap>, + symbol_storage_map: MutMap>, // A map from child to parent storage. // In the case that subdata is still referenced from an overall structure, @@ -345,7 +347,6 @@ impl< Stack(Complex { base_offset, size: struct_size, - refs: bumpalo::vec![in self.env.arena], }), ); @@ -455,32 +456,7 @@ impl< Stack(Primitive { reg: None, .. }) => { internal_error!("Cannot free reg from symbol without a reg: {}", sym) } - Stack(Complex { - base_offset, - size, - mut refs, - }) => { - match refs - .iter() - .position(|(_, reg_storage)| *reg_storage == wanted_reg) - { - Some(pos) => { - refs.remove(pos); - self.symbol_storage_map.insert( - *sym, - Stack(Complex { - base_offset, - size, - refs, - }), - ); - } - None => { - internal_error!("Cannot free reg from symbol without a reg: {}", sym) - } - } - } - NoData => { + NoData | Stack(Complex { .. }) => { internal_error!("Cannot free reg from symbol without a reg: {}", sym) } } From aa1c0c11b284ef1448b72e239accd683c0812ef6 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 16:32:28 -0800 Subject: [PATCH 17/56] add referenced primitives and move loading struct fields to storage manager --- compiler/gen_dev/src/generic64/mod.rs | 21 +--- compiler/gen_dev/src/generic64/storage.rs | 126 ++++++++++++++++++---- compiler/gen_dev/src/generic64/x86_64.rs | 8 +- 3 files changed, 111 insertions(+), 44 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 0d131334e5..9f17d1558c 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1040,23 +1040,8 @@ impl< index: u64, field_layouts: &'a [Layout<'a>], ) { - if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) { - let mut data_offset = *offset; - for i in 0..index { - let field_size = field_layouts[i as usize].stack_size(self.target_info); - data_offset += field_size as i32; - } - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Base { - offset: data_offset, - size: field_layouts[index as usize].stack_size(self.target_info), - owned: false, - }, - ); - } else { - internal_error!("unknown struct: {:?}", structure); - } + self.storage_manager + .load_field_at_index(sym, structure, index, field_layouts); } fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) { @@ -1247,7 +1232,7 @@ macro_rules! single_register_floats { } #[macro_export] -macro_rules! single_register_builtins { +macro_rules! single_register_layouts { () => { single_register_integers!() | single_register_floats!() }; diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 37c5dd40ab..a454a538d7 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,5 +1,7 @@ -use crate::generic64::{Assembler, CallConv, RegTrait}; -use crate::Env; +use crate::{ + generic64::{Assembler, CallConv, RegTrait}, + single_register_floats, single_register_integers, single_register_layouts, Env, +}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; @@ -25,12 +27,23 @@ enum StackStorage { // Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. // Their data must always be 8 byte aligned and will be moved as a block. // They are never part of a struct, union, or more complex value. + // The rest of the bytes should be zero due to how these are loaded. Primitive { // Offset from the base pointer in bytes. base_offset: i32, // Optional register also holding the value. reg: Option>, }, + // Referenced Primitives are primitives within a complex structure. + // They have no guarentees about alignment or zeroed bits. + // When they are loaded, they should be aligned and zeroed. + // After loading, they should just be stored in a register. + ReferencedPrimitive { + // Offset from the base pointer in bytes. + base_offset: i32, + // Size on the stack in bytes. + size: u32, + }, // Complex data (lists, unions, structs, str) stored on the stack. // Note, this is also used for referencing a value within a struct/union. // It has no alignment guarantees. @@ -67,12 +80,12 @@ pub struct StorageManager< // Data about where each symbol is stored. symbol_storage_map: MutMap>, - // A map from child to parent storage. + // A map from symbol to its owning allocation. + // This is only used for complex data on the stack and its references. // In the case that subdata is still referenced from an overall structure, // We can't free the entire structure until the subdata is no longer needed. - // If a symbol has no parent, it points to itself. - // This in only used for data on the stack. - reference_map: MutMap>, + // If a symbol has only one reference, we can free it. + allocation_map: MutMap>, // This should probably be smarter than a vec. // There are certain registers we should always use first. With pushing and popping, this could get mixed. @@ -109,7 +122,7 @@ pub fn new_storage_manager< env, target_info, symbol_storage_map: MutMap::default(), - reference_map: MutMap::default(), + allocation_map: MutMap::default(), general_free_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena], general_used_callee_saved_regs: MutSet::default(), @@ -131,7 +144,7 @@ impl< { pub fn reset(&mut self) { self.symbol_storage_map.clear(); - self.reference_map.clear(); + self.allocation_map.clear(); self.general_used_callee_saved_regs.clear(); self.general_free_regs.clear(); self.general_used_regs.clear(); @@ -267,6 +280,9 @@ impl< ); reg } + Stack(ReferencedPrimitive { .. }) => { + todo!("loading referenced primitives") + } Stack(Complex { .. }) => { internal_error!("Cannot load large values into general registers: {}", sym) } @@ -319,6 +335,9 @@ impl< ); reg } + Stack(ReferencedPrimitive { .. }) => { + todo!("loading referenced primitives") + } Stack(Complex { .. }) => { internal_error!("Cannot load large values into float registers: {}", sym) } @@ -328,6 +347,65 @@ impl< } } + // Loads a field from a struct or tag union. + // This is lazy by default. It will not copy anything around. + pub fn load_field_at_index( + &mut self, + sym: &Symbol, + structure: &Symbol, + index: u64, + field_layouts: &'a [Layout<'a>], + ) { + debug_assert!(index < field_layouts.len()); + let storage = if let Some(storage) = self.symbol_storage_map.get(structure) { + storage + } else { + internal_error!("Unknown symbol: {}", structure); + }; + // This must be removed and reinserted for ownership and mutability reasons. + let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) { + owned_data + } else { + internal_error!("Unknown symbol: {}", structure); + }; + self.allocation_map + .insert(*structure, Rc::clone(&owned_data)); + match storage { + Stack(Complex { base_offset, size }) => { + let (base_offset, size) = (*base_offset, *size); + let mut data_offset = base_offset; + for i in 0..index as usize { + let field_size = field_layouts[i].stack_size(self.target_info); + data_offset += field_size as i32; + } + debug_assert!(data_offset < base_offset + size as i32); + self.allocation_map.insert(*sym, owned_data); + let layout = field_layouts[index as usize]; + let size = layout.stack_size(self.target_info); + self.symbol_storage_map.insert( + *sym, + Stack(if is_primitive(&layout) { + ReferencedPrimitive { + base_offset: data_offset, + size, + } + } else { + Complex { + base_offset: data_offset, + size, + } + }), + ); + } + _ => { + internal_error!( + "Cannot load field from data with storage type: {:?}", + storage + ); + } + } + } + // Creates a struct on the stack, moving the data in fields into the struct. pub fn create_struct( &mut self, @@ -349,6 +427,8 @@ impl< size: struct_size, }), ); + self.allocation_map + .insert(*sym, Rc::new((base_offset, struct_size))); if let Layout::Struct(field_layouts) = layout { let mut current_offset = base_offset; @@ -453,10 +533,8 @@ impl< }), ); } - Stack(Primitive { reg: None, .. }) => { - internal_error!("Cannot free reg from symbol without a reg: {}", sym) - } - NoData | Stack(Complex { .. }) => { + NoData + | Stack(Complex { .. } | Primitive { reg: None, .. } | ReferencedPrimitive { .. }) => { internal_error!("Cannot free reg from symbol without a reg: {}", sym) } } @@ -509,20 +587,20 @@ impl< } else { internal_error!("Unknown symbol: {}", sym); }; - let rc_sym = if let Some(rc_sym) = self.reference_map.remove(sym) { - rc_sym - } else { - internal_error!("Unknown symbol: {}", sym); - }; match storage { // Free stack chunck if this is the last reference to the chunk. - Stack(Primitive { base_offset, .. }) if Rc::strong_count(&rc_sym) == 1 => { + Stack(Primitive { base_offset, .. }) => { self.free_stack_chunk(base_offset, 8); } - Stack(Complex { - base_offset, size, .. - }) if Rc::strong_count(&rc_sym) == 1 => { - self.free_stack_chunk(base_offset, size); + Stack(Complex { .. } | ReferencedPrimitive { .. }) => { + let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { + owned_data + } else { + internal_error!("Unknown symbol: {}", sym); + }; + if Rc::strong_count(&owned_data) == 1 { + self.free_stack_chunk(owned_data.0, owned_data.1); + } } _ => {} } @@ -621,3 +699,7 @@ impl< } } } + +fn is_primitive<'a>(layout: &Layout<'a>) -> bool { + matches!(layout, single_register_layouts!()) +} diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 8b43849627..164f06a3a1 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,6 +1,6 @@ use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage}; use crate::{ - single_register_builtins, single_register_floats, single_register_integers, Relocation, + single_register_floats, single_register_integers, single_register_layouts, Relocation, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; @@ -294,7 +294,7 @@ impl CallConv for X86_64SystemV { // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { - single_register_builtins!() | Layout::Builtin(Builtin::Str) | Layout::Struct([]) => { + single_register_layouts!() | Layout::Builtin(Builtin::Str) | Layout::Struct([]) => { // Nothing needs to be done for any of these cases. } x => { @@ -613,7 +613,7 @@ impl CallConv for X86_64WindowsFastcall { } } else { arg_offset += match layout { - single_register_builtins!() => 8, + single_register_layouts!() => 8, x => { todo!("Loading args with layout {:?}", x); } @@ -643,7 +643,7 @@ impl CallConv for X86_64WindowsFastcall { // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { - single_register_builtins!() | Layout::Struct([]) => { + single_register_layouts!() | Layout::Struct([]) => { // Nothing needs to be done for any of these cases. } x => { From d33c02febdcf2bf2211d9f673e953f45caadd863 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 16:43:12 -0800 Subject: [PATCH 18/56] fix compilation bug and expand enum derives --- compiler/gen_dev/src/generic64/storage.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index a454a538d7..22567b0dfa 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -16,13 +16,13 @@ use RegStorage::*; use StackStorage::*; use Storage::*; -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] enum RegStorage { General(GeneralReg), Float(FloatReg), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] enum StackStorage { // Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. // Their data must always be 8 byte aligned and will be moved as a block. @@ -59,7 +59,7 @@ enum StackStorage { }, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Storage { Reg(RegStorage), Stack(StackStorage), @@ -356,7 +356,7 @@ impl< index: u64, field_layouts: &'a [Layout<'a>], ) { - debug_assert!(index < field_layouts.len()); + debug_assert!(index < field_layouts.len() as u64); let storage = if let Some(storage) = self.symbol_storage_map.get(structure) { storage } else { From e71da49dd11aaf70e483ecfc04fd794a6c084f8b Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 17:47:25 -0800 Subject: [PATCH 19/56] re-add loading small string literals --- compiler/gen_dev/src/generic64/mod.rs | 47 ++++++++++------------- compiler/gen_dev/src/generic64/storage.rs | 25 +++++++----- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 9f17d1558c..1d9e5e02dd 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1073,34 +1073,29 @@ impl< let val = *x as f32; ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val); } - // (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 16 => { - // Load small string. - // let reg = self.get_tmp_general_reg(); + (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 16 => { + // Load small string. + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, reg| { + let base_offset = storage_manager.claim_stack_area(sym, 16); + let mut bytes = [0; 16]; + bytes[..x.len()].copy_from_slice(x.as_bytes()); + bytes[15] = (x.len() as u8) | 0b1000_0000; - // let offset = self.claim_stack_size(16); - // self.symbol_storage_map.insert( - // *sym, - // SymbolStorage::Base { - // offset, - // size: 16, - // owned: true, - // }, - // ); - // let mut bytes = [0; 16]; - // bytes[..x.len()].copy_from_slice(x.as_bytes()); - // bytes[15] = (x.len() as u8) | 0b1000_0000; + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset, reg); - // let mut num_bytes = [0; 8]; - // num_bytes.copy_from_slice(&bytes[..8]); - // let num = i64::from_ne_bytes(num_bytes); - // ASM::mov_reg64_imm64(&mut self.buf, reg, num); - // ASM::mov_base32_reg64(&mut self.buf, offset, reg); - - // num_bytes.copy_from_slice(&bytes[8..]); - // let num = i64::from_ne_bytes(num_bytes); - // ASM::mov_reg64_imm64(&mut self.buf, reg, num); - // ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); - // } + num_bytes.copy_from_slice(&bytes[8..]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + }, + ); + } x => todo!("loading literal, {:?}", x), } } diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 22567b0dfa..2320221929 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -48,6 +48,7 @@ enum StackStorage { // Note, this is also used for referencing a value within a struct/union. // It has no alignment guarantees. // When a primitive value is being loaded from this, it should be moved into a register. + // To start, the primitive can just be loaded as a ReferencePrimitive. Complex { // Offset from the base pointer in bytes. base_offset: i32, @@ -419,16 +420,7 @@ impl< self.symbol_storage_map.insert(*sym, NoData); return; } - let base_offset = self.claim_stack_size(struct_size); - self.symbol_storage_map.insert( - *sym, - Stack(Complex { - base_offset, - size: struct_size, - }), - ); - self.allocation_map - .insert(*sym, Rc::new((base_offset, struct_size))); + let base_offset = self.claim_stack_area(sym, struct_size); if let Layout::Struct(field_layouts) = layout { let mut current_offset = base_offset; @@ -540,6 +532,19 @@ impl< } } + /// claim_stack_area is the public wrapper around claim_stack_size. + /// It also deals with updating symbol storage. + /// It returns the base offset of the stack area. + /// It should only be used for complex data and not primitives. + pub fn claim_stack_area(&mut self, sym: &Symbol, size: u32) -> i32 { + let base_offset = self.claim_stack_size(size); + self.symbol_storage_map + .insert(*sym, Stack(Complex { base_offset, size })); + self.allocation_map + .insert(*sym, Rc::new((base_offset, size))); + base_offset + } + /// claim_stack_size claims `amount` bytes from the stack alignind to 8. /// This may be free space in the stack or result in increasing the stack size. /// It returns base pointer relative offset of the new data. From bf6e825e252015860de6130ee45fbfcc69407b95 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 17:55:29 -0800 Subject: [PATCH 20/56] make clippy happier --- compiler/gen_dev/src/generic64/mod.rs | 3 +-- compiler/gen_dev/src/generic64/storage.rs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 1d9e5e02dd..f9ed576ba9 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1028,8 +1028,7 @@ impl< } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { - return self - .storage_manager + self.storage_manager .create_struct(&mut self.buf, sym, layout, fields); } diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 2320221929..4892013d87 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -375,8 +375,8 @@ impl< Stack(Complex { base_offset, size }) => { let (base_offset, size) = (*base_offset, *size); let mut data_offset = base_offset; - for i in 0..index as usize { - let field_size = field_layouts[i].stack_size(self.target_info); + for layout in field_layouts.iter().take(index as usize) { + let field_size = layout.stack_size(self.target_info); data_offset += field_size as i32; } debug_assert!(data_offset < base_offset + size as i32); @@ -705,6 +705,6 @@ impl< } } -fn is_primitive<'a>(layout: &Layout<'a>) -> bool { +fn is_primitive(layout: &Layout<'_>) -> bool { matches!(layout, single_register_layouts!()) } From 77120cb063a60fe8ffce98ee3e65808d95a8fa17 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 20:46:36 -0800 Subject: [PATCH 21/56] enable returning basic symbols with storage manager --- compiler/gen_dev/src/generic64/aarch64.rs | 19 +++- compiler/gen_dev/src/generic64/mod.rs | 115 +++++++++------------ compiler/gen_dev/src/generic64/storage.rs | 116 +++++++++++++++++++++- compiler/gen_dev/src/generic64/x86_64.rs | 103 ++++++++++++++++++- 4 files changed, 275 insertions(+), 78 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 591c799052..2de5b9f8f1 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -1,4 +1,4 @@ -use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage}; +use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait, SymbolStorage}; use crate::Relocation; use bumpalo::collections::Vec; use packed_struct::prelude::*; @@ -75,7 +75,7 @@ pub struct AArch64Call {} const STACK_ALIGNMENT: u8 = 16; -impl CallConv for AArch64Call { +impl CallConv for AArch64Call { const BASE_PTR_REG: AArch64GeneralReg = AArch64GeneralReg::FP; const STACK_PTR_REG: AArch64GeneralReg = AArch64GeneralReg::ZRSP; @@ -268,6 +268,21 @@ impl CallConv for AArch64Call { todo!("Storing args for AArch64"); } + fn return_complex_symbol<'a>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager< + 'a, + AArch64GeneralReg, + AArch64FloatReg, + AArch64Assembler, + AArch64Call, + >, + sym: &Symbol, + layout: &Layout<'a>, + ) { + todo!("Returning complex symbols for AArch64"); + } + fn return_struct<'a>( _buf: &mut Vec<'a, u8>, _struct_offset: i32, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index f9ed576ba9..12a7e41ef5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1,4 +1,7 @@ -use crate::{Backend, Env, Relocation}; +use crate::{ + single_register_floats, single_register_integers, single_register_layouts, Backend, Env, + Relocation, +}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; @@ -10,9 +13,9 @@ use roc_mono::layout::{Builtin, Layout}; use roc_target::TargetInfo; use std::marker::PhantomData; -pub mod aarch64; -mod storage; -pub mod x86_64; +pub(crate) mod aarch64; +pub(crate) mod storage; +pub(crate) mod x86_64; // TODO: StorageManager is still not fully integrated. // General pieces needed: @@ -26,7 +29,9 @@ pub mod x86_64; // - look into fixing join to no longer use multiple backends??? use storage::StorageManager; -pub trait CallConv { +pub trait CallConv>: + Sized +{ const BASE_PTR_REG: GeneralReg; const STACK_PTR_REG: GeneralReg; @@ -53,12 +58,14 @@ pub trait CallConv { fn setup_stack<'a>( buf: &mut Vec<'a, u8>, + // TODO: This should deal with float regs as well. general_saved_regs: &[GeneralReg], requested_stack_size: i32, fn_call_stack_size: i32, ) -> i32; fn cleanup_stack<'a>( buf: &mut Vec<'a, u8>, + // TODO: This should deal with float regs as well. general_saved_regs: &[GeneralReg], aligned_stack_size: i32, fn_call_stack_size: i32, @@ -86,6 +93,15 @@ pub trait CallConv { ret_layout: &Layout<'a>, ) -> u32; + /// return_complex_symbol returns the specified complex/non-primative symbol. + /// It uses the layout to determine how the data should be returned. + fn return_complex_symbol<'a>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, + sym: &Symbol, + layout: &Layout<'a>, + ); + // 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>( @@ -106,7 +122,7 @@ pub trait CallConv { /// Thus, some backends will need to use mulitiple instructions to preform a single one of this calls. /// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`. /// dst should always come before sources. -pub trait Assembler { +pub trait Assembler: Sized { fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn abs_freg64_freg64( buf: &mut Vec<'_, u8>, @@ -262,7 +278,7 @@ pub struct Backend64Bit< GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, > { phantom_asm: PhantomData, phantom_cc: PhantomData, @@ -313,7 +329,7 @@ pub fn new_backend_64bit< GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, >( env: &'a Env, target_info: TargetInfo, @@ -355,7 +371,7 @@ impl< GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, > Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { fn env(&self) -> &Env<'a> { @@ -1104,71 +1120,30 @@ impl< } fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) { - let val = self.symbol_storage_map.get(sym); - match val { - Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {} - Some(SymbolStorage::GeneralReg(reg)) => { - // If it fits in a general purpose register, just copy it over to. - // Technically this can be optimized to produce shorter instructions if less than 64bits. - ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg); - } - Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => {} - Some(SymbolStorage::FloatReg(reg)) => { - ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); - } - Some(SymbolStorage::Base { offset, size, .. }) => match layout { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); + if self.storage_manager.is_stored_primitive(sym) { + // Just load it to the correct type of reg as a stand alone value. + match layout { + single_register_integers!() => { + self.storage_manager.load_to_specified_general_reg( + &mut self.buf, + sym, + CC::GENERAL_RETURN_REGS[0], + ); } - Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); + single_register_floats!() => { + self.storage_manager.load_to_specified_float_reg( + &mut self.buf, + sym, + CC::FLOAT_RETURN_REGS[0], + ); } - Layout::Builtin(Builtin::Str) => { - if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { - // This will happen on windows, return via pointer here. - todo!("Returning strings via pointer"); - } else { - ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); - ASM::mov_reg64_base32( - &mut self.buf, - CC::GENERAL_RETURN_REGS[1], - *offset + 8, - ); - } + _ => { + internal_error!("All primitive valuse should fit in a single register"); } - Layout::Struct(field_layouts) => { - let (offset, size) = (*offset, *size); - // Nothing to do for empty struct - if size > 0 { - let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) - { - Some( - self.storage_manager - .load_to_general_reg(&mut self.buf, &Symbol::RET_POINTER), - ) - } else { - None - }; - CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg); - } - } - x => todo!("returning symbol with layout, {:?}", x), - }, - Some(x) => todo!("returning symbol storage, {:?}", x), - None if layout == &Layout::Struct(&[]) => { - // Empty struct is not defined and does nothing. - } - None => { - internal_error!("Unknown return symbol: {:?}", sym); } + return; } - let inst_loc = self.buf.len() as u64; - let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64; - self.relocs.push(Relocation::JmpToReturn { - inst_loc, - inst_size: self.buf.len() as u64 - inst_loc, - offset, - }); + CC::return_complex_symbol(&mut self.buf, &mut self.storage_manager, sym, layout) } } @@ -1179,7 +1154,7 @@ impl< FloatReg: RegTrait, GeneralReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { // Updates a jump instruction to a new offset and returns the number of bytes written. diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 4892013d87..804ba41a01 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -72,7 +72,7 @@ pub struct StorageManager< GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, > { phantom_cc: PhantomData, phantom_asm: PhantomData, @@ -112,7 +112,7 @@ pub fn new_storage_manager< GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, >( env: &'a Env, target_info: TargetInfo, @@ -140,7 +140,7 @@ impl< FloatReg: RegTrait, GeneralReg: RegTrait, ASM: Assembler, - CC: CallConv, + CC: CallConv, > StorageManager<'a, GeneralReg, FloatReg, ASM, CC> { pub fn reset(&mut self) { @@ -160,6 +160,19 @@ impl< self.stack_size = 0; } + // Returns true if the symbol is storing a primitive value. + pub fn is_stored_primitive(&self, sym: &Symbol) -> bool { + let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + matches!( + storage, + Reg(_) | Stack(Primitive { .. } | ReferencedPrimitive { .. }) + ) + } + // Get a general register from the free list. // Will free data to the stack if necessary to get the register. fn get_general_reg(&mut self, buf: &mut Vec<'a, u8>) -> GeneralReg { @@ -348,6 +361,103 @@ impl< } } + // Loads the symbol to the specified register. + // It will fail if the symbol is stored in a float register. + // This is only made to be used in special cases where exact regs are needed (function args and returns). + // It will not try to free the register first. + // This will not track the symbol change (it makes no assumptions about the new reg). + pub fn load_to_specified_general_reg( + &self, + buf: &mut Vec<'a, u8>, + sym: &Symbol, + reg: GeneralReg, + ) { + let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + Reg(General(old_reg)) + | Stack(Primitive { + reg: Some(General(old_reg)), + .. + }) => { + debug_assert_ne!(*old_reg, reg); + ASM::mov_reg64_reg64(buf, reg, *old_reg); + } + Reg(Float(_)) + | Stack(Primitive { + reg: Some(Float(_)), + .. + }) => { + internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) + } + Stack(Primitive { + reg: None, + base_offset, + }) => { + debug_assert_eq!(base_offset % 8, 0); + ASM::mov_reg64_base32(buf, reg, *base_offset); + } + Stack(ReferencedPrimitive { .. }) => { + todo!("loading referenced primitives") + } + Stack(Complex { .. }) => { + internal_error!("Cannot load large values into general registers: {}", sym) + } + NoData => { + internal_error!("Cannot load no data into general registers: {}", sym) + } + } + } + + // Loads the symbol to the specified register. + // It will fail if the symbol is stored in a general register. + // This is only made to be used in special cases where exact regs are needed (function args and returns). + // It will not try to free the register first. + // This will not track the symbol change (it makes no assumptions about the new reg). + pub fn load_to_specified_float_reg(&self, buf: &mut Vec<'a, u8>, sym: &Symbol, reg: FloatReg) { + let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + Reg(Float(old_reg)) + | Stack(Primitive { + reg: Some(Float(old_reg)), + .. + }) => { + debug_assert_ne!(*old_reg, reg); + ASM::mov_freg64_freg64(buf, reg, *old_reg); + } + Reg(General(_)) + | Stack(Primitive { + reg: Some(General(_)), + .. + }) => { + internal_error!("Cannot load general symbol into FloatReg: {}", sym) + } + Stack(Primitive { + reg: None, + base_offset, + }) => { + debug_assert_eq!(base_offset % 8, 0); + ASM::mov_freg64_base32(buf, reg, *base_offset); + } + Stack(ReferencedPrimitive { .. }) => { + todo!("loading referenced primitives") + } + Stack(Complex { .. }) => { + internal_error!("Cannot load large values into float registers: {}", sym) + } + NoData => { + internal_error!("Cannot load no data into general registers: {}", sym) + } + } + } + // Loads a field from a struct or tag union. // This is lazy by default. It will not copy anything around. pub fn load_field_at_index( diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 164f06a3a1..b8e164cbab 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::{storage::StorageManager, Assembler, CallConv, RegTrait, SymbolStorage}; use crate::{ single_register_floats, single_register_integers, single_register_layouts, Relocation, }; @@ -70,7 +70,7 @@ pub struct X86_64SystemV {} const STACK_ALIGNMENT: u8 = 16; -impl CallConv for X86_64SystemV { +impl CallConv for X86_64SystemV { const BASE_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RBP; const STACK_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RSP; @@ -441,6 +441,88 @@ impl CallConv for X86_64SystemV { stack_offset as u32 } + fn return_complex_symbol<'a>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64SystemV, + >, + sym: &Symbol, + layout: &Layout<'a>, + ) { + // Complex types. + // let val = self.symbol_storage_map.get(sym); + // match val { + // Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {} + // Some(SymbolStorage::GeneralReg(reg)) => { + // // If it fits in a general purpose register, just copy it over to. + // // Technically this can be optimized to produce shorter instructions if less than 64bits. + // ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg); + // } + // Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => {} + // Some(SymbolStorage::FloatReg(reg)) => { + // ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); + // } + // Some(SymbolStorage::Base { offset, size, .. }) => match layout { + // Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + // ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); + // } + // Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + // ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); + // } + // Layout::Builtin(Builtin::Str) => { + // if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { + // // This will happen on windows, return via pointer here. + // todo!("Returning strings via pointer"); + // } else { + // ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); + // ASM::mov_reg64_base32( + // &mut self.buf, + // CC::GENERAL_RETURN_REGS[1], + // *offset + 8, + // ); + // } + // } + // Layout::Struct(field_layouts) => { + // let (offset, size) = (*offset, *size); + // // Nothing to do for empty struct + // if size > 0 { + // let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) + // { + // Some( + // self.storage_manager + // .load_to_general_reg(&mut self.buf, &Symbol::RET_POINTER), + // ) + // } else { + // None + // }; + // CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg); + // } + // } + // x => todo!("returning symbol with layout, {:?}", x), + // }, + // Some(x) => todo!("returning symbol storage, {:?}", x), + // None if layout == &Layout::Struct(&[]) => { + // // Empty struct is not defined and does nothing. + // } + // None => { + // internal_error!("Unknown return symbol: {:?}", sym); + // } + // } + // let inst_loc = self.buf.len() as u64; + // let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64; + // self.relocs.push(Relocation::JmpToReturn { + // inst_loc, + // inst_size: self.buf.len() as u64 - inst_loc, + // offset, + // }); + // } + todo!("Returning complex symbols for X86_64"); + } + fn return_struct<'a>( _buf: &mut Vec<'a, u8>, _struct_offset: i32, @@ -458,7 +540,7 @@ impl CallConv for X86_64SystemV { } } -impl CallConv for X86_64WindowsFastcall { +impl CallConv for X86_64WindowsFastcall { const BASE_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RBP; const STACK_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RSP; @@ -765,6 +847,21 @@ impl CallConv for X86_64WindowsFastcall { stack_offset as u32 } + fn return_complex_symbol<'a>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64WindowsFastcall, + >, + sym: &Symbol, + layout: &Layout<'a>, + ) { + todo!("Returning symbols for X86_64"); + } + fn return_struct<'a>( _buf: &mut Vec<'a, u8>, _struct_offset: i32, From 55c6c9abadf76823dd9fb261af7e26fdc9c0f36d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 20:48:48 -0800 Subject: [PATCH 22/56] fix wrong assumption --- compiler/gen_dev/src/generic64/storage.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 804ba41a01..c4552e5761 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -383,7 +383,9 @@ impl< reg: Some(General(old_reg)), .. }) => { - debug_assert_ne!(*old_reg, reg); + if *old_reg == reg { + return; + } ASM::mov_reg64_reg64(buf, reg, *old_reg); } Reg(Float(_)) @@ -429,7 +431,9 @@ impl< reg: Some(Float(old_reg)), .. }) => { - debug_assert_ne!(*old_reg, reg); + if *old_reg == reg { + return; + } ASM::mov_freg64_freg64(buf, reg, *old_reg); } Reg(General(_)) From cb64543476d5a39cefea22c3d00b6089fbe1971b Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 21:08:28 -0800 Subject: [PATCH 23/56] add basic returned value loading --- compiler/gen_dev/src/generic64/aarch64.rs | 25 +++++---- compiler/gen_dev/src/generic64/mod.rs | 44 ++++++--------- compiler/gen_dev/src/generic64/x86_64.rs | 65 +++++++++++++++-------- 3 files changed, 74 insertions(+), 60 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 2de5b9f8f1..3410b0fb20 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -269,28 +269,33 @@ impl CallConv for AArch64C } fn return_complex_symbol<'a>( - buf: &mut Vec<'a, u8>, - storage_manager: &mut StorageManager< + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager< 'a, AArch64GeneralReg, AArch64FloatReg, AArch64Assembler, AArch64Call, >, - sym: &Symbol, - layout: &Layout<'a>, + _sym: &Symbol, + _layout: &Layout<'a>, ) { todo!("Returning complex symbols for AArch64"); } - fn return_struct<'a>( + fn load_returned_complex_symbol<'a>( _buf: &mut Vec<'a, u8>, - _struct_offset: i32, - _struct_size: u32, - _field_layouts: &[Layout<'a>], - _ret_reg: Option, + _storage_manager: &mut StorageManager< + 'a, + AArch64GeneralReg, + AArch64FloatReg, + AArch64Assembler, + AArch64Call, + >, + _sym: &Symbol, + _layout: &Layout<'a>, ) { - todo!("Returning structs for AArch64"); + todo!("Loading returned complex symbols for AArch64"); } fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool { diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 12a7e41ef5..07048a46de 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -102,14 +102,13 @@ pub trait CallConv, ); - // 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>( + /// load_returned_complex_symbol loads a complex symbol that was returned from a function call. + /// It uses the layout to determine how the data should be loaded into the symbol. + fn load_returned_complex_symbol<'a>( buf: &mut Vec<'a, u8>, - struct_offset: i32, - struct_size: u32, - field_layouts: &[Layout<'a>], - ret_reg: Option, + storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, + sym: &Symbol, + layout: &Layout<'a>, ); // returns true if the layout should be returned via an argument pointer. @@ -595,36 +594,25 @@ impl< // move return value to dst. match ret_layout { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64) | Builtin::Bool) => { + single_register_integers!() => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); } - Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + single_register_floats!() => { let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); } - // Layout::Builtin(Builtin::Str) => { - // if CC::returns_via_arg_pointer(ret_layout) { - // // This will happen on windows, return via pointer here. - // todo!("FnCall: Returning strings via pointer"); - // } else { - // let offset = self.claim_stack_size(16); - // self.symbol_storage_map.insert( - // *dst, - // SymbolStorage::Base { - // offset, - // size: 16, - // owned: true, - // }, - // ); - // ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); - // ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); - // } - // } Layout::Struct([]) => { // Nothing needs to be done to load a returned empty struct. } - x => todo!("FnCall: receiving return type, {:?}", x), + _ => { + CC::load_returned_complex_symbol( + &mut self.buf, + &mut self.storage_manager, + dst, + ret_layout, + ); + } } } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index b8e164cbab..e333f02142 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -442,16 +442,16 @@ impl CallConv for X86_64Syste } fn return_complex_symbol<'a>( - buf: &mut Vec<'a, u8>, - storage_manager: &mut StorageManager< + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager< 'a, X86_64GeneralReg, X86_64FloatReg, X86_64Assembler, X86_64SystemV, >, - sym: &Symbol, - layout: &Layout<'a>, + _sym: &Symbol, + _layout: &Layout<'a>, ) { // Complex types. // let val = self.symbol_storage_map.get(sym); @@ -523,14 +523,30 @@ impl CallConv for X86_64Syste todo!("Returning complex symbols for X86_64"); } - fn return_struct<'a>( - _buf: &mut Vec<'a, u8>, - _struct_offset: i32, - _struct_size: u32, - _field_layouts: &[Layout<'a>], - _ret_reg: Option, + fn load_returned_complex_symbol<'a>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64SystemV, + >, + sym: &Symbol, + layout: &Layout<'a>, ) { - todo!("Returning structs for X86_64"); + match layout { + single_register_layouts!() => { + internal_error!("single register layouts are not complex symbols"); + } + Layout::Struct([]) => {} + Layout::Builtin(Builtin::Str | Builtin::List(_)) => { + let offset = storage_manager.claim_stack_area(sym, 16); + X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); + X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]); + } + x => todo!("receiving complex return type, {:?}", x), + } } fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { @@ -848,28 +864,33 @@ impl CallConv for X86_64Windo } fn return_complex_symbol<'a>( - buf: &mut Vec<'a, u8>, - storage_manager: &mut StorageManager< + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager< 'a, X86_64GeneralReg, X86_64FloatReg, X86_64Assembler, X86_64WindowsFastcall, >, - sym: &Symbol, - layout: &Layout<'a>, + _sym: &Symbol, + _layout: &Layout<'a>, ) { - todo!("Returning symbols for X86_64"); + todo!("Returning complex symbols for X86_64"); } - fn return_struct<'a>( + fn load_returned_complex_symbol<'a>( _buf: &mut Vec<'a, u8>, - _struct_offset: i32, - _struct_size: u32, - _field_layouts: &[Layout<'a>], - _ret_reg: Option, + _storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64WindowsFastcall, + >, + _sym: &Symbol, + _layout: &Layout<'a>, ) { - todo!("Returning structs for X86_64WindowsFastCall"); + todo!("Loading returned complex symbols for X86_64"); } fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { From d65a9715082b39548569cc15d83d36813fa4120a Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 21:17:56 -0800 Subject: [PATCH 24/56] add returning str and list --- compiler/gen_dev/src/generic64/mod.rs | 8 +- compiler/gen_dev/src/generic64/storage.rs | 23 ++++++ compiler/gen_dev/src/generic64/x86_64.rs | 92 +++++------------------ 3 files changed, 44 insertions(+), 79 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 07048a46de..66f7aa012c 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1,7 +1,4 @@ -use crate::{ - single_register_floats, single_register_integers, single_register_layouts, Backend, Env, - Relocation, -}; +use crate::{single_register_floats, single_register_integers, Backend, Env, Relocation}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; @@ -602,9 +599,6 @@ impl< let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); } - Layout::Struct([]) => { - // Nothing needs to be done to load a returned empty struct. - } _ => { CC::load_returned_complex_symbol( &mut self.buf, diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index c4552e5761..effd454f5c 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -646,6 +646,29 @@ impl< } } + // gets the stack offset and size of the specified symbol. + // the symbol must already be stored on the stack. + pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) { + let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + }; + match storage { + Stack(Primitive { base_offset, .. }) => (*base_offset, 8), + Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => { + (*base_offset, *size) + } + _ => { + internal_error!( + "Data no on the stack for sym ({}) with storage ({:?})", + sym, + storage + ) + } + } + } + /// claim_stack_area is the public wrapper around claim_stack_size. /// It also deals with updating symbol storage. /// It returns the base offset of the stack area. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index e333f02142..711f42da8f 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -442,85 +442,33 @@ impl CallConv for X86_64Syste } fn return_complex_symbol<'a>( - _buf: &mut Vec<'a, u8>, - _storage_manager: &mut StorageManager< + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager< 'a, X86_64GeneralReg, X86_64FloatReg, X86_64Assembler, X86_64SystemV, >, - _sym: &Symbol, - _layout: &Layout<'a>, + sym: &Symbol, + layout: &Layout<'a>, ) { - // Complex types. - // let val = self.symbol_storage_map.get(sym); - // match val { - // Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {} - // Some(SymbolStorage::GeneralReg(reg)) => { - // // If it fits in a general purpose register, just copy it over to. - // // Technically this can be optimized to produce shorter instructions if less than 64bits. - // ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg); - // } - // Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => {} - // Some(SymbolStorage::FloatReg(reg)) => { - // ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); - // } - // Some(SymbolStorage::Base { offset, size, .. }) => match layout { - // Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - // ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); - // } - // Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - // ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); - // } - // Layout::Builtin(Builtin::Str) => { - // if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { - // // This will happen on windows, return via pointer here. - // todo!("Returning strings via pointer"); - // } else { - // ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); - // ASM::mov_reg64_base32( - // &mut self.buf, - // CC::GENERAL_RETURN_REGS[1], - // *offset + 8, - // ); - // } - // } - // Layout::Struct(field_layouts) => { - // let (offset, size) = (*offset, *size); - // // Nothing to do for empty struct - // if size > 0 { - // let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) - // { - // Some( - // self.storage_manager - // .load_to_general_reg(&mut self.buf, &Symbol::RET_POINTER), - // ) - // } else { - // None - // }; - // CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg); - // } - // } - // x => todo!("returning symbol with layout, {:?}", x), - // }, - // Some(x) => todo!("returning symbol storage, {:?}", x), - // None if layout == &Layout::Struct(&[]) => { - // // Empty struct is not defined and does nothing. - // } - // None => { - // internal_error!("Unknown return symbol: {:?}", sym); - // } - // } - // let inst_loc = self.buf.len() as u64; - // let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64; - // self.relocs.push(Relocation::JmpToReturn { - // inst_loc, - // inst_size: self.buf.len() as u64 - inst_loc, - // offset, - // }); - // } - todo!("Returning complex symbols for X86_64"); + let (base_offset, size) = storage_manager.stack_offset_and_size(sym); + match layout { + single_register_layouts!() => { + internal_error!("single register layouts are not complex symbols"); + } + Layout::Struct([]) => {} + Layout::Builtin(Builtin::Str | Builtin::List(_)) => { + X86_64Assembler::mov_reg64_base32(buf, Self::GENERAL_RETURN_REGS[0], base_offset); + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[1], + base_offset + 8, + ); + } + x => todo!("returning complex type, {:?}", x), + } } fn load_returned_complex_symbol<'a>( From 8eb1b09ff95a7c54a66b904fe68d84c74affb1b4 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 21:21:07 -0800 Subject: [PATCH 25/56] remove returns via arg pointer from the callconv interface --- compiler/gen_dev/src/generic64/aarch64.rs | 4 ---- compiler/gen_dev/src/generic64/mod.rs | 3 --- compiler/gen_dev/src/generic64/x86_64.rs | 6 +++++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 3410b0fb20..caa675617c 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -297,10 +297,6 @@ impl CallConv for AArch64C ) { todo!("Loading returned complex symbols for AArch64"); } - - fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool { - todo!("Returning via arg pointer for AArch64"); - } } impl Assembler for AArch64Assembler { diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 66f7aa012c..7b31f0178f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -107,9 +107,6 @@ pub trait CallConv, ); - - // returns true if the layout should be returned via an argument pointer. - fn returns_via_arg_pointer(ret_layout: &Layout) -> bool; } /// Assembler contains calls to the backend assembly generator. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 711f42da8f..2286cbd7fc 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -496,9 +496,11 @@ impl CallConv for X86_64Syste x => todo!("receiving complex return type, {:?}", x), } } +} +impl X86_64SystemV { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { - // TODO: This may need to be more complex/extended to fully support the calling convention. + // TODO: This will 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 ret_layout.stack_size(TARGET_INFO) > 16 } @@ -840,7 +842,9 @@ impl CallConv for X86_64Windo ) { todo!("Loading returned complex symbols for X86_64"); } +} +impl X86_64WindowsFastcall { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { // 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 From 76f03c722fb6a025283ee9a4dffdd53ce02c46eb Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 21:22:03 -0800 Subject: [PATCH 26/56] add alignment dbg assert --- compiler/gen_dev/src/generic64/x86_64.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 2286cbd7fc..275b08b05b 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -460,6 +460,7 @@ impl CallConv for X86_64Syste } Layout::Struct([]) => {} Layout::Builtin(Builtin::Str | Builtin::List(_)) => { + debug_assert_eq!(base_offset % 8, 0); X86_64Assembler::mov_reg64_base32(buf, Self::GENERAL_RETURN_REGS[0], base_offset); X86_64Assembler::mov_reg64_base32( buf, From 907050ceaa92e6001046118498144cae7ec0223f Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 21:52:41 -0800 Subject: [PATCH 27/56] convert loading args to storage manager --- compiler/gen_dev/src/generic64/aarch64.rs | 11 ++- compiler/gen_dev/src/generic64/mod.rs | 29 +----- compiler/gen_dev/src/generic64/storage.rs | 31 +++++++ compiler/gen_dev/src/generic64/x86_64.rs | 105 ++++++++-------------- 4 files changed, 79 insertions(+), 97 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index caa675617c..018bd36245 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -249,11 +249,16 @@ impl CallConv for AArch64C #[inline(always)] fn load_args<'a>( _buf: &mut Vec<'a, u8>, - _symbol_map: &mut MutMap>, + _storage_manager: &mut StorageManager< + 'a, + AArch64GeneralReg, + AArch64FloatReg, + AArch64Assembler, + AArch64Call, + >, _args: &'a [(Layout<'a>, Symbol)], _ret_layout: &Layout<'a>, - mut _stack_size: u32, - ) -> u32 { + ) { todo!("Loading args for AArch64"); } diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 7b31f0178f..cff59e83a5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -72,12 +72,11 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, - symbol_map: &mut MutMap>, + storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, 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>, - stack_size: u32, - ) -> u32; + ); // store_args stores the args in registers and on the stack for function calling. // It returns the amount of stack space needed to temporarily store the args. @@ -262,7 +261,7 @@ pub enum SymbolStorage { }, } -pub trait RegTrait: Copy + Eq + std::hash::Hash + std::fmt::Debug + 'static { +pub trait RegTrait: Copy + PartialEq + Eq + std::hash::Hash + std::fmt::Debug + 'static { fn value(&self) -> u8; } @@ -525,27 +524,7 @@ impl< } fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>) { - self.stack_size = CC::load_args( - &mut self.buf, - &mut self.symbol_storage_map, - args, - ret_layout, - self.stack_size, - ); - // Update used and free regs. - for (sym, storage) in &self.symbol_storage_map { - match storage { - SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg { reg, .. } => { - self.general_free_regs.retain(|r| *r != *reg); - self.general_used_regs.push((*reg, *sym)); - } - SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg { reg, .. } => { - self.float_free_regs.retain(|r| *r != *reg); - self.float_used_regs.push((*reg, *sym)); - } - SymbolStorage::Base { .. } => {} - } - } + CC::load_args(&mut self.buf, &mut self.storage_manager, args, ret_layout); } /// Used for generating wrappers for malloc/realloc/free diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index effd454f5c..0bb48abd2f 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -669,6 +669,37 @@ impl< } } + // Specifies a symbol is loaded at the specified general register. + pub fn general_reg_arg(&mut self, sym: &Symbol, reg: GeneralReg) { + self.symbol_storage_map.insert(*sym, Reg(General(reg))); + self.general_free_regs.retain(|r| *r != reg); + self.general_used_regs.push((reg, *sym)); + } + + // Specifies a symbol is loaded at the specified float register. + pub fn float_reg_arg(&mut self, sym: &Symbol, reg: FloatReg) { + self.symbol_storage_map.insert(*sym, Reg(Float(reg))); + self.float_free_regs.retain(|r| *r != reg); + self.float_used_regs.push((reg, *sym)); + } + + // Specifies a primitive is loaded at the specific base offset. + pub fn primitive_stack_arg(&mut self, sym: &Symbol, base_offset: i32) { + self.symbol_storage_map.insert( + *sym, + Stack(Primitive { + base_offset, + reg: None, + }), + ); + } + + // Loads the arg pointer symbol to the specified general reg. + pub fn ret_pionter_arg(&mut self, reg: GeneralReg) { + self.symbol_storage_map + .insert(Symbol::RET_POINTER, Reg(General(reg))); + } + /// claim_stack_area is the public wrapper around claim_stack_size. /// It also deals with updating symbol storage. /// It returns the base offset of the stack area. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 275b08b05b..07c00fe694 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -194,81 +194,54 @@ impl CallConv for X86_64Syste #[inline(always)] fn load_args<'a>( buf: &mut Vec<'a, u8>, - symbol_map: &mut MutMap>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64SystemV, + >, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, - mut stack_size: u32, - ) -> u32 { - let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. + ) { + let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and 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]), - ); + storage_manager.ret_pionter_arg(Self::GENERAL_PARAM_REGS[0]); general_i += 1; } for (layout, sym) in args.iter() { match layout { single_register_integers!() => { if general_i < Self::GENERAL_PARAM_REGS.len() { - symbol_map.insert( - *sym, - SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]), - ); + storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]); general_i += 1; } else { + storage_manager.primitive_stack_arg(sym, arg_offset); arg_offset += 8; - symbol_map.insert( - *sym, - SymbolStorage::Base { - offset: arg_offset, - size: 8, - owned: true, - }, - ); } } single_register_floats!() => { if float_i < Self::FLOAT_PARAM_REGS.len() { - symbol_map.insert( - *sym, - SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[float_i]), - ); + storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[float_i]); float_i += 1; } else { + storage_manager.primitive_stack_arg(sym, arg_offset); arg_offset += 8; - symbol_map.insert( - *sym, - SymbolStorage::Base { - offset: arg_offset, - size: 8, - owned: true, - }, - ); } } - Layout::Builtin(Builtin::Str) => { + Layout::Builtin(Builtin::Str | Builtin::List(_)) => { if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { // Load the value from the param reg into a useable base offset. let src1 = Self::GENERAL_PARAM_REGS[general_i]; let src2 = Self::GENERAL_PARAM_REGS[general_i + 1]; - stack_size += 16; - let offset = -(stack_size as i32); - X86_64Assembler::mov_base32_reg64(buf, offset, src1); - X86_64Assembler::mov_base32_reg64(buf, offset + 8, src2); - symbol_map.insert( - *sym, - SymbolStorage::Base { - offset, - size: 16, - owned: true, - }, - ); + let base_offset = storage_manager.claim_stack_area(sym, 16); + X86_64Assembler::mov_base32_reg64(buf, base_offset, src1); + X86_64Assembler::mov_base32_reg64(buf, base_offset + 8, src2); general_i += 2; } else { - todo!("loading strings args on the stack"); + todo!("loading lists and strings args on the stack"); } } Layout::Struct(&[]) => {} @@ -277,7 +250,6 @@ impl CallConv for X86_64Syste } } } - stack_size } #[inline(always)] @@ -625,30 +597,31 @@ impl CallConv for X86_64Windo #[inline(always)] fn load_args<'a>( _buf: &mut Vec<'a, u8>, - symbol_map: &mut MutMap>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64WindowsFastcall, + >, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, - stack_size: u32, - ) -> u32 { - let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. + ) { + let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer. 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]), - ); + storage_manager.ret_pionter_arg(Self::GENERAL_PARAM_REGS[i]); i += 1; } for (layout, sym) in args.iter() { if i < Self::GENERAL_PARAM_REGS.len() { match layout { single_register_integers!() => { - symbol_map - .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); + storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[i]); i += 1; } single_register_floats!() => { - symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); + storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[i]); i += 1; } Layout::Builtin(Builtin::Str) => { @@ -661,23 +634,17 @@ impl CallConv for X86_64Windo } } } else { - arg_offset += match layout { - single_register_layouts!() => 8, + match layout { + single_register_layouts!() => { + storage_manager.primitive_stack_arg(sym, arg_offset); + arg_offset += 8; + } x => { todo!("Loading args with layout {:?}", x); } }; - symbol_map.insert( - *sym, - SymbolStorage::Base { - offset: arg_offset, - size: 8, - owned: true, - }, - ); } } - stack_size } #[inline(always)] From 2bcbb19f0778236a70c5a63f94f78e30441d60fa Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 22:08:35 -0800 Subject: [PATCH 28/56] enable loading aligned referenced primitives --- compiler/gen_dev/src/generic64/storage.rs | 55 +++++++++++++++++++---- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 0bb48abd2f..551dd8974f 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -294,6 +294,17 @@ impl< ); reg } + Stack(ReferencedPrimitive { base_offset, size }) + if base_offset % 8 == 0 && size == 8 => + { + // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. + let reg = self.get_general_reg(buf); + ASM::mov_reg64_base32(buf, reg, base_offset); + self.general_used_regs.push((reg, *sym)); + self.symbol_storage_map.insert(*sym, Reg(General(reg))); + self.free_reference(sym); + reg + } Stack(ReferencedPrimitive { .. }) => { todo!("loading referenced primitives") } @@ -349,6 +360,17 @@ impl< ); reg } + Stack(ReferencedPrimitive { base_offset, size }) + if base_offset % 8 == 0 && size == 8 => + { + // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. + let reg = self.get_float_reg(buf); + ASM::mov_freg64_base32(buf, reg, base_offset); + self.float_used_regs.push((reg, *sym)); + self.symbol_storage_map.insert(*sym, Reg(Float(reg))); + self.free_reference(sym); + reg + } Stack(ReferencedPrimitive { .. }) => { todo!("loading referenced primitives") } @@ -402,6 +424,12 @@ impl< debug_assert_eq!(base_offset % 8, 0); ASM::mov_reg64_base32(buf, reg, *base_offset); } + Stack(ReferencedPrimitive { base_offset, size }) + if base_offset % 8 == 0 && *size == 8 => + { + // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. + ASM::mov_reg64_base32(buf, reg, *base_offset); + } Stack(ReferencedPrimitive { .. }) => { todo!("loading referenced primitives") } @@ -450,6 +478,12 @@ impl< debug_assert_eq!(base_offset % 8, 0); ASM::mov_freg64_base32(buf, reg, *base_offset); } + Stack(ReferencedPrimitive { base_offset, size }) + if base_offset % 8 == 0 && *size == 8 => + { + // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. + ASM::mov_freg64_base32(buf, reg, *base_offset); + } Stack(ReferencedPrimitive { .. }) => { todo!("loading referenced primitives") } @@ -766,14 +800,7 @@ impl< self.free_stack_chunk(base_offset, 8); } Stack(Complex { .. } | ReferencedPrimitive { .. }) => { - let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { - owned_data - } else { - internal_error!("Unknown symbol: {}", sym); - }; - if Rc::strong_count(&owned_data) == 1 { - self.free_stack_chunk(owned_data.0, owned_data.1); - } + self.free_reference(sym); } _ => {} } @@ -795,6 +822,18 @@ impl< } } + // Frees an reference and release an allocation if it is no longer used. + fn free_reference(&mut self, sym: &Symbol) { + let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { + owned_data + } else { + internal_error!("Unknown symbol: {}", sym); + }; + if Rc::strong_count(&owned_data) == 1 { + self.free_stack_chunk(owned_data.0, owned_data.1); + } + } + fn free_stack_chunk(&mut self, base_offset: i32, size: u32) { let loc = (base_offset, size); // Note: this position current points to the offset following the specified location. From 25f232ae0f568886428b4e582cb4295a2e61c9ca Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 22:09:46 -0800 Subject: [PATCH 29/56] fix minor typo --- compiler/gen_dev/src/generic64/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 551dd8974f..2fbe5ef845 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -695,7 +695,7 @@ impl< } _ => { internal_error!( - "Data no on the stack for sym ({}) with storage ({:?})", + "Data not on the stack for sym ({}) with storage ({:?})", sym, storage ) From d578dae3ef27ae6b73c325582cd3cca2d9afd5ec Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 22:12:54 -0800 Subject: [PATCH 30/56] fix empty struct bug --- compiler/gen_dev/src/generic64/x86_64.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 07c00fe694..234601aa30 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -425,13 +425,13 @@ impl CallConv for X86_64Syste sym: &Symbol, layout: &Layout<'a>, ) { - let (base_offset, size) = storage_manager.stack_offset_and_size(sym); match layout { single_register_layouts!() => { internal_error!("single register layouts are not complex symbols"); } Layout::Struct([]) => {} Layout::Builtin(Builtin::Str | Builtin::List(_)) => { + let (base_offset, size) = storage_manager.stack_offset_and_size(sym); debug_assert_eq!(base_offset % 8, 0); X86_64Assembler::mov_reg64_base32(buf, Self::GENERAL_RETURN_REGS[0], base_offset); X86_64Assembler::mov_reg64_base32( From 9fa420f871a6f67b25092143ea1fe1ac57fc5c2e Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 22:41:34 -0800 Subject: [PATCH 31/56] move storing args to use storage manager --- compiler/gen_dev/src/generic64/aarch64.rs | 13 +- compiler/gen_dev/src/generic64/mod.rs | 14 +- compiler/gen_dev/src/generic64/storage.rs | 11 + compiler/gen_dev/src/generic64/x86_64.rs | 347 +++++++--------------- 4 files changed, 141 insertions(+), 244 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 018bd36245..13f29ce81f 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -1,8 +1,7 @@ -use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait, SymbolStorage}; +use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::Relocation; use bumpalo::collections::Vec; use packed_struct::prelude::*; -use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::Layout; @@ -265,11 +264,17 @@ impl CallConv for AArch64C #[inline(always)] fn store_args<'a>( _buf: &mut Vec<'a, u8>, - _symbol_map: &MutMap>, + _storage_manager: &mut StorageManager< + 'a, + AArch64GeneralReg, + AArch64FloatReg, + AArch64Assembler, + AArch64Call, + >, _args: &'a [Symbol], _arg_layouts: &[Layout<'a>], _ret_layout: &Layout<'a>, - ) -> u32 { + ) { todo!("Storing args for AArch64"); } diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index cff59e83a5..3a2a370fcd 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -82,12 +82,12 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, - symbol_map: &MutMap>, + storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, args: &'a [Symbol], arg_layouts: &[Layout<'a>], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. ret_layout: &Layout<'a>, - ) -> u32; + ); /// return_complex_symbol returns the specified complex/non-primative symbol. /// It uses the layout to determine how the data should be returned. @@ -553,14 +553,13 @@ impl< .push_used_caller_saved_regs_to_stack(&mut self.buf); // Put values in param regs or on top of the stack. - let tmp_stack_size = CC::store_args( + CC::store_args( &mut self.buf, - &self.symbol_storage_map, + &mut self.storage_manager, args, arg_layouts, ret_layout, ); - self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size); // Call function and generate reloc. ASM::call(&mut self.buf, &mut self.relocs, fn_name); @@ -754,14 +753,13 @@ impl< self.storage_manager .push_used_caller_saved_regs_to_stack(&mut self.buf); - let tmp_stack_size = CC::store_args( + CC::store_args( &mut self.buf, - &self.symbol_storage_map, + &mut self.storage_manager, args, arg_layouts, ret_layout, ); - self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size); let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 2fbe5ef845..40c64ef298 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -9,6 +9,7 @@ use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; use roc_target::TargetInfo; +use std::cmp::max; use std::marker::PhantomData; use std::rc::Rc; @@ -105,6 +106,9 @@ pub struct StorageManager< free_stack_chunks: Vec<'a, (i32, u32)>, stack_size: u32, + + // The amount of extra stack space needed to pass args for function calling. + fn_call_stack_size: u32, } pub fn new_storage_manager< @@ -132,6 +136,7 @@ pub fn new_storage_manager< float_used_callee_saved_regs: MutSet::default(), free_stack_chunks: bumpalo::vec![in env.arena], stack_size: 0, + fn_call_stack_size: 0, } } @@ -158,6 +163,7 @@ impl< .extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS); self.free_stack_chunks.clear(); self.stack_size = 0; + self.fn_call_stack_size = 0; } // Returns true if the symbol is storing a primitive value. @@ -734,6 +740,11 @@ impl< .insert(Symbol::RET_POINTER, Reg(General(reg))); } + // updates the function call stack size to the max of its current value and the size need for this call. + pub fn update_fn_call_stack_size(&mut self, tmp_size: u32) { + self.fn_call_stack_size = max(self.fn_call_stack_size, tmp_size); + } + /// claim_stack_area is the public wrapper around claim_stack_size. /// It also deals with updating symbol storage. /// It returns the base offset of the stack area. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 234601aa30..b34a0ddc3c 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,10 +1,9 @@ -use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait, SymbolStorage}; +use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::{ single_register_floats, single_register_integers, single_register_layouts, Relocation, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; @@ -255,150 +254,88 @@ impl CallConv for X86_64Syste #[inline(always)] fn store_args<'a>( buf: &mut Vec<'a, u8>, - symbol_map: &MutMap>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64SystemV, + >, args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> u32 { - let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; + ) { + let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; + if Self::returns_via_arg_pointer(ret_layout) { + // Save space on the stack for the arg we will return. + storage_manager + .claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO)); + todo!("claim first parama reg for the address"); + } let mut general_i = 0; let mut float_i = 0; - // For most return layouts we will do nothing. - // In some cases, we need to put the return address as the first arg. - match ret_layout { - single_register_layouts!() | Layout::Builtin(Builtin::Str) | Layout::Struct([]) => { - // Nothing needs to be done for any of these cases. - } - x => { - todo!("receiving return type, {:?}", x); - } - } - for (i, layout) in arg_layouts.iter().enumerate() { + for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { match layout { single_register_integers!() => { - let storage = match symbol_map.get(&args[i]) { - Some(storage) => storage, - None => { - internal_error!("function argument does not reference any symbol") - } - }; if general_i < Self::GENERAL_PARAM_REGS.len() { - // Load the value to the param reg. - let dst = Self::GENERAL_PARAM_REGS[general_i]; - match storage { - SymbolStorage::GeneralReg(reg) - | SymbolStorage::BaseAndGeneralReg { reg, .. } => { - X86_64Assembler::mov_reg64_reg64(buf, dst, *reg); - } - SymbolStorage::Base { offset, .. } => { - X86_64Assembler::mov_reg64_base32(buf, dst, *offset); - } - SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - internal_error!("Cannot load floating point symbol into GeneralReg") - } - } + storage_manager.load_to_specified_general_reg( + buf, + sym, + Self::GENERAL_PARAM_REGS[i], + ); general_i += 1; } else { - // Load the value to the stack. - match storage { - SymbolStorage::GeneralReg(reg) - | SymbolStorage::BaseAndGeneralReg { reg, .. } => { - X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg); - } - SymbolStorage::Base { offset, .. } => { - // Use RAX as a tmp reg because it will be free before function calls. - X86_64Assembler::mov_reg64_base32( - buf, - X86_64GeneralReg::RAX, - *offset, - ); - X86_64Assembler::mov_stack32_reg64( - buf, - stack_offset, - X86_64GeneralReg::RAX, - ); - } - SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - internal_error!("Cannot load floating point symbol into GeneralReg") - } - } - stack_offset += 8; + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_general_reg( + buf, + sym, + Self::GENERAL_RETURN_REGS[0], + ); + X86_64Assembler::mov_stack32_reg64( + buf, + tmp_stack_offset, + Self::GENERAL_RETURN_REGS[0], + ); + tmp_stack_offset += 8; } } single_register_floats!() => { - let storage = match symbol_map.get(&args[i]) { - Some(storage) => storage, - None => { - internal_error!("function argument does not reference any symbol") - } - }; if float_i < Self::FLOAT_PARAM_REGS.len() { - // Load the value to the param reg. - let dst = Self::FLOAT_PARAM_REGS[float_i]; - match storage { - SymbolStorage::FloatReg(reg) - | SymbolStorage::BaseAndFloatReg { reg, .. } => { - X86_64Assembler::mov_freg64_freg64(buf, dst, *reg); - } - SymbolStorage::Base { offset, .. } => { - X86_64Assembler::mov_freg64_base32(buf, dst, *offset); - } - SymbolStorage::GeneralReg(_) - | SymbolStorage::BaseAndGeneralReg { .. } => { - internal_error!("Cannot load general symbol into FloatReg") - } - } + storage_manager.load_to_specified_float_reg( + buf, + sym, + Self::FLOAT_PARAM_REGS[i], + ); float_i += 1; } else { - // Load the value to the stack. - match storage { - SymbolStorage::FloatReg(reg) - | SymbolStorage::BaseAndFloatReg { reg, .. } => { - X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg); - } - SymbolStorage::Base { offset, .. } => { - // Use XMM0 as a tmp reg because it will be free before function calls. - X86_64Assembler::mov_freg64_base32( - buf, - X86_64FloatReg::XMM0, - *offset, - ); - X86_64Assembler::mov_stack32_freg64( - buf, - stack_offset, - X86_64FloatReg::XMM0, - ); - } - SymbolStorage::GeneralReg(_) - | SymbolStorage::BaseAndGeneralReg { .. } => { - internal_error!("Cannot load general symbol into FloatReg") - } - } - stack_offset += 8; + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_float_reg( + buf, + sym, + Self::FLOAT_RETURN_REGS[0], + ); + X86_64Assembler::mov_stack32_freg64( + buf, + tmp_stack_offset, + Self::FLOAT_RETURN_REGS[0], + ); + tmp_stack_offset += 8; } } Layout::Builtin(Builtin::Str) => { - let storage = match symbol_map.get(&args[i]) { - Some(storage) => storage, - None => { - internal_error!("function argument does not reference any symbol") - } - }; if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { - // Load the value to the param reg. - let dst1 = Self::GENERAL_PARAM_REGS[general_i]; - let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1]; - match storage { - SymbolStorage::Base { offset, .. } => { - X86_64Assembler::mov_reg64_base32(buf, dst1, *offset); - X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8); - } - _ => { - internal_error!( - "Strings only support being loaded from base offsets" - ); - } - } + let (base_offset, _size) = storage_manager.stack_offset_and_size(sym); + debug_assert_eq!(base_offset % 8, 0); + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_PARAM_REGS[general_i], + base_offset, + ); + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_PARAM_REGS[general_i + 1], + base_offset + 8, + ); general_i += 2; } else { todo!("calling functions with strings on the stack"); @@ -410,7 +347,7 @@ impl CallConv for X86_64Syste } } } - stack_offset as u32 + storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32); } fn return_complex_symbol<'a>( @@ -431,7 +368,7 @@ impl CallConv for X86_64Syste } Layout::Struct([]) => {} Layout::Builtin(Builtin::Str | Builtin::List(_)) => { - let (base_offset, size) = storage_manager.stack_offset_and_size(sym); + let (base_offset, _size) = storage_manager.stack_offset_and_size(sym); debug_assert_eq!(base_offset % 8, 0); X86_64Assembler::mov_reg64_base32(buf, Self::GENERAL_RETURN_REGS[0], base_offset); X86_64Assembler::mov_reg64_base32( @@ -650,122 +587,68 @@ impl CallConv for X86_64Windo #[inline(always)] fn store_args<'a>( buf: &mut Vec<'a, u8>, - symbol_map: &MutMap>, + storage_manager: &mut StorageManager< + 'a, + X86_64GeneralReg, + X86_64FloatReg, + X86_64Assembler, + X86_64WindowsFastcall, + >, args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> u32 { - let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; - // For most return layouts we will do nothing. - // In some cases, we need to put the return address as the first arg. - match ret_layout { - single_register_layouts!() | Layout::Struct([]) => { - // Nothing needs to be done for any of these cases. - } - x => { - todo!("receiving return type, {:?}", x); - } + ) { + let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; + if Self::returns_via_arg_pointer(ret_layout) { + // Save space on the stack for the arg we will return. + storage_manager + .claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO)); + todo!("claim first parama reg for the address"); } - for (i, layout) in arg_layouts.iter().enumerate() { + for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { match layout { single_register_integers!() => { - let storage = match symbol_map.get(&args[i]) { - Some(storage) => storage, - None => { - internal_error!("function argument does not reference any symbol") - } - }; if i < Self::GENERAL_PARAM_REGS.len() { - // Load the value to the param reg. - let dst = Self::GENERAL_PARAM_REGS[i]; - match storage { - SymbolStorage::GeneralReg(reg) - | SymbolStorage::BaseAndGeneralReg { reg, .. } => { - X86_64Assembler::mov_reg64_reg64(buf, dst, *reg); - } - SymbolStorage::Base { offset, .. } => { - X86_64Assembler::mov_reg64_base32(buf, dst, *offset); - } - SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - internal_error!("Cannot load floating point symbol into GeneralReg") - } - } + storage_manager.load_to_specified_general_reg( + buf, + sym, + Self::GENERAL_PARAM_REGS[i], + ); } else { - // Load the value to the stack. - match storage { - SymbolStorage::GeneralReg(reg) - | SymbolStorage::BaseAndGeneralReg { reg, .. } => { - X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg); - } - SymbolStorage::Base { offset, .. } => { - // Use RAX as a tmp reg because it will be free before function calls. - X86_64Assembler::mov_reg64_base32( - buf, - X86_64GeneralReg::RAX, - *offset, - ); - X86_64Assembler::mov_stack32_reg64( - buf, - stack_offset, - X86_64GeneralReg::RAX, - ); - } - SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - internal_error!("Cannot load floating point symbol into GeneralReg") - } - } - stack_offset += 8; + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_general_reg( + buf, + sym, + Self::GENERAL_RETURN_REGS[0], + ); + X86_64Assembler::mov_stack32_reg64( + buf, + tmp_stack_offset, + Self::GENERAL_RETURN_REGS[0], + ); + tmp_stack_offset += 8; } } single_register_floats!() => { - let storage = match symbol_map.get(&args[i]) { - Some(storage) => storage, - None => { - internal_error!("function argument does not reference any symbol") - } - }; if i < Self::FLOAT_PARAM_REGS.len() { - // Load the value to the param reg. - let dst = Self::FLOAT_PARAM_REGS[i]; - match storage { - SymbolStorage::FloatReg(reg) - | SymbolStorage::BaseAndFloatReg { reg, .. } => { - X86_64Assembler::mov_freg64_freg64(buf, dst, *reg); - } - SymbolStorage::Base { offset, .. } => { - X86_64Assembler::mov_freg64_base32(buf, dst, *offset); - } - SymbolStorage::GeneralReg(_) - | SymbolStorage::BaseAndGeneralReg { .. } => { - internal_error!("Cannot load general symbol into FloatReg") - } - } + storage_manager.load_to_specified_float_reg( + buf, + sym, + Self::FLOAT_PARAM_REGS[i], + ); } else { - // Load the value to the stack. - match storage { - SymbolStorage::FloatReg(reg) - | SymbolStorage::BaseAndFloatReg { reg, .. } => { - X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg); - } - SymbolStorage::Base { offset, .. } => { - // Use XMM0 as a tmp reg because it will be free before function calls. - X86_64Assembler::mov_freg64_base32( - buf, - X86_64FloatReg::XMM0, - *offset, - ); - X86_64Assembler::mov_stack32_freg64( - buf, - stack_offset, - X86_64FloatReg::XMM0, - ); - } - SymbolStorage::GeneralReg(_) - | SymbolStorage::BaseAndGeneralReg { .. } => { - internal_error!("Cannot load general symbol into FloatReg") - } - } - stack_offset += 8; + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_float_reg( + buf, + sym, + Self::FLOAT_RETURN_REGS[0], + ); + X86_64Assembler::mov_stack32_freg64( + buf, + tmp_stack_offset, + Self::FLOAT_RETURN_REGS[0], + ); + tmp_stack_offset += 8; } } Layout::Builtin(Builtin::Str) => { @@ -778,7 +661,7 @@ impl CallConv for X86_64Windo } } } - stack_offset as u32 + storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32); } fn return_complex_symbol<'a>( From b6a61aa1cd9e795f606aad4c68bc0a03d9e57308 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 22:46:41 -0800 Subject: [PATCH 32/56] update todo --- compiler/gen_dev/src/generic64/mod.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 3a2a370fcd..2131da3e71 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -16,13 +16,8 @@ pub(crate) mod x86_64; // TODO: StorageManager is still not fully integrated. // General pieces needed: -// - loading and Storing args into storage manager -// - returning data (note: remove return struct send everything to CC) // - function call stack? (maybe can stay here) -// - re-enabling some commented out things // - remove data that is duplicated here and in storage manager -// - ensure storage map doesn't leak out of storage, try to make it clean and generic -// - Look into Complex values on the stack and reference. They may not work well. // - look into fixing join to no longer use multiple backends??? use storage::StorageManager; From fb589f7dc5a02f2035e40e2d4683bf8b9fb5403b Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 23:06:55 -0800 Subject: [PATCH 33/56] removing owning symbol map, now delt with by storage manager --- compiler/gen_dev/src/lib.rs | 65 +++++++++++++------------------------ 1 file changed, 22 insertions(+), 43 deletions(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index af382ee988..df76cd03f3 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -694,16 +694,8 @@ trait Backend<'a> { fn free_symbol(&mut self, sym: &Symbol); /// set_last_seen sets the statement a symbol was last seen in. - fn set_last_seen( - &mut self, - sym: Symbol, - stmt: &Stmt<'a>, - owning_symbol: &MutMap, - ) { + fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) { self.last_seen_map().insert(sym, stmt); - if let Some(parent) = owning_symbol.get(&sym) { - self.last_seen_map().insert(*parent, stmt); - } } /// last_seen_map gets the map from symbol to when it is last seen in the function. @@ -749,45 +741,37 @@ trait Backend<'a> { /// scan_ast runs through the ast and fill the last seen map. /// This must iterate through the ast in the same way that build_stmt does. i.e. then before else. fn scan_ast(&mut self, stmt: &Stmt<'a>) { - // This keeps track of symbols that depend on other symbols. - // The main case of this is data in structures and tagged unions. - // This data must extend the lifetime of the original structure or tagged union. - // For arrays the loading is always done through low levels and does not depend on the underlying array's lifetime. - let mut owning_symbol: MutMap = MutMap::default(); match stmt { Stmt::Let(sym, expr, _, following) => { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); match expr { Expr::Literal(_) => {} - Expr::Call(call) => self.scan_ast_call(call, stmt, &owning_symbol), + Expr::Call(call) => self.scan_ast_call(call, stmt), Expr::Tag { arguments, .. } => { for sym in *arguments { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } } Expr::Struct(syms) => { for sym in *syms { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } } Expr::StructAtIndex { structure, .. } => { - self.set_last_seen(*structure, stmt, &owning_symbol); - owning_symbol.insert(*sym, *structure); + self.set_last_seen(*structure, stmt); } Expr::GetTagId { structure, .. } => { - self.set_last_seen(*structure, stmt, &owning_symbol); - owning_symbol.insert(*sym, *structure); + self.set_last_seen(*structure, stmt); } Expr::UnionAtIndex { structure, .. } => { - self.set_last_seen(*structure, stmt, &owning_symbol); - owning_symbol.insert(*sym, *structure); + self.set_last_seen(*structure, stmt); } Expr::Array { elems, .. } => { for elem in *elems { if let ListLiteralElement::Symbol(sym) = elem { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } } } @@ -797,22 +781,22 @@ trait Backend<'a> { tag_name, .. } => { - self.set_last_seen(*symbol, stmt, &owning_symbol); + self.set_last_seen(*symbol, stmt); match tag_name { TagName::Closure(sym) => { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } TagName::Private(sym) => { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } TagName::Global(_) => {} } for sym in *arguments { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } } Expr::Reset { symbol, .. } => { - self.set_last_seen(*symbol, stmt, &owning_symbol); + self.set_last_seen(*symbol, stmt); } Expr::EmptyArray => {} Expr::RuntimeErrorFunction(_) => {} @@ -826,19 +810,19 @@ trait Backend<'a> { default_branch, .. } => { - self.set_last_seen(*cond_symbol, stmt, &owning_symbol); + self.set_last_seen(*cond_symbol, stmt); for (_, _, branch) in *branches { self.scan_ast(branch); } self.scan_ast(default_branch.1); } Stmt::Ret(sym) => { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } Stmt::Refcounting(modify, following) => { let sym = modify.get_symbol(); - self.set_last_seen(sym, stmt, &owning_symbol); + self.set_last_seen(sym, stmt); self.scan_ast(following); } Stmt::Join { @@ -848,34 +832,29 @@ trait Backend<'a> { .. } => { for param in *parameters { - self.set_last_seen(param.symbol, stmt, &owning_symbol); + self.set_last_seen(param.symbol, stmt); } self.scan_ast(continuation); self.scan_ast(remainder); } Stmt::Jump(JoinPointId(sym), symbols) => { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); for sym in *symbols { - self.set_last_seen(*sym, stmt, &owning_symbol); + self.set_last_seen(*sym, stmt); } } Stmt::RuntimeError(_) => {} } } - fn scan_ast_call( - &mut self, - call: &roc_mono::ir::Call, - stmt: &roc_mono::ir::Stmt<'a>, - owning_symbol: &MutMap, - ) { + fn scan_ast_call(&mut self, call: &roc_mono::ir::Call, stmt: &roc_mono::ir::Stmt<'a>) { let roc_mono::ir::Call { call_type, arguments, } = call; for sym in *arguments { - self.set_last_seen(*sym, stmt, owning_symbol); + self.set_last_seen(*sym, stmt); } match call_type { From b00ef5ea4fca5488ffcea147fee228d91f0668da Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 17 Feb 2022 23:17:17 -0800 Subject: [PATCH 34/56] add JoinPointId tracking for parameters --- compiler/gen_dev/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index df76cd03f3..0bc6a20315 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -741,6 +741,8 @@ trait Backend<'a> { /// scan_ast runs through the ast and fill the last seen map. /// This must iterate through the ast in the same way that build_stmt does. i.e. then before else. fn scan_ast(&mut self, stmt: &Stmt<'a>) { + // Join map keeps track of join point paramaters so that we can keep them around while they still might be jumped to. + let mut join_map: MutMap]> = MutMap::default(); match stmt { Stmt::Let(sym, expr, _, following) => { self.set_last_seen(*sym, stmt); @@ -829,8 +831,10 @@ trait Backend<'a> { parameters, body: continuation, remainder, + id, .. } => { + join_map.insert(*id, parameters); for param in *parameters { self.set_last_seen(param.symbol, stmt); } @@ -838,6 +842,12 @@ trait Backend<'a> { self.scan_ast(remainder); } Stmt::Jump(JoinPointId(sym), symbols) => { + if let Some(parameters) = join_map.get(&JoinPointId(*sym)) { + // Keep the parameters around. They will be overwritten when jumping. + for param in *parameters { + self.set_last_seen(param.symbol, stmt); + } + } self.set_last_seen(*sym, stmt); for sym in *symbols { self.set_last_seen(*sym, stmt); From 554db4556b464895f2a557eaa1af77418e0bd5e3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 13:25:39 -0800 Subject: [PATCH 35/56] refactor out loading values from storage or internal error --- compiler/gen_dev/src/generic64/storage.rs | 84 ++++++++--------------- 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 40c64ef298..3af5be31e8 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -100,6 +100,7 @@ pub struct StorageManager< general_used_regs: Vec<'a, (GeneralReg, Symbol)>, float_used_regs: Vec<'a, (FloatReg, Symbol)>, + // TODO: it probably would be faster to make these a list that linearly scans rather than hashing. // used callee saved regs must be tracked for pushing and popping at the beginning/end of the function. general_used_callee_saved_regs: MutSet, float_used_callee_saved_regs: MutSet, @@ -168,13 +169,8 @@ impl< // Returns true if the symbol is storing a primitive value. pub fn is_stored_primitive(&self, sym: &Symbol) -> bool { - let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; matches!( - storage, + self.get_storage_for_sym(sym), Reg(_) | Stack(Primitive { .. } | ReferencedPrimitive { .. }) ) } @@ -262,11 +258,7 @@ impl< // Will fail on values stored in float regs. // Will fail for values that don't fit in a single register. pub fn load_to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { - let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; + let storage = self.remove_storage_for_sym(sym); match storage { Reg(General(reg)) | Stack(Primitive { @@ -328,11 +320,7 @@ impl< // Will fail on values stored in general regs. // Will fail for values that don't fit in a single register. pub fn load_to_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { - let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; + let storage = self.remove_storage_for_sym(sym); match storage { Reg(Float(reg)) | Stack(Primitive { @@ -400,12 +388,7 @@ impl< sym: &Symbol, reg: GeneralReg, ) { - let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; - match storage { + match self.get_storage_for_sym(sym) { Reg(General(old_reg)) | Stack(Primitive { reg: Some(General(old_reg)), @@ -454,12 +437,7 @@ impl< // It will not try to free the register first. // This will not track the symbol change (it makes no assumptions about the new reg). pub fn load_to_specified_float_reg(&self, buf: &mut Vec<'a, u8>, sym: &Symbol, reg: FloatReg) { - let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; - match storage { + match self.get_storage_for_sym(sym) { Reg(Float(old_reg)) | Stack(Primitive { reg: Some(Float(old_reg)), @@ -512,11 +490,6 @@ impl< field_layouts: &'a [Layout<'a>], ) { debug_assert!(index < field_layouts.len() as u64); - let storage = if let Some(storage) = self.symbol_storage_map.get(structure) { - storage - } else { - internal_error!("Unknown symbol: {}", structure); - }; // This must be removed and reinserted for ownership and mutability reasons. let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) { owned_data @@ -525,7 +498,7 @@ impl< }; self.allocation_map .insert(*structure, Rc::clone(&owned_data)); - match storage { + match self.get_storage_for_sym(structure) { Stack(Complex { base_offset, size }) => { let (base_offset, size) = (*base_offset, *size); let mut data_offset = base_offset; @@ -552,7 +525,7 @@ impl< }), ); } - _ => { + storage => { internal_error!( "Cannot load field from data with storage type: {:?}", storage @@ -645,12 +618,7 @@ impl< sym: &Symbol, wanted_reg: RegStorage, ) { - let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; - match storage { + match self.remove_storage_for_sym(sym) { Reg(reg_storage) => { debug_assert_eq!(reg_storage, wanted_reg); let base_offset = self.claim_stack_size(8); @@ -689,17 +657,12 @@ impl< // gets the stack offset and size of the specified symbol. // the symbol must already be stored on the stack. pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) { - let storage = if let Some(storage) = self.symbol_storage_map.get(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; - match storage { + match self.get_storage_for_sym(sym) { Stack(Primitive { base_offset, .. }) => (*base_offset, 8), Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => { (*base_offset, *size) } - _ => { + storage => { internal_error!( "Data not on the stack for sym ({}) with storage ({:?})", sym, @@ -800,12 +763,7 @@ impl< } pub fn free_symbol(&mut self, sym: &Symbol) { - let storage = if let Some(storage) = self.symbol_storage_map.remove(sym) { - storage - } else { - internal_error!("Unknown symbol: {}", sym); - }; - match storage { + match self.remove_storage_for_sym(sym) { // Free stack chunck if this is the last reference to the chunk. Stack(Primitive { base_offset, .. }) => { self.free_stack_chunk(base_offset, 8); @@ -921,6 +879,24 @@ impl< } } } + + /// Gets a value from storage. They index symbol must be defined. + fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage { + if let Some(storage) = self.symbol_storage_map.get(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + } + } + + /// Removes and returns a value from storage. They index symbol must be defined. + fn remove_storage_for_sym(&mut self, sym: &Symbol) -> Storage { + if let Some(storage) = self.symbol_storage_map.remove(sym) { + storage + } else { + internal_error!("Unknown symbol: {}", sym); + } + } } fn is_primitive(layout: &Layout<'_>) -> bool { From 1f8ac3e150d90ff593c46dc0139a1797f903dc64 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 14:46:31 -0800 Subject: [PATCH 36/56] refactor join points and jumps --- compiler/gen_dev/src/generic64/mod.rs | 93 ++----------- compiler/gen_dev/src/generic64/storage.rs | 153 +++++++++++++++++++++- 2 files changed, 166 insertions(+), 80 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 2131da3e71..c130453dfb 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -16,9 +16,12 @@ pub(crate) mod x86_64; // TODO: StorageManager is still not fully integrated. // General pieces needed: -// - function call stack? (maybe can stay here) +// - function call stack fully moved to storage manager // - remove data that is duplicated here and in storage manager -// - look into fixing join to no longer use multiple backends??? +// To make joinpoints better: +// - consider their parameters to be alive until the last jump to them +// - save the location of the parameters at the start of the joinpoint in a special location. +// - When jumping to them, move the args to the location of the parameters (pushing to stack if necessary) use storage::StorageManager; pub trait CallConv>: @@ -267,6 +270,8 @@ pub struct Backend64Bit< ASM: Assembler, CC: CallConv, > { + // TODO: A number of the uses of MutMap could probably be some form of linear mutmap + // They are likely to be small enough that it is faster to use a vec and linearly scan it or keep it sorted and binary search. phantom_asm: PhantomData, phantom_cc: PhantomData, env: &'a Env<'a>, @@ -656,73 +661,13 @@ impl< let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); - // This section can essentially be seen as a sub function within the main function. - // Thus we build using a new backend with some minor extra synchronization. - { - let mut sub_backend = new_backend_64bit::( - self.env, - self.target_info, - self.interns, - ); - sub_backend.reset( - self.proc_name.as_ref().unwrap().clone(), - self.is_self_recursive.as_ref().unwrap().clone(), - ); - // Sync static maps of important information. - sub_backend.last_seen_map = self.last_seen_map.clone(); - sub_backend.layout_map = self.layout_map.clone(); - sub_backend.free_map = self.free_map.clone(); + // Ensure all the joinpoint parameters are loaded in only one location. + // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. + self.storage_manager.setup_joinpoint(id, parameters); - // Setup join point. - sub_backend.join_map.insert(*id, 0); - self.join_map.insert(*id, self.buf.len() as u64); + // Build all statements in body. + self.build_stmt(body, ret_layout); - // Sync stack size so the "sub function" doesn't mess up our stack. - sub_backend.stack_size = self.stack_size; - sub_backend.fn_call_stack_size = self.fn_call_stack_size; - - // Load params as if they were args. - let mut args = bumpalo::vec![in self.env.arena]; - for param in parameters { - args.push((param.layout, param.symbol)); - } - sub_backend.load_args(args.into_bump_slice(), ret_layout); - - // Build all statements in body. - sub_backend.build_stmt(body, ret_layout); - - // Merge the "sub function" into the main function. - let sub_func_offset = self.buf.len() as u64; - self.buf.extend_from_slice(&sub_backend.buf); - // Update stack based on how much was used by the sub function. - self.stack_size = sub_backend.stack_size; - self.fn_call_stack_size = sub_backend.fn_call_stack_size; - // Relocations must be shifted to be merged correctly. - self.relocs - .extend(sub_backend.relocs.into_iter().map(|reloc| match reloc { - Relocation::LocalData { offset, data } => Relocation::LocalData { - offset: offset + sub_func_offset, - data, - }, - Relocation::LinkedData { offset, name } => Relocation::LinkedData { - offset: offset + sub_func_offset, - name, - }, - Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction { - offset: offset + sub_func_offset, - name, - }, - Relocation::JmpToReturn { - inst_loc, - inst_size, - offset, - } => Relocation::JmpToReturn { - inst_loc: inst_loc + sub_func_offset, - inst_size, - offset: offset + sub_func_offset, - }, - })); - } // Overwrite the original jump with the correct offset. let mut tmp = bumpalo::vec![in self.env.arena]; self.update_jmp_imm32_offset( @@ -741,20 +686,10 @@ impl< id: &JoinPointId, args: &'a [Symbol], arg_layouts: &[Layout<'a>], - ret_layout: &Layout<'a>, + _ret_layout: &Layout<'a>, ) { - // Treat this like a function call, but with a jump instead of a call instruction at the end. - self.storage_manager - .push_used_caller_saved_regs_to_stack(&mut self.buf); - - CC::store_args( - &mut self.buf, - &mut self.storage_manager, - args, - arg_layouts, - ret_layout, - ); + .setup_jump(&mut self.buf, id, args, arg_layouts); let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 3af5be31e8..14f92e3c42 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -7,7 +7,10 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; -use roc_mono::layout::{Builtin, Layout}; +use roc_mono::{ + ir::{JoinPointId, Param}, + layout::{Builtin, Layout}, +}; use roc_target::TargetInfo; use std::cmp::max; use std::marker::PhantomData; @@ -89,6 +92,10 @@ pub struct StorageManager< // If a symbol has only one reference, we can free it. allocation_map: MutMap>, + // The storage for parameters of a join point. + // When jumping to the join point, the parameters should be setup to match this. + join_param_map: MutMap>>, + // This should probably be smarter than a vec. // There are certain registers we should always use first. With pushing and popping, this could get mixed. general_free_regs: Vec<'a, GeneralReg>, @@ -129,6 +136,7 @@ pub fn new_storage_manager< target_info, symbol_storage_map: MutMap::default(), allocation_map: MutMap::default(), + join_param_map: MutMap::default(), general_free_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena], general_used_callee_saved_regs: MutSet::default(), @@ -152,6 +160,7 @@ impl< pub fn reset(&mut self) { self.symbol_storage_map.clear(); self.allocation_map.clear(); + self.join_param_map.clear(); self.general_used_callee_saved_regs.clear(); self.general_free_regs.clear(); self.general_used_regs.clear(); @@ -611,7 +620,54 @@ impl< } } + /// Ensures that a register is free. If it is not free, data will be moved to make it free. + fn ensure_reg_free( + &mut self, + buf: &mut Vec<'a, u8>, + wanted_reg: RegStorage, + ) { + match wanted_reg { + General(reg) => { + if self.general_free_regs.contains(®) { + return; + } + match self + .general_used_regs + .iter() + .position(|(used_reg, sym)| reg == *used_reg) + { + Some(position) => { + let (_, sym) = self.general_used_regs.remove(position); + self.free_to_stack(buf, &sym, wanted_reg); + } + None => { + internal_error!("wanted register ({:?}) is not used or free", wanted_reg); + } + } + } + Float(reg) => { + if self.float_free_regs.contains(®) { + return; + } + match self + .float_used_regs + .iter() + .position(|(used_reg, sym)| reg == *used_reg) + { + Some(position) => { + let (_, sym) = self.float_used_regs.remove(position); + self.free_to_stack(buf, &sym, wanted_reg); + } + None => { + internal_error!("wanted register ({:?}) is not used or free", wanted_reg); + } + } + } + } + } + // Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. + // Note, used and free regs are expected to be updated outside of this function. fn free_to_stack( &mut self, buf: &mut Vec<'a, u8>, @@ -708,6 +764,101 @@ impl< self.fn_call_stack_size = max(self.fn_call_stack_size, tmp_size); } + /// Setups a join piont. + /// To do this, each of the join pionts params are loaded into a single location (either stack or reg). + /// Then those locations are stored. + /// Later jumps to the join point can overwrite the stored locations to pass parameters. + pub fn setup_joinpoint(&mut self, id: &JoinPointId, params: &'a [Param<'a>]) { + let mut param_storage = bumpalo::vec![in self.env.arena]; + param_storage.reserve(params.len()); + for Param { + symbol, + borrow, + layout: _, + } in params + { + if *borrow { + // These probably need to be passed by pointer/reference? + // Otherwise, we probably need to copy back to the param at the end of the joinpoint. + todo!("joinpoints with borrowed parameters"); + } + // move to single location. + // TODO: this may be wrong, params may be a list of new symbols. + // In that case, this should be claiming storage for each symbol. + match self.remove_storage_for_sym(symbol) { + // Only values that might be in 2 locations are primitives. + // They can be in a reg and on the stack. + Stack(Primitive { + reg: Some(reg), + base_offset, + }) => { + // Only care about the value in the register and reload to that. + // free the associated stack space. + self.free_stack_chunk(base_offset, 8); + self.symbol_storage_map.insert(*symbol, Reg(reg)); + param_storage.push(Reg(reg)); + } + storage => { + self.symbol_storage_map.insert(*symbol, storage); + param_storage.push(storage); + } + } + } + self.join_param_map.insert(*id, param_storage); + } + + // Setup jump loads the parameters for the joinpoint. + // This enables the jump to correctly passe arguments to the joinpoint. + pub fn setup_jump( + &mut self, + buf: &mut Vec<'a, u8>, + id: &JoinPointId, + args: &'a [Symbol], + arg_layouts: &[Layout<'a>], + ) { + // TODO: remove was use here and for current_storage to deal with borrow checker. + // See if we can do this better. + let param_storage = match self.join_param_map.remove(id) { + Some(storages) => storages, + None => internal_error!("Jump: unknown point specified to jump to: {:?}", id), + }; + for ((sym, layout), wanted_storage) in + args.iter().zip(arg_layouts).zip(param_storage.iter()) + { + // Note: it is possible that the storage we want to move to is in use by one of the args we want to pass. + let current_storage = self.remove_storage_for_sym(sym); + if ¤t_storage == wanted_storage { + continue; + } + match wanted_storage { + Reg(General(reg)) => { + // Ensure the reg is free, if not free it. + self.ensure_reg_free(buf, General(*reg)); + // Copy the value over to the reg. + self.load_to_specified_general_reg(buf, sym, *reg) + } + Reg(Float(reg)) => { + // Ensure the reg is free, if not free it. + self.ensure_reg_free(buf, Float(*reg)); + // Copy the value over to the reg. + self.load_to_specified_float_reg(buf, sym, *reg) + } + Stack(ReferencedPrimitive { base_offset, .. } | Complex { base_offset, .. }) => { + // TODO: This might be better not to call. + // Maybe we want a more memcpy like method to directly get called here. + // That would also be capable of asserting the size. + // Maybe copy stack to stack or something. + self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout); + } + NoData => {} + Stack(Primitive { .. }) => { + internal_error!("Primitive stack storage is not allowed for jumping") + } + } + self.symbol_storage_map.insert(*sym, current_storage); + } + self.join_param_map.insert(*id, param_storage); + } /// claim_stack_area is the public wrapper around claim_stack_size. /// It also deals with updating symbol storage. /// It returns the base offset of the stack area. From 37afe28c98ff9ddd1c9c746f5a21988a8231a83d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 14:59:51 -0800 Subject: [PATCH 37/56] fix join point param loading --- compiler/gen_dev/src/generic64/mod.rs | 10 +++-- compiler/gen_dev/src/generic64/storage.rs | 45 ++++++++++++----------- compiler/gen_dev/src/generic64/x86_64.rs | 12 +++--- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index c130453dfb..254f9f6f15 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -657,15 +657,17 @@ impl< remainder: &'a Stmt<'a>, ret_layout: &Layout<'a>, ) { + // Ensure all the joinpoint parameters have storage locations. + // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. + self.storage_manager + .setup_joinpoint(&mut self.buf, id, parameters); + // Create jump to remaining. let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); - // Ensure all the joinpoint parameters are loaded in only one location. - // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. - self.storage_manager.setup_joinpoint(id, parameters); - // Build all statements in body. + self.join_map.insert(*id, self.buf.len() as u64); self.build_stmt(body, ret_layout); // Overwrite the original jump with the correct offset. diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 14f92e3c42..03660924ce 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -764,17 +764,22 @@ impl< self.fn_call_stack_size = max(self.fn_call_stack_size, tmp_size); } - /// Setups a join piont. - /// To do this, each of the join pionts params are loaded into a single location (either stack or reg). + /// Setups a join point. + /// To do this, each of the join pionts params are given a storage location. /// Then those locations are stored. /// Later jumps to the join point can overwrite the stored locations to pass parameters. - pub fn setup_joinpoint(&mut self, id: &JoinPointId, params: &'a [Param<'a>]) { + pub fn setup_joinpoint( + &mut self, + buf: &mut Vec<'a, u8>, + id: &JoinPointId, + params: &'a [Param<'a>], + ) { let mut param_storage = bumpalo::vec![in self.env.arena]; param_storage.reserve(params.len()); for Param { symbol, borrow, - layout: _, + layout, } in params { if *borrow { @@ -782,25 +787,21 @@ impl< // Otherwise, we probably need to copy back to the param at the end of the joinpoint. todo!("joinpoints with borrowed parameters"); } - // move to single location. - // TODO: this may be wrong, params may be a list of new symbols. - // In that case, this should be claiming storage for each symbol. - match self.remove_storage_for_sym(symbol) { - // Only values that might be in 2 locations are primitives. - // They can be in a reg and on the stack. - Stack(Primitive { - reg: Some(reg), - base_offset, - }) => { - // Only care about the value in the register and reload to that. - // free the associated stack space. - self.free_stack_chunk(base_offset, 8); - self.symbol_storage_map.insert(*symbol, Reg(reg)); - param_storage.push(Reg(reg)); + // Claim a location for every join point parameter to be loaded at. + match layout { + single_register_integers!() => { + self.claim_general_reg(buf, symbol); } - storage => { - self.symbol_storage_map.insert(*symbol, storage); - param_storage.push(storage); + single_register_floats!() => { + self.claim_float_reg(buf, symbol); + } + _ => { + let stack_size = layout.stack_size(self.target_info); + if stack_size == 0 { + self.symbol_storage_map.insert(*symbol, NoData); + } else { + self.claim_stack_area(symbol, stack_size); + } } } } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index b34a0ddc3c..26c606f90a 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -243,7 +243,7 @@ impl CallConv for X86_64Syste todo!("loading lists and strings args on the stack"); } } - Layout::Struct(&[]) => {} + x if x.stack_size(TARGET_INFO) == 0 => {} x => { todo!("Loading args with layout {:?}", x); } @@ -341,7 +341,7 @@ impl CallConv for X86_64Syste todo!("calling functions with strings on the stack"); } } - Layout::Struct(&[]) => {} + x if x.stack_size(TARGET_INFO) == 0 => {} x => { todo!("calling with arg type, {:?}", x); } @@ -366,7 +366,6 @@ impl CallConv for X86_64Syste single_register_layouts!() => { internal_error!("single register layouts are not complex symbols"); } - Layout::Struct([]) => {} Layout::Builtin(Builtin::Str | Builtin::List(_)) => { let (base_offset, _size) = storage_manager.stack_offset_and_size(sym); debug_assert_eq!(base_offset % 8, 0); @@ -377,6 +376,7 @@ impl CallConv for X86_64Syste base_offset + 8, ); } + x if x.stack_size(TARGET_INFO) == 0 => {} x => todo!("returning complex type, {:?}", x), } } @@ -397,12 +397,12 @@ impl CallConv for X86_64Syste single_register_layouts!() => { internal_error!("single register layouts are not complex symbols"); } - Layout::Struct([]) => {} Layout::Builtin(Builtin::Str | Builtin::List(_)) => { let offset = storage_manager.claim_stack_area(sym, 16); X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]); } + x if x.stack_size(TARGET_INFO) == 0 => {} x => todo!("receiving complex return type, {:?}", x), } } @@ -565,7 +565,7 @@ impl CallConv for X86_64Windo // I think this just needs to be passed on the stack, so not a huge deal. todo!("Passing str args with Windows fast call"); } - Layout::Struct(&[]) => {} + x if x.stack_size(TARGET_INFO) == 0 => {} x => { todo!("Loading args with layout {:?}", x); } @@ -655,7 +655,7 @@ impl CallConv for X86_64Windo // I think this just needs to be passed on the stack, so not a huge deal. todo!("Passing str args with Windows fast call"); } - Layout::Struct(&[]) => {} + x if x.stack_size(TARGET_INFO) == 0 => {} x => { todo!("calling with arg type, {:?}", x); } From 3a970dffa7f00e0963bb3a9f0ff882c591b8628b Mon Sep 17 00:00:00 2001 From: Ryan Olson Date: Fri, 18 Feb 2022 16:46:07 -0700 Subject: [PATCH 38/56] Add tests for `roc format --check {{file}}` --- cli/tests/cli_run.rs | 21 +++++++++++++++++++++ cli/tests/fixtures/format/Formatted.roc | 6 ++++++ cli/tests/fixtures/format/NotFormatted.roc | 6 ++++++ 3 files changed, 33 insertions(+) create mode 100644 cli/tests/fixtures/format/Formatted.roc create mode 100644 cli/tests/fixtures/format/NotFormatted.roc diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index a4a105349b..45a32eecd9 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -69,6 +69,17 @@ mod cli_run { assert_multiline_str_eq!(err, expected.into()); } + fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { + let flags = &["--check"]; + let out = run_roc(&[&["format", &file.to_str().unwrap()], &flags[..]].concat()); + + if expects_success_exit_code { + assert!(out.status.success()); + } else { + assert!(!out.status.success()); + } + } + fn check_output_with_stdin( file: &Path, stdin: &[&str], @@ -863,6 +874,16 @@ mod cli_run { ), ); } + + #[test] + fn format_check_good() { + check_format_check_as_expected(&fixture_file("format", "Formatted.roc"), true); + } + + #[test] + fn format_check_reformatting_needed() { + check_format_check_as_expected(&fixture_file("format", "NotFormatted.roc"), false); + } } #[allow(dead_code)] diff --git a/cli/tests/fixtures/format/Formatted.roc b/cli/tests/fixtures/format/Formatted.roc new file mode 100644 index 0000000000..b62c494e66 --- /dev/null +++ b/cli/tests/fixtures/format/Formatted.roc @@ -0,0 +1,6 @@ +app "formatted" + packages { pf: "platform" } imports [] + provides [ main ] to pf + +main : Str +main = Dep1.value1 {} diff --git a/cli/tests/fixtures/format/NotFormatted.roc b/cli/tests/fixtures/format/NotFormatted.roc new file mode 100644 index 0000000000..df12071466 --- /dev/null +++ b/cli/tests/fixtures/format/NotFormatted.roc @@ -0,0 +1,6 @@ +app "formatted" + packages { pf: "platform" } + provides [ main ] to pf + +main : Str +main = Dep1.value1 {} From f16c0f7db5f48602e674b63915f449f2ac86f7f4 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 16:15:10 -0800 Subject: [PATCH 39/56] fix joinpoint and returning issues --- compiler/gen_dev/src/generic64/mod.rs | 12 ++++++++++-- compiler/gen_dev/src/generic64/storage.rs | 19 ++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 254f9f6f15..cef4249980 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1004,6 +1004,7 @@ impl< } fn free_symbol(&mut self, sym: &Symbol) { + self.join_map.remove(&JoinPointId(*sym)); self.storage_manager.free_symbol(sym); } @@ -1029,9 +1030,16 @@ impl< internal_error!("All primitive valuse should fit in a single register"); } } - return; + } else { + CC::return_complex_symbol(&mut self.buf, &mut self.storage_manager, sym, layout) } - CC::return_complex_symbol(&mut self.buf, &mut self.storage_manager, sym, layout) + let inst_loc = self.buf.len() as u64; + let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64; + self.relocs.push(Relocation::JmpToReturn { + inst_loc, + inst_size: self.buf.len() as u64 - inst_loc, + offset, + }); } } diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 03660924ce..d3978994a1 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -790,7 +790,7 @@ impl< // Claim a location for every join point parameter to be loaded at. match layout { single_register_integers!() => { - self.claim_general_reg(buf, symbol); + let reg = self.claim_general_reg(buf, symbol); } single_register_floats!() => { self.claim_float_reg(buf, symbol); @@ -804,6 +804,7 @@ impl< } } } + param_storage.push(*self.get_storage_for_sym(symbol)); } self.join_param_map.insert(*id, param_storage); } @@ -823,12 +824,12 @@ impl< Some(storages) => storages, None => internal_error!("Jump: unknown point specified to jump to: {:?}", id), }; + println!("Param storage: {:?}", param_storage); for ((sym, layout), wanted_storage) in args.iter().zip(arg_layouts).zip(param_storage.iter()) { // Note: it is possible that the storage we want to move to is in use by one of the args we want to pass. - let current_storage = self.remove_storage_for_sym(sym); - if ¤t_storage == wanted_storage { + if self.get_storage_for_sym(sym) == wanted_storage { continue; } match wanted_storage { @@ -836,6 +837,7 @@ impl< // Ensure the reg is free, if not free it. self.ensure_reg_free(buf, General(*reg)); // Copy the value over to the reg. + println!("Loading {:?} to {:?}", sym, reg); self.load_to_specified_general_reg(buf, sym, *reg) } Reg(Float(reg)) => { @@ -856,7 +858,6 @@ impl< internal_error!("Primitive stack storage is not allowed for jumping") } } - self.symbol_storage_map.insert(*sym, current_storage); } self.join_param_map.insert(*id, param_storage); } @@ -915,6 +916,10 @@ impl< } pub fn free_symbol(&mut self, sym: &Symbol) { + if let Some(_) = self.join_param_map.remove(&JoinPointId(*sym)) { + // This is a join point and will not be in the storage map. + return; + } match self.remove_storage_for_sym(sym) { // Free stack chunck if this is the last reference to the chunk. Stack(Primitive { base_offset, .. }) => { @@ -948,7 +953,7 @@ impl< let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { owned_data } else { - internal_error!("Unknown symbol: {}", sym); + internal_error!("Unknown symbol: {:?}", sym); }; if Rc::strong_count(&owned_data) == 1 { self.free_stack_chunk(owned_data.0, owned_data.1); @@ -1037,7 +1042,7 @@ impl< if let Some(storage) = self.symbol_storage_map.get(sym) { storage } else { - internal_error!("Unknown symbol: {}", sym); + internal_error!("Unknown symbol: {:?}", sym); } } @@ -1046,7 +1051,7 @@ impl< if let Some(storage) = self.symbol_storage_map.remove(sym) { storage } else { - internal_error!("Unknown symbol: {}", sym); + internal_error!("Unknown symbol: {:?}", sym); } } } From c533295cc17d9610a8523b67c2ae435ded6bdae4 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 16:32:48 -0800 Subject: [PATCH 40/56] remove redundant move --- compiler/gen_dev/src/generic64/x86_64.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 26c606f90a..b2abb1dcf6 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1353,6 +1353,9 @@ fn mov_reg64_base64_offset32( /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register. #[inline(always)] fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + if dst == src { + return; + } let dst_high = dst as u8 > 7; let dst_mod = dst as u8 % 8; let src_high = src as u8 > 7; From c81a1c7c2c0afe7c0ce19188d381723e927c4899 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 16:32:48 -0800 Subject: [PATCH 41/56] remove redundant move --- compiler/gen_dev/src/generic64/x86_64.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 26c606f90a..eb53c7db19 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1353,6 +1353,9 @@ fn mov_reg64_base64_offset32( /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register. #[inline(always)] fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + if dst == src { + return; + } let dst_high = dst as u8 > 7; let dst_mod = dst as u8 % 8; let src_high = src as u8 > 7; @@ -2085,10 +2088,7 @@ mod tests { let arena = bumpalo::Bump::new(); let mut buf = bumpalo::vec![in &arena]; for ((dst, src), expected) in &[ - ( - (X86_64FloatReg::XMM0, X86_64FloatReg::XMM0), - vec![0xF2, 0x0F, 0x10, 0xC0], - ), + ((X86_64FloatReg::XMM0, X86_64FloatReg::XMM0), vec![]), ( (X86_64FloatReg::XMM0, X86_64FloatReg::XMM15), vec![0xF2, 0x41, 0x0F, 0x10, 0xC7], @@ -2097,10 +2097,7 @@ mod tests { (X86_64FloatReg::XMM15, X86_64FloatReg::XMM0), vec![0xF2, 0x44, 0x0F, 0x10, 0xF8], ), - ( - (X86_64FloatReg::XMM15, X86_64FloatReg::XMM15), - vec![0xF2, 0x45, 0x0F, 0x10, 0xFF], - ), + ((X86_64FloatReg::XMM15, X86_64FloatReg::XMM15), vec![]), ] { buf.clear(); movsd_freg64_freg64(&mut buf, *dst, *src); From f564514d9c3dcffd16850fab6ef3cbee7bb1b15b Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 16:45:34 -0800 Subject: [PATCH 42/56] stop trying to free symbols that don't exist due to being call args --- compiler/gen_dev/src/generic64/storage.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index d3978994a1..5ab6558106 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -920,12 +920,12 @@ impl< // This is a join point and will not be in the storage map. return; } - match self.remove_storage_for_sym(sym) { + match self.symbol_storage_map.remove(sym) { // Free stack chunck if this is the last reference to the chunk. - Stack(Primitive { base_offset, .. }) => { + Some(Stack(Primitive { base_offset, .. })) => { self.free_stack_chunk(base_offset, 8); } - Stack(Complex { .. } | ReferencedPrimitive { .. }) => { + Some(Stack(Complex { .. } | ReferencedPrimitive { .. })) => { self.free_reference(sym); } _ => {} From 39e03ee6125d5b5edad414d42e5c5099136b7da0 Mon Sep 17 00:00:00 2001 From: Elijah Schow Date: Fri, 18 Feb 2022 21:08:13 -0600 Subject: [PATCH 43/56] Improve website typography This commit adds minimal styles to improve the reading experience on the placeholder website. - Set a max width - Increase leading (line height) - Center the content - Add space after list items to let them breath - Change the font to sans-serif* ___ *This change isn't strictly necessary, but sans-serif fonts are arguably more legible on digital displays than their serif counterparts. Your mileage may vary. --- www/public/index.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/www/public/index.html b/www/public/index.html index 832c81ef38..8b6d782d41 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -8,7 +8,17 @@ The Roc Programming Language - + From 719e10d3575bb4d06eed5e9e7de67905db48fb48 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 18 Feb 2022 23:01:38 -0500 Subject: [PATCH 44/56] Add FAQ.md --- FAQ.md | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000000..f04ad30a70 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,277 @@ +# Frequently Asked Questions + +## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types? + +_Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._ + +A valuable aspect of Roc's type system is that it has [principal](https://en.wikipedia.org/wiki/Principal_type) +type inference. This means that: + +* At compile time, Roc can correctly infer the types for every expression in a program, even if you don't annotate any of the types. +* This inference always infers the most general type possible; you couldn't possibly add a valid type annotation that would make the type more flexible than the one that Roc would infer if you deleted the annotation. + +It's been proven that any type system which supports either [higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf) or [arbitrary-rank types](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf) cannot have +principal type inference. With either of those features in the language, there will be situations where the compiler +reports an error that can only be fixed by the programmer adding a type annotation. This also means there would be +situations where the editor would not be able to reliably tell you the type of part of your program, unlike today +where it can accurately tell you the type of anything, even if you have no type annotations in your entire code base. + +### Arbitrary-rank types + +Unlike arbitrary-rank (aka "Rank-N") types, both Rank-1 and Rank-2 type systems are compatible with principal +type inference. Roc currently uses Rank-1 types, and the benefits of Rank-N over Rank-2 don't seem worth +sacrificing principal type inference to attain, so let's focus on the trade-offs between Rank-1 and Rank-2. + +Supporting Rank-2 types in Roc has been discussed before, but it has several important downsides: + +* It would increase the complexity of the language. +* It would make some compiler error messages more confusing (e.g. they might mention `forall` because that was the most general type that could be inferred, even if that wasn't helpful or related to the actual problem). +* It would substantially increase the complexity of the type checker, which would necessarily slow it down. + +No implementation of Rank-2 types can remove any of these downsides. Thus far, we've been able to come up +with sufficiently nice APIs that only require Rank-1 types, and we haven't seen a really compelling use case +where the gap between the Rank-2 and Rank-1 designs was big enough to justify switching to Rank-2. + +Since I prefer Roc being simpler and having a faster compiler with nicer error messages, my hope is that Roc +will never get Rank-2 types. However, it may turn out that in the future we learn about currently-unknown +upsides that somehow outweigh these downsides, so I'm open to considering the possibility - while rooting against it. + +### Higher-kinded polymorphism + +I want to be really clear about this one: the explicit plan is that Roc will never support higher-kinded polymorphism. + +On the technical side, the reasons for this are ordinary: I understand the practical benefits and +drawbacks of HKP, and I think the drawbacks outweigh the benefits when it comes to Roc. (Those who come to a +different conclusion may think HKP's drawbacks would be less of a big a deal in Roc than I do. That's reasonable; +we programmers often weigh the same trade-offs differently.) To be clear, I think this in the specific context of +Roc; there are plenty of other languages where HKP seems like a great fit. For example, it's hard to imagine Haskell +without it. Similarly, I think lifetime annotations are a great fit for Rust, but don't think they'd be right +for Roc either. + +I also think it's important to consider the cultural implications of deciding whether or not to support HKP. +To illustrate what I mean, imagine this conversation: + +**Programmer 1:** "How do you feel about higher-kinded polymorphism?" + +**Programmer 2:** "I have no idea what that is." + +**Programmer 1:** "Okay, how do you feel about monads?" + +**Programmer 2:** "OH NO." + +I've had several variations of this conversation: I'm talking about higher-kinded types, +another programmer asks what that means, I give monads as an example, and their reaction is strongly negative. +I've also had plenty of conversations with programmers who love HKP and vigorously advocate for its addition +to languages they use which don't have it. Feelings about HKP seem strongly divided, maybe more so +than any other type system feature besides static and dynamic types. + +It's impossible for a programming language to be neutral on this. If the language doesn't support HKP, nobody can +implement a Monad typeclass (or equivalent) in any way that can be expected to catch on. Advocacy to add HKP to the +language will inevitably follow. If the language does support HKP, one or more alternate standard libraries built +around monads will inevitably follow, along with corresponding cultural changes. (See Scala for example.) +Culturally, to support HKP is to take a side, and to decline to support it is also to take a side. + +Given this, language designers have three options: + +* Have HKP and have Monad in the standard library. Embrace them and build a culture and ecosystem around them. +* Have HKP and don't have Monad in the standard library. An alternate standard lbirary built around monads will inevitably emerge, and both the community and ecosystem will divide themselves along pro-monad and anti-monad lines. +* Don't have HKP; build a culture and ecosystem around other things. + +Considering that these are the only three options, I think the best choice for Roc—not only on a technical +level, but on a cultural level as well—is to make it clear that the plan is for Roc never to support HKP. +I hope this clarity can save a lot of community members' time that would otherwise be spent on advocacy or +arguing between the two sides of the divide. Again, I think it's completely reasonable for anyone to have a +different preference, but given that language designers can only choose one of these options, I'm confident +I've made the right choice for Roc by designing it never to have higher-kinded polymorphism. + +## Why does Roc's syntax and standard library differ from Elm's? + +Roc is a direct descendant of [Elm](https://elm-lang.org/). However, there are some differences between the two languages. + +Syntactic differences are among these. This is a feature, not a bug; if Roc had identical syntax to Elm, then it's +predictable that people would write code that was designed to work in both languages - and would then rely on +that being true, for example by making a package which advertised "Works in both Elm and Roc!" This in turn +would mean that later if either language were to change its syntax in a way that didn't make sense for the other, +the result would be broken code and sadness. + +So why does Roc have the specific syntax changes it does? Here are some brief explanations: + +* `#` instead of `--` for comments - this allows [hashbang](https://senthilnayagan.medium.com/shebang-hashbang-10966b8f28a8)s to work without needing special syntax. That isn't a use case Elm supports, but it is one Roc is designed to support. +* `{}` instead of `()` for the unit type - Elm has both, and they can both be used as a unit type. Since `{}` has other uses in the type system, but `()` doesn't, I consider it redundant and took it out. +* No tuples - I wanted to try simplifying the language and seeing how much we'd miss them. Anything that could be represented as a tuple can be represented with either a record or a single-tag union instead (e.g. `Pair x y = ...`), so is it really necessary to have a third syntax for representing a [product type](https://en.wikipedia.org/wiki/Product_type)? +* `when`...`is` instead of `case`...`of` - I predict it will be easier for beginners to pick up, because usually the way I explain `case`...`of` to beginners is by saying the words "when" and "is" out loud - e.g. "when `color` is `Red`, it runs this first branch; when `color` is `Blue`, it runs this other branch..." +* `:` instead of `=` for record field names: I like `=` being reserved for definitions, and `:` is the most popular alternative. +* Backpassing syntax - since Roc is designed to be used for use cases like command-line apps, shell scripts, and servers, I expect chained effects to come up a lot more often than they do in Elm. I think backpassing is nice for those use cases, similarly to how `do` notation is nice for them in Haskell. +* Tag unions instead of Elm's custom types (aka algebraic data types). This isn't just a syntactic change; tag unions are mainly in Roc because they can facilitate errors being accumulated across chained effects, which (as noted a moment ago) I expect to be a lot more common in Roc than in Elm. If you have tag unions, you don't really need a separate language feature for algebraic data types, since closed tag unions essentially work the same way - aside from not giving you a way to selectively expose variants or define phantom types. Roc's opaque types language feature covers those use cases instead. +* No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens. +* `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.) +* No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.) +* Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas. +* The `!` unary prefix operator. I didn't want to have a `Basics` module (more on that in a moment), and without `Basics`, this would either need to be called fully-qualified (`Bool.not`) or else a module import of `Bool.{ not }` would be necessary. Both seemed less nice than supporting the `!` prefix that's common to so many widely-used languages, especially when we already have a unary prefix operator of `-` for negation (e.g. `-x`). +* `!=` for the inequality operator (instead of Elm's `/=`) - this one pairs more naturally with the `!` prefix operator and is also very common in other languages. + +Roc also has a different standard library from Elm. Some of the differences come down to platforms and applications (e.g. having `Task` in Roc's standard library wouldn't make sense), but others do not. Here are some brief explanations: + +* No `Basics` module. I wanted to have a simple rule of "all modules in the standard library are imported by default, and so are their exposed types," and that's it. Given that I wanted the comparison operators (e.g. `<`) to work only on numbers, it ended up that having `Num` and `Bool` modules meant that almost nothing would be left for a `Basics` equivalent in Roc except `identity` and `Never`. The Roc type `[]` (empty tag union) is equivalent to `Never`, so that wasn't necessary, and I generally think that `identity` is a good concept but a sign of an incomplete API whenever its use comes up in practice. For example, instead of calling `|> List.filterMap identity` I'd rather have access to a more self-descriptive function like `|> List.dropNothings`. With `Num` and `Bool`, and without `identity` and `Never`, there was nothing left in `Basics`. +* `Str` instead of `String` - after using the `str` type in Rust, I realized I had no issue whatsoever with the more concise name, especially since it was used in so many places (similar to `Msg` and `Cmd` in Elm) - so I decided to save a couple of letters. +* No function composition operators - I stopped using these in Elm so long ago, at one point I forgot they were in the language! See the FAQ entry on currying for details about why. +* No `Maybe`. If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail. Optional record fields can be handled using the explicit Optional Record Field language feature. To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful? On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed. +* No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value). +* No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail. +* No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places. + +## Why aren't Roc functions curried by default? + +Although technically any language with first-class functions makes it possible to curry +any function (e.g. I can manually curry a Roc function `\x, y, z ->` by writing `\x -> \y -> \z ->` instead), +typically what people mean when they say Roc isn't a curried language is that Roc functions aren't curried +by default. For the rest of this section, I'll use "currying" as a shorthand for "functions that are curried +by default" for the sake of brevity. + +As I see it, currying has one major upside and three major downsides. The upside: + +* It makes function calls more concise in some cases. + +The downsides: + +* It makes the `|>` operator more error-prone in some cases. +* It makes higher-order function calls need more parentheses in some cases. +* It significantly increases the language's learning curve. +* It facilitates pointfree function composition. (More on why this is listed as a downside later.) + +There's also a downside that it would make runtime performance of compiled progarms worse by default, +but I assume it would be possible to optimize that away at the cost of slightly longer compile times. + +I consider the one upside (conciseness in some places) extremely minor, and have almost never missed it in Roc. +Here are some more details about the downsides as I see them. + +### Currying and the `|>` operator + +In Roc, this code produces `"Hello, World!"` + +```elm +"Hello, World" + |> Str.concat "!" +``` + +This is because Roc's `|>` operator uses the expression before the `|>` as the *first* argument to the function +after it. For functions where both arguments have the same type, but it's obvious which argument goes where (e.g. +`Str.concat "Hello, " "World!"`, `List.concat [ 1, 2 ] [ 3, 4 ]`), this works out well. Another example would +be `|> Num.sub 1`, which subtracts 1 from whatever came before the `|>`. + +For this reason, "pipeline-friendliness" in Roc means that the first argument to each function is typically +the one that's most likely to be built up using a pipeline. For example, `List.map`: + +```elm +numbers + |> List.map Num.abs +``` + +This argument ordering convention also often makes it possible to pass anonymous functions to higher-order +functions without needing parentheses, like so: + +```elm +List.map numbers \num -> Num.abs (num - 1) +``` + +(If the arguments were reversed, this would be `List.map (\num -> Num.abs (num - 1)) numbers` and the +extra parentheses would be required.) + +Neither of these benefits is compatible with the argument ordering currying encourages. Currying encourages +`List.map` to take the `List` as its second argument instead of the first, so that you can partially apply it +like `(List.map Num.abs)`; if Roc introduced currying but kept the order of `List.map` the same way it is today, +then partially applying `List.map` (e.g. `(List.map numbers)`) would be much less useful than if the arguments +were swapped - but that in turn would make it less useful with `|>` and would require parentheses when passing +it an anonymous function. + +This is a fundamental design tension. One argument order works well with `|>` (at least the way it works in Roc +today) and with passing anonymous functions to higher-order functions, and the other works well with currying. +It's impossible to have both. + +Of note, one possible design is to have currying while also having `|>` pass the *last* argument instead of the first. +This is what Elm does, and it makes pipeline-friendliness and curry-friendliness the same thing. However, it also +means that either `|> Str.concat "!"` would add the `"!"` to the front of the string, or else `Str.concat`'s +arguments would have to be flipped - meaning that `Str.concat "Hello, World" "!"` would evaluate to `"!Hello, World"`. + +The only way to have `Str.concat` work the way it does in Roc today (where both pipelines and non-pipeline calling +do what you'd want them to) is to order function arguments in a way that is not conducive to currying. This design +tension only exists if there's currying in the language; without it, you can order arguments for pipeline-friendliness +without concern. + +### Currying and learning curve + +Prior to designing Roc, I taught a lot of beginner [Elm](https://elm-lang.org/) workshops. Sometimes at +conferences, sometimes for [Frontend Masters](https://frontendmasters.com/courses/intro-elm/), +sometimes for free at local coding bootcamps or meetup groups. +In total I've spent well over 100 hours standing in front of a class, introdcuing the students to their +first pure functional programming language. + +Here was my experience teaching currying: + +* The only way to avoid teaching it is to refuse to explain why multi-argument functions have multiple `->`s in them. (If you don't explain it, at least one student will ask about it - and many if not all of the others will wonder.) +* Teaching currying properly takes a solid chunk of time, because it requires explaining partial application, explaining how curried functions facilitate partial application, how function signatures accurately reflect that they're curried, and going through examples for all of these. +* Even after doing all this, and iterating on my approach each time to try to explain it more effectively than I had the time before, I'd estimate that under 50% of the class ended up actually understanding currying. I consistently heard that in practice it only "clicked" for most people after spending significantly more time writing code with it. + +This is not the end of the world, especially because it's easy enough to think "okay, I still don't totally get this +even after that explanation, but I can remember that function arguments are separated by `->` in this language +and maybe I'll understand the rest later." (Which they almost always do, if they stick with the language.) +Clearly currying doesn't preclude a language from being easy to learn, because Elm has currying, and Elm's learning +curve is famously gentle. + +That said, beginners who feel confused while learning the language are less likely to continue with it. +And however easy Roc would be to learn if it had currying, the language is certainly easier to learn without it. + +### Pointfree function composition + +[Pointfree function composition](https://en.wikipedia.org/wiki/Tacit_programming) is where you define +a new function by composing together two existing functions without naming intermediate arguments. +Here's an example: + +```elm +reverseSort : List elem -> List elem +reverseSort = compose List.reverse List.sort + +compose : (a -> b), (c -> a) -> (c -> b) +compose = \f, g, x -> f (g x) +``` + +Here's how I would instead write this: + +```elm +reverseSort : List elem -> List elem +reverseSort = \list -> List.reverse (List.sort list) +``` + +I've consistently found that I can more quickly and accurately understand function definitions that use +named arguments, even though the code is longer. I suspect this is because I'm faster at reading than I am at +desugaring, and whenever I read the top version I end up needing to mentally desugar it into the bottom version. +In more complex examples (this is among the tamest pointfree function composition examples I've seen), I make +a mistake in my mental desugaring, and misunderstand what the function is doing - which can cause bugs. + +I assumed I would get faster and more accurate at this over time. However, by now it's been about a decade +since I first learned about the technique, and I'm still slower and less accurate at reading code that uses +pointfree function composition (including if I wrote it - but even moreso if I didn't) than code written with +with named arguments. I've asked a lot of other programmers about their experiences with pointfree function +composition over the years, and the overwhelming majority of responses have been consistent with my experience. + +As such, my opinion about pointfree function composition has gotten less and less nuanced over time. I've now moved +past "it's the right tool for the job, sometimes" to concluding it's best thought of as an antipattern. This is +because I realized how much time I was spending evaluating on a case-by-case basis whether it might be the +right fit for a given situation. The time spent on this analysis alone vastly outweighed the sum of all the +benefits I got in the rare cases where I concluded it was a fit. So I've found the way to get the most out of +pointfree function composition is to never even think about using it; every other strategy leads to a worse outcome. + +Currying facilitates the antipattern of pointfree function composition, which I view as a downside of currying. + +Stacking up all these downsides of currying against the one upside of making certain function calls more concise, +I concluded that it would be a mistake to have it in Roc. + +## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP? + +Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious +effort to focus on the Roc Editor *instead of* adding Roc support to other editors - specifically in order to give the Roc +Editor the best possible chance at kickstarting a virtuous cycle of plugin authorship. + +This is an unusual approach, but there are more details in [this 2021 interview](https://youtu.be/ITrDd6-PbvY?t=212). + +In the meantime, using CoffeeScript syntax highlighting for .roc files turns out to work surprisingly well! From 6d38bdc5864a0e2de50618c5f77cfba88d110212 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 18 Feb 2022 23:28:47 -0500 Subject: [PATCH 45/56] Add Elijah Schow to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 00f072e430..58357ab2bd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -64,3 +64,4 @@ Jan Van Bruggen Mats Sigge <> Drew Lazzeri Tom Dohrmann +Elijah Schow From 054cb881b1dd2102ffe843d84a2d223fdbc849f8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 18 Feb 2022 23:36:32 -0500 Subject: [PATCH 46/56] Add FAQ to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 777f2bc0d9..6197a8a6ac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The [tutorial](TUTORIAL.md) is the best place to learn about how to use the lang There's also a folder of [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) - the [CLI example](https://github.com/rtfeldman/roc/tree/trunk/examples/cli) in particular is a reasonable starting point to build on. -[Roc Zulip chat](https://roc.zulipchat.com) is the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects. +If you have a specific question, the [FAQ](FAQ.md) might have an answer, although [Roc Zulip chat](https://roc.zulipchat.com) is overall the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects. ## State of Roc From 99f6dd7e7bd2f49718e93ecb1eb38180dda07627 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 21:14:01 -0800 Subject: [PATCH 47/56] fully integrate storage manager removing old code --- compiler/gen_dev/src/generic64/aarch64.rs | 20 +++-- compiler/gen_dev/src/generic64/mod.rs | 92 +++-------------------- compiler/gen_dev/src/generic64/storage.rs | 27 ++++++- compiler/gen_dev/src/generic64/x86_64.rs | 54 +++++++++---- 4 files changed, 92 insertions(+), 101 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 13f29ce81f..302db9b596 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -159,13 +159,14 @@ impl CallConv for AArch64C #[inline(always)] fn setup_stack( buf: &mut Vec<'_, u8>, - saved_regs: &[AArch64GeneralReg], + saved_general_regs: &[AArch64GeneralReg], + saved_float_regs: &[AArch64FloatReg], requested_stack_size: i32, fn_call_stack_size: i32, ) -> i32 { // Full size is upcast to i64 to make sure we don't overflow here. let full_stack_size = match requested_stack_size - .checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer. + .checked_add(8 * (saved_general_regs.len() + saved_float_regs.len()) as i32 + 8) // The extra 8 is space to store the frame pointer. .and_then(|size| size.checked_add(fn_call_stack_size)) { Some(size) => size, @@ -203,10 +204,14 @@ impl CallConv for AArch64C AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::FP); offset = aligned_stack_size - fn_call_stack_size; - for reg in saved_regs { + for reg in saved_general_regs { offset -= 8; AArch64Assembler::mov_base32_reg64(buf, offset, *reg); } + for reg in saved_float_regs { + offset -= 8; + AArch64Assembler::mov_base32_freg64(buf, offset, *reg); + } aligned_stack_size } else { 0 @@ -219,7 +224,8 @@ impl CallConv for AArch64C #[inline(always)] fn cleanup_stack( buf: &mut Vec<'_, u8>, - saved_regs: &[AArch64GeneralReg], + saved_general_regs: &[AArch64GeneralReg], + saved_float_regs: &[AArch64FloatReg], aligned_stack_size: i32, fn_call_stack_size: i32, ) { @@ -232,10 +238,14 @@ impl CallConv for AArch64C AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::FP, offset); offset = aligned_stack_size - fn_call_stack_size; - for reg in saved_regs { + for reg in saved_general_regs { offset -= 8; AArch64Assembler::mov_reg64_base32(buf, *reg, offset); } + for reg in saved_float_regs { + offset -= 8; + AArch64Assembler::mov_freg64_base32(buf, *reg, offset); + } AArch64Assembler::add_reg64_reg64_imm32( buf, AArch64GeneralReg::ZRSP, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index cef4249980..5244c85cc4 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1,7 +1,7 @@ use crate::{single_register_floats, single_register_integers, Backend, Env, Relocation}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_collections::all::{MutMap, MutSet}; +use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; @@ -53,15 +53,15 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, - // TODO: This should deal with float regs as well. general_saved_regs: &[GeneralReg], + float_saved_regs: &[FloatReg], requested_stack_size: i32, fn_call_stack_size: i32, ) -> i32; fn cleanup_stack<'a>( buf: &mut Vec<'a, u8>, - // TODO: This should deal with float regs as well. general_saved_regs: &[GeneralReg], + float_saved_regs: &[FloatReg], aligned_stack_size: i32, fn_call_stack_size: i32, ); @@ -236,29 +236,6 @@ pub trait Assembler: Sized { fn ret(buf: &mut Vec<'_, u8>); } -#[derive(Clone, Debug, PartialEq)] -pub enum SymbolStorage { - GeneralReg(GeneralReg), - FloatReg(FloatReg), - Base { - offset: i32, - size: u32, - owned: bool, - }, - BaseAndGeneralReg { - reg: GeneralReg, - offset: i32, - size: u32, - owned: bool, - }, - BaseAndFloatReg { - reg: FloatReg, - offset: i32, - size: u32, - owned: bool, - }, -} - pub trait RegTrait: Copy + PartialEq + Eq + std::hash::Hash + std::fmt::Debug + 'static { fn value(&self) -> u8; } @@ -275,7 +252,6 @@ pub struct Backend64Bit< phantom_asm: PhantomData, phantom_cc: PhantomData, env: &'a Env<'a>, - target_info: TargetInfo, interns: &'a mut Interns, helper_proc_gen: CodeGenHelp<'a>, helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>, @@ -292,27 +268,6 @@ pub struct Backend64Bit< join_map: MutMap, storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>, - symbol_storage_map: MutMap>, - - // This should probably be smarter than a vec. - // There are certain registers we should always use first. With pushing and popping, this could get mixed. - general_free_regs: Vec<'a, GeneralReg>, - float_free_regs: Vec<'a, FloatReg>, - - // The last major thing we need is a way to decide what reg to free when all of them are full. - // Theoretically we want a basic lru cache for the currently loaded symbols. - // For now just a vec of used registers and the symbols they contain. - general_used_regs: Vec<'a, (GeneralReg, Symbol)>, - float_used_regs: Vec<'a, (FloatReg, Symbol)>, - - // used callee saved regs must be tracked for pushing and popping at the beginning/end of the function. - general_used_callee_saved_regs: MutSet, - float_used_callee_saved_regs: MutSet, - - free_stack_chunks: Vec<'a, (i32, u32)>, - stack_size: u32, - // The amount of stack space needed to pass args for function calling. - fn_call_stack_size: u32, } /// new creates a new backend that will output to the specific Object. @@ -331,7 +286,6 @@ pub fn new_backend_64bit< phantom_asm: PhantomData, phantom_cc: PhantomData, env, - target_info, interns, helper_proc_gen: CodeGenHelp::new(env.arena, target_info, env.module_id), helper_proc_symbols: bumpalo::vec![in env.arena], @@ -342,18 +296,8 @@ pub fn new_backend_64bit< last_seen_map: MutMap::default(), layout_map: MutMap::default(), free_map: MutMap::default(), - symbol_storage_map: MutMap::default(), literal_map: MutMap::default(), join_map: MutMap::default(), - general_free_regs: bumpalo::vec![in env.arena], - general_used_regs: bumpalo::vec![in env.arena], - general_used_callee_saved_regs: MutSet::default(), - float_free_regs: bumpalo::vec![in env.arena], - float_used_regs: bumpalo::vec![in env.arena], - float_used_callee_saved_regs: MutSet::default(), - free_stack_chunks: bumpalo::vec![in env.arena], - stack_size: 0, - fn_call_stack_size: 0, storage_manager: storage::new_storage_manager(env, target_info), } } @@ -388,25 +332,11 @@ impl< fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) { self.proc_name = Some(name); self.is_self_recursive = Some(is_self_recursive); - self.stack_size = 0; - self.free_stack_chunks.clear(); - self.fn_call_stack_size = 0; self.last_seen_map.clear(); self.layout_map.clear(); self.join_map.clear(); self.free_map.clear(); - self.symbol_storage_map.clear(); self.buf.clear(); - self.general_used_callee_saved_regs.clear(); - self.general_free_regs.clear(); - self.general_used_regs.clear(); - self.general_free_regs - .extend_from_slice(CC::GENERAL_DEFAULT_FREE_REGS); - self.float_used_callee_saved_regs.clear(); - self.float_free_regs.clear(); - self.float_used_regs.clear(); - self.float_free_regs - .extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS); self.helper_proc_symbols.clear(); self.storage_manager.reset(); } @@ -435,13 +365,14 @@ impl< let mut out = bumpalo::vec![in self.env.arena]; // Setup stack. - let mut used_regs = bumpalo::vec![in self.env.arena]; - used_regs.extend(&self.general_used_callee_saved_regs); + let used_general_regs = self.storage_manager.general_used_callee_saved_regs(); + let used_float_regs = self.storage_manager.float_used_callee_saved_regs(); let aligned_stack_size = CC::setup_stack( &mut out, - &used_regs, - self.stack_size as i32, - self.fn_call_stack_size as i32, + &used_general_regs, + &used_float_regs, + self.storage_manager.stack_size() as i32, + self.storage_manager.fn_call_stack_size() as i32, ); let setup_offset = out.len(); @@ -492,9 +423,10 @@ impl< // Cleanup stack. CC::cleanup_stack( &mut out, - &used_regs, + &used_general_regs, + &used_float_regs, aligned_stack_size, - self.fn_call_stack_size as i32, + self.storage_manager.fn_call_stack_size() as i32, ); ASM::ret(&mut out); diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 5ab6558106..e96d8b76d8 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -176,6 +176,26 @@ impl< self.fn_call_stack_size = 0; } + pub fn stack_size(&self) -> u32 { + self.stack_size + } + + pub fn fn_call_stack_size(&self) -> u32 { + self.fn_call_stack_size + } + + pub fn general_used_callee_saved_regs(&self) -> Vec<'a, GeneralReg> { + let mut used_regs = bumpalo::vec![in self.env.arena]; + used_regs.extend(&self.general_used_callee_saved_regs); + used_regs + } + + pub fn float_used_callee_saved_regs(&self) -> Vec<'a, FloatReg> { + let mut used_regs = bumpalo::vec![in self.env.arena]; + used_regs.extend(&self.float_used_callee_saved_regs); + used_regs + } + // Returns true if the symbol is storing a primitive value. pub fn is_stored_primitive(&self, sym: &Symbol) -> bool { matches!( @@ -250,6 +270,7 @@ impl< self.general_free_regs.push(reg); } + #[allow(dead_code)] // This claims a temporary float register and enables is used in the passed in function. // Temporary registers are not safe across call instructions. pub fn with_tmp_float_reg, FloatReg)>( @@ -634,7 +655,7 @@ impl< match self .general_used_regs .iter() - .position(|(used_reg, sym)| reg == *used_reg) + .position(|(used_reg, _sym)| reg == *used_reg) { Some(position) => { let (_, sym) = self.general_used_regs.remove(position); @@ -652,7 +673,7 @@ impl< match self .float_used_regs .iter() - .position(|(used_reg, sym)| reg == *used_reg) + .position(|(used_reg, _sym)| reg == *used_reg) { Some(position) => { let (_, sym) = self.float_used_regs.remove(position); @@ -790,7 +811,7 @@ impl< // Claim a location for every join point parameter to be loaded at. match layout { single_register_integers!() => { - let reg = self.claim_general_reg(buf, symbol); + self.claim_general_reg(buf, symbol); } single_register_floats!() => { self.claim_float_reg(buf, symbol); diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index eb53c7db19..7c6ff36f39 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -163,13 +163,15 @@ impl CallConv for X86_64Syste #[inline(always)] fn setup_stack<'a>( buf: &mut Vec<'a, u8>, - general_saved_regs: &[X86_64GeneralReg], + saved_general_regs: &[X86_64GeneralReg], + saved_float_regs: &[X86_64FloatReg], requested_stack_size: i32, fn_call_stack_size: i32, ) -> i32 { x86_64_generic_setup_stack( buf, - general_saved_regs, + saved_general_regs, + saved_float_regs, requested_stack_size, fn_call_stack_size, ) @@ -178,13 +180,15 @@ impl CallConv for X86_64Syste #[inline(always)] fn cleanup_stack<'a>( buf: &mut Vec<'a, u8>, - general_saved_regs: &[X86_64GeneralReg], + saved_general_regs: &[X86_64GeneralReg], + saved_float_regs: &[X86_64FloatReg], aligned_stack_size: i32, fn_call_stack_size: i32, ) { x86_64_generic_cleanup_stack( buf, - general_saved_regs, + saved_general_regs, + saved_float_regs, aligned_stack_size, fn_call_stack_size, ) @@ -514,21 +518,35 @@ impl CallConv for X86_64Windo #[inline(always)] fn setup_stack<'a>( buf: &mut Vec<'a, u8>, - saved_regs: &[X86_64GeneralReg], + saved_general_regs: &[X86_64GeneralReg], + saved_float_regs: &[X86_64FloatReg], requested_stack_size: i32, fn_call_stack_size: i32, ) -> i32 { - x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size) + x86_64_generic_setup_stack( + buf, + saved_general_regs, + saved_float_regs, + requested_stack_size, + fn_call_stack_size, + ) } #[inline(always)] fn cleanup_stack<'a>( buf: &mut Vec<'a, u8>, - saved_regs: &[X86_64GeneralReg], + saved_general_regs: &[X86_64GeneralReg], + saved_float_regs: &[X86_64FloatReg], aligned_stack_size: i32, fn_call_stack_size: i32, ) { - x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size) + x86_64_generic_cleanup_stack( + buf, + saved_general_regs, + saved_float_regs, + aligned_stack_size, + fn_call_stack_size, + ) } #[inline(always)] @@ -706,7 +724,8 @@ impl X86_64WindowsFastcall { #[inline(always)] fn x86_64_generic_setup_stack<'a>( buf: &mut Vec<'a, u8>, - saved_regs: &[X86_64GeneralReg], + saved_general_regs: &[X86_64GeneralReg], + saved_float_regs: &[X86_64FloatReg], requested_stack_size: i32, fn_call_stack_size: i32, ) -> i32 { @@ -714,7 +733,7 @@ fn x86_64_generic_setup_stack<'a>( X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP); let full_stack_size = match requested_stack_size - .checked_add(8 * saved_regs.len() as i32) + .checked_add(8 * (saved_general_regs.len() + saved_float_regs.len()) as i32) .and_then(|size| size.checked_add(fn_call_stack_size)) { Some(size) => size, @@ -741,10 +760,14 @@ fn x86_64_generic_setup_stack<'a>( // Put values at the top of the stack to avoid conflicts with previously saved variables. let mut offset = aligned_stack_size - fn_call_stack_size; - for reg in saved_regs { + for reg in saved_general_regs { X86_64Assembler::mov_base32_reg64(buf, -offset, *reg); offset -= 8; } + for reg in saved_float_regs { + X86_64Assembler::mov_base32_freg64(buf, -offset, *reg); + offset -= 8; + } aligned_stack_size } else { 0 @@ -758,16 +781,21 @@ fn x86_64_generic_setup_stack<'a>( #[allow(clippy::unnecessary_wraps)] fn x86_64_generic_cleanup_stack<'a>( buf: &mut Vec<'a, u8>, - saved_regs: &[X86_64GeneralReg], + saved_general_regs: &[X86_64GeneralReg], + saved_float_regs: &[X86_64FloatReg], aligned_stack_size: i32, fn_call_stack_size: i32, ) { if aligned_stack_size > 0 { let mut offset = aligned_stack_size - fn_call_stack_size; - for reg in saved_regs { + for reg in saved_general_regs { X86_64Assembler::mov_reg64_base32(buf, *reg, -offset); offset -= 8; } + for reg in saved_float_regs { + X86_64Assembler::mov_freg64_base32(buf, *reg, -offset); + offset -= 8; + } X86_64Assembler::add_reg64_reg64_imm32( buf, X86_64GeneralReg::RSP, From 7df6b34a2107198dfe2ec4a4343c64efdc76fbc8 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 21:25:09 -0800 Subject: [PATCH 48/56] correct arg storing index --- compiler/gen_dev/src/generic64/x86_64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 7c6ff36f39..341ffd45a2 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -285,7 +285,7 @@ impl CallConv for X86_64Syste storage_manager.load_to_specified_general_reg( buf, sym, - Self::GENERAL_PARAM_REGS[i], + Self::GENERAL_PARAM_REGS[general_i], ); general_i += 1; } else { @@ -308,7 +308,7 @@ impl CallConv for X86_64Syste storage_manager.load_to_specified_float_reg( buf, sym, - Self::FLOAT_PARAM_REGS[i], + Self::FLOAT_PARAM_REGS[float_i], ); float_i += 1; } else { From 469ecbe6c51f55bddf7a5af83c3212c561f57b03 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 21:29:18 -0800 Subject: [PATCH 49/56] remove todo and use doc comments in more places --- compiler/gen_dev/src/generic64/mod.rs | 30 ++--- compiler/gen_dev/src/generic64/storage.rs | 127 +++++++++++----------- 2 files changed, 75 insertions(+), 82 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 5244c85cc4..e07b593149 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -14,14 +14,6 @@ pub(crate) mod aarch64; pub(crate) mod storage; pub(crate) mod x86_64; -// TODO: StorageManager is still not fully integrated. -// General pieces needed: -// - function call stack fully moved to storage manager -// - remove data that is duplicated here and in storage manager -// To make joinpoints better: -// - consider their parameters to be alive until the last jump to them -// - save the location of the parameters at the start of the joinpoint in a special location. -// - When jumping to them, move the args to the location of the parameters (pushing to stack if necessary) use storage::StorageManager; pub trait CallConv>: @@ -66,8 +58,8 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, @@ -76,8 +68,8 @@ pub trait CallConv, ); - // store_args stores the args in registers and on the stack for function calling. - // It returns the amount of stack space needed to temporarily store the args. + /// store_args stores the args in registers and on the stack for function calling. + /// It returns the amount of stack space needed to temporarily store the args. fn store_args<'a>( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, @@ -137,16 +129,16 @@ pub trait Assembler: Sized { fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String); - // Jumps by an offset of offset bytes unconditionally. - // It should always generate the same number of bytes to enable replacement if offset changes. - // It returns the base offset to calculate the jump from (generally the instruction after the jump). + /// Jumps by an offset of offset bytes unconditionally. + /// It should always generate the same number of bytes to enable replacement if offset changes. + /// It returns the base offset to calculate the jump from (generally the instruction after the jump). fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize; fn tail_call(buf: &mut Vec<'_, u8>) -> u64; - // Jumps by an offset of offset bytes if reg is not equal to imm. - // It should always generate the same number of bytes to enable replacement if offset changes. - // It returns the base offset to calculate the jump from (generally the instruction after the jump). + /// Jumps by an offset of offset bytes if reg is not equal to imm. + /// It should always generate the same number of bytes to enable replacement if offset changes. + /// It returns the base offset to calculate the jump from (generally the instruction after the jump). fn jne_reg64_imm64_imm32( buf: &mut Vec<'_, u8>, reg: GeneralReg, @@ -985,7 +977,7 @@ impl< CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - // Updates a jump instruction to a new offset and returns the number of bytes written. + /// Updates a jump instruction to a new offset and returns the number of bytes written. fn update_jmp_imm32_offset( &mut self, tmp: &mut Vec<'a, u8>, diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index e96d8b76d8..592daf96a5 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -28,31 +28,31 @@ enum RegStorage { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum StackStorage { - // Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. - // Their data must always be 8 byte aligned and will be moved as a block. - // They are never part of a struct, union, or more complex value. - // The rest of the bytes should be zero due to how these are loaded. + /// Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. + /// Their data must always be 8 byte aligned and will be moved as a block. + /// They are never part of a struct, union, or more complex value. + /// The rest of the bytes should be zero due to how these are loaded. Primitive { // Offset from the base pointer in bytes. base_offset: i32, // Optional register also holding the value. reg: Option>, }, - // Referenced Primitives are primitives within a complex structure. - // They have no guarentees about alignment or zeroed bits. - // When they are loaded, they should be aligned and zeroed. - // After loading, they should just be stored in a register. + /// Referenced Primitives are primitives within a complex structure. + /// They have no guarentees about alignment or zeroed bits. + /// When they are loaded, they should be aligned and zeroed. + /// After loading, they should just be stored in a register. ReferencedPrimitive { // Offset from the base pointer in bytes. base_offset: i32, // Size on the stack in bytes. size: u32, }, - // Complex data (lists, unions, structs, str) stored on the stack. - // Note, this is also used for referencing a value within a struct/union. - // It has no alignment guarantees. - // When a primitive value is being loaded from this, it should be moved into a register. - // To start, the primitive can just be loaded as a ReferencePrimitive. + /// Complex data (lists, unions, structs, str) stored on the stack. + /// Note, this is also used for referencing a value within a struct/union. + /// It has no alignment guarantees. + /// When a primitive value is being loaded from this, it should be moved into a register. + /// To start, the primitive can just be loaded as a ReferencePrimitive. Complex { // Offset from the base pointer in bytes. base_offset: i32, @@ -196,7 +196,7 @@ impl< used_regs } - // Returns true if the symbol is storing a primitive value. + /// Returns true if the symbol is storing a primitive value. pub fn is_stored_primitive(&self, sym: &Symbol) -> bool { matches!( self.get_storage_for_sym(sym), @@ -204,8 +204,8 @@ impl< ) } - // Get a general register from the free list. - // Will free data to the stack if necessary to get the register. + /// Get a general register from the free list. + /// Will free data to the stack if necessary to get the register. fn get_general_reg(&mut self, buf: &mut Vec<'a, u8>) -> GeneralReg { if let Some(reg) = self.general_free_regs.pop() { if CC::general_callee_saved(®) { @@ -221,8 +221,8 @@ impl< } } - // Get a float register from the free list. - // Will free data to the stack if necessary to get the register. + /// Get a float register from the free list. + /// Will free data to the stack if necessary to get the register. fn get_float_reg(&mut self, buf: &mut Vec<'a, u8>) -> FloatReg { if let Some(reg) = self.float_free_regs.pop() { if CC::float_callee_saved(®) { @@ -238,8 +238,8 @@ impl< } } - // Claims a general reg for a specific symbol. - // They symbol should not already have storage. + /// Claims a general reg for a specific symbol. + /// They symbol should not already have storage. pub fn claim_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { debug_assert_eq!(self.symbol_storage_map.get(sym), None); let reg = self.get_general_reg(buf); @@ -248,8 +248,8 @@ impl< reg } - // Claims a float reg for a specific symbol. - // They symbol should not already have storage. + /// Claims a float reg for a specific symbol. + /// They symbol should not already have storage. pub fn claim_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { debug_assert_eq!(self.symbol_storage_map.get(sym), None); let reg = self.get_float_reg(buf); @@ -258,8 +258,8 @@ impl< reg } - // This claims a temporary general register and enables is used in the passed in function. - // Temporary registers are not safe across call instructions. + /// This claims a temporary general register and enables is used in the passed in function. + /// Temporary registers are not safe across call instructions. pub fn with_tmp_general_reg, GeneralReg)>( &mut self, buf: &mut Vec<'a, u8>, @@ -271,8 +271,8 @@ impl< } #[allow(dead_code)] - // This claims a temporary float register and enables is used in the passed in function. - // Temporary registers are not safe across call instructions. + /// This claims a temporary float register and enables is used in the passed in function. + /// Temporary registers are not safe across call instructions. pub fn with_tmp_float_reg, FloatReg)>( &mut self, buf: &mut Vec<'a, u8>, @@ -283,10 +283,10 @@ impl< self.float_free_regs.push(reg); } - // Loads a symbol into a general reg and returns that register. - // The symbol must already be stored somewhere. - // Will fail on values stored in float regs. - // Will fail for values that don't fit in a single register. + /// Loads a symbol into a general reg and returns that register. + /// The symbol must already be stored somewhere. + /// Will fail on values stored in float regs. + /// Will fail for values that don't fit in a single register. pub fn load_to_general_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> GeneralReg { let storage = self.remove_storage_for_sym(sym); match storage { @@ -345,10 +345,10 @@ impl< } } - // Loads a symbol into a float reg and returns that register. - // The symbol must already be stored somewhere. - // Will fail on values stored in general regs. - // Will fail for values that don't fit in a single register. + /// Loads a symbol into a float reg and returns that register. + /// The symbol must already be stored somewhere. + /// Will fail on values stored in general regs. + /// Will fail for values that don't fit in a single register. pub fn load_to_float_reg(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) -> FloatReg { let storage = self.remove_storage_for_sym(sym); match storage { @@ -407,11 +407,11 @@ impl< } } - // Loads the symbol to the specified register. - // It will fail if the symbol is stored in a float register. - // This is only made to be used in special cases where exact regs are needed (function args and returns). - // It will not try to free the register first. - // This will not track the symbol change (it makes no assumptions about the new reg). + /// Loads the symbol to the specified register. + /// It will fail if the symbol is stored in a float register. + /// This is only made to be used in special cases where exact regs are needed (function args and returns). + /// It will not try to free the register first. + /// This will not track the symbol change (it makes no assumptions about the new reg). pub fn load_to_specified_general_reg( &self, buf: &mut Vec<'a, u8>, @@ -461,11 +461,11 @@ impl< } } - // Loads the symbol to the specified register. - // It will fail if the symbol is stored in a general register. - // This is only made to be used in special cases where exact regs are needed (function args and returns). - // It will not try to free the register first. - // This will not track the symbol change (it makes no assumptions about the new reg). + /// Loads the symbol to the specified register. + /// It will fail if the symbol is stored in a general register. + /// This is only made to be used in special cases where exact regs are needed (function args and returns). + /// It will not try to free the register first. + /// This will not track the symbol change (it makes no assumptions about the new reg). pub fn load_to_specified_float_reg(&self, buf: &mut Vec<'a, u8>, sym: &Symbol, reg: FloatReg) { match self.get_storage_for_sym(sym) { Reg(Float(old_reg)) @@ -510,8 +510,8 @@ impl< } } - // Loads a field from a struct or tag union. - // This is lazy by default. It will not copy anything around. + /// Loads a field from a struct or tag union. + /// This is lazy by default. It will not copy anything around. pub fn load_field_at_index( &mut self, sym: &Symbol, @@ -564,7 +564,7 @@ impl< } } - // Creates a struct on the stack, moving the data in fields into the struct. + /// Creates a struct on the stack, moving the data in fields into the struct. pub fn create_struct( &mut self, buf: &mut Vec<'a, u8>, @@ -593,10 +593,10 @@ impl< } } - // Copies a symbol to the specified stack offset. This is used for things like filling structs. - // The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan. - // This means that, for example 2 I32s might be back to back on the stack. - // Always interact with the stack using aligned 64bit movement. + /// Copies a symbol to the specified stack offset. This is used for things like filling structs. + /// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan. + /// This means that, for example 2 I32s might be back to back on the stack. + /// Always interact with the stack using aligned 64bit movement. fn copy_symbol_to_stack_offset( &mut self, buf: &mut Vec<'a, u8>, @@ -687,8 +687,8 @@ impl< } } - // Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. - // Note, used and free regs are expected to be updated outside of this function. + /// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. + /// Note, used and free regs are expected to be updated outside of this function. fn free_to_stack( &mut self, buf: &mut Vec<'a, u8>, @@ -731,8 +731,8 @@ impl< } } - // gets the stack offset and size of the specified symbol. - // the symbol must already be stored on the stack. + /// gets the stack offset and size of the specified symbol. + /// the symbol must already be stored on the stack. pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) { match self.get_storage_for_sym(sym) { Stack(Primitive { base_offset, .. }) => (*base_offset, 8), @@ -749,21 +749,21 @@ impl< } } - // Specifies a symbol is loaded at the specified general register. + /// Specifies a symbol is loaded at the specified general register. pub fn general_reg_arg(&mut self, sym: &Symbol, reg: GeneralReg) { self.symbol_storage_map.insert(*sym, Reg(General(reg))); self.general_free_regs.retain(|r| *r != reg); self.general_used_regs.push((reg, *sym)); } - // Specifies a symbol is loaded at the specified float register. + /// Specifies a symbol is loaded at the specified float register. pub fn float_reg_arg(&mut self, sym: &Symbol, reg: FloatReg) { self.symbol_storage_map.insert(*sym, Reg(Float(reg))); self.float_free_regs.retain(|r| *r != reg); self.float_used_regs.push((reg, *sym)); } - // Specifies a primitive is loaded at the specific base offset. + /// Specifies a primitive is loaded at the specific base offset. pub fn primitive_stack_arg(&mut self, sym: &Symbol, base_offset: i32) { self.symbol_storage_map.insert( *sym, @@ -774,13 +774,13 @@ impl< ); } - // Loads the arg pointer symbol to the specified general reg. + /// Loads the arg pointer symbol to the specified general reg. pub fn ret_pionter_arg(&mut self, reg: GeneralReg) { self.symbol_storage_map .insert(Symbol::RET_POINTER, Reg(General(reg))); } - // updates the function call stack size to the max of its current value and the size need for this call. + /// updates the function call stack size to the max of its current value and the size need for this call. pub fn update_fn_call_stack_size(&mut self, tmp_size: u32) { self.fn_call_stack_size = max(self.fn_call_stack_size, tmp_size); } @@ -830,8 +830,8 @@ impl< self.join_param_map.insert(*id, param_storage); } - // Setup jump loads the parameters for the joinpoint. - // This enables the jump to correctly passe arguments to the joinpoint. + /// Setup jump loads the parameters for the joinpoint. + /// This enables the jump to correctly passe arguments to the joinpoint. pub fn setup_jump( &mut self, buf: &mut Vec<'a, u8>, @@ -882,6 +882,7 @@ impl< } self.join_param_map.insert(*id, param_storage); } + /// claim_stack_area is the public wrapper around claim_stack_size. /// It also deals with updating symbol storage. /// It returns the base offset of the stack area. @@ -969,7 +970,7 @@ impl< } } - // Frees an reference and release an allocation if it is no longer used. + /// Frees an reference and release an allocation if it is no longer used. fn free_reference(&mut self, sym: &Symbol) { let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { owned_data From b63d62418b438596a5b57685fc498dc133570437 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 21:30:58 -0800 Subject: [PATCH 50/56] clippy --- compiler/gen_dev/src/generic64/storage.rs | 2 +- compiler/gen_dev/src/generic64/x86_64.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 592daf96a5..df88952f01 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -938,7 +938,7 @@ impl< } pub fn free_symbol(&mut self, sym: &Symbol) { - if let Some(_) = self.join_param_map.remove(&JoinPointId(*sym)) { + if self.join_param_map.remove(&JoinPointId(*sym)).is_some() { // This is a join point and will not be in the storage map. return; } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 341ffd45a2..012a1bca5a 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -278,7 +278,7 @@ impl CallConv for X86_64Syste } let mut general_i = 0; let mut float_i = 0; - for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { + for (sym, layout) in args.iter().zip(arg_layouts.iter()) { match layout { single_register_integers!() => { if general_i < Self::GENERAL_PARAM_REGS.len() { From 427f3e2b683ea0a95c2dfd839457f31710457f3c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 21:35:30 -0800 Subject: [PATCH 51/56] fix typo --- compiler/gen_dev/src/generic64/storage.rs | 2 +- compiler/gen_dev/src/generic64/x86_64.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index df88952f01..ec01957cd4 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -775,7 +775,7 @@ impl< } /// Loads the arg pointer symbol to the specified general reg. - pub fn ret_pionter_arg(&mut self, reg: GeneralReg) { + pub fn ret_pointer_arg(&mut self, reg: GeneralReg) { self.symbol_storage_map .insert(Symbol::RET_POINTER, Reg(General(reg))); } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 012a1bca5a..70ac3f2c47 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -211,7 +211,7 @@ impl CallConv for X86_64Syste let mut general_i = 0; let mut float_i = 0; if X86_64SystemV::returns_via_arg_pointer(ret_layout) { - storage_manager.ret_pionter_arg(Self::GENERAL_PARAM_REGS[0]); + storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]); general_i += 1; } for (layout, sym) in args.iter() { @@ -565,7 +565,7 @@ impl CallConv for X86_64Windo let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer. let mut i = 0; if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) { - storage_manager.ret_pionter_arg(Self::GENERAL_PARAM_REGS[i]); + storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[i]); i += 1; } for (layout, sym) in args.iter() { From 6c0230ae4f92e18d18a8982960c2b97f7db6d100 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 22:10:15 -0800 Subject: [PATCH 52/56] fix typos --- compiler/gen_dev/src/generic64/storage.rs | 2 +- compiler/gen_dev/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index ec01957cd4..1c0e7def8b 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -39,7 +39,7 @@ enum StackStorage { reg: Option>, }, /// Referenced Primitives are primitives within a complex structure. - /// They have no guarentees about alignment or zeroed bits. + /// They have no guarantees about alignment or zeroed bits. /// When they are loaded, they should be aligned and zeroed. /// After loading, they should just be stored in a register. ReferencedPrimitive { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 0bc6a20315..2971d30ddb 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -741,7 +741,7 @@ trait Backend<'a> { /// scan_ast runs through the ast and fill the last seen map. /// This must iterate through the ast in the same way that build_stmt does. i.e. then before else. fn scan_ast(&mut self, stmt: &Stmt<'a>) { - // Join map keeps track of join point paramaters so that we can keep them around while they still might be jumped to. + // Join map keeps track of join point parameters so that we can keep them around while they still might be jumped to. let mut join_map: MutMap]> = MutMap::default(); match stmt { Stmt::Let(sym, expr, _, following) => { From 4e4dcef1b905d55b49c9043122b0a7d57ce79def Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 18 Feb 2022 22:55:19 -0800 Subject: [PATCH 53/56] fix missed register freeing --- compiler/gen_dev/src/generic64/storage.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 1c0e7def8b..8d154a06ee 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -658,8 +658,9 @@ impl< .position(|(used_reg, _sym)| reg == *used_reg) { Some(position) => { - let (_, sym) = self.general_used_regs.remove(position); + let (used_reg, sym) = self.general_used_regs.remove(position); self.free_to_stack(buf, &sym, wanted_reg); + self.general_free_regs.push(used_reg); } None => { internal_error!("wanted register ({:?}) is not used or free", wanted_reg); @@ -676,8 +677,9 @@ impl< .position(|(used_reg, _sym)| reg == *used_reg) { Some(position) => { - let (_, sym) = self.float_used_regs.remove(position); + let (used_reg, sym) = self.float_used_regs.remove(position); self.free_to_stack(buf, &sym, wanted_reg); + self.float_free_regs.push(used_reg); } None => { internal_error!("wanted register ({:?}) is not used or free", wanted_reg); @@ -845,7 +847,6 @@ impl< Some(storages) => storages, None => internal_error!("Jump: unknown point specified to jump to: {:?}", id), }; - println!("Param storage: {:?}", param_storage); for ((sym, layout), wanted_storage) in args.iter().zip(arg_layouts).zip(param_storage.iter()) { @@ -858,7 +859,6 @@ impl< // Ensure the reg is free, if not free it. self.ensure_reg_free(buf, General(*reg)); // Copy the value over to the reg. - println!("Loading {:?} to {:?}", sym, reg); self.load_to_specified_general_reg(buf, sym, *reg) } Reg(Float(reg)) => { From f3ef21773f7e7bd79ef0893c43496ac15c8e8393 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 00:09:27 -0800 Subject: [PATCH 54/56] Add timing info for dev and wasm backend --- cli/src/build.rs | 6 +++++- compiler/build/src/program.rs | 26 ++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index 4786df2e65..98563beedb 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -204,7 +204,11 @@ pub fn build_file<'a>( buf.push_str("Code Generation"); buf.push('\n'); - report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen); + report_timing( + buf, + "Generate Assembly from Mono IR", + code_gen_timing.code_gen, + ); report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file); let compilation_end = compilation_start.elapsed().unwrap(); diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 252a75c050..933e8f9e7a 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -7,7 +7,7 @@ use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::OptLevel; use roc_region::all::LineInfo; use std::path::{Path, PathBuf}; -use std::time::Duration; +use std::time::{Duration, SystemTime}; use roc_collections::all::MutMap; #[cfg(feature = "target-wasm32")] @@ -230,7 +230,6 @@ pub fn gen_from_mono_module_llvm( use inkwell::context::Context; use inkwell::module::Linkage; use inkwell::targets::{CodeModel, FileType, RelocMode}; - use std::time::SystemTime; let code_gen_start = SystemTime::now(); @@ -486,6 +485,7 @@ fn gen_from_mono_module_dev_wasm32( loaded: MonomorphizedModule, app_o_file: &Path, ) -> CodeGenTiming { + let code_gen_start = SystemTime::now(); let MonomorphizedModule { module_id, procedures, @@ -519,9 +519,17 @@ fn gen_from_mono_module_dev_wasm32( procedures, ); + let code_gen = code_gen_start.elapsed().unwrap(); + let emit_o_file_start = SystemTime::now(); + std::fs::write(&app_o_file, &bytes).expect("failed to write object to file"); - CodeGenTiming::default() + let emit_o_file = emit_o_file_start.elapsed().unwrap(); + + CodeGenTiming { + code_gen, + emit_o_file, + } } fn gen_from_mono_module_dev_assembly( @@ -530,6 +538,8 @@ fn gen_from_mono_module_dev_assembly( target: &target_lexicon::Triple, app_o_file: &Path, ) -> CodeGenTiming { + let code_gen_start = SystemTime::now(); + let lazy_literals = true; let generate_allocators = false; // provided by the platform @@ -551,10 +561,18 @@ fn gen_from_mono_module_dev_assembly( let module_object = roc_gen_dev::build_module(&env, &mut interns, target, procedures); + let code_gen = code_gen_start.elapsed().unwrap(); + let emit_o_file_start = SystemTime::now(); + let module_out = module_object .write() .expect("failed to build output object"); std::fs::write(&app_o_file, module_out).expect("failed to write object to file"); - CodeGenTiming::default() + let emit_o_file = emit_o_file_start.elapsed().unwrap(); + + CodeGenTiming { + code_gen, + emit_o_file, + } } From 54896f28e1f68e993597d2e85db3bf2e6bd287bd Mon Sep 17 00:00:00 2001 From: MartinSStewart Date: Sat, 19 Feb 2022 14:09:25 +0100 Subject: [PATCH 55/56] Fix typo in FAQ --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index f04ad30a70..a80cc87ccc 100644 --- a/FAQ.md +++ b/FAQ.md @@ -203,7 +203,7 @@ without concern. Prior to designing Roc, I taught a lot of beginner [Elm](https://elm-lang.org/) workshops. Sometimes at conferences, sometimes for [Frontend Masters](https://frontendmasters.com/courses/intro-elm/), sometimes for free at local coding bootcamps or meetup groups. -In total I've spent well over 100 hours standing in front of a class, introdcuing the students to their +In total I've spent well over 100 hours standing in front of a class, introducing the students to their first pure functional programming language. Here was my experience teaching currying: From 0dae9014febfb455acf7301b7a0ebaa0cca392bc Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 10:59:32 -0800 Subject: [PATCH 56/56] Update comments --- compiler/gen_dev/src/generic64/mod.rs | 5 ++--- compiler/gen_dev/src/generic64/storage.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index e07b593149..6639d27d6f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -58,8 +58,7 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, @@ -69,7 +68,7 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 8d154a06ee..93c3734073 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -29,19 +29,20 @@ enum RegStorage { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum StackStorage { /// Primitives are 8 bytes or less. That generally live in registers but can move stored on the stack. - /// Their data must always be 8 byte aligned and will be moved as a block. + /// Their data, when on the stack, must always be 8 byte aligned and will be moved as a block. /// They are never part of a struct, union, or more complex value. - /// The rest of the bytes should be zero due to how these are loaded. + /// The rest of the bytes should be the sign extension due to how these are loaded. Primitive { // Offset from the base pointer in bytes. base_offset: i32, // Optional register also holding the value. reg: Option>, }, - /// Referenced Primitives are primitives within a complex structure. - /// They have no guarantees about alignment or zeroed bits. - /// When they are loaded, they should be aligned and zeroed. - /// After loading, they should just be stored in a register. + /// Referenced Primitives are primitives within a complex structures. + /// They have no guarantees about the bits around them and cannot simply be loaded as an 8 byte value. + /// For example, a U8 in a struct must be loaded as a single byte and sign extended. + /// If it was loaded as an 8 byte value, a bunch of garbage data would be loaded with the U8. + /// After loading, they should just be stored in a register, removing the reference. ReferencedPrimitive { // Offset from the base pointer in bytes. base_offset: i32,