A virtual machine implements a computer in software.

Task[edit]
Write a virtual machine interpreter. This interpreter should be able to run virtual
assembly language programs created via the task. This is a
byte-coded, 32-bit word stack based virtual machine.

The program should read input from a file and/or stdin, and write output to a file and/or
stdout.

Input format:

Given the following program:

count = 1;
while (count < 10) {
    print("count is: ", count, "\n");
    count = count + 1;
}

The output from the Code generator is a virtual assembly code program:



 Output from gen, input to VM



Datasize: 1 Strings: 2
"count is: "
"\n"
    0 push  1
    5 store [0]
   10 fetch [0]
   15 push  10
   20 lt
   21 jz     (43) 65
   26 push  0
   31 prts
   32 fetch [0]
   37 prti
   38 push  1
   43 prts
   44 fetch [0]
   49 push  1
   54 add
   55 store [0]
   60 jmp    (-51) 10
   65 halt

The first line of the input specifies the datasize required and the number of constant
strings, in the order that they are reference via the code.

The data can be stored in a separate array, or the data can be stored at the beginning of
the stack. Data is addressed starting at 0. If there are 3 variables, the 3rd one if
referenced at address 2.

If there are one or more constant strings, they come next. The code refers to these
strings by their index. The index starts at 0. So if there are 3 strings, and the code
wants to reference the 3rd string, 2 will be used.

Next comes the actual virtual assembly code. The first number is the code address of that
instruction. After that is the instruction mnemonic, followed by optional operands,
depending on the instruction.

Registers:

sp:

   the stack pointer - points to the next top of stack.  The stack is a 32-bit integer
   array.

pc:

   the program counter - points to the current instruction to be performed.  The code is an
   array of bytes.

Data:

   data
   string pool

Instructions:

Each instruction is one byte. The following instructions also have a 32-bit integer
operand:

fetch [index]

where index is an index into the data array.

store [index]

where index is an index into the data array.

push n

where value is a 32-bit integer that will be pushed onto the stack.

jmp (n) addr

where (n) is a 32-bit integer specifying the distance between the current location and the
desired location. addr is an unsigned value of the actual code address.

jz (n) addr

where (n) is a 32-bit integer specifying the distance between the current location and the
desired location. addr is an unsigned value of the actual code address.

The following instructions do not have an operand. They perform their operation directly
against the stack:

For the following instructions, the operation is performed against the top two entries in
the stack:

add
sub
mul
div
mod
lt
gt
le
ge
eq
ne
and
or

For the following instructions, the operation is performed against the top entry in the
stack:

neg
not

Print the word at stack top as a character.

prtc

Print the word at stack top as an integer.

prti

Stack top points to an index into the string pool. Print that entry.

prts

Unconditional stop.

halt

 A simple example virtual machine

def run_vm(data_size)
    int stack[data_size + 1000]
    set stack[0..data_size - 1] to 0
    int pc = 0
    while True:
        op = code[pc]
        pc += 1
 
        if op == FETCH:
            stack.append(stack[bytes_to_int(code[pc:pc+word_size])[0]]);
            pc += word_size
        elif op == STORE:
            stack[bytes_to_int(code[pc:pc+word_size])[0]] = stack.pop();
            pc += word_size
        elif op == PUSH:
            stack.append(bytes_to_int(code[pc:pc+word_size])[0]);
            pc += word_size
        elif op == ADD:   stack[-2] += stack[-1]; stack.pop()
        elif op == SUB:   stack[-2] -= stack[-1]; stack.pop()
        elif op == MUL:   stack[-2] *= stack[-1]; stack.pop()
        elif op == DIV:   stack[-2] /= stack[-1]; stack.pop()
        elif op == MOD:   stack[-2] %= stack[-1]; stack.pop()
        elif op == LT:    stack[-2] = stack[-2] <  stack[-1]; stack.pop()
        elif op == GT:    stack[-2] = stack[-2] >  stack[-1]; stack.pop()
        elif op == LE:    stack[-2] = stack[-2] <= stack[-1]; stack.pop()
        elif op == GE:    stack[-2] = stack[-2] >= stack[-1]; stack.pop()
        elif op == EQ:    stack[-2] = stack[-2] == stack[-1]; stack.pop()
        elif op == NE:    stack[-2] = stack[-2] != stack[-1]; stack.pop()
        elif op == AND:   stack[-2] = stack[-2] and stack[-1]; stack.pop()
        elif op == OR:    stack[-2] = stack[-2] or  stack[-1]; stack.pop()
        elif op == NEG:   stack[-1] = -stack[-1]
        elif op == NOT:   stack[-1] = not stack[-1]
        elif op == JMP:   pc += bytes_to_int(code[pc:pc+word_size])[0]
        elif op == JZ:    if stack.pop() then pc += word_size else pc += bytes_to_int(code[pc:pc+word_size])[0]
        elif op == PRTC:  print stack[-1] as a character; stack.pop()
        elif op == PRTS:  print the constant string referred to by stack[-1]; stack.pop()
        elif op == PRTI:  print stack[-1] as an integer; stack.pop()
        elif op == HALT:  break
 Additional examples
Your solution should pass all the test cases above and the additional tests found Here.

Reference
The C and Python versions can be considered reference implementations.

Related Tasks
 Lexical Analyzer task
 Syntax Analyzer task
 Code Generator task
 AST Interpreter task

//
// The Rosetta Code Virtual Machine in D.
//
// This code was migrated from an implementation in ATS. I have tried
// to keep it possible to compare the two languages easily, although
// in some cases the demonstration of "low level" techniques in ATS
// (such as avoiding memory leaks that might require garbage
// collection), or the use of linked lists as intermediate storage, or
// other such matters, seemed inappropriate to duplicate in D
// programming.
//
// (For example: in ATS, using a fully built linked list to initialize
// an array solves typechecking issues that simply do not exist in D's
// type system.)
//
 
import std.ascii;
import std.conv;
import std.stdint;
import std.stdio;
import std.string;
import std.typecons;
 
enum Op {
  HALT    = 0x0000,  // 00000
  ADD     = 0x0001,  // 00001
  SUB     = 0x0002,  // 00010
  MUL     = 0x0003,  // 00011
  DIV     = 0x0004,  // 00100
  MOD     = 0x0005,  // 00101
  LT      = 0x0006,  // 00110
  GT      = 0x0007,  // 00111
  LE      = 0x0008,  // 01000
  GE      = 0x0009,  // 01001
  EQ      = 0x000A,  // 01010
  NE      = 0x000B,  // 01011
  AND     = 0x000C,  // 01100
  OR      = 0x000D,  // 01101
  NEG     = 0x000E,  // 01110
  NOT     = 0x000F,  // 01111
  PRTC    = 0x0010,  // 10000
  PRTI    = 0x0011,  // 10001
  PRTS    = 0x0012,  // 10010
  FETCH   = 0x0013,  // 10011
  STORE   = 0x0014,  // 10100
  PUSH    = 0x0015,  // 10101
  JMP     = 0x0016,  // 10110
  JZ      = 0x0017   // 10111
}
 
const string[] opcodeOrder =
  ["halt",  // 00000   bit pattern
   "add",   // 00001
   "sub",   // 00010
   "mul",   // 00011
   "div",   // 00100
   "mod",   // 00101
   "lt",    // 00110
   "gt",    // 00111
   "le",    // 01000
   "ge",    // 01001
   "eq",    // 01010
   "ne",    // 01011
   "and",   // 01100
   "or",    // 01101
   "neg",   // 01110
   "not",   // 01111
   "prtc",  // 10000
   "prti",  // 10001
   "prts",  // 10010
   "fetch", // 10011
   "store", // 10100
   "push",  // 10101
   "jmp",   // 10110
   "jz"];   // 10111
 
enum Register {
  PC = 0,
  SP = 1,
  MAX = SP
}
 
alias vmint = uint32_t;
 
class VM {
  string[] strings;
  ubyte[] code;
  vmint[] data;
  vmint[] stack;
  vmint[Register.MAX + 1] registers;
}
 
class BadVMException : Exception
{
  this(string msg, string file = __FILE__, size_t line = __LINE__)
  {
    super(msg, file, line);
  }
}
 
class VMRuntimeException : Exception
{
  this(string msg, string file = __FILE__, size_t line = __LINE__)
  {
    super(msg, file, line);
  }
}
 
vmint
twosComplement (vmint x)
{
  // This computes the negative of x, if x is regarded as signed.
  pragma(inline);
  return (~x) + vmint(1U);
}
 
vmint
add (vmint x, vmint y)
{
  // This works whether x or y is regarded as unsigned or signed.
  pragma(inline);
  return x + y;
}
 
vmint
sub (vmint x, vmint y)
{
  // This works whether x or y is regarded as unsigned or signed.  
  pragma(inline);
  return x - y;
}
 
vmint
equality (vmint x, vmint y)
{
  pragma(inline);
  return vmint(x == y);
}
 
vmint
inequality (vmint x, vmint y)
{
  pragma(inline);
  return vmint(x != y);
}
 
vmint
signedLt (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) < int32_t(y));
}
 
vmint
signedGt (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) > int32_t(y));
}
 
vmint
signedLte (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) <= int32_t(y));
}
 
vmint
signedGte (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) >= int32_t(y));
}
 
vmint
signedMul (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) * int32_t(y));
}
 
vmint
signedDiv (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) / int32_t(y));
}
 
vmint
signedMod (vmint x, vmint y)
{
  pragma(inline);
  return vmint(int32_t(x) % int32_t(y));
}
 
vmint
logicalNot (vmint x)
{
  pragma(inline);
  return vmint(!x);
}
 
vmint
logicalAnd (vmint x, vmint y)
{
  pragma(inline);
  return vmint((!!x) * (!!y));
}
 
vmint
logicalOr (vmint x, vmint y)
{
  pragma(inline);
  return (vmint(1) - vmint((!x) * (!y)));
}
 
vmint
parseDigits (string s, size_t i, size_t j)
{
  const badInteger = "bad integer";
 
  if (j == i)
    throw new BadVMException (badInteger);
  auto x = vmint(0);
  for (size_t k = i; k < j; k += 1)
    if (!isDigit (s[k]))
      throw new BadVMException (badInteger);
    else
      // The result is allowed to overflow freely.
      x = (vmint(10) * x) + vmint(s[k] - '0');
  return x;
}
 
vmint
parseInteger (string s, size_t i, size_t j)
{
  const badInteger = "bad integer";
 
  vmint retval;
  if (j == i)
    throw new BadVMException (badInteger);
  else if (j == i + vmint(1) && !isDigit (s[i]))
    throw new BadVMException (badInteger);
  else if (s[i] != '-')
    retval = parseDigits (s, i, j);
  else if (j == i + vmint(1))
    throw new BadVMException (badInteger);
  else
    retval = twosComplement (parseDigits (s, i + vmint(1), j));
  return retval;
}
 
size_t
skipWhitespace (string s, size_t n, size_t i)
{
  while (i < n && isWhite (s[i]))
    i += 1;
  return i;
}
 
size_t
skipNonwhitespace (string s, size_t n, size_t i)
{
  while (i < n && !isWhite (s[i]))
    i += 1;
  return i;
}
 
bool
substrEqual (string s, size_t i, size_t j, string t)
{
  // Is s[i .. j-1] equal to t?
 
  auto retval = false;
  auto m = t.length;
  if (m == j - i)
    {
      auto k = size_t(0);
      while (k < m && s[i + k] == t[k])
        k += 1;
      retval = (k == m);
    }
  return retval;
}
 
string
dequoteString (string s, size_t n)
{
  const badQuotedString = "bad quoted string";
 
  string t = "";
  s = strip(s);
  if (s.length < 2 || s[0] != '"' || s[$ - 1] != '"')
    throw new BadVMException (badQuotedString);
  auto i = 1;
  while (i < s.length - 1)
    if (s[i] != '\\')
      {
        t ~= s[i];
        i += 1;
      }
    else if (i + 1 == s.length - 1)
      throw new BadVMException (badQuotedString);
    else if (s[i + 1] == 'n')
      {
        t ~= '\n';
        i += 2;
      }
    else if (s[i + 1] == '\\')
      {
        t ~= '\\';
        i += 2;
      }
    else
      throw new BadVMException (badQuotedString);
  return t;
}
 
string[]
readStrings (File f, size_t stringsSize)
{
  const badQuotedString = "Bad quoted string.";
 
  string[] strings;
  strings.length = stringsSize;
  for (size_t k = 0; k < stringsSize; k += 1)
    {
      auto line = f.readln();
      strings[k] = dequoteString (line, line.length);
    }
  return strings;
}
 
ubyte
opcodeNameTo_ubyte (string str, size_t i, size_t j)
{
  size_t k = 0;
  while (k < opcodeOrder.length &&
         !substrEqual (str, i, j, opcodeOrder[k]))
    k += 1;
  if (k == opcodeOrder.length)
    throw new BadVMException ("unrecognized opcode name");
  return to!ubyte(k);
}
 
ubyte
vmintByte0 (vmint i)
{
  return (i & 0xFF);
}
 
ubyte
vmintByte1 (vmint i)
{
  return ((i >> 8) & 0xFF);
}
 
ubyte
vmintByte2 (vmint i)
{
  return ((i >> 16) & 0xFF);
}
 
ubyte
vmintByte3 (vmint i)
{
  return (i >> 24);
}
 
ubyte[]
parseInstruction (string line)
{
  const bad_instruction = "bad VM instruction";
 
  const n = line.length;
  auto i = skipWhitespace (line, n, 0);
 
  // Skip the address field.
  i = skipNonwhitespace (line, n, i);
 
  i = skipWhitespace (line, n, i);
  auto j = skipNonwhitespace (line, n, i);
  auto opcode = opcodeNameTo_ubyte (line, i, j);
 
  auto startOfArgument = j;
 
  ubyte[] finishPush ()
  {
    const i1 = skipWhitespace (line, n, startOfArgument);
    const j1 = skipNonwhitespace (line, n, i1);
    const arg = parseInteger (line, i1, j1);
    // Little-endian storage.
    return [opcode, vmintByte0 (arg), vmintByte1 (arg),
            vmintByte2 (arg), vmintByte3 (arg)];
  }
 
  ubyte[] finishFetchOrStore ()
  {
    const i1 = skipWhitespace (line, n, startOfArgument);
    const j1 = skipNonwhitespace (line, n, i1);
    if (j1 - i1 < 3 || line[i1] != '[' || line[j1 - 1] != ']')
      throw new BadVMException (bad_instruction);
    const arg = parseInteger (line, i1 + 1, j1 - 1);
    // Little-endian storage.
    return [opcode, vmintByte0 (arg), vmintByte1 (arg),
            vmintByte2 (arg), vmintByte3 (arg)];
  }
 
  ubyte[] finishJmpOrJz ()
  {
    const i1 = skipWhitespace (line, n, startOfArgument);
    const j1 = skipNonwhitespace (line, n, i1);
    if (j1 - i1 < 3 || line[i1] != '(' || line[j1 - 1] != ')')
      throw new BadVMException (bad_instruction);
    const arg = parseInteger (line, i1 + 1, j1 - 1);
    // Little-endian storage.
    return [opcode, vmintByte0 (arg), vmintByte1 (arg),
            vmintByte2 (arg), vmintByte3 (arg)];
  }
 
  ubyte[] retval;
  switch (opcode)
    {
    case Op.PUSH:
      retval = finishPush ();
      break;
    case Op.FETCH:
    case Op.STORE:
      retval = finishFetchOrStore ();
      break;
    case Op.JMP:
    case Op.JZ:
      retval = finishJmpOrJz ();
      break;
    default:
      retval = [opcode];
      break;
    }
 
  return retval;
}
 
ubyte[]
readCode (File f)
{
  // Read the instructions from the input, producing an array of
  // array of instruction bytes.
  ubyte[] code = [];
  auto line = f.readln();
  while (line !is null)
    {
      code ~= parseInstruction (line);
      line = f.readln();
    }
  return code;
}
 
void
parseHeaderLine (string line, ref size_t dataSize,
                 ref size_t stringsSize)
{
  const bad_vm_header_line = "bad VM header line";
 
  const n = line.length;
  auto i = skipWhitespace (line, n, 0);
  auto j = skipNonwhitespace (line, n, i);
  if (!substrEqual (line, i, j, "Datasize:"))
    throw new BadVMException (bad_vm_header_line);
  i = skipWhitespace (line, n, j);
  j = skipNonwhitespace (line, n, i);
  dataSize = parseInteger (line, i, j);
  i = skipWhitespace (line, n, j);
  j = skipNonwhitespace (line, n, i);
  if (!substrEqual (line, i, j, "Strings:"))
    throw new BadVMException (bad_vm_header_line);
  i = skipWhitespace (line, n, j);
  j = skipNonwhitespace (line, n, i);
  stringsSize = parseInteger (line, i, j);
}
 
VM
readVM (File f)
{
  const line = f.readln();
 
  size_t dataSize;
  size_t stringsSize;
  parseHeaderLine (line, dataSize, stringsSize);
 
  VM vm = new VM();
  vm.strings = readStrings (f, stringsSize);
  vm.code = readCode (f);
  vm.data.length = dataSize;
  vm.stack.length = 65536; // A VERY big stack, MUCH bigger than is
                           // "reasonable" for this VM. The same size
                           // as in the ATS, however.
  vm.registers[Register.PC] = vmint(0);
  vm.registers[Register.SP] = vmint(0);
 
  return vm;
}
 
vmint
pop (VM vm)
{
  pragma(inline);
  const spBefore = vm.registers[Register.SP];
  if (spBefore == 0)
    throw new VMRuntimeException ("stack underflow");
  const spAfter = spBefore - vmint(1);
  vm.registers[Register.SP] = spAfter;
  return vm.stack[spAfter];
}
 
void
push (VM vm, vmint x)
{
  pragma(inline);
  const spBefore = vm.registers[Register.SP];
  if (vm.stack.length <= spBefore)
    throw new VMRuntimeException ("stack overflow");
  vm.stack[spBefore] = x;
  const spAfter = spBefore + vmint(1);
  vm.registers[Register.SP] = spAfter;
}
 
vmint
fetchData (VM vm, vmint index)
{
  pragma(inline);
  if (vm.data.length <= index)
    throw new VMRuntimeException
      ("fetch from outside the data section");
  return vm.data[index];
}
 
void
storeData (VM vm, vmint index, vmint x)
{
  pragma(inline);
  if (vm.data.length <= index)
    throw new VMRuntimeException
      ("store to outside the data section");
  vm.data[index] = x;
}
 
vmint
getArgument (VM vm)
{
  pragma(inline);
  auto pc = vm.registers[Register.PC];
  if (vm.code.length <= pc + vmint(4))
    throw new VMRuntimeException
      ("the program counter is out of bounds");
  // The data is stored little-endian.
  const byte0 = vmint (vm.code[pc]);
  const byte1 = vmint (vm.code[pc + vmint(1)]);
  const byte2 = vmint (vm.code[pc + vmint(2)]);
  const byte3 = vmint (vm.code[pc + vmint(3)]);
  return (byte0) | (byte1 << 8) | (byte2 << 16) | (byte3 << 24);
}
 
void
skipArgument (VM vm)
{
  pragma(inline);
  vm.registers[Register.PC] += vmint(4);
}
 
//
// The string mixins below are going to do for us *some* of what the
// ATS template system did for us. The two methods hardly resemble
// each other, but both can be used to construct function definitions
// at compile time.
//
 
template
UnaryOperation (alias name, alias func)
{
  const char[] UnaryOperation =
    "void " ~
    name ~ " (VM vm)
    {
      pragma(inline);
      const sp = vm.registers[Register.SP];
      if (sp == vmint(0))
        throw new VMRuntimeException (\"stack underflow\");
      const x = vm.stack[sp - vmint(1)];
      const z = " ~ func ~ " (x);
      vm.stack[sp - vmint(1)] = z;
    }";
}
 
template
BinaryOperation (alias name, alias func)
{
  const char[] BinaryOperation =
    "void " ~
    name ~ " (VM vm)
    {
      pragma(inline);
      const spBefore = vm.registers[Register.SP];
      if (spBefore <= vmint(1))
        throw new VMRuntimeException (\"stack underflow\");
      const spAfter = spBefore - vmint(1);
      vm.registers[Register.SP] = spAfter;
      const x = vm.stack[spAfter - vmint(1)];
      const y = vm.stack[spAfter];
      const z = " ~ func ~ "(x, y);
      vm.stack[spAfter - vmint(1)] = z;
    }";
}
 
mixin (UnaryOperation!("uopNeg", "twosComplement"));
mixin (UnaryOperation!("uopNot", "logicalNot"));
 
mixin (BinaryOperation!("binopAdd", "add"));
mixin (BinaryOperation!("binopSub", "sub"));
mixin (BinaryOperation!("binopMul", "signedMul"));
mixin (BinaryOperation!("binopDiv", "signedDiv"));
mixin (BinaryOperation!("binopMod", "signedMod"));
mixin (BinaryOperation!("binopEq", "equality"));
mixin (BinaryOperation!("binopNe", "inequality"));
mixin (BinaryOperation!("binopLt", "signedLt"));
mixin (BinaryOperation!("binopGt", "signedGt"));
mixin (BinaryOperation!("binopLe", "signedLte"));
mixin (BinaryOperation!("binopGe", "signedGte"));
mixin (BinaryOperation!("binopAnd", "logicalAnd"));
mixin (BinaryOperation!("binopOr", "logicalOr"));
 
void
doPush (VM vm)
{
  pragma(inline);
  const arg = getArgument (vm);
  push (vm, arg);
  skipArgument (vm);
}
 
void
doFetch (VM vm)
{
  pragma(inline);
  const i = getArgument (vm);
  const x = fetchData (vm, i);
  push (vm, x);
  skipArgument (vm);
}
 
void
doStore (VM vm)
{
  pragma(inline);
  const i = getArgument (vm);
  const x = pop (vm);
  storeData (vm, i, x);
  skipArgument (vm);
}
 
void
doJmp (VM vm)
{
  pragma(inline);
  const arg = getArgument (vm);
  vm.registers[Register.PC] += arg;
}
 
void
doJz (VM vm)
{
  pragma(inline);
  const x = pop (vm);
  if (x == vmint(0))
    doJmp (vm);
  else
    skipArgument (vm);
}
 
void
doPrtc (File fOut, VM vm)
{
  const x = pop (vm);
  fOut.write (to!char(x));
}
 
void
doPrti (File fOut, VM vm)
{
  const x = pop (vm);
  fOut.write (int32_t(x));
}
 
void
doPrts (File fOut, VM vm)
{
  const i = pop (vm);
  if (vm.strings.length <= i)
    throw new VMRuntimeException ("string index out of bounds");
  fOut.write (vm.strings[i]);
}
 
void
vmStep (File fOut, VM vm, ref bool machineHalt, ref bool badOpcode)
{
  const pc = vm.registers[Register.PC];
  if (vm.code.length <= pc)
    throw new VMRuntimeException
      ("the program counter is out of bounds");
  vm.registers[Register.PC] = pc + vmint(1);
  const opcode = vm.code[pc];
  const uOpcode = uint(opcode);
 
  // Dispatch by bifurcation on the bit pattern of the opcode. This
  // method is logarithmic in the number of opcode values.
 
  machineHalt = false;
  badOpcode = false;
  if ((uOpcode & (~0x1FU)) == 0U)
    {
      if ((uOpcode & 0x10U) == 0U)
        {
          if ((uOpcode & 0x08U) == 0U)
            {
              if ((uOpcode & 0x04U) == 0U)
                {
                  if ((uOpcode & 0x02U) == 0U)
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        machineHalt = true;
                      else
                        binopAdd (vm);
                    }
                  else
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        binopSub (vm);
                      else
                        binopMul (vm);
                    }
                }
              else
                {
                  if ((uOpcode & 0x02U) == 0U)
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        binopDiv (vm);
                      else
                        binopMod (vm);
                    }
                  else
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        binopLt (vm);
                      else
                        binopGt (vm);
                    }
                }
            }
          else
            {
              if ((uOpcode & 0x04U) == 0U)
                {
                  if ((uOpcode & 0x02U) == 0U)
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        binopLe (vm);
                      else
                        binopGe (vm);
                    }
                  else
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        binopEq (vm);
                      else
                        binopNe (vm);
                    }
                }
              else
                {
                  if ((uOpcode & 0x02U) == 0U)
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        binopAnd (vm);
                      else
                        binopOr (vm);
                    }
                  else
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        uopNeg (vm);
                      else
                        uopNot (vm);
                    }
                }
            }
        }
      else
        {
          if ((uOpcode & 0x08U) == 0U)
            {
              if ((uOpcode & 0x04U) == 0U)
                {
                  if ((uOpcode & 0x02U) == 0U)
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        doPrtc (fOut, vm);
                      else
                        doPrti (fOut, vm);
                    }
                  else
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        doPrts (fOut, vm);
                      else
                        doFetch (vm);
                    }
                }
              else
                {
                  if ((uOpcode & 0x02U) == 0U)
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        doStore (vm);
                      else
                        doPush (vm);
                    }
                  else
                    {
                      if ((uOpcode & 0x01U) == 0U)
                        doJmp (vm);
                      else
                        doJz (vm);
                    }
                }
            }
          else
            badOpcode = true;
        }
    }
  else
    badOpcode = true;
}
 
void
vmContinue (File fOut, VM vm)
{
  auto machineHalt = false;
  auto badOpcode = false;
  while (!machineHalt && !badOpcode)
    vmStep (fOut, vm, machineHalt, badOpcode);
  if (badOpcode)
    throw new VMRuntimeException ("unrecognized opcode at runtime");
}
 
void
vmInitialize (VM vm)
{
  foreach (ref x; vm.data)
    x = vmint(0);
  vm.registers[Register.PC] = vmint(0);
  vm.registers[Register.SP] = vmint(0);
}
 
void
vmRun (File fOut, VM vm)
{
  vmInitialize (vm);
  vmContinue (fOut, vm);
}
 
void
ensure_that_vmint_is_suitable ()
{
  // Try to guarantee that vmint is exactly 32 bits, and that it
  // allows overflow in either direction.
  assert (vmint(0xFFFFFFFFU) + vmint(1U) == vmint(0U));
  assert (vmint(0U) - vmint(1U) == vmint(0xFFFFFFFFU));
  assert (vmint(-1234) == twosComplement (vmint(1234)));
}
 
int
main (char[][] args)
{
  auto inpFilename = "-";
  auto outFilename = "-";
  if (2 <= args.length)
    inpFilename = to!string (args[1]);
  if (3 <= args.length)
    outFilename = to!string (args[2]);
 
  auto inpF = stdin;
  if (inpFilename != "-")
    inpF = File (inpFilename, "r");
  auto vm = readVM (inpF);
  if (inpFilename != "-")
    inpF.close();
 
  auto outF = stdout;
  if (outFilename != "-")
    outF = File (outFilename, "w");
  ensure_that_vmint_is_suitable ();
  vmRun (outF, vm);
  if (outFilename != "-")
    outF.close();
 
  return 0;
}