Last active
April 16, 2017 12:15
-
-
Save codecat/b1c265f7c32e1432e5138001afa46fb2 to your computer and use it in GitHub Desktop.
16 bit bytecode VM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// This is an incomplete VM that runs 16 bit bytecode written in C++. | |
// It's very basic and probably lacks a lot, so this is not to be used in production at all. | |
// It was however fun to write, so I provide this as a Gist. | |
// | |
// https://github.com/codecat | |
// | |
#include <cstdio> | |
#include <cstdint> | |
#include <cstdlib> | |
#include <cstring> | |
#include <cassert> | |
#include <string> | |
class VM_RAM | |
{ | |
private: | |
uint8_t* m_buffer; | |
uint16_t m_size; | |
public: | |
VM_RAM(uint16_t size) | |
{ | |
m_buffer = (uint8_t*)malloc(size); | |
m_size = size; | |
memset(m_buffer, 0, size); | |
} | |
~VM_RAM() | |
{ | |
free(m_buffer); | |
} | |
uint16_t GetSize() | |
{ | |
return m_size; | |
} | |
uint16_t Write(uint16_t ptr, void* buffer, uint16_t bufferSize) | |
{ | |
uint16_t writeSize = bufferSize; | |
if (ptr >= m_size) { | |
return 0; | |
} | |
if (ptr + writeSize > m_size) { | |
writeSize = m_size - ptr; | |
} | |
memcpy(m_buffer + ptr, buffer, writeSize); | |
return writeSize; | |
} | |
//TODO: Read() | |
uint16_t WriteByte(uint16_t ptr, uint8_t b) | |
{ | |
return Write(ptr, &b, sizeof(uint8_t)); | |
} | |
uint16_t WriteShort(uint16_t ptr, uint16_t i) | |
{ | |
return Write(ptr, &i, sizeof(uint16_t)); | |
} | |
uint8_t ReadByte(uint16_t ptr) | |
{ | |
if (ptr >= m_size) { | |
return 0xFF; | |
} | |
return m_buffer[ptr]; | |
} | |
uint16_t ReadShort(uint16_t ptr) | |
{ | |
if (ptr + 1 >= m_size) { | |
return (ReadByte(ptr) << 8) | 0xFF; | |
} | |
return *(uint16_t*)(m_buffer + ptr); | |
} | |
}; | |
enum VM_Op | |
{ | |
// (1) Does nothing. | |
op_Nop = 0x00, | |
// (4) Sets a register(+1) to a constant value(+2). | |
op_MovReg = 0x01, | |
// (4) Puts a register(+1)'s value into memory at pointer(+2). | |
op_MovPut = 0x02, | |
// (4) Gets a value from memory at pointer(+2) and stores it in a register(+1). | |
op_MovGet = 0x03, | |
// (2) Push register(+1) on the stack. | |
op_Push = 0x04, | |
// (2) Pop register(+1) from the stack and store the stack value in the register(+1). | |
op_Pop = 0x05, | |
// (2,4) Either jumps to the address in a register(+1), or if the register is unnamed, jump to an absolute memory address(+2). | |
op_Jump = 0x06, | |
// (2,4) Either jumps down to the offset in a register(+1), or if the register is unnamed, jump down to the offset(+2). | |
op_JumpDown = 0x07, | |
// (2,4) Either jumps up to the offset in a register(+1), or if the register is unnamed, jump up to the offset(+2). | |
op_JumpUp = 0x08, | |
op_CmpReg = 0x09, | |
op_JumpEqual = 0x0A, | |
op_JumpEqualDown = 0x0B, | |
op_JumpEqualUp = 0x0C, | |
op_JumpNotEqual = 0x0D, | |
op_JumpNotEqualDown = 0x0E, | |
op_JumpNotEqualUp = 0x0F, | |
// (1) Interrupts the VM. | |
op_Interrupt = 0xFE, | |
// (1) Halts the VM. | |
op_Halt = 0xFF, | |
}; | |
enum VM_Flag | |
{ | |
fl_ZeroFlag = 0, | |
}; | |
/* | |
VM RAM Layout: | |
0x0010-0x0FFF: Code | |
0x1000-0x14FF: Stack | |
0x1500-0xFFFF: Heap | |
*/ | |
class VM | |
{ | |
public: | |
bool m_halted = false; | |
bool m_interrupted = false; | |
struct | |
{ | |
uint16_t ax, bx, cx, dx; | |
uint16_t ip, sp, bp; | |
uint16_t flags; | |
} m_regs; | |
VM_RAM m_ram; | |
public: | |
VM(uint16_t ramSize) | |
: m_ram(ramSize) | |
{ | |
memset(&m_regs, 0, sizeof(m_regs)); | |
m_regs.ip = 0x0010; | |
m_regs.sp = 0x1500; | |
} | |
void DumpRegs() | |
{ | |
printf("REG DUMP:\n"); | |
printf(" ax: %04X bx: %04X cx: %04X dx: %04X\n", m_regs.ax, m_regs.bx, m_regs.cx, m_regs.dx); | |
printf(" ip: %04X sp: %04X bp: %04X\n", m_regs.ip, m_regs.sp, m_regs.bp); | |
printf(" flags: %04X\n", m_regs.flags); | |
} | |
void Halt() | |
{ | |
printf("HALT\n"); | |
m_halted = true; | |
} | |
void Interrupt() | |
{ | |
m_interrupted = true; | |
} | |
void Step() | |
{ | |
m_interrupted = false; | |
uint16_t &ip = m_regs.ip; | |
uint16_t &sp = m_regs.sp; | |
if (ip > m_ram.GetSize() || ip < 0x0010) { | |
Halt(); | |
return; | |
} | |
VM_Op op = (VM_Op)m_ram.ReadByte(ip); | |
if (op == op_Nop) { | |
ip++; | |
return; | |
} | |
if (op == op_MovReg) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg == nullptr) { | |
Halt(); | |
} else { | |
*reg = m_ram.ReadShort(ip + 2); | |
} | |
ip += 4; | |
return; | |
} | |
if (op == op_MovPut) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg == nullptr) { | |
Halt(); | |
} else { | |
uint16_t ptr = m_ram.ReadShort(ip + 2); | |
m_ram.WriteShort(ptr, *reg); | |
} | |
ip += 4; | |
return; | |
} | |
if (op == op_MovGet) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg == nullptr) { | |
Halt(); | |
} else { | |
uint16_t ptr = m_ram.ReadShort(ip + 2); | |
*reg = m_ram.ReadShort(ptr); | |
} | |
ip += 4; | |
return; | |
} | |
if (op == op_Push) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg == nullptr) { | |
Halt(); | |
} else { | |
sp -= 2; | |
if (sp < 0x1000) { | |
Halt(); | |
} | |
m_ram.WriteShort(sp, *reg); | |
} | |
ip += 2; | |
return; | |
} | |
if (op == op_Pop) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg == nullptr) { | |
Halt(); | |
} else { | |
*reg = m_ram.ReadShort(sp); | |
sp += 2; | |
} | |
ip += 2; | |
return; | |
} | |
if (op == op_Jump || op == op_JumpDown || op == op_JumpUp) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg != nullptr) { | |
switch (op) { | |
case op_Jump: ip = *reg; break; | |
case op_JumpDown: ip += *reg; break; | |
case op_JumpUp: ip -= *reg; break; | |
} | |
} else { | |
uint16_t ptr = m_ram.ReadShort(ip + 2); | |
switch (op) { | |
case op_Jump: ip = ptr; break; | |
case op_JumpDown: ip += ptr; break; | |
case op_JumpUp: ip -= ptr; break; | |
} | |
} | |
return; | |
} | |
if (op == op_CmpReg) { | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg == nullptr) { | |
Halt(); | |
} else { | |
uint16_t a = *reg; | |
uint16_t b = m_ram.ReadShort(ip + 2); | |
SetFlag(fl_ZeroFlag, a == b); | |
} | |
ip += 4; | |
return; | |
} | |
if (op == op_JumpEqual || op == op_JumpEqualDown || op == op_JumpEqualUp || op == op_JumpNotEqual || op == op_JumpNotEqualDown || op == op_JumpNotEqualUp) { | |
bool take = GetFlag(fl_ZeroFlag); | |
if (op == op_JumpNotEqual || op == op_JumpNotEqualDown || op == op_JumpNotEqualUp) { | |
take = !take; | |
} | |
uint16_t* reg = GetReg(m_ram.ReadByte(ip + 1)); | |
if (reg != nullptr) { | |
if (take) { | |
switch (op) { | |
case op_JumpEqual: | |
case op_JumpNotEqual: ip = *reg; break; | |
case op_JumpEqualDown: | |
case op_JumpNotEqualDown: ip += *reg; break; | |
case op_JumpEqualUp: | |
case op_JumpNotEqualUp: ip -= *reg; break; | |
} | |
} else { | |
ip += 2; | |
} | |
} else { | |
if (take) { | |
uint16_t ptr = m_ram.ReadShort(ip + 2); | |
switch (op) { | |
case op_JumpEqual: | |
case op_JumpNotEqual: ip = ptr; break; | |
case op_JumpEqualDown: | |
case op_JumpNotEqualDown: ip += ptr; break; | |
case op_JumpEqualUp: | |
case op_JumpNotEqualUp: ip -= ptr; break; | |
} | |
} else { | |
ip += 4; | |
} | |
} | |
return; | |
} | |
if (op == op_Interrupt) { | |
ip++; | |
Interrupt(); | |
return; | |
} | |
if (op == op_Halt) { | |
ip++; | |
Halt(); | |
return; | |
} | |
// If we get here, we didn't match any opcode | |
Halt(); | |
} | |
private: | |
uint16_t* GetReg(uint8_t id) | |
{ | |
switch (id) { | |
case 0: return &m_regs.ax; | |
case 1: return &m_regs.bx; | |
case 2: return &m_regs.cx; | |
case 3: return &m_regs.dx; | |
case 4: return &m_regs.ip; | |
case 5: return &m_regs.sp; | |
case 6: return &m_regs.bp; | |
case 7: return &m_regs.flags; | |
} | |
return nullptr; | |
} | |
void SetFlag(VM_Flag flag, bool b) | |
{ | |
if (b) { | |
m_regs.flags |= (1 << flag); | |
} else { | |
m_regs.flags &= ~(1 << flag); | |
} | |
} | |
bool GetFlag(VM_Flag flag) | |
{ | |
return (m_regs.flags & (1 << flag)) > 0; | |
} | |
}; | |
class VM_ASM | |
{ | |
private: | |
const char* m_text = nullptr; | |
int m_textLength = 0; | |
uint8_t* m_buffer = nullptr; | |
uint16_t m_size = 0x100; | |
uint16_t m_offset = 0; | |
public: | |
VM_ASM(const char* text) | |
{ | |
m_text = text; | |
m_textLength = strlen(text); | |
ResizeBuffer(m_size); | |
} | |
~VM_ASM() | |
{ | |
if (m_buffer != nullptr) { | |
free(m_buffer); | |
} | |
} | |
uint8_t* GetBuffer() | |
{ | |
return m_buffer; | |
} | |
uint16_t GetSize() | |
{ | |
return m_size; | |
} | |
void Assemble() | |
{ | |
const char* p = m_text; | |
while (*p != '\0') { | |
const char* szLine = p; | |
while (*p != '\n' && *p != '\0') { | |
p++; | |
} | |
std::string line(szLine, p - szLine); | |
AssembleLine(line.c_str()); | |
p++; | |
continue; | |
} | |
} | |
private: | |
void AssembleLine(const char* line) | |
{ | |
const char* p = line; | |
while (*p != ' ' && *p != '\0') { | |
p++; | |
} | |
std::string op(line, p - line); | |
//TODO | |
} | |
void ResizeBuffer(uint16_t size) | |
{ | |
m_buffer = (uint8_t*)realloc(m_buffer, size); | |
} | |
}; | |
int main_vmtest() | |
{ | |
VM vm(0x2000); | |
/* | |
VM_ASM assembler("mov ax, 0x0001\n\ | |
cmp ax, 0x0001\n\ | |
jed 0x05\n\ | |
halt\n\ | |
mov ax, 0xFFFF\n\ | |
halt" | |
); | |
assembler.Assemble(); | |
*/ | |
uint8_t bytecode[] = { | |
// mov ax, 0x0001 | |
0x01, 0x00, 0x01, 0x00, | |
// cmp ax, 0x0001 | |
0x09, 0x00, 0x01, 0x00, | |
// jed 0x05 | |
0x0B, 0xFF, 0x05, 0x00, | |
// halt | |
0xFF, | |
// mov ax, 0xFFFF | |
0x01, 0x00, 0xFF, 0xFF, | |
// halt | |
0xFF, | |
}; | |
vm.m_ram.Write(0x0010, bytecode, sizeof(bytecode)); | |
int stepCount = 0; | |
while (!vm.m_halted) { | |
do { | |
vm.Step(); | |
stepCount++; | |
} while (!vm.m_interrupted && !vm.m_halted); | |
if (!vm.m_halted) { | |
printf("VM execution was interrupted after %d steps.\n", stepCount); | |
vm.DumpRegs(); | |
} | |
} | |
printf("VM execution was halted after %d steps.\n", stepCount); | |
vm.DumpRegs(); | |
getchar(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment