908 lines
20 KiB
D
908 lines
20 KiB
D
//
|
|
// 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;
|
|
}
|