Created
January 30, 2018 23:11
-
-
Save jesyspa/dacef8d9749a7ff49c439c11d51ca9e0 to your computer and use it in GitHub Desktop.
A very very simple virtual machine.
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
#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