/* qfcc.c QuakeForge Code Compiler (main program) Copyright (C) 1996-1997 id Software, Inc. Copyright (C) 2001 Jeff Teunissen Copyright (C) 2001 Bill Currie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif static __attribute__ ((used)) const char rcsid[] = "$Id$"; #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_IO_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "qfcc.h" #include "class.h" #include "codespace.h" #include "cpp.h" #include "debug.h" #include "def.h" #include "defspace.h" #include "diagnostic.h" #include "emit.h" #include "expr.h" #include "function.h" #include "idstuff.h" #include "immediate.h" #include "linker.h" #include "method.h" #include "obj_file.h" #include "opcodes.h" #include "options.h" #include "reloc.h" #include "strpool.h" #include "struct.h" #include "symtab.h" #include "type.h" options_t options; const char *sourcedir; const char *progs_src; char debugfile[1024]; pr_info_t pr; ddef_t *globals; int numglobaldefs; int num_localdefs; const char *big_function = 0; ddef_t *fields; int numfielddefs; #ifdef _WIN32 char * fix_backslash (char *path) { char *s; for (s = path; *s; s++) if (*s == '\\') *s = '/'; return path; } #endif static void InitData (void) { if (pr.code) { codespace_delete (pr.code); strpool_delete (pr.strings); } memset (&pr, 0, sizeof (pr)); pr.source_line = 1; pr.error_count = 0; pr.code = codespace_new (); memset (codespace_newstatement (pr.code), 0, sizeof (dstatement_t)); pr.strings = strpool_new (); pr.num_functions = 1; pr.data = new_defspace (); pr.far_data = new_defspace (); pr.near_data = new_defspace (); pr.near_data->data = calloc (65536, sizeof (pr_type_t)); pr.near_data->max_size = 65536; pr.near_data->grow = 0; pr.symtab = new_symtab (0, stab_global); pr.symtab->space = pr.near_data; pr.entity_data = new_defspace (); numglobaldefs = 1; numfielddefs = 1; } static int WriteData (int crc) { def_t *def; ddef_t *dd; dprograms_t progs; pr_debug_header_t debug; QFile *h; int i; int num_defs = 0; for (def = pr.data->defs; def; def = def->next) num_defs++; globals = calloc (num_defs + 1, sizeof (ddef_t)); fields = calloc (num_defs + 1, sizeof (ddef_t)); for (def = pr.data->defs; def; def = def->next) { if (def->local || !def->name) continue; if (options.code.progsversion == PROG_ID_VERSION && *def->name == '.' && strcmp (def->name, ".imm") != 0 && strcmp (def->name, ".debug_file") != 0) continue; if (def->type->type == ev_func) { } else if (def->type->type == ev_field && strcmp (def->name, ".imm") != 0) { dd = &fields[numfielddefs++]; def_to_ddef (def, dd, 1); dd->ofs = D_INT (def); } dd = &globals[numglobaldefs++]; def_to_ddef (def, dd, 0); if (!def->nosave && !def->constant && def->type->type != ev_func && def->type->type != ev_field && def->global) dd->type |= DEF_SAVEGLOBAL; } while (pr.strings->size & 3) pr.strings->strings[pr.strings->size++] = 0; if (options.verbosity >= 0) { if (!big_function) big_function = ""; printf ("%6i strofs\n", pr.strings->size); printf ("%6i statements\n", pr.code->size); printf ("%6i functions\n", pr.num_functions); printf ("%6i global defs\n", numglobaldefs); printf ("%6i fielddefs\n", numfielddefs); printf ("%6i globals\n", pr.data->size); printf (" %6i near globals\n", pr.near_data->size); printf (" %6i locals size (%s)\n", num_localdefs, big_function); printf (" %6i far globals\n", pr.far_data->size); printf ("%6i entity fields\n", pr.entity_data->size); } if (!(h = Qopen (options.output_file, "wb"))) Sys_Error ("%s: %s\n", options.output_file, strerror(errno)); memset (&progs, 0, sizeof (progs)); Qwrite (h, &progs, sizeof (progs)); progs.ofs_strings = Qtell (h); progs.numstrings = pr.strings->size; Qwrite (h, pr.strings->strings, pr.strings->size); progs.ofs_statements = Qtell (h); progs.numstatements = pr.code->size; for (i = 0; i < pr.code->size; i++) { pr.code->code[i].op = LittleShort (pr.code->code[i].op); pr.code->code[i].a = LittleShort (pr.code->code[i].a); pr.code->code[i].b = LittleShort (pr.code->code[i].b); pr.code->code[i].c = LittleShort (pr.code->code[i].c); } Qwrite (h, pr.code->code, pr.code->size * sizeof (dstatement_t)); { dfunction_t *df; progs.ofs_functions = Qtell (h); progs.numfunctions = pr.num_functions; for (i = 0, df = pr.functions + 1; i < pr.num_functions; i++, df++) { df->first_statement = LittleLong (df->first_statement); df->parm_start = LittleLong (df->parm_start); df->s_name = LittleLong (df->s_name); df->s_file = LittleLong (df->s_file); df->numparms = LittleLong (df->numparms); df->locals = LittleLong (df->locals); } Qwrite (h, pr.functions, pr.num_functions * sizeof (dfunction_t)); } progs.ofs_globaldefs = Qtell (h); progs.numglobaldefs = numglobaldefs; for (i = 0; i < numglobaldefs; i++) { globals[i].type = LittleShort (globals[i].type); globals[i].ofs = LittleShort (globals[i].ofs); globals[i].s_name = LittleLong (globals[i].s_name); } Qwrite (h, globals, numglobaldefs * sizeof (ddef_t)); progs.ofs_fielddefs = Qtell (h); progs.numfielddefs = numfielddefs; for (i = 0; i < numfielddefs; i++) { fields[i].type = LittleShort (fields[i].type); fields[i].ofs = LittleShort (fields[i].ofs); fields[i].s_name = LittleLong (fields[i].s_name); } Qwrite (h, fields, numfielddefs * sizeof (ddef_t)); progs.ofs_globals = Qtell (h); progs.numglobals = pr.data->size; for (i = 0; i < pr.data->size; i++) pr.data->data[i] = LittleLong (pr.data->data[i]); Qwrite (h, pr.data->data, pr.data->size * 4); if (options.verbosity >= -1) printf ("%6i TOTAL SIZE\n", (int) Qtell (h)); progs.entityfields = pr.entity_data->size; progs.version = options.code.progsversion; progs.crc = crc; // byte swap the header and write it out for (i = 0; i < (int) sizeof (progs) / 4; i++) ((int *) &progs)[i] = LittleLong (((int *) &progs)[i]); Qseek (h, 0, SEEK_SET); Qwrite (h, &progs, sizeof (progs)); Qclose (h); if (!options.code.debug) { return 0; } if (!(h = Qopen (options.output_file, "rb"))) Sys_Error ("%s: %s\n", options.output_file, strerror(errno)); memset (&debug, 0, sizeof (debug)); debug.version = LittleLong (PROG_DEBUG_VERSION); CRC_Init (&debug.crc); while ((i = Qgetc (h)) != EOF) CRC_ProcessByte (&debug.crc, i); Qclose (h); debug.crc = LittleShort (debug.crc); debug.you_tell_me_and_we_will_both_know = 0; if (!(h = Qopen (debugfile, "wb"))) Sys_Error ("%s: %s\n", options.output_file, strerror(errno)); Qwrite (h, &debug, sizeof (debug)); debug.auxfunctions = LittleLong (Qtell (h)); debug.num_auxfunctions = LittleLong (pr.num_auxfunctions); for (i = 0; i < pr.num_auxfunctions; i++) { pr.auxfunctions[i].function = LittleLong (pr.auxfunctions[i].function); pr.auxfunctions[i].source_line = LittleLong (pr.auxfunctions[i].source_line); pr.auxfunctions[i].line_info = LittleLong (pr.auxfunctions[i].line_info); pr.auxfunctions[i].local_defs = LittleLong (pr.auxfunctions[i].local_defs); pr.auxfunctions[i].num_locals = LittleLong (pr.auxfunctions[i].num_locals); } Qwrite (h, pr.auxfunctions, pr.num_auxfunctions * sizeof (pr_auxfunction_t)); debug.linenos = LittleLong (Qtell (h)); debug.num_linenos = LittleLong (pr.num_linenos); for (i = 0; i < pr.num_linenos; i++) { pr.linenos[i].fa.addr = LittleLong (pr.linenos[i].fa.addr); pr.linenos[i].line = LittleLong (pr.linenos[i].line); } Qwrite (h, pr.linenos, pr.num_linenos * sizeof (pr_lineno_t)); debug.locals = LittleLong (Qtell (h)); debug.num_locals = LittleLong (pr.num_locals); for (i = 0; i < pr.num_locals; i++) { pr.locals[i].type = LittleShort (pr.locals[i].type); pr.locals[i].ofs = LittleShort (pr.locals[i].ofs); pr.locals[i].s_name = LittleLong (pr.locals[i].s_name); } Qwrite (h, pr.locals, pr.num_locals * sizeof (ddef_t)); Qseek (h, 0, SEEK_SET); Qwrite (h, &debug, sizeof (debug)); Qclose (h); return 0; } static void begin_compilation (void) { pr.func_tail = &pr.func_head; pr.error_count = 0; } static void setup_param_block (void) { static struct_def_t defs[] = { {".zero", &type_zero}, {".return", &type_param}, {".param_0", &type_param}, {".param_1", &type_param}, {".param_2", &type_param}, {".param_3", &type_param}, {".param_4", &type_param}, {".param_5", &type_param}, {".param_6", &type_param}, {".param_7", &type_param}, }; size_t i; symbol_t *sym; for (i = 0; i < sizeof (defs) / sizeof (defs[0]); i++) { sym = make_symbol (defs[i].name, defs[i].type, pr.symtab->space, st_global); symtab_addsymbol (pr.symtab, sym); } } static qboolean finish_compilation (void) { def_t *d; qboolean errors = false; function_t *f; def_t *def; dfunction_t *df; int far_base; for (d = pr.near_data->defs; d; d = d->next) { if (d->external && d->relocs) { #if 0 //FIXME if (strcmp (d->name, ".self") == 0) { get_def (d->type, ".self", pr.scope, st_global); } else if (strcmp (d->name, ".this") == 0) { get_def (d->type, ".this", pr.scope, st_global); } else { #endif errors = true; error (0, "undefined global %s", d->name); //FIXME } } } for (d = pr.far_data->defs; d; d = d->next) { if (d->external && d->relocs) { errors = true; error (0, "undefined global %s", d->name); } } if (errors) return !errors; pr.functions = calloc (pr.num_functions + 1, sizeof (dfunction_t)); for (df = pr.functions + 1, f = pr.func_head; f; df++, f = f->next) { df->s_name = f->s_name; df->s_file = f->s_file; df->numparms = function_parms (f, df->parm_size); if (f->symtab && f->symtab->space) df->locals = f->symtab->space->size; if (f->builtin) { df->first_statement = -f->builtin; continue; } if (!f->code) continue; df->first_statement = f->code; if (options.code.local_merging) { if (f->symtab->space->size > num_localdefs) { num_localdefs = f->symtab->space->size; big_function = f->def->name; } df->parm_start = pr.near_data->size; } else { df->parm_start = defspace_new_loc (pr.near_data, f->symtab->space->size); num_localdefs += f->symtab->space->size; } for (def = f->symtab->space->defs; def; def = def->next) { if (!def->local) continue; def->offset += df->parm_start; } } if (options.code.local_merging) defspace_new_loc (pr.near_data, num_localdefs); if (options.code.progsversion != PROG_ID_VERSION) { //FIXME better init code symbol_t *sym = new_symbol (".param_size"); initialize_def (sym, &type_integer, 0, pr.far_data, st_global); D_INT (sym->s.def) = type_size (&type_param); } if (options.code.debug) { //FIXME better init code symbol_t *sym = new_symbol (".debug_file"); initialize_def (sym, &type_string, 0, pr.far_data, st_global); EMIT_STRING (sym->s.def->space, D_STRUCT (string_t, sym->s.def), debugfile); } // merge near and far data defspace_add_data (pr.data, pr.near_data->data, pr.near_data->size); far_base = pr.data->size; defspace_add_data (pr.data, pr.far_data->data, pr.far_data->size); for (d = pr.far_data->defs; d; d = d->next) { if (!d->external) d->offset += far_base; } if (pr.near_data->defs) { pr.data->defs = pr.near_data->defs; pr.data->def_tail = pr.near_data->def_tail; pr.near_data->defs = 0; pr.near_data->def_tail = &pr.near_data->defs; } if (pr.far_data->defs) { *pr.data->def_tail = pr.far_data->defs; pr.data->def_tail = pr.far_data->def_tail; pr.far_data->defs = 0; pr.far_data->def_tail = &pr.far_data->defs; } // point near and far data spaces into the merged data. this allows // relocations to work without having to adjust their location. pr.near_data->data = pr.data->data; pr.far_data->data = pr.data->data + far_base; // check to make sure all functions prototyped have code if (options.warnings.undefined_function) { for (d = pr.data->defs; d; d = d->next) { if (d->type->type == ev_func && d->global) { // function args ok //FIXME if (d->used) { if (!d->initialized) { warning (0, "function %s was called but not defined\n", d->name); } //FIXME } } } } for (def = pr.data->defs; def; def = def->next) relocate_refs (def->relocs, def->offset); for (df = pr.functions + 1, f = pr.func_head; f; df++, f = f->next) { relocate_refs (f->refs, f->function_num); if (!f->code) continue; for (def = f->symtab->space->defs; def; def = def->next) { if (!def->local) continue; relocate_refs (def->relocs, def->offset); } } return !errors; } const char * strip_path (const char *filename) { const char *p = filename; int i = options.strip_path; while (i-- > 0) { while (*p && *p != '/') p++; if (!*p) break; filename = ++p; } return filename; } static void setup_sym_file (const char *output_file) { if (options.code.debug) { char *s; strcpy (debugfile, output_file); s = debugfile + strlen (debugfile); while (s-- > debugfile) { if (*s == '.') break; if (*s == '/' || *s == '\\') { s = debugfile + strlen (debugfile); break; } } strcpy (s, ".sym"); if (options.verbosity >= 1) printf ("debug file: %s\n", debugfile); } } static int compile_to_obj (const char *file, const char *obj) { int err; yyin = preprocess_file (file, 0); if (!yyin) return !options.preprocess_only; InitData (); clear_frame_macros (); clear_classes (); clear_immediates (); clear_selectors (); chain_initial_types (); begin_compilation (); pr.source_file = ReuseString (strip_path (file)); err = yyparse () || pr.error_count; fclose (yyin); if (cpp_name && !options.save_temps) { if (unlink (tempname->str)) { perror ("unlink"); exit (1); } } if (!err) { qfo_t *qfo; class_finish_module (); qfo = qfo_from_progs (&pr); err = qfo_write (qfo, obj); qfo_delete (qfo); } return err; } static int separate_compile (void) { const char **file; const char **temp_files; dstring_t *output_file = dstring_newstr (); dstring_t *extension = dstring_newstr (); char *f; int err = 0; int i; if (options.compile && options.output_file && source_files[1]) { fprintf (stderr, "%s: cannot use -c and -o together with multiple files\n", this_program); return 1; } for (file = source_files, i = 0; *file; file++) i++; temp_files = calloc (i + 1, sizeof (const char*)); for (file = source_files, i = 0; *file; file++) { dstring_clearstr (extension); dstring_clearstr (output_file); dstring_appendstr (output_file, *file); f = output_file->str + strlen (output_file->str); while (f >= output_file->str && *f != '.' && *f != '/') f--; if (f >= output_file->str && *f == '.') { output_file->size -= strlen (f); dstring_appendstr (extension, f); *f = 0; } if (options.compile && options.output_file) { dstring_clearstr (output_file); dstring_appendstr (output_file, options.output_file); } else { dstring_appendstr (output_file, ".qfo"); } if (strncmp (*file, "-l", 2) && (!strcmp (extension->str, ".r") || !strcmp (extension->str, ".qc") || !strcmp (extension->str, ".c"))) { if (options.verbosity >= 2) printf ("%s %s\n", *file, output_file->str); temp_files[i++] = save_string (output_file->str); err = compile_to_obj (*file, output_file->str) || err; free ((char *)*file); *file = strdup (output_file->str); } else { if (options.compile) fprintf (stderr, "%s: %s: ignoring object file since linking " "not done\n", this_program, *file); } } if (!err && !options.compile) { qfo_t *qfo; linker_begin (); for (file = source_files; *file; file++) { if (strncmp (*file, "-l", 2)) { if (strlen (*file) >= 2 && strcmp (*file + strlen (*file) - 2, ".a") == 0) { err = linker_add_lib (*file); } else { err = linker_add_object_file (*file); } } else { err = linker_add_lib (*file); } if (err) return err; } qfo = linker_finish (); if (qfo) { if (!options.output_file) options.output_file = "progs.dat"; if (options.partial_link) { qfo_write (qfo, options.output_file); } else { int crc = 0; qfo_to_progs (qfo, &pr); setup_sym_file (options.output_file); finish_compilation (); // write progdefs.h if (options.progdefs_h) crc = WriteProgdefs ("progdefs.h"); WriteData (crc); } } else { err = 1; } if (!options.save_temps) for (file = temp_files; *file; file++) unlink (*file); } return err; } static const char * load_file (const char *fname) { QFile *file; char *src; FILE *tmpfile; tmpfile = preprocess_file (fname, "i1"); if (!tmpfile) return 0; file = Qfopen (tmpfile, "rb"); if (!file) { perror (fname); return 0; } src = malloc (Qfilesize (file) + 1); src[Qfilesize (file)] = 0; Qread (file, src, Qfilesize (file)); Qclose (file); if (cpp_name && (!options.save_temps)) { if (unlink (tempname->str)) { perror ("unlink"); exit (1); } } return src; } static void parse_cpp_line (script_t *script, dstring_t *filename) { if (Script_TokenAvailable (script, 0)) { Script_GetToken (script, 0); // subtract 1 for \n script->line = atoi (script->token->str) - 1; if (Script_TokenAvailable (script, 0)) { Script_GetToken (script, 0); dstring_copystr (filename, script->token->str); script->file = filename->str; } } // There shouldn't be anything else, but just to be safe... while (Script_TokenAvailable (script, 0)) Script_GetToken (script, 0); } static int compile_file (const char *filename) { int err; yyin = preprocess_file (filename, 0); if (!yyin) return !options.preprocess_only; pr.source_file = ReuseString (strip_path (filename)); pr.source_line = 1; clear_frame_macros (); err = yyparse () || pr.error_count; fclose (yyin); if (cpp_name && (!options.save_temps)) { if (unlink (tempname->str)) { perror ("unlink"); exit (1); } } return err; } static int progs_src_compile (void) { dstring_t *filename = dstring_newstr (); dstring_t *qc_filename = dstring_newstr (); dstring_t *single_name = dstring_newstr (); const char *src; int crc = 0; script_t *script; FILE *single = 0; if (options.verbosity >= 1 && strcmp (sourcedir, "")) { printf ("Source directory: %s\n", sourcedir); } if (options.verbosity >= 1 && strcmp (progs_src, "progs.src")) { printf ("progs.src: %s\n", progs_src); } if (*sourcedir) dsprintf (filename, "%s/%s", sourcedir, progs_src); else dsprintf (filename, "%s", progs_src); if (options.single_cpp) { intermediate_file (single_name, filename->str, "i2", 1); if (!options.save_temps) { #ifdef _WIN32 mktemp (single_name->str); #else int tempfd = mkstemp (single_name->str); single = fdopen (tempfd, "wb"); #endif } if (!single) single = fopen (single_name->str, "wb"); if (!single) { perror (single_name->str); exit (1); } } src = load_file (filename->str); if (!src) { fprintf (stderr, "couldn't open %s\n", filename->str); unlink (single_name->str); return 1; } script = Script_New (); Script_Start (script, filename->str, src); while (1) { if (!Script_GetToken (script, 1)) { fprintf (stderr, "No destination filename. qfcc --help for info.\n"); return 1; } if (strcmp (script->token->str, "#") == 0) { parse_cpp_line (script, filename); continue; } break; } if (!options.output_file) options.output_file = save_string (script->token->str); if (options.verbosity >= 1) { printf ("output file: %s\n", options.output_file); } setup_sym_file (options.output_file); InitData (); chain_initial_types (); begin_compilation (); if (!options.compile) setup_param_block (); // compile all the files while (Script_GetToken (script, 1)) { if (strcmp (script->token->str, "#") == 0) { parse_cpp_line (script, filename); continue; } while (1) { if (*sourcedir) dsprintf (qc_filename, "%s%c%s", sourcedir, PATH_SEPARATOR, script->token->str); else dsprintf (qc_filename, "%s", script->token->str); if (options.verbosity >= 2) printf ("%s:%d: compiling %s\n", script->file, script->line, qc_filename->str); if (single) { fprintf (single, "$frame_reset\n"); fprintf (single, "#line %d \"%s\"\n", script->line, script->file); fprintf (single, "#include \"%s\"\n", qc_filename->str); } else { if (compile_file (qc_filename->str)) return 1; } if (!Script_TokenAvailable (script, 0)) break; Script_GetToken (script, 0); } } if (single) { int err; fclose (single); err = compile_file (single_name->str); if (!options.save_temps) { if (unlink (single_name->str)) { perror ("unlink"); exit (1); } } if (err) return 1; } if (options.compile) { qfo_t *qfo = qfo_from_progs (&pr); qfo_write (qfo, options.output_file); qfo_delete (qfo); } else { class_finish_module (); if (!finish_compilation ()) { fprintf (stderr, "compilation errors\n"); return 1; } // write progdefs.h if (options.progdefs_h) crc = WriteProgdefs ("progdefs.h"); // write data file if (WriteData (crc)) return 1; // write files.dat if (options.files_dat) if (WriteFiles (sourcedir)) return 1; } return 0; } /* main The nerve center of our little operation */ int main (int argc, char **argv) { double start, stop; int res; start = Sys_DoubleTime (); #ifdef _WIN32 if (putenv ("POSIXLY_INCORRECT_GETOPT=1")) { fprintf (stderr, "Warning: putenv failed\n"); } #endif this_program = NORMALIZE (argv[0]); DecodeArgs (argc, argv); tempname = dstring_new (); parse_cpp_name (); opcode_init (); InitData (); init_types (); clear_immediates (); if (source_files) { res = separate_compile (); } else { res = progs_src_compile (); } if (res) return res; stop = Sys_DoubleTime (); if (options.verbosity >= 0) printf ("Compilation time: %0.3g seconds.\n", (stop - start)); return 0; }