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