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, JMP = 0x1002,
JZ = 0x1003, JZ = 0x1003,
JNZ = 0x1004, JNZ = 0x1004,
MOV = 0x2000, CALL = 0x2000,
RET = 0x2001,
PUSH = 0x2002,
POP = 0x2003,
MOV = 0x3000,
HALT = 0xF000, HALT = 0xF000,
NOP = 0xF001, NOP = 0xF001,
DUMP = 0xF002, DUMP = 0xF002,
@@ -69,6 +73,10 @@ pub enum Inst {
Jmp(Source), Jmp(Source),
Jz(Source), Jz(Source),
Jnz(Source), Jnz(Source),
Call(Source),
Ret,
Push(Source),
Pop(Dest),
Mov(Dest, Source), Mov(Dest, Source),
Halt, Halt,
Nop, Nop,
@@ -98,6 +106,10 @@ impl Inst {
Inst::Jz(_) => JZ, Inst::Jz(_) => JZ,
Inst::Jnz(_) => JNZ, Inst::Jnz(_) => JNZ,
Inst::Mov(_, _) => MOV, Inst::Mov(_, _) => MOV,
Inst::Call(_) => CALL,
Inst::Ret => RET,
Inst::Push(_) => PUSH,
Inst::Pop(_) => POP,
Inst::Halt => HALT, Inst::Halt => HALT,
Inst::Nop => NOP, Inst::Nop => NOP,
Inst::Dump => DUMP, Inst::Dump => DUMP,
@@ -125,8 +137,12 @@ impl Inst {
| Inst::CmpLt(s1, s2) => { 3 + s1.len() + s2.len() } | Inst::CmpLt(s1, s2) => { 3 + s1.len() + s2.len() }
Inst::Jmp(v) Inst::Jmp(v)
| Inst::Jz(v) | Inst::Jz(v)
| Inst::Jnz(v) => { 3 + v.len() } | Inst::Jnz(v)
Inst::Halt | Inst::Call(v)
| Inst::Push(v) => { 3 + v.len() }
Inst::Pop(v) => { 3 + v.len() }
Inst::Ret
| Inst::Halt
| Inst::Nop | Inst::Nop
| Inst::Dump => { 2 } | Inst::Dump => { 2 }
} }
@@ -156,6 +172,31 @@ impl Source {
Source::Imm(_) => 8, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -179,6 +220,15 @@ impl Dest {
Dest::Reg(_) => 1, 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 // TODO : make this an enum

View File

@@ -90,6 +90,13 @@ impl<T> MemCursor<T>
Ok(Inst::$variant(source)) 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 { let inst = match op {
ADD => dest_source!(Add), ADD => dest_source!(Add),
SUB => dest_source!(Sub), SUB => dest_source!(Sub),
@@ -110,6 +117,10 @@ impl<T> MemCursor<T>
JMP => source!(Jmp), JMP => source!(Jmp),
JZ => source!(Jz), JZ => source!(Jz),
JNZ => source!(Jnz), JNZ => source!(Jnz),
CALL => source!(Call),
RET => Ok(Inst::Ret),
PUSH => source!(Push),
POP => dest!(Pop),
MOV => dest_source!(Mov), MOV => dest_source!(Mov),
HALT => Ok(Inst::Halt), HALT => Ok(Inst::Halt),
NOP => Ok(Inst::Nop), 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 // 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::CmpEq(v1, v2) => map_inst!(inst::CMPEQ, v1, v2),
Inst::CmpLt(v1, v2) => map_inst!(inst::CMPLT, 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::Jmp(v) => map_inst!(inst::JMP, v),
Inst::Jz(v) => map_inst!(inst::JZ, v), Inst::Jz(v) => map_inst!(inst::JZ, v),
Inst::Jnz(v) => map_inst!(inst::JNZ, 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::Halt => map_inst!(inst::HALT),
Inst::Nop => map_inst!(inst::NOP), Inst::Nop => map_inst!(inst::NOP),
Inst::Dump => map_inst!(inst::DUMP), Inst::Dump => map_inst!(inst::DUMP),

View File

@@ -207,6 +207,10 @@ pub enum Inst {
Jmp(Value), Jmp(Value),
Jz(Value), Jz(Value),
Jnz(Value), Jnz(Value),
Call(Value),
Ret,
Push(Value),
Pop(Value),
Mov(Value, Value), Mov(Value, Value),
Halt, Halt,
Nop, Nop,
@@ -235,8 +239,12 @@ impl Inst {
| Inst::Mov(v1, v2) => { 3 + v1.len() + v2.len() } | Inst::Mov(v1, v2) => { 3 + v1.len() + v2.len() }
Inst::Jmp(v) Inst::Jmp(v)
| Inst::Jz(v) | Inst::Jz(v)
| Inst::Jnz(v) => { 3 + v.len() } | Inst::Jnz(v)
Inst::Halt | Inst::Call(v)
| Inst::Push(v)
| Inst::Pop(v) => { 3 + v.len() }
Inst::Ret
| Inst::Halt
| Inst::Nop | Inst::Nop
| Inst::Dump => { 2 } | Inst::Dump => { 2 }
} }

View File

@@ -46,6 +46,10 @@ cmplt "CMPLT"
jmp "JMP" jmp "JMP"
jz "JZ" jz "JZ"
jnz "JNZ" jnz "JNZ"
call "CALL"
ret "RET"
push "PUSH"
pop "POP"
mov "MOV" mov "MOV"
halt "HALT" halt "HALT"
nop "NOP" nop "NOP"

View File

@@ -95,6 +95,10 @@ Inst -> Inst:
| 'JMP' Value { Inst::Jmp($2) } | 'JMP' Value { Inst::Jmp($2) }
| 'JZ' Value { Inst::Jz($2) } | 'JZ' Value { Inst::Jz($2) }
| 'JNZ' Value { Inst::Jnz($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) } | 'MOV' Value 'COMMA' Value { Inst::Mov($2, $4) }
| 'HALT' { Inst::Halt } | 'HALT' { Inst::Halt }
| 'NOP' { Inst::Nop } | 'NOP' { Inst::Nop }

View File

@@ -86,10 +86,22 @@ impl State {
} }
} }
////////////////////////////////////////////////////////////////////////////////
// Registers
////////////////////////////////////////////////////////////////////////////////
pub fn ip(&self) -> u64 { pub fn ip(&self) -> u64 {
self.get_reg_unchecked(IP) 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 // Flags
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -228,6 +240,37 @@ impl State {
next_ip = self.load_source(s)?; 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) => { Inst::Mov(d, s) => {
let value = self.load_source(s)?; let value = self.load_source(s)?;
self.store_dest(d, value)?; self.store_dest(d, value)?;
@@ -244,6 +287,44 @@ impl State {
Ok(()) 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<()> { fn store_dest(&mut self, dest: Dest, value: u64) -> Result<()> {
match dest { match dest {
Dest::Addr64(a) => self.mem_cursor_mut(a).write_u64(value), 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 * Opcode: 0x1004
* Params: Source * 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 ## Data movement
* Mov * Mov
* Opcode: 0x2000 * Opcode: 0x3000
## Miscellaneous ## Miscellaneous