1#include "vm.h"
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7void bytecode_init(Bytecode *bc) {
8 bc->words = NULL;
9 bc->count = 0;
10 bc->capacity = 0;
11}
12
13void bytecode_free(Bytecode *bc) {
14 free(bc->words);
15 bc->words = NULL;
16 bc->count = 0;
17 bc->capacity = 0;
18}
19
20VmError bytecode_emit(Bytecode *bc, int64_t word) {
21 if (bc->count == bc->capacity) {
22 size_t new_capacity = bc->capacity == 0 ? 64 : bc->capacity * 2;
23 int64_t *new_words = (int64_t *)realloc(bc->words, new_capacity * sizeof(*new_words));
24 if (!new_words) {
25 return VM_ERR_OUT_OF_MEMORY;
26 }
27 bc->words = new_words;
28 bc->capacity = new_capacity;
29 }
30 bc->words[bc->count++] = word;
31 return VM_OK;
32}
33
34static int default_output(int64_t value, void *userdata) {
35 (void)userdata;
36 return printf("%lld\n", (long long)value) < 0 ? -1 : 0;
37}
38
39void vm_init(VM *vm, const Bytecode *code) {
40 memset(vm, 0, sizeof(*vm));
41 vm->code = code;
42 vm->output = default_output;
43 vm->frame_count = 1;
44}
45
46void vm_set_output(VM *vm, VmOutputFn output, void *userdata) {
47 vm->output = output ? output : default_output;
48 vm->output_userdata = userdata;
49}
50
51static VmError push(VM *vm, int64_t value) {
52 if (vm->sp >= VM_STACK_MAX) {
53 return VM_ERR_STACK_OVERFLOW;
54 }
55 vm->stack[vm->sp++] = value;
56 return VM_OK;
57}
58
59static VmError pop(VM *vm, int64_t *out) {
60 if (vm->sp == 0) {
61 return VM_ERR_STACK_UNDERFLOW;
62 }
63 *out = vm->stack[--vm->sp];
64 return VM_OK;
65}
66
67static VmError read_operand(VM *vm, int64_t *out) {
68 if (vm->ip >= vm->code->count) {
69 return VM_ERR_TRUNCATED_INSTRUCTION;
70 }
71 *out = vm->code->words[vm->ip++];
72 return VM_OK;
73}
74
−static int is_valid_opcode(int64_t opcode) {
− return opcode >= OP_PUSH && opcode <= OP_HALT;
75static int opcode_operand_count(int64_t opcode, size_t *count) {
76 if (!is_valid_opcode(opcode)) {
77 return 0;
78 }
79 switch ((VmOpcode)opcode) {
80 case OP_PUSH:
81 case OP_JMP:
82 case OP_JZ:
83 case OP_JNZ:
84 case OP_CALL:
85 case OP_LOAD:
86 case OP_STORE:
87 *count = 1;
88 return 1;
89 case OP_POP:
90 case OP_DUP:
91 case OP_SWAP:
92 case OP_ADD:
93 case OP_SUB:
94 case OP_MUL:
95 case OP_DIV:
96 case OP_MOD:
97 case OP_NEG:
98 case OP_EQ:
99 case OP_LT:
100 case OP_GT:
101 case OP_RET:
102 case OP_PRINT:
103 case OP_HALT:
104 *count = 0;
105 return 1;
106 }
107 return 0;
108}
109
110static int is_instruction_boundary(const VM *vm, size_t target) {
111 size_t pos = 0;
112 while (pos < vm->code->count) {
113 if (pos == target) {
114 return 1;
115 }
116 size_t operands = 0;
117 if (!opcode_operand_count(vm->code->words[pos], &operands)) {
118 return 0;
119 }
120 if (pos + 1 + operands > vm->code->count) {
121 return 0;
122 }
123 pos += 1 + operands;
124 }
125 return 0;
126}
127
128static VmError validate_target(const VM *vm, int64_t target) {
129 if (target < 0 || (uint64_t)target >= vm->code->count) {
130 return VM_ERR_BAD_JUMP;
131 }
− if (!is_valid_opcode(vm->code->words[target])) {
132 if (!is_instruction_boundary(vm, (size_t)target)) {
133 return VM_ERR_BAD_JUMP;
134 }
135 return VM_OK;
136}
137
138VmError vm_run(VM *vm) {
139 if (!vm->code) {
140 vm->last_error = VM_ERR_BAD_OPERAND;
141 return vm->last_error;
142 }
143
144 VmError err = VM_OK;
145 for (;;) {
146 err = VM_OK;
147 if (vm->ip >= vm->code->count) {
148 vm->last_error = VM_ERR_NO_HALT;
149 return vm->last_error;
150 }
151
152 int64_t raw_opcode = vm->code->words[vm->ip++];
153 if (!is_valid_opcode(raw_opcode)) {
154 vm->last_error = VM_ERR_BAD_OPCODE;
155 return vm->last_error;
156 }
157
158 VmOpcode opcode = (VmOpcode)raw_opcode;
159 int64_t a = 0;
160 int64_t b = 0;
161 int64_t operand = 0;
162
163 switch (opcode) {
164 case OP_PUSH:
165 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
166 if ((err = push(vm, operand)) != VM_OK) goto fail;
167 break;
168
169 case OP_POP:
170 if ((err = pop(vm, &a)) != VM_OK) goto fail;
171 break;
172
173 case OP_DUP:
174 if (vm->sp == 0) {
175 err = VM_ERR_STACK_UNDERFLOW;
176 goto fail;
177 }
178 if ((err = push(vm, vm->stack[vm->sp - 1])) != VM_OK) goto fail;
179 break;
180
181 case OP_SWAP:
182 if (vm->sp < 2) {
183 err = VM_ERR_STACK_UNDERFLOW;
184 goto fail;
185 }
186 a = vm->stack[vm->sp - 1];
187 vm->stack[vm->sp - 1] = vm->stack[vm->sp - 2];
188 vm->stack[vm->sp - 2] = a;
189 break;
190
191 case OP_ADD:
192 if ((err = pop(vm, &b)) != VM_OK) goto fail;
193 if ((err = pop(vm, &a)) != VM_OK) goto fail;
194 if ((err = push(vm, a + b)) != VM_OK) goto fail;
195 break;
196
197 case OP_SUB:
198 if ((err = pop(vm, &b)) != VM_OK) goto fail;
199 if ((err = pop(vm, &a)) != VM_OK) goto fail;
200 if ((err = push(vm, a - b)) != VM_OK) goto fail;
201 break;
202
203 case OP_MUL:
204 if ((err = pop(vm, &b)) != VM_OK) goto fail;
205 if ((err = pop(vm, &a)) != VM_OK) goto fail;
206 if ((err = push(vm, a * b)) != VM_OK) goto fail;
207 break;
208
209 case OP_DIV:
210 if ((err = pop(vm, &b)) != VM_OK) goto fail;
211 if ((err = pop(vm, &a)) != VM_OK) goto fail;
212 if (b == 0) {
213 err = VM_ERR_DIVISION_BY_ZERO;
214 goto fail;
215 }
216 if ((err = push(vm, a / b)) != VM_OK) goto fail;
217 break;
218
219 case OP_MOD:
220 if ((err = pop(vm, &b)) != VM_OK) goto fail;
221 if ((err = pop(vm, &a)) != VM_OK) goto fail;
222 if (b == 0) {
223 err = VM_ERR_DIVISION_BY_ZERO;
224 goto fail;
225 }
226 if ((err = push(vm, a % b)) != VM_OK) goto fail;
227 break;
228
229 case OP_NEG:
230 if ((err = pop(vm, &a)) != VM_OK) goto fail;
231 if ((err = push(vm, -a)) != VM_OK) goto fail;
232 break;
233
234 case OP_EQ:
235 if ((err = pop(vm, &b)) != VM_OK) goto fail;
236 if ((err = pop(vm, &a)) != VM_OK) goto fail;
237 if ((err = push(vm, a == b ? 1 : 0)) != VM_OK) goto fail;
238 break;
239
240 case OP_LT:
241 if ((err = pop(vm, &b)) != VM_OK) goto fail;
242 if ((err = pop(vm, &a)) != VM_OK) goto fail;
243 if ((err = push(vm, a < b ? 1 : 0)) != VM_OK) goto fail;
244 break;
245
246 case OP_GT:
247 if ((err = pop(vm, &b)) != VM_OK) goto fail;
248 if ((err = pop(vm, &a)) != VM_OK) goto fail;
249 if ((err = push(vm, a > b ? 1 : 0)) != VM_OK) goto fail;
250 break;
251
252 case OP_JMP:
253 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
254 if ((err = validate_target(vm, operand)) != VM_OK) goto fail;
255 vm->ip = (size_t)operand;
256 break;
257
258 case OP_JZ:
259 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
260 if ((err = pop(vm, &a)) != VM_OK) goto fail;
261 if (a == 0) {
262 if ((err = validate_target(vm, operand)) != VM_OK) goto fail;
263 vm->ip = (size_t)operand;
264 }
265 break;
266
267 case OP_JNZ:
268 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
269 if ((err = pop(vm, &a)) != VM_OK) goto fail;
270 if (a != 0) {
271 if ((err = validate_target(vm, operand)) != VM_OK) goto fail;
272 vm->ip = (size_t)operand;
273 }
274 break;
275
276 case OP_CALL:
277 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
278 if ((err = validate_target(vm, operand)) != VM_OK) goto fail;
279 if (vm->frame_count >= VM_CALL_STACK_MAX) {
280 err = VM_ERR_CALL_OVERFLOW;
281 goto fail;
282 }
283 vm->frames[vm->frame_count].return_ip = vm->ip;
284 memset(vm->frames[vm->frame_count].locals, 0, sizeof(vm->frames[vm->frame_count].locals));
285 vm->frame_count++;
286 vm->ip = (size_t)operand;
287 break;
288
289 case OP_RET:
290 if (vm->frame_count <= 1) {
291 err = VM_ERR_CALL_UNDERFLOW;
292 goto fail;
293 }
294 vm->frame_count--;
295 vm->ip = vm->frames[vm->frame_count].return_ip;
296 break;
297
298 case OP_LOAD:
299 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
300 if (operand < 0 || operand >= VM_LOCALS_PER_FRAME) {
301 err = VM_ERR_BAD_LOCAL;
302 goto fail;
303 }
304 if ((err = push(vm, vm->frames[vm->frame_count - 1].locals[operand])) != VM_OK) goto fail;
305 break;
306
307 case OP_STORE:
308 if ((err = read_operand(vm, &operand)) != VM_OK) goto fail;
309 if (operand < 0 || operand >= VM_LOCALS_PER_FRAME) {
310 err = VM_ERR_BAD_LOCAL;
311 goto fail;
312 }
313 if ((err = pop(vm, &a)) != VM_OK) goto fail;
314 vm->frames[vm->frame_count - 1].locals[operand] = a;
315 break;
316
317 case OP_PRINT:
318 if ((err = pop(vm, &a)) != VM_OK) goto fail;
319 if (vm->output(a, vm->output_userdata) != 0) {
320 err = VM_ERR_OUTPUT;
321 goto fail;
322 }
323 break;
324
325 case OP_HALT:
326 vm->last_error = VM_OK;
327 return VM_OK;
328 }
329 }
330
331fail:
332 vm->last_error = err;
333 return err;
334}
335
336const char *vm_error_string(VmError error) {
337 switch (error) {
338 case VM_OK: return "ok";
339 case VM_ERR_STACK_UNDERFLOW: return "stack underflow";
340 case VM_ERR_STACK_OVERFLOW: return "stack overflow";
341 case VM_ERR_CALL_UNDERFLOW: return "call stack underflow";
342 case VM_ERR_CALL_OVERFLOW: return "call stack overflow";
343 case VM_ERR_DIVISION_BY_ZERO: return "division by zero";
344 case VM_ERR_BAD_OPCODE: return "bad opcode";
345 case VM_ERR_BAD_OPERAND: return "bad operand";
346 case VM_ERR_BAD_LOCAL: return "bad local";
347 case VM_ERR_BAD_JUMP: return "out-of-range jump";
348 case VM_ERR_TRUNCATED_INSTRUCTION: return "truncated instruction";
349 case VM_ERR_NO_HALT: return "program ended without halt";
350 case VM_ERR_OUT_OF_MEMORY: return "out of memory";
351 case VM_ERR_OUTPUT: return "output error";
352 default: return "unknown error";
353 }
354}
355
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.