Wed, 07 Apr 2010 03:05:24 +0100
Add chunk property to LFunction to show which chunk it came from
var OP_MOVE = 0; var OP_LOADK = 1; var OP_LOADNIL = 3; var OP_GETGLOBAL = 5; var OP_SETGLOBAL = 7; var OP_CALL = 28; var OP_RETURN = 30; var debugMode = 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 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; 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: "+JSON.stringify(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_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_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_CALL: var f = this.frame.reg[INS_A(instruction)].call(); // return JS or LValue var args = this.frame.reg.slice(INS_A(instruction)+1, INS_A(instruction)+(INS_B(instruction))); if(typeof(f) == "function") { // JS native function var ret = f.apply(null, args.map(function (a) { return a.value; })); } else { // Lua function } break; case OP_RETURN: this.callstack.pop(); 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); }