Skip to content

Instantly share code, notes, and snippets.

@codecat
Last active April 16, 2017 12:15
Show Gist options
  • Save codecat/b1c265f7c32e1432e5138001afa46fb2 to your computer and use it in GitHub Desktop.
Save codecat/b1c265f7c32e1432e5138001afa46fb2 to your computer and use it in GitHub Desktop.
16 bit bytecode VM
//
// 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