Sun, 23 May 2010 17:31:41 +0100
Implement OP_LOADBOOL
var OP_MOVE = 0; var OP_LOADK = 1; var OP_LOADBOOL = 2; 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_SELF = 11; var OP_CALL = 28; var OP_RETURN = 30; var OP_FORLOOP = 31; var OP_FORPREP = 32; var OP_CLOSURE = 36; var debugMode = false; function LValue(type, value) { this.type = type||"nil"; this.value = value; } 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)+" sBx: "+(INS_Bx(pi)-0x1FFFE)); } } 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)-0x1FFFF); } 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); for(var i=0;i<lfFunction.maxStackSize;i++) this.frame.reg[i] = new LValue("nil", null); 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_LOADBOOL: this.frame.reg[INS_A(instruction)] = new LValue("boolean", INS_B(instruction)!=0); if(INS_C(instruction)!=0) this.frame.pc++; 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 constant = this.frame.f.constants[INS_Bx(instruction)]; this.frame.reg[INS_A(instruction)] = new LValue(constant.type, constant.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)); for(var i=args.length+1;i<f.maxStackSize;i++) args[i] = new LValue("nil", null); 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; case OP_SELF: var table = this.frame.reg[INS_B(instruction)]; this.frame.reg[INS_A(instruction)+1] = table; var C = INS_C(instruction); var keysource = (C&256)?this.frame.f.constants:this.frame.reg; var key = keysource[C&0xff]; this.frame.reg[INS_A(instruction)] = table.index(key); break; case OP_FORPREP: this.frame.pc+=(INS_sBx(instruction)); var A = INS_A(instruction); this.frame.reg[A].value -= this.frame.reg[A+2].value; this.frame.reg[A+3] = new LValue("number", null); break; case OP_FORLOOP: var A = INS_A(instruction); var RA = this.frame.reg[A]; RA.value += this.frame.reg[A+2].value; if(RA.value <= this.frame.reg[A+1].value) { this.frame.pc += INS_sBx(instruction); this.frame.reg[A+3].value = RA.value; } 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(typeof(e) == "object" && "stack" in e) print(e.stack); }