I am a web developer, trying to have fun with C++11, so any positive/negative feedback is very welcome.
This is a toy virtual machine with 3 registers and 3 commands:
vm/vm.h
#ifndef VM_H
#define VM_H
#include <iostream>
#include <vector>
#include "vtypes.h"
namespace violet
{
class Vm
{
bool running;
std::vector<u16> program;
u16 regs[REGS_N];
u16 pc;
public:
Vm(const std::vector<u16>);
~Vm();
u16 fetch();
int run();
std::vector<u16> dump_regs() const;
instruction decode(const unsigned int) const;
};
}
#endif // VM_H
vm/vm.cpp
#include "vm.h"
namespace violet
{
Vm::Vm(const std::vector<u16> program):
running(false),
program(program),
pc(0)
{
}
Vm::~Vm()
{
}
u16 Vm::fetch()
{
return program.at(pc++);
}
int Vm::run()
{
running = true;
instruction instr;
if (program.empty()) return 1;
while(running && pc < program.size())
{
instr = decode(fetch());
switch(instr.code)
{
case i_halt:
running = false;
break;
case i_loadi:
regs[instr.r0] = instr.imm;
break;
case i_add:
regs[instr.r0] = regs[instr.r1] + regs[instr.r2];
break;
}
}
return 0;
}
std::vector<u16> Vm::dump_regs() const
{
return std::vector<u16>(std::begin(regs), std::end(regs));
}
instruction Vm::decode(const unsigned int opcode) const
{
instruction i;
// Bits 15-12 (4bits) Instruction number
// Bits 11-8 (4bits) Register number
// Bits 7-0 (8bits) Immediate value
i.code = (opcode & 0xF000) >> 12;
i.r0 = (opcode & 0xF00) >> 8;
i.r1 = (opcode & 0xF0) >> 4;
i.r2 = (opcode & 0xF);
i.imm = (opcode & 0xFF);
return i;
}
}
vm/vtypes.h
#ifndef VTYPES_H
#define VTYPES_H
using u8 = unsigned char; // At least 8-bits
using u16 = unsigned short; // At least 16-bits - (Usually)
namespace violet
{
const unsigned int REGS_N {3};
// Instructions
const unsigned int i_halt {0};
const unsigned int i_loadi {0x1};
const unsigned int i_add {0x2};
struct instruction
{
unsigned int code;
unsigned int r0;
unsigned int r1;
unsigned int r2;
int imm;
};
}
#endif // VTYPES_H
vm/main.cpp
#include <vector>
#include "vm/vm.h"
#include "etc/utils.h"
using namespace violet;
int main(int argc, char *argv[])
{
int vm_res;
const std::vector<u16> program = utils::load_program_file(argv[1]);
Vm vm(program);
vm_res = vm.run();
for (auto r : vm.dump_regs())
{
std::cout << r << " ";
} std::cout << std::endl;
return vm_res;
}
asm/vlasm.h
#ifndef VLASM_H
#define VLASM_H
#include <vector>
#include <string>
#include <unordered_map>
#include "vm/vtypes.h"
namespace violet
{
namespace vlasm
{
const char comment(';');
const std::string i_halt("halt");
const std::string i_loadi("loadi");
const std::string i_add("add");
const std::unordered_map<std::string, unsigned int>
registers {
{"r0", 0},
{"r1", 1},
{"r2", 2}
};
std::vector<unsigned int> parse_source(const std::vector<std::string>);
std::vector<std::string> parse_line(const std::string);
unsigned int parse_tokens(const std::vector<std::string>);
unsigned int parse_reg(const std::string);
void die(const char *);
};
}
#endif // VLASM_H
asm/vlasm.cpp
#include <sstream>
#include <iostream>
#include <iterator>
#include "vlasm.h"
namespace violet
{
namespace vlasm
{
std::vector<unsigned int>
parse_source(const std::vector<std::string> source)
{
std::vector<unsigned int> program;
for (auto line : source)
{
if (line[0] == comment) continue;
std::vector<std::string> tokens = parse_line(line);
program.push_back(parse_tokens(tokens));
}
return program;
}
std::vector<std::string>
parse_line(const std::string line)
{
std::istringstream iss(line);
std::vector<std::string> tokens {
std::istream_iterator<std::string>{iss},
std::istream_iterator<std::string>{}
};
return tokens;
}
unsigned int parse_tokens(const std::vector<std::string> tokens)
{
unsigned int opcode;
if (tokens[0] == vlasm::i_halt)
{
opcode = violet::i_halt;
}
else if (tokens[0] == vlasm::i_loadi)
{
if (tokens.size() != 3) die("Invalid load operation");
opcode = (violet::i_loadi << 12) |
(parse_reg(tokens[1]) << 8) |
std::stoi(tokens[2]) ;
}
else if (tokens[0] == vlasm::i_add)
{
if (tokens.size() != 4) die("Invalid addition operation");
opcode = (violet::i_add << 12) |
(parse_reg(tokens[1]) << 8) |
(parse_reg(tokens[2]) << 4) |
parse_reg(tokens[3]);
}
else
{
die("Invalid instruction found");
}
return opcode;
}
unsigned int parse_reg(std::string reg_str)
{
std::unordered_map<std::string, unsigned int>::const_iterator
r = registers.find(reg_str);
if (r == registers.end()) die("Invalid register");
return r->second;
}
void die(const char *message)
{
std::cerr << message << std::endl;
exit(1);
}
}
}
asm/main.cpp
#include <vector>
#include <etc/utils.h>
#include "vlasm.h"
using namespace violet;
int main(int argc, char *argv[])
{
const std::vector<std::string> source = utils::load_source_file(argv[1]);
const std::vector<unsigned int> program = vlasm::parse_source(source);
return utils::write_program_file(argv[2], program);
}
etc/utils.h
#ifndef UTILS_H
#define UTILS_H
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include "vm/vtypes.h"
namespace violet
{
namespace utils
{
std::vector<u16> load_program_file(const std::string);
std::vector<std::string> load_source_file(const std::string);
int write_program_file(const std::string, const std::vector<unsigned int>);
}
};
#endif // UTILS_H
etc/utils.cpp
#include "utils.h"
namespace violet
{
namespace utils
{
std::vector<u16>
load_program_file(const std::string filename)
{
u16 instr;
u8 bytes[2];
std::vector<u16> result;
std::ifstream file(filename, std::ios::binary);
if (file.fail())
{
std::cerr << "Could not load program" << std::endl;
return result;
}
while(file.read(reinterpret_cast<char*>(bytes), 2))
{
// Little endian
instr = bytes[0] | bytes[1] << 8;
result.push_back(instr);
}
return result;
}
std::vector<std::string>
load_source_file(const std::string filename)
{
std::string line;
std::vector<std::string> result;
std::ifstream file(filename);
if (file.fail())
{
std::cerr << "Could not load source" << std::endl;
return result;
}
while(std::getline(file, line))
{
result.push_back(line);
}
return result;
}
int write_program_file(
const std::string filename,
const std::vector<unsigned int> program)
{
std::ofstream out(filename, std::ios::trunc | std::ios::binary);
if (out.fail())
{
std::cerr << "Could not write program file" << std::endl;
return 1;
}
for (auto i : program)
{
out.write(reinterpret_cast<const char *>(&i), sizeof(u16));
}
return 0;
}
}
}
Usage:
$ violet /path/to/bin.s
$ vlasm /path/to/source.vl /path/to/bin.s
Asm syntax:
loadi [reg][val]
- load immediate value([val]
) to register([reg]
)add [reg1][reg2][reg3]
- place the sum of[reg2]
and[reg3]
into[reg1]
halt
- stop the execution
Example program
; comments start with semicolon
loadi r0 100
loadi r1 200
add r2 r0 r1
; now r2 holds the value 300
halt
This program compiled to vm bytecode:
6410 c811 0122 0000