A Virtual Machine in JavaScript

The code shown below implements a simple virtual machine in JavaScript, with a virtual processor, a custom instruction set, a bunch of virtual registers, virtual memory, a virtual disk drive, and a virtual display that interfaces with the host web browser.

To see the code in action, copy it into an .html file and open that file in a web browser. After the virtual machine is built, it will then execute a simple “boot stage one” program written in the instruction set. This program will read another, “boot stage two” program off of the virtual disk drive and copy it to some pre-determined address in the virtual memory. The last instruction in the boot stage one program then jumps to the address of the stage two program, which will then begin executing.

For now, the boot stage two program writes the character “H” (short for “Hello world”, I guess) to the display, instruction by laborious instruction. Because the virtual machine is currently set to only run ten instructions per second, it may take a while to see this happen.

As of this writing, some of the instructions in the instruction set, while they have theoretically been implemented, have not been even slightly tested, and thus are likely to either crash the script or cause unexpected logic errors. This includes all the conditional jump instructions (jgt, jlt, jeq, etc.). Also, the implementation of assembly-language operands with dereferencing and offsets (eg. “mov ax, [bx+4]”) is in progress, but not complete.

UPDATE 2014/07/02: I have refactored this somewhat… now it doesn’t even display the “H”. I really need to get dereferencing/offsets working. Still, I feel that the code’s in a slightly better place overall.

<html>
<body>
<script type="text/javascript">

function VirtualizationTest()
{
	this.main = function()
	{
		MachineArchitecture.initializeStatic();

		var architecture = MachineArchitecture.Instances.Default;

		var disk0 = new Disk
		(
			new Device
			(
				"Disk 0",
				Disk.DeviceDefn,
				500, // addressInCells
				4 // sizeInCells
			),
			200000 // sizeOfDiskInCells
		);

		var programBootStage2 = Program.fromLinesAssembly
		(
			"BootStage2",
			architecture,
			AssemblyLanguageSyntax.Instances.Default,
			[

				"set ax, HelloWorldText",
				"push ax	; charToPrint",
				"call CharPrintToScreen",

				"DoNothingForever:",
				"jmp DoNothingForever",

				"HelloWorldText:",
				"data \"Hello world!\"",

				"CharPrintToScreen:",
				"push bp	; save bp",
				"mov bp, sp	; save return address from stack to bp",
				"push ax	; save registers",
				"push bx",
				"push cx",
				"push dx",
				"push si",
				"push di",

				"set dx, 2 	; dx = displayID",
				"devad si, dx 	; si = devices[displayID]",
				"mov di, si	; di = display.operation",
				"stori di, 1	; display.operation = write",
				"addi di, 1	; di = display.charPosX",
				"mov bx, di	; bx = display.charPosX",
				"stori di, 1	; display.charPosX = 1",
				"addi di, 1	; di = display.charPosY",
				"stori di, 2	; display.charPosY = 2",
				"addi di, 1	; di = display.charValue",
				"stori di, 8	; display.charValue = 'H'",
				"stor di,[bp+1] ; display.charValue = charToWrite",
				"devup dx	; display.update()",

				"pop di		; restore registers",
				"pop si", 
				"pop dx",
				"pop cx",
				"pop bx",
				"pop ax",
				"pop bp",
				"ret 0		; return to caller",
			]
		);

		disk0.writeMemoryCells
		(
			0, // address
			programBootStage2.toMemoryCells
			(
				architecture
			)
		);

		var keyboard0 = new Keyboard
		(
			new Device
			(
				"Keyboard 0",
				Keyboard.DeviceDefn,
				600, // addressInCells
				4 // sizeInCells
			)
		);

		var display0 = new Display
		(
			new Device
			(
				"Display 0",
				Display.DeviceDefn,
				1000, // addressInCells
				2000 // sizeInCells
			),
			new Coords(64, 16) // sizeInCharacters
		);

		var machine = new Machine
		(
			"Test Machine",
			architecture,
			3000, // numberOfMemoryCellsAddressable
			500, // offsetOfDevices
			[
				disk0.device,
				keyboard0.device,
				display0.device,
			]
		);

		Globals.Instance.initialize
		(
			10, // millisecondsPerTick
			machine
		);
	}
}

// extensions

Array.prototype.addLookups = function(keyName)
{
	for (var i = 0; i < this.length; i++)
	{
		var element = this[i];
		this[element[keyName]] = element;
	}
}


// classes

function ArrayHelper()
{}
{
	ArrayHelper.overwriteArrayWithOther = function
	(
		sourceArray,
		sourceStartIndex,
		targetArray,
		targetStartIndex,
		numberOfItemsToOverwrite
	)
	{
		for (var i = 0; i < numberOfItemsToOverwrite; i++)
		{
			targetArray[targetStartIndex + i] = sourceArray[sourceStartIndex + i];
		}
	}
}

function AssemblyLanguageSyntax(name, delimiterForComments)
{
	this.name = name;
	this.delimiterForComments = delimiterForComments;
}
{
	AssemblyLanguageSyntax.Instances = new AssemblyLanguageSyntax_Instances();

	function AssemblyLanguageSyntax_Instances()
	{
		this.Default = new AssemblyLanguageSyntax("Default", ";");
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;

		return this;
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;

		return this;
	}
}

function Device(name, defn, address, sizeInCells)
{
	this.name = name;
	this.defn = defn;
	this.address = address;
	this.sizeInCells = sizeInCells;

	this.machine = null;
}
{
	Device.prototype.portValue = function(portName)
	{
		return this.machine.memoryCells[this.address + this.defn.ports[portName].offset];
	}

	Device.prototype.initialize = function()
	{
		this.defn.deviceInitialize(this);
	}

	Device.prototype.update = function()
	{
		this.defn.deviceUpdate(this);
	}
}

function DeviceDefn(name, ports, deviceInitialize, deviceUpdate)
{
	this.name = name;
	this.ports = ports;
	this.deviceInitialize = deviceInitialize;
	this.deviceUpdate = deviceUpdate;

	this.ports.addLookups("name");
}

function DevicePort(name, offset)
{
	this.name = name;
	this.offset = offset;
}

function Disk(device, sizeInMemoryCells)
{
	this.device = device;
	this.device.disk = this;
	this.sizeInMemoryCells = sizeInMemoryCells;

	this.memoryCells = [];
}
{
	Disk.PortNames = new Disk_PortNames();
	function Disk_PortNames()
	{
		this.OperationToPerform = "OperationToPerform";
		this.DiskAddress = "DiskAddress";
		this.MachineAddress = "MachineAddress";
		this.NumberOfCellsToMove = "NumberOfCellsToMove";
	}

	Disk.OperationValues = new Disk_OperationValues();
	function Disk_OperationValues()
	{
		this.Read = "0";
		this.Write = "1";
	}

	Disk.DeviceDefn = new DeviceDefn
	(
		"Disk",
		[
			new DevicePort(Disk.PortNames.OperationToPerform, 0),
			new DevicePort(Disk.PortNames.DiskAddress, 1),
			new DevicePort(Disk.PortNames.MachineAddress, 2),
			new DevicePort(Disk.PortNames.NumberOfCellsToMove, 3),
		],
		// deviceInitialize
		function(device) { /* todo */ },			
		// deviceUpdate
		function(device) 
		{ 
			var machine = this.machine;
			var portNames = Disk.PortNames;
			var operationValues = Disk.OperationValues;

			var operationToPerform = device.portValue(portNames.OperationToPerform);

			if (operationToPerform == operationValues.Read)
			{
				var diskAddress = device.portValue(Disk.PortNames.DiskAddress);
				var machineAddress = device.portValue(Disk.PortNames.MachineAddress);
				var numberOfCellsToMove = device.portValue(Disk.PortNames.NumberOfCellsToMove);

				device.disk.readToMemory
				(
					diskAddress,
					machineAddress,
					numberOfCellsToMove
				);
			}
			else if (operationToPerform == operationValues.Write)
			{
				device.disk.writeFromMemory
				(
					device.portValue(Disk.PortNames.MachineAddress),
					device.portValue(Disk.PortNames.DiskAddress),
					device.portValue(Disk.PortNames.NumberOfCellsToMove)
				);					
			}
		}
	);

	Disk.prototype.readToMemory = function(diskAddress, machineAddress, numberOfCellsToRead)
	{
		ArrayHelper.overwriteArrayWithOther
		(
			this.memoryCells,
			diskAddress,
			this.device.machine.memoryCells,
			machineAddress,
			numberOfCellsToRead
		);
	}

	Disk.prototype.writeFromMemory = function(machineAddress, diskAddress, numberOfCellsToWrite)
	{
		ArrayHelper.overwriteArrayWithOther
		(
			this.machine.memoryCells,
			machineAddress,
			this.memoryCells,
			diskAddress,
			numberOfCellsToWrite
		);		
	}

	Disk.prototype.writeMemoryCells = function(diskAddress, memoryCellsToWrite)
	{
		ArrayHelper.overwriteArrayWithOther
		(
			memoryCellsToWrite,
			0,
			this.memoryCells,
			diskAddress,
			memoryCellsToWrite.length
		);		
	}
}

function Display(device, sizeInCharacters)
{
	this.device = device;
	this.device.display = this;
	this.sizeInCharacters = sizeInCharacters;

	this.charSizeInPixels = new Coords(8, 16);
	this.sizeInPixels = this.sizeInCharacters.clone().multiply
	(
		this.charSizeInPixels
	); 
}
{
	Display.PortNames = new Display_PortNames();
	function Display_PortNames()
	{
		this.OperationToPerform = "OperationToPerform";
		this.CharPosX = "CharPosX";
		this.CharPosY = "CharPosY";
		this.CharValue = "CharValue";
		this.DisplayMemory = "DisplayMemory";
	}

	Display.OperationValues = new Display_OperationValues();
	function Display_OperationValues()
	{
		this.Read = "0";
		this.Write = "1";
	}

	Display.DeviceDefn = new DeviceDefn
	(
		"Display",
		[
			new DevicePort(Display.PortNames.OperationToPerform, 0),
			new DevicePort(Display.PortNames.CharPosX, 1),
			new DevicePort(Display.PortNames.CharPosY, 2),
			new DevicePort(Display.PortNames.CharValue, 3),
			new DevicePort(Display.PortNames.DisplayMemory, 4),
		],
		// initialize
		function(device) 
		{
			device.display.initialize(); 
		},
		// update
		function(device) 
		{ 
			var display = device.display;
			var machine = device.machine;
			var portNames = Display.PortNames;
			var operationValues = Display.OperationValues;

			var operationToPerform = device.portValue(portNames.OperationToPerform);
			var charPos = new Coords
			(
				device.portValue(portNames.CharPosX),
				device.portValue(portNames.CharPosY)
			);
			var charValue = device.portValue(portNames.CharValue);

			var baseAddressOfDisplayMemory = 
				device.address
				+ this.ports[portNames.DisplayMemory].offset;
			var charOffset = charPos.y * display.sizeInCharacters.x + charPos.x;
			var charAddress = 
				baseAddressOfDisplayMemory
				+ charOffset;

			if (operationToPerform == Display.OperationValues.Read)
			{
				// todo
			}
			else if (operationToPerform == Display.OperationValues.Write)
			{	
				machine.memoryCells[charAddress] = charValue;
				display.update(this);
			}

			
		}
	);

	// device

	Display.prototype.initialize = function()
	{
		document.body.appendChild(this.toHTMLElement());
		this.update();
	}

	Display.prototype.update = function()
	{
		var canvas = this.htmlElement;
		var graphics = canvas.graphics;

		graphics.fillStyle = "White";
		graphics.fillRect
		(
			0, 0, 
			this.sizeInPixels.x, 
			this.sizeInPixels.y
		);
		graphics.strokeStyle = "LightGray";
		graphics.strokeRect
		(
			0, 0, 
			this.sizeInPixels.x, 
			this.sizeInPixels.y
		);

		graphics.fillStyle = "LightGray";

		var device = this.device;
		var machine = device.machine;

		var charPos = new Coords(0, 0);
		var charPosInPixels = new Coords(0, 0);

		var baseAddressOfDisplayMemory = 
			device.address
			+ device.defn.ports[Display.PortNames.DisplayMemory].offset;

		for (var y = 0; y < this.sizeInCharacters.y; y++)
		{
			charPos.y = y;
		
			for (var x = 0; x < this.sizeInCharacters.x; x++)
			{
				charPos.x = x;	

				charPosInPixels.overwriteWith(charPos).multiply
				(
					this.charSizeInPixels
				);

				var charOffset = charPos.y * this.sizeInCharacters.x + charPos.x;
				var charAddress = 
					baseAddressOfDisplayMemory
					+ charOffset;

				var charValue = machine.memoryCells[charAddress];
				var charToDisplay;

				if (charValue == 0)
				{
					charToDisplay = "-";
				}
				else
				{
					charToDisplay = String.fromCharCode(charValue);
				}

				graphics.fillText
				(
					charToDisplay,
					charPosInPixels.x,
					charPosInPixels.y
				);			
			}
		}
	}

	// html

	Display.prototype.toHTMLElement = function()
	{		
		var canvas = document.createElement("canvas");
		canvas.width = this.sizeInPixels.x;
		canvas.height = this.sizeInPixels.y;
		canvas.graphics = canvas.getContext("2d");		

		this.htmlElement = canvas;

		return this.htmlElement;
	}

}

function Keyboard(device)
{
	this.device = device;
	this.device.keyboard = this;
}
{
	// DeviceDefn Instance

	Keyboard.PortNames = new Keyboard_PortNames();
	function Keyboard_PortNames()
	{
		this.KeyCodePressed = "KeyCodePressed";
	}

	Keyboard.DeviceDefn = new DeviceDefn
	(
		"Keyboard",
		[
			new DevicePort(Keyboard.PortNames.KeyCodePressed, 0),
		],
		// initialize
		function(device) 
		{
			device.keyboard.initialize(); 
		},
		// update
		function(device) 
		{
			// todo
		}
	);

	// Static Methods

	Keyboard.processKeyDown = function(event)
	{
		// todo
		var keyCodePressed = event.keyCode;
	}

	// Device Methods

	Keyboard.prototype.initialize = function()
	{
		document.keyboard = this;
		document.onkeydown = Keyboard.processKeyDown;
	}

	Keyboard.prototype.update = function()
	{
		// todo
	}	
}

function Globals()
{}
{
	Globals.Instance = new Globals();

	Globals.prototype.initialize = function(millisecondsPerTick, machine)
	{
		this.machine = machine;

		this.machine.boot();

		setInterval(Globals.Instance.tick, millisecondsPerTick);
	}

	Globals.prototype.tick = function()
	{
		Globals.Instance.machine.tick();
	}
}

function Instruction(opcode, operands)
{
	this.opcode = opcode;
	this.operands = operands;
}
{
	Instruction.prototype.run = function(machine)
	{
		this.opcode.run(machine, this.operands);
	}

	// memory

	Instruction.fromMemoryCell = function(architecture, memoryCellValue)
	{
		var shiftForOpcode = 
			architecture.instructionSizeInBits
			- architecture.opcodeSizeInBits;

		var opcodeValue = memoryCellValue >> shiftForOpcode;

		var opcode = architecture.instructionSet.valueToOpcodeLookup["_" + opcodeValue];

		var operands = [];

		memoryCellValue -= opcodeValue << shiftForOpcode;

		var bitsRemaining = 
			architecture.instructionSizeInBits 
			- architecture.opcodeSizeInBits;

		var operandDefns = opcode.operandDefns;
		for (var i = 0; i < operandDefns.length; i++)
		{
			var operandDefn = operandDefns[i];

			var bitsToShift = bitsRemaining - operandDefn.sizeInBits;

			var operandValue = memoryCellValue >> bitsToShift; 

			memoryCellValue -= operandValue << bitsToShift;

			bitsRemaining -= operandDefn.sizeInBits;

			var operand = new Operand
			(
				operandDefn, 
				OperandExpressionType.Instances.Direct,
				operandValue
			);

			operands.push(operand);
		}

		var returnValue = new Instruction(opcode, operands);

		return returnValue;	
	}

	Instruction.prototype.toMemoryCell = function(architecture)
	{
		var returnValue = this.opcode.value;		

		var bitsUsedSoFar = architecture.opcodeSizeInBits; 

		var operandDefns = this.opcode.operandDefns;
		for (var i = 0; i < operandDefns.length; i++)
		{
			var operand = this.operands[i];
			var operandSizeInBits = operandDefns[i].sizeInBits;

			returnValue = returnValue << operandSizeInBits;

			bitsUsedSoFar += operandSizeInBits;

			var mask = Math.pow(2, operandSizeInBits) - 1;

			returnValue += operand.toMemoryBits() & mask;
		}

		var bitsUnused = architecture.instructionSizeInBits - bitsUsedSoFar;

		returnValue = returnValue << bitsUnused;

		return returnValue;	
	}

	// strings

	Instruction.prototype.toStringAssembly = function(architecture)
	{
		var returnValue = this.opcode.mnemonic;

		for (var i = 0; i < this.operands.length; i++)
		{
			var operand = this.operands[i];
			returnValue += " " + operand.toStringAssembly(); // todo
		}

		return returnValue;
	}
}

function InstructionSet(name, opcodes)
{
	this.name = name;
	this.opcodes = opcodes;

	this.mnemonicToOpcodeLookup = [];
	this.valueToOpcodeLookup = [];

	for (var i = 0; i < this.opcodes.length; i++)
	{
		var opcode = this.opcodes[i];
		this.mnemonicToOpcodeLookup[opcode.mnemonic] = opcode;
		this.valueToOpcodeLookup["_" + opcode.value] = opcode;
	}
}

function Machine
(
	name, 
	architecture, 
	numberOfMemoryCellsAddressable,
	memoryCellOffsetForDevices, 
	devices
)
{
	this.name = name;
	this.architecture = architecture;;
	this.numberOfMemoryCellsAddressable = numberOfMemoryCellsAddressable;
	this.memoryCellOffsetForDevices = memoryCellOffsetForDevices;
	this.devices = devices;

	this.registers = [];
	this.memoryCells = new Array(this.numberOfMemoryCellsAddressable);

	for (var c = 0; c < this.numberOfMemoryCellsAddressable; c++)
	{
		this.memoryCells[c] = 0;
	}

	for (var d = 0; d < this.devices.length; d++)
	{
		var device = this.devices[d];
		device.machine = this;
		this.devices["_" + device.address] = device;
		//device.initialize();
	}

	this.registerAbbreviationToIndexLookup = [];

	var registerDefns = this.architecture.registerDefns;
	for (var r = 0; r < registerDefns.length; r++)
	{
		this.registers[r] = 0;
		var registerDefn = registerDefns[r];
		this.registerAbbreviationToIndexLookup[registerDefn.abbreviation] = r;
	}

	// convenience aliases

	this.defn = this.architecture;
	this.reg = this.registers;
	this.ri = this.registerAbbreviationToIndexLookup;
	this.mem = this.memoryCells;
}
{
	Machine.prototype.boot = function()
	{
		this.reg[this.ri.sp] = this.memoryCellOffsetForDevices - 1;

		for (var d = 0; d < this.devices.length; d++)
		{
			this.devices[d].initialize();
		}

		this.memoryWriteCells
		(
			this.architecture.bootProgram.toMemoryCells(this.architecture),
			0 // origin	
		);		
	}

	Machine.prototype.devicesUpdate = function()
	{
		for (var d = 0; d < this.devices.length; d++)
		{
			var device = this.devices[d];
			device.update();
		}
	}

	Machine.prototype.memoryWriteCells = function(cellsToWrite, addressToWriteTo)
	{
		ArrayHelper.overwriteArrayWithOther
		(
			cellsToWrite,
			0, 
			this.memoryCells,
			addressToWriteTo,
			cellsToWrite.length			
		);
	}

	Machine.prototype.tick = function()
	{
		var ip = this.reg[this.ri.ip];
		var instructionAsMemoryCell = this.memoryCells[ip];
		var instruction = Instruction.fromMemoryCell
		(
			this.architecture,
			instructionAsMemoryCell
		);
		this.reg[this.ri.ip]++;

if (instruction.opcode.mnemonic == "stor")
{
	var one = 1;

	if (instruction.operands[1].expression == "bp+1")
	{
		var two = 2;
	}
}

		instruction.run(this);

	}
}

function MachineArchitecture
(
	name, 
	instructionSizeInBits, 
	opcodeSizeInBits,
	operandSizeInBits,
	memoryCellSizeInBits, 
	registerDefns,
	instructionSet,
	assemblyLanguageSyntax,
	bootProgramLines
)
{
	this.name = name;
	this.instructionSizeInBits = instructionSizeInBits;
	this.opcodeSizeInBits = opcodeSizeInBits;
	this.operandSizeInBits = operandSizeInBits;
	this.memoryCellSizeInBits = memoryCellSizeInBits;
	this.registerDefns = registerDefns;
	this.instructionSet = instructionSet;

	this.registerAbbreviationToIndexLookup = [];

	for (var r = 0; r < this.registerDefns.length; r++)
	{
		var registerDefn = this.registerDefns[r];
		this.registerAbbreviationToIndexLookup[registerDefn.abbreviation] = r;
	}

	this.bootProgram = Program.fromLinesAssembly
	(
		"Boot Routine",
		this,
		assemblyLanguageSyntax,
		bootProgramLines
	);
}
{	
	MachineArchitecture.initializeStatic = function()
	{
		MachineArchitecture.BitsPerByte = 8;
		MachineArchitecture.Instances = new MachineArchitecture_Instances();
	}

	function MachineArchitecture_Instances()
	{
		var od = OperandDefn;

		var opcodes = 
		[
			//function Opcode(name, mnemonic, value, operandDefns, run)
			new Opcode("DoNothing", "nop", 	-1, [], 				function(m, o) { /* do nothing */ }),

			new Opcode("Add",	"add",	-1, [new od(4), new od(4)], 		function(m, o) { m.reg[o[0].v()] += m.reg[o[1].v()]; }),
			new Opcode("AddImmed",	"addi",	-1, [new od(4), new od(6)], 		function(m, o) { m.reg[o[0].v()] += o[1].v(); }),
			new Opcode("Call",	"call",	-1, [new od(10)], 			function(m, o) { m.reg[m.ri.sp]--; m.mem[m.reg[m.ri.sp]] = m.reg[m.ri.ip]; m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); }),
			new Opcode("Compare", 	"cmp", 	-1, [new od(4), new od(4)],		function(m, o) { m.reg[m.ri.cr] = m.reg[o[0].v()] - m.reg[o[1].v()];}),
			new Opcode("DevAddress","devad",-1, [new od(4), new od(4)], 		function(m, o) { m.reg[o[0].v()] = m.devices[m.reg[o[1].v()]].address; }),
			new Opcode("DevUpdate", "devup",-1, [new od(4)],			function(m, o) { m.devices[m.reg[o[0].v()]].update(); }),
			new Opcode("Divide",	"div",	-1, [new od(4), new od(4)], 		function(m, o) { m.reg[o[0].v()] /= m.reg[o[1].v()]; }),
			new Opcode("Jump",	"jmp", 	-1, [new od(10)], 			function(m, o) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); }),
			new Opcode("JmpEqual", 	"jeq",  -1, [new od(10)], 			function(m, o) { if (m.reg[m.ri.cr] == 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("JmpGreater","jgt",  -1, [new od(10)], 			function(m, o) { if (m.reg[m.ri.cr] > 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("JmpGTE",	"jgte", -1, [new od(10)], 			function(m, o) { if (m.reg[m.ri.cr] >= 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("JmpLess",	"jlt",  -1, [new od(10)], 			function(m, o) { if (m.reg[m.ri.cr] < 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("JmpLTE",	"jlte", -1, [new od(10)], 			function(m, o) { if (m.reg[m.ri.cr] <= 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("JmpNotEqual","jne", -1, [new od(10)], 			function(m, o) { if (m.reg[m.ri.cr] != 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("Load", 	"load", -1, [new od(4), new od(4)], 		function(m, o) { m.reg[o[0].v()] = m.mem[m.reg[o[1].v()]]; }),
			new Opcode("LoadImmed", "loadi", -1,[new od(4), new od(6)], 		function(m, o) { m.reg[o[0].v()] = o[1].v(); }),
			new Opcode("Move",	"mov",	-1, [new od(5), new od(5)], function(m, o) { m.reg[o[0].v()] = m.reg[o[1].v()]; }),
			new Opcode("Loop",	"loop",	-1, [new od(10)], 			function(m, o) { m.reg[m.ri.cx]--; if (m.reg[m.ri.cx] > 0) { m.reg[m.ri.ip] = (m.reg[m.ri.cs] << 8) | o[0].v(); } }),
			new Opcode("MemCopy",	"mcopy",-1, [new od(3), new od(3), new od(3)], 	function(m, o) { ArrayHelper.overwriteArrayWithOther( m.mem, m.reg[o[1].v()], m.mem, m.reg[o[0].v()], m.reg[o[2].v()] ); }),
			new Opcode("Pop",	"pop",	-1, [new od(4)], 			function(m, o) { m.reg[o[0].v()] = m.mem[m.reg[m.ri.sp]]; m.reg[m.ri.sp]++; }),
			new Opcode("Push",	"push",	-1, [new od(4)], 			function(m, o) { m.reg[m.ri.sp]--; m.mem[m.reg[m.ri.sp]] = m.reg[o[0].v()]; }),
			new Opcode("Return",	"ret", 	-1, [new od(10)], 			function(m, o) { m.reg[m.ri.ip] = m.mem[m.reg[m.ri.sp]]; m.reg[m.ri.sp] += o[0].v(); }),
			new Opcode("Set", 	"set", 	-1, [new od(2), new od(8)], 		function(m, o) { m.reg[o[0].v()] = o[1].v(); }),
			new Opcode("SetByteLow","setbl",-1, [new od(2), new od(8)],		function(m, o) { m.reg[o[0].v()] = (m.reg[o[0].v()] & 0xFF00) | o[1].v(); }), 
			new Opcode("SetByteHi",	"setbh",-1, [new od(2), new od(8)],		function(m, o) { m.reg[o[0].v()] = (m.reg[o[0].v()] & 0x00FF) | (o[1].v() << MachineArchitecture.BitsPerByte); }),
			new Opcode("SetCodeSeg","setcs",-1, [new od(10)],			function(m, o) { m.reg[m.ri.cs] = o[0].v(); }),
			new Opcode("Store", 	"stor",-1,  [new od(4), new od(4, true)], 	function(m, o) { m.mem[m.reg[o[0].v()]] = m.reg[o[1].v()]; }),
			new Opcode("StoreImmed","stori",-1, [new od(4), new od(6)], 		function(m, o) { m.mem[m.reg[o[0].v()]] = o[1].v(); }),
			new Opcode("Subtract",	"sub",	-1, [new od(4), new od(4)], 		function(m, o) { m.reg[o[0].v()] += m.reg[o[1].v()]; }),
			new Opcode("SubtractI",	"subi",	-1, [new od(4), new od(6)], 		function(m, o) { m.reg[o[0].v()] -= m.reg[o[1].v()]; }),
		];

		for (var i = 0; i < opcodes.length; i++)
		{
			// for now
			opcodes[i].value = i;
		}

		this.Default = new MachineArchitecture
		(
			"Default Machine Architecture",
			16, // instructionSizeInBits
			6, // opcodeSizeInBits
			3, // operandSizeInBits
			16, // memoryCellSizeInBits
			// registerDefns
			[
				new RegisterDefn("General Register A", "ax"),
				new RegisterDefn("General Register B", "bx"),
				new RegisterDefn("General Register C", "cx"),
				new RegisterDefn("General Register D", "dx"),

				new RegisterDefn("Source Index", "si"),
				new RegisterDefn("Destination Index", "di"),

				new RegisterDefn("Base Pointer", "bp"),
				new RegisterDefn("Stack Pointer", "sp"),
				new RegisterDefn("Code Segment", "cs"),

				new RegisterDefn("Instruction Pointer", "ip"), 
				new RegisterDefn("Comparison Result", "cr"),
			],
			new InstructionSet
			(
				"Default Instruction Set",
				opcodes
			),
			AssemblyLanguageSyntax.Instances.Default,
			// bootProgramLines
			[
				"set ax, 0	; ax = deviceID 0 (disk)",
				"devad si, ax 	; si = disk",
				"mov di, si	; di = disk.operation",
				"stori di, 0 	; disk.operation = read",
				"addi di, 1	; di = disk.diskAddress",
				"stori di, 0	; disk.diskAddress = 0",
				"addi di, 1	; di = disk.memoryAddress",
				"setbh dx, 1 	; dx = memory address for 2nd stage boot",
				"setbl dx, 0	; 	(address of segment 1)",
				"stor di, dx  	; disk.memoryAddress = 2nd stage boot",
				"addi di, 1	; di = disk.numberOfCellsToMove",
				"stori di, 32	; disk.numberOfCellsToMove = 32",
				"devup ax	; disk.update()",
				"setcs 1	; prepare to jump to segment 1",
				"jmp 0		; to 2nd stage boot",
			]	
		);
	}
}

function Opcode(name, mnemonic, value, operandDefns, run)
{
	this.name = name;
	this.mnemonic = mnemonic;
	this.value = value;
	this.operandDefns = operandDefns;
	this.run = run;
}

function Operand(defn, expressionType, expression)
{
	this.defn = defn;
	this.expressionType = expressionType;
	this.expression = expression;
}
{
	// static methods

	Operand.fromLinesAssembly = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar,
		tokens,
		opcode,
		operands,
		operandDefn,
		operandAsString
	)
	{
		var operandSizeInBits = operandDefn.sizeInBits;

		if (operandDefn.canBeDereference == false)
		{
			Operand.fromLinesAssembly_Direct
			(
				programLine, 
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar,
				tokens,
				opcode,
				operands,
				operandDefn,
				operandAsString,
				operandSizeInBits
			);
		}
		else
		{
			Operand.fromLinesAssembly_Dereference
			(
				programLine, 
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar,
				tokens,
				opcode,
				operands,
				operandDefn,
				operandAsString,
				operandSizeInBits
			);
		}
	}

	Operand.fromLinesAssembly_Direct = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar,
		tokens,
		opcode,
		operands,
		operandDefn,
		operandAsString,
		operandSizeInBits
	)
	{
		var registerIndex = architecture.registerAbbreviationToIndexLookup[operandAsString];
		if (registerIndex != null)
		{		
			if (registerIndex >= Math.pow(2, operandSizeInBits))
			{
				errorsSoFar.push
				(
					"Register cannot be used as an argument for opcode: "
					+ "'" + operandAsString + "', '" + mnemonic + "'"
					+ "."
				);
			}
			operandAsString = "" + registerIndex;
		}


		var operand;

		if (isNaN(operandAsString) == true)
		{
			var operand = new Operand
			(
				operandDefn, 
				OperandExpressionType.Instances.Label,
				operandAsString
			);
		}
		else
		{
			var operandAsInteger = parseInt(operandAsString);

			var operand = new Operand
			(
				operandDefn, 
				OperandExpressionType.Instances.Direct,
				operandAsInteger
			);
		}


		operands.push(operand);
	}

	Operand.fromLinesAssembly_Dereference = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar,
		tokens,
		opcode,
		operands,
		operandDefn,
		operandAsString,
		operandSizeInBits
	)
	{
		if (operandAsString.indexOf("[") == 0)
		{
			if (operandAsString.indexOf("]") != operandAsString.length - 1)
			{
				errorsSoFar.push
				(
					"Unclosed bracket in operand."
				);						
			}
			else
			{
				operandAsString = operandAsString.substr(1, operandAsString.length - 2);
				operandAsString = operandAsString.replace(" ","");

				var operand = new Operand
				(
					operandDefn,
					OperandExpressionType.Instances.Dereference,
					operandAsString
				)

				operands.push(operand);
			}	
		}
		else
		{
			Operand.fromLinesAssembly_Direct
			(
				programLine, 
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar,
				tokens,
				opcode,
				operands,
				operandDefn,
				operandAsString,
				operandSizeInBits
			);
		}				
	}


	// instance methods

	Operand.prototype.toStringAssembly = function()
	{
		throw "Not yet implemented!";
	}

	Operand.prototype.toMemoryBits = function()
	{
		var returnValue;

		if (this.expressionType == OperandExpressionType.Instances.Direct)
		{
			returnValue = this.expression;
		}
		else if (this.expressionType == OperandExpressionType.Instances.Dereference)
		{
			throw "Not yet implemented!";

			var operandBaseAsString;
			var operandOffset;

			var plusSign = "+";

			if (this.expression.indexOf(plusSign) == -1)
			{
				operandBaseAsString = this.expression;
			}
			else
			{
				var expressionSplitOnPlusSign = this.expression.split(plusSign);
				operandBaseAsString = expressionSplitOnPlusSign[0];
				operandOffset = parseInt(expressionSplitOnPlusSign[1]);
			}

			var machine = Globals.Instance.machine; // hack
			var architecture = machine.architecture; // hack
			var registerIndex = architecture.registerAbbreviationToIndexLookup[operandBaseAsString];

			if (registerIndex == null)
			{
				throw "Not yet implemented!"
			}
			else
			{
				returnValue = machine.memoryCells[machine.registers[registerIndex] + operandOffset]
			}
		}
		else
		{
			throw "Unrecognized operand expression type!";
		}

		return returnValue;
	}

	Operand.prototype.v = function()
	{
		var returnValue;

		if (this.expressionType == OperandExpressionType.Instances.Direct)
		{
			returnValue = this.expression;
		}
		else if (this.expressionType == OperandExpressionType.Instances.Dereference)
		{
			var operandBaseAsString;
			var operandOffset;

			var plusSign = "+";

			if (this.expression.indexOf(plusSign) == -1)
			{
				operandBaseAsString = this.expression;
			}
			else
			{
				var expressionSplitOnPlusSign = this.expression.split(plusSign);
				operandBaseAsString = expressionSplitOnPlusSign[0];
				operandOffset = parseInt(expressionSplitOnPlusSign[1]);
			}

			var machine = Globals.Instance.machine; // hack
			var architecture = machine.architecture; // hack
			var registerIndex = architecture.registerAbbreviationToIndexLookup[operandBaseAsString];

			if (registerIndex == null)
			{
				throw "Not yet implemented!"
			}
			else
			{
				returnValue = machine.memoryCells[machine.registers[registerIndex] + operandOffset]
			}
		}
		else
		{
			throw "Unrecognized operand expression type!";
		}

		return returnValue;
	}
}

function OperandDefn(sizeInBits, canBeDereference)
{
	this.sizeInBits = sizeInBits;
	this.canBeDereference = (canBeDereference == null ? false : canBeDereference);
}

function OperandExpressionType(name, value)
{
	this.name = name;
	this.value = value;	
}
{
	OperandExpressionType.Instances = new OperandExpressionType_Instances();

	function OperandExpressionType_Instances()
	{
		this.Dereference 	= new OperandExpressionType("Dereference", 1);
		this.Direct 		= new OperandExpressionType("Direct", 0);
		this.Label		= new OperandExpressionType("Label", 0);
		this.Register 		= new OperandExpressionType("Register", 1);
	}
}

function Program(name, instructions)
{
	this.name = name;
	this.instructions = instructions;
}
{
	Program.fromLinesAssembly = function(name, architecture, assemblyLanguageSyntax, programLines)
	{
		var instructionsSoFar = [];
		var labelToOffsetLookup = [];
		var mnemonicToOpcodeLookup = architecture.instructionSet.mnemonicToOpcodeLookup;
		var errorsSoFar = [];

		for (var i = 0; i < programLines.length; i++)
		{
			var programLine = programLines[i]; 

			var indexOfCommentDelimiter = programLine.indexOf
			(
				assemblyLanguageSyntax.delimiterForComments
			);

			if (indexOfCommentDelimiter >= 0)
			{
				programLine = programLine.substring(0, indexOfCommentDelimiter);
			}

			programLine = programLine.trim();

			programLine = programLine.replace(",", " ");
			while (programLine.indexOf("  ") >= 0)
			{
				programLine = programLine.replace("  ", " ");
			}

			if (programLine.length > 0)
			{
				Program.fromLinesAssembly_ParseLabelOrInstruction
				(
					programLine,
					architecture,
					instructionsSoFar,
					labelToOffsetLookup, 
					mnemonicToOpcodeLookup,
					errorsSoFar
				);
			}
		}

		if (errorsSoFar.length > 0)
		{
			// todo - Handle errors.
		}

		for (var i = 0; i < instructionsSoFar.length; i++)
		{
			var instruction = instructionsSoFar[i];

			var operands = instruction.operands;
			for (var j = 0; j < operands.length; j++)
			{
				var operand = operands[j];

				if (operand.expressionType == OperandExpressionType.Instances.Label)
				{
					var label = operand.expression;
					var offsetForLabel = labelToOffsetLookup[label];
					if (offsetForLabel == null)
					{
						throw "Label '" + label + "' not found!";
					}
					else
					{
						operand.expressionType = OperandExpressionType.Instances.Direct;
						operand.expression = offsetForLabel;		
					}
				}
			}
		}

		var returnValue = new Program
		(
			name,
			instructionsSoFar
		);

		return returnValue;
	}

	Program.fromLinesAssembly_ParseLabelOrInstruction = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar
	)
	{
		if (programLine.indexOf(":") == programLine.length - 1)
		{
			Program.fromLinesAssembly_ParseLabel
			(
				programLine,
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar
			);
		}
		else if (programLine.indexOf("data") == 0)
		{
			Program.fromLinesAssembly_ParseData
			(
				programLine,
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar
			);			
		}
		else
		{
			Program.fromLinesAssembly_ParseInstruction
			(
				programLine,
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar
			);
		}
	}

	Program.fromLinesAssembly_ParseData = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar
	)
	{		
		var tokens = programLine.split(",");

		for (var t = 1; t < tokens.length; t++)
		{
			var token = tokens[t];

			token = token.trim();

			if (token.indexOf("\"") == 0)
			{
				if (token.lastIndexOf("\"") != token.length - 1)
				{
					errorsSoFar.push
					(
						"Invalid data value."
					);
				}
				else
				{
					for (var c = 1; c < token.length - 1; c++)
					{
						var charFromString = token[c];

						instructionsSoFar.push
						(
							new Instruction
							(
								null, // opcode
								[ charFromString ]
							)
						);
					}
				}
			}
			else
			{
				var tokenAsInteger = parseInt(token);

				if (isNaN(tokenAsInteger) == true)
				{
					errorsSoFar.push
					(
						"Invalid data value."
					);
				}
				else
				{
					instructionsSoFar.push
					(
						new Instruction
						(
							null, // opcode
							[ tokenAsInteger ]
						)
					);
				}
			}
		}
	}

	Program.fromLinesAssembly_ParseLabel = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar
	)
	{		
		var indexOfColon = programLine.indexOf(":");

		var label = programLine.substring(0, indexOfColon).trim();

		if (labelToOffsetLookup[label] != null)
		{
			errorsSoFar.push
			(
				"Label already in use: '" + label + "'."
			);
		}
		else if (mnemonicToOpcodeLookup[label] != null)
		{
			"Label must not be an opcode: '" + label + "'."
		}
		else
		{
			labelToOffsetLookup[label] = instructionsSoFar.length;
		}
	}

	Program.fromLinesAssembly_ParseInstruction = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar
	)
	{	
		var operandExpressionTypes = OperandExpressionType.Instances;

		var tokens = programLine.split(" ");

		var mnemonic = tokens[0];

		var opcode = mnemonicToOpcodeLookup[mnemonic];

		if (opcode == null)
		{
			errorsSoFar.push
			(
				("Invalid opcode: '" + mnemonic + "'.")
			);
		}
		else
		{
			Program.fromLinesAssembly_ParseInstruction_Operands
			(
				programLine, 
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar,
				tokens,
				opcode
			);
		}
	}

	Program.fromLinesAssembly_ParseInstruction_Operands = function
	(
		programLine, 
		architecture,
		instructionsSoFar,
		labelToOffsetLookup, 
		mnemonicToOpcodeLookup,
		errorsSoFar,
		tokens,
		opcode
	)
	{
		var operands = [];

		var operandDefns = opcode.operandDefns;

		for (var s = 0; s < operandDefns.length; s++)
		{
			var operandDefn = operandDefns[s];

			var operandAsString = tokens[s + 1];

			Operand.fromLinesAssembly
			(
				programLine, 
				architecture,
				instructionsSoFar,
				labelToOffsetLookup, 
				mnemonicToOpcodeLookup,
				errorsSoFar,
				tokens,
				opcode,
				operands,
				operandDefn,
				operandAsString
			);
		}

		var instruction = new Instruction(opcode, operands);

		instructionsSoFar.push(instruction);
	}

	Program.fromStringAssembly = function(architecture, programAsString)
	{
		var newline = "\r\n";
		var returnValue = Program.fromLinesAssembly
		(
			architecture,
			programAsString.split(newline)
		);
		return returnValue;
	}

	Program.prototype.toStringAssembly = function(architecture)
	{
		var returnValue = "";

		var newline = "\r\n";

		for (var i = 0; i < this.instructions.length; i++)
		{
			var instruction = this.instructions[i];

			returnValue += 
				instruction.toStringAssembly(architecture) 
				+ newline;
		}

		return returnValue;
	}

	// memory

	Program.prototype.toMemoryCells = function(architecture)
	{
		var returnValue = [];

		for (var i = 0; i < this.instructions.length; i++)
		{
			var instruction = this.instructions[i];
			var instructionAsMemoryCell = instruction.toMemoryCell
			(
				architecture
			);
			returnValue.push(instructionAsMemoryCell);
		}

		return returnValue;
	}

}

function RegisterDefn(name, abbreviation)
{
	this.name = name;
	this.abbreviation = abbreviation;
}

// run

new VirtualizationTest().main();

</script>
</body>
</html>
Advertisements
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s