use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc}; use crate::{ backend::dsa::{ instruction::{InsBlock, Instruction}, registers::{Register, RegisterAllocator}, }, error, model::{CompilerError, Name, TypeId}, }; /// scope object pub struct Scope<'a> { /// outer scope, for a function this will be the global scope. parent: Option<&'a mut Scope<'a>>, alloc: Rc>, /// 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, entry_stack_offset: i32, } impl<'a> Scope<'a> { pub fn new() -> Scope<'a> { let alloc = Rc::new(RefCell::new(Allocator::new())); let entry_stack_offset = alloc.borrow().get_stack_offset(); Self { alloc, entry_stack_offset, parent: None, r#type: ScopeType::Function, variables: HashMap::new(), } } pub fn new_from(parent: &'a mut Scope<'a>, r#type: ScopeType) -> Scope<'a> { let alloc = Rc::clone(&parent.alloc); let entry_stack_offset = alloc.borrow().get_stack_offset(); Self { alloc, entry_stack_offset, parent: Some(parent), r#type, variables: HashMap::new(), } } pub fn create_var( &mut self, name: String, r#type: TypeId, ) -> Result<(), CompilerError> { let mut var = Variable::new(name, r#type.clone()); if r#type.size() > 4 { let slot = self.alloc.borrow_mut().allocate_stack_slot(r#type.size()); var.stack_slot = Some(slot); } else { let reg = self.alloc.borrow_mut().allocate_var()?; var.register = Some(reg); } self.variables.insert(var.name.clone(), var); Ok(()) } pub fn alloc_temp(&mut self) -> Result { self.alloc.borrow_mut().allocate_temp() } pub fn free_temp(&mut self, temp: &TempReg) { self.alloc.borrow_mut().free_temp(temp) } pub fn free_var(&mut self, reg: &AssignedReg) { self.alloc.borrow_mut().free_var(reg); } pub fn close(&mut self) { // tell the allocator that this scope is closing // this reverts the stack offset to what it was before this scope was created. self.alloc.clone().borrow_mut().destroy_scope(self); for var in self.variables.clone().values() { if let Some(reg) = var.register { self.free_var(®); } } } fn get_var(&mut self, var: &str) -> Option<&mut Variable> { self.variables.get_mut(var) } pub fn offset_read( &mut self, var: &str, offset: i32, ) -> Result<(TempReg, Instruction), CompilerError> { if let Some(var) = self.get_var(var) { let slot = var.stack_slot.ok_or_else(|| { error("Attempt to read from a var without a stack slot!") })?; return self.alloc.borrow_mut().offset_read(&slot, offset); } Err(CompilerError::Undefined(Name::new(var, None))) } pub fn offset_write( &mut self, reg: &TempReg, var: &str, offset: i32, ) -> Result { if let Some(var) = self.get_var(var) { let slot = var.stack_slot.ok_or_else(|| { error("Attempt to write to a var without a stack slot!") })?; return Ok(self.alloc.borrow_mut().offset_write(reg, &slot, offset)); } Err(CompilerError::Undefined(Name::new(var, None))) } pub fn load_var( &mut self, var: &str, ) -> Result<(AssignedReg, Instruction), CompilerError> { if let Some(v) = self.get_var(var).cloned() && let Some(slot) = v.stack_slot { let res = self.alloc.borrow_mut().load_var(&slot)?; self.get_var(var).unwrap().register = Some(res.0); return Ok(res); } panic!("e") } pub fn spill_var(&mut self, var: &str) -> Result { if let Some(v) = self.get_var(var).cloned() && let Some(rg) = v.register { let mut slot = v.stack_slot; let res = self.alloc.borrow_mut().spill_var(&rg, &mut slot); self.get_var(var).unwrap().stack_slot = slot; return res; } Err(CompilerError::Undefined(Name::new(var, None))) } } impl Drop for Scope<'_> { fn drop(&mut self) { self.close() } } #[derive(PartialEq, Copy, Clone, Debug)] pub enum ScopeType { Function, IfBlock, LoopBlock, } #[derive(Clone)] pub struct Variable { pub 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. pub size: usize, pub stack_slot: Option, pub register: Option, } impl Variable { pub fn new(name: String, r#type: TypeId) -> Self { Self { name, size: r#type.size(), r#type, stack_slot: None, register: None, } } } pub struct Allocator { stack_offset: i32, in_use: [(Register, bool); 16], } impl Allocator { pub fn new() -> Self { let mut in_use = [(Register::Null, false); 16]; in_use.copy_from_slice(&Register::get_gp().map(|r| (r, false))[0..16]); Self { stack_offset: 0, in_use, } } pub fn get_stack_offset(&self) -> i32 { self.stack_offset } pub fn destroy_scope(&mut self, scope: &mut Scope) { self.stack_offset = scope.entry_stack_offset; for var in scope.variables.drain() { if let Some(assigned) = var.1.register { self.free_var(&assigned); } } } // what we need: // - create var in register from temporary register. free temp and use it. // // - create var on stack from struct/array literal. return stack offset to write to. // // - spill var from register to stack. return stack offset to write to. // // - read/write var from stack+offset into register to use while preserving the stack // slot. // // - read / write bytes from the stack+offset in a larger variable into a register. pub fn offset_read( &mut self, slot: &StackSlot, offset: i32, ) -> Result<(TempReg, Instruction), CompilerError> { let register = self.allocate_temp()?; // instruction: reg = *(&var + offset) Ok(( register.clone(), Instruction::ldw_reg_offset( Register::Spr, *register, (**slot + offset) - self.stack_offset, ), )) } pub fn offset_write( &mut self, reg: &TempReg, slot: &StackSlot, offset: i32, ) -> Instruction { // instruction: *(&var + offset) = reg Instruction::stw_reg_offset( **reg, Register::Spr, (**slot + offset) - self.stack_offset, ) } pub fn load_var( &mut self, slot: &StackSlot, ) -> Result<(AssignedReg, Instruction), CompilerError> { let reg = self.allocate_var()?; Ok(( reg.clone(), Instruction::ldw_reg_offset(Register::Spr, *reg, **slot - self.stack_offset), )) } pub fn spill_var( &mut self, reg: &AssignedReg, slot: &mut Option, // var: &mut Variable, ) -> Result { if let Some(slot) = &slot { let block = Instruction::stw_reg_offset( **reg, Register::Spr, **slot - self.stack_offset, ); self.free_var(reg); return Ok(block); } // var doesn't have a stack slot so we need to create one let new_slot = self.allocate_stack_slot(4); // alloc 4 bytes for reg value. let block = Instruction::push(**reg); self.free_var(reg); *slot = Some(new_slot); Ok(block) } pub fn allocate_stack_slot(&mut self, size: usize) -> StackSlot { self.stack_offset -= size as i32; let offset = self.stack_offset; StackSlot(offset) } pub fn allocate_var(&mut self) -> Result { if let Some(reg) = self.find_free_register() { Ok(AssignedReg(reg)) } else { Err(CompilerError::Generic( "No free registers available".to_string(), )) } } pub fn allocate_temp(&mut self) -> Result { // allocates a temporary register if let Some(reg) = self.find_free_register() { Ok(TempReg(reg)) } else { todo!("an efficient stack spilling algorithm. needs scope awareness."); } } pub fn free_temp(&mut self, temp: &TempReg) { // frees a temporary register. self.in_use[**temp as usize].1 = false; } pub fn free_var(&mut self, reg: &AssignedReg) { // frees a register. self.in_use[**reg as usize].1 = false; } // if we have register(s) free, return the first one. fn find_free_register(&mut self) -> Option { self.in_use.iter_mut().find_map(|(reg, used)| { if !*used { *used = true; Some(*reg) } else { None } }) } } #[derive(Clone, Copy, Debug)] pub struct TempReg(Register); #[derive(Clone, Copy, Debug)] pub struct AssignedReg(Register); #[derive(Clone, Copy, Debug)] pub struct StackSlot(i32); impl Deref for TempReg { type Target = Register; fn deref(&self) -> &Self::Target { &self.0 } } impl Deref for AssignedReg { type Target = Register; fn deref(&self) -> &Self::Target { &self.0 } } impl Deref for StackSlot { type Target = i32; fn deref(&self) -> &Self::Target { &self.0 } }