1#include "vm.h"
2
3#include <inttypes.h>
4#include <stdlib.h>
5#include <string.h>
6
7typedef struct VMFileOutput {
8 FILE *stream;
9} VMFileOutput;
10
11static VMError vm_fail(VM *vm, VMError error, size_t pc)
12{
13 if (vm != NULL) {
14 vm->error = error;
15 vm->error_pc = pc;
16 vm->halted = 0;
17 }
18 return error;
19}
20
21static int opcode_is_valid(uint8_t opcode)
22{
23 return opcode >= VM_OP_PUSH && opcode <= VM_OP_HALT;
24}
25
26int vm_opcode_has_operand(uint8_t opcode)
27{
28 switch (opcode) {
29 case VM_OP_PUSH:
30 case VM_OP_JMP:
31 case VM_OP_JZ:
32 case VM_OP_JNZ:
33 case VM_OP_CALL:
34 case VM_OP_LOAD:
35 case VM_OP_STORE:
36 return 1;
37 default:
38 return 0;
39 }
40}
41
42static size_t opcode_width(uint8_t opcode)
43{
44 if (!opcode_is_valid(opcode)) {
45 return 0;
46 }
47 return VM_OPCODE_SIZE + (vm_opcode_has_operand(opcode) ? VM_OPERAND_SIZE : 0);
48}
49
50static int32_t read_i32_le(const uint8_t *bytes)
51{
52 uint32_t value = (uint32_t)bytes[0]
53 | ((uint32_t)bytes[1] << 8)
54 | ((uint32_t)bytes[2] << 16)
55 | ((uint32_t)bytes[3] << 24);
56
57
58 if (value <= INT32_MAX) {
59 return (int32_t)value;
60 }
61 return (int32_t)((int64_t)value - ((int64_t)UINT32_MAX + 1));
62}
63
64
65
66
67
68static VMError validate_program(VM *vm, const uint8_t *code, size_t code_len)
69{
70 uint8_t *starts;
71 size_t pc;
72
73 if (code == NULL && code_len != 0) {
74 return vm_fail(vm, VM_ERR_INVALID_ARGUMENT, 0);
75 }
76 if (code_len == 0) {
77 return VM_OK;
78 }
79
80 starts = calloc(code_len, sizeof(*starts));
81 if (starts == NULL) {
82 return vm_fail(vm, VM_ERR_ALLOCATION, 0);
83 }
84
85 pc = 0;
86 while (pc < code_len) {
87 size_t width;
88
89 starts[pc] = 1;
90 width = opcode_width(code[pc]);
91 if (width == 0) {
92 free(starts);
93 return vm_fail(vm, VM_ERR_BAD_OPCODE, pc);
94 }
95 if (code_len - pc < width) {
96 free(starts);
97 return vm_fail(vm, VM_ERR_TRUNCATED_INSTRUCTION, pc);
98 }
99 pc += width;
100 }
101
102 pc = 0;
103 while (pc < code_len) {
104 uint8_t opcode = code[pc];
105 size_t width = opcode_width(opcode);
106
107 if (vm_opcode_has_operand(opcode)) {
108 int32_t operand = read_i32_le(code + pc + VM_OPCODE_SIZE);
109
110 if (opcode == VM_OP_JMP || opcode == VM_OP_JZ
111 || opcode == VM_OP_JNZ || opcode == VM_OP_CALL) {
112 if (operand < 0 || (size_t)operand >= code_len
113 || starts[(size_t)operand] == 0) {
114 free(starts);
115 return vm_fail(vm, VM_ERR_INVALID_JUMP, pc);
116 }
117 } else if ((opcode == VM_OP_LOAD || opcode == VM_OP_STORE)
118 && (operand < 0 || operand >= VM_LOCALS_PER_FRAME)) {
119 free(starts);
120 return vm_fail(vm, VM_ERR_INVALID_LOCAL, pc);
121 }
122 }
123 pc += width;
124 }
125
126 free(starts);
127 return VM_OK;
128}
129
130static int add_overflows_i64(int64_t left, int64_t right, int64_t *result)
131{
132 if ((right > 0 && left > INT64_MAX - right)
133 || (right < 0 && left < INT64_MIN - right)) {
134 return 1;
135 }
136 *result = left + right;
137 return 0;
138}
139
140static int sub_overflows_i64(int64_t left, int64_t right, int64_t *result)
141{
142 if ((right > 0 && left < INT64_MIN + right)
143 || (right < 0 && left > INT64_MAX + right)) {
144 return 1;
145 }
146 *result = left - right;
147 return 0;
148}
149
150static int mul_overflows_i64(int64_t left, int64_t right, int64_t *result)
151{
152 if (left == 0 || right == 0) {
153 *result = 0;
154 return 0;
155 }
156 if (left == -1) {
157 if (right == INT64_MIN) {
158 return 1;
159 }
160 *result = -right;
161 return 0;
162 }
163 if (right == -1) {
164 if (left == INT64_MIN) {
165 return 1;
166 }
167 *result = -left;
168 return 0;
169 }
170
171 if (left > 0) {
172 if (right > 0) {
173 if (left > INT64_MAX / right) {
174 return 1;
175 }
176 } else if (right < INT64_MIN / left) {
177 return 1;
178 }
179 } else if (right > 0) {
180 if (left < INT64_MIN / right) {
181 return 1;
182 }
183 } else if (left < INT64_MAX / right) {
184
185 return 1;
186 }
187
188 *result = left * right;
189 return 0;
190}
191
192static int file_output(void *user, int64_t value)
193{
194 VMFileOutput *output = user;
195
196 return fprintf(output->stream, "%" PRId64 "\n", value) < 0 ? -1 : 0;
197}
198
199int vm_init(VM *vm, size_t max_stack, size_t max_call_frames)
200{
201 if (vm == NULL) {
202 return -1;
203 }
204
205 memset(vm, 0, sizeof(*vm));
206 if (max_stack == 0) {
207 max_stack = VM_DEFAULT_STACK_LIMIT;
208 }
209 if (max_call_frames == 0) {
210 max_call_frames = VM_DEFAULT_CALL_STACK_LIMIT;
211 }
212 if (max_stack > SIZE_MAX / sizeof(*vm->stack)
213 || max_call_frames > SIZE_MAX / sizeof(*vm->frames)) {
214 vm->error = VM_ERR_ALLOCATION;
215 return -1;
216 }
217
218 vm->stack = calloc(max_stack, sizeof(*vm->stack));
219 vm->frames = calloc(max_call_frames, sizeof(*vm->frames));
220 if (vm->stack == NULL || vm->frames == NULL) {
221 free(vm->stack);
222 free(vm->frames);
223 vm->stack = NULL;
224 vm->frames = NULL;
225 vm->error = VM_ERR_ALLOCATION;
226 return -1;
227 }
228
229 vm->stack_capacity = max_stack;
230 vm->frame_capacity = max_call_frames;
231 vm->max_stack = max_stack;
232 vm->max_call_frames = max_call_frames;
233 vm->error = VM_OK;
234 return 0;
235}
236
237void vm_free(VM *vm)
238{
239 if (vm == NULL) {
240 return;
241 }
242 free(vm->stack);
243 free(vm->frames);
244 memset(vm, 0, sizeof(*vm));
245}
246
247void vm_reset(VM *vm)
248{
249 if (vm == NULL) {
250 return;
251 }
252 vm->stack_size = 0;
253 vm->frame_count = 0;
254 vm->error = VM_OK;
255 vm->error_pc = 0;
256 vm->halted = 0;
257}
258
259VMError vm_run_callback(VM *vm, const uint8_t *code, size_t code_len,
260 VMOutputFn output, void *output_user)
261{
262 VMError validation_error;
263 size_t pc = 0;
264
265 if (vm == NULL) {
266 return VM_ERR_INVALID_ARGUMENT;
267 }
268 vm_reset(vm);
269 if (vm->stack == NULL || vm->frames == NULL || vm->stack_capacity == 0
270 || vm->frame_capacity == 0 || vm->max_stack == 0
271 || vm->max_call_frames == 0) {
272 return vm_fail(vm, VM_ERR_INVALID_ARGUMENT, 0);
273 }
274
275 validation_error = validate_program(vm, code, code_len);
276 if (validation_error != VM_OK) {
277 return validation_error;
278 }
279
280
281 memset(&vm->frames[0], 0, sizeof(vm->frames[0]));
282 vm->frames[0].return_pc = SIZE_MAX;
283 vm->frame_count = 1;
284
285 while (pc < code_len) {
286 size_t instruction_pc = pc;
287 uint8_t opcode = code[pc++];
288 int32_t operand = 0;
289
290 if (vm_opcode_has_operand(opcode)) {
291
292 operand = read_i32_le(code + pc);
293 pc += VM_OPERAND_SIZE;
294 }
295
296 switch (opcode) {
297 case VM_OP_PUSH:
298 if (vm->stack_size >= vm->max_stack
299 || vm->stack_size >= vm->stack_capacity) {
300 return vm_fail(vm, VM_ERR_STACK_OVERFLOW, instruction_pc);
301 }
302 vm->stack[vm->stack_size++] = operand;
303 break;
304
305 case VM_OP_POP:
306 if (vm->stack_size < 1) {
307 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
308 }
309 --vm->stack_size;
310 break;
311
312 case VM_OP_DUP:
313 if (vm->stack_size < 1) {
314 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
315 }
316 if (vm->stack_size >= vm->max_stack
317 || vm->stack_size >= vm->stack_capacity) {
318 return vm_fail(vm, VM_ERR_STACK_OVERFLOW, instruction_pc);
319 }
320 vm->stack[vm->stack_size] = vm->stack[vm->stack_size - 1];
321 ++vm->stack_size;
322 break;
323
324 case VM_OP_SWAP:
325 if (vm->stack_size < 2) {
326 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
327 } else {
328 int64_t top = vm->stack[vm->stack_size - 1];
329 vm->stack[vm->stack_size - 1] = vm->stack[vm->stack_size - 2];
330 vm->stack[vm->stack_size - 2] = top;
331 }
332 break;
333
334 case VM_OP_ADD:
335 case VM_OP_SUB:
336 case VM_OP_MUL:
337 case VM_OP_DIV:
338 case VM_OP_MOD:
339 case VM_OP_EQ:
340 case VM_OP_LT:
341 case VM_OP_GT:
342 if (vm->stack_size < 2) {
343 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
344 } else {
345 int64_t left = vm->stack[vm->stack_size - 2];
346 int64_t right = vm->stack[vm->stack_size - 1];
347 int64_t result;
348
349 switch (opcode) {
350 case VM_OP_ADD:
351 if (add_overflows_i64(left, right, &result)) {
352 return vm_fail(vm, VM_ERR_ARITHMETIC_OVERFLOW,
353 instruction_pc);
354 }
355 break;
356 case VM_OP_SUB:
357 if (sub_overflows_i64(left, right, &result)) {
358 return vm_fail(vm, VM_ERR_ARITHMETIC_OVERFLOW,
359 instruction_pc);
360 }
361 break;
362 case VM_OP_MUL:
363 if (mul_overflows_i64(left, right, &result)) {
364 return vm_fail(vm, VM_ERR_ARITHMETIC_OVERFLOW,
365 instruction_pc);
366 }
367 break;
368 case VM_OP_DIV:
369 if (right == 0) {
370 return vm_fail(vm, VM_ERR_DIVISION_BY_ZERO,
371 instruction_pc);
372 }
373 if (left == INT64_MIN && right == -1) {
374 return vm_fail(vm, VM_ERR_ARITHMETIC_OVERFLOW,
375 instruction_pc);
376 }
377 result = left / right;
378 break;
379 case VM_OP_MOD:
380 if (right == 0) {
381 return vm_fail(vm, VM_ERR_DIVISION_BY_ZERO,
382 instruction_pc);
383 }
384 if (left == INT64_MIN && right == -1) {
385 return vm_fail(vm, VM_ERR_ARITHMETIC_OVERFLOW,
386 instruction_pc);
387 }
388 result = left % right;
389 break;
390 case VM_OP_EQ:
391 result = left == right;
392 break;
393 case VM_OP_LT:
394 result = left < right;
395 break;
396 case VM_OP_GT:
397 result = left > right;
398 break;
399 default:
400 return vm_fail(vm, VM_ERR_BAD_OPCODE, instruction_pc);
401 }
402
403 vm->stack[vm->stack_size - 2] = result;
404 --vm->stack_size;
405 }
406 break;
407
408 case VM_OP_NEG:
409 if (vm->stack_size < 1) {
410 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
411 }
412 if (vm->stack[vm->stack_size - 1] == INT64_MIN) {
413 return vm_fail(vm, VM_ERR_ARITHMETIC_OVERFLOW, instruction_pc);
414 }
415 vm->stack[vm->stack_size - 1] = -vm->stack[vm->stack_size - 1];
416 break;
417
418 case VM_OP_JMP:
419 pc = (size_t)operand;
420 break;
421
422 case VM_OP_JZ:
423 case VM_OP_JNZ:
424 if (vm->stack_size < 1) {
425 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
426 } else {
427 int64_t condition = vm->stack[--vm->stack_size];
428 if ((opcode == VM_OP_JZ && condition == 0)
429 || (opcode == VM_OP_JNZ && condition != 0)) {
430 pc = (size_t)operand;
431 }
432 }
433 break;
434
435 case VM_OP_CALL:
436 if (vm->frame_count >= vm->max_call_frames
437 || vm->frame_count >= vm->frame_capacity) {
438 return vm_fail(vm, VM_ERR_CALL_STACK_OVERFLOW, instruction_pc);
439 }
440 memset(&vm->frames[vm->frame_count], 0,
441 sizeof(vm->frames[vm->frame_count]));
442 vm->frames[vm->frame_count].return_pc = pc;
443 ++vm->frame_count;
444 pc = (size_t)operand;
445 break;
446
447 case VM_OP_RET:
448 if (vm->frame_count <= 1) {
449 return vm_fail(vm, VM_ERR_CALL_STACK_UNDERFLOW, instruction_pc);
450 }
451 --vm->frame_count;
452 pc = vm->frames[vm->frame_count].return_pc;
453 break;
454
455 case VM_OP_LOAD:
456 if (operand < 0 || operand >= VM_LOCALS_PER_FRAME) {
457 return vm_fail(vm, VM_ERR_INVALID_LOCAL, instruction_pc);
458 }
459 if (vm->stack_size >= vm->max_stack
460 || vm->stack_size >= vm->stack_capacity) {
461 return vm_fail(vm, VM_ERR_STACK_OVERFLOW, instruction_pc);
462 }
463 vm->stack[vm->stack_size++] =
464 vm->frames[vm->frame_count - 1].locals[(size_t)operand];
465 break;
466
467 case VM_OP_STORE:
468 if (operand < 0 || operand >= VM_LOCALS_PER_FRAME) {
469 return vm_fail(vm, VM_ERR_INVALID_LOCAL, instruction_pc);
470 }
471 if (vm->stack_size < 1) {
472 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
473 }
474 vm->frames[vm->frame_count - 1].locals[(size_t)operand] =
475 vm->stack[--vm->stack_size];
476 break;
477
478 case VM_OP_PRINT:
479 if (vm->stack_size < 1) {
480 return vm_fail(vm, VM_ERR_STACK_UNDERFLOW, instruction_pc);
481 }
482 if (output != NULL
483 && output(output_user, vm->stack[vm->stack_size - 1]) != 0) {
484 return vm_fail(vm, VM_ERR_OUTPUT, instruction_pc);
485 }
486 --vm->stack_size;
487 break;
488
489 case VM_OP_HALT:
490 vm->halted = 1;
491 vm->error = VM_OK;
492 vm->error_pc = instruction_pc;
493 return VM_OK;
494
495 default:
496 return vm_fail(vm, VM_ERR_BAD_OPCODE, instruction_pc);
497 }
498 }
499
500 return vm_fail(vm, VM_ERR_NO_HALT, pc);
501}
502
503VMError vm_run(VM *vm, const uint8_t *code, size_t code_len, FILE *output)
504{
505 VMFileOutput file_sink;
506
507 file_sink.stream = output;
508 return vm_run_callback(vm, code, code_len,
509 output == NULL ? NULL : file_output, &file_sink);
510}
511
512const char *vm_error_string(VMError error)
513{
514 switch (error) {
515 case VM_OK:
516 return "success";
517 case VM_ERR_INVALID_ARGUMENT:
518 return "invalid argument";
519 case VM_ERR_ALLOCATION:
520 return "allocation failure";
521 case VM_ERR_STACK_UNDERFLOW:
522 return "operand stack underflow";
523 case VM_ERR_STACK_OVERFLOW:
524 return "operand stack overflow";
525 case VM_ERR_CALL_STACK_UNDERFLOW:
526 return "call stack underflow";
527 case VM_ERR_CALL_STACK_OVERFLOW:
528 return "call stack overflow";
529 case VM_ERR_DIVISION_BY_ZERO:
530 return "division by zero";
531 case VM_ERR_ARITHMETIC_OVERFLOW:
532 return "arithmetic overflow";
533 case VM_ERR_BAD_OPCODE:
534 return "bad opcode";
535 case VM_ERR_TRUNCATED_INSTRUCTION:
536 return "truncated instruction";
537 case VM_ERR_INVALID_JUMP:
538 return "invalid jump target";
539 case VM_ERR_INVALID_LOCAL:
540 return "invalid local index";
541 case VM_ERR_OUTPUT:
542 return "output failure";
543 case VM_ERR_NO_HALT:
544 return "program ended without HALT";
545 default:
546 return "unknown VM error";
547 }
548}
549
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.