function RegisterError(message) {
this.name = 'Register Error';
this.message = message;
}
function JumpError(message) {
this.name = 'Jump Error';
this.message = message;
}
function MipsError(message) {
this.name = 'Mips Error';
this.message = message;
}
/**
* Mips emulator constructor
* @param {Object} mipsArgs Arguments to construct the mips emulater.
* @param mipsArgs.startingCode Set the default code for this emulator to run.
* @param mipsArgs.debug If debug is set to true, the console will print debug statements
* @return {mipsEmulator}
* @member mipsEmulator
*/
function MipsEmulator(mipsArgs){
mipsArgs = mipsArgs || {};
var ME = this;
mipsArgs = _.defaults(mipsArgs, {
startingCode: null,
debug: false,
onError: function(message, lineNumber){
alert(message);
return false;
},
onWarning: function(message, lineNumber){
alert(message);
},
onRegisterChange: function(regName, newValue) {
},
onFinish: function(){
if (debug) console.log("Finished running emulation, resetting $sp to line 1");
},
onStackChange: function(){
},
onStackAdd: function() {
},
onHeapChange: function(){
},
onHeapAdjustSize: function(){
},
onOutput: function(message) {
console.log(message)
},
onInput: function(message) {
assert(false, "Expecting input, but there is no handler.");
},
onConfirm: function(message) {
assert(false, "Expecting confirmation, but there is no handler.");
},
onAlert: function(message) {
assert(false, "Expecting alert to be displayed, but there is no handler.");
},
baseStackAddress: undefined,
baseHeapAddress: undefined
});
var debug = mipsArgs.debug;
//////////////////////////////////
// Private Variables / Setup
//////////////////////////////////
var stack = new Stack({
onChange: mipsArgs.onStackChange,
baseAddress: mipsArgs.baseStackAddress
});
var heap = new Heap({
onChange: mipsArgs.onHeapChange,
onAdjustSize: mipsArgs.onHeapAdjustSize,
baseAddress: mipsArgs.baseHeapAddress
});
var memory = new BigEndianAccess(
new CombinedMemory([heap, stack])
);
/**
* Hash table of registers
* @property registers
* @private
* @member mipsEmulator
* @type {Object}
*/
var registers = {};
/**
* Array of all registers
* @property allRegs
* @private
* @member mipsEmulator
* @type {Array}
*/
var allRegs = [
'$zero', '$at', '$v0', '$v1', '$a0', '$a1', '$a2', '$a3',
'$t0', '$t1', '$t2', '$t3', '$t4', '$t5', '$t6', '$t7',
'$s0', '$s1', '$s2', '$s3', '$s4', '$s5', '$s6', '$s7',
'$t8', '$t9', '$k0', '$k1', '$gp', '$sp', '$fp', '$ra',
'hi', 'lo'
];
var preservedRegs = [ // these are the registers that are preserved across function calls
'$zero',
'$s0', '$s1', '$s2', '$s3', '$s4', '$s5', '$s6', '$s7',
'$gp', '$sp', '$fp', '$ra',
// TODO: these shouldn't be here but are required right now because of the check of writable (e.g. when setting garbage data)
'$at', '$k0', '$k1'
];
/**
* Array of read only registers
* @property readonlyRegs
* @private
* @member mipsEmulator
* @type {Array}
*/
var readonlyRegs = [
'$zero', '$at', '$k0', '$k1', '$gp'
];
/**
* The next line to be fetched
* @property nextLineToFetch
* @private
* @member mipsEmulator
* @type {Number}
*/
var nextLineToFetch = 1;
/**
* The next line to execute
* @property nextLineToExecute
* @private
* @member mipsEmulator
* @type {Number}
*/
var nextLineToExecute = undefined;
/** The current line being executed
* This is used for error messages
* @property currentLine
* @private
* @member mipsEmulator
* @type {Number}
*/
var currentLine;
/** Flag for enabling pseudo instructions
* @property pseudoInstructionsEnabled
* @private
* @member mipsEmulator
* @type {boolean}
*/
var pseudoInstructionsEnabled = true;
/** Flag for enabling pipeline emulation
*
* When this is activated, we emulate a pipeline consisting of a
* fetch and execute stage. The result is that branches are delayed
* by one instruction.
*
* @property pipelineEmulationEnabled
* @private
* @member mipsEmulator
* @type {boolean}
*/
var pipelineEmulationEnabled = false;
// populate registers with all the read and write registers and set their inital values to random
for (var i = 0; i < allRegs.length; i++) {
registers[allRegs[i]] = createRegister(allRegs[i], i);
};
registers.$zero.val = 0;
registers.$sp.val = stack.pointerToBottomOfStack();
registers.$fp.val = stack.pointerToBottomOfStack();
// Object that will contain analyzed code information
/**
* @class mipsCode
* @private
* @member mipsEmulator
* Object that keeps the code to be executed
* @type {Object}
*/
var mipsCode = {
/**
* Array of lines that can be exectued
* @property code
* @member mipsCode
* @type {Array}
*/
code:[null], // Initialize with null in the 0 place, to make line numbers line up.
/**
* Hashtable of labels pointing to lines of code
* @property labels
* @member mipsCode
* @type {Object}
*/
labels: {},
/**
* Hashtable of symbols with constant values
* @property symbols
* @member mipsCode
* @type {number}
*/
symbols: {}
};
// Public methods
/**
* @class mipsEmulator
* Mips Emulation engine.
*/
this.FINISHED_EMULATION = 'FINISHED_EMULATION',
this.BYTES_PER_REGISTER = 4,
this.BITS_PER_REGISTER = 32,
this.running = false,
this.stack = stack,
this.heap = heap,
this.memory = memory;
/**
* Returns a specified registers value
* @member mipsEmulator
* @param {String} reg
* @return {Number}
*/
this.getRegisterVal = function(reg) {
var regval = MIPS.unsignedNumberToSignedNumber(this.getRegister(reg).val, this.BITS_PER_REGISTER);
if(debug) console.log("Getting signed register value: " + regval );
return regval;
},
/**
* Returns a specified registers unsigned value
* @member mipsEmulator
* @param {String} reg
* @return {Number}
*/
this.getRegisterUnsignedVal = function(reg) {
var regval = MIPS.signedNumberToUnsignedNumber(this.getRegister(reg).val, this.BITS_PER_REGISTER);
if(debug) console.log("Getting unsigned register value: " + regval);
return regval;
},
/**
* Returns a specified register
* @member mipsEmulator
* @param {String} reg
* @return {Object} register object.
*/
this.getRegister = function(reg) {
if(!reg)throw new Error("Register must be non empty");
if (!(reg in registers)) {
throw new RegisterError('Non existant register: {0}'.format(reg));
}
return registers[reg];
},
/**
* Checks if a register exists and can be directly addressed.
*
* Directly addressable registers are those registers that start with '$'.
*
* @member mipsEmulator
* @param {String} reg Name of a register ex: '$ra'
* @return {Boolean}
*/
this.isValidRegister = function(reg) {
return (reg.charAt(0)=='$' && typeof registers[reg] !== "undefined");
},
/**
* checks if a register is writable
* @member mipsEmulator
* @param {String} reg Register name
* @return {Boolean}
*/
this.isValidWritableRegister = function(reg) {
return this.isValidRegister(reg) && this.getRegister(reg).writable === true;
},
/**
* Set a register value, and call onChange function for that register
* @member mipsEmulator
* @param {String} reg
* @param {Number} value
*/
this.setRegisterVal = function(reg, value, enableCallback) {
if(debug) console.log("Setting register " + reg + " to " + value);
var minRegisterValue = MIPS.minSignedValue(this.BITS_PER_REGISTER);
var maxRegisterValue = MIPS.maxUnsignedValue(this.BITS_PER_REGISTER);
if (value < minRegisterValue || maxRegisterValue < value) {
throw new RegisterError('Value out of range: {0}. Must be between {1} and {2}.'.format(value, minRegisterValue, maxRegisterValue));
}
enableCallback = enableCallback || true;
var register = registers[reg];
if(!register) return error("Line " + currentLine + " register: '" + reg + "' does not exist", currentLine);
if (!register.writable) {
throw new RegisterError('Register "{0}" is readonly.'.format(reg));
}
if(register.onChange && enableCallback) {
register.onChange();
}
register.val = value;
if(mipsArgs.onRegisterChange && enableCallback) {
mipsArgs.onRegisterChange(reg, value);
}
if(debug) console.log("----> New value: "+ ME.getRegister(reg));
},
/**
* Set an Onchange function for a register
* @member mipsEmulator
* @param {String} reg
* @param {Function} func
* @return {null}
*/
this.onChange = function(reg, func){
registers[reg].onChange = func;
},
/**
* Set which line to fetch next.
*
* This resets the pipeline.
*
* @member mipsEmulator
* @param {Number} lineNo
* @return {Number} Returns the number the line was set too.
*/
this.setNextLineToFetch = function(lineNo){
var line = mipsCode.code[lineNo];
if(debug) console.log("setting line: "+ lineNo + " - " + JSON.stringify(line));
if(!line) return false;
nextLineToFetch = getFirstActiveLine(lineNo);
nextLineToExecute = undefined;
return nextLineToFetch;
},
/**
* Checks if a string is a valid mips line
* @member mipsEmulator
* @param {String} line
* @return {Boolean}
*/
this.isValidLine = function(line){
return !(new mipsLine(line).error);
},
/**
* Resets mips labes, code, and stack
* @member mipsEmulator
* @return {null}
*/
this.reset = function() {
mipsCode.labels = {};
mipsCode.code = [null];
mipsCode.symbols = {};
memory.reset();
registers.$sp.val = stack.pointerToBottomOfStack();
},
/**
* Set the debug option for the mips_emulator
* @member mipsEmulator
* @param {Boolean} dbg
*/
this.setDebug = function(dbg){
debug = dbg;
},
/**
* Set code to be emulated
* @member mipsEmulator
* @param {String} mc
*/
this.setCode = function(mc){
ME.reset();
if(debug) console.log("Analyzing...");
$.each(mc.split('\n'), function(index, val){
var line = new mipsLine(val, mipsCode.code.length);
line.lineNo = mipsCode.code.length; // save the line number
// if(debug) console.log(JSON.stringify(line));
mipsCode.code.push(line);
});
// Reset to first active line, as the code changed
this.setNextLineToFetch(1);
},
/**
* Run an individual line
* @member mipsEmulator
* @param {String} inputLine
* @return {null}
*/
this.runLine = function(inputLine) {
var line = new mipsLine(inputLine);
// This refers to the private method, private method should probably be renamed.
runLine(line);
},
/**
* Run an array of lines
* @member mipsEmulator
* @param {Array} lines Array of mips code, one line per index
* @return {null}
*/
this.runLines = function(lines) {
// lines is an array of strings
lines = lines.join('\n');
this.setCode(lines);
this.run();
},
/**
* runs the current set of code in the mips emulator non-stop;
* @member mipsEmulator
* @return {null}
*/
this.run = function() {
// run the current set of instructions which were set via setCode
assert(mipsCode.code !== null, 'Must have already set the code to run.');
this.running = true;
while (this.running) {
this.step();
}
},
/**
* execute the line PC is pointing to.
* @member mipsEmulator
* @return {Object}
* returns object.lineRan which is the line that was just run
* and object.nextLine which is the line that is about to be run.
*/
this.step = function(){
/* Advance the pipeline */
currentLine = this.getNextLineToExecute();
nextLineToExecute = nextLineToFetch;
nextLineToFetch = getFirstActiveLine(nextLineToExecute + 1);
if(debug) console.log("Running line: " + currentLine + " - " + JSON.stringify(mipsCode.code[currentLine]));
// check if we are finished with the emulation
if(currentLine > mipsCode.code.length - 1) return finishEmulation();
if(!mipsCode.code[currentLine]) throw new MipsError("Line " + currentLine + " does not exist");
if(mipsCode.code[currentLine].error) throw new MipsError(mipsCode.code[currentLine].error.toString());
assert(!mipsCode.code[currentLine].ignore);
// we need to check again, because the remainder of the lines could have been comments or blank.
var ret = {
lineRan: Number(currentLine)
};
/* We pre-set the next line here, so that we do not need to call
* incrementPC all the time and get more centralized control over
* execution progress.
*/
var lineToExecute = mipsCode.code[currentLine];
runLine(lineToExecute);
if (!pipelineEmulationEnabled) {
/* We skip the fetch step if the pipeline is disabled */
nextLineToExecute = nextLineToFetch;
}
ret.nextLine = nextLineToExecute;
if(nextLineToExecute > mipsCode.code.length - 1) finishEmulation();
return ret;
},
/**
* Returns the current line number (the next to be run)
* @member mipsEmulator
* @return {Number}
*/
this.getNextLineToExecute = function(){
return nextLineToExecute || nextLineToFetch;
},
/**
* Returns the next line number to be fetched
* @member mipsEmulator
* @return {Number}
*/
this.getCurrentLine = function(){
return currentLine;
},
/**
* Jump to a specified label
* @member mipsEmulator
* @param {String} label The label to jump to
* @return {Number} The line number you jumped to
*/
this.goToLabel = function(label){
var line = mipsCode.labels[label];
if(debug) console.log("Getting label: "+ label + " - " +JSON.stringify(line) );
if(line){
return this.goToLine(line.lineNo);
} else {
throw new JumpError('Unknown label: {0}'.format(label));
}
}
/**
* Jump to a specified line number
* @member mipsEmulator
* @param {String} lineNo The line number to jump to
* @return {Number} The line number you jumped to
*/
this.goToLine = function(lineNo) {
if(debug) console.log("Going to line: "+ line);
nextLineToFetch = getFirstActiveLine(lineNo);
return nextLineToFetch;
}
this.linkReturnAddress = function(reg) {
var returnInstruction = (pipelineEmulationEnabled?nextLineToFetch:this.getNextLineToExecute()+1);
this.setRegisterVal(reg, returnInstruction);
}
this.onSetOverflowFlag = function() {}, // e.g. for 8 bit registers signed, 127 + 1 causes an overflow, since we can't store 128, so it wraps around to -128.
this.onSetCarryFlag = function() {}, // e.g. for 8 bit registers unsigned, 255 + 1 causes a carry flag, since we can't store 256, so it wraps around to 0.
this.setUnpreservedRegsToGarbage = function() {
for (var i = 0; i < allRegs.length; i++) {
var register = this.getRegister(allRegs[i]);
if (register.preserved) {
continue;
}
this.setRegisterVal(register.regName, getGarbageRegisterData());
};
},
this.output = function(string) {
mipsArgs.onOutput(string);
},
this.getInput = function(message) {
return mipsArgs.onInput(message);
},
this.mipsConfirm = function(message) {
return mipsArgs.onConfirm(message);
},
this.mipsAlert = function(message) {
mipsArgs.onAlert(message);
}
this.setPseudoInstructionsEnabled = function(value) {
pseudoInstructionsEnabled = value;
}
this.setPipelineEmulationEnabled = function(value) {
pipelineEmulationEnabled = value;
}
////////////////////////////////////////////////
// Private Methods
////////////////////////////////////////////////
function finishEmulation(){
ME.running = false;
mipsArgs.onFinish();
if(debug) console.log("Emulation finished. Returning to line: " + ME.setNextLineToFetch(1));
ME.setNextLineToFetch(1);
return ME.FINISHED_EMULATION;
};
/** Determine the first line that is not marked to be ignored
* @param {Number} lineNo The starting line number
* @return {Number} The first line not marked to be ignored
* at or after the given starting line number
*/
function getFirstActiveLine(lineNo) {
while(lineNo <= mipsCode.code.length
&& mipsCode.code[lineNo]
&& mipsCode.code[lineNo].ignore != false)
{
if(debug) console.log("ignoring line: " + lineNo);
lineNo++;
}
return lineNo;
}
/**
* Run an individual line
* @member mipsEmulator
* @private
* @return {null}
*/
function runLine(line) {
if(debug) console.log("running line: " + line.lineNo);
if (line.error) {
throw new MipsError('Error on line: {0}'.format(line.error));
// TODO: get rid of the other error handler
}
if (!line || line.ignore || line.error) {
if(!line) error("Line is null");
else error(line.error); // returns error if there is one or null if not.
return false;
}
instruction = instructions[line.instruction];
assert(instruction);
if (!pseudoInstructionsEnabled && instruction.pseudoInstruction) {
throw new MipsError('Pseudo instruction {0} is disabled'.format(line.instruction));
}
var runMethod = instruction.runMethod;
assert(runMethod);
runMethod(line.args);
};
function getGarbageRegisterData() {
return Math.floor((Math.random()*1000));
}
/**
* Create a default register
* @member mipsEmulator
* @private
* @param {Object} reg
* @return {register}
*/
function createRegister(regName, regNumber){
/**
* @class register
* contains register information.
*/
return {
/**
* registers value
* @property {number}
*/
val: getGarbageRegisterData(),
/**
* Function that is called when this register is changed.
* @type {Function}
*/
onChange: null,
/**
* Wether or not this register is writable (false if this register is read only)
* @type {Boolean}
*/
writable: readonlyRegs.indexOf(regName) === -1,
/**
* This registers name
* @type {String}
*/
regName: regName,
regNumber: regNumber,
preserved: preservedRegs.indexOf(regName) > -1
};
};
// these will be called after the parse method has been called
// the goal is to make these methods look as close to the MIPS cheat sheet as possible.
var instructions = mipsInstructionExecutor(ME);
/**
* If the user defined an anError message, use that, if not, alert the message
* @param {String} message
* @param {Number} lineNo
* @return {null}
*/
function error(message, lineNo){
if(debug) console.error("Error being sent");
if(debug) console.error("--->" + message);
lineNo = lineNo || currentLine;
mipsArgs.onError(message, lineNo);
}
/**
* Turns a string into a mips line object which contains a mips line of code and metadata needed to run it
* @member mipsEmulator
* @private
* @param {String} line
* @return {Object}
*/
function mipsLine(line, lineNo){
lineNo = lineNo || null
// Object that will save information about a line of code.
/**
* @class line
* Contains information about a single line of mips code
* @member mipsEmulator
* @private
*/
var LINE = {
/**
* Arguments for this line of code ex: [$t0, $s0, $zero]
* @property {array}
*/
args: [],
/**
* The lines instruction ex: ADD
* @type {String}
*/
instruction: null,
/**
* flag to indicate weather this line should be ignored (not run).
* @type {Boolean}
*/
ignore: true,
/**
* Error when running this line of code (if any)
* @type {String}
*/
error: null,
lineNo: lineNo,
text: line
};
let instructionParser = Parser.instructionParserFromString(line, mipsCode.symbols);
try {
let instruction = instructionParser.parseLine();
if (instruction.symbols) {
for (symbol of instruction.symbols) {
mipsCode.symbols[symbol.name] = symbol.value;
}
}
if (instruction.labels) {
for (label of instruction.labels) {
mipsCode.labels[label] = LINE;
}
}
if (instruction.instr) {
LINE.ignore = false;
LINE.instruction = instruction.instr.mnemonic;
LINE.args = instruction.instr.args;
}
} catch (e) {
if (e instanceof Parser.Error) {
/* Do not ignore erroneous lines! */
LINE.ignore = false;
LINE.error = e;
}
}
if(debug) console.log("Finished parsing line: " + JSON.stringify(LINE));
return LINE;
}
// Set the starting code if there was any.
if(mipsArgs.startingCode) ME.setCode(mipsArgs.startingCode);
return this;
}