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:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
25
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user