Assembling directly from AST.

This commit is contained in:
Jesse Brault 2026-02-28 08:22:57 -06:00
parent e5e1647b70
commit 6593a1cfd1
19 changed files with 401 additions and 46 deletions

View File

@ -2,12 +2,15 @@ use crate::asm::asm_instruction::AsmInstruction;
#[derive(Debug)]
pub struct AsmBlock {
id: usize,
name: String,
instructions: Vec<AsmInstruction>,
}
impl AsmBlock {
pub fn new(id: usize, instructions: Vec<AsmInstruction>) -> Self {
Self { id, instructions }
pub fn new(name: &str, instructions: Vec<AsmInstruction>) -> Self {
Self {
name: name.into(),
instructions,
}
}
}

View File

@ -11,6 +11,7 @@ pub enum AsmInstruction {
pub enum Operand {
IntegerLiteral(i64),
Register(usize),
StackFrameOffset(usize),
}
#[derive(Debug)]

View File

@ -5,8 +5,7 @@ pub mod asm_instruction;
#[cfg(test)]
mod smoke_tests {
use crate::asm::asm_function::AsmFunction;
use crate::ir::assemble_context::AssembleContext;
use crate::ir::Ir;
use crate::constants_table::ConstantsTable;
use crate::parser::parse_compilation_unit;
use crate::symbol_table::SymbolTable;
@ -16,17 +15,7 @@ mod smoke_tests {
compilation_unit.gather_declared_names(&mut symbol_table);
compilation_unit.check_name_usages(&symbol_table);
compilation_unit.type_check(&symbol_table);
let irs = compilation_unit.lower_to_ir();
let mut asm_functions = vec![];
for ir in &irs {
match ir {
Ir::Function(ir_function) => {
asm_functions.push(ir_function.assemble(&mut AssembleContext::new()));
}
_ => {}
}
}
asm_functions
compilation_unit.assemble(&symbol_table, &mut ConstantsTable::new())
}
#[test]

View File

@ -0,0 +1,114 @@
use crate::asm::asm_block::AsmBlock;
use crate::asm::asm_function::AsmFunction;
use crate::asm::asm_instruction::AsmInstruction;
use crate::source_range::SourceRange;
pub struct AssembleContext {
functions: Vec<AsmFunction>,
current_function: Option<FunctionAssembler>,
}
impl AssembleContext {
pub fn new() -> Self {
Self {
functions: vec![],
current_function: None,
}
}
pub fn take_functions(&mut self) -> Vec<AsmFunction> {
std::mem::take(&mut self.functions)
}
pub fn new_function(&mut self, name: &str, source_range: &SourceRange) {
self.current_function = Some(FunctionAssembler::new(name, source_range));
}
pub fn complete_function(&mut self) {
let mut function_assembler = self.current_function.take().unwrap();
function_assembler.complete_block();
let asm_function = function_assembler.result();
self.functions.push(asm_function);
}
pub fn new_block(&mut self, name: &str) {
self.current_function.as_mut().unwrap().new_block(name);
}
pub fn new_local_register(&mut self) -> usize {
self.current_function.as_mut().unwrap().new_local_register()
}
pub fn instruction(&mut self, to_add: AsmInstruction) {
self.current_function.as_mut().unwrap().instruction(to_add);
}
}
struct FunctionAssembler {
name: String,
source_range: SourceRange,
blocks: Vec<AsmBlock>,
current_block: Option<BlockAssembler>,
local_register_counter: usize,
}
impl FunctionAssembler {
fn new(name: &str, source_range: &SourceRange) -> Self {
Self {
name: name.into(),
source_range: source_range.clone(),
blocks: vec![],
current_block: None,
local_register_counter: 0,
}
}
fn new_block(&mut self, name: &str) {
let maybe_block_assembler = self.current_block.take();
if let Some(mut block_assembler) = maybe_block_assembler {
self.blocks.push(block_assembler.result());
}
self.current_block = Some(BlockAssembler::new(name))
}
fn complete_block(&mut self) {
let mut block_assembler = self.current_block.take().unwrap();
self.blocks.push(block_assembler.result());
}
fn result(&mut self) -> AsmFunction {
AsmFunction::new(&self.name, std::mem::take(&mut self.blocks))
}
fn new_local_register(&mut self) -> usize {
let register = self.local_register_counter;
self.local_register_counter += 1;
register
}
fn instruction(&mut self, to_add: AsmInstruction) {
self.current_block.as_mut().unwrap().instruction(to_add);
}
}
struct BlockAssembler {
name: String,
instructions: Vec<AsmInstruction>,
}
impl BlockAssembler {
fn new(name: &str) -> Self {
Self {
name: name.into(),
instructions: vec![],
}
}
fn instruction(&mut self, to_add: AsmInstruction) {
self.instructions.push(to_add);
}
fn result(&mut self) -> AsmBlock {
AsmBlock::new(&self.name, std::mem::take(&mut self.instructions))
}
}

View File

@ -1,9 +1,15 @@
use crate::asm::asm_instruction::{
AsmInstruction, InvokePlatformStatic, LoadConstant, Operand, Push,
};
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression;
use crate::ast::function::FunctionLoweringContext;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::source_range::SourceRange;
use crate::symbol::ExpressibleSymbol;
use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
@ -90,6 +96,63 @@ impl Call {
IrExpression::Call(IrCall::new(function_name, arguments))
}
pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
// push all args
for argument in &self.arguments {
match argument {
Expression::Call(call) => {
call.assemble(context, symbol_table, constants_table); // will leave return val on stack
}
Expression::IntegerLiteral(integer_literal) => {
context.instruction(AsmInstruction::Push(Push::new(Operand::IntegerLiteral(
integer_literal.value(),
))));
}
Expression::String(string_literal) => {
let name = constants_table.insert_string(string_literal.content());
let temp_register = context.new_local_register();
context.instruction(AsmInstruction::LoadConstant(LoadConstant::new(
&name,
temp_register,
)));
context.instruction(AsmInstruction::Push(Push::new(Operand::Register(
temp_register,
))));
}
Expression::Identifier(identifier) => match identifier.expressible_symbol() {
ExpressibleSymbol::Function(_) => {
panic!("Pushing function symbols not supported.")
}
ExpressibleSymbol::Parameter(parameter_symbol) => {
context.instruction(AsmInstruction::Push(Push::new(
Operand::StackFrameOffset(
parameter_symbol.borrow().stack_frame_offset(),
),
)));
}
ExpressibleSymbol::Variable(variable_symbol) => {
context.instruction(AsmInstruction::Push(Push::new(Operand::Register(
variable_symbol.borrow().register(),
))))
}
},
}
}
let function_name = match self.callee() {
Expression::Identifier(identifier) => identifier.name(),
_ => panic!("Calling things other than identifiers not yet supported."),
};
context.instruction(AsmInstruction::InvokePlatformStatic(
InvokePlatformStatic::new(function_name),
));
}
pub fn source_range(&self) -> &SourceRange {
&self.source_range
}

View File

@ -1,4 +1,7 @@
use crate::asm::asm_function::AsmFunction;
use crate::ast::assemble_context::AssembleContext;
use crate::ast::function::Function;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::ir::Ir;
use crate::symbol_table::SymbolTable;
@ -49,4 +52,16 @@ impl CompilationUnit {
}
irs
}
pub fn assemble(
&self,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) -> Vec<AsmFunction> {
let mut context = AssembleContext::new();
for function in &self.functions {
function.assemble(&mut context, symbol_table, constants_table);
}
context.take_functions()
}
}

View File

@ -1,5 +1,7 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression;
use crate::ast::function::FunctionLoweringContext;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_statement::IrStatement;
@ -49,4 +51,18 @@ impl ExpressionStatement {
}
}
}
pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
match self.expression.as_ref() {
Expression::Call(call) => {
call.assemble(context, symbol_table, constants_table);
}
_ => unreachable!(),
}
}
}

View File

@ -1,4 +1,6 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::statement::Statement;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::ir::Ir;
use crate::ir::ir_constant::IrConstant;
@ -93,6 +95,20 @@ impl Function {
irs.push(Ir::Function(ir_function));
irs
}
pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
context.new_function(&self.declared_name, &self.declared_name_source_range);
context.new_block(&format!("{}_enter", self.declared_name));
for statement in &self.statements {
statement.assemble(context, symbol_table, constants_table);
}
context.complete_function();
}
}
pub struct FunctionLoweringContext {

View File

@ -56,11 +56,19 @@ impl Identifier {
ExpressibleSymbol::Function(function_symbol) => {
TypeInfo::Function(function_symbol.clone())
}
ExpressibleSymbol::Parameter(parameter_symbol) => parameter_symbol.type_info().clone(),
ExpressibleSymbol::Variable(variable_symbol) => variable_symbol.type_info().clone(),
ExpressibleSymbol::Parameter(parameter_symbol) => {
parameter_symbol.borrow().type_info().clone()
}
ExpressibleSymbol::Variable(variable_symbol) => {
variable_symbol.borrow().type_info().clone()
}
}
}
pub fn expressible_symbol(&self) -> &ExpressibleSymbol {
self.expressible_symbol.as_ref().unwrap()
}
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) -> IrExpression {
IrExpression::Variable(IrVariable::new(self.name()))
}

View File

@ -1,17 +1,21 @@
use crate::asm::asm_instruction::{AsmInstruction, LoadConstant, Move, Operand, Pop};
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression;
use crate::ast::function::FunctionLoweringContext;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_variable::IrVariable;
use crate::source_range::SourceRange;
use crate::symbol::VariableSymbol;
use crate::symbol::{ExpressibleSymbol, VariableSymbol};
use crate::symbol_table::{SymbolInsertError, SymbolTable};
pub struct LetStatement {
declared_name: String,
declared_name_source_range: SourceRange,
initializer: Box<Expression>,
scope_id: Option<usize>,
}
impl LetStatement {
@ -24,6 +28,7 @@ impl LetStatement {
declared_name: declared_name.to_string(),
declared_name_source_range,
initializer: initializer.into(),
scope_id: None,
}
}
@ -60,6 +65,7 @@ impl LetStatement {
}
}
}
self.scope_id = Some(symbol_table.current_scope_id());
diagnostics
}
@ -79,4 +85,62 @@ impl LetStatement {
let assign_statement = IrAssign::new(destination, data);
context.add_statement(IrStatement::Assign(assign_statement));
}
pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
let destination_register = context.new_local_register();
// save register to symbol
let variable_symbol =
symbol_table.get_variable_symbol(self.scope_id.unwrap(), self.declared_name());
variable_symbol
.borrow_mut()
.set_register(destination_register);
match self.initializer() {
Expression::Call(call) => {
call.assemble(context, symbol_table, constants_table);
context.instruction(AsmInstruction::Pop(Pop::new(destination_register)));
}
Expression::IntegerLiteral(integer_literal) => {
context.instruction(AsmInstruction::Move(Move::new(
Operand::IntegerLiteral(integer_literal.value()),
destination_register,
)));
}
Expression::String(string_literal) => {
let name = constants_table.insert_string(string_literal.content());
context.instruction(AsmInstruction::LoadConstant(LoadConstant::new(
&name,
destination_register,
)));
}
Expression::Identifier(identifier) => {
let expressible_symbol = identifier.expressible_symbol();
match expressible_symbol {
ExpressibleSymbol::Function(_) => {
panic!("Moving functions to registers not yet supported");
}
ExpressibleSymbol::Parameter(parameter_symbol) => {
context.instruction(AsmInstruction::Move(Move::new(
Operand::StackFrameOffset(
parameter_symbol.borrow().stack_frame_offset(),
),
destination_register,
)));
}
ExpressibleSymbol::Variable(variable_symbol) => {
context.instruction(AsmInstruction::Move(Move::new(
Operand::Register(variable_symbol.borrow().register()),
destination_register,
)));
}
}
}
}
}
}

View File

@ -1,3 +1,4 @@
mod assemble_context;
pub mod call;
pub mod compilation_unit;
pub mod expression;
@ -12,6 +13,7 @@ pub mod string_literal;
#[cfg(test)]
mod name_tests {
use crate::constants_table::ConstantsTable;
use crate::ir::Ir;
use crate::ir::assemble_context::AssembleContext;
use crate::parser::parse_compilation_unit;
@ -29,18 +31,10 @@ mod name_tests {
0
);
assert_eq!(compilation_unit.check_name_usages(&symbol_table).len(), 0);
let irs = compilation_unit.lower_to_ir();
for ir in &irs {
println!("{:#?}", ir);
}
for ir in &irs {
match ir {
Ir::Function(ir_function) => {
let asm_function = ir_function.assemble(&mut AssembleContext::new());
println!("{:#?}", asm_function);
}
_ => {}
}
let mut constants_table = ConstantsTable::new();
let asm_functions = compilation_unit.assemble(&symbol_table, &mut constants_table);
for asm_function in &asm_functions {
println!("{:#?}", asm_function);
}
}

View File

@ -1,6 +1,8 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::function::FunctionLoweringContext;
use crate::ast::let_statement::LetStatement;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable;
@ -47,4 +49,20 @@ impl Statement {
}
}
}
pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
match self {
Statement::Let(let_statement) => {
let_statement.assemble(context, symbol_table, constants_table);
}
Statement::Expression(expression_statement) => {
expression_statement.assemble(context, symbol_table, constants_table);
}
}
}
}

View File

@ -0,0 +1,22 @@
use std::collections::HashMap;
pub struct ConstantsTable {
string_counter: usize,
strings_to_names: HashMap<String, String>,
}
impl ConstantsTable {
pub fn new() -> Self {
Self {
string_counter: 0,
strings_to_names: HashMap::new(),
}
}
pub fn insert_string(&mut self, s: &str) -> String {
let name = format!("s_{}", self.string_counter);
self.string_counter += 1;
self.strings_to_names.insert(s.into(), name.clone());
name
}
}

View File

@ -22,7 +22,7 @@ impl IrFunction {
for statement in &self.statements {
instructions.append(&mut statement.assemble(context));
}
let blocks = vec![AsmBlock::new(0, instructions)];
let blocks = vec![AsmBlock::new("oops", instructions)];
AsmFunction::new(&self.name, blocks)
}
}

View File

@ -1,5 +1,6 @@
mod asm;
mod ast;
mod constants_table;
mod diagnostic;
mod ir;
mod lexer;

View File

@ -1,4 +1,5 @@
use crate::symbol::{FunctionSymbol, ParameterSymbol, VariableSymbol};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
@ -6,8 +7,8 @@ pub struct Scope {
debug_name: String,
parent_id: Option<usize>,
function_symbols: HashMap<Rc<str>, Rc<FunctionSymbol>>,
parameter_symbols: HashMap<Rc<str>, Rc<ParameterSymbol>>,
variable_symbols: HashMap<Rc<str>, Rc<VariableSymbol>>,
parameter_symbols: HashMap<Rc<str>, Rc<RefCell<ParameterSymbol>>>,
variable_symbols: HashMap<Rc<str>, Rc<RefCell<VariableSymbol>>>,
}
impl Scope {
@ -29,19 +30,19 @@ impl Scope {
&mut self.function_symbols
}
pub fn parameter_symbols(&self) -> &HashMap<Rc<str>, Rc<ParameterSymbol>> {
pub fn parameter_symbols(&self) -> &HashMap<Rc<str>, Rc<RefCell<ParameterSymbol>>> {
&self.parameter_symbols
}
pub fn parameter_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<ParameterSymbol>> {
pub fn parameter_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<RefCell<ParameterSymbol>>> {
&mut self.parameter_symbols
}
pub fn variable_symbols(&self) -> &HashMap<Rc<str>, Rc<VariableSymbol>> {
pub fn variable_symbols(&self) -> &HashMap<Rc<str>, Rc<RefCell<VariableSymbol>>> {
&self.variable_symbols
}
pub fn variable_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<VariableSymbol>> {
pub fn variable_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<RefCell<VariableSymbol>>> {
&mut self.variable_symbols
}

View File

@ -1,3 +1,4 @@
#[derive(Debug, Clone)]
pub struct SourceRange {
start: usize,
end: usize,

View File

@ -1,4 +1,5 @@
use crate::type_info::TypeInfo;
use std::cell::RefCell;
use std::rc::Rc;
pub struct FunctionSymbol {
@ -34,6 +35,7 @@ impl FunctionSymbol {
pub struct ParameterSymbol {
name: Rc<str>,
type_info: TypeInfo,
stack_frame_offset: Option<usize>,
}
impl ParameterSymbol {
@ -41,6 +43,7 @@ impl ParameterSymbol {
Self {
name: name.into(),
type_info,
stack_frame_offset: None,
}
}
@ -55,11 +58,20 @@ impl ParameterSymbol {
pub fn type_info(&self) -> &TypeInfo {
&self.type_info
}
pub fn set_stack_frame_offset(&mut self, offset: usize) {
self.stack_frame_offset = Some(offset);
}
pub fn stack_frame_offset(&self) -> usize {
self.stack_frame_offset.unwrap()
}
}
pub struct VariableSymbol {
name: Rc<str>,
type_info: TypeInfo,
register: Option<usize>,
}
impl VariableSymbol {
@ -67,6 +79,7 @@ impl VariableSymbol {
Self {
name: name.into(),
type_info,
register: None,
}
}
@ -81,10 +94,18 @@ impl VariableSymbol {
pub fn type_info(&self) -> &TypeInfo {
&self.type_info
}
pub fn set_register(&mut self, register: usize) {
self.register = Some(register);
}
pub fn register(&self) -> usize {
self.register.unwrap()
}
}
pub enum ExpressibleSymbol {
Function(Rc<FunctionSymbol>),
Parameter(Rc<ParameterSymbol>),
Variable(Rc<VariableSymbol>),
Parameter(Rc<RefCell<ParameterSymbol>>),
Variable(Rc<RefCell<VariableSymbol>>),
}

View File

@ -1,5 +1,6 @@
use crate::scope::Scope;
use crate::symbol::{ExpressibleSymbol, FunctionSymbol, ParameterSymbol, VariableSymbol};
use std::cell::RefCell;
use std::rc::Rc;
pub struct SymbolTable {
@ -71,9 +72,10 @@ impl SymbolTable {
parameter_symbol.name(),
)));
}
self.current_scope_mut()
.parameter_symbols_mut()
.insert(parameter_symbol.name_owned(), Rc::new(parameter_symbol));
self.current_scope_mut().parameter_symbols_mut().insert(
parameter_symbol.name_owned(),
Rc::new(RefCell::new(parameter_symbol)),
);
Ok(())
}
@ -86,9 +88,10 @@ impl SymbolTable {
variable_symbol.name(),
)));
}
self.current_scope_mut()
.variable_symbols_mut()
.insert(variable_symbol.name_owned(), Rc::new(variable_symbol));
self.current_scope_mut().variable_symbols_mut().insert(
variable_symbol.name_owned(),
Rc::new(RefCell::new(variable_symbol)),
);
Ok(())
}
@ -125,6 +128,11 @@ impl SymbolTable {
}
None
}
pub fn get_variable_symbol(&self, scope_id: usize, name: &str) -> Rc<RefCell<VariableSymbol>> {
let scope = self.scopes.get(scope_id).unwrap();
scope.variable_symbols().get(name).unwrap().clone()
}
}
pub enum SymbolInsertError {