use std::{collections::HashMap, hash::Hash}; use crate::{ backend::dsa::{ instruction::{InsBlock, Reg}, registers::Register, }, model::{CompilerError, TypeId}, }; pub struct Variable { name: String, /// the type of the variable. r#type: TypeId, /// size taken up in bytes. /// if size > 4, value must be stored on the stack. size: usize, // location /// this must be None if it cannot be stored in a register. allocated_register: Option, /// represents the offset from the base pointer (Bpr) of the stack frame. /// needs to be signed as offset is positive for function args and negative for local /// variables. as we can't access values at negative offsets, we use the following /// formula: addr = Spr + offset - (Spr - Bpr) where we know (Spr-Bpr) at compile /// time. bpr_offset: Option, } impl Variable { pub fn new_uninit(name: String, r#type: TypeId) -> Self { Self { name, size: r#type.size(), r#type, allocated_register: None, bpr_offset: None, } } fn alloc_register( &mut self, scope: &'_ mut Scope, ) -> Result { if self.size > 4 { return Err(CompilerError::Generic(format!( "Type {} cannot be allocated a register as it has a size of {} bytes", self.r#type, self.size ))); } todo!("integrate with register alloc logic") // self.allocated_register = Some(...) } fn alloc_stack(&mut self, scope: &'_ mut Scope) -> Result { todo!("integrate with stack alloc logic") // self.bpr_offset = Some(...) } pub fn load(&mut self, scope: &'_ mut Scope) -> Result { todo!("load var from stack to reg (if possible)") } pub fn new_local( name: String, r#type: TypeId, scope: &'_ mut Scope, ) -> Result { let mut var = Self::new_uninit(name, r#type); var.alloc_register(scope)?; Ok(var) } pub fn new_stack( name: String, r#type: TypeId, scope: &'_ mut Scope, ) -> Result { let mut init = Self::new_uninit(name, r#type); init.alloc_stack(scope)?; Ok(init) } pub fn drop(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> { if let Some(reg) = self.allocated_register { todo!("dealloc reg in current function") } if let Some(offset) = self.bpr_offset { todo!("free stack slot in current function") } Ok(()) } pub fn spill(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> { todo!() } } /// scope object pub struct Scope<'a> { /// outer scope, for a function this will be the global scope. parent: Option<&'a mut Scope<'a>>, /// is the scope a function body or just a loop? /// depending on the type, ending a scope will have different behaviour r#type: ScopeType, /// variables variables: HashMap, /// tells us if a given register is being used or not. /// this can be an array as registers have u8 representation. in_use: [(Register, bool); 16], stack_offset: i32, } impl<'a> Scope<'a> { pub fn new(parent: &'a mut Scope<'a>, r#type: ScopeType) -> Scope<'a> { Self { stack_offset: parent.stack_offset, parent: Some(parent), r#type, variables: HashMap::new(), in_use: Register::get_gp().map(|reg| (reg, false)), } } pub fn stack_offset(&self) -> i32 { self.stack_offset } pub fn stack_offset_mut(&mut self) -> &mut i32 { &mut self.stack_offset } pub fn close(&mut self) -> Result<(), CompilerError> { // closing a scope means we need to drop all variables in scope and free registers. for (name, var) in self.variables { if let Some(reg) = var.allocated_register { } if let Some(off) } for reg in self.in_use Ok(()) } pub fn alloc_temp_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> { todo!() } pub fn alloc_var_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> { todo!() } pub fn alloc_var_stack(&mut self) -> Result<(Register, InsBlock), CompilerError> { todo!() } pub fn free_var_stack(&mut self) -> Result<(Register, InsBlock), CompilerError> { todo!() } pub fn free_temp_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> { todo!() } } #[derive(PartialEq, Copy, Clone, Debug)] enum ScopeType { Function, IfBlock, LoopBlock, }