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