/*
* MIPS-Simulator : vm.js [Virtual Machine for Executing Instructions]
* Copyright (C) 2017 Progyan Bhattacharya, Bytes Club
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const register = require("./vm/register"),
load = require("./vm/load"),
move = require("./vm/move"),
store = require("./vm/store"),
add = require("./vm/add"),
subtract = require("./vm/subtract"),
multiply = require("./vm/multiply"),
divide = require("./vm/divide"),
logicalAnd = require("./vm/logical-and"),
logicalOr = require("./vm/logical-or"),
logicalShift = require("./vm/logical-shift"),
branch = require("./vm/branch"),
syscall = require("./vm/syscall");
const regObject = (reg, register, i) => {
let regObj = null;
if (/\$r\d+/.test(reg)) {
// Written in $r[n] format
const index = Number(reg.slice(2));
regObj = register.slice(index).shift();
} else {
regObj = register.find((v) => v.name === reg);
}
if (typeof regObj === "undefined" || regObj === null) {
// Invalid register given
console.error(`Fatal Error: Invalid register ${register} given,` +
` please check at the line number ${i}`);
process.exit(4);
}
return regObj;
};
/** class: VM
* @desc Virtual Machine for Simulator to Run
* @param {FileStream} stdin - Standard Input for Virtual Machine
* @param {FileStream} stdout - Standard Output for Virtual Machine
* @prop {Array} register - Register File for Virtual Machine
* @prop {Array} AddressTable - Address Lookup Table
* @prop {Array} InstructionSet - Parse Tree Generated by Parse
* @prop {Number} fenceSize - Fence Register for Access Limitation
* @prop {Number} ProgramCounter - Next Instruction Index
* @prop {boolean} shouldRun - Execution State (Running | Halt)
*/
class VM extends Object {
constructor({ stdin, stdout }) {
super();
// Member variables
this.stdin = typeof stdin !== "undefined" ?
stdin :
process.stdin;
this.stdout = typeof stdout !== "undefined" ?
stdout :
process.stdout;
this.register = register;
this.AddressTable = null;
this.InstructionSet = null;
this.fenceSize = 0;
this.ProgramCounter = -1;
this.shouldRun = true;
this.run = this.run.bind(this);
this.execute = this.execute.bind(this);
this.load = load.bind(this);
this.move = move.bind(this);
this.loadOnRegister = this.loadOnRegister.bind(this);
this.valueFromRegister = this.valueFromRegister.bind(this);
this.encodeFromRegister = this.encodeFromRegister.bind(this);
this.valueFromLabel = this.valueFromLabel.bind(this);
this.store = store.bind(this);
this.memory = this.memory.bind(this);
this.add = add.bind(this);
this.subtract = subtract.bind(this);
this.multiply = multiply.bind(this);
this.divide = divide.bind(this);
this.logicalAnd = logicalAnd.bind(this);
this.logicalOr = logicalOr.bind(this);
this.logicalShift = logicalShift.bind(this);
this.branch = branch.bind(this);
this.updatePC = this.updatePC.bind(this);
this.syscall = syscall.bind(this);
}
/** method: run
* @desc Invocation Method to ENtry POint of The Program
* @param {Array} SymbolTable - Address Lookup Table
* @param {Array} SyntaxTree - Abstract Syntax Tree
*/
run({ SymbolTable, SyntaxTree }) {
// Load content in memory
this.AddressTable = [].concat(SymbolTable);
this.InstructionSet = [].concat(SyntaxTree);
this.fenceSize = this.InstructionSet.length;
const loader = SyntaxTree.shift();
if (typeof loader !== "undefined" || loader !== null) {
// Get entry point
this.ProgramCounter = this.valueFromLabel("main");
// Check if valid PC, or exit has called
while (this.shouldRun && this.ProgramCounter >= 0 &&
this.ProgramCounter < this.fenceSize) {
this.execute();
}
}
}
/** method: execute
* @desc Execution Module for Virtual Machine
* @param {Array} InstructionSet - Set of Instructions to be Executed
* @param {Number} ProgramCounter - Index of Next Instruction
*/
execute() {
const { InstructionSet, ProgramCounter } = this;
// Extract instruction at PC from set
const key = InstructionSet.slice(ProgramCounter,
ProgramCounter + 1).shift();
// Validity of instruction
if (typeof key === "undefined" || key === null) {
const counter = Number((ProgramCounter * 4) +
0x10000).toString(16).substr(-4);
console.error("Undefined behaviour found at" +
`instruction 0x${counter}`);
process.exit(4);
}
// Increment PC by 1, point to next instrcution
this.ProgramCounter = this.ProgramCounter + 1;
// Execute current instrcution
const { index, item } = key;
switch (item.action) {
case "Load":
this.load(item, index);
break;
case "Move":
this.move(item, index);
break;
case "Store":
this.store(item, index);
break;
case "Add":
this.add(item, index);
break;
case "Subtract":
this.subtract(item, index);
break;
case "Multiplication":
this.multiply(item, index);
break;
case "Division":
this.divide(item, index);
break;
case "And":
this.logicalAnd(item, index);
break;
case "Or":
this.logicalOr(item, index);
break;
case "Shift":
this.logicalShift(item, index);
break;
case "Branch":
this.branch(item, index);
break;
case "OS":
this.syscall(index);
break;
default:
console.error("Fatal Error: Undefined action recieved for" +
`execution at line ${index}`);
process.exit(4);
}
}
/** method: loadOnRegister
* @desc Load Data Into Register File
* @param {string} register - Name of The Register
* @param {any} value - Value to be Put
* @param {Number} i - Index of The Instruction
*/
loadOnRegister(register, value, i) {
const regObj = regObject(register, this.register, i);
if (regObj.name === "$zero") {
// $r0/$zero is a readonly register
console.error("Fatal Error: Cannot overwrite zero register," +
` please check at the line number ${i}`);
process.exit(4);
}
if (typeof value === "number") {
// Integer value given
if (isNaN(value)) {
// Invalid value given
console.error("Type Error: Type mismatch while storing into" +
` register, expected integer at line ${i}`);
process.exit(4);
}
// Update the value
regObj.value = value;
} else {
// String value given
regObj.value = value.data;
regObj.encoding = value.encoding;
}
}
/** method: valueFromRegister
* @desc Fetch Data From Register File
* @param {string} register - Name of The Register
* @param {Number} i - Index of The Instruction
*/
valueFromRegister(register, i) {
const regObj = regObject(register, this.register, i);
if (! regObj.hasOwnProperty("value")) {
// Un-initialized value received
console.error("Value Error: Un-initialized value found" +
` in ${register}, please check at the line number ${i}`);
process.exit(4);
}
if (typeof value === "number" && isNaN(regObj.value)) {
// Invalid value found
console.error(`Value Error: Invalid value found in ${register},` +
` please check at the line number ${i}`);
process.exit(4);
}
// Return the value
return regObj.value;
}
/** method: encodeFromRegister
* @desc Fetch Encoding Information from Register File
* @param {string} register - Name of The Register
* @param {Number} i - Index of The Instruction
*/
encodeFromRegister(register, i) {
const regObj = regObject(register, this.register, i);
if (! regObj.hasOwnProperty("encoding")) {
// Encoding information not found
console.error("Type Error: Non string literal found in" +
` ${register}, please check at the line number ${i}`);
process.exit(4);
}
// Return the encoding
return regObj.encoding;
}
/** method: valueFromLabel
* @desc Load Data From Main Memory
* @param {string} label - Name of The Address Label
* @param {Number} i - Index of The Instruction
*/
valueFromLabel(label, i) {
const { AddressTable } = this;
const id = AddressTable.find(item => item.name === label);
if (typeof id === "undefined" || id === null) {
if (label === "main") {
// Entry point not found
console.error("Fatal Error: Cannot find main entry point," +
" failed to start execution");
process.exit(4);
}
// Label (other than main) not found
console.error(`Reference Error: Cannot find label ${label} at` +
` line ${i}`);
process.exit(4);
}
// Default return
return id.value;
}
/** method: memory
* @desc Store Data Into Main Memory
* @param {string} target - Name of The Taget Address
* @param {any} value - Value to be Put
* @param {Number} i - Index of The Instruction
*/
memory(target, value, i) {
const entry = this.AddressTable.find(e => e.name === target);
if (typeof entry !== "undefined" && (entry.readOnly === true ||
entry.type === "Program Instructions")) {
console.error("Error: Access to read only memory region." +
` Permission denied at ${i}`);
process.exit(4);
}
this.AddressTable.push(
{
"name": target,
"type": "Data Declaration",
"value": {
"encoding": "ASCII",
"data": value
}
}
);
}
/** method: updatePC
* @desc Modify Program Counter in Case of Branching
* @param {Number} address - Address of The Resolved Branch
*/
updatePC(address) {
this.ProgramCounter = address - 1;
}
}
module.exports = VM;