1 module wren.dbg;
2 import core.stdc.stdio : printf;
3 import wren.common;
4 import wren.opcodes;
5 import wren.value;
6 import wren.vm;
7 
8 @nogc:
9 
10 void wrenDebugPrintStackTrace(WrenVM* vm)
11 {
12     // Bail if the host doesn't enable printing errors.
13     if (vm.config.errorFn == null) return;
14 
15     ObjFiber* fiber = vm.fiber;
16     if (IS_STRING(fiber.error))
17     {
18         vm.config.errorFn(vm, WrenErrorType.WREN_ERROR_RUNTIME,
19                         null, -1, AS_CSTRING(fiber.error));
20     }
21     else
22     {
23         // TODO: Print something a little useful here. Maybe the name of the error's
24         // class?
25         vm.config.errorFn(vm, WrenErrorType.WREN_ERROR_RUNTIME,
26                         null, -1, "[error object]");
27     }
28 
29     for (int i = fiber.numFrames - 1; i >= 0; i--)
30     {
31         CallFrame* frame = &fiber.frames[i];
32         ObjFn* fn = frame.closure.fn;
33 
34         // Skip over stub functions for calling methods from the C API.
35         if (fn.module_ == null) continue;
36         
37         // The built-in core module has no name. We explicitly omit it from stack
38         // traces since we don't want to highlight to a user the implementation
39         // detail of what part of the core module is written in C and what is Wren.
40         if (fn.module_.name == null) continue;
41         
42         // -1 because IP has advanced past the instruction that it just executed.
43         int line = fn.debug_.sourceLines.data[frame.ip - fn.code.data - 1];
44         vm.config.errorFn(vm, WrenErrorType.WREN_ERROR_STACK_TRACE,
45                         fn.module_.name.value.ptr, line,
46                         fn.debug_.name);
47     }
48 }
49 
50 static void dumpObject(Obj* obj)
51 {
52     switch (obj.type) with(ObjType)
53     {
54         case OBJ_CLASS:
55             printf("[class %s %p]", (cast(ObjClass*)obj).name.value.ptr, obj);
56             break;
57         case OBJ_CLOSURE: printf("[closure %p]", obj); break;
58         case OBJ_FIBER: printf("[fiber %p]", obj); break;
59         case OBJ_FN: printf("[fn %p]", obj); break;
60         case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
61         case OBJ_INSTANCE: printf("[instance %p]", obj); break;
62         case OBJ_LIST: printf("[list %p]", obj); break;
63         case OBJ_MAP: printf("[map %p]", obj); break;
64         case OBJ_MODULE: printf("[module %p]", obj); break;
65         case OBJ_RANGE: printf("[range %p]", obj); break;
66         case OBJ_STRING: printf("%s", (cast(ObjString*)obj).value.ptr); break;
67         case OBJ_UPVALUE: printf("[upvalue %p]", obj); break;
68         default: printf("[unknown object %d]", obj.type); break;
69     }
70 
71 }
72 
73 void wrenDumpValue(Value value)
74 {
75     static if (WREN_NAN_TAGGING)
76     {
77         if (IS_NUM(value))
78         {
79             printf("%.14g", AS_NUM(value));
80         }
81         else if (IS_OBJ(value))
82         {
83             dumpObject(AS_OBJ(value));
84         }
85         else
86         {
87             switch (GET_TAG(value))
88             {
89                 case TAG_FALSE:     printf("false"); break;
90                 case TAG_NAN:       printf("NaN"); break;
91                 case TAG_NULL:      printf("null"); break;
92                 case TAG_TRUE:      printf("true"); break;
93                 case TAG_UNDEFINED: assert(0, "Unreachable");
94                 default: assert(0, "Unexpected tag");
95             }
96         }
97     }
98     else
99     {
100         switch (value.type) with(ValueType)
101         {
102             case VAL_FALSE:     printf("false"); break;
103             case VAL_NULL:      printf("null"); break;
104             case VAL_NUM:       printf("%.14g", AS_NUM(value)); break;
105             case VAL_TRUE:      printf("true"); break;
106             case VAL_OBJ:       dumpObject(AS_OBJ(value)); break;
107             case VAL_UNDEFINED: assert(0, "Unreachable");
108             default: assert(0, "Unexpected type");
109         }
110     }
111 
112 }
113 
114 static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
115 {
116     int start = i;
117     ubyte* bytecode = fn.code.data;
118     Code code = cast(Code)bytecode[i];
119 
120     int line = fn.debug_.sourceLines.data[i];
121     if (lastLine == null || *lastLine != line)
122     {
123         printf("%4d:", line);
124         if (lastLine != null) *lastLine = line;
125     }
126     else
127     {
128         printf("     ");
129     }
130 
131     printf(" %04d  ", i++);
132 
133     ubyte READ_BYTE() {
134         return bytecode[i++];
135     }
136 
137     ushort READ_SHORT() {
138         i += 2;
139         return (bytecode[i - 2] << 8 | bytecode[i - 1]);
140     }
141 
142     auto BYTE_INSTRUCTION(const(char)[] instr) {
143         return "printf(\"%-16s %5d\n\", \"" ~ instr ~ "\".ptr, READ_BYTE()); break;";
144     }
145 
146     switch (code) with (Code)
147     {
148         case CODE_CONSTANT:
149         {
150             int constant = READ_SHORT();
151             printf("%-16s %5d '", "CONSTANT".ptr, constant);
152             wrenDumpValue(fn.constants.data[constant]);
153             printf("'\n");
154             break;
155         }
156 
157         case CODE_NULL:  printf("NULL\n"); break;
158         case CODE_FALSE: printf("FALSE\n"); break;
159         case CODE_TRUE:  printf("TRUE\n"); break;
160 
161         case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break;
162         case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break;
163         case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break;
164         case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break;
165         case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break;
166         case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break;
167         case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break;
168         case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break;
169         case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break;
170 
171         case CODE_LOAD_LOCAL: {
172             mixin (BYTE_INSTRUCTION("LOAD_LOCAL"));
173         }
174         case CODE_STORE_LOCAL: mixin (BYTE_INSTRUCTION("STORE_LOCAL"));
175         case CODE_LOAD_UPVALUE: mixin (BYTE_INSTRUCTION("LOAD_UPVALUE"));
176         case CODE_STORE_UPVALUE: mixin (BYTE_INSTRUCTION("STORE_UPVALUE"));
177 
178         case CODE_LOAD_MODULE_VAR:
179         {
180             int slot = READ_SHORT();
181             printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR".ptr, slot,
182                     fn.module_.variableNames.data[slot].value.ptr);
183             break;
184         }
185 
186         case CODE_STORE_MODULE_VAR:
187         {
188             int slot = READ_SHORT();
189             printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR".ptr, slot,
190                     fn.module_.variableNames.data[slot].value.ptr);
191             break;
192         }
193 
194         case CODE_LOAD_FIELD_THIS: mixin (BYTE_INSTRUCTION("LOAD_FIELD_THIS"));
195         case CODE_STORE_FIELD_THIS: mixin (BYTE_INSTRUCTION("STORE_FIELD_THIS"));
196         case CODE_LOAD_FIELD: mixin (BYTE_INSTRUCTION("LOAD_FIELD"));
197         case CODE_STORE_FIELD: mixin (BYTE_INSTRUCTION("STORE_FIELD"));
198 
199         case CODE_POP: printf("POP\n"); break;
200 
201         case CODE_CALL_0:
202         case CODE_CALL_1:
203         case CODE_CALL_2:
204         case CODE_CALL_3:
205         case CODE_CALL_4:
206         case CODE_CALL_5:
207         case CODE_CALL_6:
208         case CODE_CALL_7:
209         case CODE_CALL_8:
210         case CODE_CALL_9:
211         case CODE_CALL_10:
212         case CODE_CALL_11:
213         case CODE_CALL_12:
214         case CODE_CALL_13:
215         case CODE_CALL_14:
216         case CODE_CALL_15:
217         case CODE_CALL_16:
218         {
219             int numArgs = bytecode[i - 1] - CODE_CALL_0;
220             int symbol = READ_SHORT();
221             printf("CALL_%-11d %5d '%s'\n", numArgs, symbol,
222                     vm.methodNames.data[symbol].value.ptr);
223             break;
224         }
225 
226         case CODE_SUPER_0:
227         case CODE_SUPER_1:
228         case CODE_SUPER_2:
229         case CODE_SUPER_3:
230         case CODE_SUPER_4:
231         case CODE_SUPER_5:
232         case CODE_SUPER_6:
233         case CODE_SUPER_7:
234         case CODE_SUPER_8:
235         case CODE_SUPER_9:
236         case CODE_SUPER_10:
237         case CODE_SUPER_11:
238         case CODE_SUPER_12:
239         case CODE_SUPER_13:
240         case CODE_SUPER_14:
241         case CODE_SUPER_15:
242         case CODE_SUPER_16:
243         {
244             int numArgs = bytecode[i - 1] - CODE_SUPER_0;
245             int symbol = READ_SHORT();
246             int superclass = READ_SHORT();
247             printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol,
248                     vm.methodNames.data[symbol].value.ptr, superclass);
249             break;
250         }
251 
252         case CODE_JUMP:
253         {
254             int offset = READ_SHORT();
255             printf("%-16s %5d to %d\n", "JUMP".ptr, offset, i + offset);
256             break;
257         }
258 
259         case CODE_LOOP:
260         {
261             int offset = READ_SHORT();
262             printf("%-16s %5d to %d\n", "LOOP".ptr, offset, i - offset);
263             break;
264         }
265 
266         case CODE_JUMP_IF:
267         {
268             int offset = READ_SHORT();
269             printf("%-16s %5d to %d\n", "JUMP_IF".ptr, offset, i + offset);
270             break;
271         }
272 
273         case CODE_AND:
274         {
275             int offset = READ_SHORT();
276             printf("%-16s %5d to %d\n", "AND".ptr, offset, i + offset);
277             break;
278         }
279 
280         case CODE_OR:
281         {
282             int offset = READ_SHORT();
283             printf("%-16s %5d to %d\n", "OR".ptr, offset, i + offset);
284             break;
285         }
286 
287         case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break;
288         case CODE_RETURN:        printf("RETURN\n"); break;
289 
290         case CODE_CLOSURE:
291         {
292             int constant = READ_SHORT();
293             printf("%-16s %5d ", "CLOSURE".ptr, constant);
294             wrenDumpValue(fn.constants.data[constant]);
295             printf(" ");
296             ObjFn* loadedFn = AS_FN(fn.constants.data[constant]);
297             for (int j = 0; j < loadedFn.numUpvalues; j++)
298             {
299                 int isLocal = READ_BYTE();
300                 int index = READ_BYTE();
301                 if (j > 0) printf(", ");
302                 printf("%s %d", isLocal ? "local".ptr : "upvalue".ptr, index);
303             }
304             printf("\n");
305             break;
306         }
307 
308         case CODE_CONSTRUCT:         printf("CONSTRUCT\n"); break;
309         case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break;
310         
311         case CODE_CLASS:
312         {
313             int numFields = READ_BYTE();
314             printf("%-16s %5d fields\n", "CLASS".ptr, numFields);
315             break;
316         }
317 
318         case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
319         case CODE_END_CLASS: printf("END_CLASS\n"); break;
320 
321         case CODE_METHOD_INSTANCE:
322         {
323             int symbol = READ_SHORT();
324             printf("%-16s %5d '%s'\n", "METHOD_INSTANCE".ptr, symbol,
325                     vm.methodNames.data[symbol].value.ptr);
326             break;
327         }
328 
329         case CODE_METHOD_STATIC:
330         {
331             int symbol = READ_SHORT();
332             printf("%-16s %5d '%s'\n", "METHOD_STATIC".ptr, symbol,
333                     vm.methodNames.data[symbol].value.ptr);
334             break;
335         }
336         
337         case CODE_END_MODULE:
338             printf("END_MODULE\n");
339             break;
340         
341         case CODE_IMPORT_MODULE:
342         {
343             int name = READ_SHORT();
344             printf("%-16s %5d '", "IMPORT_MODULE".ptr, name);
345             wrenDumpValue(fn.constants.data[name]);
346             printf("'\n");
347             break;
348         }
349         
350         case CODE_IMPORT_VARIABLE:
351         {
352             int variable = READ_SHORT();
353             printf("%-16s %5d '", "IMPORT_VARIABLE".ptr, variable);
354             wrenDumpValue(fn.constants.data[variable]);
355             printf("'\n");
356             break;
357         }
358         
359         case CODE_END:
360             printf("END\n");
361             break;
362 
363         default:
364             printf("UKNOWN! [%d]\n", bytecode[i - 1]);
365             break;
366     }
367 
368     // Return how many bytes this instruction takes, or -1 if it's an END.
369     if (code == Code.CODE_END) return -1;
370     return i - start;
371 }
372 
373 int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i)
374 {
375     return dumpInstruction(vm, fn, i, null);
376 }
377 
378 void wrenDumpCode(WrenVM* vm, ObjFn* fn)
379 {
380     printf("%s: %s\n",
381         fn.module_.name == null ? "<core>" : fn.module_.name.value.ptr,
382         fn.debug_.name);
383 
384     int i = 0;
385     int lastLine = -1;
386     for (;;)
387     {
388         int offset = dumpInstruction(vm, fn, i, &lastLine);
389         if (offset == -1) break;
390         i += offset;
391     }
392 
393     printf("\n");
394 }
395 
396 void wrenDumpStack(ObjFiber* fiber)
397 {
398     printf("(fiber %p) ", fiber);
399     for (Value* slot = fiber.stack; slot < fiber.stackTop; slot++)
400     {
401         wrenDumpValue(*slot);
402         printf(" | ");
403     }
404     printf("\n");
405 }