]> git.xonotic.org Git - xonotic/gmqcc.git/blob - exec.c
2e1f58d476baf9f99c622153dcfaa5093b884505
[xonotic/gmqcc.git] / exec.c
1 #include <errno.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdarg.h>
5
6 #include "gmqcc.h"
7
8 MEM_VEC_FUNCTIONS(qc_program,   prog_section_statement, code)
9 MEM_VEC_FUNCTIONS(qc_program,   prog_section_def,       defs)
10 MEM_VEC_FUNCTIONS(qc_program,   prog_section_def,       fields)
11 MEM_VEC_FUNCTIONS(qc_program,   prog_section_function,  functions)
12 MEM_VEC_FUNCTIONS(qc_program,   char,                   strings)
13 MEM_VEC_FUN_APPEND(qc_program,  char,                   strings)
14 MEM_VEC_FUN_RESIZE(qc_program,  char,                   strings)
15 MEM_VEC_FUNCTIONS(qc_program,   qcint,                  globals)
16 MEM_VEC_FUNCTIONS(qc_program,   qcint,                  entitydata)
17 MEM_VEC_FUNCTIONS(qc_program,   bool,                   entitypool)
18
19 MEM_VEC_FUNCTIONS(qc_program,   qcint,         localstack)
20 MEM_VEC_FUN_APPEND(qc_program,  qcint,         localstack)
21 MEM_VEC_FUN_RESIZE(qc_program,  qcint,         localstack)
22 MEM_VEC_FUNCTIONS(qc_program,   qc_exec_stack, stack)
23
24 MEM_VEC_FUNCTIONS(qc_program,   size_t, profile)
25 MEM_VEC_FUN_RESIZE(qc_program,  size_t, profile)
26
27 MEM_VEC_FUNCTIONS(qc_program,   prog_builtin, builtins)
28
29 static void loaderror(const char *fmt, ...)
30 {
31     int     err = errno;
32     va_list ap;
33     va_start(ap, fmt);
34     vprintf(fmt, ap);
35     va_end(ap);
36     printf(": %s\n", strerror(err));
37 }
38
39 static void qcvmerror(qc_program *prog, const char *fmt, ...)
40 {
41     va_list ap;
42
43     prog->vmerror++;
44
45     va_start(ap, fmt);
46     vprintf(fmt, ap);
47     va_end(ap);
48     putchar('\n');
49 }
50
51 qc_program* prog_load(const char *filename)
52 {
53     qc_program *prog;
54     prog_header header;
55     size_t      i;
56     FILE *file;
57
58     file = util_fopen(filename, "rb");
59     if (!file)
60         return NULL;
61
62     if (fread(&header, sizeof(header), 1, file) != 1) {
63         loaderror("failed to read header from '%s'", filename);
64         fclose(file);
65         return NULL;
66     }
67
68     if (header.version != 6) {
69         loaderror("header says this is a version %i progs, we need version 6\n", header.version);
70         fclose(file);
71         return NULL;
72     }
73
74     prog = (qc_program*)mem_a(sizeof(qc_program));
75     if (!prog) {
76         fclose(file);
77         printf("failed to allocate program data\n");
78         return NULL;
79     }
80     memset(prog, 0, sizeof(*prog));
81
82     prog->entityfields = header.entfield;
83
84     prog->filename = util_strdup(filename);
85     if (!prog->filename) {
86         loaderror("failed to store program name");
87         goto error;
88     }
89
90 #define read_data(hdrvar, progvar, type)                                         \
91     if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) {                      \
92         loaderror("seek failed");                                                \
93         goto error;                                                              \
94     }                                                                            \
95     prog->progvar##_alloc = header.hdrvar.length;                                \
96     prog->progvar##_count = header.hdrvar.length;                                \
97     prog->progvar = (type*)mem_a(header.hdrvar.length * sizeof(*prog->progvar)); \
98     if (!prog->progvar)                                                          \
99         goto error;                                                              \
100     if (fread(prog->progvar, sizeof(*prog->progvar), header.hdrvar.length, file) \
101         != header.hdrvar.length) {                                               \
102         loaderror("read failed");                                                \
103         goto error;                                                              \
104     }
105 #define read_data1(x, y) read_data(x, x, y)
106
107     read_data (statements, code, prog_section_statement);
108     read_data1(defs,             prog_section_def);
109     read_data1(fields,           prog_section_def);
110     read_data1(functions,        prog_section_function);
111     read_data1(strings,          char);
112     read_data1(globals,          qcint);
113
114     fclose(file);
115
116     /* profile counters */
117     if (!qc_program_profile_resize(prog, prog->code_count))
118         goto error;
119
120     /* Add tempstring area */
121     prog->tempstring_start = prog->strings_count;
122     prog->tempstring_at    = prog->strings_count;
123     if (!qc_program_strings_resize(prog, prog->strings_count + 16*1024))
124         goto error;
125
126     /* spawn the world entity */
127     if (!qc_program_entitypool_add(prog, true)) {
128         loaderror("failed to allocate world entity\n");
129         goto error;
130     }
131     for (i = 0; i < prog->entityfields; ++i) {
132         if (!qc_program_entitydata_add(prog, 0)) {
133             loaderror("failed to allocate world data\n");
134             goto error;
135         }
136     }
137     prog->entities = 1;
138
139     return prog;
140
141 error:
142     if (prog->filename)   mem_d(prog->filename);
143     if (prog->code)       mem_d(prog->code);
144     if (prog->defs)       mem_d(prog->defs);
145     if (prog->fields)     mem_d(prog->fields);
146     if (prog->functions)  mem_d(prog->functions);
147     if (prog->strings)    mem_d(prog->strings);
148     if (prog->globals)    mem_d(prog->globals);
149     if (prog->entitydata) mem_d(prog->entitydata);
150     if (prog->entitypool) mem_d(prog->entitypool);
151     mem_d(prog);
152     return NULL;
153 }
154
155 void prog_delete(qc_program *prog)
156 {
157     if (prog->filename) mem_d(prog->filename);
158     MEM_VECTOR_CLEAR(prog, code);
159     MEM_VECTOR_CLEAR(prog, defs);
160     MEM_VECTOR_CLEAR(prog, fields);
161     MEM_VECTOR_CLEAR(prog, functions);
162     MEM_VECTOR_CLEAR(prog, strings);
163     MEM_VECTOR_CLEAR(prog, globals);
164     MEM_VECTOR_CLEAR(prog, entitydata);
165     MEM_VECTOR_CLEAR(prog, entitypool);
166     MEM_VECTOR_CLEAR(prog, localstack);
167     MEM_VECTOR_CLEAR(prog, stack);
168     MEM_VECTOR_CLEAR(prog, profile);
169
170     if (prog->builtins_alloc) {
171         MEM_VECTOR_CLEAR(prog, builtins);
172     }
173     /* otherwise the builtins were statically allocated */
174     mem_d(prog);
175 }
176
177 /***********************************************************************
178  * VM code
179  */
180
181 char* prog_getstring(qc_program *prog, qcint str)
182 {
183     if (str < 0 || str >= prog->strings_count)
184         return "<<<invalid string>>>";
185     return prog->strings + str;
186 }
187
188 prog_section_def* prog_entfield(qc_program *prog, qcint off)
189 {
190     size_t i;
191     for (i = 0; i < prog->fields_count; ++i) {
192         if (prog->fields[i].offset == off)
193             return (prog->fields + i);
194     }
195     return NULL;
196 }
197
198 prog_section_def* prog_getdef(qc_program *prog, qcint off)
199 {
200     size_t i;
201     for (i = 0; i < prog->defs_count; ++i) {
202         if (prog->defs[i].offset == off)
203             return (prog->defs + i);
204     }
205     return NULL;
206 }
207
208 qcany* prog_getedict(qc_program *prog, qcint e)
209 {
210     if (e >= prog->entitypool_count) {
211         prog->vmerror++;
212         printf("Accessing out of bounds edict %i\n", (int)e);
213         e = 0;
214     }
215     return (qcany*)(prog->entitydata + (prog->entityfields * e));
216 }
217
218 qcint prog_spawn_entity(qc_program *prog)
219 {
220     size_t i;
221     qcint  e;
222     for (e = 0; e < (qcint)prog->entitypool_count; ++e) {
223         if (!prog->entitypool[e]) {
224             char *data = (char*)(prog->entitydata + (prog->entityfields * e));
225             memset(data, 0, prog->entityfields * sizeof(qcint));
226             return e;
227         }
228     }
229     if (!qc_program_entitypool_add(prog, true)) {
230         prog->vmerror++;
231         printf("Failed to allocate entity\n");
232         return 0;
233     }
234     prog->entities++;
235     for (i = 0; i < prog->entityfields; ++i) {
236         if (!qc_program_entitydata_add(prog, 0)) {
237             printf("Failed to allocate entity\n");
238             return 0;
239         }
240     }
241     return e;
242 }
243
244 void prog_free_entity(qc_program *prog, qcint e)
245 {
246     if (!e) {
247         prog->vmerror++;
248         printf("Trying to free world entity\n");
249         return;
250     }
251     if (e >= prog->entitypool_count) {
252         prog->vmerror++;
253         printf("Trying to free out of bounds entity\n");
254         return;
255     }
256     if (!prog->entitypool[e]) {
257         prog->vmerror++;
258         printf("Double free on entity\n");
259         return;
260     }
261     prog->entitypool[e] = false;
262 }
263
264 qcint prog_tempstring(qc_program *prog, const char *_str)
265 {
266     /* we don't access it, but the macro-generated functions don't use
267      * const
268      */
269     char *str = (char*)_str;
270
271     size_t len = strlen(str);
272     size_t at = prog->tempstring_at;
273
274     /* when we reach the end we start over */
275     if (at + len >= prog->strings_count)
276         at = prog->tempstring_start;
277
278     /* when it doesn't fit, reallocate */
279     if (at + len >= prog->strings_count)
280     {
281         prog->strings_count = at;
282         if (!qc_program_strings_append(prog, str, len+1)) {
283             prog->vmerror = VMERR_TEMPSTRING_ALLOC;
284             return 0;
285         }
286         return at;
287     }
288
289     /* when it fits, just copy */
290     memcpy(prog->strings + at, str, len+1);
291     prog->tempstring_at += len+1;
292     return at;
293 }
294
295 static int print_escaped_string(const char *str)
296 {
297     int len = 2;
298     putchar('"');
299     while (*str) {
300         switch (*str) {
301             case '\a': len += 2; putchar('\\'); putchar('a'); break;
302             case '\b': len += 2; putchar('\\'); putchar('b'); break;
303             case '\r': len += 2; putchar('\\'); putchar('r'); break;
304             case '\n': len += 2; putchar('\\'); putchar('n'); break;
305             case '\t': len += 2; putchar('\\'); putchar('t'); break;
306             case '\f': len += 2; putchar('\\'); putchar('f'); break;
307             case '\v': len += 2; putchar('\\'); putchar('v'); break;
308             case '\\': len += 2; putchar('\\'); putchar('\\'); break;
309             case '"':  len += 2; putchar('\\'); putchar('"'); break;
310             default:
311                 ++len;
312                 putchar(*str);
313                 break;
314         }
315         ++str;
316     }
317     putchar('"');
318     return len;
319 }
320
321 static void trace_print_global(qc_program *prog, unsigned int glob, int vtype)
322 {
323     static char spaces[40+1] = "                                        ";
324     prog_section_def *def;
325     qcany    *value;
326     int       len;
327
328     if (!glob) {
329         len = printf("<null>,");
330         goto done;
331     }
332
333     def = prog_getdef(prog, glob);
334     value = (qcany*)(&prog->globals[glob]);
335
336     if (def) {
337         len = printf("[%s] ", prog_getstring(prog, def->name));
338         vtype = def->type;
339     }
340     else
341         len = printf("[@%u] ", glob);
342
343     switch (vtype) {
344         case TYPE_VOID:
345         case TYPE_ENTITY:
346         case TYPE_FIELD:
347         case TYPE_FUNCTION:
348         case TYPE_POINTER:
349             len += printf("%i,", value->_int);
350             break;
351         case TYPE_VECTOR:
352             len += printf("'%g %g %g',", value->vector[0],
353                                          value->vector[1],
354                                          value->vector[2]);
355             break;
356         case TYPE_STRING:
357             len += print_escaped_string(prog_getstring(prog, value->string));
358             len += printf(",");
359             /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
360             break;
361         case TYPE_FLOAT:
362         default:
363             len += printf("%g,", value->_float);
364             break;
365     }
366 done:
367     if (len < sizeof(spaces)-1) {
368         spaces[sizeof(spaces)-1-len] = 0;
369         printf(spaces);
370         spaces[sizeof(spaces)-1-len] = ' ';
371     }
372 }
373
374 static void prog_print_statement(qc_program *prog, prog_section_statement *st)
375 {
376     if (st->opcode >= (sizeof(asm_instr)/sizeof(asm_instr[0]))) {
377         printf("<illegal instruction %d>\n", st->opcode);
378         return;
379     }
380     printf(" <> %-12s", asm_instr[st->opcode].m);
381     if (st->opcode >= INSTR_IF &&
382         st->opcode <= INSTR_IFNOT)
383     {
384         trace_print_global(prog, st->o1.u1, TYPE_FLOAT);
385         printf("%d\n", st->o2.s1);
386     }
387     else if (st->opcode >= INSTR_CALL0 &&
388              st->opcode <= INSTR_CALL8)
389     {
390         printf("\n");
391     }
392     else if (st->opcode == INSTR_GOTO)
393     {
394         printf("%i\n", st->o1.s1);
395     }
396     else
397     {
398         int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT };
399         switch (st->opcode)
400         {
401             case INSTR_MUL_FV:
402                 t[1] = t[2] = TYPE_VECTOR;
403                 break;
404             case INSTR_MUL_VF:
405                 t[0] = t[2] = TYPE_VECTOR;
406                 break;
407             case INSTR_MUL_V:
408                 t[0] = t[1] = TYPE_VECTOR;
409                 break;
410             case INSTR_ADD_V:
411             case INSTR_SUB_V:
412             case INSTR_EQ_V:
413             case INSTR_NE_V:
414                 t[0] = t[1] = t[2] = TYPE_VECTOR;
415                 break;
416             case INSTR_EQ_S:
417             case INSTR_NE_S:
418                 t[0] = t[1] = TYPE_STRING;
419                 break;
420             case INSTR_STORE_F:
421             case INSTR_STOREP_F:
422                 t[2] = -1;
423                 break;
424             case INSTR_STORE_V:
425                 t[0] = t[1] = TYPE_VECTOR; t[2] = -1;
426                 break;
427             case INSTR_STORE_S:
428                 t[0] = t[1] = TYPE_STRING; t[2] = -1;
429                 break;
430             case INSTR_STORE_ENT:
431                 t[0] = t[1] = TYPE_ENTITY; t[2] = -1;
432                 break;
433             case INSTR_STORE_FLD:
434                 t[0] = t[1] = TYPE_FIELD; t[2] = -1;
435                 break;
436             case INSTR_STORE_FNC:
437                 t[0] = t[1] = TYPE_FUNCTION; t[2] = -1;
438                 break;
439             case INSTR_STOREP_V:
440                 t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1;
441                 break;
442             case INSTR_STOREP_S:
443                 t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1;
444                 break;
445             case INSTR_STOREP_ENT:
446                 t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1;
447                 break;
448             case INSTR_STOREP_FLD:
449                 t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1;
450                 break;
451             case INSTR_STOREP_FNC:
452                 t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1;
453                 break;
454         }
455         if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]);
456         else           printf("(none),          ");
457         if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]);
458         else           printf("(none),          ");
459         if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]);
460         else           printf("(none)");
461         printf("\n");
462     }
463     fflush(stdout);
464 }
465
466 static qcint prog_enterfunction(qc_program *prog, prog_section_function *func)
467 {
468     qc_exec_stack st;
469     size_t p, parampos;
470
471     /* back up locals */
472     st.localsp  = prog->localstack_count;
473     st.stmt     = prog->statement;
474     st.function = func;
475
476 #ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
477     if (prog->stack_count)
478     {
479         prog_section_function *cur;
480         cur = prog->stack[prog->stack_count-1].function;
481         if (cur)
482         {
483             qcint *globals = prog->globals + cur->firstlocal;
484             if (!qc_program_localstack_append(prog, globals, cur->locals))
485             {
486                 printf("out of memory\n");
487                 exit(1);
488             }
489         }
490     }
491 #else
492     {
493         qcint *globals = prog->globals + func->firstlocal;
494         if (!qc_program_localstack_append(prog, globals, func->locals))
495         {
496             printf("out of memory\n");
497             exit(1);
498         }
499     }
500 #endif
501
502     /* copy parameters */
503     parampos = func->firstlocal;
504     for (p = 0; p < func->nargs; ++p)
505     {
506         size_t s;
507         for (s = 0; s < func->argsize[p]; ++s) {
508             prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s];
509             ++parampos;
510         }
511     }
512
513     if (!qc_program_stack_add(prog, st)) {
514         printf("out of memory\n");
515         exit(1);
516     }
517
518     return func->entry;
519 }
520
521 static qcint prog_leavefunction(qc_program *prog)
522 {
523     prog_section_function *prev = NULL;
524     size_t oldsp;
525
526     qc_exec_stack st = prog->stack[prog->stack_count-1];
527
528 #ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
529     if (prog->stack_count > 1) {
530         prev  = prog->stack[prog->stack_count-2].function;
531         oldsp = prog->stack[prog->stack_count-2].localsp;
532     }
533 #else
534     prev  = prog->stack[prog->stack_count-1].function;
535     oldsp = prog->stack[prog->stack_count-1].localsp;
536 #endif
537     if (prev) {
538         qcint *globals = prog->globals + prev->firstlocal;
539         memcpy(globals, prog->localstack + oldsp, prev->locals);
540         if (!qc_program_localstack_resize(prog, oldsp)) {
541             printf("out of memory\n");
542             exit(1);
543         }
544     }
545
546     if (!qc_program_stack_remove(prog, prog->stack_count-1)) {
547         printf("out of memory\n");
548         exit(1);
549     }
550
551     return st.stmt - 1; /* offset the ++st */
552 }
553
554 bool prog_exec(qc_program *prog, prog_section_function *func, size_t flags, long maxjumps)
555 {
556     long jumpcount = 0;
557     prog_section_statement *st;
558
559     prog->vmerror = 0;
560
561     st = prog->code + prog_enterfunction(prog, func);
562     --st;
563     switch (flags)
564     {
565         default:
566         case 0:
567         {
568 #define QCVM_PROFILE 0
569 #define QCVM_TRACE   0
570 #           include "execloop.h"
571             break;
572         }
573         case (VMXF_TRACE):
574         {
575 #define QCVM_PROFILE 0
576 #define QCVM_TRACE   1
577 #           include "execloop.h"
578             break;
579         }
580         case (VMXF_PROFILE):
581         {
582 #define QCVM_PROFILE 1
583 #define QCVM_TRACE   0
584 #           include "execloop.h"
585             break;
586         }
587         case (VMXF_TRACE|VMXF_PROFILE):
588         {
589 #define QCVM_PROFILE 1
590 #define QCVM_TRACE   1
591 #           include "execloop.h"
592             break;
593         }
594     };
595
596 cleanup:
597     prog->localstack_count = 0;
598     prog->stack_count = 0;
599     if (prog->vmerror)
600         return false;
601     return true;
602 }
603
604 /***********************************************************************
605  * main for when building the standalone executor
606  */
607
608 #if defined(QCVM_EXECUTOR)
609 bool        opts_debug    = false;
610 bool        opts_memchk   = false;
611
612 #define CheckArgs(num) do {                                                    \
613     if (prog->argc != (num)) {                                                 \
614         prog->vmerror++;                                                       \
615         printf("ERROR: invalid number of arguments for %s: %i, expected %i\n", \
616         __FUNCTION__, prog->argc, (num));                                      \
617         return -1;                                                             \
618     }                                                                          \
619 } while (0)
620
621 #define GetGlobal(idx) ((qcany*)(prog->globals + (idx)))
622 #define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
623 #define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
624
625 static int qc_print(qc_program *prog)
626 {
627     size_t i;
628     for (i = 0; i < prog->argc; ++i) {
629         qcany *str = (qcany*)(prog->globals + OFS_PARM0 + 3*i);
630         printf("%s", prog_getstring(prog, str->string));
631     }
632     return 0;
633 }
634
635 static int qc_ftos(qc_program *prog)
636 {
637     char buffer[512];
638     qcany *num;
639     qcany str;
640     CheckArgs(1);
641     num = GetArg(0);
642     snprintf(buffer, sizeof(buffer), "%g", num->_float);
643     str.string = prog_tempstring(prog, buffer);
644     Return(str);
645     return 0;
646 }
647
648 static int qc_spawn(qc_program *prog)
649 {
650     qcany ent;
651     CheckArgs(0);
652     ent.edict = prog_spawn_entity(prog);
653     Return(ent);
654     return (ent.edict ? 0 : -1);
655 }
656
657 static int qc_kill(qc_program *prog)
658 {
659     qcany *ent;
660     CheckArgs(1);
661     ent = GetArg(0);
662     prog_free_entity(prog, ent->edict);
663     return 0;
664 }
665
666 static prog_builtin qc_builtins[] = {
667     NULL,
668     &qc_print,
669     &qc_ftos,
670     &qc_spawn,
671     &qc_kill
672 };
673 static size_t qc_builtins_count = sizeof(qc_builtins) / sizeof(qc_builtins[0]);
674
675 static const char *arg0 = NULL;
676
677 void usage()
678 {
679     printf("usage: [-debug] %s file\n", arg0);
680     exit(1);
681 }
682
683 int main(int argc, char **argv)
684 {
685     size_t      i;
686     qcint       fnmain = -1;
687     qc_program *prog;
688     size_t      xflags = VMXF_DEFAULT;
689
690     arg0 = argv[0];
691
692     if (argc < 2)
693         usage();
694
695     while (argc > 2) {
696         if (!strcmp(argv[1], "-trace")) {
697             --argc;
698             ++argv;
699             xflags |= VMXF_TRACE;
700         }
701         else if (!strcmp(argv[1], "-profile")) {
702             --argc;
703             ++argv;
704             xflags |= VMXF_PROFILE;
705         }
706         else
707             usage();
708     }
709
710
711     prog = prog_load(argv[1]);
712     if (!prog) {
713         printf("failed to load program '%s'\n", argv[1]);
714         exit(1);
715     }
716
717     prog->builtins       = qc_builtins;
718     prog->builtins_count = qc_builtins_count;
719     prog->builtins_alloc = 0;
720
721     for (i = 1; i < prog->functions_count; ++i) {
722         const char *name = prog_getstring(prog, prog->functions[i].name);
723         /* printf("Found function: %s\n", name); */
724         if (!strcmp(name, "main"))
725             fnmain = (qcint)i;
726     }
727     printf("Entity field space: %i\n", (int)prog->entityfields);
728     if (fnmain > 0)
729     {
730         prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
731     }
732     else
733         printf("No main function found\n");
734
735     prog_delete(prog);
736     return 0;
737 }
738 #endif