module_dis.c
1 #include <stdio.h>
2 #include <string.h>
3 
4 #include <kuroko/debug.h>
5 #include <kuroko/vm.h>
6 #include <kuroko/util.h>
7 #include <kuroko/compiler.h>
8 
9 #include "../private.h"
10 #include "../opcode_enum.h"
11 
12 #ifndef KRK_DISABLE_DEBUG
13 
14 KRK_Function(enablebreakpoint) {
15  int breakIndex;
16  if (!krk_parseArgs("i",(const char*[]){"breakpoint"}, &breakIndex)) return NONE_VAL();
17  if (krk_debug_enableBreakpoint(breakIndex))
18  return krk_runtimeError(vm.exceptions->indexError, "invalid breakpoint id");
19  return NONE_VAL();
20 }
21 
22 KRK_Function(disablebreakpoint) {
23  int breakIndex;
24  if (!krk_parseArgs("i",(const char*[]){"breakpoint"}, &breakIndex)) return NONE_VAL();
25  if (krk_debug_disableBreakpoint(breakIndex))
26  return krk_runtimeError(vm.exceptions->indexError, "invalid breakpoint id");
27  return NONE_VAL();
28 }
29 
30 KRK_Function(delbreakpoint) {
31  int breakIndex;
32  if (!krk_parseArgs("i",(const char*[]){"breakpoint"}, &breakIndex)) return NONE_VAL();
33  if (krk_debug_removeBreakpoint(breakIndex))
34  return krk_runtimeError(vm.exceptions->indexError, "invalid breakpoint id");
35  return NONE_VAL();
36 }
37 
38 KRK_Function(addbreakpoint) {
39  KrkValue func;
40  int lineNo;
41  int flags = KRK_BREAKPOINT_NORMAL;
42  if (!krk_parseArgs("Vi|i",(const char*[]){"func","lineno","flags"}, &func, &lineNo, &flags)) return NONE_VAL();
43 
44  int result;
45  if (IS_STRING(func)) {
46  result = krk_debug_addBreakpointFileLine(AS_STRING(func), lineNo, flags);
47  } else {
48  KrkCodeObject * target = NULL;
49  if (IS_CLOSURE(func)) {
50  target = AS_CLOSURE(func)->function;
51  } else if (IS_BOUND_METHOD(func) && IS_CLOSURE(OBJECT_VAL(AS_BOUND_METHOD(func)->method))) {
52  target = AS_CLOSURE(OBJECT_VAL(AS_BOUND_METHOD(func)->method))->function;
53  } else if (IS_codeobject(func)) {
54  target = AS_codeobject(func);
55  } else {
56  return TYPE_ERROR(function or method or filename,func);
57  }
58  /* Figure out what instruction this should be on */
59  size_t last = 0;
60  for (size_t i = 0; i < target->chunk.linesCount; ++i) {
61  if (target->chunk.lines[i].line > (size_t)lineNo) break;
62  if (target->chunk.lines[i].line == (size_t)lineNo) {
63  last = target->chunk.lines[i].startOffset;
64  break;
65  }
66  last = target->chunk.lines[i].startOffset;
67  }
68  result = krk_debug_addBreakpointCodeOffset(target,last,flags);
69  }
70 
71  if (result < 0)
72  return krk_runtimeError(vm.exceptions->baseException, "Could not add breakpoint.");
73 
74  return INTEGER_VAL(result);
75 }
76 
77 
81 KRK_Function(dis) {
82  KrkValue funcVal;
83  if (!krk_parseArgs("V",(const char*[]){"func"},&funcVal)) return NONE_VAL();
84 
85  if (IS_CLOSURE(funcVal)) {
86  KrkCodeObject * func = AS_CLOSURE(funcVal)->function;
87  krk_disassembleCodeObject(stdout, func, func->name ? func->name->chars : "<unnamed>");
88  } else if (IS_codeobject(funcVal)) {
89  krk_disassembleCodeObject(stdout, AS_codeobject(funcVal), AS_codeobject(funcVal)->name ? AS_codeobject(funcVal)->name->chars : "<unnamed>");
90  } else if (IS_BOUND_METHOD(funcVal)) {
91  if (AS_BOUND_METHOD(funcVal)->method->type == KRK_OBJ_CLOSURE) {
92  KrkCodeObject * func = ((KrkClosure*)AS_BOUND_METHOD(funcVal)->method)->function;
93  const char * methodName = func->name ? func->name->chars : "<unnamed>";
94  const char * typeName = IS_CLASS(AS_BOUND_METHOD(funcVal)->receiver) ? AS_CLASS(AS_BOUND_METHOD(funcVal)->receiver)->name->chars : krk_typeName(AS_BOUND_METHOD(funcVal)->receiver);
95  size_t allocSize = strlen(methodName) + strlen(typeName) + 2;
96  char * tmp = malloc(allocSize);
97  snprintf(tmp, allocSize, "%s.%s", typeName, methodName);
98  krk_disassembleCodeObject(stdout, func, tmp);
99  free(tmp);
100  } else {
101  krk_runtimeError(vm.exceptions->typeError, "Can not disassemble built-in method of '%T'", AS_BOUND_METHOD(funcVal)->receiver);
102  }
103  } else if (IS_CLASS(funcVal)) {
104  KrkValue code;
105  if (krk_tableGet(&AS_CLASS(funcVal)->methods, vm.specialMethodNames[METHOD_FUNC], &code) && IS_CLOSURE(code)) {
106  KrkCodeObject * func = AS_CLOSURE(code)->function;
107  krk_disassembleCodeObject(stdout, func, AS_CLASS(funcVal)->name->chars);
108  }
109  /* TODO Methods! */
110  } else {
111  krk_runtimeError(vm.exceptions->typeError, "Don't know how to disassemble '%T'", funcVal);
112  }
113 
114  return NONE_VAL();
115 }
116 
117 KRK_Function(build) {
118  char * code;
119  char * fileName = "<source>";
120  if (!krk_parseArgs("s|s", (const char*[]){"code","filename"}, &code, &fileName)) return NONE_VAL();
121 
122  /* Unset module */
123  krk_push(OBJECT_VAL(krk_currentThread.module));
125  krk_currentThread.module = NULL;
126  KrkCodeObject * c = krk_compile(code,fileName);
127  krk_currentThread.module = module;
128  krk_pop();
129  if (c) return OBJECT_VAL(c);
130  else return NONE_VAL();
131 }
132 
133 #define NOOP (void)0
134 #define SIMPLE(opc) case opc: size = 1; break;
135 #define CONSTANT(opc,more) case opc: { constant = chunk->code[offset + 1]; size = 2; more; break; } \
136  case opc ## _LONG: { constant = (chunk->code[offset + 1] << 16) | \
137  (chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); size = 4; more; break; }
138 #define OPERAND(opc,more) case opc: { operand = chunk->code[offset + 1]; size = 2; more; break; } \
139  case opc ## _LONG: { operand = (chunk->code[offset + 1] << 16) | \
140  (chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); size = 4; more; break; }
141 #define JUMP(opc,sign) case opc: { jump = 0 sign ((chunk->code[offset + 1] << 8) | (chunk->code[offset + 2])); \
142  size = 3; break; }
143 #define COMPLICATED(opc,more) case opc: size = 1; more; break;
144 #define OVERLONG_JUMP_MORE size = 3; jump = (chunk->code[offset + 1] << 8) | (chunk->code[offset + 2])
145 #define CLOSURE_MORE \
146  KrkCodeObject * function = AS_codeobject(chunk->constants.values[constant]); \
147  size_t baseOffset = offset; \
148  for (size_t j = 0; j < function->upvalueCount; ++j) { \
149  int isLocal = chunk->code[baseOffset++ + size]; \
150  baseOffset++; \
151  if (isLocal & 2) { \
152  baseOffset += 2; \
153  } \
154  } \
155  size += baseOffset - offset;
156 #define EXPAND_ARGS_MORE
157 #define FORMAT_VALUE_MORE
158 #define LOCAL_MORE local = operand;
159 static KrkValue _examineInternal(KrkCodeObject* func) {
160  KrkValue output = krk_list_of(0,NULL,0);
161  krk_push(output);
162 
163  KrkChunk * chunk = &func->chunk;
164  size_t offset = 0;
165  while (offset < chunk->count) {
166  uint8_t opcode = chunk->code[offset];
167  size_t size = 0;
168  ssize_t constant = -1;
169  ssize_t jump = 0;
170  ssize_t operand = -1;
171  ssize_t local = -1;
172  switch (opcode) {
173 #include "opcodes.h"
174  }
175 
176  KrkTuple * newTuple = krk_newTuple(3);
177  krk_push(OBJECT_VAL(newTuple));
178  newTuple->values.values[newTuple->values.count++] = INTEGER_VAL(opcode);
179  newTuple->values.values[newTuple->values.count++] = INTEGER_VAL(size);
180  if (constant != -1) {
181  newTuple->values.values[newTuple->values.count++] = chunk->constants.values[constant];
182  } else if (jump != 0) {
183  newTuple->values.values[newTuple->values.count++] = INTEGER_VAL(jump);
184  } else if (local != -1) {
185  newTuple->values.values[newTuple->values.count++] = INTEGER_VAL(operand); /* Just in case */
186  for (size_t i = 0; i < func->localNameCount; ++i) {
187  if (func->localNames[i].id == (size_t)local && func->localNames[i].birthday <= offset && func->localNames[i].deathday >= offset) {
188  newTuple->values.values[newTuple->values.count-1] = OBJECT_VAL(func->localNames[i].name);
189  break;
190  }
191  }
192  } else if (operand != -1) {
193  newTuple->values.values[newTuple->values.count++] = INTEGER_VAL(operand);
194  } else {
195  newTuple->values.values[newTuple->values.count++] = NONE_VAL();
196  }
197  krk_writeValueArray(AS_LIST(output), krk_peek(0));
198  krk_pop();
199 
200  if (size == 0) {
201  abort();
202  }
203 
204  offset += size;
205  }
206 
207  return krk_pop();
208 }
209 KRK_Function(examine) {
210  KrkCodeObject * func;
211  if (!krk_parseArgs("O!",(const char*[]){"func"}, KRK_BASE_CLASS(codeobject), &func)) return NONE_VAL();
212  return _examineInternal(func);
213 }
214 
215 #undef SIMPLE
216 #undef OPERANDB
217 #undef OPERAND
218 #undef CONSTANT
219 #undef JUMP
220 #undef COMPLICATED
221 #undef OVERLONG_JUMP_MORE
222 #undef CLOSURE_MORE
223 #undef LOCAL_MORE
224 #undef EXPAND_ARGS_MORE
225 #undef FORMAT_VALUE_MORE
226 
227 KRK_Function(ip_to_expression) {
228  KrkValue func;
229  size_t ip;
230 
231  if (!krk_parseArgs("VN",(const char*[]){"func","ip"}, &func, &ip)) return NONE_VAL();
232 
233  KrkCodeObject * actual;
234 
235  if (IS_CLOSURE(func)) actual = AS_CLOSURE(func)->function;
236  else if (IS_codeobject(func)) actual = AS_codeobject(func);
237  else if (IS_BOUND_METHOD(func) && IS_CLOSURE(OBJECT_VAL(AS_BOUND_METHOD(func)->method))) actual = ((KrkClosure*)AS_BOUND_METHOD(func)->method)->function;
238  else return krk_runtimeError(vm.exceptions->typeError, "func must be a managed function, method, or codeobject, not '%T'", func);
239 
240  int lineNo = krk_lineNumber(&actual->chunk, ip);
241  uint8_t start, midStart, midEnd, end;
242 
243  if (krk_debug_expressionUnderline(actual, &start, &midStart, &midEnd, &end, ip)) {
244  KrkTuple * out = krk_newTuple(5);
245  krk_push(OBJECT_VAL(out));
246 
247  out->values.values[out->values.count++] = INTEGER_VAL(lineNo);
248  out->values.values[out->values.count++] = INTEGER_VAL(start);
249  out->values.values[out->values.count++] = INTEGER_VAL(midStart);
250  out->values.values[out->values.count++] = INTEGER_VAL(midEnd);
251  out->values.values[out->values.count++] = INTEGER_VAL(end);
252 
253  return krk_pop();
254  }
255 
256  return NONE_VAL();
257 }
258 
259 #endif
260 
261 KRK_Module(dis) {
262 #ifndef KRK_DISABLE_DEBUG
263  KRK_DOC(module,
264  "@brief Provides tools for disassembling bytecode.\n\n"
265  "### Code Disassembly in Kuroko\n\n"
266  "The @c dis module contains functions for dealing with _code objects_ which "
267  "represent the compiled bytecode of a Kuroko function. The bytecode compilation "
268  "process is entirely static and bytecode analysis can be performed without calling "
269  "into the VM to run dynamic code.\n\n"
270  "### Debugger Breakpoints\n\n"
271  "Kuroko interpreters can provide a debugger hook through the C API's "
272  "@ref krk_debug_registerCallback() function. Breakpoints can be managed both "
273  "from the C API and from this module's @ref addbreakpoint, @ref delbreakpoint, "
274  "@ref enablebreakpoint, and @ref disablebreakpoint methods."
275  );
276 
277  KRK_DOC(BIND_FUNC(module, dis),
278  "@brief Disassemble an object.\n"
279  "@arguments obj\n\n"
280  "Dumps a disassembly of the bytecode in the code object associated with @p obj. "
281  "If @p obj can not be disassembled, a @ref TypeError is raised.");
282 
283  KRK_DOC(BIND_FUNC(module, build),
284  "@brief Compile a string to a code object.\n"
285  "@arguments code\n\n"
286  "Compiles the string @p code and returns a code object. If a syntax "
287  "error is encountered, it will be raised.");
288 
289  KRK_DOC(BIND_FUNC(module, examine),
290  "@brief Convert a code object to a list of instructions.\n"
291  "@arguments func\n\n"
292  "Examines the code object @p func and returns a list representation of its instructions. "
293  "Each instruction entry is a tuple of the opcode, total instruction size in bytes, and "
294  "the operand of the argument, either as an integer for jump offsets, the actual value for "
295  "constant operands, or the name of a local or global variable if available.");
296 
297  KRK_DOC(BIND_FUNC(module, addbreakpoint),
298  "@brief Attach a breakpoint to a code object.\n"
299  "@arguments func, line\n\n"
300  "@p func may be a filename string, or a function, method, or code object. Returns "
301  "the new breakpoint index, or raises @ref Exception if a breakpoint code not be added.");
302 
303  KRK_DOC(BIND_FUNC(module, delbreakpoint),
304  "@brief Delete a breakpoint.\n"
305  "@arguments handle\n\n"
306  "Delete the breakpoint specified by @p handle, disabling it if it was enabled. "
307  "May raise @ref IndexError if @p handle is not a valid breakpoint handle.");
308 
309  KRK_DOC(BIND_FUNC(module, enablebreakpoint),
310  "@brief Enable a breakpoint.\n"
311  "@arguments handle\n\n"
312  "Enable the breakpoint specified by @p handle. May raise @ref IndexError if "
313  "@p handle is not a valid breakpoint handle.");
314 
315  KRK_DOC(BIND_FUNC(module, disablebreakpoint),
316  "@brief Disable a breakpoint.\n"
317  "@arguments handle\n\n"
318  "Disable the breakpoint specified by @p handle. May raise @ref IndexError if "
319  "@p handle is not a valid breakpoint handle.");
320 
321  KRK_DOC(BIND_FUNC(module, ip_to_expression),
322  "@brief Map an IP in a codeobject or function to an expression span.\n"
323  "@arguments func,ip\n\n"
324  "For various reasons, the instruction pointer @p ip must be the last byte of an opcode.");
325 
326  krk_attachNamedValue(&module->fields, "BREAKPOINT_ONCE", INTEGER_VAL(KRK_BREAKPOINT_ONCE));
327  krk_attachNamedValue(&module->fields, "BREAKPOINT_REPEAT", INTEGER_VAL(KRK_BREAKPOINT_REPEAT));
328 
329 #define OPCODE(opc) krk_attachNamedValue(&module->fields, #opc, INTEGER_VAL(opc));
330 #define SIMPLE(opc) OPCODE(opc)
331 #define CONSTANT(opc,more) OPCODE(opc) OPCODE(opc ## _LONG)
332 #define OPERAND(opc,more) OPCODE(opc) OPCODE(opc ## _LONG)
333 #define JUMP(opc,sign) OPCODE(opc)
334 #define COMPLICATED(opc,more) OPCODE(opc)
335 #include "opcodes.h"
336 #undef SIMPLE
337 #undef OPERANDB
338 #undef OPERAND
339 #undef CONSTANT
340 #undef JUMP
341 #undef COMPLICATED
342 
343  if (runAs && !strcmp(runAs->chars,"__main__")) {
344  /* Force `dis` into the module table early */
345  krk_attachNamedObject(&vm.modules, "dis", (KrkObj*)module);
346  /* Start executing additional code */
347  krk_startModule("_dis");
349  "import dis\n"
350  "def disrec(code, seen):\n"
351  " let next = [code]\n"
352  " while next:\n"
353  " let co = next[0]\n"
354  " next = next[1:]\n"
355  " dis.dis(co)\n"
356  " for inst,size,operand in dis.examine(co):\n"
357  " if isinstance(operand,codeobject) and operand not in seen and operand not in next:\n"
358  " next.append(operand)\n"
359  " if next:\n"
360  " print()\n"
361  "import kuroko\n"
362  "if (len(kuroko.argv) < 2):\n"
363  " print(\"Usage: kuroko -m dis FILE\")\n"
364  " return 1\n"
365  "import fileio\n"
366  "for file in kuroko.argv[1:]:\n"
367  " with fileio.open(file,'r') as f:\n"
368  " let result = dis.build(f.read(), file)\n"
369  " disrec(result,set())\n",
370  "_dis"
371  );
372  }
373 #else
374  krk_runtimeError(vm.exceptions->notImplementedError, "debugger support is disabled");
375 #endif
376 }
KrkCodeObject * krk_compile(const char *src, const char *fileName)
Compile a source string to bytecode.
Definition: compiler.c:4129
Exported methods for the source compiler.
Functions for debugging bytecode execution.
#define KRK_BREAKPOINT_ONCE
Definition: debug.h:232
int krk_debug_addBreakpointCodeOffset(KrkCodeObject *codeObject, size_t offset, int flags)
Add a breakpoint to the given code object.
Definition: debug.c:469
int krk_debug_enableBreakpoint(int breakpointId)
Enable a breakpoint.
Definition: debug.c:535
int krk_debug_removeBreakpoint(int breakpointId)
Remove a breakpoint from the breakpoint table.
Definition: debug.c:553
int krk_debug_disableBreakpoint(int breakpointId)
Disable a breakpoint.
Definition: debug.c:542
int krk_debug_addBreakpointFileLine(KrkString *filename, size_t line, int flags)
Add a breakpoint to the given line of a file.
Definition: debug.c:495
#define KRK_BREAKPOINT_REPEAT
Definition: debug.h:233
void krk_disassembleCodeObject(FILE *f, KrkCodeObject *func, const char *name)
Print a disassembly of 'func' to the stream 'f'.
Definition: debug.c:163
#define KRK_BREAKPOINT_NORMAL
Definition: debug.h:231
KrkValue krk_runtimeError(KrkClass *type, const char *fmt,...)
Produce and raise an exception with a formatted message.
Definition: exceptions.c:460
KRK_Module(socket)
Opcode chunk of a code object.
Definition: chunk.h:36
size_t krk_lineNumber(KrkChunk *chunk, size_t offset)
Obtain the line number for a byte offset into a bytecode chunk.
Definition: chunk.c:75
Function object.
Definition: object.h:195
Code object.
Definition: object.h:163
KrkChunk chunk
Bytecode data.
Definition: object.h:170
KrkLocalEntry * localNames
Stores the names of local variables used in the function, for debugging.
Definition: object.h:177
int krk_debug_expressionUnderline(const KrkCodeObject *codeobject, uint8_t *start, uint8_t *midStart, uint8_t *endStart, uint8_t *end, size_t instruction)
Extract expression mapping from chunk.
Definition: debug.c:726
size_t localNameCount
Number of entries in localNames.
Definition: object.h:176
KrkString * name
Name of the function.
Definition: object.h:171
An object of a class.
Definition: object.h:281
KrkTable fields
Attributes table.
Definition: object.h:284
KrkValue krk_list_of(int argc, const KrkValue argv[], int hasKw)
Create a list object.
Definition: obj_list.c:30
size_t birthday
Instruction offset that this local name became valid on.
Definition: object.h:131
KrkString * name
Name of the local.
Definition: object.h:133
size_t id
Local ID as used by opcodes; offset from the frame's stack base.
Definition: object.h:130
size_t deathday
Instruction offset that this local name becomes invalid on.
Definition: object.h:132
The most basic object type.
Definition: object.h:41
char * chars
UTF8 canonical data.
Definition: object.h:97
int krk_tableGet(KrkTable *table, KrkValue key, KrkValue *value)
Obtain the value associated with a key in a table.
Definition: table.c:211
void krk_attachNamedObject(KrkTable *table, const char name[], KrkObj *obj)
Attach an object to an attribute table.
Definition: vm.c:808
void krk_attachNamedValue(KrkTable *table, const char name[], KrkValue obj)
Attach a value to an attribute table.
Definition: vm.c:794
KrkInstance * module
Definition: vm.h:163
Immutable sequence of arbitrary values.
Definition: object.h:323
KrkValueArray values
Stores the length, capacity, and actual values of the tuple.
Definition: object.h:325
KrkTuple * krk_newTuple(size_t length)
Create a new tuple.
Definition: object.c:357
KrkValue * values
Definition: value.h:78
void krk_writeValueArray(KrkValueArray *array, KrkValue value)
Add a value to a value array.
Definition: value.c:17
size_t count
Definition: value.h:77
Stack reference or primative value.
const char * krk_typeName(KrkValue value)
Get the name of the type of a value.
Definition: vm.c:984
Utilities for creating native bindings.
#define krk_parseArgs(f, n,...)
Parse arguments to a function while accepting keyword arguments.
Definition: util.h:360
#define KRK_DOC(thing, text)
Attach documentation to a thing of various types.
Definition: util.h:304
Core API for the bytecode virtual machine.
krk_threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
#define vm
Convenience macro for namespacing.
Definition: vm.h:257
KrkValue krk_pop(void)
Pop the top of the stack.
Definition: vm.c:131
KrkValue krk_interpret(const char *src, const char *fromFile)
Compile and execute a source code input.
Definition: vm.c:3219
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:118
KrkInstance * krk_startModule(const char *name)
Set up a new module object in the current thread.
Definition: vm.c:3209
KrkValue krk_peek(int distance)
Peek down from the top of the stack.
Definition: vm.c:139