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