From 7a6c2d80ab95deb80a55b9acf4e7a06bf60b60fd Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Wed, 26 Feb 2020 10:14:48 -0500 Subject: [PATCH] Add call/ret/push/pop instructions * Call/ret/push/pop are implemented and appear to be working * Call/ret/push/pop is specified in the 0x2000 block, replacing the mov instruction * Mov instruction is now specified in the 0x3000 block Signed-off-by: Alek Ratzloff --- src/vm/inst.rs | 56 ++++++++++++++++++++++++++-- src/vm/mem.rs | 11 ++++++ src/vm/obj/assemble.rs | 20 +++++++++- src/vm/obj/syn/ast.rs | 12 +++++- src/vm/obj/syn/lexer.l | 4 ++ src/vm/obj/syn/parser.y | 4 ++ src/vm/state.rs | 81 +++++++++++++++++++++++++++++++++++++++++ vm.md | 25 ++++++++++++- 8 files changed, 206 insertions(+), 7 deletions(-) diff --git a/src/vm/inst.rs b/src/vm/inst.rs index 36ac227..f391a2e 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -42,7 +42,11 @@ instructions! { JMP = 0x1002, JZ = 0x1003, JNZ = 0x1004, - MOV = 0x2000, + CALL = 0x2000, + RET = 0x2001, + PUSH = 0x2002, + POP = 0x2003, + MOV = 0x3000, HALT = 0xF000, NOP = 0xF001, DUMP = 0xF002, @@ -69,6 +73,10 @@ pub enum Inst { Jmp(Source), Jz(Source), Jnz(Source), + Call(Source), + Ret, + Push(Source), + Pop(Dest), Mov(Dest, Source), Halt, Nop, @@ -98,6 +106,10 @@ impl Inst { Inst::Jz(_) => JZ, Inst::Jnz(_) => JNZ, Inst::Mov(_, _) => MOV, + Inst::Call(_) => CALL, + Inst::Ret => RET, + Inst::Push(_) => PUSH, + Inst::Pop(_) => POP, Inst::Halt => HALT, Inst::Nop => NOP, Inst::Dump => DUMP, @@ -125,8 +137,12 @@ impl Inst { | Inst::CmpLt(s1, s2) => { 3 + s1.len() + s2.len() } Inst::Jmp(v) | Inst::Jz(v) - | Inst::Jnz(v) => { 3 + v.len() } - Inst::Halt + | Inst::Jnz(v) + | Inst::Call(v) + | Inst::Push(v) => { 3 + v.len() } + Inst::Pop(v) => { 3 + v.len() } + Inst::Ret + | Inst::Halt | Inst::Nop | Inst::Dump => { 2 } } @@ -156,6 +172,31 @@ impl Source { Source::Imm(_) => 8, } } + + pub fn value_len(&self) -> usize { + match self { + Source::Addr64(_) | Source::RegAddr64(_) | Source::Reg(_) | Source::Imm(_) => 8, + Source::Addr32(_) | Source::RegAddr32(_) => 4, + Source::Addr16(_) | Source::RegAddr16(_) => 2, + Source::Addr8(_) | Source::RegAddr8(_) => 1, + } + } +} + +impl From for Source { + fn from(other: Dest) -> Self { + match other { + Dest::Addr64(a) => Source::Addr64(a), + Dest::Addr32(a) => Source::Addr32(a), + Dest::Addr16(a) => Source::Addr16(a), + Dest::Addr8(a) => Source::Addr8(a), + Dest::RegAddr64(r) => Source::RegAddr64(r), + Dest::RegAddr32(r) => Source::RegAddr32(r), + Dest::RegAddr16(r) => Source::RegAddr16(r), + Dest::RegAddr8(r) => Source::RegAddr8(r), + Dest::Reg(r) => Source::Reg(r), + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -179,6 +220,15 @@ impl Dest { Dest::Reg(_) => 1, } } + + pub fn value_len(&self) -> usize { + match self { + Dest::Addr64(_) | Dest::RegAddr64(_) | Dest::Reg(_) => 8, + Dest::Addr32(_) | Dest::RegAddr32(_) => 4, + Dest::Addr16(_) | Dest::RegAddr16(_) => 2, + Dest::Addr8(_) | Dest::RegAddr8(_) => 1, + } + } } // TODO : make this an enum diff --git a/src/vm/mem.rs b/src/vm/mem.rs index 8d84ea7..37a4c2e 100644 --- a/src/vm/mem.rs +++ b/src/vm/mem.rs @@ -90,6 +90,13 @@ impl MemCursor Ok(Inst::$variant(source)) }}; } + macro_rules! dest { + ($variant:ident) => {{ + let spec = (self.next_u8()? & 0xF0) >> 4; + let dest = self.next_dest(spec)?; + Ok(Inst::$variant(dest)) + }}; + } let inst = match op { ADD => dest_source!(Add), SUB => dest_source!(Sub), @@ -110,6 +117,10 @@ impl MemCursor JMP => source!(Jmp), JZ => source!(Jz), JNZ => source!(Jnz), + CALL => source!(Call), + RET => Ok(Inst::Ret), + PUSH => source!(Push), + POP => dest!(Pop), MOV => dest_source!(Mov), HALT => Ok(Inst::Halt), NOP => Ok(Inst::Nop), diff --git a/src/vm/obj/assemble.rs b/src/vm/obj/assemble.rs index 8e5b84a..017a4e2 100644 --- a/src/vm/obj/assemble.rs +++ b/src/vm/obj/assemble.rs @@ -298,10 +298,28 @@ impl Assemble for Inst { // TODO/BUG: CmpEq and CmpLt both take two sources instead of a source and destination Inst::CmpEq(v1, v2) => map_inst!(inst::CMPEQ, v1, v2), Inst::CmpLt(v1, v2) => map_inst!(inst::CMPLT, v1, v2), - Inst::Mov(v1, v2) => map_inst!(inst::MOV, v1, v2), Inst::Jmp(v) => map_inst!(inst::JMP, v), Inst::Jz(v) => map_inst!(inst::JZ, v), Inst::Jnz(v) => map_inst!(inst::JNZ, v), + Inst::Call(v) => map_inst!(inst::CALL, v), + Inst::Ret => map_inst!(inst::RET), + Inst::Push(v) => map_inst!(inst::PUSH, v), + Inst::Pop(dest) => { + let mut bytes = Vec::with_capacity(len); + bytes.write_u16::(inst::POP).unwrap(); + let dest_encoding = dest.source_encoding() << 4; + bytes.write_u8(dest_encoding).unwrap(); + bytes.extend(dest.assemble(asm)?); + assert_eq!( + self.len(), + bytes.len(), + "instruction size mismatch in inst::PUSH instruction - {:?} produces these bytes {:?}", + self, + bytes + ); + Ok(bytes) + } + Inst::Mov(v1, v2) => map_inst!(inst::MOV, v1, v2), Inst::Halt => map_inst!(inst::HALT), Inst::Nop => map_inst!(inst::NOP), Inst::Dump => map_inst!(inst::DUMP), diff --git a/src/vm/obj/syn/ast.rs b/src/vm/obj/syn/ast.rs index 11766f5..6cc4781 100644 --- a/src/vm/obj/syn/ast.rs +++ b/src/vm/obj/syn/ast.rs @@ -207,6 +207,10 @@ pub enum Inst { Jmp(Value), Jz(Value), Jnz(Value), + Call(Value), + Ret, + Push(Value), + Pop(Value), Mov(Value, Value), Halt, Nop, @@ -235,8 +239,12 @@ impl Inst { | Inst::Mov(v1, v2) => { 3 + v1.len() + v2.len() } Inst::Jmp(v) | Inst::Jz(v) - | Inst::Jnz(v) => { 3 + v.len() } - Inst::Halt + | Inst::Jnz(v) + | Inst::Call(v) + | Inst::Push(v) + | Inst::Pop(v) => { 3 + v.len() } + Inst::Ret + | Inst::Halt | Inst::Nop | Inst::Dump => { 2 } } diff --git a/src/vm/obj/syn/lexer.l b/src/vm/obj/syn/lexer.l index 4dfd5a7..25d7618 100644 --- a/src/vm/obj/syn/lexer.l +++ b/src/vm/obj/syn/lexer.l @@ -46,6 +46,10 @@ cmplt "CMPLT" jmp "JMP" jz "JZ" jnz "JNZ" +call "CALL" +ret "RET" +push "PUSH" +pop "POP" mov "MOV" halt "HALT" nop "NOP" diff --git a/src/vm/obj/syn/parser.y b/src/vm/obj/syn/parser.y index 385ba4a..fc7491e 100644 --- a/src/vm/obj/syn/parser.y +++ b/src/vm/obj/syn/parser.y @@ -95,6 +95,10 @@ Inst -> Inst: | 'JMP' Value { Inst::Jmp($2) } | 'JZ' Value { Inst::Jz($2) } | 'JNZ' Value { Inst::Jnz($2) } + | 'CALL' Value { Inst::Call($2) } + | 'RET' { Inst::Ret } + | 'PUSH' Value { Inst::Push($2) } + | 'POP' Value { Inst::Pop($2) } | 'MOV' Value 'COMMA' Value { Inst::Mov($2, $4) } | 'HALT' { Inst::Halt } | 'NOP' { Inst::Nop } diff --git a/src/vm/state.rs b/src/vm/state.rs index 89dd06c..264f1e8 100644 --- a/src/vm/state.rs +++ b/src/vm/state.rs @@ -86,10 +86,22 @@ impl State { } } + //////////////////////////////////////////////////////////////////////////////// + // Registers + //////////////////////////////////////////////////////////////////////////////// + pub fn ip(&self) -> u64 { self.get_reg_unchecked(IP) } + pub fn sp(&self) -> u64 { + self.get_reg_unchecked(SP) + } + + pub fn fp(&self) -> u64 { + self.get_reg_unchecked(FP) + } + //////////////////////////////////////////////////////////////////////////////// // Flags //////////////////////////////////////////////////////////////////////////////// @@ -228,6 +240,37 @@ impl State { next_ip = self.load_source(s)?; } } + Inst::Call(s) => { + { + let fp = self.fp(); + let ip = next_ip; + self.push(Source::Imm(fp))?; + self.push(Source::Imm(ip))?; + } + + { + let sp = self.sp(); + self.set_reg_unchecked(FP, sp - 16); + + next_ip = self.load_source(s)?; + } + } + Inst::Ret => { + let fp = self.fp(); + let sp = fp + 16; + self.set_reg_unchecked(SP, sp); + + self.pop(Dest::Reg(IP))?; + self.pop(Dest::Reg(FP))?; + + next_ip = self.ip(); + } + Inst::Push(s) => { + self.push(s)?; + } + Inst::Pop(d) => { + self.pop(d)?; + } Inst::Mov(d, s) => { let value = self.load_source(s)?; self.store_dest(d, value)?; @@ -244,6 +287,44 @@ impl State { Ok(()) } + fn push(&mut self, source: Source) -> Result<()> { + let value = self.load_source(source)?; + let mut stack_addr = self.sp(); + + // create a destination based on the size of the source + let dest = match source { + Source::Addr64(_) | Source::RegAddr64(_) | Source::Reg(_) | Source::Imm(_) => Dest::Addr64(Addr(stack_addr)), + Source::Addr32(_) | Source::RegAddr32(_) => Dest::Addr32(Addr(stack_addr)), + Source::Addr16(_) | Source::RegAddr16(_) => Dest::Addr16(Addr(stack_addr)), + Source::Addr8(_) | Source::RegAddr8(_) => Dest::Addr8(Addr(stack_addr)), + }; + self.store_dest(dest, value)?; + assert_eq!(source.value_len(), dest.value_len()); + + stack_addr += source.value_len() as u64; + self.set_reg_unchecked(SP, stack_addr); + Ok(()) + } + + fn pop(&mut self, dest: Dest) -> Result<()> { + let sp = self.sp() - (dest.value_len() as u64); + + let sp_source = match dest { + Dest::Addr64(_) | Dest::RegAddr64(_) | Dest::Reg(_) => Source::Addr64(Addr(sp)), + Dest::Addr32(_) | Dest::RegAddr32(_) => Source::Addr32(Addr(sp)), + Dest::Addr16(_) | Dest::RegAddr16(_) => Source::Addr16(Addr(sp)), + Dest::Addr8(_) | Dest::RegAddr8(_) => Source::Addr8(Addr(sp)), + }; + + let value = self.load_source(sp_source)?; + + // Set the SP first, because the destination may be the SP itself + self.set_reg_unchecked(SP, sp); + self.store_dest(dest, value)?; + + Ok(()) + } + fn store_dest(&mut self, dest: Dest, value: u64) -> Result<()> { match dest { Dest::Addr64(a) => self.mem_cursor_mut(a).write_u64(value), diff --git a/vm.md b/vm.md index 2d0c95e..7b59f83 100644 --- a/vm.md +++ b/vm.md @@ -181,10 +181,33 @@ wrapping around to 0. * Opcode: 0x1004 * Params: Source +## Functions + +* Call + * Opcode: 0x2000 + * Params: Source + * When this instruction is executed, these actions occur: + * Push the current stack frame pointer + * Push the IP of the next instruction + * Update the IP (i.e., jump) to the value at the given source. +* Ret + * Opcode: 0x2001 + * When this instruction is executed, these actions occur: + * Update the stack pointer to the current frame pointer + 16. + * Pop the IP of the next instruction. + * Pop the old stack frame. + * Restore the last three values in an undefined order +* Push + * Opcode: 0x2002 + * Params: Source +* Pop + * Opcode: 0x2003 + * Params: Dest + ## Data movement * Mov - * Opcode: 0x2000 + * Opcode: 0x3000 ## Miscellaneous