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 <alekratz@gmail.com>
This commit is contained in:
2020-02-26 10:14:48 -05:00
parent bd34cdad63
commit 7a6c2d80ab
8 changed files with 206 additions and 7 deletions

View File

@@ -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<Dest> 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

View File

@@ -90,6 +90,13 @@ impl<T> MemCursor<T>
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<T> MemCursor<T>
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),

View File

@@ -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::<LE>(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),

View File

@@ -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 }
}

View File

@@ -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"

View File

@@ -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 }

View File

@@ -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),

25
vm.md
View File

@@ -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