From 3761cddca66c1d9dd9e1be724ffa02dbf434664b Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Mon, 28 Feb 2011 23:18:07 +0900 Subject: [PATCH] Finally, some real progress on the linker. There is still much to be done, but this seems to be a workable approach. --- tools/qfcc/source/linker.c | 570 ++++++++++++++++++++++++++++++------- 1 file changed, 460 insertions(+), 110 deletions(-) diff --git a/tools/qfcc/source/linker.c b/tools/qfcc/source/linker.c index b1911d787..38ccce5fc 100644 --- a/tools/qfcc/source/linker.c +++ b/tools/qfcc/source/linker.c @@ -66,14 +66,19 @@ static __attribute__ ((used)) const char rcsid[] = "$Id$"; #include "immediate.h" #include "linker.h" #include "obj_file.h" +#include "obj_type.h" #include "options.h" #include "qfcc.h" #include "reloc.h" #include "strpool.h" #include "type.h" +static void linker_error (const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +static void linker_warning (const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); static void def_error (qfo_def_t *def, const char *fmt, ...) - __attribute__ ((used, format (printf, 2, 3))); + __attribute__ ((format (printf, 2, 3))); static void def_warning (qfo_def_t *def, const char *fmt, ...) __attribute__ ((used, format (printf, 2, 3))); @@ -96,7 +101,27 @@ static builtin_sym_t builtin_symbols[] __attribute__ ((used)) = { {".param_7", &type_param, QFOD_NOSAVE}, }; +typedef struct defref_s { + struct defref_s *next; + qfo_def_t **def_list; + int def; + int space; +} defref_t; + +#define REF(r) ((*(r)->def_list) + (r)->def) + +static defref_t *free_defrefs; + +static hashtab_t *extern_defs; +static hashtab_t *defined_defs; + +static hashtab_t *extern_type_defs; +static hashtab_t *defined_type_defs; +static qfo_mspace_t *qfo_type_defs; + static qfo_t *work; +static defref_t **work_defrefs; +static int num_work_defrefs; static strpool_t *work_strings; static codespace_t *work_code; static defspace_t *work_near_data; @@ -104,6 +129,59 @@ static defspace_t *work_far_data; static defspace_t *work_entity_data; static defspace_t *work_type_data; +static dstring_t *linker_current_file; + +#define QFOSTR(q,s) QFO_GETSTR (q, s) +#define WORKSTR(s) QFOSTR (work, s) + +/** Produce an error message for defs with mismatched types. + + \param def The new def. + \param prev The previous definition. +*/ +static void +linker_type_mismatch (qfo_def_t *def, qfo_def_t *prev) +{ + def_error (def, "type mismatch for `%s' `%s'", + WORKSTR (def->name), + QFO_TYPESTR (work, def->type)); + def_error (prev, "previous definition `%s'", + QFO_TYPESTR (work, prev->type)); +} + +/** Create a new def reference. + + References are required to avoid problems with realloc moving things + around. + + The \a def must be in the \a space, and the \a space must be in the + \c work qfo. + + \param def The def for which the reference will be created. + \param space The defspace in \c work which holds \a def. + \param def_list The list which currently holds the def. + \return The new reference. +*/ +static defref_t * +get_defref (qfo_def_t *def, qfo_mspace_t *space, qfo_def_t **def_list) +{ + defref_t *defref; + + ALLOC (16384, defref_t, defrefs, defref); + defref->def_list = def_list; + defref->def = def - *def_list; + defref->space = space - work->spaces; + return defref; +} + +static const char * +defs_get_key (void *_r, void *unused) +{ + defref_t *r = (defref_t *)_r; + qfo_def_t *d = REF (r); + return WORKSTR (d->name); +} + /** Add a string to the working qfo string pool. If the string is already in the string pool, the already existing string @@ -124,6 +202,108 @@ add_string (const char *str) return new; } +/** Resolve an external def with its global definition. + + The types of the external def and the global def must match. + The offset and flags of the global def are copied to the external def. + \fixme handle cross-space resolutions. + \fixme copy relocs from \a ext to \a def and "delete" \a ext? + + \param ext The external def. + \param def The global definition. +*/ +static void +resolve_external_def (defref_t *ext, defref_t *def) +{ + if (REF (ext)->type != REF (def)->type) { + linker_type_mismatch (REF (ext), REF (def)); + return; + } + if (def->space != ext->space) + internal_error (0, "help, help!"); + REF (ext)->offset = REF (def)->offset; + REF (ext)->flags = REF (def)->flags; +} + +static void +process_def (defref_t *ref, qfo_mspace_t *space) +{ + qfo_def_t *def; + defref_t *r; + const char *name; + + def = REF (ref); + name = WORKSTR (def->name); + r = Hash_Find (defined_defs, name); + if (def->flags & QFOD_EXTERNAL) { + if (!def->num_relocs) + return; + if (r) { + resolve_external_def (ref, r); + } else { + Hash_Add (extern_defs, ref); + } + } else if (def->flags & QFOD_GLOBAL) { + if (r) { + if (REF (r)->flags & QFOD_SYSTEM) { + if (def->type != REF (r)->type) { + linker_type_mismatch (def, REF (r)); + return; + } + /// System defs may be redefined only once. + REF (r)->flags &= ~QFOD_SYSTEM; + if (ref->space != r->space) + internal_error (0, "help, help!"); + //FIXME copy stuff from new def to existing def??? + def->offset = REF(r)->offset; + def->flags = REF(r)->flags; + } else { + def_error (def, "%s redefined", WORKSTR (def->name)); + def_error (REF (r), "previous definition"); + } + return; + } + Hash_Add (defined_defs, ref); + if (!(def->flags & QFOD_LOCAL)) + def->offset += space->data_size; + while ((r = Hash_Del (extern_defs, name))) + resolve_external_def (r, ref); + } +} + +static int +add_defs (qfo_t *qfo, qfo_mspace_t *space, qfo_mspace_t *dest_space) +{ + int count = space->num_defs; + int size; + int i; + qfo_def_t *idef; + qfo_def_t *odef; + defref_t *ref; + + size = (num_work_defrefs + count) * sizeof (defref_t *); + work_defrefs = realloc (work_defrefs, size); + size = (dest_space->num_defs + count) * sizeof (qfo_def_t); + dest_space->defs = realloc (dest_space->defs, size); + idef = space->defs; + odef = dest_space->defs + dest_space->num_defs; + for (i = 0; i < count; i++, idef++, odef++) { + *odef = *idef; + idef->offset = num_work_defrefs; // so def can be found + odef->name = add_string (QFOSTR (qfo, idef->name)); + odef->file = add_string (QFOSTR (qfo, idef->file)); + ref = get_defref (odef, dest_space, &dest_space->defs); + work_defrefs[num_work_defrefs++] = ref; + process_def (ref, dest_space); + } + dest_space->num_defs += count; + return count; +} + +/** Add all the strings in the strings space to the working qfo. + + \param strings The strings space of the qfo being linked. +*/ static void add_qfo_strings (qfo_mspace_t *strings) { @@ -137,6 +317,10 @@ add_qfo_strings (qfo_mspace_t *strings) } } +/** Add the code in the code space to the working qfo. + + \param code The code space of the qfo being linked. +*/ static void add_code (qfo_mspace_t *code) { @@ -145,6 +329,10 @@ add_code (qfo_mspace_t *code) work->spaces[qfo_code_space].data_size = work_code->size; } +/** Add the data in a data space to the working qfo. + + \param data A data space of the qfo being linked. +*/ static void add_data (int space, qfo_mspace_t *data) { @@ -163,93 +351,33 @@ add_data (int space, qfo_mspace_t *data) work->spaces[space].data_size = work_spaces[space]->size; } -#define QFOSTR(q,s) ((q)->spaces[qfo_strings_space].d.strings + (s)) -#define WORKSTR(q,s) QFOSTR (work, s) +/** Add a defspace to the working qfo. + \param qfo The qfo being linked. + \param space The defspace from \a qfo that will be added to the working + qfo. +*/ static void -update_relocs (qfo_t *qfo) +add_space (qfo_t *qfo, qfo_mspace_t *space) { - int i; - qfo_reloc_t *reloc; - - for (reloc = qfo->relocs, i = 0; i < qfo->num_relocs; i++, reloc++) { - if (!reloc->space) { - // code space is implied - reloc->offset += work->spaces[qfo_code_space].data_size; - } else if (reloc->space < 0 || reloc->space >= qfo->num_spaces) { - //FIXME proper diagnostic - fprintf (stderr, "bad reloc space: %d", reloc->space); - reloc->type = rel_none; - } else if (reloc->space < qfo_num_spaces) { - reloc->offset += work->spaces[reloc->space].data_size; - } else { - reloc->space += work->num_spaces; - } - reloc->target += work->num_defs; //FIXME wrong - } -} - -static void -update_defs (qfo_t *qfo) -{ - int space; - int i; - qfo_def_t *def; - - for (space = 0; space < qfo->num_spaces; space++) { - if (space == qfo_type_space) - continue; // complicated. handle separately - for (def = qfo->spaces[space].defs, i = 0; - i < qfo->spaces[space].num_defs; i++, def++) { - //XXX type handled later - def->name = add_string (QFOSTR (qfo, def->name)); - if (space < qfo_num_spaces) - def->offset += work->spaces[space].data_size; - def->relocs += work->num_relocs; - def->file = add_string (QFOSTR (qfo, def->file)); - } - } -} - -static void -update_funcs (qfo_t *qfo) -{ - int i; - qfo_func_t *func; - - for (func = qfo->funcs, i = 0; i < qfo->num_funcs; i++, func++) { - func->name = add_string (QFOSTR (qfo, func->name)); - //XXX type handled later - func->file = add_string (QFOSTR (qfo, func->file)); - if (func->code > 0) - func->code += work->spaces[qfo_code_space].data_size; - func->def += work->num_defs; - if (!func->locals_space) { - // no locals (builtin function?) - } else if (func->locals_space < qfo_num_spaces) { - //FIXME proper diagnostic - fprintf (stderr, "function with weird locals: setting to none\n"); - func->locals_space = 0; - } else { - func->locals_space += work->num_spaces - qfo_num_spaces; - } - func->line_info += work->num_lines; - func->relocs += work->num_relocs; - } -} - -static void -update_lines (qfo_t *qfo) -{ - int i; - pr_lineno_t *lineno; - - for (lineno = qfo->lines, i = 0; i < qfo->num_lines; i++, lineno++) { - if (lineno->line) - lineno->fa.addr += work->spaces[qfo_code_space].data_size; - else - lineno->fa.func += work->num_funcs; + qfo_mspace_t *ws; + if (space->type != qfos_data) + internal_error (0, "bad space type for add_space (): %d", space->type); + space->id = work->num_spaces++; // so the space in work can be found + work->spaces = realloc (work->spaces, + work->num_spaces * sizeof (qfo_mspace_t)); + ws = &work->spaces[space->id]; + memset (ws, 0, sizeof (qfo_mspace_t)); + ws->type = space->type; + if (space->num_defs) + add_defs (qfo, space, ws); + if (space->d.data) { + int size = space->data_size * sizeof (pr_type_t); + ws->d.data = malloc (size); + memcpy (ws->d.data, space->d.data, size); } + ws->data_size = space->data_size; + ws->id = space->id; } static __attribute__ ((used)) void @@ -258,12 +386,29 @@ define_def (const char *name, etype_t basic_type, const char *full_type, { } +/** Initialize the linker state. +*/ void linker_begin (void) { + linker_current_file = dstring_newstr (); + + extern_defs = Hash_NewTable (16381, defs_get_key, 0, 0); + defined_defs = Hash_NewTable (16381, defs_get_key, 0, 0); + + extern_type_defs = Hash_NewTable (16381, defs_get_key, 0, 0); + defined_type_defs = Hash_NewTable (16381, defs_get_key, 0, 0); + work = qfo_new (); work->spaces = calloc (qfo_num_spaces, sizeof (qfo_mspace_t)); work->num_spaces = qfo_num_spaces; + work->spaces[qfo_null_space].type = qfos_null; + work->spaces[qfo_strings_space].type = qfos_string; + work->spaces[qfo_code_space].type = qfos_code; + work->spaces[qfo_near_data_space].type = qfos_data; + work->spaces[qfo_far_data_space].type = qfos_data; + work->spaces[qfo_entity_space].type = qfos_entity; + work->spaces[qfo_type_space].type = qfos_type; // adding data will take care of connecting the work qfo spaces with // the actual space data @@ -275,20 +420,185 @@ linker_begin (void) work_type_data = defspace_new (); } -static void +typedef int (*space_func) (qfo_t *qfo, qfo_mspace_t *space); + +static int +process_null (qfo_t *qfo, qfo_mspace_t *space) +{ + if (space->defs || space->num_defs || space->d.data || space->data_size + || space->id) { + linker_error ("non-null null space"); + return 1; + } + return 0; +} + +static int +process_code (qfo_t *qfo, qfo_mspace_t *space) +{ + if (space->defs || space->num_defs) { + linker_error ("defs in code space"); + return 1; + } + if (space->id != qfo_code_space) + linker_warning ("hmm, unexpected code space. *shrug*"); + add_code (space); + return 0; +} + +static int +process_data (qfo_t *qfo, qfo_mspace_t *space) +{ + if (space->id == qfo_near_data_space) { + add_data (qfo_near_data_space, space); + } else if (space->id == qfo_far_data_space) { + add_data (qfo_far_data_space, space); + } else { + add_space (qfo, space); + } + return 0; +} + +static int +process_strings (qfo_t *qfo, qfo_mspace_t *space) +{ + if (space->defs || space->num_defs) { + linker_error ("defs in strings space"); + return 1; + } + add_qfo_strings (space); + return 0; +} + +static int +process_entity (qfo_t *qfo, qfo_mspace_t *space) +{ + add_data (qfo_entity_space, space); + return 0; +} + +static pointer_t +transfer_type (qfo_t *qfo, qfo_mspace_t *space, pointer_t type_offset) +{ + qfot_type_t *type; + int i; + + type = (qfot_type_t *) (char *) &space->d.data[type_offset]; + if (type->ty < 0) + return type->t.class; + switch ((ty_type_e) type->ty) { + case ty_none: + if (type->t.type == ev_func) { + int count; + qfot_func_t *f = &type->t.func; + pointer_t *p; + count = f->num_params; + if (count < 0) + count = ~count; //ones complement + f->return_type = transfer_type (qfo, space, f->return_type); + for (i = 0, p = f->param_types; i < count; i++, p++) + *p = transfer_type (qfo, space, *p); + } else if (type->t.type == ev_pointer + || type->t.type == ev_field) { + qfot_fldptr_t *fp = &type->t.fldptr; + fp->aux_type = transfer_type (qfo, space, fp->aux_type); + } + break; + case ty_struct: + case ty_union: + case ty_enum: + type->t.strct.tag = add_string (QFOSTR (qfo, type->t.strct.tag)); + for (i = 0; i < type->t.strct.num_fields; i++) { + qfot_var_t *field = &type->t.strct.fields[i]; + field->type = transfer_type (qfo, space, field->type); + field->name = add_string (QFOSTR (qfo, field->name)); + } + break; + case ty_array: + type->t.array.type = transfer_type (qfo, space, + type->t.array.type); + break; + case ty_class: + //FIXME this is broken + break; + } + type_offset = defspace_alloc_loc (work_type_data, type->size); + memcpy (work_type_data->data + type_offset, type, + type->size * sizeof (pr_type_t)); + type->ty = -1; + type->t.class = type_offset; + return type_offset; +} + +static int +process_type (qfo_t *qfo, qfo_mspace_t *space) +{ + int i; + int size; + qfo_def_t *def; + qfo_def_t *type_def; + qfo_mspace_t *type_space; + qfot_type_t *type; + const char *name; + defref_t *ref; + pointer_t offset; + + if (qfo_type_defs) { + linker_error ("type space already defined"); + return 1; + } + qfo_type_defs = space; + type_space = &work->spaces[qfo_type_space]; + size = (type_space->num_defs + space->num_defs) * sizeof (qfo_def_t); + type_space->defs = realloc (type_space->defs, size); + for (i = 0, def = space->defs; i < space->num_defs; i++, def++) { + name = QFOSTR (qfo, def->name); + type = (qfot_type_t *) (char *) &space->d.data[def->offset]; + if ((ref = Hash_Find (defined_type_defs, name))) { + type->ty = -1; + type->t.class = REF (ref)->offset; + continue; + } + offset = transfer_type (qfo, space, def->offset); + type_def = type_space->defs + type_space->num_defs++; + ref = get_defref (type_def, type_space, &type_space->defs); + Hash_Add (defined_type_defs, ref); + while ((ref = Hash_Del (extern_type_defs, name))) { + REF (ref)->flags = 0; + REF (ref)->offset = type_def->offset; + } + } + size = type_space->num_defs * sizeof (qfo_def_t); + type_space->defs = realloc (type_space->defs, size); + + type_space->d.data = work_type_data->data; + type_space->data_size = work_type_data->size; + return 0; +} + +static int linker_add_qfo (qfo_t *qfo) { - update_relocs (qfo); - update_defs (qfo); - update_funcs (qfo); - update_lines (qfo); + static space_func funcs[] = { + process_null, + process_code, + process_data, + process_strings, + process_entity, + process_type, + }; + int i; + qfo_mspace_t *space; - add_qfo_strings (&qfo->spaces[qfo_strings_space]); - add_code (&qfo->spaces[qfo_code_space]); - add_data (qfo_near_data_space, &qfo->spaces[qfo_near_data_space]); - add_data (qfo_far_data_space, &qfo->spaces[qfo_far_data_space]); - add_data (qfo_entity_space, &qfo->spaces[qfo_entity_space]); - //FIXME handle type data + for (i = 0, space = qfo->spaces; i < qfo->num_spaces; i++, space++) { + if (space->type < 0 || space->type > qfos_type) { + linker_error ("bad space type"); + return 1; + } + if (funcs[space->type] (qfo, space)) + return 1; + } + return 0; } int @@ -296,9 +606,13 @@ linker_add_object_file (const char *filename) { qfo_t *qfo; + dsprintf (linker_current_file, "%s", filename); + qfo = qfo_open (filename); - if (!qfo) + if (!qfo) { + linker_error ("error opening"); return 1; + } if (qfo->num_spaces < qfo_num_spaces || qfo->spaces[qfo_null_space].type != qfos_null || qfo->spaces[qfo_strings_space].type != qfos_string @@ -307,8 +621,7 @@ linker_add_object_file (const char *filename) || qfo->spaces[qfo_far_data_space].type != qfos_data || qfo->spaces[qfo_entity_space].type != qfos_entity || qfo->spaces[qfo_type_space].type != qfos_type) { - //FIXME proper diagnostic - fprintf (stderr, "%s: missing or mangled standard spaces", filename); + linker_error ("missing or mangled standard spaces"); return 1; } @@ -372,26 +685,30 @@ linker_add_lib (const char *libname) QFile *f; qfo_t *qfo; + dsprintf (linker_current_file, "%s(%s)", path_name, + pack->files[i].name); f = Qsubopen (path_name, pack->files[i].filepos, pack->files[i].filelen, 1); qfo = qfo_read (f); Qclose (f); - if (!qfo) + if (!qfo) { + linker_error ("error opening"); return 1; + } for (j = 0; j < qfo->num_defs; j++) { -// qfo_def_t *def = qfo->defs + j; -// if ((def->flags & QFOD_GLOBAL) -// && !(def->flags & QFOD_EXTERNAL) -// && Hash_Find (extern_defs, qfo->strings + def->name)) { -// if (options.verbosity >= 2) -// printf ("adding %s because of %s\n", -// pack->files[i].name, qfo->strings + def->name); -// linker_add_qfo (qfo); -// did_something = 1; -// break; -// } + qfo_def_t *def = qfo->defs + j; + if ((def->flags & QFOD_GLOBAL) + && !(def->flags & QFOD_EXTERNAL) + && Hash_Find (extern_defs, QFOSTR (qfo, def->name))) { + if (options.verbosity >= 2) + printf ("adding %s because of %s\n", + pack->files[i].name, QFOSTR (qfo, def->name)); + linker_add_qfo (qfo); + did_something = 1; + break; + } } qfo_delete (qfo); @@ -559,3 +876,36 @@ def_warning (qfo_def_t *def, const char *fmt, ...) // pr.strings = strings; warning (0, "%s", string->str); } + +static void +linker_warning (const char *fmt, ...) +{ + va_list args; + + fprintf (stderr, "%s: warning: ", linker_current_file->str); + + va_start (args, fmt); + vfprintf (stderr, fmt, args); + va_end (args); + + fputs ("\n", stderr); + + if (options.warnings.promote) + pr.error_count++; +} + +static void +linker_error (const char *fmt, ...) +{ + va_list args; + + fprintf (stderr, "%s: ", linker_current_file->str); + + va_start (args, fmt); + vfprintf (stderr, fmt, args); + va_end (args); + + fputs ("\n", stderr); + + pr.error_count++; +}