-
-
Save Robbepop/756eedb75466e09e0262ef917818c553 to your computer and use it in GitHub Desktop.
Simple interpreter architecture using ControlFlow.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[derive(Copy, Clone)] | |
pub enum Instruction { | |
I32Const(i32), | |
I32Add, | |
Br(usize), | |
BrEqz(usize), | |
Ret, | |
} | |
pub type ControlFlow = core::ops::ControlFlow<BreakTarget, ()>; | |
#[derive(Copy, Clone)] | |
pub enum BreakTarget { | |
Goto(usize), | |
Ret, | |
} | |
pub struct Context<'func, 'engine> { | |
pc: usize, | |
insts: &'func [Instruction], | |
stack: &'engine mut Vec<i32>, | |
} | |
impl<'func, 'engine> Context<'func, 'engine> { | |
#[no_mangle] | |
fn execute_until_done(&mut self) -> Result<(), Error> { | |
'outer: loop { | |
let next_inst = self.insts[self.pc]; | |
match self.execute_instruction(next_inst)? { | |
ControlFlow::Continue(()) => { | |
self.pc += 1; | |
} | |
ControlFlow::Break(BreakTarget::Goto(new_pc)) => { | |
self.pc = new_pc; | |
} | |
ControlFlow::Break(BreakTarget::Ret) => { | |
break 'outer | |
} | |
} | |
} | |
Ok(()) | |
} | |
#[inline(always)] | |
fn execute_instruction(&mut self, inst: Instruction) -> Result<ControlFlow, Error> { | |
match inst { | |
Instruction::I32Const(value) => { | |
let sp = self.stack.len(); | |
self.stack.get_mut(sp - 1) | |
.map(|cell| *cell = value) | |
.ok_or(Error::BugInInterpreter)?; | |
Ok(ControlFlow::Continue(())) | |
} | |
Instruction::I32Add => { | |
let rhs = self.stack.pop().ok_or(Error::BugInInterpreter)?; | |
let lhs = self.stack.pop().ok_or(Error::BugInInterpreter)?; | |
let result = lhs.wrapping_add(rhs); | |
let sp = self.stack.len(); | |
self.stack.get_mut(sp - 1) | |
.map(|cell| *cell = result) | |
.ok_or(Error::BugInInterpreter)?; | |
Ok(ControlFlow::Continue(())) | |
} | |
Instruction::Br(target) => { | |
Ok(ControlFlow::Break(BreakTarget::Goto(target))) | |
} | |
Instruction::BrEqz(target) => { | |
let value = self.stack.pop().ok_or(Error::BugInInterpreter)?; | |
let targets = [self.pc + 1, target]; | |
let new_pc = targets[(value == 0) as usize]; | |
Ok(ControlFlow::Break(BreakTarget::Goto(new_pc))) | |
} | |
Instruction::Ret => { | |
Ok(ControlFlow::Break(BreakTarget::Ret)) | |
} | |
} | |
} | |
} | |
pub struct Function { | |
insts: Vec<Instruction>, | |
} | |
#[derive(Debug)] | |
pub enum Error { | |
BugInInterpreter, | |
} | |
pub struct Interpreter { | |
stack: Vec<i32>, | |
} | |
impl Default for Interpreter { | |
fn default() -> Self { | |
Self { stack: vec![0x00; 1024] } | |
} | |
} | |
impl Interpreter { | |
fn execute(&mut self, func: &Function) -> Result<(), Error> { | |
Context { | |
pc: 0, | |
insts: &func.insts[..], | |
stack: &mut self.stack, | |
}.execute_until_done() | |
} | |
} | |
#[no_mangle] | |
fn execute_program() { | |
let mut engine = Interpreter::default(); | |
let func = Function { | |
insts: vec![ | |
Instruction::I32Const(1), | |
Instruction::I32Const(2), | |
Instruction::I32Add, | |
Instruction::Ret, | |
], | |
}; | |
engine.execute(&func).unwrap(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment