Romasm Virtual Machine

How the VM executes Romasm instructions

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:

  1. Push current PC to stack (return address)
  2. 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:

  1. Pop return address from stack
  2. 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