commit 1b3c582ee24b21ea5aa66a9b60c937ce92dc0ffe Author: Jur van den Berg Date: Tue Jun 6 14:19:35 2017 +0200 Passing all `tests` i guess diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..623ee40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target +*.jas +*.ijvm +*.conf +goJASM diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..005e0c7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "rustijvm" +version = "0.1.0" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..545e5b7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rustijvm" +version = "0.1.0" +authors = ["Jur van den Berg "] + +[dependencies] diff --git a/src/ijvm/binread.rs b/src/ijvm/binread.rs new file mode 100644 index 0000000..f8e83c0 --- /dev/null +++ b/src/ijvm/binread.rs @@ -0,0 +1,82 @@ +use ijvm::Result; +use ijvm::block::Block; +use ijvm::ops::{Operation,num_to_op}; + +pub trait BinReadable { + fn get(&mut self) -> Result; + fn cur(&self) -> usize; + fn len(&self) -> usize; + fn slice(&mut self, len: usize) -> &[u8]; +} + +pub trait BinRead { + fn has_i8(&self) -> bool; + fn has_i16(&self) -> bool; + fn has_i32(&self) -> bool; + + fn read_u8(&mut self) -> Result; + fn read_u16(&mut self) -> Result; + fn read_u32(&mut self) -> Result; + + fn read_i8(&mut self) -> Result; + fn read_i16(&mut self) -> Result; + fn read_i32(&mut self) -> Result; + + fn read_block(&mut self) -> Result; + fn read_op(&mut self) -> Result; +} + +impl BinRead for T { + fn has_i8(&self) -> bool { + self.cur() < self.len() + } + + fn has_i16(&self) -> bool { + self.cur() + 1 < self.len() + } + + fn has_i32(&self) -> bool { + self.cur() + 3 < self.len() + } + + fn read_u8(&mut self) -> Result { + self.get() + } + + fn read_u16(&mut self) -> Result { + let a = self.read_u8()? as u16; + let b = self.read_u8()? as u16; + Ok(a << 8 | b) + } + + fn read_u32(&mut self) -> Result { + let a = self.read_u16()? as u32; + let b = self.read_u16()? as u32; + Ok(a << 16 | b) + } + fn read_i8(&mut self) -> Result { + Ok(self.get()? as i8) + } + + fn read_i16(&mut self) -> Result { + let a = self.read_u8()? as i16; + let b = self.read_u8()? as i16; + Ok((a << 8 | b) as i16) + } + + fn read_i32(&mut self) -> Result { + let a = self.read_u16()? as i32; + let b = self.read_u16()? as i32; + Ok(a << 16 | b) + } + + fn read_block(&mut self) -> Result { + let origin = self.read_u32()?; + let len = self.read_u32()? as usize; + Ok(Block::new(origin, self.slice(len))) + } + + fn read_op(&mut self) -> Result { + Ok(num_to_op(self.read_u8()?)) + } +} \ No newline at end of file diff --git a/src/ijvm/block.rs b/src/ijvm/block.rs new file mode 100644 index 0000000..1d6a88f --- /dev/null +++ b/src/ijvm/block.rs @@ -0,0 +1,71 @@ +use ijvm::Result; +use ijvm::binread::BinReadable; + +#[derive(Debug)] +pub struct Block { + origin: u32, + + length: usize, + pointer: usize, + source: Vec +} + +impl Block { + pub fn new(origin: u32, source: &[u8]) -> Block { + Block { + origin: origin, + length: source.len(), + pointer: 0, + source: source.to_owned() + } + } + + pub fn jump(&mut self, offset: i32) -> Result<()> { + if offset < 0 { + if (-offset as usize) > self.pointer { + return Err("Jump before block") + } + self.pointer -= -offset as usize; + } else { + if self.pointer + (offset as usize) >= self.length { + return Err("Jump outside of block") + } + self.pointer += offset as usize; + } + Ok(()) + } + + pub fn seek(&mut self, location: usize) -> Result<()> { + if location >= self.length { + println!("hoho {}", location); + return Err("Seek outside of block") + } + self.pointer = location; + Ok(()) + } +} + +impl BinReadable for Block { + fn get(&mut self) -> Result { + if self.pointer >= self.length { + return Err("EOF reached") + } + let byte = self.source[self.pointer]; + self.pointer += 1; + return Ok(byte) + } + + fn len(&self) -> usize { + return self.source.len() + } + + fn cur(&self) -> usize { + return self.pointer; + } + + fn slice(&mut self, len: usize) -> &[u8] { + let slice = &self.source[self.pointer..self.pointer+len]; + self.pointer += len; + slice + } +} diff --git a/src/ijvm/frame.rs b/src/ijvm/frame.rs new file mode 100644 index 0000000..30a3e0f --- /dev/null +++ b/src/ijvm/frame.rs @@ -0,0 +1,33 @@ +use ijvm::Result; +use ijvm::stack::Stack; + +#[derive(Debug)] +pub struct Frame { + pub stack: Stack, + locals: Vec, +} + +impl Frame { + pub fn new(num_locals: usize) -> Frame { +// println!("Initializing frame of len {}", num_locals); + return Frame { + stack: Stack::new(), + locals: vec![0; num_locals+1], + } + } + + pub fn get(&self, offset: usize) -> Result { + if offset >= self.locals.len() { + return Err("Local variable out of range"); + } + return Ok(self.locals[offset]) + } + + pub fn set(&mut self, offset: usize, val: i32) -> Result<()> { + if offset >= self.locals.len() { + return Err("Local variable out of range"); + } + self.locals[offset] = val; + return Ok(()); + } +} \ No newline at end of file diff --git a/src/ijvm/ijvmreader.rs b/src/ijvm/ijvmreader.rs new file mode 100644 index 0000000..e227282 --- /dev/null +++ b/src/ijvm/ijvmreader.rs @@ -0,0 +1,48 @@ +use std::io::Read; +use std::fs::File; +use ijvm::Result; +use ijvm::binread::BinReadable; + +pub struct IJVMReader { + pointer: usize, + source: Vec +} + +impl IJVMReader { + pub fn new(path: &str) -> Result { + let file = match File::open(path) { + Ok(file) => file, + Err(_) => return Err("Could not open file"), + }; + let abc: Vec = file.bytes().map(|e| e.unwrap()).collect(); + Ok(IJVMReader { + pointer: 0, + source: abc + }) + } +} + +impl BinReadable for IJVMReader { + fn get(&mut self) -> Result { + if self.pointer >= self.source.len() { + return Err("EOF reached") + } + let byte = self.source[self.pointer]; + self.pointer += 1; + return Ok(byte) + } + + fn len(&self) -> usize { + return self.source.len() + } + + fn cur(&self) -> usize { + return self.pointer; + } + + fn slice(&mut self, len: usize) -> &[u8] { + let slice = &self.source[self.pointer..self.pointer+len]; + self.pointer += len; + slice + } +} diff --git a/src/ijvm/machine.rs b/src/ijvm/machine.rs new file mode 100644 index 0000000..050fcd4 --- /dev/null +++ b/src/ijvm/machine.rs @@ -0,0 +1,96 @@ +use ijvm::block::Block; + +use ijvm::ops::Operation; +use ijvm::ijvmreader::IJVMReader; +use ijvm::binread::BinReadable; +use ijvm::binread::BinRead; +use ijvm::frame::Frame; +use ijvm::stack::Stack; +use ijvm::pool::Pool; +use ijvm::Result; + +const MAGIC_HEADER:u32 = 0x1deadfad; +const ANTI_BS_SIZE:usize = 0xFFFF; + +pub struct Machine { + pub wide: bool, + pub pool: Pool, + pub block: Block, + pub frame: Vec +} + +impl Machine { + pub fn new(pool: Pool, block: Block) -> Machine{ + Machine { + wide: false, + pool: pool, + block: block, + frame: vec![Frame::new(ANTI_BS_SIZE)], + } + } + + pub fn new_from_file(file: &str) -> Result { + let mut reader = IJVMReader::new(file).unwrap(); + let magic = reader.read_u32()?; + if magic != MAGIC_HEADER { + return Err("Invalid magic header"); + } + let constants = match reader.read_block() { + Ok(a) => a, + Err(_) => return Err("Failed to read constants block") + }; + let text = match reader.read_block() { + Ok(block) => block, + Err(_) => return Err("Failed to read text block") + }; + + let pool = Pool::new(constants)?; + + return Ok(Machine::new(pool, text)); + } + + pub fn step(&mut self) -> Result<()> { + match self.block.read_op() { + Ok(Operation::Op(a, func)) => { +// println!("{}", a); +// println!("Stack: {:?}", self.cur_frame().stack.data); + let x = func(self); +// println!("Stack: {:?}", self.cur_frame().stack.data); + x + }, + Ok(Operation::Invalid(a)) => { + println!("OP: {}", a); + Err("Invalid op") + }, + Err(str) => Err(str) + } + } + + pub fn has_step(&self) -> bool { + return self.block.has_i8(); + } + + pub fn get_program_counter(&self) -> usize { + return self.block.cur() + } + + pub fn cur_frame(&mut self) -> &mut Frame { + return self.frame.last_mut().unwrap(); + } + + pub fn cur_stack(&mut self) -> &mut Stack { + return &mut self.cur_frame().stack; + } + +// pub fn get_stack_pointer(&self) -> usize { +// return self.frame.last().unwrap().stack.len(); +// } + + pub fn read_index(&mut self) -> Result { + if self.wide { + self.wide = false; + return Ok(self.block.read_u16()? as usize) + } + Ok(self.block.read_u8()? as usize) + } +} \ No newline at end of file diff --git a/src/ijvm/mod.rs b/src/ijvm/mod.rs new file mode 100644 index 0000000..8a8fb8b --- /dev/null +++ b/src/ijvm/mod.rs @@ -0,0 +1,10 @@ +pub mod ijvmreader; +pub mod binread; +pub mod block; +pub mod ops; +pub mod machine; +pub mod stack; +pub mod pool; +pub mod frame; + +type Result = ::std::result::Result; diff --git a/src/ijvm/ops.rs b/src/ijvm/ops.rs new file mode 100644 index 0000000..2252b82 --- /dev/null +++ b/src/ijvm/ops.rs @@ -0,0 +1,245 @@ +use ijvm::binread::BinRead; +use ijvm::machine::Machine; +use ijvm::frame::Frame; +use ijvm::Result; +use std::io::Write; +use std::io::Read; + +pub type OpFunc = fn(&mut Machine) -> Result<()>; + +pub enum Operation { + Op(&'static str, OpFunc), + Invalid(u8) +} + +const JUMP_OFFSET: i32 = 3; + +pub fn num_to_op(op: u8) -> Operation { + match op { + 0x00 => Operation::Op("NOP", nop), + 0x10 => Operation::Op("BIPUSH", bipush), + 0x13 => Operation::Op("LDC_W", ldc_w), + 0x15 => Operation::Op("ILOAD", iload), + 0x36 => Operation::Op("ISTORE", istore), + 0x57 => Operation::Op("POP", pop), + 0x59 => Operation::Op("DUP", dup), + 0x5F => Operation::Op("SWAP", swap), + 0x60 => Operation::Op("IADD", iadd), + 0x64 => Operation::Op("ISUB", isub), + 0x7E => Operation::Op("IAND", iand), + 0xB0 => Operation::Op("IOR", ior), + 0x84 => Operation::Op("IINC", iinc), + 0x99 => Operation::Op("IFEQ", ifeq), + 0x9b => Operation::Op("IFLT", iflt), + 0x9F => Operation::Op("IF_ICMPEQ", if_icmpeq), + 0xA7 => Operation::Op("GOTO", goto), + 0xAC => Operation::Op("IRETURN", ireturn), + 0xB6 => Operation::Op("INVOKEVIRTUAL", invokevirtual), + 0xC4 => Operation::Op("WIDE", wide), + 0xFC => Operation::Op("IN", _in), + 0xFD => Operation::Op("OUT", out), + 0xFF => Operation::Op("HALT", halt), + + 0xF0 => Operation::Op("SLP", slp), + x => Operation::Invalid(x) + } +} + +fn nop(_: &mut Machine) -> Result<()> { + Ok(()) +} + +fn bipush(machine: &mut Machine) -> Result<()> { + let val = match machine.block.read_i8() { + Ok(a) => a, + Err(_) => return Err("Expected argument"), + } as i32; + machine.cur_stack().push(val); + Ok(()) +} + +fn dup(machine: &mut Machine) -> Result<()> { + let val = machine.cur_stack().top()?; + machine.cur_stack().push(val); + Ok(()) +} + +fn out(machine: &mut Machine) -> Result<()> { + let val = machine.cur_stack().pop()?; + print!("{}", val as u8 as char); + ::std::io::stdout().flush().unwrap(); + Ok(()) +} + +fn pop(machine: &mut Machine) -> Result<()> { + machine.cur_stack().pop()?; + Ok(()) +} + +fn iadd(machine: &mut Machine) -> Result<()> { + let a = machine.cur_stack().pop()?; + let b = machine.cur_stack().pop()?; + machine.cur_stack().push(a + b); + Ok(()) +} + +fn isub(machine: &mut Machine) -> Result<()> { + let a = machine.cur_stack().pop()?; + let b = machine.cur_stack().pop()?; + machine.cur_stack().push(b - a); + Ok(()) +} + +fn _in(machine: &mut Machine) -> Result<()> { + let char: Option = + ::std::io::stdin() + .bytes() + .next() + .and_then(|r| r.ok()); + let val = match char { + None => 0 as i32, + Some(i) => i as i32 + }; + machine.cur_stack().push(val); + Ok(()) +} + +fn goto(machine: &mut Machine) -> Result<()> { + let offset = machine.block.read_i16()? as i32 - JUMP_OFFSET; + machine.block.jump(offset) +} + +fn halt(_: &mut Machine) -> Result<()> { + Err("Halt") +} + +fn ifeq(machine: &mut Machine) -> Result<()> { + let offset = machine.block.read_i16()? as i32 - JUMP_OFFSET; + if machine.cur_stack().pop()? == 0 { + return machine.block.jump(offset) + } + Ok(()) +} + +fn iflt(machine: &mut Machine) -> Result<()> { + let offset = machine.block.read_i16()? as i32 - JUMP_OFFSET; + if machine.cur_stack().pop()? < 0 { + return machine.block.jump(offset) + } + Ok(()) +} + +fn if_icmpeq(machine: &mut Machine) -> Result<()> { + let offset = machine.block.read_i16()? as i32 - JUMP_OFFSET; + if machine.cur_stack().pop()? == machine.cur_stack().pop()? { + return machine.block.jump(offset) + } + Ok(()) +} + +fn iand(machine: &mut Machine) -> Result<()> { + let a = machine.cur_stack().pop()?; + let b = machine.cur_stack().pop()?; + machine.cur_stack().push(a & b); + Ok(()) +} + +fn ior(machine: &mut Machine) -> Result<()> { + let a = machine.cur_stack().pop()?; + let b = machine.cur_stack().pop()?; + machine.cur_stack().push(a | b); + Ok(()) +} + +fn ldc_w(machine: &mut Machine) -> Result<()> { + let offset = machine.block.read_i16()? as usize; + let val = machine.pool.get(offset)?; + machine.cur_stack().push(val); + Ok(()) +} + +fn swap(machine: &mut Machine) -> Result<()> { + let a = machine.cur_stack().pop()?; + let b = machine.cur_stack().pop()?; + machine.cur_stack().push(a); + machine.cur_stack().push(b); + Ok(()) +} + +fn wide(machine: &mut Machine) -> Result<()> { + machine.wide = true; + Ok(()) +} + +fn iload(machine: &mut Machine) -> Result<()> { + let a = machine.read_index()?; + let val = machine.cur_frame().get(a)?; + machine.cur_stack().push(val); + Ok(()) +} + +fn istore(machine: &mut Machine) -> Result<()> { + let a = machine.read_index()?; + let val = machine.cur_stack().pop()?; + machine.cur_frame().set(a, val) +} + +fn iinc(machine: &mut Machine) -> Result<()> { + let a = machine.read_index()?; + let b = machine.block.read_i8()?; + let val = machine.cur_frame().get(a)?; + machine.cur_frame().set(a, val + b as i32) +} + +fn invokevirtual(machine: &mut Machine) -> Result<()> { + let method_index = machine.block.read_u16()?; + let invoke_addr = machine.pool.get(method_index as usize)?; + let return_addr = machine.get_program_counter(); +// println!("METHOD {}", method_index); + machine.block.seek(invoke_addr as usize)?; + let arg_count = machine.block.read_u16()?; + let var_count = machine.block.read_u16()?; + let mut newframe = Frame::new((arg_count + var_count) as usize); + + // Lifetime for cur_stack + { + let mut cur_stack = machine.cur_stack(); + for i in 1..arg_count { + newframe.set((arg_count - i) as usize, cur_stack.pop()?)?; + } + + cur_stack.pop()?; // Nuke the OBJREF + } + +// println!("retaddr set {}" ,return_addr); + newframe.set(0, return_addr as i32)?; + + machine.frame.push(newframe); + Ok(()) + +} + +fn ireturn(machine: &mut Machine) -> Result<()> { + let mut prev_frame: Frame = match machine.frame.pop() { + Some(a) => a, + None => return Err("Got no frame... somehow...") + }; +// println!("stack: {:?}", prev_frame.stack); + let result = prev_frame.stack.pop()?; + let return_addr = prev_frame.get(0)?; +// println!("result: {}\nretaddr: {}", result, return_addr); + machine.cur_stack().push(result); + machine.block.seek(return_addr as usize) +} + +fn slp(machine: &mut Machine) -> Result<()> { + use std::time; + use std::thread; + let val = match machine.block.read_u8() { + Ok(a) => a, + Err(_) => return Err("Expected argument"), + } as u64; + let slpdur = time::Duration::from_millis(val*100); + thread::sleep(slpdur); + Ok(()) +} \ No newline at end of file diff --git a/src/ijvm/pool.rs b/src/ijvm/pool.rs new file mode 100644 index 0000000..97c0ade --- /dev/null +++ b/src/ijvm/pool.rs @@ -0,0 +1,28 @@ +use ijvm::Result; +use ijvm::block::Block; +use ijvm::binread::{BinRead, BinReadable}; + +#[derive(Debug)] +pub struct Pool { + pub data: Vec +} + +impl Pool { + pub fn new(mut block: Block) -> Result { + let cap = block.len() / 4; + let mut vec = Vec::with_capacity(cap); + while block.has_i32() { + vec.push(block.read_i32()?); + } + Ok(Pool { + data: vec + }) + } + + pub fn get(&self, index: usize) -> Result { + if index >= self.data.len() { + return Err("No such constant") + } + Ok(self.data[index]) + } +} diff --git a/src/ijvm/stack.rs b/src/ijvm/stack.rs new file mode 100644 index 0000000..349c53b --- /dev/null +++ b/src/ijvm/stack.rs @@ -0,0 +1,40 @@ +use ijvm::Result; + +#[derive(Debug)] +pub struct Stack { + pub data: Vec +} + +impl Stack { + pub fn new() -> Stack { + Stack { + data: Vec::new() + } + } + +// pub fn len(&self) -> usize { +// return self.data.len(); +// } + + pub fn top(&self) -> Result { + Ok(match self.data.last() { + Some(a) => *a, + None => return Err("Stack empty"), + }) + } + + pub fn pop(&mut self) -> Result { + Ok(match self.data.pop() { + Some(a) => a, + None => return Err("Stack empty"), + }) + } + + pub fn push(&mut self, val: i32) { + self.data.push(val); + } + + pub fn len(&self) -> usize { + self.data.len() + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5f1d798 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +mod ijvm; + + +use ijvm::machine::Machine; +use std::env; + + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + println!("Usage: {} ijvmfile", args[0]); + return; + } + + let ijvmfile = &args[1]; + + let mut machine = Machine::new_from_file(ijvmfile).unwrap(); + while machine.has_step() { + match machine.step() { + Ok(_) => (), + Err(str) => { + println!("\n\nERROR: pc=0x{:x} fp=0x{:x}: {}\nProgram exit", + machine.get_program_counter(), + machine.frame.len() - 1, + str); + return; + } + } + } +} \ No newline at end of file