continued work on new register allocator implementation.
next commit will have it integrated if it works
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -4,7 +4,6 @@ mod codegen;
|
||||
mod instruction;
|
||||
mod registers;
|
||||
mod scope;
|
||||
mod variable;
|
||||
|
||||
pub fn generate_code(ast: &Program) -> Result<String, CompilerError> {
|
||||
let mut codegen = codegen::CodeGenerator::new(ast.clone());
|
||||
|
||||
+273
-166
@@ -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<RefCell<Allocator>>,
|
||||
|
||||
/// 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<String, Variable>,
|
||||
|
||||
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<TempReg, CompilerError> {
|
||||
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<Instruction, CompilerError> {
|
||||
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<Instruction, CompilerError> {
|
||||
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<StackSlot>,
|
||||
pub register: Option<AssignedReg>,
|
||||
}
|
||||
|
||||
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<InsBlock, CompilerError> {
|
||||
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<InsBlock, CompilerError> {
|
||||
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<InsBlock, CompilerError> {
|
||||
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<StackSlot>,
|
||||
// var: &mut Variable,
|
||||
) -> Result<Instruction, CompilerError> {
|
||||
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<Allocator>,
|
||||
}
|
||||
#[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<FunctionContext>,
|
||||
|
||||
/// 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<Uuid, Variable>,
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<StackSlot>,
|
||||
pub register: Option<AssignedReg>,
|
||||
}
|
||||
|
||||
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<Self, CompilerError> {
|
||||
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<Register, CompilerError> {
|
||||
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<usize, CompilerError> {
|
||||
todo!("integrate with stack alloc logic")
|
||||
|
||||
// self.bpr_offset = Some(...)
|
||||
}
|
||||
|
||||
pub fn load(&mut self, scope: &'_ mut Scope) -> Result<Register, CompilerError> {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -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<String>) -> CompilerError {
|
||||
CompilerError::Generic(msg.into())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user