1#include "vm.h"
2
3#include <string.h>
4
5static VMFrame *current_frame(VM *vm) {
6 return &vm->call_stack[vm->fp - 1];
7}
8
9static int vm_push(VM *vm, int64_t val) {
10 if (vm->sp >= VM_STACK_MAX) {
11 vm->status = VM_ERR_STACK_OVERFLOW;
12 return 0;
13 }
14 vm->stack[vm->sp++] = val;
15 return 1;
16}
17
18static int vm_pop(VM *vm, int64_t *out) {
19 if (vm->sp <= 0) {
20 vm->status = VM_ERR_STACK_UNDERFLOW;
21 return 0;
22 }
23 *out = vm->stack[--vm->sp];
24 return 1;
25}
26
27static int read_i64(VM *vm, int64_t *out) {
28 if (vm->ip + sizeof(int64_t) > vm->code_size) return 0;
29 memcpy(out, vm->code + vm->ip, sizeof(int64_t));
30 vm->ip += sizeof(int64_t);
31 return 1;
32}
33
34static int read_i32(VM *vm, int32_t *out) {
35 if (vm->ip + sizeof(int32_t) > vm->code_size) return 0;
36 memcpy(out, vm->code + vm->ip, sizeof(int32_t));
37 vm->ip += sizeof(int32_t);
38 return 1;
39}
40
41static int read_u8(VM *vm, uint8_t *out) {
42 if (vm->ip + sizeof(uint8_t) > vm->code_size) return 0;
43 *out = vm->code[vm->ip];
44 vm->ip += sizeof(uint8_t);
45 return 1;
46}
47
48void vm_init(VM *vm, const uint8_t *code, size_t code_size, FILE *out) {
49 memset(vm, 0, sizeof(*vm));
50 vm->code = code;
51 vm->code_size = code_size;
52 vm->ip = 0;
53 vm->sp = 0;
54 vm->fp = 1;
55 vm->out = out ? out : stdout;
56 vm->status = VM_OK;
57 vm->halted = 0;
58}
59
60VMStatus vm_run(VM *vm) {
61 if (vm->code == NULL || vm->code_size == 0) {
62 vm->status = VM_ERR_NO_CODE;
63 return vm->status;
64 }
65
66 while (!vm->halted) {
67 if (vm->ip >= vm->code_size) {
68 vm->status = VM_ERR_BAD_JUMP;
69 return vm->status;
70 }
71
72 uint8_t opcode = vm->code[vm->ip];
73 vm->ip += 1;
74
75 switch (opcode) {
76 case OP_PUSH: {
77 int64_t val;
78 if (!read_i64(vm, &val)) {
79 vm->status = VM_ERR_TRUNCATED;
80 return vm->status;
81 }
82 if (!vm_push(vm, val)) return vm->status;
83 break;
84 }
85
86 case OP_POP: {
87 int64_t v;
88 if (!vm_pop(vm, &v)) return vm->status;
89 break;
90 }
91
92 case OP_DUP: {
93 if (vm->sp <= 0) {
94 vm->status = VM_ERR_STACK_UNDERFLOW;
95 return vm->status;
96 }
97 if (!vm_push(vm, vm->stack[vm->sp - 1])) return vm->status;
98 break;
99 }
100
101 case OP_SWAP: {
102 if (vm->sp < 2) {
103 vm->status = VM_ERR_STACK_UNDERFLOW;
104 return vm->status;
105 }
106 int64_t tmp = vm->stack[vm->sp - 1];
107 vm->stack[vm->sp - 1] = vm->stack[vm->sp - 2];
108 vm->stack[vm->sp - 2] = tmp;
109 break;
110 }
111
112 case OP_ADD:
113 case OP_SUB:
114 case OP_MUL:
115 case OP_DIV:
116 case OP_MOD:
117 case OP_EQ:
118 case OP_LT:
119 case OP_GT: {
120 int64_t b, a;
121 if (!vm_pop(vm, &b)) return vm->status;
122 if (!vm_pop(vm, &a)) return vm->status;
123 int64_t result;
124 switch (opcode) {
125 case OP_ADD: result = a + b; break;
126 case OP_SUB: result = a - b; break;
127 case OP_MUL: result = a * b; break;
128 case OP_DIV:
129 if (b == 0) {
130 vm->status = VM_ERR_DIV_ZERO;
131 return vm->status;
132 }
133
134 result = (a == INT64_MIN && b == -1) ? INT64_MIN : a / b;
135 break;
136 case OP_MOD:
137 if (b == 0) {
138 vm->status = VM_ERR_DIV_ZERO;
139 return vm->status;
140 }
141 result = (a == INT64_MIN && b == -1) ? 0 : a % b;
142 break;
143 case OP_EQ: result = (a == b) ? 1 : 0; break;
144 case OP_LT: result = (a < b) ? 1 : 0; break;
145 case OP_GT: result = (a > b) ? 1 : 0; break;
146 default: result = 0; break;
147 }
148 if (!vm_push(vm, result)) return vm->status;
149 break;
150 }
151
152 case OP_NEG: {
153 int64_t v;
154 if (!vm_pop(vm, &v)) return vm->status;
155 if (!vm_push(vm, -v)) return vm->status;
156 break;
157 }
158
159 case OP_JMP: {
160 int32_t target;
161 if (!read_i32(vm, &target)) {
162 vm->status = VM_ERR_TRUNCATED;
163 return vm->status;
164 }
165 if (target < 0 || (size_t)target >= vm->code_size) {
166 vm->status = VM_ERR_BAD_JUMP;
167 return vm->status;
168 }
169 vm->ip = (size_t)target;
170 break;
171 }
172
173 case OP_JZ:
174 case OP_JNZ: {
175 int32_t target;
176 if (!read_i32(vm, &target)) {
177 vm->status = VM_ERR_TRUNCATED;
178 return vm->status;
179 }
180 int64_t v;
181 if (!vm_pop(vm, &v)) return vm->status;
182 int take = (opcode == OP_JZ) ? (v == 0) : (v != 0);
183 if (take) {
184 if (target < 0 || (size_t)target >= vm->code_size) {
185 vm->status = VM_ERR_BAD_JUMP;
186 return vm->status;
187 }
188 vm->ip = (size_t)target;
189 }
190 break;
191 }
192
193 case OP_CALL: {
194 int32_t target;
195 if (!read_i32(vm, &target)) {
196 vm->status = VM_ERR_TRUNCATED;
197 return vm->status;
198 }
199 if (target < 0 || (size_t)target >= vm->code_size) {
200 vm->status = VM_ERR_BAD_JUMP;
201 return vm->status;
202 }
203 if (vm->fp >= VM_CALL_STACK_MAX) {
204 vm->status = VM_ERR_CALLSTACK_OVERFLOW;
205 return vm->status;
206 }
207 VMFrame *nf = &vm->call_stack[vm->fp];
208 nf->return_addr = (int64_t)vm->ip;
209 memset(nf->locals, 0, sizeof(nf->locals));
210 vm->fp++;
211 vm->ip = (size_t)target;
212 break;
213 }
214
215 case OP_RET: {
216 if (vm->fp <= 1) {
217 vm->status = VM_ERR_CALLSTACK_UNDERFLOW;
218 return vm->status;
219 }
220 vm->fp--;
221 vm->ip = (size_t)vm->call_stack[vm->fp].return_addr;
222 break;
223 }
224
225 case OP_LOAD: {
226 uint8_t idx;
227 if (!read_u8(vm, &idx)) {
228 vm->status = VM_ERR_TRUNCATED;
229 return vm->status;
230 }
231 if (idx >= VM_LOCALS_PER_FRAME) {
232 vm->status = VM_ERR_BAD_LOCAL_INDEX;
233 return vm->status;
234 }
235 if (!vm_push(vm, current_frame(vm)->locals[idx])) return vm->status;
236 break;
237 }
238
239 case OP_STORE: {
240 uint8_t idx;
241 if (!read_u8(vm, &idx)) {
242 vm->status = VM_ERR_TRUNCATED;
243 return vm->status;
244 }
245 if (idx >= VM_LOCALS_PER_FRAME) {
246 vm->status = VM_ERR_BAD_LOCAL_INDEX;
247 return vm->status;
248 }
249 int64_t v;
250 if (!vm_pop(vm, &v)) return vm->status;
251 current_frame(vm)->locals[idx] = v;
252 break;
253 }
254
255 case OP_PRINT: {
256 int64_t v;
257 if (!vm_pop(vm, &v)) return vm->status;
258 fprintf(vm->out, "%lld\n", (long long)v);
259 break;
260 }
261
262 case OP_HALT: {
263 vm->halted = 1;
264 break;
265 }
266
267 default:
268 vm->status = VM_ERR_BAD_OPCODE;
269 return vm->status;
270 }
271 }
272
273 return vm->status;
274}
275
276const char *vm_status_str(VMStatus status) {
277 switch (status) {
278 case VM_OK: return "ok";
279 case VM_ERR_STACK_OVERFLOW: return "stack overflow";
280 case VM_ERR_STACK_UNDERFLOW: return "stack underflow";
281 case VM_ERR_CALLSTACK_OVERFLOW: return "call stack overflow";
282 case VM_ERR_CALLSTACK_UNDERFLOW: return "call stack underflow";
283 case VM_ERR_DIV_ZERO: return "division by zero";
284 case VM_ERR_BAD_OPCODE: return "bad opcode";
285 case VM_ERR_BAD_JUMP: return "out-of-range jump";
286 case VM_ERR_BAD_LOCAL_INDEX: return "bad local index";
287 case VM_ERR_NO_CODE: return "no code loaded";
288 case VM_ERR_TRUNCATED: return "truncated instruction";
289 default: return "unknown error";
290 }
291}
292
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.