Skip to content

Instantly share code, notes, and snippets.

@jesyspa
Created January 30, 2018 23:11
Show Gist options
  • Save jesyspa/dacef8d9749a7ff49c439c11d51ca9e0 to your computer and use it in GitHub Desktop.
Save jesyspa/dacef8d9749a7ff49c439c11d51ca9e0 to your computer and use it in GitHub Desktop.
A very very simple virtual machine.
#include <iostream>
#include <vector>
#include <array>
#include <string>
#include <variant>
#include <stdexcept>
#include <iomanip>
struct StoreTag {};
struct LoadTag {};
struct Addr {
unsigned addr;
};
struct Reg {
unsigned reg;
};
struct IndirectReg {
unsigned reg;
};
struct ConstantLoad {
unsigned constant;
};
using LoadTarget = std::variant<Addr, Reg, IndirectReg, ConstantLoad>;
using StoreTarget = std::variant<Addr, Reg, IndirectReg>;
enum class BinaryOpCode {
Add, Sub, Eq, Le
};
struct BinaryOp {
BinaryOpCode code;
LoadTarget arg0;
LoadTarget arg1;
StoreTarget tgt;
};
struct MoveOp {
LoadTarget src;
StoreTarget tgt;
};
struct CondMoveOp {
LoadTarget cond;
LoadTarget src;
StoreTarget tgt;
};
struct HaltOp {};
struct PrintOp {
bool ascii;
LoadTarget src;
};
struct ReadOp {
bool ascii;
StoreTarget tgt;
};
using Op = std::variant<BinaryOp, MoveOp, CondMoveOp, HaltOp, PrintOp, ReadOp>;
// stolen from cppreference
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
class Machine {
std::array<unsigned, 16> m_regs;
std::vector<unsigned> m_memory;
bool running = false;
unsigned execute_load(LoadTarget tgt) const {
return std::visit(overloaded {
[this](Addr addr) { return m_memory[addr.addr]; },
[this](Reg reg) { return m_regs[reg.reg]; },
[this](IndirectReg ireg) { return m_memory[m_regs[ireg.reg]]; },
[](ConstantLoad cload) { return cload.constant; }
}, tgt);
}
void execute_store(StoreTarget tgt, unsigned val) {
std::visit(overloaded {
[this, val](Addr addr) { m_memory[addr.addr] = val; },
[this, val](Reg reg) { m_regs[reg.reg] = val; },
[this, val](IndirectReg ireg) { m_memory[m_regs[ireg.reg]] = val; }
}, tgt);
}
unsigned execute_bin_opcode(BinaryOpCode code, unsigned x, unsigned y) {
switch (code) {
case BinaryOpCode::Add:
return x + y;
case BinaryOpCode::Sub:
return x - y;
case BinaryOpCode::Eq:
return x == y;
case BinaryOpCode::Le:
return x < y;
default:
throw std::runtime_error{"Unknown binop"};
};
}
void execute_bin_op(BinaryOp binop) {
auto arg0 = execute_load(binop.arg0);
auto arg1 = execute_load(binop.arg1);
auto value = execute_bin_opcode(binop.code, arg0, arg1);
execute_store(binop.tgt, value);
}
void execute_move_op(MoveOp moveop) {
auto value = execute_load(moveop.src);
execute_store(moveop.tgt, value);
}
void execute_cmove_op(CondMoveOp cmoveop) {
auto cond = execute_load(cmoveop.cond);
if (!cond)
return;
auto value = execute_load(cmoveop.src);
execute_store(cmoveop.tgt, value);
}
void execute_print_op(PrintOp printop) {
auto val = execute_load(printop.src);
if (printop.ascii)
std::cout << (char)val;
else
std::cout << val;
}
void execute_read_op(ReadOp readop) {
if (readop.ascii) {
execute_store(readop.tgt, std::cin.get());
} else {
unsigned val;
std::cin >> val;
execute_store(readop.tgt, val);
}
}
void execute_op(Op op) {
std::visit(overloaded {
[this](BinaryOp binop) { execute_bin_op(binop); },
[this](MoveOp moveop) { execute_move_op(moveop); },
[this](CondMoveOp cmoveop) { execute_cmove_op(cmoveop); },
[this](ReadOp readop) { execute_read_op(readop); },
[this](PrintOp printop) { execute_print_op(printop); },
[this](HaltOp) { running = false; },
}, op);
}
unsigned& ip() {
return m_regs[15];
}
// takes the 6 bit code (see instruction format)
// may modify ip
LoadTarget get_load_target(unsigned code) {
switch (code >> 4) {
case 0:
return Reg{code & 0xF};
case 1:
return IndirectReg{code & 0xF};
case 2:
{
auto val = m_memory[ip()];
ip() += 1;
if (code == 0x20)
return ConstantLoad{val};
else if (code == 0x21)
return Addr{val};
else
throw std::runtime_error{"Unrecognised access mode"};
}
default:
throw std::runtime_error{"Invalid code"};
}
}
StoreTarget get_store_target(unsigned code) {
switch (code >> 4) {
case 0:
return Reg{code & 0xF};
case 1:
return IndirectReg{code & 0xF};
case 2:
{
auto val = m_memory[ip()];
ip() += 1;
if (code == 0x21)
return Addr{val};
else
throw std::runtime_error{"Unrecognised access mode"};
}
default:
throw std::runtime_error{"Invalid code"};
}
}
Op decode_next() {
// Instruction format. 32 bits per instruction.
// starts with 0000: special
// all-zero: halt
// starts with 0001: IO
// starts with 10XX: non-conditional XX-ary op
// starts with 11XX: conditional XX-ary op.
// next four bits indicate the operation.
// IO operations are read, print, read ascii, print ascii
// Only unary operation is move.
// Binary operators are listed in BinaryOpCode (have corresponding indices).
// next groups of six bits indicate whether to use
// 00XXXX register XXXX
// 01XXXX indirect register XXXX
// 100000 literal value (next word)
// 100001 memory at address (next word)
//
// If I was serious, I would implement helpers to access ranges easily.
auto opcode = m_memory[ip()];
ip() += 1;
if (opcode == 0)
return HaltOp{};
switch (opcode >> 28) {
// only unary and binary ops implemented so far
case 1: // special
{
if ((opcode >> 24) & 1) {
PrintOp printop;
printop.ascii = (opcode >> 25) & 1;
printop.src = get_load_target((opcode >> 18) & 0x2F);
return printop;
} else {
ReadOp readop;
readop.ascii = (opcode >> 25) & 1;
readop.tgt = get_store_target((opcode >> 18) & 0x2F);
return readop;
}
}
case 9: // unary
{
if ((opcode >> 24) & 0xF)
throw std::runtime_error{"Only valid unary op is move."};
MoveOp moveop;
moveop.src = get_load_target((opcode >> 18) & 0x2F);
moveop.tgt = get_store_target((opcode >> 12) & 0x2F);
return moveop;
}
case 10: // binary
{
BinaryOp binop;
binop.code = (BinaryOpCode)((opcode >> 24) & 0xF);
binop.arg0 = get_load_target((opcode >> 18) & 0x2F);
binop.arg1 = get_load_target((opcode >> 12) & 0x2F);
binop.tgt = get_store_target((opcode >> 6) & 0x2F);
return binop;
}
case 13: // conditional unary
{
if ((opcode >> 24) & 0xF)
throw std::runtime_error{"Only valid unary op is move."};
CondMoveOp cmoveop;
cmoveop.cond = get_load_target((opcode >> 18) & 0x2F);
cmoveop.src = get_load_target((opcode >> 12) & 0x2F);
cmoveop.tgt = get_store_target((opcode >> 6) & 0x2F);
return cmoveop;
}
default:
throw std::runtime_error{"Unrecognised opcode"};
}
};
public:
Machine(unsigned size, std::vector<unsigned> prog) : m_regs{}, m_memory(prog) {
if (size < prog.size())
throw std::runtime_error{"Need more memory."};
m_memory.resize(size);
}
void run() {
running = true;
while (running) {
// the decoding process advances the IP
auto op = decode_next();
execute_op(op);
}
}
};
unsigned combine(unsigned opcode1 = 0, unsigned opcode2 = 0, unsigned arg0 = 0, unsigned arg1 = 0, unsigned arg2 = 0, unsigned arg3 = 0) {
return (opcode1 << 28) | (opcode2 << 24) | (arg0 << 18) | (arg1 << 12) | (arg2 << 6) | arg3;
}
int main() {
std::vector<unsigned> program {
combine(1, 0, 0),
combine(1, 1, 0),
combine(1, 3, 0x20),
+'\n',
combine(10, 2, 0, 13, 1),
combine(13, 0, 1, 0x20, 15),
100,
combine(10, 1, 0, 0x20, 0),
1,
combine(9, 0, 0x20, 15),
1
};
Machine machine(512, program);
machine.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment