Finally, some real progress on the linker.

There is still much to be done, but this seems to be a workable approach.
This commit is contained in:
Bill Currie 2011-02-28 23:18:07 +09:00
parent bad7078797
commit 3761cddca6
1 changed files with 460 additions and 110 deletions

View File

@ -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++;
}