1 module zua.vm.dump; 2 import zua.vm.engine; 3 import zua.compiler.utils; 4 import std.stdio; 5 import std.conv; 6 import std.bitmanip; 7 import std.uni; 8 9 version(assert) { 10 private string formatLua(string s) { 11 return "[[" ~ s ~ "]]"; 12 } 13 14 private class Dumper { 15 16 private ubyte[] buffer; 17 size_t ip; 18 ulong tabs; 19 20 this(immutable(ubyte)[] buffer) { 21 this.buffer = buffer.dup; 22 } 23 24 /** Read a single numeric value */ 25 private T read(T)(size_t index) { 26 ubyte[T.sizeof] data = buffer[index..index + T.sizeof]; 27 return littleEndianToNative!(T, T.sizeof)(data); 28 } 29 30 /** Read a string */ 31 private string readStr(size_t index) { 32 const length = cast(size_t)read!ulong(index); 33 string result = cast(string)buffer[index + 8..index + 8 + length]; 34 return result; 35 } 36 37 /** Read a single numeric value */ 38 private T read(T)() { 39 ubyte[T.sizeof] data = buffer[ip..ip + T.sizeof]; 40 ip += T.sizeof; 41 return littleEndianToNative!(T, T.sizeof)(data); 42 } 43 44 /** Read a string */ 45 private string readStr() { 46 const length = cast(size_t)read!ulong(); 47 string result = cast(string)buffer[ip..ip + length]; 48 ip += length; 49 return result; 50 } 51 52 string prefix() { 53 string result; 54 foreach (i; 0..tabs * 4) result ~= ' '; 55 return result; 56 } 57 58 string dumpInst() { 59 const Opcode op = cast(Opcode)read!OpcodeSize; 60 string opName = op.to!string.toUpper; 61 switch (op) { 62 case Opcode.Add: 63 case Opcode.Sub: 64 case Opcode.Mul: 65 case Opcode.Div: 66 case Opcode.Exp: 67 case Opcode.Mod: 68 case Opcode.Unm: 69 case Opcode.Not: 70 case Opcode.Len: 71 case Opcode.Concat: 72 case Opcode.Eq: 73 case Opcode.Ne: 74 case Opcode.Lt: 75 case Opcode.Le: 76 case Opcode.Gt: 77 case Opcode.Ge: 78 case Opcode.Ret: 79 case Opcode.Getfenv: 80 case Opcode.Call: 81 case Opcode.Drop: 82 case Opcode.Dup: 83 case Opcode.LdNil: 84 case Opcode.LdFalse: 85 case Opcode.LdTrue: 86 case Opcode.LdArgs: 87 case Opcode.NewTable: 88 case Opcode.GetTable: 89 case Opcode.SetTable: 90 case Opcode.SetTableRev: 91 case Opcode.DropLoop: 92 return opName; 93 case Opcode.LdNum: 94 return opName ~ " " ~ read!double.to!string; 95 case Opcode.LdFun: { 96 const ulong index = read!ulong; 97 const ulong upvalues = read!ulong; 98 string res = opName ~ " " ~ index.to!string ~ ", ["; 99 foreach (i; 0..upvalues) { 100 if (i > 0) 101 res ~= ", "; 102 const long uv = read!long; 103 if (uv < 0) { 104 res ~= "upvalue " ~ (~uv).to!string; 105 } 106 else { 107 res ~= "local " ~ uv.to!string; 108 } 109 } 110 return res ~ "]"; 111 } 112 case Opcode.Introspect: 113 case Opcode.DropTuple: 114 case Opcode.UnpackRev: 115 case Opcode.Unpack: 116 case Opcode.Pack: 117 case Opcode.UnpackD: 118 case Opcode.DupN: 119 case Opcode.SetArray: 120 return opName ~ " " ~ read!StackOffset.to!string; 121 case Opcode.Loop: 122 case Opcode.Jmp: 123 case Opcode.JmpT: 124 case Opcode.JmpF: 125 case Opcode.JmpNil: 126 return opName ~ " " ~ read!FullWidth.to!string; 127 default: 128 return opName ~ " " ~ read!CommonOperand.to!string; 129 } 130 } 131 132 string dumpFunc(ulong idx = 0) { 133 const ulong dataLength = read!ulong; 134 const ulong funcLength = read!ulong; 135 const ulong locals = read!ulong; 136 const ulong upvalues = read!ulong; 137 const ulong codeLength = read!ulong; 138 const startIp = ip; 139 string result; 140 result ~= prefix ~ "function " ~ idx.to!string ~ " (\n"; 141 tabs++; 142 result ~= prefix ~ "locals: " ~ locals.to!string ~ "\n"; 143 result ~= prefix ~ "upvalues: " ~ upvalues.to!string ~ "\n"; 144 tabs--; 145 result ~= prefix ~ ")\n"; 146 tabs++; 147 // read data segment: 148 ip += codeLength; 149 const ulong datasegIndices = ip; 150 const ulong datasegPtr = ip + (dataLength + 1) * 8; 151 foreach (i; 0..dataLength) { 152 ip = datasegIndices + 8 * i; 153 ip = datasegPtr + read!ulong; 154 result ~= prefix ~ "string " ~ i.to!string ~ " = " ~ readStr().formatLua ~ "\n"; 155 } 156 // read function segment: 157 ip = datasegIndices; 158 ip += 8 * dataLength; 159 const ulong datasegSize = read!ulong; 160 ip += datasegSize; 161 const ulong funcsegIndices = ip; 162 const ulong funcsegPtr = ip + (funcLength + 1) * 8; 163 foreach (i; 0..funcLength) { 164 ip = funcsegIndices + 8 * i; 165 ip = funcsegPtr + read!ulong; 166 result ~= dumpFunc(i); 167 } 168 // read code: 169 ip = startIp; 170 while (ip < startIp + codeLength) { 171 result ~= prefix ~ dumpInst() ~ "\n"; 172 } 173 tabs--; 174 result ~= prefix ~ "end\n"; 175 return result; 176 } 177 178 } 179 180 /** Dump some bytecode */ 181 string dump(immutable(ubyte)[] data) { 182 return new Dumper(data).dumpFunc(); 183 } 184 185 unittest { 186 import zua.compiler.utils : Indices, OperandValue, AtomicInstruction, MonadInstruction, Function, LdFun, serialize; 187 import zua.compiler.sourcemap : SourceMap; 188 189 Function f = new Function; 190 f.locals = 5; 191 auto a = new AtomicInstruction(Opcode.LdNil); 192 f.code ~= a; 193 auto c = new MonadInstruction(Opcode.LdNum, OperandValue(5.3)); 194 f.code ~= c; 195 auto d = new LdFun(5, [0, 3]); 196 f.code ~= d; 197 198 Function e = new Function; 199 e.upvalues = 2; 200 f.functions ~= e; 201 202 auto g = new MonadInstruction(Opcode.Pack, OperandValue(7)); 203 e.code ~= g; 204 205 Function h = new Function; 206 h.upvalues = 92; 207 f.functions ~= h; 208 209 Function i = new Function; 210 i.locals = 54; 211 i.upvalues = 4; 212 e.functions ~= i; 213 214 f.data ~= "hello"; 215 f.data ~= "world!"; 216 217 h.data ~= "Hello, WOrld!"; 218 219 auto map = new SourceMap; 220 auto b = serialize(f, new Indices(0, 0), map); 221 222 assert(dump(b) == `function 0 ( 223 locals: 5 224 upvalues: 0 225 ) 226 string 0 = [[hello]] 227 string 1 = [[world!]] 228 function 0 ( 229 locals: 0 230 upvalues: 2 231 ) 232 function 0 ( 233 locals: 54 234 upvalues: 4 235 ) 236 end 237 PACK 7 238 end 239 function 1 ( 240 locals: 0 241 upvalues: 92 242 ) 243 string 0 = [[Hello, WOrld!]] 244 end 245 LDNIL 246 LDNUM 5.3 247 LDFUN 5, [local 0, local 3] 248 end 249 `); 250 } 251 }