Merge branch 'mplamann' into run_vm
Conflicts: public/vm/index.js
This commit is contained in:
@@ -1,234 +1,464 @@
|
||||
op = require("./opcodes");
|
||||
|
||||
var ProgramState = function(parsed_file) {
|
||||
var main_function = parsed_file.function_pool[0];
|
||||
|
||||
this.stack = []
|
||||
this.pc = 0;
|
||||
this.program = main_function.code;
|
||||
this.variables = [];
|
||||
for (var i = 0; i < main_function.num_vars; i++)
|
||||
variables.push(0);
|
||||
this.file = parsed_file;
|
||||
var INT_MIN = 0x80000000;
|
||||
var INT_MAX = 0x7FFFFFFF;
|
||||
|
||||
var verbose = false;
|
||||
function log(message) {
|
||||
if (verbose) console.log(message);
|
||||
}
|
||||
|
||||
function c0_assertion_failure(val) {
|
||||
throw ("c0 assertion failure: " + val);
|
||||
}
|
||||
|
||||
ProgramState.prototype.doIf = function(shouldJump) {
|
||||
if (shouldJump) {
|
||||
var address_offset = (this.program[this.pc+1] * 0x1000) +
|
||||
this.program[this.pc+2];
|
||||
this.pc += address_offset;
|
||||
function c0_memory_error(val) {
|
||||
throw ("c0 memory error: " + val);
|
||||
}
|
||||
|
||||
function i32_to_array(i32) {
|
||||
return [(i32 & 0xFF),
|
||||
((i32 >> 8) & 0xFF),
|
||||
((i32 >> 16) & 0xFF),
|
||||
((i32 >> 24) & 0xFF)];
|
||||
|
||||
}
|
||||
|
||||
function array_to_i32(array) {
|
||||
return array[0] +
|
||||
(array[1] << 8) +
|
||||
(array[2] << 16) +
|
||||
(array[3] << 24);
|
||||
}
|
||||
|
||||
var StackFrame = function(file, f) {
|
||||
log("Creating stack frame");
|
||||
this.stack = [];
|
||||
this.pc = 0;
|
||||
this.program = f.code;
|
||||
this.variables = [];
|
||||
for (var i = 0; i < f.num_vars; i++)
|
||||
this.variables.push(0);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
var ProgramState = function(parsed_file, callback_dict) {
|
||||
log("Creating program state with file " + parsed_file);
|
||||
var main_function = parsed_file.function_pool[0];
|
||||
|
||||
this.frame = new StackFrame(parsed_file, parsed_file.function_pool[0]);
|
||||
this.call_stack = [];
|
||||
this.file = parsed_file;
|
||||
|
||||
this.natives = {};
|
||||
|
||||
for (var i = 0; i < 95; i++) {
|
||||
try {
|
||||
this.natives[i] = callback_dict[i];
|
||||
} catch (key_not_found) {
|
||||
this.natives[i] = function (arg) {
|
||||
console.log("Native function " + name + " called, ran method stub.");
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Memory is just a big array of bytes, right?
|
||||
// "Allocation" is appending onto this array
|
||||
// A pointer to memory is an index into this array.
|
||||
|
||||
// Structs are stored as themselves
|
||||
// Arrays are stored as an entry for the number of elements
|
||||
// and then the array, byte-by-byte
|
||||
this.heap = [];
|
||||
}
|
||||
|
||||
ProgramState.prototype.push = function(val) {
|
||||
this.frame.stack.push(val);
|
||||
}
|
||||
|
||||
ProgramState.prototype.pop = function() {
|
||||
return this.frame.stack.pop();
|
||||
}
|
||||
|
||||
ProgramState.prototype.goto_offset = function() {
|
||||
var c1 = this.frame.program[this.frame.pc+1];
|
||||
var c2 = this.frame.program[this.frame.pc+2]
|
||||
|
||||
var address_offset = (c1 << 8) + c2;
|
||||
// Handle negative values
|
||||
if ((address_offset & 0x8000) != 0)
|
||||
address_offset = -0x8000 + (address_offset & 0x7FFF);
|
||||
|
||||
this.frame.pc += address_offset;
|
||||
}
|
||||
|
||||
ProgramState.prototype.doIf = function(f) {
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
if (f(x,y)) {
|
||||
this.goto_offset();
|
||||
} else {
|
||||
this.pc += 3;
|
||||
this.frame.pc += 3;
|
||||
}
|
||||
}
|
||||
|
||||
ProgramState.prototype.step = function() {
|
||||
console.log("Running opcode " + op.lookup_table[this.program[this.pc]]);
|
||||
switch (this.program[this.pc]) {
|
||||
var opcode = this.frame.program[this.frame.pc]
|
||||
log("0x" + this.frame.pc.toString(16) + " Running opcode " +
|
||||
op.lookup_table[opcode]);
|
||||
switch (opcode) {
|
||||
// Stack manipulation
|
||||
case op.POP:
|
||||
this.pc++;
|
||||
this.stack.pop();
|
||||
this.frame.pc++;
|
||||
this.pop();
|
||||
break;
|
||||
case op.DUP:
|
||||
this.pc++;
|
||||
var v = this.stack.pop();
|
||||
this.stack.push(v);
|
||||
this.stack.push(v);
|
||||
this.frame.pc++;
|
||||
var v = this.pop();
|
||||
this.push(v);
|
||||
this.push(v);
|
||||
break;
|
||||
case op.SWAP:
|
||||
this.pc++;
|
||||
var v1 = this.stack.pop();
|
||||
var v2 = this.stack.pop();
|
||||
this.stack.push(v1);
|
||||
this.stack.push(v2);
|
||||
this.frame.pc++;
|
||||
var v1 = this.pop();
|
||||
var v2 = this.pop();
|
||||
this.push(v1);
|
||||
this.push(v2);
|
||||
break;
|
||||
case op.BIPUSH:
|
||||
this.pc += 2;
|
||||
var val = this.program[this.pc-1];
|
||||
|
||||
this.frame.pc += 2;
|
||||
var val = this.frame.program[this.frame.pc-1];
|
||||
|
||||
// Do sign extension if necessary
|
||||
if (val & 0x80 != 0)
|
||||
if ((val & 0x80) != 0)
|
||||
val = -0x80 + (val & 0x7F);
|
||||
this.stack.push(val);
|
||||
this.push(val);
|
||||
break;
|
||||
|
||||
// Returning from a function
|
||||
case op.RETURN:
|
||||
var retVal = this.stack.pop();
|
||||
throw retVal;
|
||||
var retVal = this.pop();
|
||||
if (this.call_stack.length == 0)
|
||||
return retVal;
|
||||
|
||||
this.frame = this.call_stack.pop();
|
||||
this.push(retVal);
|
||||
|
||||
break;
|
||||
// Arithmetic
|
||||
case op.IADD:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
console.log("Adding " + x + " and " + y);
|
||||
this.stack.push((x+y) % 0x100000000);
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
log("Adding " + x + " and " + y);
|
||||
this.push((x+y) % 0x100000000);
|
||||
break;
|
||||
case op.ISUB:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.stack.push((x-y) % 0x100000000);
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
this.push((x-y) % 0x100000000);
|
||||
break;
|
||||
case op.IMUL:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.stack.push((x*y) % 0x100000000);
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
this.push((x*y) % 0x100000000);
|
||||
break;
|
||||
case op.IDIV:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
if (y == 0) c0_arith_error("Divide by zero");
|
||||
if (x == INT_MIN && y == -1) c0_arith_error("Arithmetic overflow");
|
||||
|
||||
// This does int division.
|
||||
// As I understand it, the ~~ is treated as the identity on integers
|
||||
// which forces the type to int, not float
|
||||
this.stack.push(~~(x/y));
|
||||
this.push(~~(x/y));
|
||||
break;
|
||||
case op.IREM:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
if (y == 0) c0_arith_error("Divide by zero");
|
||||
if (x == INT_MIN && y == -1) c0_arith_error("Arithmetic overflow");
|
||||
this.stack.push(x%y);
|
||||
this.push(x%y);
|
||||
break;
|
||||
case op.IAND:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.stack.push(x&y);
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
this.push(x&y);
|
||||
break;
|
||||
case op.IOR:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.stack.push(x|y);
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
this.push(x|y);
|
||||
break;
|
||||
case op.IXOR:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.stack.push(x^y);
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
this.push(x^y);
|
||||
break;
|
||||
case op.ISHL:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
if (y < 0 || y > 31) c0_arith_error("Shifting by too many bits");
|
||||
this.stack.push(x<<y);
|
||||
this.push(x<<y);
|
||||
break;
|
||||
case op.ISHR:
|
||||
this.pc++;
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.frame.pc++;
|
||||
var y = this.pop();
|
||||
var x = this.pop();
|
||||
if (y < 0 || y > 31) c0_arith_error("Shifting by too many bits");
|
||||
this.stack.push(x>>y);
|
||||
this.push(x>>y);
|
||||
break;
|
||||
|
||||
// Operations on local variables
|
||||
|
||||
case op.VLOAD:
|
||||
this.pc += 2;
|
||||
var index = this.program[this.pc-1];
|
||||
this.stack.push(this.variables[index]);
|
||||
this.frame.pc += 2;
|
||||
var index = this.frame.program[this.frame.pc-1];
|
||||
this.push(this.frame.variables[index]);
|
||||
break;
|
||||
case op.VSTORE:
|
||||
this.pc += 2;
|
||||
var index = this.program[this.pc-1];
|
||||
this.variables[index] = this.stack.pop();
|
||||
this.frame.pc += 2;
|
||||
var index = this.frame.program[this.frame.pc-1];
|
||||
this.frame.variables[index] = this.pop();
|
||||
break;
|
||||
case op.ACONST_NULL:
|
||||
this.pc++;
|
||||
this.stack.push(0);
|
||||
this.frame.pc++;
|
||||
this.push(0);
|
||||
break;
|
||||
case op.ILDC:
|
||||
this.pc += 3;
|
||||
var c1 = this.program[this.pc-2];
|
||||
var c2 = this.program[this.pc-1];
|
||||
this.frame.pc += 3;
|
||||
var c1 = this.frame.program[this.frame.pc-2];
|
||||
var c2 = this.frame.program[this.frame.pc-1];
|
||||
var index = (c1 * 0x1000) + c2;
|
||||
|
||||
this.stack.push(this.file.int_pool[index]);
|
||||
this.push(this.file.int_pool[index]);
|
||||
break;
|
||||
case op.ALDC:
|
||||
console.log("Error: I don't know how to handle ALDC yet");
|
||||
throw "Error - can't handle ALDC";
|
||||
this.frame.pc += 3;
|
||||
var c1 = this.frame.program[this.frame.pc-2];
|
||||
var c2 = this.frame.program[this.frame.pc-1];
|
||||
var index = (c1 * 0x1000) + c2;
|
||||
|
||||
this.push(this.file.string_pool[index]);
|
||||
break;
|
||||
|
||||
// Control flow
|
||||
case op.NOP:
|
||||
this.pc++;
|
||||
this.frame.pc++;
|
||||
break;
|
||||
case op.IF_CMPEQ:
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.doIf(y == x);
|
||||
this.doIf(function (x,y) {return y == x;});
|
||||
break;
|
||||
case op.IF_CMPNE:
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.doIf(y != x);
|
||||
this.doIf(function (x,y) {return y != x;});
|
||||
break;
|
||||
case op.IF_CMPLT:
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.doIf(y > x);
|
||||
case op.IF_ICMPLT:
|
||||
this.doIf(function (x,y) {return y > x;});
|
||||
break;
|
||||
case op.IF_CMPGE:
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.doIf(y <= x);
|
||||
case op.IF_ICMPGE:
|
||||
this.doIf(function (x,y) {return y <= x;});
|
||||
break;
|
||||
case op.IF_CMPGT:
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.doIf(y < x);
|
||||
case op.IF_ICMPGT:
|
||||
this.doIf(function (x,y) {return y < x;});
|
||||
break;
|
||||
case op.IF_CMPLE:
|
||||
var y = this.stack.pop();
|
||||
var x = this.stack.pop();
|
||||
this.doIf(y >= x);
|
||||
case op.IF_ICMPLE:
|
||||
this.doIf(function (x,y) {return y >= x;});
|
||||
break;
|
||||
case op.GOTO:
|
||||
this.doIf(true);
|
||||
this.goto_offset();
|
||||
break;
|
||||
case op.ATHROW:
|
||||
this.pc++;
|
||||
c0_user_error(this.stack.pop());
|
||||
this.frame.pc++;
|
||||
c0_user_error(this.pop());
|
||||
break;
|
||||
case op.ASSERT:
|
||||
this.pc++;
|
||||
var a = this.stack.pop();
|
||||
if (this.stack.pop() == 0)
|
||||
this.frame.pc++;
|
||||
var a = this.pop();
|
||||
if (this.pop() == 0)
|
||||
c0_assertion_failure(a);
|
||||
break;
|
||||
|
||||
// Function call operations
|
||||
|
||||
case op.INVOKESTATIC:
|
||||
var c1 = this.frame.program[this.frame.pc+1];
|
||||
var c2 = this.frame.program[this.frame.pc+2];
|
||||
this.frame.pc += 3;
|
||||
|
||||
var index = (c1 << 8) + c2;
|
||||
|
||||
var f = this.file.function_pool[index];
|
||||
var newFrame = new StackFrame(this.file, f);
|
||||
for (var i = f.num_args - 1; i >= 0; i--) {
|
||||
newFrame.variables[i] = this.pop();
|
||||
}
|
||||
|
||||
this.call_stack.push(this.frame);
|
||||
this.frame = newFrame;
|
||||
break;
|
||||
|
||||
case op.INVOKENATIVE:
|
||||
var c1 = this.frame.program[this.frame.pc+1];
|
||||
var c2 = this.frame.program[this.frame.pc+2];
|
||||
this.frame.pc += 3;
|
||||
|
||||
var index = (c1 << 8) + c2;
|
||||
|
||||
var f = this.file.native_pool[index];
|
||||
var arg_array = [];
|
||||
for (var i = f.num_args - 1; i >= 0; i--)
|
||||
arg_array[i] = this.pop();
|
||||
|
||||
var native_function = this.natives[f.function_table_index];
|
||||
if (native_function === undefined) {
|
||||
native_function = function (ignored) {
|
||||
console.log("Could not find native function with index " +
|
||||
f.function_table_index);
|
||||
return 0;
|
||||
};
|
||||
console.log("Unknown native function index " + f.function_table_index);
|
||||
}
|
||||
this.push(native_function(arg_array));
|
||||
break;
|
||||
|
||||
// Memory allocation operations:
|
||||
|
||||
case op.NEW:
|
||||
var size = this.frame.program[this.frame.pc+1];
|
||||
var address = this.heap.length;
|
||||
|
||||
for (var i = 0; i < size; i++) this.heap.push(0);
|
||||
|
||||
this.push(address);
|
||||
this.frame.pc += 2;
|
||||
break;
|
||||
|
||||
case op.NEWARRAY:
|
||||
var size = this.frame.program[this.frame.pc+1];
|
||||
var address = this.heap.length;
|
||||
var num_elements = this.pop();
|
||||
if (num_elements < 0) c0_memory_error("Array size must be nonnegative");
|
||||
|
||||
this.heap.push(num_elements);
|
||||
this.heap.push(size);
|
||||
|
||||
for (var i = 0; i < num_elements; i++) {
|
||||
for (var j = 0; j < size; j++)
|
||||
this.heap.push(0);
|
||||
}
|
||||
|
||||
this.push(address);
|
||||
this.frame.pc += 2;
|
||||
break;
|
||||
|
||||
case op.ARRAYLENGTH:
|
||||
var pointer = this.pop();
|
||||
this.push(this.heap[pointer]);
|
||||
break;
|
||||
|
||||
// Memory access operations:
|
||||
|
||||
case op.AADDF:
|
||||
// Read offset into a struct
|
||||
var offset = this.frame.program[this.frame.pc + 1];
|
||||
var index = this.pop();
|
||||
this.push(this.heap[index + offset]);
|
||||
this.frame.pc += 2;
|
||||
break;
|
||||
|
||||
case op.AADDS:
|
||||
// Read offset into an array
|
||||
var elt_index = this.pop();
|
||||
var index = this.pop();
|
||||
var array_length = this.heap[index];
|
||||
var elt_size = this.heap[index+1];
|
||||
if (elt_index >= array_length) c0_memory_error("Array index out of bounds.");
|
||||
this.push(this.heap[index + 2 + elt_size*elt_index]);
|
||||
this.frame.pc++;
|
||||
break;
|
||||
|
||||
case op.IMLOAD:
|
||||
var addr = this.pop();
|
||||
// Get int32 from bytes
|
||||
var c1 = this.heap[addr];
|
||||
var c2 = this.heap[addr+1];
|
||||
var c3 = this.heap[addr+2];
|
||||
var c4 = this.heap[addr+3];
|
||||
var combined = array_to_i32([c1, c2, c3, c4]);
|
||||
this.push(combined);
|
||||
this.frame.pc++;
|
||||
break;
|
||||
|
||||
case op.IMSTORE:
|
||||
var value = this.pop();
|
||||
var addr = this.pop();
|
||||
var array = i32_to_array(value);
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
this.heap[addr + i] = array[i];
|
||||
this.frame.pc++;
|
||||
|
||||
case op.AMLOAD:
|
||||
var addr = this.pop();
|
||||
this.push(this.heap[addr]);
|
||||
this.frame.pc++;
|
||||
break;
|
||||
|
||||
case op.AMSTORE:
|
||||
var value = this.pop();
|
||||
var addr = this.pop();
|
||||
this.heap[addr] = value;
|
||||
this.frame.pc++;
|
||||
break;
|
||||
|
||||
case op.CMLOAD:
|
||||
var addr = this.pop();
|
||||
this.push(this.heap[addr]);
|
||||
this.frame.pc++;
|
||||
break;
|
||||
|
||||
case op.CMSTORE:
|
||||
var value = this.pop();
|
||||
var addr = this.pop();
|
||||
this.heap[addr] = value;
|
||||
this.frame.pc++;
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Error: Unknown opcode\n");
|
||||
var opcode_name;
|
||||
try {
|
||||
opcode_name = op.lookup_table[opcode];
|
||||
} catch (ignored) {
|
||||
opcode_name = "UNKNOWN";
|
||||
}
|
||||
console.log("Error: Unknown opcode: 0x" + opcode.toString(16) +
|
||||
" (" + opcode_name + ")\n");
|
||||
throw "Error - unknown opcode";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Takes in a parsed .bc0 file and runs it
|
||||
function execute(f) {
|
||||
console.log("Beginning execution of file " + f);
|
||||
function execute(file, callbacks, v) {
|
||||
verbose = typeof v !== 'undefined' ? v : true;
|
||||
log("Initializing with file " + file);
|
||||
|
||||
var state = new ProgramState(f);
|
||||
var state = new ProgramState(file, callbacks);
|
||||
|
||||
log("Beginning execution");
|
||||
|
||||
while (true) {
|
||||
// I'm not sure how to structure this control flow yet,
|
||||
// so if anyone has a better idea, let me know
|
||||
try {
|
||||
state.step();
|
||||
} catch (val) {
|
||||
return val;
|
||||
}
|
||||
var val = state.step();
|
||||
if (val !== undefined) return val;
|
||||
|
||||
// if (at_breakpoint) {
|
||||
// save state (maybe in a global in this file?)
|
||||
@@ -238,6 +468,3 @@ function execute(f) {
|
||||
}
|
||||
|
||||
exports.execute = execute;
|
||||
|
||||
// opcode definitions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user