Overview
The Romasm Virtual Machine (VM) is a JavaScript-based interpreter that executes Romasm assembly instructions. It's not compiled to machine code or WebAssembly - it's a pure JavaScript interpreter that simulates a CPU.
VM Architecture
Registers
The VM maintains 9 registers (R0-R8, mapped to Roman numerals I-IX):
this.registers = {
'I': 0, 'II': 0, 'III': 0, 'IV': 0, 'V': 0,
'VI': 0, 'VII': 0, 'VIII': 0, 'IX': 0
};
Memory
A sparse associative array for memory storage:
this.memory = {}; // Address -> Value mapping
Stack
Used for function calls and temporary storage:
this.stack = []; // Array-based stack
Program Counter
Tracks the current instruction being executed:
this.pc = 0; // Current instruction index
Flags
Status flags set by comparison operations:
this.flags = {
equal: false,
lessThan: false,
greaterThan: false
};
Execution Loop
The VM executes instructions in a loop until the program halts:
Step-by-Step Execution
while (!this.halted && steps < maxSteps) {
// 1. Fetch instruction at PC
const instruction = this.instructions[this.pc];
// 2. Execute the instruction
this.executeInstruction(instruction);
// 3. Increment program counter (unless jumped)
this.pc++;
// 4. Check for halt condition
if (this.halted) break;
}
Instruction Execution
Each instruction is executed by a switch statement that maps opcodes to operations:
Example: ADD Instruction
case 'A': // ADD
const reg1 = operands[0].value; // 'I' (R0)
const reg2 = operands[1].value; // 'II' (R1)
this.registers[reg1] += this.registers[reg2];
break;
When you write ADD R0, R1, the VM actually executes:
this.registers['I'] += this.registers['II'];
Example: LOAD Instruction
case 'L': // LOAD
const reg = operands[0].value;
const src = operands[1];
if (src.type === 'immediate') {
this.registers[reg] = src.value;
} else if (src.type === 'register') {
this.registers[reg] = this.registers[src.value];
} else if (src.isMemory) {
this.registers[reg] = this.memory[src.value] || 0;
}
break;
Function Calls
CALL Instruction
When CALL function_name is executed:
- Push current PC to stack (return address)
- Jump to function's address
case 'CA': // CALL
this.stack.push(this.pc); // Save return address
this.pc = operands[0].value - 1; // Jump to function
break;
RET Instruction
When RET is executed:
- Pop return address from stack
- Jump back to caller
case 'R': // RET
if (this.stack.length > 0) {
this.pc = this.stack.pop(); // Restore return address
} else {
this.halted = true; // No return address = program end
}
break;
Canvas Integration
The VM can be initialized with a canvas context for drawing operations:
Drawing Opcodes
case 'MOV': // MOVE
const y = this.stack.pop();
const x = this.stack.pop();
if (this.canvasContext) {
this.canvasContext.moveTo(x, y);
}
break;
case 'DRW': // DRAW
const y = this.stack.pop();
const x = this.stack.pop();
if (this.canvasContext) {
this.canvasContext.lineTo(x, y);
}
break;
Safety Features
Maximum Steps
The VM has a safety limit to prevent infinite loops:
run(maxSteps = 10000) {
while (!this.halted && steps < maxSteps) {
// Execute instructions...
}
}
Error Handling
Division by zero and other errors are caught:
case 'DI': // DIV
const divisor = this.registers[reg2];
if (divisor === 0) {
throw new Error('Division by zero');
}
// ... perform division
break;
Related Documentation
- Assembler - How code is converted to instructions
- Instruction Set - All available instructions
- Registers & Memory - Understanding the register system