166 lines
5.5 KiB
Rust
166 lines
5.5 KiB
Rust
|
struct VM<'p> { program: &'p Program, block: u32, ip: u32, stack: Vec<Term>, status: Status }
|
||
|
|
||
|
enum Status {
|
||
|
Running,
|
||
|
AwaitSyscall(Term),
|
||
|
Crash(&'static str),
|
||
|
Exit(Term),
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
struct Term { atom: u32, args: Vec<Term>, }
|
||
|
struct Program { blocks: Vec<Block> }
|
||
|
struct Block { instructions: Vec<Instruction> }
|
||
|
|
||
|
enum Instruction {
|
||
|
// push Atom to stack, optionally with the current `stack` appended to it
|
||
|
Push { atom: u32, cc: bool },
|
||
|
// do a binary operation between the top two stack items
|
||
|
BinOp { op: fn(Term, Term) -> Result<Term, &'static str> },
|
||
|
// expand the top stack item to the stack, failing if its len is not `len`
|
||
|
Expand { len: u32 },
|
||
|
// jump to the given block. its stack is the top stack item
|
||
|
Call { block: u32 },
|
||
|
// system call. pass the top stack item. get a stack item back
|
||
|
Syscall { syscall: u32 },
|
||
|
// kill the program
|
||
|
Crash(&'static str),
|
||
|
// end the program, popping the top stack item
|
||
|
Exit
|
||
|
}
|
||
|
|
||
|
const SYSCALL_DBG: u32 = 0x1000;
|
||
|
|
||
|
|
||
|
fn main() {
|
||
|
host_program(&Program {
|
||
|
blocks: vec![
|
||
|
Block { instructions: vec![
|
||
|
Instruction::Expand { len: 0 },
|
||
|
Instruction::Push { atom: SYSCALL_DBG, cc: false },
|
||
|
Instruction::Push { atom: 0xdead0000, cc: false },
|
||
|
Instruction::Push { atom: 0x0000ba75, cc: false },
|
||
|
Instruction::BinOp { op: binop_add },
|
||
|
Instruction::BinOp { op: binop_append },
|
||
|
Instruction::Syscall { syscall: SYSCALL_DBG },
|
||
|
Instruction::Push { atom: 0x0, cc: false },
|
||
|
Instruction::Exit,
|
||
|
]}
|
||
|
]
|
||
|
}).unwrap();
|
||
|
}
|
||
|
|
||
|
fn host_program(p: &Program) -> Result<Term, String> {
|
||
|
let mut vm = VM::start(p);
|
||
|
loop {
|
||
|
let status = vm.step();
|
||
|
match status {
|
||
|
Status::Running => {}
|
||
|
Status::AwaitSyscall(sc) => {
|
||
|
match sc.atom {
|
||
|
SYSCALL_DBG => {
|
||
|
println!("debug print: {:08x?}", sc.args[0].atom);
|
||
|
vm.complete_syscall(None)
|
||
|
}
|
||
|
_ => { return Err(format!("unknown syscall: {}", sc.atom)) }
|
||
|
}
|
||
|
}
|
||
|
Status::Crash(err) => { return Err(err.to_string()); }
|
||
|
Status::Exit(t) => { return Ok(t.clone()); }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
impl<'p> VM<'p> {
|
||
|
pub fn start(program: &'p Program) -> VM<'p> {
|
||
|
VM {
|
||
|
program,
|
||
|
block: 0,
|
||
|
ip: 0,
|
||
|
stack: vec![Term { atom: 0, args: vec![] }],
|
||
|
status: Status::Running,
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn step<'a>(&'a mut self) -> &'a Status {
|
||
|
let error = match self.internal_step() {
|
||
|
Ok(_) => { return &self.status; }
|
||
|
Err(e) => e
|
||
|
};
|
||
|
self.status = Status::Crash(error);
|
||
|
return &self.status
|
||
|
}
|
||
|
fn internal_step(&mut self) -> Result<(), &'static str> {
|
||
|
if let Status::Running = self.status { } else { return Ok(()); }
|
||
|
|
||
|
let instruction =
|
||
|
self.program
|
||
|
.blocks.get(self.block as usize).ok_or("block must exist")?
|
||
|
.instructions.get(self.ip as usize).ok_or("instruction must exist")?;
|
||
|
self.ip += 1;
|
||
|
|
||
|
match instruction {
|
||
|
&Instruction::Push { atom, cc } => {
|
||
|
let mut term = Term { atom, args: vec![] };
|
||
|
if cc { term.args.extend(self.stack.drain(..)) }
|
||
|
self.stack.push(term)
|
||
|
}
|
||
|
Instruction::BinOp { op } => {
|
||
|
let t1 = self.stack.pop().ok_or("t1 must be present")?;
|
||
|
let t0 = self.stack.pop().ok_or("t0 must be present")?;
|
||
|
self.stack.push(op(t0, t1)?);
|
||
|
}
|
||
|
&Instruction::Expand { len } => {
|
||
|
let term = self.stack.pop().ok_or("term must be present")?;
|
||
|
if term.args.len() != len as usize { panic!("term must have {} args", len) }
|
||
|
}
|
||
|
&Instruction::Call { block } => {
|
||
|
let invocation = self.stack.pop().ok_or("invocation must be present")?;
|
||
|
if invocation.atom != block { panic!("invocation doesn't match block"); }
|
||
|
self.stack.clear();
|
||
|
self.stack.push(invocation);
|
||
|
self.block = block;
|
||
|
self.ip = 0;
|
||
|
|
||
|
}
|
||
|
&Instruction::Syscall { syscall } => {
|
||
|
let invocation = self.stack.pop().ok_or("invocation must be present")?;
|
||
|
if invocation.atom != syscall { panic!("invocation doesn't match syscall"); }
|
||
|
self.status = Status::AwaitSyscall(invocation)
|
||
|
}
|
||
|
Instruction::Crash(c) => {
|
||
|
panic!("crashed: {}", c)
|
||
|
}
|
||
|
Instruction::Exit => {
|
||
|
let exit = self.stack.pop().ok_or("exit arg must be present")?;
|
||
|
self.status = Status::Exit(exit)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
pub fn complete_syscall(&mut self, result: Option<Term>) {
|
||
|
match &self.status {
|
||
|
Status::AwaitSyscall(_) => {
|
||
|
if let Some(t) = result { self.stack.push(t); }
|
||
|
self.status = Status::Running;
|
||
|
}, // continue
|
||
|
_ => {
|
||
|
self.status = Status::Crash("tried to complete a syscall while not awaiting one")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
fn binop_append(mut t0: Term, mut t1: Term) -> Result<Term, &'static str> {
|
||
|
t0.args.push(t1); Ok(t0)
|
||
|
}
|
||
|
|
||
|
fn binop_add(mut t0: Term, mut t1: Term) -> Result<Term, &'static str> {
|
||
|
t0.atom += t1.atom;
|
||
|
Ok(t0)
|
||
|
}
|