]> git.xonotic.org Git - xonotic/gmqcc.git/blob - code.c
Some statistics as Samual wanted.
[xonotic/gmqcc.git] / code.c
1 /*
2  * Copyright (C) 2012, 2013
3  *     Dale Weiler
4  *     Wolfgang Bumiller
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy of
7  * this software and associated documentation files (the "Software"), to deal in
8  * the Software without restriction, including without limitation the rights to
9  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10  * of the Software, and to permit persons to whom the Software is furnished to do
11  * so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include <string.h>
25 #include "gmqcc.h"
26
27 /*
28  * We could use the old method of casting to uintptr_t then to void*
29  * or qcint_t; however, it's incredibly unsafe for two reasons.
30  * 1) The compilers aliasing optimization can legally make it unstable
31  *    (it's undefined behaviour).
32  *
33  * 2) The cast itself depends on fresh storage (newly allocated in which
34  *    ever function is using the cast macros), the contents of which are
35  *    transferred in a way that the obligation to release storage is not
36  *    propagated.
37  */
38 typedef union {
39     void   *enter;
40     qcint_t   leave;
41 } code_hash_entry_t;
42
43 /* Some sanity macros */
44 #define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter)
45 #define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave)
46
47 void code_push_statement(code_t *code, prog_section_statement_t *stmt, int linenum)
48 {
49     vec_push(code->statements, *stmt);
50     vec_push(code->linenums,   linenum);
51 }
52
53 void code_pop_statement(code_t *code)
54 {
55     vec_pop(code->statements);
56     vec_pop(code->linenums);
57 }
58
59 code_t *code_init() {
60     static prog_section_function_t  empty_function  = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}};
61     static prog_section_statement_t empty_statement = {0,{0},{0},{0}};
62     static prog_section_def_t       empty_def       = {0, 0, 0};
63
64     code_t *code       = (code_t*)mem_a(sizeof(code_t));
65     int     i          = 0;
66
67     memset(code, 0, sizeof(code_t));
68     code->entfields    = 0;
69     code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024);
70
71     /*
72      * The way progs.dat is suppose to work is odd, there needs to be
73      * some null (empty) statements, functions, and 28 globals
74      */
75     for(; i < 28; i++)
76         vec_push(code->globals, 0);
77
78     vec_push(code->chars, '\0');
79     vec_push(code->functions,  empty_function);
80
81     code_push_statement(code, &empty_statement, 0);
82
83     vec_push(code->defs,    empty_def);
84     vec_push(code->fields,  empty_def);
85
86     return code;
87 }
88
89 void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
90
91 uint32_t code_genstring(code_t *code, const char *str) {
92     size_t            hash;
93     code_hash_entry_t existing;
94
95     if (!str)
96         return 0;
97
98     if (!*str) {
99         if (!code->string_cached_empty) {
100             code->string_cached_empty = vec_size(code->chars);
101             vec_push(code->chars, 0);
102         }
103         return code->string_cached_empty;
104     }
105
106     if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) {
107         hash                      = ((unsigned char*)str)[strlen(str)-1];
108         CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash);
109     } else {
110         hash                      = util_hthash(code->string_cache, str);
111         CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash);
112     }
113
114     if (CODE_HASH_ENTER(existing))
115         return CODE_HASH_LEAVE(existing);
116
117     CODE_HASH_LEAVE(existing) = vec_size(code->chars);
118     vec_upload(code->chars, str, strlen(str)+1);
119
120     util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing));
121     return CODE_HASH_LEAVE(existing);
122 }
123
124 qcint_t code_alloc_field (code_t *code, size_t qcsize)
125 {
126     qcint_t pos = (qcint_t)code->entfields;
127     code->entfields += qcsize;
128     return pos;
129 }
130
131 static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) {
132     size_t size = 0;
133     if (lno) {
134         size += 4;  /* LNOF */
135         size += sizeof(uint32_t); /* version */
136         size += sizeof(code_header->defs.length);
137         size += sizeof(code_header->globals.length);
138         size += sizeof(code_header->fields.length);
139         size += sizeof(code_header->statements.length);
140         size += sizeof(code->linenums[0]) * vec_size(code->linenums);
141     } else {
142         size += sizeof(prog_header_t);
143         size += sizeof(prog_section_statement_t) * vec_size(code->statements);
144         size += sizeof(prog_section_def_t)       * vec_size(code->defs);
145         size += sizeof(prog_section_field_t)     * vec_size(code->fields);
146         size += sizeof(prog_section_function_t)  * vec_size(code->functions);
147         size += sizeof(int32_t)                  * vec_size(code->globals);
148         size += 1                                * vec_size(code->chars);
149     }
150     return size;
151 }
152
153 #define code_size_binary(C, H) code_size_generic((C), (H), false)
154 #define code_size_debug(C, H)  code_size_generic((C), (H), true)
155
156 static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) {
157     size_t i;
158
159     code_header->statements.offset = sizeof(prog_header_t);
160     code_header->statements.length = vec_size(code->statements);
161     code_header->defs.offset       = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements));
162     code_header->defs.length       = vec_size(code->defs);
163     code_header->fields.offset     = code_header->defs.offset       + (sizeof(prog_section_def_t)       * vec_size(code->defs));
164     code_header->fields.length     = vec_size(code->fields);
165     code_header->functions.offset  = code_header->fields.offset     + (sizeof(prog_section_field_t)     * vec_size(code->fields));
166     code_header->functions.length  = vec_size(code->functions);
167     code_header->globals.offset    = code_header->functions.offset  + (sizeof(prog_section_function_t)  * vec_size(code->functions));
168     code_header->globals.length    = vec_size(code->globals);
169     code_header->strings.offset    = code_header->globals.offset    + (sizeof(int32_t)                  * vec_size(code->globals));
170     code_header->strings.length    = vec_size(code->chars);
171     code_header->version           = 6;
172     code_header->skip              = 0;
173
174     if (OPTS_OPTION_BOOL(OPTION_FORCECRC))
175         code_header->crc16         = OPTS_OPTION_U16(OPTION_FORCED_CRC);
176     else
177         code_header->crc16         = code->crc;
178     code_header->entfield          = code->entfields;
179
180     if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) {
181         util_debug("GEN", "Patching stringtable for -fdarkplaces-stringtablebug\n");
182
183         /* >= + P */
184         vec_push(code->chars, '\0'); /* > */
185         vec_push(code->chars, '\0'); /* = */
186         vec_push(code->chars, '\0'); /* P */
187     }
188
189     /* ensure all data is in LE format */
190     util_endianswap(&code_header->version,    1, sizeof(code_header->version));
191     util_endianswap(&code_header->crc16,      1, sizeof(code_header->crc16));
192     util_endianswap(&code_header->statements, 2, sizeof(code_header->statements.offset));
193     util_endianswap(&code_header->defs,       2, sizeof(code_header->statements.offset));
194     util_endianswap(&code_header->fields,     2, sizeof(code_header->statements.offset));
195     util_endianswap(&code_header->functions,  2, sizeof(code_header->statements.offset));
196     util_endianswap(&code_header->strings,    2, sizeof(code_header->statements.offset));
197     util_endianswap(&code_header->globals,    2, sizeof(code_header->statements.offset));
198     util_endianswap(&code_header->entfield,   1, sizeof(code_header->entfield));
199
200     /*
201      * These are not part of the header but we ensure LE format here to save on duplicated
202      * code.
203      */
204     util_endianswap(code->statements, vec_size(code->statements), sizeof(prog_section_statement_t));
205     util_endianswap(code->defs,       vec_size(code->defs),       sizeof(prog_section_def_t));
206     util_endianswap(code->fields,     vec_size(code->fields),     sizeof(prog_section_field_t));
207     util_endianswap(code->functions,  vec_size(code->functions),  sizeof(prog_section_function_t));
208     util_endianswap(code->globals,    vec_size(code->globals),    sizeof(int32_t));
209
210
211     if (!OPTS_OPTION_BOOL(OPTION_QUIET)) {
212         if (lnofile)
213             con_out("writing '%s' and '%s'...\n", filename, lnofile);
214         else
215             con_out("writing '%s'\n", filename);
216     }
217
218     if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
219         !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
220     {
221         char buffer[1024];
222         con_out("\nOptimizations:\n");
223         for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) {
224             if (opts_optimizationcount[i]) {
225                 util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer));
226                 con_out(
227                     "    %s: %u\n",
228                     buffer,
229                     (unsigned int)opts_optimizationcount[i]
230                 );
231             }
232         }
233     }
234 }
235
236 static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) {
237     if (OPTS_OPTION_BOOL(OPTION_QUIET) ||
238         OPTS_OPTION_BOOL(OPTION_PP_ONLY))
239             return;
240
241     con_out("\nFile statistics:\n");
242     con_out("    dat:\n");
243     con_out("        name: %s\n",         filename);
244     con_out("        size: %u (bytes)\n", code_size_binary(code, code_header));
245     con_out("        crc:  0x%04X\n",     code->crc);
246
247     if (lnofile) {
248         con_out("    lno:\n");
249         con_out("        name: %s\n",  lnofile);
250         con_out("        size: %u (bytes)\n",  code_size_debug(code, code_header));
251     }
252 }
253
254 /*
255  * Same principle except this one allocates memory and writes the lno(optional) and the dat file
256  * directly out to allocated memory. Which is actually very useful for the future library support
257  * we're going to add.
258  */
259 bool code_write_memory(code_t *code, uint8_t **datmem, size_t *sizedat, uint8_t **lnomem, size_t *sizelno) {
260     prog_header_t code_header;
261     uint32_t      offset  = 0;
262
263     if (!datmem)
264         return false;
265
266     code_create_header(code, &code_header, "<<memory>>", "<<memory>>");
267
268     #define WRITE_CHUNK(C,X,S)                                     \
269         do {                                                       \
270             memcpy((void*)(&(*C)[offset]), (const void*)(X), (S)); \
271             offset += (S);                                         \
272         } while (0)
273
274     /* Calculate size required to store entire file out to memory */
275     if (lnomem) {
276         uint32_t version = 1;
277
278         *sizelno = code_size_debug(code, &code_header);
279         *lnomem  = (uint8_t*)mem_a(*sizelno);
280
281         WRITE_CHUNK(lnomem, "LNOF",                         4);
282         WRITE_CHUNK(lnomem, &version,                       sizeof(version));
283         WRITE_CHUNK(lnomem, &code_header.defs.length,       sizeof(code_header.defs.length));
284         WRITE_CHUNK(lnomem, &code_header.globals.length,    sizeof(code_header.globals.length));
285         WRITE_CHUNK(lnomem, &code_header.fields.length,     sizeof(code_header.fields.length));
286         WRITE_CHUNK(lnomem, &code_header.statements.length, sizeof(code_header.statements.length));
287
288         /* something went terribly wrong */
289         if (offset != *sizelno) {
290             mem_d(*lnomem);
291             *sizelno = 0;
292             return false;
293         }
294         offset = 0;
295     }
296
297     /* Write out the dat */
298     *sizedat = code_size_binary(code, &code_header);
299     *datmem  = (uint8_t*)mem_a(*sizedat);
300
301     WRITE_CHUNK(datmem, &code_header,     sizeof(prog_header_t));
302     WRITE_CHUNK(datmem, code->statements, sizeof(prog_section_statement_t) * vec_size(code->statements));
303     WRITE_CHUNK(datmem, code->defs,       sizeof(prog_section_def_t)       * vec_size(code->defs));
304     WRITE_CHUNK(datmem, code->fields,     sizeof(prog_section_field_t)     * vec_size(code->fields));
305     WRITE_CHUNK(datmem, code->functions,  sizeof(prog_section_function_t)  * vec_size(code->functions));
306     WRITE_CHUNK(datmem, code->globals,    sizeof(int32_t)                  * vec_size(code->globals));
307     WRITE_CHUNK(datmem, code->chars,      1                                * vec_size(code->chars));
308
309     vec_free(code->statements);
310     vec_free(code->linenums);
311     vec_free(code->defs);
312     vec_free(code->fields);
313     vec_free(code->functions);
314     vec_free(code->globals);
315     vec_free(code->chars);
316
317     util_htdel(code->string_cache);
318     mem_d(code);
319     code_stats("<<memory>>", (lnomem) ? "<<memory>>" : NULL, code, &code_header);
320     return true;
321 }
322 #undef WRITE_CHUNK
323
324 bool code_write(code_t *code, const char *filename, const char *lnofile) {
325     prog_header_t  code_header;
326     FILE          *fp           = NULL;
327     size_t         it           = 2;
328
329     code_create_header(code, &code_header, filename, lnofile);
330
331     if (lnofile) {
332         uint32_t version = 1;
333
334         fp = fs_file_open(lnofile, "wb");
335         if (!fp)
336             return false;
337
338         util_endianswap(&version,      1,                         sizeof(version));
339         util_endianswap(code->linenums, vec_size(code->linenums), sizeof(code->linenums[0]));
340
341
342         if (fs_file_write("LNOF",                          4,                                      1,                        fp) != 1 ||
343             fs_file_write(&version,                        sizeof(version),                        1,                        fp) != 1 ||
344             fs_file_write(&code_header.defs.length,        sizeof(code_header.defs.length),        1,                        fp) != 1 ||
345             fs_file_write(&code_header.globals.length,     sizeof(code_header.globals.length),     1,                        fp) != 1 ||
346             fs_file_write(&code_header.fields.length,      sizeof(code_header.fields.length),      1,                        fp) != 1 ||
347             fs_file_write(&code_header.statements.length,  sizeof(code_header.statements.length),  1,                        fp) != 1 ||
348             fs_file_write(code->linenums,                  sizeof(code->linenums[0]),              vec_size(code->linenums), fp) != vec_size(code->linenums))
349         {
350             con_err("failed to write lno file\n");
351         }
352
353         fs_file_close(fp);
354         fp = NULL;
355     }
356
357     fp = fs_file_open(filename, "wb");
358     if (!fp)
359         return false;
360
361     if (1                          != fs_file_write(&code_header,     sizeof(prog_header_t)           , 1                         , fp) ||
362         vec_size(code->statements) != fs_file_write(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) ||
363         vec_size(code->defs)       != fs_file_write(code->defs,       sizeof(prog_section_def_t)      , vec_size(code->defs)      , fp) ||
364         vec_size(code->fields)     != fs_file_write(code->fields,     sizeof(prog_section_field_t)    , vec_size(code->fields)    , fp) ||
365         vec_size(code->functions)  != fs_file_write(code->functions,  sizeof(prog_section_function_t) , vec_size(code->functions) , fp) ||
366         vec_size(code->globals)    != fs_file_write(code->globals,    sizeof(int32_t)                 , vec_size(code->globals)   , fp) ||
367         vec_size(code->chars)      != fs_file_write(code->chars,      1                               , vec_size(code->chars)     , fp))
368     {
369         fs_file_close(fp);
370         return false;
371     }
372
373     util_debug("GEN","HEADER:\n");
374     util_debug("GEN","    version:    = %d\n", code_header.version );
375     util_debug("GEN","    crc16:      = %d\n", code_header.crc16   );
376     util_debug("GEN","    entfield:   = %d\n", code_header.entfield);
377     util_debug("GEN","    statements  = {.offset = % 8d, .length = % 8d}\n", code_header.statements.offset, code_header.statements.length);
378     util_debug("GEN","    defs        = {.offset = % 8d, .length = % 8d}\n", code_header.defs      .offset, code_header.defs      .length);
379     util_debug("GEN","    fields      = {.offset = % 8d, .length = % 8d}\n", code_header.fields    .offset, code_header.fields    .length);
380     util_debug("GEN","    functions   = {.offset = % 8d, .length = % 8d}\n", code_header.functions .offset, code_header.functions .length);
381     util_debug("GEN","    globals     = {.offset = % 8d, .length = % 8d}\n", code_header.globals   .offset, code_header.globals   .length);
382     util_debug("GEN","    strings     = {.offset = % 8d, .length = % 8d}\n", code_header.strings   .offset, code_header.strings   .length);
383
384     /* FUNCTIONS */
385     util_debug("GEN", "FUNCTIONS:\n");
386     for (; it < vec_size(code->functions); it++) {
387         size_t j = code->functions[it].entry;
388         util_debug("GEN", "    {.entry =% 5d, .firstlocal =% 5d, .locals =% 5d, .profile =% 5d, .name =% 5d, .file =% 5d, .nargs =% 5d, .argsize ={%d,%d,%d,%d,%d,%d,%d,%d} }\n",
389             code->functions[it].entry,
390             code->functions[it].firstlocal,
391             code->functions[it].locals,
392             code->functions[it].profile,
393             code->functions[it].name,
394             code->functions[it].file,
395             code->functions[it].nargs,
396             code->functions[it].argsize[0],
397             code->functions[it].argsize[1],
398             code->functions[it].argsize[2],
399             code->functions[it].argsize[3],
400             code->functions[it].argsize[4],
401             code->functions[it].argsize[5],
402             code->functions[it].argsize[6],
403             code->functions[it].argsize[7]
404
405         );
406         util_debug("GEN", "    NAME: %s\n", &code->chars[code->functions[it].name]);
407         /* Internal functions have no code */
408         if (code->functions[it].entry >= 0) {
409             util_debug("GEN", "    CODE:\n");
410             for (;;) {
411                 if (code->statements[j].opcode != INSTR_DONE)
412                     util_debug("GEN", "        %-12s {% 5i,% 5i,% 5i}\n",
413                         util_instr_str[code->statements[j].opcode],
414                         code->statements[j].o1.s1,
415                         code->statements[j].o2.s1,
416                         code->statements[j].o3.s1
417                     );
418                 else {
419                     util_debug("GEN", "        DONE  {0x00000,0x00000,0x00000}\n");
420                     break;
421                 }
422                 j++;
423             }
424         }
425     }
426
427     fs_file_close(fp);
428     code_stats(filename, lnofile, code, &code_header);
429     return true;
430 }
431
432 void code_cleanup(code_t *code) {
433     vec_free(code->statements);
434     vec_free(code->linenums);
435     vec_free(code->defs);
436     vec_free(code->fields);
437     vec_free(code->functions);
438     vec_free(code->globals);
439     vec_free(code->chars);
440
441     util_htdel(code->string_cache);
442
443     mem_d(code);
444 }