Last active
June 15, 2018 20:38
-
-
Save DmitrySoshnikov/6407781 to your computer and use it in GitHub Desktop.
A simple educational Register-based VM, Assembler, and Disassembler.
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
// "Fetch, decode, eval!" | |
// A simple Register-based VM, Assembler, and Disassembler. | |
// by Dmitry Soshnikov <[email protected]> | |
// This virtual machine (VM) consists of registers (data storage), | |
// and operations (instructions) which operate on the registers. | |
// -------------------------------------------------------------- | |
// Registers. | |
// -------------------------------------------------------------- | |
// Storage: actual regs contents, initially empty (0). | |
var regStore = {ax: 0, bx: 0, cx: 0, dx: 0}; | |
// Array to map register name to a number (index of the array), to be encoded | |
// in the instructions: ax: 0, bx: 1, etc. | |
var regs = Object.keys(regStore); | |
// -------------------------------------------------------------- | |
// Instructions. Known as "opcodes" (operation codes). | |
// -------------------------------------------------------------- | |
var instructionSet = { | |
halt: 0, // stops the program | |
mov : 1, // moves contents to a register | |
add : 2 // sums reg1 to reg2 and stores back in reg1 | |
}; | |
// -------------------------------------------------------------- | |
// Assembler. Compiles mnemonics into a machine code. | |
// -------------------------------------------------------------- | |
function assemble(program) { | |
return program.map(function(command) { | |
var | |
parsed = command.replace(',', '').split(/\s+/); | |
instruction = parsed[0], | |
op1 = parsed[1], | |
op2 = parsed[2]; | |
// Each instruction is encoded as: [opcode, op1, op2], which in memory | |
// represented as HEX code 0x<opcode><op1><op2>, e.g. 0x1014 -- mov ax, 20. | |
var machineInstruction = [ | |
instructionSet[instruction], | |
// In our instructions, firt op is always a register. | |
regs.indexOf(op1) > -1 ? regs.indexOf(op1) : 0, | |
// Second can be a register or an immediate value (stored as HEX). | |
regs.indexOf(op2) > -1 ? regs.indexOf(op2) : | |
op2 ? Number(op2).toString(16) : 0, | |
]; | |
// Actual memory data, representing code in HEX. | |
memoryDump.push('0x' + machineInstruction.join('').toUpperCase()); | |
return machineInstruction; | |
}); | |
} | |
// -------------------------------------------------------------- | |
// Decoder. Decodes instructions from memory. | |
// -------------------------------------------------------------- | |
function decode(instruction) { | |
// In real machine, here goes decoding of encoded instruction number in | |
// memory. Here we have a simple version: [instr, reg, (reg|hex-value)]. | |
return [ | |
instruction[0], | |
instruction[1], | |
parseInt(instruction[2], 16) | |
]; | |
} | |
// -------------------------------------------------------------- | |
// Evaluating module of CPU. | |
// -------------------------------------------------------------- | |
function eval(decoded) { | |
var | |
instruction = decoded[0], | |
op1 = decoded[1], | |
op2 = decoded[2]; | |
switch (instruction) { | |
case 0: // halt, stop execution. | |
console.log('halt'); | |
isRunning = false; | |
break; | |
case 1: // mov | |
regStore[regs[op1]] = op2; | |
// Debug disassembler at running. | |
console.log('mov ', regs[op1], ', ', op2); | |
break; | |
case 2: // add | |
regStore[regs[op1]] = regStore[regs[op1]] + regStore[regs[op2]]; | |
console.log('add', regs[op1], ', ', regs[op2]); | |
break; | |
} | |
} | |
// -------------------------------------------------------------- | |
// Fetcher. | |
// -------------------------------------------------------------- | |
// Instructions pointer, points to the current command to be executed. | |
var ip = 0; | |
// Fetches the next command from the program (code segment in memory). | |
function fetch() { | |
return program[ip++]; | |
} | |
// -------------------------------------------------------------- | |
// Testing. | |
// -------------------------------------------------------------- | |
// Test assembly program. | |
var assemblyCode = [ | |
'mov ax, 10', | |
'mov bx, 20', | |
'add ax, bx', | |
'halt' | |
]; | |
// Generate machine code. | |
var memoryDump = []; | |
var program = assemble(assemblyCode); | |
// Check generated machine code. | |
// ['0x10A', '0x1114', '0x201', '0x000'] | |
console.log('Code segment:', memoryDump); | |
// CPU at work. | |
var isRunning = true; | |
while (isRunning) { | |
// Main execution loop: fetch, decode, eval. | |
var instruction = fetch(); | |
var decoded = decode(instruction); | |
eval(decoded); | |
} | |
// Check registers contents after program execution. | |
// {ax: 30, bx: 20, cx: 0, dx: 0} | |
console.log('Registers:', regStore); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In this case it's not about JS, it's about machine ;) JS here used as just a quick language for testing.