diff options
Diffstat (limited to 'src/bytecode/vm.h')
-rwxr-xr-x | src/bytecode/vm.h | 396 |
1 files changed, 0 insertions, 396 deletions
diff --git a/src/bytecode/vm.h b/src/bytecode/vm.h deleted file mode 100755 index d363958..0000000 --- a/src/bytecode/vm.h +++ /dev/null | |||
@@ -1,396 +0,0 @@ | |||
1 | #ifndef BDL_VM_H | ||
2 | #define BDL_VM_H | ||
3 | |||
4 | #include "types.h" | ||
5 | #include "errors.h" | ||
6 | #include "chunk.h" | ||
7 | #include "ops.h" | ||
8 | #include "debug.h" | ||
9 | #include "hashtable.h" | ||
10 | |||
11 | #define VM_FRAMES_CAP 64 | ||
12 | #define VM_STACK_CAP 1024 | ||
13 | |||
14 | typedef struct CallFrame { | ||
15 | // Current code being run. | ||
16 | Closure *closure; | ||
17 | // Return counter. | ||
18 | u8 *rp; | ||
19 | // Starting point of the locals for this procedure. | ||
20 | size_t stack_offset; | ||
21 | } CallFrame; | ||
22 | |||
23 | typedef struct VM { | ||
24 | CallFrame *frames; | ||
25 | // Stack. | ||
26 | Object *stack; | ||
27 | // Program counter. | ||
28 | u8 *pc; | ||
29 | // Global variables. | ||
30 | HashTable *globals; | ||
31 | } VM; | ||
32 | |||
33 | void vm_init(VM *vm); | ||
34 | void vm_free(VM *vm); | ||
35 | void vm_reset(VM *vm); | ||
36 | void vm_interpret(VM *vm); | ||
37 | |||
38 | void | ||
39 | vm_init(VM *vm) { | ||
40 | *vm = (VM){0}; | ||
41 | array_init(vm->frames, VM_FRAMES_CAP); | ||
42 | array_init(vm->stack, VM_STACK_CAP); | ||
43 | vm->globals = ht_init(); | ||
44 | } | ||
45 | |||
46 | void | ||
47 | vm_free(VM *vm) { | ||
48 | array_free(vm->frames); | ||
49 | array_free(vm->stack); | ||
50 | ht_free(vm->globals); | ||
51 | } | ||
52 | |||
53 | void | ||
54 | vm_reset(VM *vm) { | ||
55 | vm_free(vm); | ||
56 | vm_init(vm); | ||
57 | } | ||
58 | |||
59 | // Helper macros for a more clear VM switch. | ||
60 | #ifdef DEBUG | ||
61 | #define STACK_TRACE() \ | ||
62 | fprintf(stderr, "stack trace:\n"); \ | ||
63 | for (ssize_t i = array_size(vm->frames) - 1; i >= 0 ; i--) { \ | ||
64 | CallFrame frame = vm->frames[i]; \ | ||
65 | Chunk *chunk = frame.closure->chunk; \ | ||
66 | size_t instruction = vm->pc - chunk->code - 1; \ | ||
67 | fprintf(stderr, "\t%-4ld -> ", i); \ | ||
68 | fprintf(stderr, "%.*s",(int)array_size(chunk->name), chunk->name); \ | ||
69 | fprintf(stderr, ":%ld:%ld\n", chunk->lines[instruction].line, chunk->lines[instruction].col); \ | ||
70 | vm->pc = frame.rp; \ | ||
71 | } | ||
72 | #else | ||
73 | #define STACK_TRACE() | ||
74 | #endif | ||
75 | |||
76 | #define RUNTIME_ERROR(ERR) \ | ||
77 | error_push((Error){ \ | ||
78 | .type = ERR_TYPE_RUNTIME, \ | ||
79 | .value = (ERR), \ | ||
80 | .line = chunk->lines[vm->pc - chunk->code - 1].line, \ | ||
81 | .col = chunk->lines[vm->pc - chunk->code - 1].col, \ | ||
82 | }); \ | ||
83 | STACK_TRACE() \ | ||
84 | return | ||
85 | |||
86 | #define FIXNUM_ARITHMETIC_OP(OP) \ | ||
87 | do { \ | ||
88 | ssize_t n = AS_FIXNUM(array_pop(vm->stack)); \ | ||
89 | size_t stack_size = array_size(vm->stack) - n; \ | ||
90 | Object obj = array_peek(vm->stack, n - 1); \ | ||
91 | if (!IS_FIXNUM(obj)) { \ | ||
92 | RUNTIME_ERROR(ERR_WRONG_ARG_TYPE); \ | ||
93 | return; \ | ||
94 | } \ | ||
95 | ssize_t acc = AS_FIXNUM(obj); \ | ||
96 | while (n > 1) { \ | ||
97 | obj = array_peek(vm->stack, n - 2); \ | ||
98 | if (!IS_FIXNUM(obj)) { \ | ||
99 | RUNTIME_ERROR(ERR_WRONG_ARG_TYPE); \ | ||
100 | return; \ | ||
101 | } \ | ||
102 | ssize_t current = AS_FIXNUM(obj); \ | ||
103 | acc = acc OP current; \ | ||
104 | n--; \ | ||
105 | } \ | ||
106 | array_head(vm->stack)->size = stack_size; \ | ||
107 | array_push(vm->stack, FIXNUM_VAL(acc)); \ | ||
108 | } while (false) | ||
109 | |||
110 | #define FIXNUM_COMPARE_OP(OP) \ | ||
111 | do { \ | ||
112 | ssize_t n = AS_FIXNUM(array_pop(vm->stack)); \ | ||
113 | size_t stack_size = array_size(vm->stack) - n; \ | ||
114 | Object obj = array_peek(vm->stack, n - 1); \ | ||
115 | if (!IS_FIXNUM(obj)) { \ | ||
116 | RUNTIME_ERROR(ERR_WRONG_ARG_TYPE); \ | ||
117 | return; \ | ||
118 | } \ | ||
119 | ssize_t prev = AS_FIXNUM(obj); \ | ||
120 | bool ret = true; \ | ||
121 | while (n > 1) { \ | ||
122 | obj = array_peek(vm->stack, n - 2); \ | ||
123 | if (!IS_FIXNUM(obj)) { \ | ||
124 | RUNTIME_ERROR(ERR_WRONG_ARG_TYPE); \ | ||
125 | return; \ | ||
126 | } \ | ||
127 | ssize_t current = AS_FIXNUM(obj); \ | ||
128 | ret = ret && (prev OP current); \ | ||
129 | prev = current; \ | ||
130 | n--; \ | ||
131 | } \ | ||
132 | array_head(vm->stack)->size = stack_size; \ | ||
133 | array_push(vm->stack, BOOL_VAL(ret)); \ | ||
134 | } while (false) | ||
135 | |||
136 | #define LOGIC_OP(OP) \ | ||
137 | do { \ | ||
138 | ssize_t n = AS_FIXNUM(array_pop(vm->stack)); \ | ||
139 | size_t stack_size = array_size(vm->stack) - n; \ | ||
140 | Object obj = array_peek(vm->stack, n - 1); \ | ||
141 | bool ret = IS_TRUE(obj); \ | ||
142 | while (n > 1) { \ | ||
143 | obj = array_peek(vm->stack, n - 2); \ | ||
144 | bool current = IS_TRUE(obj); \ | ||
145 | ret = ret OP current; \ | ||
146 | n--; \ | ||
147 | } \ | ||
148 | array_head(vm->stack)->size = stack_size; \ | ||
149 | array_push(vm->stack, BOOL_VAL(ret)); \ | ||
150 | } while (false) | ||
151 | |||
152 | void | ||
153 | vm_interpret(VM *vm) { | ||
154 | CallFrame *frame = &vm->frames[0]; | ||
155 | Chunk *chunk = frame->closure->chunk; | ||
156 | vm->pc = chunk->code; | ||
157 | frame->rp = NULL; | ||
158 | |||
159 | if (chunk->code == NULL || array_size(chunk->code) == 0) { | ||
160 | error_push((Error){ | ||
161 | .type = ERR_TYPE_RUNTIME, | ||
162 | .value = ERR_EMPTY_CHUNK, | ||
163 | }); | ||
164 | return; | ||
165 | } | ||
166 | |||
167 | while (true) { | ||
168 | #ifdef DEBUG_TRACE_EXECUTION | ||
169 | printf("stack: [ "); | ||
170 | for (size_t i = 0; i < array_size(vm->stack); i++) { | ||
171 | object_display(vm->stack[i]); | ||
172 | if (i < array_size(vm->stack) - 1) { | ||
173 | printf(" | "); | ||
174 | } | ||
175 | } | ||
176 | printf(" ]\nop: "); | ||
177 | disassemble_instruction(chunk, (vm->pc - chunk->code)); | ||
178 | #endif | ||
179 | u8 instruction = *vm->pc++; | ||
180 | switch (instruction) { | ||
181 | case OP_LOCAL: { | ||
182 | ssize_t idx = AS_FIXNUM(array_pop(vm->stack)); | ||
183 | array_push(vm->stack, vm->stack[frame->stack_offset + idx]); | ||
184 | } break; | ||
185 | case OP_CONSTANT: { | ||
186 | u8 constant = *vm->pc++; | ||
187 | Object obj = chunk->constants[constant]; | ||
188 | array_push(vm->stack, obj); | ||
189 | } break; | ||
190 | case OP_CAPTURED: { | ||
191 | ssize_t idx = AS_FIXNUM(array_pop(vm->stack)); | ||
192 | array_push(vm->stack, frame->closure->values[idx]); | ||
193 | } break; | ||
194 | case OP_CAPTURE_LOCAL: { | ||
195 | // This is a closure with captured variables. We need a copy | ||
196 | // of it that lives on the heap. | ||
197 | Object proc = array_pop(vm->stack); | ||
198 | proc = make_lambda(proc.closure->chunk); | ||
199 | |||
200 | ssize_t n_captured = AS_FIXNUM(array_pop(vm->stack)); | ||
201 | for (ssize_t i = 0; i < n_captured; i++) { | ||
202 | Object value = array_pop(vm->stack); | ||
203 | array_push(proc.closure->values, value); | ||
204 | } | ||
205 | array_push(vm->stack, proc); | ||
206 | } break; | ||
207 | case OP_SET_CAPTURED: { | ||
208 | Object value = array_pop(vm->stack); | ||
209 | ssize_t idx = AS_FIXNUM(array_pop(vm->stack)); | ||
210 | frame->closure->values[idx] = value; | ||
211 | } break; | ||
212 | case OP_DEF_LOCAL: { | ||
213 | Object value = array_pop(vm->stack); | ||
214 | ssize_t idx = AS_FIXNUM(array_pop(vm->stack)); | ||
215 | vm->stack[frame->stack_offset + idx] = value; | ||
216 | } break; | ||
217 | case OP_SET_LOCAL: { | ||
218 | Object value = array_pop(vm->stack); | ||
219 | ssize_t idx = AS_FIXNUM(array_pop(vm->stack)); | ||
220 | CallFrame frame = vm->frames[array_size(vm->frames) - 1]; | ||
221 | vm->stack[frame.stack_offset + idx] = value; | ||
222 | } break; | ||
223 | case OP_DEF: { | ||
224 | Object value = array_pop(vm->stack); | ||
225 | Object name = array_pop(vm->stack); | ||
226 | ht_insert(vm->globals, name, value); | ||
227 | } break; | ||
228 | case OP_SET: { | ||
229 | Object value = array_pop(vm->stack); | ||
230 | Object name = array_pop(vm->stack); | ||
231 | Object *prev = ht_lookup(vm->globals, name); | ||
232 | if (prev == NULL) { | ||
233 | RUNTIME_ERROR(ERR_SYMBOL_NOT_FOUND); | ||
234 | return; | ||
235 | } | ||
236 | ht_insert(vm->globals, name, value); | ||
237 | } break; | ||
238 | case OP_GET: { | ||
239 | Object name = array_pop(vm->stack); | ||
240 | Object *value = ht_lookup(vm->globals, name); | ||
241 | if (value == NULL) { | ||
242 | RUNTIME_ERROR(ERR_SYMBOL_NOT_FOUND); | ||
243 | return; | ||
244 | } | ||
245 | array_push(vm->stack, *value); | ||
246 | } break; | ||
247 | case OP_SUM: { FIXNUM_ARITHMETIC_OP(+); } break; | ||
248 | case OP_SUB: { FIXNUM_ARITHMETIC_OP(-); } break; | ||
249 | case OP_MUL: { FIXNUM_ARITHMETIC_OP(*); } break; | ||
250 | case OP_DIV: { FIXNUM_ARITHMETIC_OP(/); } break; | ||
251 | case OP_MOD: { FIXNUM_ARITHMETIC_OP(%); } break; | ||
252 | case OP_NOT: { | ||
253 | Object prev = array_pop(vm->stack); | ||
254 | Object new = IS_TRUE(prev) ? FALSE_VAL : TRUE_VAL; | ||
255 | array_push(vm->stack, new); | ||
256 | } break; | ||
257 | case OP_AND: { LOGIC_OP(&&); } break; | ||
258 | case OP_OR: { LOGIC_OP(||); } break; | ||
259 | case OP_EQUAL: { FIXNUM_COMPARE_OP(==); } break; | ||
260 | case OP_LESS: { FIXNUM_COMPARE_OP(<); } break; | ||
261 | case OP_GREATER: { FIXNUM_COMPARE_OP(>); } break; | ||
262 | case OP_LESS_EQUAL: { FIXNUM_COMPARE_OP(<=); } break; | ||
263 | case OP_GREATER_EQUAL: { FIXNUM_COMPARE_OP(>=); } break; | ||
264 | case OP_JUMP: { | ||
265 | u16 a = *vm->pc++; | ||
266 | u16 b = *vm->pc++; | ||
267 | s16 offset = (a << 8) | b; | ||
268 | vm->pc += offset; | ||
269 | } break; | ||
270 | case OP_JUMP_IF_FALSE: { | ||
271 | u16 a = *vm->pc++; | ||
272 | u16 b = *vm->pc++; | ||
273 | s16 offset = (a << 8) | b; | ||
274 | if (IS_FALSE(array_pop(vm->stack))) { | ||
275 | vm->pc += offset; | ||
276 | } | ||
277 | } break; | ||
278 | case OP_DISPLAY: { | ||
279 | object_display(array_pop(vm->stack)); | ||
280 | } break; | ||
281 | case OP_PRINT: { | ||
282 | Object obj = array_pop(vm->stack); | ||
283 | if (!IS_STRING(obj)) { | ||
284 | RUNTIME_ERROR(ERR_WRONG_ARG_TYPE); | ||
285 | } | ||
286 | StringView scanner = (StringView) { | ||
287 | .start = obj.text, | ||
288 | .n = array_size(obj.text), | ||
289 | }; | ||
290 | while (scanner.n != 0) { | ||
291 | char c = sv_next(&scanner); | ||
292 | if (c == '\\' && sv_peek(&scanner) == 'n') { | ||
293 | putchar('\n'); | ||
294 | sv_next(&scanner); | ||
295 | continue; | ||
296 | } | ||
297 | if (c == '\\' && sv_peek(&scanner) == '"') { | ||
298 | putchar('"'); | ||
299 | sv_next(&scanner); | ||
300 | continue; | ||
301 | } | ||
302 | putchar(c); | ||
303 | } | ||
304 | } break; | ||
305 | case OP_NEWLINE: { | ||
306 | printf("\n"); | ||
307 | } break; | ||
308 | case OP_CALL: { | ||
309 | ssize_t n_args = AS_FIXNUM(array_pop(vm->stack)); | ||
310 | Object proc = vm->stack[array_size(vm->stack) - 1 - n_args]; | ||
311 | |||
312 | // Check the number of arguments is correct. | ||
313 | // NOTE: This is probably better handled at compilation, but for | ||
314 | // now this is simpler to implement. | ||
315 | ssize_t n_params = proc.closure->chunk->n_params; | ||
316 | ssize_t n_locals = proc.closure->chunk->n_locals - n_params; | ||
317 | if (n_args < n_params) { | ||
318 | RUNTIME_ERROR(ERR_NOT_ENOUGH_ARGS); | ||
319 | } else if (n_args > n_params) { | ||
320 | RUNTIME_ERROR(ERR_TOO_MANY_ARGS); | ||
321 | } | ||
322 | |||
323 | #ifdef DEBUG | ||
324 | disassemble_chunk(proc.closure->chunk); | ||
325 | printf("n_locals: %ld\n", n_locals); | ||
326 | printf("n_params: %ld\n", n_params); | ||
327 | #endif | ||
328 | // Tail-call optimization. | ||
329 | if (proc.closure->chunk != chunk || *vm->pc != OP_RETURN) { | ||
330 | // Prepare new call frame. | ||
331 | CallFrame new_frame = (CallFrame){ | ||
332 | .closure = proc.closure, | ||
333 | .rp = vm->pc, | ||
334 | .stack_offset = array_size(vm->stack) - n_params, | ||
335 | }; | ||
336 | array_push(vm->frames, new_frame); | ||
337 | frame = &vm->frames[array_size(vm->frames) - 1]; | ||
338 | chunk = frame->closure->chunk; | ||
339 | |||
340 | // Prepare local slots. | ||
341 | for (ssize_t i = 0; i < n_locals; i++) { | ||
342 | array_push(vm->stack, NIL_VAL); | ||
343 | } | ||
344 | } else { | ||
345 | // Bind tail-call parameters. | ||
346 | for (ssize_t i = 0; i < n_params; i++) { | ||
347 | size_t offset = n_locals + n_params - 1 - i; | ||
348 | Object obj = array_peek(vm->stack, offset); | ||
349 | vm->stack[frame->stack_offset + i] = obj; | ||
350 | } | ||
351 | |||
352 | // Reset stack size. | ||
353 | size_t offset = frame->stack_offset + n_params + n_locals; | ||
354 | array_head(vm->stack)->size = offset; | ||
355 | } | ||
356 | vm->pc = chunk->code; | ||
357 | } break; | ||
358 | case OP_RETURN: { | ||
359 | if (frame->rp == NULL) { | ||
360 | Object ret = array_pop(vm->stack); | ||
361 | if (!IS_NIL(ret)) { | ||
362 | object_display(ret); | ||
363 | printf("\n"); | ||
364 | } | ||
365 | return; | ||
366 | } | ||
367 | vm->pc = frame->rp; | ||
368 | array_head(vm->frames)->size--; | ||
369 | Object ret = array_pop(vm->stack); | ||
370 | array_head(vm->stack)->size = frame->stack_offset - 1; | ||
371 | array_push(vm->stack, ret); | ||
372 | frame = &vm->frames[array_size(vm->frames) - 1]; | ||
373 | chunk = frame->closure->chunk; | ||
374 | } break; | ||
375 | case OP_DROP: { | ||
376 | array_head(vm->stack)->size = 0; | ||
377 | } break; | ||
378 | default: { | ||
379 | RUNTIME_ERROR(ERR_NOT_IMPLEMENTED); | ||
380 | } break; | ||
381 | } | ||
382 | } | ||
383 | |||
384 | error_push((Error){ | ||
385 | .type = ERR_TYPE_RUNTIME, | ||
386 | .value = ERR_PC_OOB, | ||
387 | .line = chunk->lines[0].line, | ||
388 | .col = chunk->lines[0].col, | ||
389 | }); | ||
390 | } | ||
391 | |||
392 | #undef FIXNUM_ARITHMETIC_OP | ||
393 | #undef FIXNUM_COMPARE_OP | ||
394 | #undef LOGIC_OP | ||
395 | |||
396 | #endif // BDL_VM_H | ||