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 }