added start and in-game menu
[awish.git] / src / vm.c
blobf9daa98ad2cb4bf0ba0e14f2661dac390973708d
1 /*
2 * This program is free software: you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation, either version 3 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #include <stdarg.h>
16 #include <stdint.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
21 #include "vm.h"
24 //#define VM_DEBUG
26 #define SECRET (42)
29 // should be enough for everyone!
30 #define MAX_INST_COUNT (1000000)
32 //extern int goobers;
35 //extern void fatal (const char *fmt, ...) __attribute__((__noreturn__)) __attribute__((format(printf, 1, 2)));
36 static __attribute__((__noreturn__)) __attribute__((format(printf, 1, 2))) void fatalis (const char *fmt, ...) {
37 va_list ap;
39 fprintf(stderr, "AWISH FATAL: ");
40 va_start(ap, fmt);
41 vfprintf(stderr, fmt, ap);
42 va_end(ap);
43 fprintf(stderr, "\n");
44 exit(1);
48 #ifndef VM_FATAL_DEFINED
49 static __attribute__((__noreturn__)) __attribute__((format(printf, 3, 4))) void vmfatal (int tid, int pc, const char *fmt, ...) {
50 va_list ap;
52 fprintf(stderr, "VM FATAL (thread #%d, pc=%04x): ", tid, (unsigned int)pc);
53 va_start(ap, fmt);
54 vfprintf(stderr, fmt, ap);
55 va_end(ap);
56 fprintf(stderr, "\n");
57 exit(1);
59 #endif
62 const char *vmOpNames[] = {
63 "add",
64 "sub",
65 "mul",
66 "div",
67 "mod",
68 "bor",
69 "xor",
70 "and",
72 "jeq",
73 "jne",
74 "jlt",
75 "jle",
76 "jgt",
77 "jge",
79 "jmp",
81 "bsr",
83 "brk",
84 "end",
85 "new",
87 "set",
88 "get",
90 "psh",
91 "pop",
92 "swp",
93 "pck",
94 "rol",
95 "dpt",
97 "tid",
98 "kil",
99 "sus",
100 "res",
101 "sta",
103 "rxc",
104 "wxc",
106 "ret",
108 "rst",
110 "mgf",
111 "mgb",
112 "msf",
113 "msb",
115 NULL
119 VMRSTCB vmRSTCB = NULL;
120 VMMapGetCB vmMapGetCB = NULL;
121 VMMapSetCB vmMapSetCB = NULL;
124 typedef struct {
125 int pc;
126 int suspended;
127 int sp;
128 int *stack; // internal
129 int *tvars; // internal
130 } VMThread;
133 int vmDebugTrace = 0;
134 FILE *vmDebugOutput = NULL;
137 static VMThread vmThreads[VM_MAX_THREADS];
138 static int vmExecuted[VM_MAX_THREADS];
139 static int vmLastThreadId = 0;
141 unsigned char vmCode[65536];
142 int vmCodeSize = 0;
143 int vmGVars[VM_VARS_SIZE];
145 int vmMaxGVar = 0;
146 int vmMaxTVar = 0;
149 static void fixLastThreadId (void) {
150 for (; vmLastThreadId > 0; --vmLastThreadId) if (vmThreads[vmLastThreadId].stack) break;
154 static int vmFindFreeThread (void) {
155 for (int f = 0; f < VM_MAX_THREADS; ++f) if (vmThreads[f].stack == NULL) return f;
156 return -1;
160 static int vmInitThread (VMThread *trd) {
161 trd->pc = 0;
162 trd->sp = 0;
163 trd->suspended = 0;
164 trd->stack = calloc(VM_STACK_SIZE, sizeof(int));
165 if (trd->stack == NULL) return -1;
166 trd->tvars = calloc(VM_VARS_SIZE, sizeof(int));
167 if (trd->tvars == NULL) { free(trd->stack); return -1; }
168 return 0;
172 static void vmFreeThread (VMThread *trd) {
173 if (trd->stack) free(trd->stack);
174 if (trd->tvars) free(trd->tvars);
175 trd->stack = NULL;
176 trd->tvars = NULL;
177 trd->pc = 0;
178 trd->sp = 0;
179 trd->suspended = 0;
183 int vmInitialize (void) {
184 vmFreeLabels();
185 memset(vmThreads, 0, sizeof(vmThreads));
186 // setup main thread
187 vmLastThreadId = 0;
188 if (vmInitThread(vmThreads)) return -1; // alas
189 return 0;
193 void vmDeinitialize (void) {
194 for (int f = 0; f < VM_MAX_THREADS; ++f) {
195 if (vmThreads[f].stack) free(vmThreads[f].stack);
196 if (vmThreads[f].tvars) free(vmThreads[f].tvars);
198 memset(vmThreads, 0, sizeof(vmThreads));
199 vmLastThreadId = 0;
200 vmFreeLabels();
204 static inline int vmGetByte (int tid, int opc, VMThread *trd) {
205 if (trd->pc < 0 || trd->pc >= vmCodeSize) vmfatal(tid, opc, "out of code");
206 return vmCode[trd->pc++];
210 #define STACK_WANT(n) do { if (trd->sp < (n)) vmfatal(tid, opc, "stack underflow"); } while (0)
211 #define STACK_FSPC(n) do { if (trd->sp+(n) > VM_STACK_SIZE) vmfatal(tid, opc, "stack overflow"); } while (0)
213 #define STACK_TOP() (trd->stack[trd->sp-1])
214 #define STACK_POP() (trd->stack[--(trd->sp)])
215 #define STACK_PUSH(n) trd->stack[(trd->sp)++] = (n)
218 #define MATH(op) do { \
219 switch (argc) { \
220 case 0: \
221 STACK_WANT(2); \
222 trd->stack[trd->sp-2] = (trd->stack[trd->sp-2]) op (trd->stack[trd->sp-1]); \
223 --(trd->sp); \
224 break; \
225 case 1: \
226 STACK_WANT(1); \
227 trd->stack[trd->sp-1] = (trd->stack[trd->sp-1]) op (argv[0]); \
228 break; \
229 case 2: if (argp[0] != NULL) *(argp[0]) = (argv[0]) op (argv[1]); break; \
230 case 3: if (argp[2] != NULL) *(argp[2]) = (argv[0]) op (argv[1]); break; \
232 } while(0)
235 #define CHECK_DIV_ZERO \
236 switch (argc) { \
237 case 0: \
238 if (trd->sp > 0 && trd->stack[trd->sp-1] == 0) { fprintf(stderr, "VM FATAL: division by zero!\n"); exit(1); } \
239 break; \
240 case 1: \
241 if (argv[0] == 0) { fprintf(stderr, "VM FATAL: division by zero!\n"); exit(1); } \
242 break; \
243 default: \
244 if (argv[1] == 0) { fprintf(stderr, "VM FATAL: division by zero!\n"); exit(1); } \
245 break; \
249 #define JXX(op) do { \
250 switch (argc) { \
251 case 0: \
252 STACK_WANT(3); \
253 argv[0] = STACK_POP(); \
254 case 1: \
255 STACK_WANT(2); \
256 argv[2] = STACK_POP(); \
257 argv[1] = STACK_POP(); \
258 break; \
259 case 2: \
260 STACK_WANT(1); \
261 argv[2] = argv[1]; \
262 argv[1] = STACK_POP(); \
263 break; \
265 if (argv[1] op argv[2]) trd->pc = argv[0]; \
266 } while (0)
269 static inline int isBranch (int opcode) {
270 return
271 opcode == VM_JMP ||
272 opcode == VM_BSR ||
273 opcode == VM_JEQ ||
274 opcode == VM_JNE ||
275 opcode == VM_JLT ||
276 opcode == VM_JLE ||
277 opcode == VM_JGT ||
278 opcode == VM_JGE;
282 // <0: BRK; >0: END
283 int vmExecuteOne (int tid) {
284 VMThread *trd = &vmThreads[tid];
285 int opcode;
286 int argc;
287 int argv[3]; // argument values
288 int *argp[3]; // pointer to vars for each arg
289 int opc = trd->pc;
291 // decode instruction
292 opcode = vmGetByte(tid, opc, trd);
293 argc = (opcode>>6)&0x03;
294 opcode &= 0x3f;
295 argv[0] = argv[1] = argv[2] = 0;
296 argp[0] = argp[1] = argp[2] = NULL;
297 // read operands
298 if (tid > vmLastThreadId) vmLastThreadId = tid;
299 for (int f = 0; f < argc; ++f) {
300 int vn = vmGetByte(tid, opc, trd);
302 if (vn == 255) {
303 // immediate
304 argv[f] = vmGetByte(tid, opc, trd);
305 argv[f] |= vmGetByte(tid, opc, trd)<<8;
306 if (argv[f] >= 32768 && (f != 0 || !isBranch(opcode))) argv[f] -= 65536;
307 } else {
308 if (vn == 127) {
309 // stack var; <0: from stack top
310 argv[f] = vmGetByte(tid, opc, trd);
311 argv[f] |= (vmGetByte(tid, opc, trd)<<8);
312 if (argv[f] >= 32768) argv[f] -= 65536;
313 if (argv[f] < 0) argv[f] += trd->sp;
314 if (argv[f] < 0 || argv[f] >= VM_STACK_SIZE) vmfatal(tid, opc, "invalid stack offset");
315 argp[f] = trd->stack+argv[f];
316 } else {
317 // variable
318 argp[f] = vn&0x80 ? vmGVars : trd->tvars;
319 argp[f] += vn&0x7f;
321 argv[f] = *(argp[f]);
325 //#ifdef VM_DEBUG
326 if (vmDebugTrace) {
327 if (vmDebugOutput == NULL) vmDebugOutput = stderr;
329 fprintf(vmDebugOutput, "(%04d) %04x: ", tid, (unsigned int)opc);
330 if (opcode < sizeof(vmOpNames)/sizeof(char *)-1) fprintf(vmDebugOutput, "%s", vmOpNames[opcode]); else fprintf(vmDebugOutput, "BAD");
331 for (int f = 0; f < argc; ++f) {
332 fputc(' ', vmDebugOutput);
333 if (argp[f]) {
334 unsigned int ofs = 0;
336 fputc('[', vmDebugOutput);
337 if (argp[f] >= trd->tvars && argp[f] < trd->tvars+VM_VARS_SIZE) {
338 ofs = (unsigned int)(argp[f]-trd->tvars);
339 } else if (argp[f] >= trd->stack && argp[f] < trd->stack+VM_VARS_SIZE) {
340 ofs = (unsigned int)(argp[f]-trd->stack);
341 fputc('.', vmDebugOutput);
342 } else if (argp[f] >= vmGVars && argp[f] < vmGVars+VM_VARS_SIZE) {
343 ofs = (unsigned int)(argp[f]-vmGVars);
344 fputc('@', vmDebugOutput);
346 fprintf(vmDebugOutput, "%u]", ofs);
348 if (f == 0 && isBranch(opcode)) {
349 fprintf(vmDebugOutput, "(0x%04x)", argv[f]);
350 } else {
351 fprintf(vmDebugOutput, "(%d)", argv[f]);
354 fputc('\n', vmDebugOutput);
355 if (vmDebugOutput != stderr && vmDebugOutput != stdout) fflush(vmDebugOutput);
357 //#endif
359 switch (opcode) {
360 case VM_ADD: MATH(+); break;
361 case VM_SUB: MATH(-); break;
362 case VM_MUL: MATH(*); break;
363 case VM_DIV:
364 CHECK_DIV_ZERO
365 MATH(/);
366 break;
367 case VM_MOD:
368 CHECK_DIV_ZERO
369 MATH(%);
370 break;
371 case VM_BOR: MATH(|); break;
372 case VM_XOR: MATH(^); break;
373 case VM_AND: MATH(&); break;
375 case VM_JEQ: JXX(==); break;
376 case VM_JNE: JXX(!=); break;
377 case VM_JLT: JXX(<); break;
378 case VM_JLE: JXX(<=); break;
379 case VM_JGT: JXX(>); break;
380 case VM_JGE: JXX(>=); break;
382 case VM_JMP:
383 if (argc == 0) {
384 STACK_WANT(1);
385 argv[0] = STACK_POP();
387 trd->pc = argv[0];
388 break;
390 case VM_BSR:
391 switch (argc) {
392 case 0:
393 STACK_WANT(1);
394 argv[0] = STACK_POP();
395 break;
396 case 1:
397 break;
398 case 2:
399 STACK_FSPC(1);
400 STACK_PUSH(argv[1]);
401 break;
402 case 3:
403 STACK_FSPC(2);
404 STACK_PUSH(argv[1]);
405 STACK_PUSH(argv[2]);
406 break;
408 STACK_FSPC(1);
409 STACK_PUSH(trd->pc);
410 trd->pc = argv[0];
411 break;
413 case VM_END: return 1;
414 case VM_BRK: return -1;
416 case VM_NEW: { // new thread
417 int xtid = vmFindFreeThread();
419 if (argc == 0) {
420 STACK_WANT(1);
421 argv[0] = STACK_POP();
423 if (xtid >= 0) {
424 if (vmInitThread(&vmThreads[xtid]) == 0) {
425 vmThreads[xtid].pc = argv[0];
426 } else {
427 xtid = -1;
430 if (xtid < 0) vmfatal(tid, opc, "too many threads");
431 if (xtid > vmLastThreadId) vmLastThreadId = xtid;
432 if (argc > 1) {
433 if (argp[1]) *(argp[1]) = xtid;
434 } else {
435 STACK_FSPC(1);
436 STACK_PUSH(xtid);
438 break; }
440 case VM_SET:
441 switch (argc) {
442 case 0:
443 STACK_WANT(2);
444 argv[0] = STACK_POP(); // varid
445 argv[1] = STACK_POP(); // value
446 if (argv[0] < 0) {
447 argv[0] = -argv[0];
448 if (argv[0] < VM_VARS_SIZE) vmGVars[argv[0]] = argv[1];
449 } else if (argv[0] < VM_VARS_SIZE) {
450 trd->tvars[argv[0]] = argv[1];
452 break;
453 case 1:
454 STACK_WANT(1);
455 argv[1] = STACK_POP(); // value
456 // fallthru
457 case 2:
458 if (argp[0]) *(argp[0]) = argv[1];
459 break;
460 case 3: {
461 int xtid = argv[2], vid = argv[0];
463 if (xtid < 0 || xtid >= VM_MAX_THREADS || vmThreads[xtid].stack == NULL || vid < 0 || vid >= VM_VARS_SIZE) break;
464 vmThreads[xtid].tvars[vid] = argv[1];
465 break; }
467 break;
469 case VM_GET: {
470 int xtid, vid, pushit;
472 switch (argc) {
473 case 0:
474 STACK_WANT(2);
475 vid = STACK_POP();
476 xtid = STACK_POP();
477 pushit = 1;
478 break;
479 case 1:
480 STACK_WANT(1);
481 vid = argv[0];
482 xtid = STACK_POP();
483 pushit = 1;
484 break;
485 case 2:
486 xtid = argv[0];
487 vid = argv[1];
488 pushit = 1;
489 break;
490 default:
491 xtid = argv[0];
492 vid = argv[1];
493 pushit = 0;
494 break;
496 if (xtid >= 0 && xtid < VM_MAX_THREADS && vmThreads[xtid].stack != NULL && vid >= 0 && vid < VM_VARS_SIZE) {
497 argv[0] = vmThreads[xtid].tvars[vid];
498 } else {
499 argv[0] = 0;
501 if (!pushit) {
502 if (argp[2]) *(argp[2]) = argv[0];
503 } else {
504 STACK_FSPC(1);
505 STACK_PUSH(argv[0]);
507 break;
509 case VM_PSH:
510 if (argc > 0) {
511 STACK_FSPC(argc);
512 for (int f = 0; f < argc; ++f) STACK_PUSH(argv[f]);
513 } else {
514 // dup
515 STACK_WANT(1);
516 STACK_FSPC(1);
517 trd->stack[trd->sp] = trd->stack[trd->sp-1];
518 ++(trd->sp);
520 break;
521 case VM_POP:
522 switch (argc) {
523 case 0: // drop one
524 STACK_WANT(1);
525 --(trd->sp);
526 break;
527 case 1:
528 if (argp[0] == NULL && argv[0] < 0) {
529 // allocate stack space
530 argc = -argv[0];
531 STACK_FSPC(argc);
532 for (; argc > 0; --argc) STACK_PUSH(0);
533 break;
535 // fallthru
536 default:
537 for (int f = 0; f < argc; ++f) {
538 if (argp[f]) {
539 // pop
540 STACK_WANT(1);
541 *(argp[f]) = STACK_POP();
542 } else if (argv[f] > 0) {
543 // drop
544 STACK_WANT(argv[f]);
545 trd->sp -= argv[f];
548 break;
551 break;
552 case VM_SWP:
553 switch (argc) {
554 case 0:
555 STACK_WANT(2);
556 argc = trd->stack[trd->sp-2];
557 trd->stack[trd->sp-2] = trd->stack[trd->sp-1];
558 trd->stack[trd->sp-1] = argc;
559 break;
560 case 1:
561 STACK_WANT(1);
562 argc = trd->stack[trd->sp-1];
563 trd->stack[trd->sp-1] = argv[0];
564 if (argp[0]) *(argp[0]) = argc;
565 break;
566 default: // 2, 3
567 if (argp[0]) *(argp[0]) = argv[1];
568 if (argp[1]) *(argp[1]) = argv[0];
569 break;
571 break;
572 case VM_PCK:
573 case VM_ROL:
574 if (argc < 1) {
575 STACK_WANT(1);
576 argv[0] = STACK_POP();
578 // get item
579 if (argv[0] < 0) argv[0] += trd->sp;
580 if (argv[0] < 0 || argv[0] >= trd->sp) vmfatal(tid, opc, "invalid stack index (%d)", argv[0]);
581 argv[2] = trd->stack[argv[0]];
583 if (opcode == VM_ROL) {
584 for (int f = argv[0]+1; f < trd->sp; ++f) trd->stack[f-1] = trd->stack[f];
585 --(trd->sp);
588 switch (argc) {
589 case 0:
590 case 1:
591 STACK_FSPC(1);
592 STACK_PUSH(argv[2]);
593 break;
594 default:
595 if (argp[1]) *(argp[1]) = argv[2];
596 break;
598 break;
599 case VM_DPT:
600 if (argc > 0) {
601 if (argp[0]) *(argp[0]) = trd->sp;
602 } else {
603 STACK_FSPC(1);
604 argc = trd->sp;
605 STACK_PUSH(argc);
607 break;
609 case VM_TID:
610 if (argc > 0) {
611 if (argp[0]) *(argp[0]) = tid;
612 } else {
613 STACK_FSPC(1);
614 STACK_PUSH(tid);
616 break;
618 case VM_KIL:
619 case VM_SUS:
620 case VM_RES:
621 if (argc == 0) {
622 STACK_WANT(1);
623 argv[0] = STACK_POP();
625 if (argv[0] >= 0 && argv[0] < VM_MAX_THREADS && vmThreads[argv[0]].stack != NULL) {
626 switch (opcode) {
627 case VM_KIL:
628 if (argv[0] == tid) return 1;
629 vmFreeThread(&vmThreads[argv[0]]);
630 fixLastThreadId();
631 break;
632 case VM_SUS:
633 if (argv[0] == tid) return -1;
634 vmThreads[argv[0]].suspended = 1;
635 break;
636 case VM_RES:
637 vmThreads[argv[0]].suspended = 0;
638 break;
641 break;
643 case VM_STA:
644 if (argc == 0) {
645 STACK_WANT(1);
646 argv[0] = STACK_POP();
648 if (argv[0] >= 0 && argv[0] < VM_MAX_THREADS && vmThreads[argv[0]].stack != NULL) {
649 argv[0] = vmThreads[argv[0]].suspended;
650 } else {
651 argv[0] = -1;
653 if (argc > 1) {
654 if (argp[1]) *(argp[1]) = argv[0];
655 } else {
656 STACK_FSPC(1);
657 STACK_PUSH(argv[0]);
659 break;
661 case VM_RXC:
662 if (argc == 0) {
663 STACK_WANT(1);
664 argv[0] = STACK_POP();
666 if (argv[0] < 0 || argv[0] >= vmCodeSize) vmfatal(tid, opc, "invalid address in RXC (%d)", argv[0]);
667 if (argc < 2) {
668 STACK_FSPC(1);
669 STACK_PUSH(vmCode[argv[0]]);
670 } else if (argp[1]) {
671 *(argp[1]) = vmCode[argv[0]];
673 break;
675 case VM_WXC:
676 switch (argc) {
677 case 0:
678 STACK_WANT(2);
679 argv[1] = STACK_POP(); // byte
680 argv[0] = STACK_POP(); // address
681 break;
682 case 1:
683 STACK_WANT(1);
684 argv[1] = STACK_POP(); // byte
685 break;
687 if (argv[0] < 0 || argv[0] >= vmCodeSize) vmfatal(tid, opc, "invalid address in WXC (%d)", argv[0]);
688 if (argv[1] < -128 || argv[1] > 255) vmfatal(tid, opc, "invalid value in WXC (%d)", argv[1]);
689 if (argv[1] < 0) argv[1] &= 0xff;
690 vmCode[argv[0]] = argv[1];
691 break;
693 case VM_RET:
694 switch (argc) {
695 case 0: // simple
696 STACK_WANT(1);
697 trd->pc = STACK_POP();
698 break;
699 case 1:
700 vmfatal(tid, opc, "invalid RET");
701 case 2: // # of locals to pop, # of arguments to pop
702 case 3: // # of locals to pop, # of arguments to pop, retvalue (taken before all pops, pushes after returning)
703 if (argv[0] < 0) argv[0] = 0;
704 if (argv[1] < 0) argv[1] = 0;
705 STACK_WANT(argv[0]+argv[1]+1);
706 trd->sp -= argv[0]; // drop locals
707 trd->pc = STACK_POP();
708 trd->sp -= argv[1]; // drop arguments
709 if (argc == 3) {
710 // push result
711 STACK_FSPC(1);
712 STACK_PUSH(argv[2]);
714 break;
716 break;
718 case VM_RST:
719 if (vmRSTCB) {
720 if (argc == 0) {
721 STACK_WANT(1);
722 argv[0] = STACK_POP();
723 argc = 1;
725 vmRSTCB(tid, opcode, argc, argv, argp);
726 } else if (argc == 0) {
727 STACK_WANT(1);
728 --(trd->sp);
730 break;
732 case VM_MGF:
733 case VM_MGB:
734 switch (argc) {
735 case 0: // nothing
736 case 1: // only dest
737 STACK_WANT(2);
738 argv[1] = STACK_POP(); // y
739 argv[0] = STACK_POP(); // x
740 break;
742 if (vmMapGetCB) argv[0] = vmMapGetCB(tid, opcode==VM_MGF, argv[0], argv[1]); else argv[0] = 0;
743 if (argc == 3) {
744 if (argp[2]) *(argp[2]) = argv[0];
745 } else if (argc == 1) {
746 if (argp[0]) *(argp[0]) = argv[0];
747 } else {
748 STACK_FSPC(1);
749 STACK_PUSH(argv[0]);
751 break;
752 case VM_MSF:
753 case VM_MSB:
754 switch (argc) {
755 case 0:
756 STACK_WANT(2);
757 argv[2] = STACK_POP(); // tile
758 argv[1] = STACK_POP(); // y
759 argv[0] = STACK_POP(); // x
760 break;
761 case 1:
762 STACK_WANT(2);
763 argv[2] = argv[0]; // tile
764 argv[1] = STACK_POP(); // y
765 argv[0] = STACK_POP(); // x
766 break;
767 case 2:
768 STACK_WANT(1);
769 argv[2] = STACK_POP(); // tile
770 break;
772 if (vmMapSetCB) vmMapSetCB(tid, opcode==VM_MSF, argv[0], argv[1], argv[2]);
773 break;
775 default:
776 vmfatal(tid, opc, "invalid opcode (%d)", opcode);
778 return 0;
782 void vmExecuteAll (int menuCheckVar, int inMenu) {
783 int runned;
785 memset(vmExecuted, 0, sizeof(vmExecuted));
786 do {
787 runned = 0;
788 for (int t = 0; t < VM_MAX_THREADS; ++t) {
789 if (!vmExecuted[t] && vmThreads[t].stack != NULL && !vmThreads[t].suspended) {
790 int f;
792 // skip non-menu threads if we are in menu
793 if (inMenu && menuCheckVar >= 0 && menuCheckVar < VM_VARS_SIZE && vmThreads[t].tvars[menuCheckVar] == 0) continue;
794 vmExecuted[t] = 1;
795 runned = 1;
796 for (f = MAX_INST_COUNT; f >= 0; --f) {
797 int res = vmExecuteOne(t);
799 if (res < 0) {
800 // BRK
801 //fprintf(stderr, "!!!\n");
802 break;
804 if (res > 0) {
805 // END
806 vmFreeThread(&vmThreads[t]);
807 fixLastThreadId();
808 break;
811 if (f < 0) vmfatal(t, vmThreads[t].pc, "too many instructions in frame");
814 } while (runned);
818 // <0: BRK; >0: END; 0: ok
819 // maxinst<0: any number of instructions
820 int vmExecuteBSR (int tid, int pc, int maxinst) {
821 int opc;
823 if (tid < 0 || tid >= VM_MAX_THREADS || vmThreads[tid].stack == NULL || vmThreads[tid].suspended) return 2; // END
824 opc = vmThreads[tid].pc;
825 vmPush(tid, -666666);
826 vmThreads[tid].pc = pc;
827 if (maxinst == 0) maxinst = MAX_INST_COUNT;
828 for (;;) {
829 int res = vmExecuteOne(tid);
831 if (!vmThreads[tid].stack) return 1;
832 if (vmThreads[tid].suspended) return -2;
833 if (res > 0) vmFreeThread(&vmThreads[tid]);
834 if (res != 0) return res;
835 if (vmThreads[tid].pc == -666666) {
836 vmThreads[tid].pc = opc;
837 return 0;
839 if (maxinst == 1) vmfatal(tid, vmThreads[tid].pc, "too many instructions in vmExecuteBSR");
840 if (maxinst > 0) --maxinst;
845 int vmIsThreadAlive (int tid) {
846 if (tid >= 0 && tid < VM_MAX_THREADS) return vmThreads[tid].stack != NULL;
847 return 0;
851 int vmNewThread (int pc) {
852 int tid = vmFindFreeThread();
854 if (tid >= 0) {
855 if (vmInitThread(&vmThreads[tid]) != 0) return -1;
856 vmThreads[tid].pc = pc;
857 if (tid > vmLastThreadId) vmLastThreadId = tid;
859 return tid;
863 int vmKillThread (int tid) {
864 if (vmIsThreadAlive(tid)) {
865 vmFreeThread(&vmThreads[tid]);
866 fixLastThreadId();
867 return 1;
869 return 0;
873 int vmIsSuspendedThread (int tid) {
874 if (vmIsThreadAlive(tid)) return vmThreads[tid].suspended;
875 return 0;
879 int vmSuspendThread (int tid) {
880 if (vmIsThreadAlive(tid)) { vmThreads[tid].suspended = 1; return 0; }
881 return -1;
885 int vmResumeThread (int tid) {
886 if (vmIsThreadAlive(tid)) { vmThreads[tid].suspended = 0; return 0; }
887 return -1;
891 int vmGetTVar (int tid, int idx) {
892 if (idx >= 0 && idx < VM_VARS_SIZE && vmIsThreadAlive(tid)) return vmThreads[tid].tvars[idx];
893 return 0;
897 int vmSetTVar (int tid, int idx, int value) {
898 if (idx >= 0 && idx < VM_VARS_SIZE && vmIsThreadAlive(tid)) { vmThreads[tid].tvars[idx] = value; return 0; }
899 return -1;
903 int vmGetSP (int tid) {
904 if (vmIsThreadAlive(tid)) return vmThreads[tid].sp;
905 return -1;
909 int vmGetPC (int tid) {
910 if (vmIsThreadAlive(tid)) return vmThreads[tid].pc;
911 return -1;
915 int vmSetPC (int tid, int pc) {
916 if (vmIsThreadAlive(tid) && pc >= 0 && pc < vmCodeSize) { vmThreads[tid].pc = pc; return 0; }
917 return -1;
921 int vmSetSP (int tid, int value) {
922 if (vmIsThreadAlive(tid)) { vmThreads[tid].sp = value; return 0; }
923 return -1;
927 int vmGetStack (int tid, int idx) {
928 if (vmIsThreadAlive(tid)) {
929 if (idx < 0) idx += vmThreads[tid].sp;
930 if (idx >= 0 && idx < VM_STACK_SIZE) return vmThreads[tid].stack[idx];
932 return -1;
936 int vmSetStack (int tid, int idx, int value) {
937 if (vmIsThreadAlive(tid)) {
938 if (idx < 0) idx += vmThreads[tid].sp;
939 if (idx >= 0 && idx < VM_STACK_SIZE) { vmThreads[tid].stack[idx] = value; return 0; }
941 return -1;
945 int vmPush (int tid, int value) {
946 if (vmIsThreadAlive(tid) && vmThreads[tid].sp < VM_STACK_SIZE) {
947 vmThreads[tid].stack[vmThreads[tid].sp++] = value;
948 return 0;
950 return -1;
954 int vmPop (int tid) {
955 if (vmIsThreadAlive(tid) && vmThreads[tid].sp > 0) return vmThreads[tid].stack[--vmThreads[tid].sp];
956 return 0;
960 int vmLoadArgs (int tid, int argc, int argv[], int *argp[], int aargc, int aargv[], int *aargp[]) {
961 if (!vmIsThreadAlive(tid)) return -1;
962 if (argc > 0) {
963 int e = argc;
965 if (e > aargc) e = aargc;
966 for (int f = 0; f < e; ++f) { argv[f] = aargv[f]; argp[f] = aargp[f]; }
967 for (int f = e; f < argc; ++f) {
968 if (vmThreads[tid].sp <= 0) return -1;
969 argp[argc-f] = NULL;
970 argv[argc-f] = vmThreads[tid].stack[--vmThreads[tid].sp];
973 return 0;
977 int vmLastThread (void) {
978 return vmLastThreadId;
982 static int readBuf (FILE *fl, void *buf, int len) {
983 unsigned char *c = (unsigned char *)buf;
985 while (len-- > 0) {
986 unsigned char b;
988 if (fread(&b, 1, 1, fl) != 1) return -1;
989 b ^= SECRET;
990 *c++ = b;
992 return 0;
996 static int writeBuf (FILE *fl, const void *buf, int len) {
997 const unsigned char *c = (const unsigned char *)buf;
999 while (len-- > 0) {
1000 unsigned char b = *c++;
1002 b ^= SECRET;
1003 if (fwrite(&b, 1, 1, fl) != 1) return -1;
1005 return 0;
1009 static int readDW (FILE *fl, int *v) {
1010 int t[4];
1012 for (int f = 0; f < 4; ++f) {
1013 unsigned char b;
1015 if (fread(&b, 1, 1, fl) != 1) return -1;
1016 t[f] = b^SECRET;
1019 if (v) {
1020 *v = 0;
1021 for (int f = 0; f < 4; ++f) *v |= t[f]<<(8*f);
1023 return 0;
1027 static int writeDW (FILE *fl, int v) {
1028 for (int f = 0; f < 4; ++f) {
1029 unsigned char b;
1031 b = v&0xff;
1032 b ^= SECRET;
1033 if (fwrite(&b, 1, 1, fl) != 1) return -1;
1034 v >>= 8;
1036 return 0;
1040 ////////////////////////////////////////////////////////////////////////////////
1041 static VMLabelInfo *labels = NULL;
1044 ////////////////////////////////////////////////////////////////////////////////
1045 void vmFreeLabels (void) {
1046 while (labels != NULL) {
1047 VMLabelInfo *l = labels;
1049 labels = l->next;
1050 if (l->name) free(l->name);
1051 free(l);
1056 void vmFreeLabelsUntilMark (const char *name) {
1057 while (labels != NULL) {
1058 VMLabelInfo *l = labels;
1059 int isMark = (l->type == LB_MARK);
1061 if (!(isMark && ((name == NULL && l->name == NULL) || (l->name != NULL && name != NULL && strcmp(name, l->name) == 0)))) isMark = 0;
1062 labels = l->next;
1063 if (l->name) free(l->name);
1064 free(l);
1065 if (isMark) break;
1070 VMLabelInfo *vmLabelAddMark (const char *name) {
1071 VMLabelInfo *l = malloc(sizeof(VMLabelInfo));
1073 if (l == NULL) fatalis("out of memory");
1074 l->name = NULL;
1075 if (name != NULL) {
1076 l->name = strdup(name);
1077 if (l->name == NULL) fatalis("out of memory");
1079 l->type = LB_MARK;
1080 l->value = -1;
1081 l->next = labels;
1082 l->pub = 0;
1083 labels = l;
1084 return l;
1088 VMLabelInfo *vmAddLabel (const char *name, int type, int value, int pub) {
1089 if (name != NULL && name[0]) {
1090 VMLabelInfo *l = malloc(sizeof(VMLabelInfo));
1092 if (l == NULL) fatalis("out of memory");
1093 if (name != NULL) {
1094 l->name = strdup(name);
1095 if (l->name == NULL) fatalis("out of memory");
1096 } else {
1097 l->name = NULL;
1099 l->type = type;
1100 l->value = value;
1101 l->pub = pub;
1102 l->next = labels;
1103 labels = l;
1104 return l;
1106 return NULL;
1110 static VMLabelInfo *vmAddLabelToEnd (const char *name, int type, int value, int pub) {
1111 if (name != NULL && name[0]) {
1112 VMLabelInfo *l = malloc(sizeof(VMLabelInfo)), *last = NULL;
1114 if (l == NULL) fatalis("out of memory");
1115 if (name != NULL) {
1116 l->name = strdup(name);
1117 if (l->name == NULL) fatalis("out of memory");
1118 } else {
1119 l->name = NULL;
1121 l->type = type;
1122 l->value = value;
1123 l->pub = pub;
1124 l->next = NULL;
1125 if (labels == NULL) {
1126 labels = l;
1127 } else {
1128 for (last = labels; last->next != NULL; last = last->next) ;
1129 last->next = l;
1131 return l;
1133 return NULL;
1137 VMLabelInfo *vmFindLabel (const char *name) {
1138 if (name != NULL && name[0]) {
1139 for (VMLabelInfo *l = labels; l != NULL; l = l->next) {
1140 if (l->name && l->name[0] && strcasecmp(l->name, name) == 0) return l;
1143 return NULL;
1147 int vmFindPC (const char *name) {
1148 VMLabelInfo *l = vmFindLabel(name);
1150 if (l == NULL && l->type != LB_CODE) fatalis("no code label: '%s'", name);
1151 return l->value;
1155 int vmFindVarIndex (const char *name) {
1156 VMLabelInfo *l = vmFindLabel(name);
1158 if (l == NULL || (l->type != LB_GVAR && l->type != LB_TVAR && l->type != LB_SVAR)) fatalis("no var label: '%s'", name);
1159 return l->value;
1163 int vmFindGVarIndex (const char *name) {
1164 VMLabelInfo *l = vmFindLabel(name);
1166 if (l == NULL || l->type != LB_GVAR) fatalis("no global var label: '%s'", name);
1167 return l->value;
1171 int vmFindTVarIndex (const char *name) {
1172 VMLabelInfo *l = vmFindLabel(name);
1174 if (l == NULL || l->type != LB_TVAR) fatalis("no thread var label: '%s'", name);
1175 return l->value;
1179 int vmFindSVarIndex (const char *name) {
1180 VMLabelInfo *l = vmFindLabel(name);
1182 if (l == NULL || l->type != LB_SVAR) fatalis("no thread var label: '%s'", name);
1183 return l->value;
1187 int vmFindConst (const char *name) {
1188 VMLabelInfo *l = vmFindLabel(name);
1190 if (l == NULL || l->type != LB_CONST) fatalis("no const label: '%s'", name);
1191 return l->value;
1195 VMLabelInfo *vmFindMark (const char *name) {
1196 for (VMLabelInfo *l = labels; l != NULL; l = l->next) {
1197 if (l->type != LB_MARK) continue;
1198 if (name == NULL && l->name == NULL) return l;
1199 if (name != NULL && l->name != NULL && strcmp(name, l->name) == 0) return l;
1201 return NULL;
1205 #define XREAD(dest,size,errlabel) do { \
1206 if (pos+(size) > rsz) goto errlabel; \
1207 if ((size) > 0) memcpy((dest), buf+pos, (size)); \
1208 pos += (size); \
1209 } while (0)
1212 #define XREADB(dest,errlabel) do { \
1213 if (pos+1 > rsz) goto errlabel; \
1214 (dest) = buf[pos]; \
1215 ++pos; \
1216 } while (0)
1219 #define XREADW(dest,errlabel) do { \
1220 if (pos+2 > rsz) goto errlabel; \
1221 (dest) = buf[pos+1]; \
1222 (dest) <<= 8; \
1223 (dest) |= buf[pos+0]; \
1224 pos += 2; \
1225 } while (0)
1229 extern int vmLoadCodeFileFromDump (const void *data, int datasize, int pc, int gvfirst, int tvfirst, int *gvmax, int *tvmax) {
1230 int rsz = datasize, pos = 4;
1231 const uint8_t *dta = (const uint8_t *)data;
1232 uint8_t *buf;
1233 int csize, lcnt, rcnt, elcnt, tlast, glast;
1235 if (rsz < 4) return -1;
1236 if (memcmp(data, "AVM2", 4) != 0) goto quitbufonly;
1237 buf = malloc(rsz);
1238 if (buf == NULL) return 1;
1240 for (int f = 4; f < rsz; ++f) buf[f] = dta[f]^SECRET;
1242 XREADW(csize, quitbufonly);
1243 XREADW(rcnt, quitbufonly); // fixups
1244 XREADW(elcnt, quitbufonly); // extern labels
1245 XREADW(lcnt, quitbufonly); // labels
1246 XREADW(glast, quitbufonly); // last used global
1247 XREADW(tlast, quitbufonly); // last used thread local
1248 if (pc < 0 || pc+csize > 65535) goto quitbufonly;
1250 /*if (goobers)*/ //fprintf(stderr, "code: %d bytes, %d public labels, %d relocations, %d externs\n", csize, lcnt, rcnt, elcnt);
1252 XREAD(vmCode+pc, csize, quitbufonly);
1254 // do relocations
1255 for (int f = 0; f < rcnt; ++f) {
1256 int rel, newpc, val;
1258 XREADW(rel, quitbufonly);
1259 if (rel < 0 || rel+1 >= csize) goto quitbufonly;
1260 newpc = pc+rel; // fix here
1261 // get old value
1262 val = vmCode[newpc+1];
1263 val <<= 8;
1264 val |= vmCode[newpc+0];
1265 // fix it
1266 val += pc;
1267 // set new value
1268 vmCode[newpc+0] = val&0xff;
1269 vmCode[newpc+1] = (val>>8)&0xff;
1272 // load extern labels and perform fixups
1273 for (int f = 0; f < elcnt; ++f) {
1274 char name[258];
1275 int type, namelen;
1276 int rcnt;
1278 XREADB(type, quitbufonly);
1279 XREADB(namelen, quitbufonly);
1280 XREAD(name, namelen, quitbufonly);
1281 XREADW(rcnt, quitbufonly);
1283 if (type < LB_MIN_TYPE || type > LB_MAX_TYPE) goto quitbufonly;
1284 if (namelen < 1) goto quitbufonly;
1285 name[namelen] = 0;
1287 for (int c = 0; c < rcnt; ++c) {
1288 int xpc, size, val;
1289 VMLabelInfo *l;
1291 XREADB(size, quitbufonly);
1292 XREADW(xpc, quitbufonly);
1294 if (size != 1 && size != 2) goto quitbufonly;
1295 if (xpc < 0 || xpc+size > csize) goto quitbufonly;
1297 xpc += pc;
1298 l = vmFindLabel(name);
1299 if (l == NULL) {
1300 /*if (goobers)*/ fprintf(stderr, "VM: unknown extern: '%s'\n", name);
1301 goto quitbufonly;
1303 val = l->value;
1304 if (l->type == LB_GVAR) val |= 0x80;
1305 //fprintf(stderr, "%d: [%s]: ofs=%d, size=%d, value=%d (%d)\n", c, l->name, xpc, size, val, vmCode[xpc]);
1306 if (size == 1 && (val < -128 || val > 255)) {
1307 /*if (goobers)*/ fprintf(stderr, "VM: extern too big: '%s'\n", name);
1308 goto quitbufonly;
1310 vmCode[xpc+0] = val&0xff;
1311 if (size == 2) vmCode[xpc+1] = (val>>8)&0xff;
1315 vmLabelAddMark(NULL); // for this code labels
1316 for (int f = 0; f < lcnt; ++f) {
1317 unsigned char type, namelen, b;
1318 char name[258];
1319 int value;
1321 XREADB(type, quit);
1323 switch (type&0x7f) {
1324 case LB_GVAR:
1325 case LB_TVAR:
1326 XREADB(b, quit);
1327 value = b;
1328 break;
1329 case LB_SVAR:
1330 case LB_CONST:
1331 XREADW(value, quit);
1332 if (value >= 32768) value -= 65536;
1333 break;
1334 case LB_CODE:
1335 XREADW(value, quit);
1336 value += pc; // fixup
1337 break;
1338 default:
1339 fatalis("invalid label type: %d\n", type);
1340 goto quit;
1343 XREADB(namelen, quit);
1344 XREAD(name, namelen, quit);
1345 name[namelen] = 0;
1347 if (namelen > 0) vmAddLabel(name, type&0x7f, value, type&0x80?1:0);
1348 //if (l->type == LB_CODE) printf("%d/%d: [%s] %d : %d\n", f, lcnt, l->name, l->value-pc, l->value);
1352 int cnt;
1354 XREADW(cnt, quit);
1355 while (cnt-- > 0) {
1356 int fpc;
1358 XREADW(fpc, quit);
1359 if (gvfirst > 0) {
1360 //fprintf(stderr, "GVFIX at 0x%04x: %d --> %d\n", fpc, vmCode[pc+fpc], vmCode[pc+fpc]+gvfirst);
1361 vmCode[pc+fpc] += gvfirst;
1365 XREADW(cnt, quit);
1366 while (cnt-- > 0) {
1367 int fpc;
1369 XREADW(fpc, quit);
1370 if (tvfirst > 0) {
1371 //fprintf(stderr, "TVFIX at 0x%04x: %d --> %d\n", fpc, vmCode[pc+fpc], vmCode[pc+fpc]+tvfirst);
1372 vmCode[pc+fpc] += tvfirst;
1376 if (gvmax) *gvmax = glast+gvfirst+1;
1377 if (tvmax) *tvmax = tlast+tvfirst+1;
1379 free(buf);
1380 return csize;
1381 quit:
1382 vmFreeLabelsUntilMark(NULL);
1383 quitbufonly:
1384 free(buf);
1385 return -1;
1389 ////////////////////////////////////////////////////////////////////////////////
1390 int vmSaveState (FILE *fl) {
1391 int lcnt = 0;
1393 fixLastThreadId();
1395 if (writeDW(fl, VM_VARS_SIZE) != 0) return -1;
1396 //if (writeDW(fl, VM_STACK_SIZE) != 0) return -1;
1397 if (writeDW(fl, vmMaxGVar) != 0) return -1;
1398 if (writeDW(fl, vmMaxTVar) != 0) return -1;
1399 if (writeDW(fl, vmLastThreadId) != 0) return -1;
1400 if (writeDW(fl, vmCodeSize) != 0) return -1;
1401 if (writeBuf(fl, vmCode, vmCodeSize) != 0) return -1;
1402 for (int f = 0; f < VM_VARS_SIZE; ++f) if (writeDW(fl, vmGVars[f]) != 0) return -1;
1403 for (int f = 0; f <= vmLastThreadId; ++f) {
1404 if (vmThreads[f].stack != NULL) {
1405 if (writeDW(fl, f) != 0) return -1;
1406 if (writeDW(fl, vmThreads[f].pc) != 0) return -1;
1407 if (writeDW(fl, vmThreads[f].suspended) != 0) return -1;
1408 if (writeDW(fl, vmThreads[f].sp) != 0) return -1;
1409 for (int c = 0; c < vmThreads[f].sp; ++c) if (writeDW(fl, vmThreads[f].stack[c]) != 0) return -1;
1410 for (int c = 0; c < VM_VARS_SIZE; ++c) if (writeDW(fl, vmThreads[f].tvars[c]) != 0) return -1;
1411 } else {
1412 if (writeDW(fl, -1) != 0) return -1;
1416 for (VMLabelInfo *l = labels; l != NULL; l = l->next) ++lcnt;
1417 if (writeDW(fl, lcnt) != 0) return -1;
1418 for (VMLabelInfo *l = labels; l != NULL; l = l->next) {
1419 int nlen = l->name != NULL ? strlen(l->name) : -1;
1421 if (writeDW(fl, l->type) != 0) return -1;
1422 if (writeDW(fl, nlen) != 0) return -1;
1423 if (nlen > 0) {
1424 if (writeBuf(fl, l->name, nlen) != 0) return -1;
1426 if (writeDW(fl, l->value) != 0) return -1;
1427 if (writeDW(fl, l->pub) != 0) return -1;
1430 return 0;
1434 int vmLoadState (FILE *fl) {
1435 int v, lcnt;
1437 vmDeinitialize();
1438 if (vmInitialize() != 0) goto fail;
1440 if (readDW(fl, &v) != 0 || v != VM_VARS_SIZE) goto fail;
1441 //if (readDW(fl, &ssz) != 0) goto fail;
1442 if (readDW(fl, &vmMaxGVar) != 0 || vmMaxGVar < 0 || vmMaxGVar > VM_VARS_SIZE) goto fail;
1443 if (readDW(fl, &vmMaxTVar) != 0 || vmMaxTVar < 0 || vmMaxTVar > VM_VARS_SIZE) goto fail;
1444 if (readDW(fl, &vmLastThreadId) != 0 || vmLastThreadId < 0 || vmLastThreadId > VM_MAX_THREADS) goto fail;
1445 if (readDW(fl, &vmCodeSize) != 0 || vmCodeSize < 1 || vmCodeSize > 65536) goto fail;
1446 if (readBuf(fl, vmCode, vmCodeSize) != 0) goto fail;
1447 for (int f = 0; f < VM_VARS_SIZE; ++f) if (readDW(fl, &vmGVars[f]) != 0) goto fail;
1448 for (int f = 0; f <= vmLastThreadId; ++f) {
1449 int flag;
1451 if (readDW(fl, &flag) != 0) goto fail;
1452 if (flag == -1) continue;
1453 if (flag != f) goto fail;
1454 if (vmInitThread(&vmThreads[f]) != 0) goto fail;
1455 if (readDW(fl, &vmThreads[f].pc) != 0) goto fail;
1456 if (readDW(fl, &vmThreads[f].suspended) != 0) goto fail;
1457 if (readDW(fl, &vmThreads[f].sp) != 0) goto fail;
1458 if (!vmThreads[f].suspended) {
1459 if (vmThreads[f].pc < 0 || vmThreads[f].pc >= vmCodeSize) goto fail;
1460 if (vmThreads[f].sp < 0 || vmThreads[f].sp > VM_STACK_SIZE) goto fail;
1462 for (int c = 0; c < vmThreads[f].sp; ++c) if (readDW(fl, &vmThreads[f].stack[c]) != 0) goto fail;
1463 for (int c = 0; c < VM_VARS_SIZE; ++c) if (readDW(fl, &vmThreads[f].tvars[c]) != 0) goto fail;
1466 if (readDW(fl, &lcnt) != 0) goto fail;
1467 for (; lcnt > 0; --lcnt) {
1468 int type, namelen, value, pub;
1469 char name[257];
1471 if (readDW(fl, &type) != 0) goto fail;
1472 if (readDW(fl, &namelen) != 0) goto fail;
1473 if (namelen > 255) goto fail;
1474 if (namelen >= 0) {
1475 if (readBuf(fl, name, namelen) != 0) goto fail;
1476 name[namelen] = 0;
1478 if (readDW(fl, &value) != 0) goto fail;
1479 if (readDW(fl, &pub) != 0) goto fail;
1480 vmAddLabelToEnd(namelen>=0 ? name : NULL, type, value, pub);
1482 return 0;
1483 fail:
1484 vmDeinitialize();
1485 return -1;