diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2261ccc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,197 @@ +[root] +name = "daly" +version = "0.1.0" +dependencies = [ + "boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "frunk 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "frunk_core 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "kaktus 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "env_logger" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "frunk" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "frunk_core 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "frunk_derives 0.0.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "frunk_core" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "frunk_derives" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "frunk_core 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kaktus" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maplit" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "regex" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" +"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" +"checksum frunk 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "42b2bd259125dfaba5473097510511f5a1b8207d4b57c452d3c15f0d10e60e65" +"checksum frunk_core 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cd6072440020a11d45de0d90b9262e5c3ef97a15715b6967882929f268e0ae61" +"checksum frunk_derives 0.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0c8eeb7faaf9ce942ed75d2bc700b679fcf2bfa59d2c2e53fbc1a792f109ed" +"checksum kaktus 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee011eaf002da2fffa16feb71bfc9c221aed5f630cae0787d84430b1db13759" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" +"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" +"checksum maplit 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "be384c560e0c3ad868b590ffb88d2c0a1effde6f59885234e4ea811c1202bfea" +"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index fab8b85..18a9985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,10 @@ version = "0.1.0" authors = ["Jasper Schulz "] [dependencies] -maplit = "*" \ No newline at end of file +maplit = "*" +kaktus = "0.1.2" +log = "0.3" +env_logger = "0.3" +frunk = "0.1.22" +frunk_core = "0.0.11" +boolinator = "2.4.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9955f01 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# daly +Simple VM for a Dyon--subset + +To run use: + + cargo run + +To see what is happening internally, you can enable logging: + + RUST_LOG=daly cargo run + +## Current state + +* `main.rs` implements a simple interpreter for some dyon-bytecode. `main()` contains the bytecode of [this](https://github.com/greenMT/example-programs/blob/master/example-programs/dyon/min_loop.dyon) program. + +* the interpreter traces execution of loops + +* `tracerunner.rs` contains an independent execution engine for generated traces + + +### Optimisations + +**Inlining** +Inlining of function calls is performed. Necessary steps for deoptimisation can be found in `tracerunner::Runner::recover`. diff --git a/src/bytecode.rs b/src/bytecode.rs new file mode 100644 index 0000000..85fedab --- /dev/null +++ b/src/bytecode.rs @@ -0,0 +1,40 @@ + + +#[derive(Debug, Clone, Copy)] +pub enum Comp { + Eq, + Lt, + Le, + Gt, + Ge, +} + + +#[derive(Debug, Clone)] +pub enum Instruction { + Call(String), + Return, + + Add, + Cmp(Comp), + + Jump(usize), + JumpIfTrue(usize), + JumpIfFalse(usize), + + Load(usize), + Store(usize), + Const(usize), + + Array(usize), + ArrayGet, + Push, + + Loop, + Break, + + // intrinsics + Len, + Print, + Clone, +} diff --git a/src/conversions.rs b/src/conversions.rs new file mode 100644 index 0000000..6390a57 --- /dev/null +++ b/src/conversions.rs @@ -0,0 +1,80 @@ + +// XXX: there might be a macro which implements From/Into for enums + +use super::*; + +impl From for Value { + fn from(b: bool) -> Self { + Value::Bool(b) + } +} + +impl From for bool { + fn from(val: Value) -> Self { + match val { + Value::Bool(b) => b, + _ => panic!("unexpeted Array variant"), + } + } +} + +impl From> for Value { + fn from(xs: Vec) -> Self { + Value::Array(xs) + } +} + +impl From for Vec { + fn from(val: Value) -> Self { + match val { + Value::Array(xs) => xs, + _ => panic!("unexpeted Array variant"), + } + } +} + +impl AsMut> for Value { + fn as_mut(&mut self) -> &mut Vec { + match *self { + Value::Array(ref mut xs) => xs, + _ => panic!("unexpeted Array variant"), + } + } +} + + +impl From for usize { + fn from(val: Value) -> Self { + match val { + Value::Usize(n) => n, + _ => panic!("unexpeted Array variant"), + } + } +} + +impl From for Value { + fn from(n: usize) -> Self { + Value::Usize(n) + } +} + + +impl<'a> From<&'a Instruction> for TraceInstruction { + fn from(instr: &Instruction) -> TraceInstruction { + use Instruction as I; + use TraceInstruction as TI; + + match *instr { + I::Add => TI::Add, + I::Cmp(c) => TI::Cmp(c), + I::Const(c) => TI::Const(c), + I::Len => TI::Len, + I::Print => TI::Print, + I::Clone => TI::Clone, + I::Array(u) => TI::Array(u), + I::ArrayGet => TI::ArrayGet, + + _ => panic!("can not convert {:?}", instr), + } + } +} diff --git a/src/main.rs b/src/main.rs index 19db0e3..c94b8ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,146 +1,122 @@ -#[macro_use] extern crate maplit; +// btreemap! macro +#[macro_use] +extern crate maplit; + +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate boolinator; +extern crate kaktus; use std::collections::BTreeMap; +use std::rc::Rc; + +use kaktus::{PushPop, Stack}; + +use bytecode::{Instruction, Comp}; +use recovery::{Guard, FrameInfo}; +use tracerunner::Runner; +use repr::{CallFrame, Func, InstrPtr, Value}; + +use traits::vec::ConvertingStack; + +mod bytecode; +mod conversions; +mod recovery; +mod tracerunner; +mod traits; +mod repr; + + +pub type TraceMap = BTreeMap; +pub type ModuleMap = BTreeMap>; + pub struct Module { - funcs: BTreeMap, + funcs: ModuleMap, } -pub struct Func { - name: String, - args: usize, - locals: usize, - instr: Vec, + +#[derive(Debug)] +pub struct Trace { + pub trace: Vec, + pub locals_count: usize, } -#[derive(Debug, Clone, Copy)] -pub enum Comp { - Eq, - Lt, - Le, - Gt, - Ge, +impl Trace { + fn new(trace: Vec, locals_count: usize) -> Self { + Trace { + trace: trace, + locals_count: locals_count, + } + } } -#[derive(Debug, Clone)] -pub enum Instruction { - Call(String), - Return, +#[derive(Debug, Clone)] +pub enum TraceInstruction { Add, - Cmp(Comp), - Jump(usize), - JumpIfTrue(usize), - JumpIfFalse(usize), - Load(usize), Store(usize), - Const(usize), Array(usize), ArrayGet, Push, - Loop, - Break, // intrinsics Len, Print, Clone, - Guard(bool), -} - -#[derive(Debug, Clone)] -pub enum Value { - Null, - Bool(bool), - Usize(usize), - Array(Vec), + Guard(Guard), } -impl From for Value { - fn from(b: bool) -> Self { - Value::Bool(b) - } -} -impl From for bool { - fn from(val: Value) -> Self { - match val { - Value::Bool(b) => b, - _ => panic!("unexpeted Array variant"), - } - } -} -impl From> for Value { - fn from(xs: Vec) -> Self { - Value::Array(xs) - } +struct TraceDataAllocator { + total_size: usize, + offsets: Vec, } -impl From for Vec { - fn from(val: Value) -> Self { - match val { - Value::Array(xs) => xs, - _ => panic!("unexpeted Array variant"), +impl TraceDataAllocator { + fn new() -> Self { + TraceDataAllocator { + total_size: 0, + offsets: Vec::new(), } } -} -impl AsMut> for Value { - fn as_mut(&mut self) -> &mut Vec { - match *self { - Value::Array(ref mut xs) => xs, - _ => panic!("unexpeted Array variant"), - } + fn alloc(&mut self, to_allocate: usize) { + self.offsets.push(self.total_size); + // reserve space at the end + self.total_size += to_allocate; } -} - -// usize -impl From for usize { - fn from(val: Value) -> Self { - match val { - Value::Usize(n) => n, - _ => panic!("unexpeted Array variant"), - } + fn pop(&mut self) { + self.offsets.pop().unwrap(); } -} -impl From for Value { - fn from(n: usize) -> Self { - Value::Usize(n) + fn current(&self) -> usize { + *self.offsets.last().unwrap() } -} - -pub struct CallFrame<'a> { - back_ref: (&'a Func, usize), - args: usize, - locals: Vec, -} -impl<'a> CallFrame<'a> { - fn for_fn(func: &Func, back_ref: (&'a Func, usize)) -> Self { - CallFrame { - back_ref: back_ref, - args: func.args, - locals: vec![Value::Null; func.args+func.locals] } + fn at(&self, idx: usize) -> usize { + self.current() + idx } } + pub struct Interpreter<'a> { module: &'a Module, stack: Vec, - frames: Vec>, + frames: Vec, } - impl<'a> Interpreter<'a> { fn new(module: &'a Module) -> Self { Interpreter { @@ -150,37 +126,54 @@ impl<'a> Interpreter<'a> { } } - fn get_fn(&self, name: &str) -> &'a Func { - self.module.funcs.get(name).unwrap() + fn get_fn(&self, name: &str) -> Rc { + self.module.funcs[name].clone() } - fn trace(&mut self, o_func: &'a Func, o_pc: usize) -> (&'a Func, usize) { + // XXX: why do I return func, pc? shouldn't that be the same as the input? + fn trace(&mut self, instr: &InstrPtr) -> (InstrPtr, Trace) { use Instruction::*; - let mut pc = o_pc; - let mut func = o_func; - let mut trace = Vec::new(); - let mut stack_size = 0; + + let mut call_tree = Stack::root(FrameInfo { + func: instr.func.clone(), + back_ref: self.frames.last().unwrap().back_ref.clone(), + offset: 0, + }); + + let mut locals = TraceDataAllocator::new(); + locals.alloc(instr.func.args_count + instr.func.locals_count); + + let mut next = instr.clone(); + loop { - let instr = &func.instr[pc]; - // println!("{:?}", instr); + let instr = next; + next = instr.next(); + + info!(target: "exec","TRACE: {:?}", instr); - pc += 1; match *instr { - Loop => { - break; - }, + Loop => break, - Break => (), + Break => unimplemented!(), Clone => (), Const(n) => self.do_const(n), Add => self.do_add(), - Load(idx) => self.do_load(idx), - Store(idx) => self.do_store(idx), + Load(idx) => { + self.do_load(idx); + trace.push(TraceInstruction::Load(locals.at(idx))); + continue; + } + + Store(idx) => { + self.do_store(idx); + trace.push(TraceInstruction::Store(locals.at(idx))); + continue; + } Print => self.do_print(), @@ -192,172 +185,195 @@ impl<'a> Interpreter<'a> { ArrayGet => self.do_array_get(), Call(ref target) => { - let new_func = self.module.funcs.get(target).unwrap(); - let mut frame = CallFrame::for_fn(new_func, (func, pc)); + let new_func = &self.module.funcs[target]; + let mut frame = CallFrame::for_fn(new_func, next); + + locals.alloc(frame.locals.len()); - for idx in 0..frame.args { + for idx in 0..frame.args_count { frame.locals[idx] = self.stack.pop().unwrap(); + trace.push(TraceInstruction::Store(locals.at(idx))); } + call_tree = call_tree.push(FrameInfo { + func: new_func.clone(), + back_ref: frame.back_ref.clone(), + offset: locals.current(), + }); + self.frames.push(frame); + next = InstrPtr::for_fn(new_func.clone()); - func = new_func; - pc = 0; + // don't add Call to trace continue; - }, + } Return => { - let frame = self.frames.pop(); + locals.pop(); + let frame = self.frames.pop(); if self.frames.is_empty() { break; } - let (f, rpc) = frame.unwrap().back_ref; - func = f; - pc = rpc; + call_tree = call_tree.pop().unwrap(); + + next = frame.unwrap().back_ref; + + // don't add Return to trace continue; - }, + } Cmp(how) => self.do_cmp(how), Jump(target) => { - pc = target; - // don't trace + next = next.jump(target); + // skip trace continue; } JumpIfFalse(target) => { - let b: bool = self.pop(); + let b: bool = self.stack.pop_into(); if !bool::from(b) { - pc = target; + next = next.jump(target); } - trace.push(Guard(b)); + let guard = Guard { + condition: b, + frame: call_tree.clone(), + pc: instr.pc, + }; + trace.push(TraceInstruction::Guard(guard)); continue; - } _ => panic!("TODO: {:?}", instr), } - trace.push(instr.clone()); + trace.push(TraceInstruction::from(&*instr)); } - println!("{:?}", trace); + info!(target: "trace", "{:?}", trace); - (func, pc) + (instr.clone(), Trace::new(trace, locals.total_size)) } fn run(&mut self) { use Instruction::*; + // `main` function has to exist let main = self.get_fn("main"); - let mut pc = 0; - - self.frames.push(CallFrame::for_fn(&main, (&main, 0))); + // a bit awkward, main would return to main + // maybe it would be better to have Option as back_ref + self.frames.push(CallFrame::for_fn(&main, InstrPtr::new(main.clone(), 0))); - let mut func = main; + let mut traces = TraceMap::new(); + let mut next = InstrPtr::for_fn(main.clone()); loop { - let instr = &func.instr[pc]; - // println!("{:?}", instr); - - pc += 1; - match *instr { - Loop => { - let res = self.trace(func, pc); - func = res.0; - pc = res.1; - }, + // get next instruction + let instr = next; + // pre-set next instruction + next = instr.next(); - Break => (), - - Clone => (), - - Const(n) => self.do_const(n), - Add => self.do_add(), - - Load(idx) => self.do_load(idx), - Store(idx) => self.do_store(idx), - - Print => self.do_print(), + info!("E: {:?}", *instr); + match *instr { + // XXX: do I care about break here? + Break | Clone => (), + + // simple dispatch of opcodes to callbacks + Const(n) => self.do_const(n), + Add => self.do_add(), + Load(idx) => self.do_load(idx), + Store(idx) => self.do_store(idx), + Print => self.do_print(), Array(size) => self.do_array(size), + Len => self.do_len(), + Push => self.do_push(), + ArrayGet => self.do_array_get(), + Cmp(how) => self.do_cmp(how), - Len => self.do_len(), - Push => self.do_push(), + // XXX: currently there is no threshhold value when to start tracing + // meaning that tracing starts immediately + Loop => { + // do we already have a trace for this position? + if let Some(trace) = traces.get(&instr.pc) { + // we need this block, since Runner takes self as &mut + { + info!("T: running trace @{:}[{:}]", instr.func.name, instr.pc); + let mut runner = Runner::new(self, trace); + next = runner.run(); + } + info!("T: return from trace to func {:?} pc {:?}", next.func.name, next.pc); + info!("T: STACK: {:?}", self.stack); + info!("T: FRAME: {:?}", self.frames.last().unwrap().locals); + continue; + } - ArrayGet => self.do_array_get(), + // no trace found => start tracing (with next instr) + let res = self.trace(&next); + next = res.0; + traces.insert(instr.pc, res.1); + } Call(ref target) => { - let new_func = self.module.funcs.get(target).unwrap(); - let mut frame = CallFrame::for_fn(new_func, (func, pc)); - - for idx in 0..frame.args { - frame.locals[idx] = self.stack.pop().unwrap(); + let new_func = &self.module.funcs[target]; + let mut frame = CallFrame::for_fn(new_func, next); + + // pass arguments to function locals + for idx in 0..frame.args_count { + frame.locals[idx] = self.stack + .pop() + .expect("Not enough arguments passed"); } self.frames.push(frame); - - func = new_func; - pc = 0; - }, + next = InstrPtr::for_fn(new_func.clone()); + } Return => { - let frame = self.frames.pop(); + // remove latest callframe + let old_frame = self.frames + .pop() + .expect("Return from non existing frame."); + // did we return from main function? if self.frames.is_empty() { break; + } else { + next = old_frame.back_ref; } - - let (f, rpc) = frame.unwrap().back_ref; - func = f; - pc = rpc; - }, - - Cmp(how) => self.do_cmp(how), + } Jump(target) => { - pc = target; + next = instr.jump(target); } JumpIfFalse(target) => { - if !bool::from(self.stack.pop().unwrap()) { - pc = target; + if let false = self.stack.pop_into::() { + next = instr.jump(target); } } _ => panic!("TODO: {:?}", instr), - } } } - fn push_stack>(&mut self, val: T) { - self.stack.push(val.into()); - } - - fn pop(&mut self) -> T - where T: From { - self.stack.pop().unwrap().into() - } - fn do_add(&mut self) { - let left = self.pop::(); - let right = self.pop::(); - - self.push_stack(left + right); + let (left, right) = self.stack.pop_2_into::(); + self.stack.push_from(left + right); } fn do_push(&mut self) { - let val = self.pop(); + let val = self.stack.pop_into(); self.stack.last_mut().unwrap().as_mut().push(val); } fn do_const(&mut self, n: usize) { - self.stack.push(n.into()); + self.stack.push_from(n); } fn do_load(&mut self, idx: usize) { @@ -369,8 +385,8 @@ impl<'a> Interpreter<'a> { } fn do_len(&mut self) { - let v: Vec = self.pop(); - self.stack.push(v.len().into()); + let v: Vec = self.stack.pop_into(); + self.stack.push_from(v.len()); } fn do_print(&mut self) { @@ -380,58 +396,57 @@ impl<'a> Interpreter<'a> { } fn do_array(&mut self, capacity: usize) { - self.stack.push(Vec::with_capacity(capacity).into()); + self.stack.push_from(Vec::with_capacity(capacity)); } fn do_array_get(&mut self) { - let index: usize = self.pop(); - let xs: Vec = self.pop(); - self.stack.push(xs[index].into()); + let index: usize = self.stack.pop_into(); + let xs: Vec = self.stack.pop_into(); + self.stack.push_from(xs[index]); } fn do_cmp(&mut self, how: Comp) { - let left: usize = self.pop(); - let right: usize = self.pop(); - - let b = match how { + let (left, right) = self.stack.pop_2_into::(); + self.stack.push_from(match how { Comp::Lt => left < right, Comp::Le => left <= right, _ => panic!("TODO"), - }; - - self.stack.push(b.into()); + }); } } + fn main() { use Instruction::*; + + env_logger::init().unwrap(); + let prog = Module { funcs: btreemap!{ "main".into() => Func { name: "main".into(), - args: 0, - locals: 0, - instr: vec![Array(8), Const(9), Push, Const(3), Push, Const(4), Push, Const(5), Push, Const(6), Push, Const(1), Push, Const(3), Push, Const(2), Push, Const(4), Push, Call(String::from("min_list")), Return], - }, + args_count: 0, + locals_count: 0, + instrs: vec![Array(8), Const(9), Push, Const(3), Push, Const(4), Push, Const(5), Push, Const(6), Push, Const(1), Push, Const(3), Push, Const(2), Push, Const(4), Push, Call(String::from("min_list")), Return], + }.into(), "min".into() => Func { name: "min".into(), - args: 2, - locals: 0, - instr: vec![Load(1), Load(0), Cmp(self::Comp::Le), JumpIfFalse(6), Load(0), Jump(8), Load(1), Jump(8), Clone, Return] - }, + args_count: 2, + locals_count: 0, + instrs: vec![Load(1), Load(0), Cmp(self::Comp::Le), JumpIfFalse(6), Load(0), Jump(8), Load(1), Jump(8), Clone, Return] + }.into(), "min_list".into() => Func { - name: "print".into(), - args: 1, - locals: 3, - instr: vec![Load(0), Const(0), ArrayGet, Store(1), Load(0), Len, Store(2), Const(0), Store(3), Loop, Load(2), Load(3), Cmp(Comp::Le), JumpIfFalse(25), Load(0), Load(3), ArrayGet, Load(1), Call(String::from("min")), Store(1), Load(3), Const(1), Add, Store(3), Jump(9), Break, Load(1), Print, Return], - } + name: "min_list".into(), + args_count: 1, + locals_count: 3, + instrs: vec![Load(0), Const(0), ArrayGet, Store(1), Load(0), Len, Store(2), Const(0), Store(3), Loop, Load(2), Load(3), Cmp(Comp::Lt), JumpIfFalse(25), Load(0), Load(3), ArrayGet, Load(1), Call(String::from("min")), Store(1), Load(3), Const(1), Add, Store(3), Jump(9), Break, Load(1), Print, Return], + }.into(), } }; let mut interpreter = Interpreter::new(&prog); - interpreter.run(); } diff --git a/src/recovery.rs b/src/recovery.rs new file mode 100644 index 0000000..581feaa --- /dev/null +++ b/src/recovery.rs @@ -0,0 +1,33 @@ + +use std::rc::Rc; +use std::fmt; + +use kaktus::Stack; + +use super::Func; +use repr::InstrPtr; + + +pub struct FrameInfo { + pub func: Rc, + pub back_ref: InstrPtr, + // offset of inlined values + pub offset: usize, +} + + +#[derive(Clone)] +pub struct Guard { + // condition guard protects + pub condition: bool, + // frame information to recover from + pub frame: Stack, + // pc position where execution can continue + pub pc: usize, +} + +impl fmt::Debug for Guard { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "-{:?}-", self.condition) + } +} diff --git a/src/repr.rs b/src/repr.rs new file mode 100644 index 0000000..abc4004 --- /dev/null +++ b/src/repr.rs @@ -0,0 +1,78 @@ + +use std::rc::Rc; +use std::ops::Deref; + +use bytecode::Instruction; + +#[derive(Debug, Clone)] +pub struct Func { + pub name: String, + pub args_count: usize, + pub locals_count: usize, + pub instrs: Vec, +} + +#[derive(Debug, Clone)] +pub enum Value { + Null, + Bool(bool), + Usize(usize), + Array(Vec), +} + + +pub struct CallFrame { + pub back_ref: InstrPtr, + pub args_count: usize, + pub locals: Vec, +} + +impl CallFrame { + pub fn for_fn(func: &Func, back_ref: InstrPtr) -> Self { + CallFrame { + back_ref: back_ref, + args_count: func.args_count, + locals: vec![Value::Null; func.args_count + func.locals_count], + } + } +} + + +#[derive(Debug, Clone)] +pub struct InstrPtr { + pub func: Rc, + pub pc: usize, +} + +impl InstrPtr { + pub fn new(func: Rc, pc: usize) -> Self { + InstrPtr { + func: func, + pc: pc, + } + } + + pub fn for_fn(func: Rc) -> Self { + InstrPtr::new(func, 0) + } + + pub fn next(&self) -> Self { + InstrPtr::new(self.func.clone(), self.pc + 1) + } + + pub fn jump(&self, target: usize) -> Self { + InstrPtr::new(self.func.clone(), target) + } + + pub fn pc(&self) -> usize { + self.pc + } +} + +impl Deref for InstrPtr { + type Target = Instruction; + + fn deref(&self) -> &Instruction { + &self.func.instrs[self.pc] + } +} diff --git a/src/tracerunner.rs b/src/tracerunner.rs new file mode 100644 index 0000000..a0cd03c --- /dev/null +++ b/src/tracerunner.rs @@ -0,0 +1,157 @@ + +use boolinator::Boolinator; +use kaktus::PushPop; + +use super::{TraceInstruction, Comp, Value, Interpreter, CallFrame, Trace}; +use recovery::Guard; +use traits::vec::ConvertingStack; +use repr::InstrPtr; + +pub struct Runner<'a, 'b: 'a> { + pub trace: &'a [TraceInstruction], + pub stack: Vec, + pub locals: Vec, + pub interp: &'a mut Interpreter<'b>, +} + +impl<'a, 'b> Runner<'a, 'b> { + pub fn new(interp: &'a mut Interpreter<'b>, trace: &'a Trace) -> Self { + // we have to copy over current stack frame from interpreter + let mut locals = vec![Value::Null; trace.locals_count]; + { + let interp_locals = &interp.frames.last().unwrap().locals; + for idx in 0..interp_locals.len() { + locals[idx] = interp_locals[idx].clone(); + } + } + + Runner { + interp: interp, + trace: &trace.trace, + stack: Vec::new(), + locals: locals, + } + } + + pub fn run(&mut self) -> InstrPtr { + use TraceInstruction::*; + + let mut pc = 0; + loop { + let instr = &self.trace[pc]; + pc = (pc + 1) % self.trace.len(); + + info!("TEXEC: {:?}", instr); + + match *instr { + Add => self.add(), + Cmp(how) => self.cmp(how), + Load(idx) => self.load(idx), + Store(idx) => self.store(idx), + ArrayGet => self.array_get(), + Const(val) => self.stack.push_from(val), + Clone => {} + + Guard(ref guard) => { + if let Err(recovery) = self.check_guard(guard) { + return recovery; + } + } + + // these opcodes are not needed for example + Array(_) | Push | Print | Len => unimplemented!(), + } + } + } + + fn check_guard(&mut self, guard: &Guard) -> Result<(), InstrPtr> { + let check = self.stack.pop_into::() == guard.condition; + check.ok_or_else(||{ + self.recover(guard); + InstrPtr::new(guard.frame.func.clone(), guard.pc) + }) + } + + /// Recovery (aka Blackholing) + /// + /// Execution has reached a point, where the trace isn't valid anymore. + /// The goal is to return to the interpreter, but the state has to be + /// recovered first. + /// + /// The following states have to be recovered: + /// * stack-frames (call-frames) + /// The failed guard might have failed within an inlined function call. + /// Thus, we have to reconstruct all missing callframes, before the + /// the interpreter can gain back control. + /// Second, we also have to consider the frame where the loop resides + /// in, since state might have also has changed there. + /// + /// * value stack + /// Also the operand stack has to be recovered. + /// Foremost, the condition, which caused the guard to fail, has to be + /// restored. + /// TODO: Are there other values which might have to be recovered? + fn recover(&mut self, guard: &Guard) { + // remove the last callframe of the Interpreter + // it gets replaced with our updated version + self.interp.frames.pop().unwrap(); + + // recover callframes + let frames = guard.frame.walk().collect::>(); + + // since callframes depend on each other, we start with the one which + // was created first (least-recent frame) `.rev()` ensures that + for frame_info in frames.iter().rev() { + // 1. create a new callframe to push + let mut frame = CallFrame::for_fn( + &frame_info.func, + frame_info.back_ref.clone()); + + // 2. fill it up with locals + for idx in 0..frame.locals.len() { + frame.locals[idx] = self.locals[frame_info.offset + idx].clone(); + } + + // 3. add frame to interpreter callframes + self.interp.frames.push(frame); + } + + // recover value stack + self.interp.stack.push_from(!guard.condition); + } +} + +// normal interpreter functions +impl<'a, 'b> Runner<'a, 'b> { + fn add(&mut self) { + let (a, b) = self.stack.pop_2_into::(); + self.stack.push_from(a + b) + } + + fn cmp(&mut self, how: Comp) { + let (left, right) = self.stack.pop_2_into::(); + + let b = match how { + Comp::Lt => left < right, + Comp::Le => left <= right, + _ => panic!("TODO"), + }; + + self.stack.push_from(b); + } + + fn load(&mut self, idx: usize) { + let val = self.locals[idx].clone(); + self.stack.push(val); + } + + fn store(&mut self, idx: usize) { + self.locals[idx] = self.stack.pop_into(); + } + + fn array_get(&mut self) { + let index: usize = self.stack.pop_into(); + let xs: Vec = self.stack.pop_into(); + self.stack.push_from(xs[index]); + } +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..18db342 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,28 @@ + +pub mod vec { + pub trait ConvertingStack { + fn pop_into(&mut self) -> U where U: From; + + fn pop_2_into(&mut self) -> (U, U) where U: From; + + fn push_from>(&mut self, val: U); + } + + impl ConvertingStack for Vec { + fn pop_into(&mut self) -> U + where U: From + { + self.pop().unwrap().into() + } + + fn pop_2_into(&mut self) -> (U, U) + where U: From + { + (self.pop().unwrap().into(), self.pop().unwrap().into()) + } + + fn push_from>(&mut self, val: U) { + self.push(val.into()); + } + } +}