Work on calling conventions and instructions.

This commit is contained in:
Jesse Brault 2026-03-04 12:13:05 -06:00
parent e1afb6b43b
commit 3cacde6a4c
9 changed files with 200 additions and 93 deletions

View File

@ -1,5 +1,5 @@
use dvm_lib::vm::DvmContext;
use dvm_lib::vm::value::Value;
use dvm_lib::vm::{DvmContext, DvmState};
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::rc::Rc;
@ -35,11 +35,7 @@ impl Display for StdCoreError {
impl Error for StdCoreError {}
pub fn std_core_println(
_context: &DvmContext,
_state: &DvmState,
args: &[Value],
) -> Result<Value, Box<dyn Error>> {
pub fn std_core_println(args: &[Value]) -> Result<Value, Box<dyn Error>> {
let maybe_to_print = args.get(0);
match maybe_to_print {
None => Err(Box::new(StdCoreError::new("Missing to_print arg"))),

View File

@ -9,7 +9,8 @@ use dmc_lib::diagnostic::Diagnostic;
use dmc_lib::parser::parse_compilation_unit;
use dmc_lib::symbol_table::SymbolTable;
use dvm_lib::vm::constant::{Constant, StringConstant};
use dvm_lib::vm::{DvmContext, DvmState, call};
use dvm_lib::vm::value::Value;
use dvm_lib::vm::{CallStack, DvmContext, call};
use std::path::PathBuf;
use std::rc::Rc;
@ -54,7 +55,7 @@ fn main() {
if args.show_asm {
for asm_function in &asm_functions {
println!("{:?}", asm_function);
println!("{:#?}", asm_function);
}
}
@ -75,9 +76,16 @@ fn main() {
)));
}
let mut dvm_state = DvmState::new();
let mut registers: Vec<Value> = vec![];
let mut call_stack = CallStack::new();
let result = call(&dvm_context, &mut dvm_state, "main", vec![]);
let result = call(
&dvm_context,
&mut registers,
&mut call_stack,
"main",
vec![],
);
println!("{:?}", result);
}

View File

@ -107,15 +107,19 @@ impl Pop {
#[derive(Debug)]
pub struct InvokeStatic {
name: String,
arg_count: usize,
}
impl InvokeStatic {
pub fn new(name: &str) -> Self {
Self { name: name.into() }
pub fn new(name: &str, arg_count: usize) -> Self {
Self {
name: name.into(),
arg_count,
}
}
pub fn dvm(&self) -> Instruction {
Instruction::InvokeStatic(Rc::from(self.name.clone()))
Instruction::InvokeStatic(Rc::from(self.name.clone()), self.arg_count)
}
}

View File

@ -194,8 +194,10 @@ impl Call {
InvokePlatformStatic::new(function_symbol.name(), arg_count),
));
} else {
let arg_count = function_symbol.parameters().len();
context.instruction(AsmInstruction::InvokeStatic(InvokeStatic::new(
function_symbol.name(),
arg_count,
)));
}
}

View File

@ -14,7 +14,7 @@ pub enum Instruction {
PushInt(i32),
PushStackFrameOffset(isize),
InvokeStatic(FunctionName),
InvokeStatic(FunctionName, ArgCount),
InvokePlatformStatic(FunctionName, ArgCount),
LoadStringConstant(ConstantName, Register),

View File

@ -1,6 +1,4 @@
use crate::vm::value::Value;
use crate::vm::{DvmContext, DvmState};
use std::error::Error;
pub type PlatformFunction =
fn(context: &DvmContext, state: &DvmState, args: &[Value]) -> Result<Value, Box<dyn Error>>;
pub type PlatformFunction = fn(args: &[Value]) -> Result<Value, Box<dyn Error>>;

View File

@ -41,6 +41,7 @@ impl DvmContext {
&mut self.platform_functions
}
#[deprecated]
pub fn add_function(&mut self, function: Function) {
self.functions.insert(function.name_owned(), function);
}
@ -49,6 +50,7 @@ impl DvmContext {
&self.constants
}
#[deprecated]
pub fn add_constant(&mut self, constant: Constant) {
match &constant {
Constant::String(string_constant) => {
@ -59,63 +61,110 @@ impl DvmContext {
}
}
pub struct DvmState {
stack: Vec<Value>,
registers: Vec<Value>,
pub struct CallFrame<'a> {
function_name: Rc<str>,
active: bool,
instructions: &'a Vec<Instruction>,
ip: usize,
stack: Vec<Value>,
fp: usize,
}
impl DvmState {
pub fn new() -> Self {
impl<'a> CallFrame<'a> {
fn new(function_name: Rc<str>, instructions: &'a Vec<Instruction>) -> Self {
Self {
stack: vec![],
registers: vec![],
function_name,
active: false,
instructions,
ip: 0,
stack: vec![],
fp: 0,
}
}
pub fn stack(&self) -> &Vec<Value> {
&self.stack
fn function_name(&self) -> &str {
&self.function_name
}
pub fn stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.stack
fn active(&self) -> bool {
self.active
}
pub fn registers(&self) -> &Vec<Value> {
&self.registers
fn mark_active(&mut self) {
self.active = true;
}
pub fn registers_mut(&mut self) -> &mut Vec<Value> {
&mut self.registers
fn instructions(&self) -> &'a [Instruction] {
self.instructions
}
pub fn ensure_registers(&mut self, count: usize) {
self.registers.resize_with(count, Default::default);
}
pub fn ip(&self) -> usize {
fn ip(&self) -> usize {
self.ip
}
pub fn increment_ip(&mut self) {
fn increment_ip(&mut self) {
self.ip += 1;
}
pub fn fp(&self) -> usize {
fn stack(&self) -> &[Value] {
&self.stack
}
fn stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.stack
}
fn fp(&self) -> usize {
self.fp
}
pub fn set_fp(&mut self, fp: usize) {
fn set_fp(&mut self, fp: usize) {
self.fp = fp;
}
}
pub fn call(
context: &DvmContext,
state: &mut DvmState,
pub struct CallStack<'a> {
call_frames: Vec<CallFrame<'a>>,
}
impl<'a> CallStack<'a> {
pub fn new() -> Self {
Self {
call_frames: vec![],
}
}
fn push(&mut self, call_frame: CallFrame<'a>) {
self.call_frames.push(call_frame);
}
fn pop(&mut self) -> CallFrame<'a> {
self.call_frames.pop().unwrap()
}
fn top(&self) -> &CallFrame<'a> {
&self.call_frames[self.call_frames.len() - 1]
}
fn top_mut(&mut self) -> &mut CallFrame<'a> {
let call_frames_len = self.call_frames.len();
&mut self.call_frames[call_frames_len - 1] // dang borrow checker
}
fn penultimate(&self) -> Option<&CallFrame<'a>> {
self.call_frames.get(self.call_frames.len() - 2)
}
fn penultimate_mut(&mut self) -> Option<&mut CallFrame<'a>> {
let call_frames_len = self.call_frames.len();
self.call_frames.get_mut(call_frames_len - 2) // dang borrow checker
}
}
pub fn call<'a>(
context: &'a DvmContext,
registers: &mut Vec<Value>,
call_stack: &mut CallStack<'a>,
function_name: &str,
arguments: Vec<Value>,
) -> Option<Value> {
@ -124,81 +173,109 @@ pub fn call(
.get(function_name)
.expect(&format!("Function {} not found", function_name));
let instructions = function.instructions();
state.ensure_registers(function.register_count());
// ensure enough registers
registers.resize_with(function.register_count(), Default::default);
// push call frame
call_stack.push(CallFrame::new(
function.name_owned(),
function.instructions(),
));
// mark it active
call_stack.top_mut().mark_active();
// put each arg on the stack
for argument in arguments {
state.stack_mut().push(argument);
call_stack.top_mut().stack_mut().push(argument);
}
while state.ip() < instructions.len() {
let instruction = &instructions[state.ip()];
while call_stack.top().ip() < call_stack.top().instructions().len() {
let instruction = &call_stack.top().instructions()[call_stack.top().ip()];
match instruction {
/* Move instructions */
Instruction::MoveRegister(source, destination) => {
// copy value from one register to another register
let value = state.registers()[*source].clone();
state.registers_mut()[*destination] = value;
let value = registers[*source].clone();
registers[*destination] = value;
}
Instruction::MoveInt(value, destination) => {
state.registers_mut()[*destination] = Value::Int(*value);
registers[*destination] = Value::Int(*value);
}
Instruction::MoveStackFrameOffset(offset, destination) => {
// copy a value offset from the current frame pointer (fp) to a register
let value_index = state
let value_index =
call_stack
.top()
.fp()
.checked_add_signed(*offset)
.expect("Overflow when adding offset to fp");
let value = state.stack()[value_index].clone();
state.registers_mut()[*destination] = value;
.expect(&format!(
"Overflow when adding offset {} to fp {}",
*offset,
call_stack.top().fp()
));
let value = call_stack.top().stack()[value_index].clone();
registers[*destination] = value;
}
/* Push instructions */
Instruction::PushRegister(source) => {
// copy a value from a register to the top of the stack
let value = state.registers()[*source].clone();
state.stack_mut().push(value);
let value = registers[*source].clone();
call_stack.top_mut().stack_mut().push(value);
}
Instruction::PushInt(value) => {
state.stack_mut().push(Value::Int(*value));
call_stack.top_mut().stack_mut().push(Value::Int(*value));
}
Instruction::PushStackFrameOffset(offset) => {
// copy a value from somewhere on the stack to the top of the stack
let value_index = state
let value_index = call_stack
.top()
.fp()
.checked_add_signed(*offset)
.expect("Overflow when adding offset to fp");
let value = state.stack()[value_index].clone();
state.stack_mut().push(value);
let value = call_stack.top().stack()[value_index].clone();
call_stack.top_mut().stack_mut().push(value);
}
/* Invoke instructions */
Instruction::InvokeStatic(function_name) => {
Instruction::InvokeStatic(function_name, arg_count) => {
let function = context
.functions
.get(function_name)
.expect(&format!("Function {} not found", function_name));
todo!("Call stack for invoking Deimos-native functions")
// get args
let mut args =
call_stack.top().stack()[call_stack.top().stack().len() - arg_count..].to_vec();
// push a new call frame
call_stack.push(CallFrame::new(
function.name_owned(),
function.instructions(),
));
// push args
call_stack.top_mut().stack_mut().append(&mut args);
// set fp for callee frame
let callee_frame = call_stack.top_mut();
callee_frame.set_fp(*arg_count);
}
Instruction::InvokePlatformStatic(function_name, arg_count) => {
let stack = state.stack();
let args = &stack[stack.len() - arg_count..];
let platform_function = context
.platform_functions()
.get(function_name)
.expect(&format!("Platform function {} not found", function_name));
let result = platform_function(context, state, args);
let args = &call_stack.top().stack()[call_stack.top().stack().len() - arg_count..];
let result = platform_function(args);
match result {
Ok(return_value) => {
// pop args off the stack
let mut i: usize = 0;
while i < *arg_count {
state.stack_mut().pop();
i += 1;
}
state.stack_mut().push(return_value);
// push return value to top of caller stack
call_stack.top_mut().stack_mut().push(return_value);
}
Err(error) => {
// Eventually we will have some kind of exception handling
@ -212,41 +289,63 @@ pub fn call(
let constant = &context.constants()[constant_name];
match constant {
Constant::String(string_constant) => {
state.registers_mut()[*destination] =
Value::String(string_constant.content_owned());
registers[*destination] = Value::String(string_constant.content_owned());
}
}
}
/* Add instructions */
Instruction::AddIntInt(lhs, rhs, destination) => {
let lhs_value = state.registers()[*lhs].unwrap_int();
let rhs_value = state.registers()[*rhs].unwrap_int();
let lhs_value = registers[*lhs].unwrap_int();
let rhs_value = registers[*rhs].unwrap_int();
let result = lhs_value + rhs_value;
state.registers_mut()[*destination] = Value::Int(result);
registers[*destination] = Value::Int(result);
}
Instruction::AddStringInt(lhs, rhs, destination) => {
let lhs_value = state.registers()[*lhs].unwrap_string();
let rhs_value = state.registers()[*rhs].unwrap_int();
let lhs_value = registers[*lhs].unwrap_string();
let rhs_value = registers[*rhs].unwrap_int();
let formatted = format!("{}{}", lhs_value, rhs_value);
state.registers_mut()[*destination] = Value::String(Rc::from(formatted));
registers[*destination] = Value::String(Rc::from(formatted));
}
Instruction::AddStringString(lhs, rhs, destination) => {
let lhs_value = state.registers()[*lhs].unwrap_string();
let rhs_value = state.registers()[*rhs].unwrap_string();
let lhs_value = registers[*lhs].unwrap_string();
let rhs_value = registers[*rhs].unwrap_string();
let formatted = format!("{}{}", lhs_value, rhs_value);
state.registers_mut()[*destination] = Value::String(Rc::from(formatted));
registers[*destination] = Value::String(Rc::from(formatted));
}
/* Pop instructions */
Instruction::Pop(maybe_register) => {
let value = state.stack_mut().pop().unwrap();
let value = call_stack.top_mut().stack_mut().pop().unwrap();
if let Some(register) = maybe_register {
state.registers_mut()[*register] = value;
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.");
}
Some(penultimate_frame) => {
if penultimate_frame.active() {
penultimate_frame.increment_ip();
} else {
panic!("Invariant broken: top two call frames are inactive.");
}
}
}
}
state.increment_ip();
}
None // todo: returning results from main functions
}

View File

@ -17,14 +17,14 @@ impl Value {
pub fn unwrap_int(&self) -> i32 {
match self {
Value::Int(i) => *i,
_ => panic!(),
_ => panic!("Attempt to unwrap Int; found {:?}", self),
}
}
pub fn unwrap_string(&self) -> &Rc<str> {
match self {
Value::String(s) => s,
_ => panic!(),
_ => panic!("Attempt to unwrap String; found {:?}", self),
}
}
}

View File

@ -1,9 +1,9 @@
extern fn println(message: Any) -> Void
fn add(a: Int, b: Int) -> Int
a + b
println(a + b)
end
fn main()
println(add(1, 2))
add(1, 2)
end