lvm.js

Sun, 23 May 2010 12:46:02 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Sun, 23 May 2010 12:46:02 +0100
changeset 29
62f3df8ed204
parent 28
d14b47c3870f
child 30
10fd886c4e8a
permissions
-rw-r--r--

Remove rawExceptions flag, always print exception's stack trace if it has one


var OP_MOVE = 0;
var OP_LOADK = 1;
var OP_LOADNIL = 3;
var OP_GETUPVAL = 4;
var OP_GETGLOBAL = 5;
var OP_GETTABLE = 6;
var OP_SETGLOBAL = 7;
var OP_SETUPVAL = 8;
var OP_SETTABLE = 9;
var OP_NEWTABLE = 10;
var OP_CALL = 28;
var OP_RETURN = 30;
var OP_CLOSURE = 36;

var debugMode = false;
var rawExceptions = false;

function LValue(type, value)
{
	this.type = type||"nil";
	this.value = value||null;
}

LValue.prototype = {
	call: function (args)
	{
		if(this.type == "function")
			return this.value;
		else
			throw "Attempt to call a " + this.type + " value";
	},
	index: function (key)
	{
		if(this.type == "table")
		{
			var val = this.value[key.value];
			if(typeof(val) == "undefined")
				return new LValue("nil", null);
			return val;
		}
		else
			throw "Attempt to index a " + this.type + " value";
	},
	setIndex: function (key, value)
	{
		if(this.type == "table")
		{
			this.value[key.value] = value;
		}
		else
			throw "Attempt to index a " + this.type + " value";
	}
};

function LValueFromString(string)
{
	return new LValue("string", string);
}

function LValueFromFunction(func)
{
	return new LValue("function", func);
}

function LValueFromValue(value)
{
	switch(typeof(value))
	{
	case "string":
		return new LValueFromString(value);
	case "function":
		return new LValueFromFunction(value);
	case "object":
		if(value == null)
			return new LValue("nil", value);
	default:
		sys.puts( "Not able to convert type " +
			typeof(value)+" from Javascript to Lua: "+sys.inspect(value));
		throw "Not able to convert type " +
			typeof(value)+" from Javascript to Lua";
	}
}

function LBinaryChunk(chunk, start)
{
	this.chunk = chunk;
	this.pos = start||12;
	
	this.sourceName = this.readString();
	this.lineDefined = this.readInt();
	this.lastLineDefined = this.readInt();
	this.numUpvalues = this.readByte();
	this.numParameters = this.readByte();
	this.isVararg = this.readByte();
	this.maxStackSize = this.readByte();
	
	this.instructions = [];
	
	this.numInstructions = this.readInt();
	for(var i=0;i<this.numInstructions;i++)
	{
		var ins = this.readInt();
		this.instructions.push([
			ins&0x3F, // Opcode
			(ins>>6)&0xFF, // Field A
			(ins>>23)&0x1FF, // Field B
			(ins>>14)&0x1FF // Field C
		]);
		if(debugMode)
		{
			var pi = this.instructions[this.instructions.length-1];
			sys.puts("Pos: "+(this.pos-4)+" Ins: "+ins+" OP: "+INS_OPCODE(pi)+" A: "+INS_A(pi)+" B: "+INS_B(pi)+" C: "+INS_C(pi)+" Bx: "+INS_Bx(pi));
		}
	}
	
	this.constants = [];
	
	this.numConstants = this.readInt();
	for(var i=0;i<this.numConstants;i++)
	{
		var type = this.readByte();
		switch(type)
		{
		case 0: // Nil
			this.constants.push(new LValue("nil", null));
			break;
		case 1: // Boolean
			this.constants.push(new LValue("boolean", this.readByte()));
			break;
		case 3: // Number
			this.constants.push(new LValue("number", this.readNumber()));
			break;
		case 4: // String
			this.constants.push(LValueFromString(this.readString()));
			break;
		default:
			throw "Invalid constant type "+type+" in bytecode";
		}
	}
	
	this.prototypes = [];
	
	this.numPrototypes = this.readInt();
	for(var i=0;i<this.numPrototypes;i++)
	{
		var p = new LBinaryChunk(chunk, this.pos);
		this.pos = p.pos;
		this.prototypes.push(p);
	}
	
	this.sourceLines = [];
	
	this.numSourceLines = this.readInt();
	for(var i=0;i<this.numSourceLines;i++)
	{
		this.sourceLines.push(this.readInt());
	}
	
	this.localList = [];
	this.numLocalList = this.readInt();
	for(var i=0;i<this.numLocalList;i++)
	{
		this.localList.push([this.readString(),this.readInt(),this.readInt()]);
	}
	
	this.upvalueList = [];
	this.numUpvalueList = this.readInt();
	for(var i=0;i<this.numUpvalueList;i++)
	{
		this.upvalueList.push(this.readString());
	}
	
	return this;
}

LBinaryChunk.prototype = {
	readBytes: function (n)
	{
		return this.chunk.slice(this.pos, this.pos+=n);
	},
	readByte: function ()
	{
		return this.readBytes(1).charCodeAt(0);
	},
	readInt: function ()
	{
		//FIXME: Endianness
		return this.readByte() | (this.readByte()<<8)
			| (this.readByte()<<16) | (this.readByte()<<24);
	},
	readString: function ()
	{
		var len = this.readInt();
		return this.readBytes(len).substring(0,len-1);
	},
	readNumber: function ()
	{
		//FIXME: Endianness
		var bytes = [this.readByte(),this.readByte(),this.readByte(),this.readByte(),
		             this.readByte(),this.readByte(),this.readByte(),this.readByte()].reverse();
		
		var sign = (bytes[0]>>7)&0x1;
		var exp = (bytes[0]&0x7F)<<4 | (bytes[1]&0xf0)>>4;
		
		var frac = ((bytes[1] & 0x0f) * Math.pow(2,48))
		          + (bytes[2] * Math.pow(2,40))
		          + (bytes[3] * Math.pow(2,32))
		          + (bytes[4] * Math.pow(2,24))
		          + (bytes[5] * Math.pow(2,16))
		          + (bytes[6] * Math.pow(2,8))
		          +  bytes[7];
		
		if(exp != 0x000 && exp != 0x7FF)
		{
			var n = (sign==1?-1:1)*Math.pow(2,exp-1023)*(1+(frac/0x10000000000000));
			return n;
		}
		else if(exp == 0x000)
		{
			return sign*0;
		}
		else
			return frac==0?sign*Infinity:NaN;
	}
};

function INS_OPCODE(ins)
{
	return ins[0];
}

function INS_A(ins)
{
	return ins[1];
}

function INS_B(ins)
{
	return ins[2];
}

function INS_C(ins)
{
	return ins[3];
}

function INS_Bx(ins)
{
	return ((INS_C(ins))|(INS_B(ins)<<9));
}

function INS_sBx(ins)
{
	return INS_Bx(ins)+0x1FFFE;
}

function LFunction(chunk, env)
{
	function F() {};
	F.prototype = chunk;
	var o = new F();
	o.environment = env;
	o.chunk = chunk;
	o.upvalues = [];
	return o;
}

function LVM()
{
	this.callstack = [];
	this.stack = [];
	return this;
}

LVM.prototype = {
	run: function (lfFunction)
	{
		this.frame = {f:lfFunction,pc:0,reg:[]};
		this.callstack.push(this.frame);
		var instruction;
		while(this.callstack.length>0)
		{
			instruction = this.frame.f.instructions[this.frame.pc++];
			if(debugMode)
			{
				sys.puts("PC: "+(this.frame.pc-1)+" OP: "+instruction[0]);
				sys.puts("STACK: "+sys.inspect(this.frame.reg));
			}
			switch(INS_OPCODE(instruction))
			{
			case OP_MOVE:
				this.frame.reg[INS_A(instruction)] = this.frame.reg[INS_B(instruction)];
				break;
			case OP_LOADNIL:
				for(var i = INS_A(instruction);i<=INS_B(instruction);i++)
					this.frame.reg[i] = new LValue("nil", null);
				break;
			case OP_GETUPVAL:
				this.frame.reg[INS_A(instruction)] = this.frame.f.upvalues[INS_B(instruction)];
				break;
			case OP_GETGLOBAL:
				var name = this.frame.f.constants[INS_Bx(instruction)];
				this.frame.reg[INS_A(instruction)] = this.frame.f.environment.index(name);
				break;
			case OP_SETUPVAL:
				var reg = this.frame.reg[INS_A(instruction)];
				var upvalue = this.frame.f.upvalues[INS_B(instruction)];
				upvalue.type = reg.type;
				upvalue.value = reg.value;
				break;
			case OP_SETGLOBAL:
				var name = this.frame.f.constants[INS_Bx(instruction)];
				this.frame.f.environment.setIndex(name, this.frame.reg[instruction[1]]);
				break;
			case OP_LOADK:
				var value = this.frame.f.constants[INS_Bx(instruction)];
				this.frame.reg[INS_A(instruction)] = value;
				break;
			case OP_NEWTABLE:
				this.frame.reg[INS_A(instruction)] = new LValue("table", {});
				break;
			case OP_GETTABLE:
				var C = INS_C(instruction);
				var keysource = (C&256)?this.frame.f.constants:this.frame.reg;
				var key = keysource[C&0xff];
				var value = this.frame.reg[INS_B(instruction)].index(key).value;
				this.frame.reg[INS_A(instruction)] = new LValueFromValue(value);
				break;
			case OP_SETTABLE:
				var C = INS_C(instruction);
				var valuesource = (C&256)?this.frame.f.constants:this.frame.reg;
				var value = valuesource[C&0xff];

				var B = INS_B(instruction);
				var keysource = (B&256)?this.frame.f.constants:this.frame.reg;
				var key = keysource[B&0xff];
				this.frame.reg[INS_A(instruction)].setIndex(key, value);
				break;
			case OP_CALL:
				var f = this.frame.reg[INS_A(instruction)].call(); // return JS or LValue
				var A = INS_A(instruction), B = INS_B(instruction), undefined;
				var args = this.frame.reg.slice(A+1, B==0?undefined:(A+B));
				if(typeof(f) == "function")
				{
					// JS native function
					var ret = f.apply(null, args.map(function (a) { return a.value; }));
				}
				else
				{
					// Lua function
					this.frame = {f:f,pc:0,reg:args,
						retAt:INS_A(instruction),retCount:INS_C(instruction)};
					this.callstack.push(this.frame);
				}
				break;
			case OP_CLOSURE:
				var prototype_id = INS_Bx(instruction);
				var chunk = this.frame.f.chunk.prototypes[prototype_id];
				var f = new LFunction(chunk, this.frame.f.environment);
				this.frame.reg[INS_A(instruction)] = new LValue("function", f);
				for(var i=0;i<chunk.numUpvalues;i++)
				{
					var upval_instruction = this.frame.f.instructions[this.frame.pc++];
					switch(INS_OPCODE(upval_instruction))
					{
					case OP_MOVE:
						f.upvalues[i] = this.frame.reg[INS_B(upval_instruction)];
						break;
					case OP_GETUPVAL:
						f.upvalues[i] = this.frame.f.upvalues[INS_B(upval_instruction)];
						break;
					default:
						throw "Invalid upvalue opcode following OP_CLOSURE";
					}
				}
				break;
			case OP_RETURN:
				var oldFrame = this.callstack.pop();
				this.frame = this.callstack[this.callstack.length-1];
				if(this.frame)
				{
					var rets;
					if(INS_B(instruction) == 0)
						rets = oldFrame.reg.slice(INS_A(instruction));
					else
						rets = oldFrame.reg.slice(INS_A(instruction),INS_A(instruction)+(INS_B(instruction)-1));
					var i;
					for(i=0;(oldFrame.retCount == 0||i<oldFrame.retCount)&&i<rets.length;i++)
						this.frame.reg[oldFrame.retAt+i] = rets[i];
					if(oldFrame.retAt+i<this.frame.reg.length)
						this.frame.reg.splice(0,oldFrame.retAt+i);
				}
				break;
			default:
				throw "Unhandled opcode: "+INS_OPCODE(instruction);
			}
		}
	}
};

var testvm = new LVM();

var fs=require("fs");
var sys=require("sys");
var c = new LBinaryChunk(fs.readFileSync("luac.out", "binary"));

var default_environment = new LValue("table", {});

var print;
if(typeof(document) == "object")
	print = function (a) { document.write(a+"<br/>") }; // Browser
else
	print = require("sys").puts; // Nodejs

default_environment.setIndex(LValueFromString("print"), LValueFromFunction(print));

var f = new LFunction(c, default_environment);

try{
	testvm.run(f);
}
catch(e)
{
	print("Error: " + e);
	if(stack in e)
		print(e.stack);
}

mercurial