/*
	obj_file.c

	qfcc object file support

	Copyright (C) 2002 Bill Currie <bill@taniwha.org>

	Author: Bill Currie <bill@taniwha.org>
	Date: 2002/6/21

	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

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>

#include "QF/dstring.h"
#include "QF/qendian.h"
#include "QF/quakeio.h"
#include "QF/va.h"

#include "compat.h"

#include "tools/qfcc/include/codespace.h"
#include "tools/qfcc/include/debug.h"
#include "tools/qfcc/include/def.h"
#include "tools/qfcc/include/defspace.h"
#include "tools/qfcc/include/emit.h"
#include "tools/qfcc/include/expr.h"
#include "tools/qfcc/include/function.h"
#include "tools/qfcc/include/obj_file.h"
#include "tools/qfcc/include/obj_type.h"
#include "tools/qfcc/include/options.h"
#include "tools/qfcc/include/qfcc.h"
#include "tools/qfcc/include/reloc.h"
#include "tools/qfcc/include/statements.h"
#include "tools/qfcc/include/strpool.h"
#include "tools/qfcc/include/symtab.h"
#include "tools/qfcc/include/type.h"
#include "tools/qfcc/include/value.h"

static int
count_relocs (reloc_t *r)
{
	int         count;

	for (count = 0; r; r = r->next)
		count++;
	return count;
}

static unsigned
qfo_def_flags (def_t *def)
{
	unsigned    flags = 0;
	if (def->initialized)
		flags |= QFOD_INITIALIZED;
	if (def->constant)
		flags |= QFOD_CONSTANT;
	if (def->global)
		flags |= QFOD_GLOBAL;
	if (def->external)
		flags |= QFOD_EXTERNAL;
	if (def->local)
		flags |= QFOD_LOCAL;
	if (def->system)
		flags |= QFOD_SYSTEM;
	if (def->nosave)
		flags |= QFOD_NOSAVE;
	if (def->param)
		flags |= QFOD_PARAM;
	return flags;
}

static void
qfo_encode_one_reloc (reloc_t *reloc, qfo_reloc_t **qfo_reloc, pr_int_t target)
{
	qfo_reloc_t *q;

	q = (*qfo_reloc)++;
	if (reloc->space)	// op_* relocs do not have a space (code is implied)
		q->space = reloc->space->qfo_space;
	q->offset = reloc->offset;
	q->type = reloc->type;
	q->target = target;
}

static int
qfo_encode_relocs (reloc_t *relocs, qfo_reloc_t **qfo_relocs, pr_int_t target)
{
	int         count;
	reloc_t    *r;

	for (count = 0, r = relocs; r; r = r->next) {
		count++;
		qfo_encode_one_reloc (r, qfo_relocs, target);
	}
	return count;
}

static int
qfo_encode_defs (qfo_t *qfo, def_t *defs, qfo_def_t **qfo_defs,
				 qfo_reloc_t **qfo_relocs)
{
	int         count;
	def_t      *d;
	qfo_def_t  *q;

	for (count = 0, d = defs; d; d = d->next) {
		count++;
		q = (*qfo_defs)++;
		d->qfo_def = q - qfo->defs;
		// defs in the type data space do not have types
		if (d->type)
			q->type = d->type->type_def->offset;
		q->name = ReuseString (d->name);
		q->offset = d->offset;
		q->relocs = *qfo_relocs - qfo->relocs;
		q->num_relocs = qfo_encode_relocs (d->relocs, qfo_relocs,
										   q - qfo->defs);
		q->flags = qfo_def_flags (d);
		q->file = d->file;
		q->line = d->line;
	}
	return count;
}

static void
qfo_count_space_stuff (qfo_t *qfo, defspace_t *space)
{
	def_t      *d;

	for (d = space->defs; d; d = d->next) {
		qfo->num_defs++;
		qfo->num_relocs += count_relocs (d->relocs);
	}
}

static void
qfo_count_function_stuff (qfo_t *qfo, function_t *functions)
{
	function_t *func;

	for (func = functions; func; func = func->next) {
		qfo->num_funcs++;
		qfo->num_relocs += count_relocs (func->refs);
		if (func->locals && func->locals->space) {
			qfo->num_spaces++;
			qfo_count_space_stuff (qfo, func->locals->space);
		}
	}
}

static void
qfo_count_stuff (qfo_t *qfo, pr_info_t *pr)
{
	qfo_count_space_stuff (qfo, pr->near_data);
	qfo_count_space_stuff (qfo, pr->far_data);
	qfo_count_space_stuff (qfo, pr->entity_data);
	qfo_count_space_stuff (qfo, pr->type_data);
	qfo_count_space_stuff (qfo, pr->debug_data);
	qfo_count_function_stuff (qfo, pr->func_head);
	qfo->num_relocs += count_relocs (pr->relocs);
}

static void
qfo_init_string_space (qfo_t *qfo, qfo_mspace_t *space, strpool_t *strings)
{
	size_t      size = strings->size * sizeof (*strings->strings);
	strings->qfo_space = space - qfo->spaces;
	space->type = qfos_string;
	space->num_defs = 0;
	space->defs = 0;
	space->strings = 0;
	if (strings->strings) {
		space->strings = malloc (size);
		memcpy (space->strings, strings->strings, size);
	}
	space->data_size = strings->size;
	space->id = qfo_strings_space;
}

static void
qfo_init_code_space (qfo_t *qfo, qfo_mspace_t *space, codespace_t *code)
{
	size_t      size = code->size * sizeof (*code->code);
	code->qfo_space = space - qfo->spaces;
	space->type = qfos_code;
	space->num_defs = 0;
	space->defs = 0;
	space->code = 0;
	if (code->code) {
		space->code = malloc (size);
		memcpy (space->code, code->code, size);
	}
	space->data_size = code->size;
	space->id = qfo_code_space;
}

static void
qfo_init_data_space (qfo_t *qfo, qfo_def_t **defs, qfo_reloc_t **relocs,
					 qfo_mspace_t *space, defspace_t *data)
{
	size_t      size = data->size * sizeof (*data->data);
	data->qfo_space = space - qfo->spaces;
	space->type = qfos_data;
	space->defs = *defs;
	space->num_defs = qfo_encode_defs (qfo, data->defs, defs, relocs);
	space->data = 0;
	if (data->data) {
		space->data = malloc (size);
		memcpy (space->data, data->data, size);
	}
	space->data_size = data->size;
	space->alignment = qfo_log2 (data->alignment);
}

static void
qfo_init_entity_space (qfo_t *qfo, qfo_def_t **defs, qfo_reloc_t **relocs,
					   qfo_mspace_t *space, defspace_t *data)
{
	data->qfo_space = space - qfo->spaces;
	space->type = qfos_entity;
	space->defs = *defs;
	space->num_defs = qfo_encode_defs (qfo, data->defs, defs, relocs);
	space->data = 0;
	space->data_size = data->size;
	space->id = qfo_entity_space;
	space->alignment = qfo_log2 (data->alignment);
}

static void
qfo_init_type_space (qfo_t *qfo, qfo_def_t **defs, qfo_reloc_t **relocs,
					 qfo_mspace_t *space, defspace_t *data)
{
	size_t      size = data->size * sizeof (*data->data);
	data->qfo_space = space - qfo->spaces;
	space->type = qfos_type;
	space->defs = *defs;
	space->num_defs = qfo_encode_defs (qfo, data->defs, defs, relocs);
	space->data = 0;
	if (data->data) {
		space->data = malloc (size);
		memcpy (space->data, data->data, size);
	}
	space->data_size = data->size;
	space->id = qfo_type_space;
}

static void
qfo_init_debug_space (qfo_t *qfo, qfo_def_t **defs, qfo_reloc_t **relocs,
					  qfo_mspace_t *space, defspace_t *data)
{
	size_t      size = data->size * sizeof (*data->data);
	data->qfo_space = space - qfo->spaces;
	space->type = qfos_debug;
	space->defs = *defs;
	space->num_defs = qfo_encode_defs (qfo, data->defs, defs, relocs);
	space->data = 0;
	if (data->data) {
		space->data = malloc (size);
		memcpy (space->data, data->data, size);
	}
	space->data_size = data->size;
	space->id = qfo_debug_space;
}

static void
qfo_encode_functions (qfo_t *qfo, qfo_def_t **defs, qfo_reloc_t **relocs,
					  qfo_mspace_t *space, function_t *functions)
{
	function_t *f;
	qfo_func_t *q;

	for (f = functions, q = qfo->funcs; f; f = f->next, q++) {
		q->name = f->s_name;
		q->type = f->def->type->type_def->offset;
		q->file = f->s_file;
		q->line = f->def->line;
		q->code = f->code;
		if (f->builtin)		// FIXME redundant
			q->code = -f->builtin;
		q->def = f->def->qfo_def;
		if (f->locals && f->locals->space) {
			space->id = f->locals->space->qfo_space;
			qfo_init_data_space (qfo, defs, relocs, space++, f->locals->space);
			q->locals_space = f->locals->space->qfo_space;
		}
		q->line_info = f->line_info;
		q->relocs = *relocs - qfo->relocs;
		q->num_relocs = qfo_encode_relocs (f->refs, relocs, q - qfo->funcs);
		q->params_start = f->params_start;
	}
}

qfo_t *
qfo_from_progs (pr_info_t *pr)
{
	qfo_t      *qfo;
	qfo_def_t  *def;
	qfo_reloc_t *reloc;
	reloc_t    *r;

	qfo = calloc (1, sizeof (qfo_t));
	qfo->num_spaces = qfo_num_spaces; // certain spaces are always present
	qfo_count_stuff (qfo, pr);
	qfo->spaces = calloc (qfo->num_spaces, sizeof (qfo_mspace_t));
	qfo->relocs = calloc (qfo->num_relocs, sizeof (qfo_reloc_t));
	qfo->defs = calloc (qfo->num_defs, sizeof (qfo_def_t));
	qfo->funcs = calloc (qfo->num_funcs, sizeof (qfo_func_t));

	def = qfo->defs;
	reloc = qfo->relocs;

	pr->code->qfo_space = qfo_code_space;
	pr->near_data->qfo_space = qfo_near_data_space;
	pr->far_data->qfo_space = qfo_far_data_space;
	pr->entity_data->qfo_space = qfo_entity_space;
	pr->type_data->qfo_space = qfo_type_space;
	pr->strings->qfo_space = qfo_strings_space;

	qfo_init_code_space (qfo, &qfo->spaces[qfo_code_space], pr->code);
	qfo_init_data_space (qfo, &def, &reloc, &qfo->spaces[qfo_near_data_space],
						 pr->near_data);
	qfo->spaces[qfo_near_data_space].id = qfo_near_data_space;
	qfo_init_data_space (qfo, &def, &reloc, &qfo->spaces[qfo_far_data_space],
						 pr->far_data);
	qfo->spaces[qfo_far_data_space].id = qfo_far_data_space;
	qfo_init_entity_space (qfo, &def, &reloc, &qfo->spaces[qfo_entity_space],
						   pr->entity_data);
	qfo_init_type_space (qfo, &def, &reloc, &qfo->spaces[qfo_type_space],
						 pr->type_data);
	qfo_init_debug_space (qfo, &def, &reloc, &qfo->spaces[qfo_debug_space],
						  pr->debug_data);

	qfo_encode_functions (qfo, &def, &reloc, qfo->spaces + qfo_num_spaces,
						  pr->func_head);
	if (pr->num_linenos) {
		qfo->lines = malloc (pr->num_linenos * sizeof (pr_lineno_t));
		memcpy (qfo->lines, pr->linenos,
				pr->num_linenos * sizeof (pr_lineno_t));
		qfo->num_lines = pr->num_linenos;
	}
	// strings must be done last because encoding defs and functions adds the
	// names
	qfo_init_string_space (qfo, &qfo->spaces[qfo_strings_space], pr->strings);

	qfo->num_loose_relocs = qfo->num_relocs - (reloc - qfo->relocs);
	for (r = pr->relocs; r; r = r->next) {
		if (r->type == rel_def_op) {
			qfo_encode_one_reloc (r, &reloc, r->label->dest->offset);
		} else if (r->type == rel_def_string) {
			def_t       d;
			d.space = r->space;
			d.offset = r->offset;
			qfo_encode_one_reloc (r, &reloc, D_STRING (&d));
		} else {
			qfo_encode_one_reloc (r, &reloc, 0);
		}
	}

	return qfo;
}

static int
qfo_space_size (qfo_mspace_t *space)
{
	switch (space->type) {
		case qfos_null:
			return 0;
		case qfos_code:
			return space->data_size * sizeof (*space->code);
		case qfos_data:
			// data size > 0 but data == null -> all data is zero
			if (!space->data)
				return 0;
			return space->data_size * sizeof (*space->data);
		case qfos_string:
			return space->data_size * sizeof (*space->strings);
		case qfos_entity:
			return 0;
		case qfos_type:
			return space->data_size * sizeof (*space->data);
		case qfos_debug:
			return space->data_size * sizeof (*space->data);
	}
	return 0;
}

static void
qfo_byteswap_space (void *space, int size, qfos_type_t type)
{
	int         c;
	pr_type_t  *val;
	dstatement_t *s;
	switch (type) {
		case qfos_null:
		case qfos_string:
			break;
		case qfos_code:
			for (s = (dstatement_t *) space, c = 0; c < size; c++, s++) {
				s->op = LittleShort (s->op);
				s->a = LittleShort (s->a);
				s->b = LittleShort (s->b);
				s->c = LittleShort (s->c);
			}
			break;
		case qfos_data:
		case qfos_entity:
		case qfos_type:
		case qfos_debug:
			for (val = (pr_type_t *) space, c = 0; c < size; c++, val++)
				val->value = LittleLong (val->value);
			break;
	}
}

int
qfo_write (qfo_t *qfo, const char *filename)
{
	unsigned    size;
	int         space_offset;
	unsigned    i;
	byte       *data;
	qfo_header_t *header;
	qfo_space_t *spaces;
	qfo_reloc_t *relocs;
	qfo_def_t  *defs;
	qfo_func_t *funcs;
	pr_lineno_t *lines;
	byte       *space_data;
	QFile      *file;

	file = Qopen (filename, options.gzip ? "wbz9" : "wb");
	if (!file) {
		perror (va (0, "failed to open %s for writing", filename));
		return -1;
	}

	size = sizeof (qfo_header_t);
	size += sizeof (qfo_space_t) * qfo->num_spaces;
	size += sizeof (qfo_reloc_t) * qfo->num_relocs;
	size += sizeof (qfo_def_t) * qfo->num_defs;
	size += sizeof (qfo_func_t) * qfo->num_funcs;
	size += sizeof (pr_lineno_t) * qfo->num_lines;
	space_offset = size;
	for (i = 0; i < qfo->num_spaces; i++)
		size += RUP (qfo_space_size (qfo->spaces + i), 16);
	data = calloc (1, size);
	header = (qfo_header_t *) data;
	memcpy (header->qfo, QFO, 4);
	header->version = LittleLong (QFO_VERSION);
	header->num_spaces = LittleLong (qfo->num_spaces);
	header->num_relocs = LittleLong (qfo->num_relocs);
	header->num_defs = LittleLong (qfo->num_defs);
	header->num_funcs = LittleLong (qfo->num_funcs);
	header->num_lines = LittleLong (qfo->num_lines);
	header->num_loose_relocs = LittleLong (qfo->num_loose_relocs);
	header->progs_version = LittleLong (options.code.progsversion);
	spaces = (qfo_space_t *) &header[1];
	relocs = (qfo_reloc_t *) &spaces[qfo->num_spaces];
	defs = (qfo_def_t *) &relocs[qfo->num_relocs];
	funcs = (qfo_func_t *) &defs[qfo->num_defs];
	lines = (pr_lineno_t *) &funcs[qfo->num_funcs];
	space_data = data + space_offset;
	for (i = 0; i < qfo->num_spaces; i++) {
		spaces[i].type = LittleLong (qfo->spaces[i].type);
		if (qfo->spaces[i].defs)
			spaces[i].defs = LittleLong (qfo->spaces[i].defs - qfo->defs);
		spaces[i].num_defs = LittleLong (qfo->spaces[i].num_defs);
		if (qfo->spaces[i].data) {
			int         space_size = qfo_space_size (qfo->spaces + i);
			spaces[i].data = LittleLong (space_data - data);
			memcpy (space_data, qfo->spaces[i].data, space_size);
			qfo_byteswap_space (space_data, qfo->spaces[i].data_size,
								qfo->spaces[i].type);
			space_data += RUP (space_size, 16);
		}
		spaces[i].data_size = LittleLong (qfo->spaces[i].data_size);
		spaces[i].id = LittleLong (qfo->spaces[i].id);
		spaces[i].alignment = LittleLong (qfo->spaces[i].alignment);
	}
	for (i = 0; i < qfo->num_relocs; i++) {
		relocs[i].space = LittleLong (qfo->relocs[i].space);
		relocs[i].offset = LittleLong (qfo->relocs[i].offset);
		relocs[i].type = LittleLong (qfo->relocs[i].type);
		relocs[i].target = LittleLong (qfo->relocs[i].target);
	}
	for (i = 0; i < qfo->num_defs; i++) {
		defs[i].type = LittleLong (qfo->defs[i].type);
		defs[i].name = LittleLong (qfo->defs[i].name);
		defs[i].offset = LittleLong (qfo->defs[i].offset);
		defs[i].relocs = LittleLong (qfo->defs[i].relocs);
		defs[i].num_relocs = LittleLong (qfo->defs[i].num_relocs);
		defs[i].flags = LittleLong (qfo->defs[i].flags);
		defs[i].file = LittleLong (qfo->defs[i].file);
		defs[i].line = LittleLong (qfo->defs[i].line);
	}
	for (i = 0; i < qfo->num_funcs; i++) {
		funcs[i].name = LittleLong (qfo->funcs[i].name);
		funcs[i].type = LittleLong (qfo->funcs[i].type);
		funcs[i].file = LittleLong (qfo->funcs[i].file);
		funcs[i].line = LittleLong (qfo->funcs[i].line);
		funcs[i].code = LittleLong (qfo->funcs[i].code);
		funcs[i].def = LittleLong (qfo->funcs[i].def);
		funcs[i].locals_space = LittleLong (qfo->funcs[i].locals_space);
		funcs[i].line_info = LittleLong (qfo->funcs[i].line_info);
		funcs[i].relocs = LittleLong (qfo->funcs[i].relocs);
		funcs[i].num_relocs = LittleLong (qfo->funcs[i].num_relocs);
		funcs[i].params_start = LittleLong (qfo->funcs[i].params_start);
	}
	for (i = 0; i < qfo->num_lines; i++) {
		lines[i].fa.addr = LittleLong (qfo->lines[i].fa.addr);
		lines[i].line = LittleLong (qfo->lines[i].line);
	}

	Qwrite (file, data, size);
	free (data);
	Qclose (file);
	return 0;
}

qfo_t *
qfo_read (QFile *file)
{
	char       *data;
	int         size;
	qfo_header_t *header;
	qfo_space_t *spaces;
	qfo_t      *qfo;
	unsigned    i;

	size = Qfilesize (file);
	data = malloc (size);
	Qread (file, data, size);

	header = (qfo_header_t *) data;
	header->version = LittleLong (header->version);
	if (memcmp (header->qfo, QFO, 4) || header->version != QFO_VERSION) {
		fprintf (stderr, "not a valid qfo file\n");
		free (data);
		return 0;
	}
	header->progs_version = LittleLong (header->progs_version);
	if (header->progs_version != PROG_ID_VERSION
		&& header->progs_version != PROG_V6P_VERSION
		&& header->progs_version != PROG_VERSION) {
		fprintf (stderr, "not a compatible qfo file\n");
		free (data);
		return 0;
	}
	// qfprogs leaves progsversion as 0
	if (options.code.progsversion
		&& header->progs_version != options.code.progsversion) {
		fprintf (stderr, "qfo file target VM does not match current target\n");
		free (data);
		return 0;
	}
	qfo = calloc (1, sizeof (qfo_t));

	qfo->num_spaces = LittleLong (header->num_spaces);
	qfo->num_relocs = LittleLong (header->num_relocs);
	qfo->num_defs = LittleLong (header->num_defs);
	qfo->num_funcs = LittleLong (header->num_funcs);
	qfo->num_lines = LittleLong (header->num_lines);
	qfo->num_loose_relocs = LittleLong (header->num_loose_relocs);
	qfo->progs_version = header->progs_version;	//already swapped

	spaces = (qfo_space_t *) &header[1];
	qfo->data = data;
	qfo->spaces = calloc (sizeof (qfo_mspace_t), qfo->num_spaces);
	qfo->relocs = (qfo_reloc_t *) &spaces[qfo->num_spaces];
	qfo->defs = (qfo_def_t *) &qfo->relocs[qfo->num_relocs];
	qfo->funcs = (qfo_func_t *) &qfo->defs[qfo->num_defs];
	qfo->lines = (pr_lineno_t *) &qfo->funcs[qfo->num_funcs];

	for (i = 0; i < qfo->num_spaces; i++) {
		qfo->spaces[i].type = LittleLong (spaces[i].type);
		qfo->spaces[i].num_defs = LittleLong (spaces[i].num_defs);
		if (qfo->spaces[i].num_defs)
			qfo->spaces[i].defs = qfo->defs + LittleLong (spaces[i].defs);
		qfo->spaces[i].data_size = LittleLong (spaces[i].data_size);
		if (spaces[i].data) {
			qfo->spaces[i].strings = data + LittleLong (spaces[i].data);
			qfo_byteswap_space (qfo->spaces[i].data,
								qfo->spaces[i].data_size, qfo->spaces[i].type);
		}
		qfo->spaces[i].id = LittleLong (spaces[i].id);
		qfo->spaces[i].alignment = LittleLong (spaces[i].alignment);
	}
	for (i = 0; i < qfo->num_relocs; i++) {
		qfo->relocs[i].space = LittleLong (qfo->relocs[i].space);
		qfo->relocs[i].offset = LittleLong (qfo->relocs[i].offset);
		qfo->relocs[i].type = LittleLong (qfo->relocs[i].type);
		qfo->relocs[i].target = LittleLong (qfo->relocs[i].target);
	}
	for (i = 0; i < qfo->num_defs; i++) {
		qfo->defs[i].type = LittleLong (qfo->defs[i].type);
		qfo->defs[i].name = LittleLong (qfo->defs[i].name);
		qfo->defs[i].offset = LittleLong (qfo->defs[i].offset);
		qfo->defs[i].relocs = LittleLong (qfo->defs[i].relocs);
		qfo->defs[i].num_relocs = LittleLong (qfo->defs[i].num_relocs);
		qfo->defs[i].flags = LittleLong (qfo->defs[i].flags);
		qfo->defs[i].file = LittleLong (qfo->defs[i].file);
		qfo->defs[i].line = LittleLong (qfo->defs[i].line);
	}
	for (i = 0; i < qfo->num_funcs; i++) {
		qfo->funcs[i].name = LittleLong (qfo->funcs[i].name);
		qfo->funcs[i].type = LittleLong (qfo->funcs[i].type);
		qfo->funcs[i].file = LittleLong (qfo->funcs[i].file);
		qfo->funcs[i].line = LittleLong (qfo->funcs[i].line);
		qfo->funcs[i].code = LittleLong (qfo->funcs[i].code);
		qfo->funcs[i].def = LittleLong (qfo->funcs[i].def);
		qfo->funcs[i].locals_space = LittleLong (qfo->funcs[i].locals_space);
		qfo->funcs[i].line_info = LittleLong (qfo->funcs[i].line_info);
		qfo->funcs[i].relocs = LittleLong (qfo->funcs[i].relocs);
		qfo->funcs[i].num_relocs = LittleLong (qfo->funcs[i].num_relocs);
	}
	for (i = 0; i < qfo->num_lines; i++) {
		qfo->lines[i].fa.addr = LittleLong (qfo->lines[i].fa.addr);
		qfo->lines[i].line = LittleLong (qfo->lines[i].line);
	}
	return qfo;
}

qfo_t *
qfo_open (const char *filename)
{
	qfo_t      *qfo;
	QFile      *file;

	file = Qopen (filename, "rbz");
	if (!file) {
		perror (filename);
		return 0;
	}
	qfo = qfo_read (file);
	Qclose (file);
	return qfo;
}

qfo_t *
qfo_new (void)
{
	return calloc (1, sizeof (qfo_t));
}

void
qfo_delete (qfo_t *qfo)
{
	if (qfo->data) {
		free (qfo->data);
	} else {
		unsigned    i;
		for (i = 0; i < qfo->num_spaces; i++)
			free (qfo->spaces[i].data);
		free (qfo->relocs);
		free (qfo->defs);
		free (qfo->funcs);
		free (qfo->lines);
	}
	free (qfo->spaces);
	free (qfo);
}

static etype_t
get_def_type (qfo_t *qfo, pr_ptr_t type)
{
	qfot_type_t *type_def;
	if (type >= qfo->spaces[qfo_type_space].data_size)
		return ev_void;
	type_def = QFO_POINTER (qfo, qfo_type_space, qfot_type_t, type);
	switch ((ty_meta_e)type_def->meta) {
		case ty_alias:	//XXX
		case ty_basic:
		case ty_handle:	//XXX
			// field, pointer and function types store their basic type in
			// the same location.
			return type_def->type;
		case ty_struct:
		case ty_union:
			return ev_invalid;
		case ty_enum:
			if (options.code.progsversion == PROG_ID_VERSION)
				return ev_float;
			return ev_int;
		case ty_array:
		case ty_class:
			return ev_invalid;
	}
	return ev_invalid;
}

static __attribute__((pure)) int
get_type_size (qfo_t *qfo, pr_ptr_t type)
{
	qfot_type_t *type_def;
	int          i, size;
	if (type >= qfo->spaces[qfo_type_space].data_size)
		return 1;
	type_def = QFO_POINTER (qfo, qfo_type_space, qfot_type_t, type);
	switch ((ty_meta_e)type_def->meta) {
		case ty_alias:
			return get_type_size (qfo, type_def->alias.aux_type);
		case ty_handle:	//XXX
		case ty_basic:
			// field, pointer and function types store their basic type in
			// the same location.
			return pr_type_size[type_def->type];
		case ty_struct:
			for (i = size = 0; i < type_def->strct.num_fields; i++)
				size += get_type_size (qfo, type_def->strct.fields[i].type);
			return size;
		case ty_union:
			for (i = size = 0; i < type_def->strct.num_fields; i++) {
				int         s;
				s = get_type_size (qfo, type_def->strct.fields[i].type);
				if (s > size)
					size = s;
			}
			return size;
		case ty_enum:
			return pr_type_size[ev_int];
		case ty_array:
			return type_def->array.size
					* get_type_size (qfo, type_def->array.type);
		case ty_class:
			return 0;	// FIXME
	}
	return 0;
}

int
qfo_log2 (unsigned x)
{
	int         log2 = 0;

	while (x > 1) {
		x >>= 1;
		log2++;
	}
	return log2;
}

static __attribute__((pure)) int
get_type_alignment_log (qfo_t *qfo, pr_ptr_t type)
{
	qfot_type_t *type_def;
	int          i, alignment;
	if (type >= qfo->spaces[qfo_type_space].data_size)
		return 0;
	type_def = QFO_POINTER (qfo, qfo_type_space, qfot_type_t, type);
	switch ((ty_meta_e)type_def->meta) {
		case ty_alias:
			return get_type_alignment_log (qfo, type_def->alias.aux_type);
		case ty_handle:	//XXX
		case ty_basic:
			// field, pointer and function types store their basic type in
			// the same location.
			return qfo_log2 (ev_types[type_def->type]->alignment);
		case ty_struct:
		case ty_union:
			for (i = alignment = 0; i < type_def->strct.num_fields; i++) {
				qfot_var_t *field = type_def->strct.fields + i;
				int         a;
				a = get_type_alignment_log (qfo, field->type);
				if (a > alignment) {
					alignment = a;
				}
			}
			return alignment;
		case ty_enum:
			return qfo_log2 (ev_types[ev_int]->alignment);
		case ty_array:
			return get_type_alignment_log (qfo, type_def->array.type);
		case ty_class:
			return 0;	// FIXME
	}
	return 0;
}

static __attribute__((pure)) dparmsize_t
get_paramsize (qfo_t *qfo, pr_ptr_t type)
{
	dparmsize_t paramsize = {
		get_type_size (qfo, type),
		get_type_alignment_log (qfo, type),
	};
	return paramsize;
}

static void
function_params (qfo_t *qfo, qfo_func_t *func, dfunction_t *df)
{
	qfot_type_t *type;
	int         num_params;
	int         i;

	if (func->type >= qfo->spaces[qfo_type_space].data_size)
		return;
	type = QFO_POINTER (qfo, qfo_type_space, qfot_type_t, func->type);
	if (type->meta == ty_alias) {
		type = QFO_POINTER (qfo, qfo_type_space, qfot_type_t,
							type->alias.aux_type);
	}
	if (type->meta != ty_basic || type->type != ev_func)
		return;
	df->numparams = num_params = type->func.num_params;
	if (num_params < 0)
		num_params = ~num_params;
	for (i = 0; i < num_params; i++) {
		df->param_size[i] = get_paramsize (qfo, type->func.param_types[i]);
	}
}

static void
qfo_def_to_ddef (qfo_t *qfo, const qfo_def_t *def, ddef_t *ddef)
{
	ddef->type = get_def_type (qfo, def->type);
	ddef->ofs = def->offset;
	ddef->name = def->name;
	if (!(def->flags & QFOD_NOSAVE)
		&& !(def->flags & QFOD_CONSTANT)
		&& (def->flags & QFOD_GLOBAL)
		&& ddef->type != ev_func
		&& ddef->type != ev_field)
		ddef->type |= DEF_SAVEGLOBAL;
}

static void
qfo_def_to_prdef (qfo_t *qfo, const qfo_def_t *def, pr_def_t *prdef)
{
	prdef->type = get_def_type (qfo, def->type);
	prdef->size = get_type_size (qfo, def->type);
	prdef->ofs = def->offset;
	prdef->name = def->name;
	prdef->type_encoding = def->type;
}

static void
qfo_relocate_refs (qfo_t *qfo)
{
	unsigned    i;
	qfo_reloc_t *reloc;

	for (i = 0, reloc = qfo->relocs; i < qfo->num_relocs; i++, reloc++) {
		// this will be valid only for *_def[_ofs] and *_field[_ofs] relocs
		qfo_def_t  *def = qfo->defs + reloc->target;

		switch (reloc->type) {
			case rel_none:
				break;
			case rel_op_a_def:
				QFO_STATEMENT (qfo, reloc->offset)->a = def->offset;
				break;
			case rel_op_b_def:
				QFO_STATEMENT (qfo, reloc->offset)->b = def->offset;
				break;
			case rel_op_c_def:
				QFO_STATEMENT (qfo, reloc->offset)->c = def->offset;
				break;
			case rel_op_a_op:
			case rel_op_b_op:
			case rel_op_c_op:
				// these should never appear in a qfo file
				break;
			case rel_def_op:
				QFO_INT (qfo, reloc->space, reloc->offset) = reloc->target;
				break;
			case rel_def_def:
				QFO_INT (qfo, reloc->space, reloc->offset) = def->offset;
				break;
			case rel_def_func:
				QFO_INT (qfo, reloc->space, reloc->offset) = reloc->target + 1;
				break;
			case rel_def_string:
				QFO_INT (qfo, reloc->space, reloc->offset) = reloc->target;
				break;
			case rel_def_field:
				QFO_INT (qfo, reloc->space, reloc->offset) = def->offset;
				break;
			case rel_op_a_def_ofs:
				QFO_STATEMENT (qfo, reloc->offset)->a += def->offset;
				break;
			case rel_op_b_def_ofs:
				QFO_STATEMENT (qfo, reloc->offset)->b += def->offset;
				break;
			case rel_op_c_def_ofs:
				QFO_STATEMENT (qfo, reloc->offset)->c += def->offset;
				break;
			case rel_def_def_ofs:
				QFO_INT (qfo, reloc->space, reloc->offset) += def->offset;
				break;
			case rel_def_field_ofs:
				QFO_INT (qfo, reloc->space, reloc->offset) += def->offset;
				break;
		}
	}
}

static unsigned
align_globals_size (unsigned size)
{
	if (options.code.progsversion == PROG_ID_VERSION)
		return size;
	return RUP (size, 16 / sizeof (pr_type_t));
}

static pr_uint_t
qfo_count_locals (const qfo_t *qfo, pr_uint_t *big_locals)
{
	if (options.code.progsversion == PROG_VERSION) {
		// Ruamoko progs keep their locals on the stack rather than a
		// "protected" area in the globals
		*big_locals = 0;
		return 0;
	}

	pr_uint_t   locals_size = 0;
	for (pr_uint_t i = qfo_num_spaces; i < qfo->num_spaces; i++) {
		if (options.code.local_merging) {
			if (locals_size < qfo->spaces[i].data_size) {
				locals_size = qfo->spaces[i].data_size;
				*big_locals = i;
			}
		} else {
			locals_size += align_globals_size (qfo->spaces[i].data_size);
		}
	}
	return locals_size;
}

typedef struct globals_info_s {
	pr_uint_t   locals_start;
	pr_uint_t   locals_size;
	pr_uint_t   big_locals;
	pr_uint_t   near_data_size;
	pr_uint_t   type_encodings_start;
	pr_uint_t   xdefs_start;
	pr_uint_t   xdefs_size;
	pr_uint_t   globals_size;
} globals_info_t;


static globals_info_t __attribute__((pure))
qfo_count_globals (qfo_t *qfo, dprograms_t *progs, int word_align)
{
	globals_info_t info = {};
	info.globals_size = qfo->spaces[qfo_near_data_space].data_size;
	info.globals_size = RUP (info.globals_size, word_align);

	info.locals_start = info.globals_size;
	info.locals_size = qfo_count_locals (qfo, &info.big_locals);
	info.globals_size += info.locals_size;
	info.near_data_size = info.globals_size;

	info.globals_size = RUP (info.globals_size, word_align);
	info.globals_size += qfo->spaces[qfo_far_data_space].data_size;

	info.type_encodings_start = info.globals_size;
	info.globals_size += qfo->spaces[qfo_type_space].data_size;
	info.globals_size = RUP (info.globals_size, type_xdef.alignment);

	info.xdefs_start = info.globals_size;
	info.xdefs_size = progs->globaldefs.count + progs->fielddefs.count;
	info.xdefs_size *= type_size (&type_xdef);
	info.globals_size += info.xdefs_size;

	return info;
}

static pr_uint_t
qfo_next_offset (pr_chunk_t chunk, pr_uint_t size, pr_uint_t align)
{
	size *= chunk.count;
	return RUP (chunk.offset + size, align);
}

dprograms_t *
qfo_to_progs (qfo_t *in_qfo, int *size)
{
	char       *strings;
	dstatement_t *statements;
	dfunction_t *functions;
	ddef_t     *globaldefs;
	ddef_t     *fielddefs;
	pr_type_t  *globals;
	pr_type_t  *locals;
	pr_type_t  *far_data;
	pr_type_t  *type_data;
	pr_type_t  *xdef_data;
	dprograms_t *progs;
	qfo_def_t  *types_def = 0;
	qfo_def_t  *xdefs_def = 0;
	unsigned    i, j;
	int         big_func = 0;
	pr_xdefs_t *xdefs = 0;
	xdef_t     *xdef;

	// id progs were aligned to only 4 bytes, but 8 is much more reasonable
	pr_uint_t   byte_align = 8;
	if (options.code.progsversion == PROG_VERSION) {
		byte_align = __alignof__ (pr_lvec4_t);
	} else if (options.code.progsversion == PROG_V6P_VERSION) {
		byte_align = 16;
	}
	pr_uint_t   word_align = byte_align / sizeof (pr_int_t);


	qfo_t      *qfo = alloca (sizeof (qfo_t)
							  + in_qfo->num_spaces * sizeof (qfo_mspace_t));
	*qfo = *in_qfo;
	qfo->spaces = (qfo_mspace_t *) (qfo + 1);
	for (i = 0; i < qfo->num_spaces; i++) {
		qfo->spaces[i] = in_qfo->spaces[i];
	}

	*size = RUP (sizeof (dprograms_t), 16);
	progs = calloc (1, *size);
	progs->version = options.code.progsversion;
	// crc is set in qfcc.c if enabled

	// these are in order in which they usually appear in the file rather
	// than the order they appear in the struct, though with the offsets
	// it doesn't matter too much. However, as people expect a certain
	// layout, it does matter enough to preserve the traditional file order.
	progs->strings.offset = *size;
	progs->strings.count = qfo->spaces[qfo_strings_space].data_size;

	progs->statements.offset = qfo_next_offset (progs->strings,
												sizeof (char),
												byte_align);
	progs->statements.count = qfo->spaces[qfo_code_space].data_size;

	progs->functions.offset = qfo_next_offset (progs->statements,
											   sizeof (dstatement_t),
											   byte_align);
	progs->functions.count = qfo->num_funcs + 1;

	progs->globaldefs.offset = qfo_next_offset (progs->functions,
												sizeof (dfunction_t),
												byte_align);
	progs->globaldefs.count = qfo->spaces[qfo_near_data_space].num_defs;
	//ddef offsets are 16 bits so the ddef ofs will likely be invalid
	//thus it will be forced invalid and the true offset written to the
	//.xdefs array in the progs file
	progs->globaldefs.count += qfo->spaces[qfo_far_data_space].num_defs;

	progs->fielddefs.offset = qfo_next_offset (progs->globaldefs,
											   sizeof (ddef_t),
											   byte_align);
	progs->fielddefs.count = qfo->spaces[qfo_entity_space].num_defs;

	progs->globals.offset = qfo_next_offset (progs->fielddefs,
											 sizeof (ddef_t),
											 byte_align);
	globals_info_t globals_info = qfo_count_globals (qfo, progs, word_align);
	progs->globals.count = globals_info.globals_size;

	progs->entityfields = qfo->spaces[qfo_entity_space].data_size;
	// qfo_debug_space does not go in the progs file

	*size = qfo_next_offset (progs->globals, sizeof (pr_type_t), 1);

	progs = realloc (progs, *size);
	memset (progs + 1, 0, *size - sizeof (dprograms_t));

#define qfo_block(t,b) (t *) ((byte *) progs + progs->b.offset)
	strings = qfo_block (char, strings);
	statements = qfo_block (dstatement_t, statements);
	functions = qfo_block (dfunction_t, functions);
	functions++;	// skip over null function
	globaldefs = qfo_block (ddef_t, globaldefs);
	fielddefs = qfo_block (ddef_t, fielddefs);
	globals = qfo_block (pr_type_t, globals);
	locals = globals + globals_info.locals_start;
	far_data = globals + globals_info.near_data_size;
	type_data = globals + globals_info.type_encodings_start;
	xdef_data = globals + globals_info.xdefs_start;

	memcpy (strings, qfo->spaces[qfo_strings_space].strings,
			qfo->spaces[qfo_strings_space].data_size * sizeof (char));
	qfo->spaces[qfo_strings_space].strings = strings;

	memcpy (statements, qfo->spaces[qfo_code_space].code,
			qfo->spaces[qfo_code_space].data_size * sizeof (dstatement_t));
	qfo->spaces[qfo_code_space].code = statements;

	for (i = 0; i < qfo->num_funcs; i++) {
		dfunction_t *df = functions + i;
		qfo_func_t *qf = qfo->funcs + i;
		qfo_mspace_t *space = qfo->spaces + qf->locals_space;

		df->first_statement = qf->code;
		df->locals = space->data_size;
		if (options.code.progsversion < PROG_VERSION) {
			df->params_start = globals_info.locals_start;
			// finalize the offsets of the local defs
			for (j = 0; j < space->num_defs; j++)
				space->defs[j].offset += globals_info.locals_start;
			if (!options.code.local_merging)
				globals_info.locals_start += align_globals_size (df->locals);
		} else {
			// relative to start of locals for Ruamoko progs
			df->params_start = qf->params_start;
		}
		df->profile = 0;
		df->name = qf->name;
		df->file = qf->file;
		function_params (qfo, qf, df);
	}

	for (i = 0; i < qfo->spaces[qfo_near_data_space].num_defs; i++) {
		qfo_def_t  *def = qfo->spaces[qfo_near_data_space].defs + i;
		const char *defname = QFO_GETSTR (qfo, def->name);
		if (!strcmp (defname, ".type_encodings"))
			types_def = def;
		if (!strcmp (defname, ".xdefs"))
			xdefs_def = def;
		qfo_def_to_ddef (qfo, def, globaldefs++);
	}

	for (i = 0; i < qfo->spaces[qfo_far_data_space].num_defs; i++) {
		qfo_def_t  *def = qfo->spaces[qfo_far_data_space].defs + i;
		def->offset += far_data - globals;
		qfo_def_to_ddef (qfo, def, globaldefs);
		// the correct offset will be written to the far data space
		globaldefs->ofs = -1;
		globaldefs++;
	}

	for (i = 0; i < qfo->spaces[qfo_type_space].num_defs; i++) {
		qfo->spaces[qfo_type_space].defs[i].offset += globals_info.type_encodings_start;
	}

	for (i = 0; i < qfo->spaces[qfo_entity_space].num_defs; i++) {
		qfo_def_to_ddef (qfo, qfo->spaces[qfo_entity_space].defs + i,
						 fielddefs + i);
	}

	// copy near data
	memcpy (globals, qfo->spaces[qfo_near_data_space].data,
			qfo->spaces[qfo_near_data_space].data_size * sizeof (pr_type_t));
	qfo->spaces[qfo_near_data_space].data = globals;
	// clear locals data
	memset (locals, 0, globals_info.locals_size * sizeof (pr_type_t));
	// copy far data
	memcpy (far_data, qfo->spaces[qfo_far_data_space].data,
			qfo->spaces[qfo_far_data_space].data_size * sizeof (pr_type_t));
	qfo->spaces[qfo_far_data_space].data = far_data;
	// copy type data
	memcpy (type_data, qfo->spaces[qfo_type_space].data,
			qfo->spaces[qfo_type_space].data_size * sizeof (pr_type_t));
	qfo->spaces[qfo_type_space].data = type_data;

	qfo_relocate_refs (qfo);
	if (types_def) {
		qfot_type_encodings_t *encodings;
		encodings = (qfot_type_encodings_t *) &globals[types_def->offset];
		encodings->types = globals_info.type_encodings_start;
		encodings->size = qfo->spaces[qfo_type_space].data_size;
	}
	if (xdefs_def) {
		xdefs = (pr_xdefs_t *) &globals[xdefs_def->offset];
		xdef = (xdef_t *) xdef_data;
		xdefs->xdefs = globals_info.xdefs_start;
		xdefs->num_xdefs = progs->globaldefs.count + progs->fielddefs.count;
		for (i = 0; i < qfo->spaces[qfo_near_data_space].num_defs;
			 i++, xdef++) {
			qfo_def_t  *def = qfo->spaces[qfo_near_data_space].defs + i;
			xdef->type = def->type + globals_info.type_encodings_start;
			xdef->ofs = def->offset;
		}
		for (i = 0; i < qfo->spaces[qfo_far_data_space].num_defs;
			 i++, xdef++) {
			qfo_def_t  *def = qfo->spaces[qfo_far_data_space].defs + i;
			xdef->type = def->type + globals_info.type_encodings_start;
			xdef->ofs = def->offset;
		}
		for (i = 0; i < qfo->spaces[qfo_entity_space].num_defs;
			 i++, xdef++) {
			qfo_def_t  *def =  qfo->spaces[qfo_entity_space].defs + i;
			xdef->type = def->type + globals_info.type_encodings_start;
			xdef->ofs = def->offset;
		}
	}

	// undo the relocation of the offsets of local defs so the local defs have
	// the correct offset in the debug info
	for (i = 0; i < qfo->num_funcs; i++) {
		dfunction_t *df = functions + i;
		qfo_func_t *qf = qfo->funcs + i;
		qfo_mspace_t *space = qfo->spaces + qf->locals_space;

		if (qf->locals_space == globals_info.big_locals)
			big_func = i;
		for (j = 0; j < space->num_defs; j++)
			space->defs[j].offset -= df->params_start;
	}

	if (options.verbosity >= 0) {
		const char *big_function = "";
		if (big_func)
			big_function = va (0, " (%s)", strings + qfo->funcs[big_func].name);
		printf ("%6i strofs\n", progs->strings.count);
		printf ("%6i statements\n", progs->statements.count);
		printf ("%6i functions\n", progs->functions.count);
		printf ("%6i global defs\n", progs->globaldefs.count);
		printf ("%6i field defs\n", progs->fielddefs.count);
		printf ("%6i globals\n", progs->globals.count);
		printf ("    %6i near globals\n", globals_info.near_data_size);
		printf ("        %6i locals size%s\n",
				globals_info.locals_size, big_function);
		printf ("    %6i far globals\n",
				qfo->spaces[qfo_far_data_space].data_size);
		printf ("    %6i type globals\n",
				qfo->spaces[qfo_type_space].data_size);
		if (xdefs) {
			printf ("    %6i extended defs\n",
					xdefs->num_xdefs * type_size (&type_xdef));
		}
		printf ("%6i entity fields\n", progs->entityfields);
	}

	if (options.verbosity >= -1)
		printf ("%6i TOTAL SIZE\n", *size);

	return progs;
}

pr_debug_header_t *
qfo_to_sym (qfo_t *qfo, int *size)
{
	pr_debug_header_t *sym;
	unsigned    i, j;
	pr_auxfunction_t *auxfuncs;
	pr_auxfunction_t *aux;
	pr_lineno_t *linenos;
	pr_def_t   *locals, *ld, *debug_defs;
	pr_type_t  *debug_data;

	*size = sizeof (pr_debug_header_t);
	sym = calloc (1, *size);

	sym->version = PROG_DEBUG_VERSION;
	for (i = 0; i < qfo->num_funcs; i++) {
		qfo_func_t *func = qfo->funcs + i;
		unsigned    num_locals = 0;

		if (func->locals_space)
			num_locals = qfo->spaces[func->locals_space].num_defs;
		if (!func->line_info && !num_locals)
			continue;
		sym->num_auxfunctions++;
		sym->num_locals += num_locals;
	}
	sym->num_linenos = qfo->num_lines;
	sym->num_debug_defs = qfo->spaces[qfo_debug_space].num_defs;
	sym->debug_data_size = qfo->spaces[qfo_debug_space].data_size;

	*size += sym->num_auxfunctions * sizeof (pr_auxfunction_t);
	*size += sym->num_linenos * sizeof (pr_lineno_t);
	*size += sym->num_locals * sizeof (pr_def_t);
	*size += sym->num_debug_defs * sizeof (pr_def_t);
	*size += sym->debug_data_size * sizeof (pr_type_t);
	sym = realloc (sym, *size);

	auxfuncs = (pr_auxfunction_t *)(sym + 1);
	linenos = (pr_lineno_t *)(auxfuncs + sym->num_auxfunctions);
	locals = (pr_def_t *)(linenos + sym->num_linenos);
	debug_defs = locals + sym->num_locals;
	debug_data = (pr_type_t *)(debug_defs + sym->num_debug_defs);

	sym->auxfunctions = (char *) auxfuncs - (char *) sym;
	sym->linenos = (char *) linenos - (char *) sym;
	sym->locals = (char *) locals - (char *) sym;
	sym->debug_defs = (char *) debug_defs - (char *) sym;
	sym->debug_data = (char *) debug_data - (char *) sym;

	ld = locals;

	for (i = 0, aux = auxfuncs; i < qfo->num_funcs; i++) {
		qfo_func_t *func = qfo->funcs + i;
		qfo_def_t  *def = 0;
		unsigned    num_locals = 0;
		qfot_type_t *type;

		if (func->locals_space) {
			num_locals = qfo->spaces[func->locals_space].num_defs;
			def = qfo->spaces[func->locals_space].defs;
		}
		if (!func->line_info && !num_locals)
			continue;
		memset (aux, 0, sizeof (*aux));
		aux->function = i + 1;
		aux->source_line = func->line;
		aux->line_info = func->line_info;
		if (func->line_info)
			qfo->lines[func->line_info].fa.func = aux - auxfuncs;
		if (num_locals) {
			aux->local_defs = ld - locals;
			for (j = 0; j < num_locals; j++, def++, ld++) {
				qfo_def_to_prdef (qfo, def, ld);
			}
		}
		aux->num_locals = num_locals;
		//FIXME check type
		type = QFO_POINTER (qfo, qfo_type_space, qfot_type_t, func->type);
		if (type->meta == ty_alias) {
			type = QFO_POINTER (qfo, qfo_type_space, qfot_type_t,
								type->alias.aux_type);
		}
		aux->return_type = type->func.return_type;
		aux++;
	}
	memcpy (linenos, qfo->lines, qfo->num_lines * sizeof (pr_lineno_t));
	for (i = 0; i < sym->num_debug_defs; i++) {
		qfo_def_t  *def = &qfo->spaces[qfo_debug_space].defs[i];
		pr_def_t   *prdef = &debug_defs[i];
		qfo_def_to_prdef (qfo, def, prdef);
	}
	memcpy (debug_data, qfo->spaces[qfo_debug_space].data,
			sym->debug_data_size * sizeof (*debug_data));
	return sym;
}