More work on calling and returning.

This commit is contained in:
Jesse Brault 2026-03-04 15:05:07 -06:00
parent 3cacde6a4c
commit d39e9afee2
7 changed files with 197 additions and 41 deletions

View File

@ -10,6 +10,8 @@ pub enum AsmInstruction {
InvokePlatformStatic(InvokePlatformStatic), InvokePlatformStatic(InvokePlatformStatic),
LoadConstant(LoadConstant), LoadConstant(LoadConstant),
Add(Add), Add(Add),
SetReturnValue(SetReturnValue),
Return(Return),
} }
impl AsmInstruction { impl AsmInstruction {
@ -24,6 +26,8 @@ impl AsmInstruction {
} }
AsmInstruction::LoadConstant(load_constant) => load_constant.dvm(), AsmInstruction::LoadConstant(load_constant) => load_constant.dvm(),
AsmInstruction::Add(add) => add.dvm(), AsmInstruction::Add(add) => add.dvm(),
AsmInstruction::SetReturnValue(set_return_value) => set_return_value.dvm(),
AsmInstruction::Return(asm_return) => asm_return.dvm(),
} }
} }
} }
@ -181,3 +185,33 @@ impl Add {
} }
} }
} }
#[derive(Debug)]
pub struct SetReturnValue {
source_register: usize,
}
impl SetReturnValue {
pub fn new(source_register: usize) -> Self {
Self { source_register }
}
pub fn dvm(&self) -> Instruction {
Instruction::SetReturnValue(self.source_register)
}
}
#[derive(Debug)]
pub struct Return {
caller_pop_count: usize,
}
impl Return {
pub fn new(caller_pop_count: usize) -> Self {
Self { caller_pop_count }
}
pub fn dvm(&self) -> Instruction {
Instruction::Return(self.caller_pop_count)
}
}

View File

@ -1,8 +1,10 @@
use crate::asm::asm_instruction::{AsmInstruction, Pop, SetReturnValue};
use crate::ast::assemble_context::AssembleContext; use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression; use crate::ast::expression::Expression;
use crate::constants_table::ConstantsTable; use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
pub struct ExpressionStatement { pub struct ExpressionStatement {
expression: Box<Expression>, expression: Box<Expression>,
@ -36,10 +38,23 @@ impl ExpressionStatement {
context: &mut AssembleContext, context: &mut AssembleContext,
symbol_table: &SymbolTable, symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable, constants_table: &mut ConstantsTable,
is_last: bool,
) { ) {
match self.expression.as_ref() { match self.expression.as_ref() {
Expression::Call(call) => { Expression::Call(call) => {
call.assemble(context, symbol_table, constants_table); call.assemble(context, symbol_table, constants_table);
// if last expression and the return value of the callee isn't void, return that
// return value
if is_last && !matches!(call.type_info(), TypeInfo::Void) {
// move to register
let return_value_register = context.new_local_register();
context.instruction(AsmInstruction::Pop(Pop::new(return_value_register)));
// set return value
context.instruction(AsmInstruction::SetReturnValue(SetReturnValue::new(
return_value_register,
)));
}
} }
Expression::Additive(additive) => { Expression::Additive(additive) => {
additive.assemble(context, symbol_table, constants_table); additive.assemble(context, symbol_table, constants_table);

View File

@ -1,3 +1,4 @@
use crate::asm::asm_instruction::{AsmInstruction, Return};
use crate::ast::assemble_context::AssembleContext; use crate::ast::assemble_context::AssembleContext;
use crate::ast::parameter::Parameter; use crate::ast::parameter::Parameter;
use crate::ast::statement::Statement; use crate::ast::statement::Statement;
@ -158,10 +159,13 @@ impl Function {
) { ) {
context.new_function(&self.declared_name, &self.declared_name_source_range); context.new_function(&self.declared_name, &self.declared_name_source_range);
context.new_block(&format!("{}_enter", self.declared_name)); context.new_block(&format!("{}_enter", self.declared_name));
for statement in &self.statements { for (i, statement) in self.statements.iter().enumerate() {
statement.assemble(context, symbol_table, constants_table); let is_last = i == self.statements.len() - 1;
statement.assemble(context, symbol_table, constants_table, is_last);
} }
// todo: function exit, including popping passed args and pushing return value // return
context.instruction(AsmInstruction::Return(Return::new(self.parameters.len())));
context.complete_function(); context.complete_function();
} }
} }

View File

@ -43,13 +43,14 @@ impl Statement {
context: &mut AssembleContext, context: &mut AssembleContext,
symbol_table: &SymbolTable, symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable, constants_table: &mut ConstantsTable,
is_last: bool,
) { ) {
match self { match self {
Statement::Let(let_statement) => { Statement::Let(let_statement) => {
let_statement.assemble(context, symbol_table, constants_table); let_statement.assemble(context, symbol_table, constants_table);
} }
Statement::Expression(expression_statement) => { Statement::Expression(expression_statement) => {
expression_statement.assemble(context, symbol_table, constants_table); expression_statement.assemble(context, symbol_table, constants_table, is_last);
} }
} }
} }

View File

@ -1,9 +1,11 @@
use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
pub type Register = usize; pub type Register = usize;
pub type ConstantName = Rc<str>; pub type ConstantName = Rc<str>;
pub type FunctionName = Rc<str>; pub type FunctionName = Rc<str>;
pub type ArgCount = usize; pub type ArgCount = usize;
pub type CallerPopCount = usize;
pub enum Instruction { pub enum Instruction {
MoveRegister(Register, Register), MoveRegister(Register, Register),
@ -24,4 +26,67 @@ pub enum Instruction {
AddIntInt(Register, Register, Register), AddIntInt(Register, Register, Register),
AddStringInt(Register, Register, Register), AddStringInt(Register, Register, Register),
AddStringString(Register, Register, Register), AddStringString(Register, Register, Register),
SetReturnValue(Register),
Return(CallerPopCount),
}
impl Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Instruction::MoveRegister(source, destination) => {
write!(f, "movr r{}, r{}", source, destination)
}
Instruction::MoveInt(i, destination) => {
write!(f, "movi {}, r{}", i, destination)
}
Instruction::MoveStackFrameOffset(offset, destination) => {
write!(f, "movsf {}, r{}", offset, destination)
}
Instruction::PushRegister(source) => {
write!(f, "pushr r{}", source)
}
Instruction::PushInt(i) => {
write!(f, "pushi {}", i)
}
Instruction::PushStackFrameOffset(offset) => {
write!(f, "pushsf {}", offset)
}
Instruction::InvokeStatic(name, arg_count) => {
write!(f, "invoke_static {} (arg_count: {})", name, arg_count)
}
Instruction::InvokePlatformStatic(name, arg_count) => {
write!(
f,
"invoke_platform_static {} (arg_count: {})",
name, arg_count
)
}
Instruction::LoadStringConstant(name, destination) => {
write!(f, "loadsc {}, r{}", name, destination)
}
Instruction::Pop(maybe_destination) => {
if let Some(destination) = maybe_destination {
write!(f, "pop r{}", destination)
} else {
write!(f, "pop")
}
}
Instruction::AddIntInt(left, right, destination) => {
write!(f, "addii r{}, r{}, r{}", left, right, destination)
}
Instruction::AddStringInt(left, right, destination) => {
write!(f, "addsi r{}, r{}, r{}", left, right, destination)
}
Instruction::AddStringString(left, right, destination) => {
write!(f, "addss r{}, r{}, r{}", left, right, destination)
}
Instruction::SetReturnValue(source) => {
write!(f, "srv r{}", source)
}
Instruction::Return(caller_pop_count) => {
write!(f, "return (caller_pop_count: {})", caller_pop_count)
}
}
}
} }

View File

@ -63,22 +63,22 @@ impl DvmContext {
pub struct CallFrame<'a> { pub struct CallFrame<'a> {
function_name: Rc<str>, function_name: Rc<str>,
active: bool,
instructions: &'a Vec<Instruction>, instructions: &'a Vec<Instruction>,
ip: usize, ip: usize,
stack: Vec<Value>, stack: Vec<Value>,
fp: usize, fp: usize,
return_value: Option<Value>,
} }
impl<'a> CallFrame<'a> { impl<'a> CallFrame<'a> {
fn new(function_name: Rc<str>, instructions: &'a Vec<Instruction>) -> Self { fn new(function_name: Rc<str>, instructions: &'a Vec<Instruction>) -> Self {
Self { Self {
function_name, function_name,
active: false,
instructions, instructions,
ip: 0, ip: 0,
stack: vec![], stack: vec![],
fp: 0, fp: 0,
return_value: None,
} }
} }
@ -86,14 +86,6 @@ impl<'a> CallFrame<'a> {
&self.function_name &self.function_name
} }
fn active(&self) -> bool {
self.active
}
fn mark_active(&mut self) {
self.active = true;
}
fn instructions(&self) -> &'a [Instruction] { fn instructions(&self) -> &'a [Instruction] {
self.instructions self.instructions
} }
@ -121,6 +113,18 @@ impl<'a> CallFrame<'a> {
fn set_fp(&mut self, fp: usize) { fn set_fp(&mut self, fp: usize) {
self.fp = fp; self.fp = fp;
} }
fn return_value(&self) -> Option<&Value> {
self.return_value.as_ref()
}
fn set_return_value(&mut self, return_value: Value) {
self.return_value = Some(return_value);
}
fn take_return_value(&mut self) -> Option<Value> {
self.return_value.take()
}
} }
pub struct CallStack<'a> { pub struct CallStack<'a> {
@ -151,6 +155,14 @@ impl<'a> CallStack<'a> {
&mut self.call_frames[call_frames_len - 1] // dang borrow checker &mut self.call_frames[call_frames_len - 1] // dang borrow checker
} }
fn maybe_top(&self) -> Option<&CallFrame<'a>> {
self.call_frames.last()
}
fn maybe_top_mut(&mut self) -> Option<&mut CallFrame<'a>> {
self.call_frames.last_mut()
}
fn penultimate(&self) -> Option<&CallFrame<'a>> { fn penultimate(&self) -> Option<&CallFrame<'a>> {
self.call_frames.get(self.call_frames.len() - 2) self.call_frames.get(self.call_frames.len() - 2)
} }
@ -182,25 +194,28 @@ pub fn call<'a>(
function.instructions(), function.instructions(),
)); ));
// mark it active
call_stack.top_mut().mark_active();
// put each arg on the stack // put each arg on the stack
for argument in arguments { for argument in arguments {
call_stack.top_mut().stack_mut().push(argument); call_stack.top_mut().stack_mut().push(argument);
} }
while call_stack.top().ip() < call_stack.top().instructions().len() { while call_stack.maybe_top().is_some()
&& call_stack.top().ip() < call_stack.top().instructions().len()
{
let instruction = &call_stack.top().instructions()[call_stack.top().ip()]; let instruction = &call_stack.top().instructions()[call_stack.top().ip()];
// println!("{}", instruction);
match instruction { match instruction {
/* Move instructions */ /* Move instructions */
Instruction::MoveRegister(source, destination) => { Instruction::MoveRegister(source, destination) => {
// copy value from one register to another register // copy value from one register to another register
let value = registers[*source].clone(); let value = registers[*source].clone();
registers[*destination] = value; registers[*destination] = value;
call_stack.top_mut().increment_ip();
} }
Instruction::MoveInt(value, destination) => { Instruction::MoveInt(value, destination) => {
registers[*destination] = Value::Int(*value); registers[*destination] = Value::Int(*value);
call_stack.top_mut().increment_ip();
} }
Instruction::MoveStackFrameOffset(offset, destination) => { Instruction::MoveStackFrameOffset(offset, destination) => {
// copy a value offset from the current frame pointer (fp) to a register // copy a value offset from the current frame pointer (fp) to a register
@ -216,6 +231,7 @@ pub fn call<'a>(
)); ));
let value = call_stack.top().stack()[value_index].clone(); let value = call_stack.top().stack()[value_index].clone();
registers[*destination] = value; registers[*destination] = value;
call_stack.top_mut().increment_ip();
} }
/* Push instructions */ /* Push instructions */
@ -223,9 +239,11 @@ pub fn call<'a>(
// copy a value from a register to the top of the stack // copy a value from a register to the top of the stack
let value = registers[*source].clone(); let value = registers[*source].clone();
call_stack.top_mut().stack_mut().push(value); call_stack.top_mut().stack_mut().push(value);
call_stack.top_mut().increment_ip();
} }
Instruction::PushInt(value) => { Instruction::PushInt(value) => {
call_stack.top_mut().stack_mut().push(Value::Int(*value)); call_stack.top_mut().stack_mut().push(Value::Int(*value));
call_stack.top_mut().increment_ip();
} }
Instruction::PushStackFrameOffset(offset) => { Instruction::PushStackFrameOffset(offset) => {
// copy a value from somewhere on the stack to the top of the stack // copy a value from somewhere on the stack to the top of the stack
@ -236,6 +254,7 @@ pub fn call<'a>(
.expect("Overflow when adding offset to fp"); .expect("Overflow when adding offset to fp");
let value = call_stack.top().stack()[value_index].clone(); let value = call_stack.top().stack()[value_index].clone();
call_stack.top_mut().stack_mut().push(value); call_stack.top_mut().stack_mut().push(value);
call_stack.top_mut().increment_ip();
} }
/* Invoke instructions */ /* Invoke instructions */
@ -249,6 +268,9 @@ pub fn call<'a>(
let mut args = let mut args =
call_stack.top().stack()[call_stack.top().stack().len() - arg_count..].to_vec(); call_stack.top().stack()[call_stack.top().stack().len() - arg_count..].to_vec();
// increment caller frame ip
call_stack.top_mut().increment_ip();
// push a new call frame // push a new call frame
call_stack.push(CallFrame::new( call_stack.push(CallFrame::new(
function.name_owned(), function.name_owned(),
@ -274,14 +296,17 @@ pub fn call<'a>(
let result = platform_function(args); let result = platform_function(args);
match result { match result {
Ok(return_value) => { Ok(return_value) => {
// push return value to top of caller stack // push return value to top of caller stack if it's not null
if !matches!(return_value, Value::Null) {
call_stack.top_mut().stack_mut().push(return_value); call_stack.top_mut().stack_mut().push(return_value);
} }
}
Err(error) => { Err(error) => {
// Eventually we will have some kind of exception handling // Eventually we will have some kind of exception handling
panic!("{}", error); panic!("{}", error);
} }
} }
call_stack.top_mut().increment_ip();
} }
/* Load constant instructions */ /* Load constant instructions */
@ -292,6 +317,7 @@ pub fn call<'a>(
registers[*destination] = Value::String(string_constant.content_owned()); registers[*destination] = Value::String(string_constant.content_owned());
} }
} }
call_stack.top_mut().increment_ip();
} }
/* Add instructions */ /* Add instructions */
@ -300,18 +326,21 @@ pub fn call<'a>(
let rhs_value = registers[*rhs].unwrap_int(); let rhs_value = registers[*rhs].unwrap_int();
let result = lhs_value + rhs_value; let result = lhs_value + rhs_value;
registers[*destination] = Value::Int(result); registers[*destination] = Value::Int(result);
call_stack.top_mut().increment_ip();
} }
Instruction::AddStringInt(lhs, rhs, destination) => { Instruction::AddStringInt(lhs, rhs, destination) => {
let lhs_value = registers[*lhs].unwrap_string(); let lhs_value = registers[*lhs].unwrap_string();
let rhs_value = registers[*rhs].unwrap_int(); let rhs_value = registers[*rhs].unwrap_int();
let formatted = format!("{}{}", lhs_value, rhs_value); let formatted = format!("{}{}", lhs_value, rhs_value);
registers[*destination] = Value::String(Rc::from(formatted)); registers[*destination] = Value::String(Rc::from(formatted));
call_stack.top_mut().increment_ip();
} }
Instruction::AddStringString(lhs, rhs, destination) => { Instruction::AddStringString(lhs, rhs, destination) => {
let lhs_value = registers[*lhs].unwrap_string(); let lhs_value = registers[*lhs].unwrap_string();
let rhs_value = registers[*rhs].unwrap_string(); let rhs_value = registers[*rhs].unwrap_string();
let formatted = format!("{}{}", lhs_value, rhs_value); let formatted = format!("{}{}", lhs_value, rhs_value);
registers[*destination] = Value::String(Rc::from(formatted)); registers[*destination] = Value::String(Rc::from(formatted));
call_stack.top_mut().increment_ip();
} }
/* Pop instructions */ /* Pop instructions */
@ -320,32 +349,37 @@ pub fn call<'a>(
if let Some(register) = maybe_register { if let Some(register) = maybe_register {
registers[*register] = value; registers[*register] = value;
} }
}
}
// Call frame bookkeeping
let top_frame = call_stack.top();
if top_frame.active() {
call_stack.top_mut().increment_ip(); call_stack.top_mut().increment_ip();
} else { }
// the "top" frame is inactive, which means it was created above
// set it to active for next instruction round
call_stack.top_mut().mark_active();
let maybe_penultimate_frame = call_stack.penultimate_mut(); /* Return instructions */
match maybe_penultimate_frame { Instruction::SetReturnValue(register) => {
None => { call_stack
panic!("Invariant broken: only one call frame and it's inactive."); .top_mut()
.set_return_value(registers[*register].clone());
call_stack.top_mut().increment_ip();
} }
Some(penultimate_frame) => {
if penultimate_frame.active() { Instruction::Return(caller_pop_count) => {
penultimate_frame.increment_ip(); call_stack.pop(); // pop this frame
} else { let maybe_caller = call_stack.maybe_top_mut();
panic!("Invariant broken: top two call frames are inactive."); if let Some(caller) = maybe_caller {
for _ in 0..*caller_pop_count {
caller.stack_mut().pop();
}
if let Some(return_value) = caller.take_return_value() {
caller.stack_mut().push(return_value);
} }
} }
// do not increment ip of the caller; this was done above BEFORE the call
} }
} }
// if let Some(top) = call_stack.maybe_top() {
// println!(" stack: {:?}", top.stack());
// println!(" registers: {:?}", registers);
// println!(" rv: {:?}", top.return_value());
// }
} }
None // todo: returning results from main functions None // todo: returning results from main functions
} }

View File

@ -1,9 +1,12 @@
extern fn println(message: Any) -> Void extern fn println(message: Any) -> Void
fn add(a: Int, b: Int) -> Int fn add(a: Int, b: Int) -> Void
println(a + b) println(a + b)
end end
fn main() fn main()
add(1, 2) let x = 1 + 2 + 3
add(x, 1)
add(x, 2)
add(21, 21)
end end