Sat, 22 May 2010 17:33:43 +0100
Support for upvalues in functions \o/ implemented OP_GETUPVAL, and a stub for OP_SETUPVAL.
var OP_MOVE = 0; var OP_LOADK = 1; var OP_LOADNIL = 3; var OP_GETUPVAL = 4; var OP_GETGLOBAL = 5; var OP_SETGLOBAL = 7; var OP_SETUPVAL = 8; var OP_CALL = 28; var OP_RETURN = 30; var OP_CLOSURE = 36; 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; 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: "+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_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: throw "Setting upvalues not implemented yet, sorry :)"; 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 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); f.me = "Yes, it's me!"; 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: sys.puts("CONSUMING OP_MOVE"); f.upvalues[i] = this.frame.reg[INS_B(upval_instruction)]; break; case OP_GETUPVAL: sys.puts("CONSUMING OP_GETUPVAL"); f.upvalues[i] = this.frame.f.upvalues[INS_B(upval_instruction)]; break; default: throw "Invalid upvalue opcode following OP_CLOSURE"; } } this.frame.reg[INS_A(instruction)] = new LValue("function", f); 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); }