quakeforge/tools/qfcc/source/qfcc.c

920 lines
22 KiB
C

/*
qfcc.c
QuakeForge Code Compiler (main program)
Copyright (C) 1996-1997 id Software, Inc.
Copyright (C) 2001 Jeff Teunissen <deek@quakeforge.net>
Copyright (C) 2001 Bill Currie <bill@taniwha.org>
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 <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef HAVE_IO_H
#include <io.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <QF/cbuf.h>
#include <QF/crc.h>
#include <QF/dstring.h>
#include <QF/hash.h>
#include <QF/qendian.h>
#include <QF/script.h>
#include <QF/sys.h>
#include <QF/va.h>
#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.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.near_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.near_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;
}
pr.strings->size = (pr.strings->size + 3) & ~3;
if (options.verbosity >= 0) {
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);
if (!big_function)
big_function = "";
printf ("%6i locals size (%s)\n", num_localdefs, big_function);
printf ("%6i fielddefs\n", numfielddefs);
printf ("%6i globals\n", pr.near_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.near_data->size;
for (i = 0; i < pr.near_data->size; i++)
pr.near_data->data[i] = LittleLong (pr.near_data->data[i]);
Qwrite (h, pr.near_data->data, pr.near_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;
expr_t e;
dfunction_t *df;
// check to make sure all functions prototyped have code
if (options.warnings.undefined_function) {
for (d = pr.near_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 (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 }
}
}
if (errors)
return !errors;
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.symtab->space, st_global);
D_INT (sym->s.def) = type_size (&type_param);
}
if (options.code.debug) {
e = *new_string_expr (debugfile);
//FIXME ReuseConstant (&e, get_def (&type_string, ".debug_file", pr.scope,
//FIXME st_global));
}
for (def = pr.near_data->defs; def; def = def->next)
relocate_refs (def->relocs, def->offset);
pr.functions = calloc (pr.num_functions + 1, sizeof (dfunction_t));
for (df = pr.functions + 1, f = pr.func_head; f; df++, f = f->next) {
relocate_refs (f->refs, f->function_num);
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;
relocate_refs (def->relocs, def->offset);
}
}
if (options.code.local_merging) {
int offset;
for (offset = defspace_new_loc (pr.near_data, num_localdefs);
offset < pr.near_data->size; offset++)
pr.near_data->data[offset].integer_var = 0;
}
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;
}