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),
LoadConstant(LoadConstant),
Add(Add),
SetReturnValue(SetReturnValue),
Return(Return),
}
impl AsmInstruction {
@ -24,6 +26,8 @@ impl AsmInstruction {
}
AsmInstruction::LoadConstant(load_constant) => load_constant.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::expression::Expression;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
pub struct ExpressionStatement {
expression: Box<Expression>,
@ -36,10 +38,23 @@ impl ExpressionStatement {
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
is_last: bool,
) {
match self.expression.as_ref() {
Expression::Call(call) => {
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) => {
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::parameter::Parameter;
use crate::ast::statement::Statement;
@ -158,10 +159,13 @@ impl Function {
) {
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);
for (i, statement) in self.statements.iter().enumerate() {
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();
}
}

View File

@ -43,13 +43,14 @@ impl Statement {
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
is_last: bool,
) {
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);
expression_statement.assemble(context, symbol_table, constants_table, is_last);
}
}
}

View File

@ -1,9 +1,11 @@
use std::fmt::Display;
use std::rc::Rc;
pub type Register = usize;
pub type ConstantName = Rc<str>;
pub type FunctionName = Rc<str>;
pub type ArgCount = usize;
pub type CallerPopCount = usize;
pub enum Instruction {
MoveRegister(Register, Register),
@ -24,4 +26,67 @@ pub enum Instruction {
AddIntInt(Register, Register, Register),
AddStringInt(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> {
function_name: Rc<str>,
active: bool,
instructions: &'a Vec<Instruction>,
ip: usize,
stack: Vec<Value>,
fp: usize,
return_value: Option<Value>,
}
impl<'a> CallFrame<'a> {
fn new(function_name: Rc<str>, instructions: &'a Vec<Instruction>) -> Self {
Self {
function_name,
active: false,
instructions,
ip: 0,
stack: vec![],
fp: 0,
return_value: None,
}
}
@ -86,14 +86,6 @@ impl<'a> CallFrame<'a> {
&self.function_name
}
fn active(&self) -> bool {
self.active
}
fn mark_active(&mut self) {
self.active = true;
}
fn instructions(&self) -> &'a [Instruction] {
self.instructions
}
@ -121,6 +113,18 @@ impl<'a> CallFrame<'a> {
fn set_fp(&mut self, fp: usize) {
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> {
@ -151,6 +155,14 @@ impl<'a> CallStack<'a> {
&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>> {
self.call_frames.get(self.call_frames.len() - 2)
}
@ -182,25 +194,28 @@ pub fn call<'a>(
function.instructions(),
));
// mark it active
call_stack.top_mut().mark_active();
// put each arg on the stack
for argument in arguments {
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()];
// println!("{}", instruction);
match instruction {
/* Move instructions */
Instruction::MoveRegister(source, destination) => {
// copy value from one register to another register
let value = registers[*source].clone();
registers[*destination] = value;
call_stack.top_mut().increment_ip();
}
Instruction::MoveInt(value, destination) => {
registers[*destination] = Value::Int(*value);
call_stack.top_mut().increment_ip();
}
Instruction::MoveStackFrameOffset(offset, destination) => {
// 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();
registers[*destination] = value;
call_stack.top_mut().increment_ip();
}
/* Push instructions */
@ -223,9 +239,11 @@ pub fn call<'a>(
// copy a value from a register to the top of the stack
let value = registers[*source].clone();
call_stack.top_mut().stack_mut().push(value);
call_stack.top_mut().increment_ip();
}
Instruction::PushInt(value) => {
call_stack.top_mut().stack_mut().push(Value::Int(*value));
call_stack.top_mut().increment_ip();
}
Instruction::PushStackFrameOffset(offset) => {
// 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");
let value = call_stack.top().stack()[value_index].clone();
call_stack.top_mut().stack_mut().push(value);
call_stack.top_mut().increment_ip();
}
/* Invoke instructions */
@ -249,6 +268,9 @@ pub fn call<'a>(
let mut args =
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
call_stack.push(CallFrame::new(
function.name_owned(),
@ -274,14 +296,17 @@ pub fn call<'a>(
let result = platform_function(args);
match result {
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);
}
}
Err(error) => {
// Eventually we will have some kind of exception handling
panic!("{}", error);
}
}
call_stack.top_mut().increment_ip();
}
/* Load constant instructions */
@ -292,6 +317,7 @@ pub fn call<'a>(
registers[*destination] = Value::String(string_constant.content_owned());
}
}
call_stack.top_mut().increment_ip();
}
/* Add instructions */
@ -300,18 +326,21 @@ pub fn call<'a>(
let rhs_value = registers[*rhs].unwrap_int();
let result = lhs_value + rhs_value;
registers[*destination] = Value::Int(result);
call_stack.top_mut().increment_ip();
}
Instruction::AddStringInt(lhs, rhs, destination) => {
let lhs_value = registers[*lhs].unwrap_string();
let rhs_value = registers[*rhs].unwrap_int();
let formatted = format!("{}{}", lhs_value, rhs_value);
registers[*destination] = Value::String(Rc::from(formatted));
call_stack.top_mut().increment_ip();
}
Instruction::AddStringString(lhs, rhs, destination) => {
let lhs_value = registers[*lhs].unwrap_string();
let rhs_value = registers[*rhs].unwrap_string();
let formatted = format!("{}{}", lhs_value, rhs_value);
registers[*destination] = Value::String(Rc::from(formatted));
call_stack.top_mut().increment_ip();
}
/* Pop instructions */
@ -320,32 +349,37 @@ pub fn call<'a>(
if let Some(register) = maybe_register {
registers[*register] = value;
}
}
}
// Call frame bookkeeping
let top_frame = call_stack.top();
if top_frame.active() {
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();
match maybe_penultimate_frame {
None => {
panic!("Invariant broken: only one call frame and it's inactive.");
/* Return instructions */
Instruction::SetReturnValue(register) => {
call_stack
.top_mut()
.set_return_value(registers[*register].clone());
call_stack.top_mut().increment_ip();
}
Some(penultimate_frame) => {
if penultimate_frame.active() {
penultimate_frame.increment_ip();
} else {
panic!("Invariant broken: top two call frames are inactive.");
Instruction::Return(caller_pop_count) => {
call_stack.pop(); // pop this frame
let maybe_caller = call_stack.maybe_top_mut();
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
}

View File

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