From 4ed5da259e844073add8bf895110a2b3d8f6223e Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 14 Feb 2026 20:23:20 +0000 Subject: [PATCH] continued work on new register allocator implementation. next commit will have it integrated if it works --- .zed/tasks.json | 5 + compiler/src/backend/dsa/mod.rs | 1 - compiler/src/backend/dsa/scope.rs | 439 +++++++++++++++++---------- compiler/src/backend/dsa/variable.rs | 93 ------ compiler/src/lib.rs | 6 +- 5 files changed, 283 insertions(+), 261 deletions(-) delete mode 100644 compiler/src/backend/dsa/variable.rs diff --git a/.zed/tasks.json b/.zed/tasks.json index 9f1f15e..cfc0da5 100644 --- a/.zed/tasks.json +++ b/.zed/tasks.json @@ -19,6 +19,11 @@ "command": "cargo run --bin dsx-build", "use_new_terminal": true, }, + { + "label": "Check All", + "command": "cargo clippy --all-targets", + "use_new_terminal": false, + }, { "label": "Build All (Release)", "command": "cargo build --release", diff --git a/compiler/src/backend/dsa/mod.rs b/compiler/src/backend/dsa/mod.rs index 9b7ed48..37fc52d 100644 --- a/compiler/src/backend/dsa/mod.rs +++ b/compiler/src/backend/dsa/mod.rs @@ -4,7 +4,6 @@ mod codegen; mod instruction; mod registers; mod scope; -mod variable; pub fn generate_code(ast: &Program) -> Result { let mut codegen = codegen::CodeGenerator::new(ast.clone()); diff --git a/compiler/src/backend/dsa/scope.rs b/compiler/src/backend/dsa/scope.rs index 911d059..296d7ef 100644 --- a/compiler/src/backend/dsa/scope.rs +++ b/compiler/src/backend/dsa/scope.rs @@ -1,49 +1,211 @@ use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc}; -use uuid::Uuid; - use crate::{ backend::dsa::{ instruction::{InsBlock, Instruction}, registers::{Register, RegisterAllocator}, - variable::Variable, }, - model::CompilerError, + 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], } -pub struct TempReg(Register); -pub struct AssignedReg(Register); -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 - } -} - impl Allocator { pub fn new() -> Self { let mut in_use = [(Register::Null, false); 16]; @@ -64,7 +226,7 @@ impl Allocator { for var in scope.variables.drain() { if let Some(assigned) = var.1.register { - self.free_assigned(&assigned); + self.free_var(&assigned); } } } @@ -82,71 +244,74 @@ impl Allocator { // // - read / write bytes from the stack+offset in a larger variable into a register. - pub fn read_var(&mut self, var: &mut Variable) -> Result { - if let Some(slot) = &mut var.stack_slot { - if var.register.is_none() { - var.register = Some(self.allocate_var()?); - } + pub fn offset_read( + &mut self, + slot: &StackSlot, + offset: i32, + ) -> Result<(TempReg, Instruction), CompilerError> { + let register = self.allocate_temp()?; - if let Some(reg) = &var.register { - return Ok(InsBlock::from(Instruction::ldw_reg_offset( - **reg, - Register::Spr, - **slot - self.stack_offset, - ))); - } - - unreachable!() - } - - Err(CompilerError::Generic(format!( - "Tried to write var {} to stack but var was not assigned a reg and/or stack slot", - var.name - ))) + // instruction: reg = *(&var + offset) + Ok(( + register.clone(), + Instruction::ldw_reg_offset( + Register::Spr, + *register, + (**slot + offset) - self.stack_offset, + ), + )) } - pub fn write_var(&mut self, var: &mut Variable) -> Result { - if let Some(slot) = &var.stack_slot { - if let Some(reg) = &var.register { - return Ok(InsBlock::from(Instruction::stw_reg_offset( - **reg, - Register::Spr, - **slot - self.stack_offset, - ))); - } - } - - Err(CompilerError::Generic(format!( - "Tried to write var {} to stack but var was not assigned a reg and/or stack slot", - var.name - ))) + 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 spill_var(&mut self, var: &mut Variable) -> Result { - if let Some(slot) = &var.stack_slot { - let block = self.write_var(var)?; - if let Some(reg) = &var.register { - self.free_assigned(reg); - var.register = None; - } + 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 - if let Some(reg) = &var.register { - let slot = self.allocate_stack_slot(var.size); - let block = InsBlock::from(Instruction::push(**reg)); + let new_slot = self.allocate_stack_slot(4); // alloc 4 bytes for reg value. + let block = Instruction::push(**reg); - self.free_assigned(reg); - var.register = None; - var.stack_slot = Some(slot); - return Ok(block); - } - - return Err(CompilerError::Generic( - "spill_var called on a variable without a register".to_string(), - )); + self.free_var(reg); + *slot = Some(new_slot); + Ok(block) } pub fn allocate_stack_slot(&mut self, size: usize) -> StackSlot { @@ -179,7 +344,7 @@ impl Allocator { self.in_use[**temp as usize].1 = false; } - fn free_assigned(&mut self, reg: &AssignedReg) { + pub fn free_var(&mut self, reg: &AssignedReg) { // frees a register. self.in_use[**reg as usize].1 = false; } @@ -197,91 +362,33 @@ impl Allocator { } } -pub struct FunctionContext { - name: String, - allocator: RefCell, -} +#[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 FunctionContext { - pub fn new(name: String) -> Self { - Self { - name, - allocator: RefCell::new(Allocator::new()), - } - } +impl Deref for TempReg { + type Target = Register; - pub fn get_stack_offset(&self) -> i32 { - self.allocator.borrow().get_stack_offset() + fn deref(&self) -> &Self::Target { + &self.0 } } -/// scope object -pub struct Scope<'a> { - /// outer scope, for a function this will be the global scope. - parent: Option<&'a mut Scope<'a>>, +impl Deref for AssignedReg { + type Target = Register; - context: 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(parent: &'a mut Scope<'a>, r#type: ScopeType) -> Scope<'a> { - Self { - entry_stack_offset: parent.context.get_stack_offset(), - context: Rc::clone(&parent.context), - parent: Some(parent), - r#type, - variables: HashMap::new(), - } - } - - 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.iter() { - todo!() - // if let Some(reg) = var.allocated_register {} - - // if let Some(offset) = var.bpr_offset { - // self.stack_offset -= offset; - // } - } - - 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!() + fn deref(&self) -> &Self::Target { + &self.0 } } -#[derive(PartialEq, Copy, Clone, Debug)] -pub enum ScopeType { - Function, - IfBlock, - LoopBlock, +impl Deref for StackSlot { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/compiler/src/backend/dsa/variable.rs b/compiler/src/backend/dsa/variable.rs deleted file mode 100644 index 9d17b67..0000000 --- a/compiler/src/backend/dsa/variable.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::{collections::HashMap, hash::Hash, rc::Rc}; - -use uuid::Uuid; - -use crate::{ - backend::dsa::{ - instruction::InsBlock, - registers::Register, - scope::{AssignedReg, FunctionContext, Scope, StackSlot}, - }, - model::{CompilerError, TypeId}, -}; - -pub struct Variable { - pub name: String, - pub uuid: Uuid, - - /// 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_uninit(name: String, r#type: TypeId) -> Self { - Self { - name, - uuid: Uuid::new_v4(), - size: r#type.size(), - r#type, - stack_slot: None, - register: None, - } - } - - pub fn new( - name: String, - r#type: TypeId, - scope: &'_ mut Scope, - ) -> Result { - let mut var = Self::new_uninit(name, r#type); - var.alloc_default(scope); - - Ok(var) - } - - fn alloc_default(&mut self, scope: &'_ mut Scope) { - if self.size > 4 { - self.alloc_stack(scope).unwrap(); - } else { - self.alloc_register(scope).unwrap(); - } - } - - 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 drop(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> { - Ok(()) - } - - pub fn spill(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> { - todo!() - } -} diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 004cfea..460445f 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -4,7 +4,7 @@ use std::path::Path; use common::logging::log; -use crate::specialised::build_specialised; +use crate::{model::CompilerError, specialised::build_specialised}; mod backend; mod frontend; @@ -70,3 +70,7 @@ pub fn compile_file( Ok(()) } + +pub fn error(msg: impl Into) -> CompilerError { + CompilerError::Generic(msg.into()) +}