]> git.xonotic.org Git - xonotic/gmqcc.git/blob - exec.c
qcvm: -trace flag
[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[16+1] = "            ";
324     prog_section_def *def;
325     qcany    *value;
326     int       len;
327
328     if (!glob)
329         return;
330
331     def = prog_getdef(prog, glob);
332     value = (qcany*)(&prog->globals[glob]);
333
334     if (def) {
335         len = printf("[%s] ", prog_getstring(prog, def->name));
336         vtype = def->type;
337     }
338     else
339         len = printf("[@%u] ", glob);
340
341     switch (vtype) {
342         case TYPE_VOID:
343         case TYPE_ENTITY:
344         case TYPE_FIELD:
345         case TYPE_FUNCTION:
346         case TYPE_POINTER:
347             len += printf("%i,", value->_int);
348             break;
349         case TYPE_VECTOR:
350             len += printf("'%g %g %g',", value->vector[0],
351                                          value->vector[1],
352                                          value->vector[2]);
353             break;
354         case TYPE_STRING:
355             len += print_escaped_string(prog_getstring(prog, value->string));
356             /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
357             break;
358         case TYPE_FLOAT:
359         default:
360             len += printf("%g,", value->_float);
361             break;
362     }
363     if (len < 16) {
364         spaces[16-len] = 0;
365         printf(spaces);
366         spaces[16-len] = ' ';
367     }
368 }
369
370 static void prog_print_statement(qc_program *prog, prog_section_statement *st)
371 {
372     if (st->opcode >= (sizeof(asm_instr)/sizeof(asm_instr[0]))) {
373         printf("<illegal instruction %d>\n", st->opcode);
374         return;
375     }
376     printf(" <> %-12s", asm_instr[st->opcode].m);
377     if (st->opcode >= INSTR_IF &&
378         st->opcode <= INSTR_IFNOT)
379     {
380         trace_print_global(prog, st->o1.u1, TYPE_FLOAT);
381         printf("%d\n", st->o2.s1);
382     }
383     else if (st->opcode >= INSTR_CALL0 &&
384              st->opcode <= INSTR_CALL8)
385     {
386         printf("\n");
387     }
388     else if (st->opcode == INSTR_GOTO)
389     {
390         printf("%i\n", st->o1.s1);
391     }
392     else
393     {
394         int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT };
395         switch (st->opcode)
396         {
397             case INSTR_MUL_FV:
398                 t[1] = t[2] = TYPE_VECTOR;
399                 break;
400             case INSTR_MUL_VF:
401                 t[0] = t[2] = TYPE_VECTOR;
402                 break;
403             case INSTR_MUL_V:
404                 t[0] = t[1] = TYPE_VECTOR;
405                 break;
406             case INSTR_ADD_V:
407             case INSTR_SUB_V:
408             case INSTR_EQ_V:
409             case INSTR_NE_V:
410                 t[0] = t[1] = t[2] = TYPE_VECTOR;
411                 break;
412             case INSTR_EQ_S:
413             case INSTR_NE_S:
414                 t[0] = t[1] = TYPE_STRING;
415                 break;
416             case INSTR_STORE_V:
417                 t[0] = t[1] = TYPE_VECTOR; t[2] = -1;
418                 break;
419             case INSTR_STORE_S:
420                 t[0] = t[1] = TYPE_STRING; t[2] = -1;
421                 break;
422         }
423         if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]);
424         if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]);
425         if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]);
426         printf("\n");
427     }
428 }
429
430 static qcint prog_enterfunction(qc_program *prog, prog_section_function *func)
431 {
432     qc_exec_stack st;
433     size_t p, parampos;
434
435     /* back up locals */
436     st.localsp  = prog->localstack_count;
437     st.stmt     = prog->statement;
438     st.function = func;
439
440 #ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
441     if (prog->stack_count)
442     {
443         prog_section_function *cur;
444         cur = prog->stack[prog->stack_count-1].function;
445         if (cur)
446         {
447             qcint *globals = prog->globals + cur->firstlocal;
448             if (!qc_program_localstack_append(prog, globals, cur->locals))
449             {
450                 printf("out of memory\n");
451                 exit(1);
452             }
453         }
454     }
455 #else
456     {
457         qcint *globals = prog->globals + func->firstlocal;
458         if (!qc_program_localstack_append(prog, globals, func->locals))
459         {
460             printf("out of memory\n");
461             exit(1);
462         }
463     }
464 #endif
465
466     /* copy parameters */
467     parampos = func->firstlocal;
468     for (p = 0; p < func->nargs; ++p)
469     {
470         size_t s;
471         for (s = 0; s < func->argsize[p]; ++s) {
472             prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s];
473             ++parampos;
474         }
475     }
476
477     if (!qc_program_stack_add(prog, st)) {
478         printf("out of memory\n");
479         exit(1);
480     }
481
482     return func->entry;
483 }
484
485 static qcint prog_leavefunction(qc_program *prog)
486 {
487     prog_section_function *prev = NULL;
488     size_t oldsp;
489
490     qc_exec_stack st = prog->stack[prog->stack_count-1];
491
492 #ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
493     if (prog->stack_count > 1) {
494         prev  = prog->stack[prog->stack_count-2].function;
495         oldsp = prog->stack[prog->stack_count-2].localsp;
496     }
497 #else
498     prev  = prog->stack[prog->stack_count-1].function;
499     oldsp = prog->stack[prog->stack_count-1].localsp;
500 #endif
501     if (prev) {
502         qcint *globals = prog->globals + prev->firstlocal;
503         memcpy(globals, prog->localstack + oldsp, prev->locals);
504         if (!qc_program_localstack_resize(prog, oldsp)) {
505             printf("out of memory\n");
506             exit(1);
507         }
508     }
509
510     if (!qc_program_stack_remove(prog, prog->stack_count-1)) {
511         printf("out of memory\n");
512         exit(1);
513     }
514
515     return st.stmt - 1; /* offset the ++st */
516 }
517
518 bool prog_exec(qc_program *prog, prog_section_function *func, size_t flags, long maxjumps)
519 {
520     long jumpcount = 0;
521     prog_section_statement *st;
522
523     prog->vmerror = 0;
524
525     st = prog->code + prog_enterfunction(prog, func);
526     --st;
527     switch (flags)
528     {
529         default:
530         case 0:
531         {
532 #define QCVM_PROFILE 0
533 #define QCVM_TRACE   0
534 #           include "execloop.h"
535             break;
536         }
537         case (VMXF_TRACE):
538         {
539 #define QCVM_PROFILE 0
540 #define QCVM_TRACE   1
541 #           include "execloop.h"
542             break;
543         }
544         case (VMXF_PROFILE):
545         {
546 #define QCVM_PROFILE 1
547 #define QCVM_TRACE   0
548 #           include "execloop.h"
549             break;
550         }
551         case (VMXF_TRACE|VMXF_PROFILE):
552         {
553 #define QCVM_PROFILE 1
554 #define QCVM_TRACE   1
555 #           include "execloop.h"
556             break;
557         }
558     };
559
560 cleanup:
561     prog->localstack_count = 0;
562     prog->stack_count = 0;
563     if (prog->vmerror)
564         return false;
565     return true;
566 }
567
568 /***********************************************************************
569  * main for when building the standalone executor
570  */
571
572 #if defined(QCVM_EXECUTOR)
573 bool        opts_debug    = false;
574 bool        opts_memchk   = false;
575
576 #define CheckArgs(num) do {                                                    \
577     if (prog->argc != (num)) {                                                 \
578         prog->vmerror++;                                                       \
579         printf("ERROR: invalid number of arguments for %s: %i, expected %i\n", \
580         __FUNCTION__, prog->argc, (num));                                      \
581         return -1;                                                             \
582     }                                                                          \
583 } while (0)
584
585 #define GetGlobal(idx) ((qcany*)(prog->globals + (idx)))
586 #define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
587 #define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
588
589 static int qc_print(qc_program *prog)
590 {
591     size_t i;
592     for (i = 0; i < prog->argc; ++i) {
593         qcany *str = (qcany*)(prog->globals + OFS_PARM0 + 3*i);
594         printf("%s", prog_getstring(prog, str->string));
595     }
596     return 0;
597 }
598
599 static int qc_ftos(qc_program *prog)
600 {
601     char buffer[512];
602     qcany *num;
603     qcany str;
604     CheckArgs(1);
605     num = GetArg(0);
606     snprintf(buffer, sizeof(buffer), "%g", num->_float);
607     str.string = prog_tempstring(prog, buffer);
608     Return(str);
609     return 0;
610 }
611
612 static int qc_spawn(qc_program *prog)
613 {
614     qcany ent;
615     CheckArgs(0);
616     ent.edict = prog_spawn_entity(prog);
617     Return(ent);
618     return (ent.edict ? 0 : -1);
619 }
620
621 static int qc_kill(qc_program *prog)
622 {
623     qcany *ent;
624     CheckArgs(1);
625     ent = GetArg(0);
626     prog_free_entity(prog, ent->edict);
627     return 0;
628 }
629
630 static prog_builtin qc_builtins[] = {
631     NULL,
632     &qc_print,
633     &qc_ftos,
634     &qc_spawn,
635     &qc_kill
636 };
637 static size_t qc_builtins_count = sizeof(qc_builtins) / sizeof(qc_builtins[0]);
638
639 static const char *arg0 = NULL;
640
641 void usage()
642 {
643     printf("usage: [-debug] %s file\n", arg0);
644     exit(1);
645 }
646
647 int main(int argc, char **argv)
648 {
649     size_t      i;
650     qcint       fnmain = -1;
651     qc_program *prog;
652     size_t      xflags = VMXF_DEFAULT;
653
654     arg0 = argv[0];
655
656     if (argc < 2)
657         usage();
658
659     while (argc > 2) {
660         if (!strcmp(argv[1], "-trace")) {
661             --argc;
662             ++argv;
663             xflags |= VMXF_TRACE;
664         }
665         else if (!strcmp(argv[1], "-profile")) {
666             --argc;
667             ++argv;
668             xflags |= VMXF_PROFILE;
669         }
670         else
671             usage();
672     }
673
674
675     prog = prog_load(argv[1]);
676     if (!prog) {
677         printf("failed to load program '%s'\n", argv[1]);
678         exit(1);
679     }
680
681     prog->builtins       = qc_builtins;
682     prog->builtins_count = qc_builtins_count;
683     prog->builtins_alloc = 0;
684
685     for (i = 1; i < prog->functions_count; ++i) {
686         const char *name = prog_getstring(prog, prog->functions[i].name);
687         /* printf("Found function: %s\n", name); */
688         if (!strcmp(name, "main"))
689             fnmain = (qcint)i;
690     }
691     printf("Entity field space: %i\n", (int)prog->entityfields);
692     if (fnmain > 0)
693     {
694         prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
695     }
696     else
697         printf("No main function found\n");
698
699     prog_delete(prog);
700     return 0;
701 }
702 #endif