/*
	def.c

	def management and symbol tables

	Copyright (C) 2002 Bill Currie <bill@taniwha.org>
	Copyright (C) 1996-1997  Id Software, Inc.

	Author: Bill Currie <bill@taniwha.org>
	Date: 2002/06/09

	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/alloc.h"
#include "QF/hash.h"
#include "QF/sys.h"
#include "QF/va.h"

#include "tools/qfcc/include/qfcc.h"
#include "tools/qfcc/include/class.h"
#include "tools/qfcc/include/def.h"
#include "tools/qfcc/include/defspace.h"
#include "tools/qfcc/include/diagnostic.h"
#include "tools/qfcc/include/emit.h"
#include "tools/qfcc/include/expr.h"
#include "tools/qfcc/include/function.h"
#include "tools/qfcc/include/options.h"
#include "tools/qfcc/include/reloc.h"
#include "tools/qfcc/include/shared.h"
#include "tools/qfcc/include/strpool.h"
#include "tools/qfcc/include/struct.h"
#include "tools/qfcc/include/symtab.h"
#include "tools/qfcc/include/type.h"
#include "tools/qfcc/include/value.h"

static def_t *defs_freelist;

static void
set_storage_bits (def_t *def, storage_class_t storage)
{
	switch (storage) {
		case sc_system:
			def->system = 1;
			// fall through
		case sc_global:
			def->global = 1;
			def->external = 0;
			def->local = 0;
			def->param = 0;
			break;
		case sc_extern:
			def->global = 1;
			def->external = 1;
			def->local = 0;
			def->param = 0;
			break;
		case sc_static:
			def->external = 0;
			def->global = 0;
			def->local = 0;
			def->param = 0;
			break;
		case sc_local:
			def->external = 0;
			def->global = 0;
			def->local = 1;
			def->param = 0;
			break;
		case sc_param:
			def->external = 0;
			def->global = 0;
			def->local = 1;
			def->param = 1;
			break;
	}
	def->initialized = 0;
}

def_t *
new_def (const char *name, type_t *type, defspace_t *space,
		 storage_class_t storage)
{
	def_t      *def;

	ALLOC (16384, def_t, defs, def);

	def->return_addr = __builtin_return_address (0);

	def->name = name ? save_string (name) : 0;
	def->type = type;

	def->file = pr.source_file;
	def->line = pr.source_line;

	set_storage_bits (def, storage);

	if (space) {
		if (space->type == ds_virtual && storage == sc_static)
			internal_error (0, "static in a virtual space");
		if (space->type != ds_virtual
			&& (storage == sc_param || storage == sc_local))
			internal_error (0, "param or local in a non-virtual space");
		def->space = space;
		*space->def_tail = def;
		space->def_tail = &def->next;
	}

	if (!type)
		return def;

	if (!space && storage != sc_extern)
		internal_error (0, "non-external def with no storage space");

	if (is_class (type) || (is_array (type) && is_class(type->t.array.type))) {
		error (0, "statically allocated instance of class %s",
			   type->t.class->name);
		return def;
	}

	if (storage != sc_extern) {
		int         size = type_size (type);
		int         alignment = type->alignment;

		if (!size) {
			error (0, "%s has incomplete type", name);
			size = 1;
			alignment = 1;
		}
		if (alignment < 1) {
			print_type (type);
			internal_error (0, "type has no alignment");
		}
		def->offset = defspace_alloc_aligned_loc (space, size, alignment);
	}

	return def;
}

def_t *
alias_def (def_t *def, type_t *type, int offset)
{
	def_t      *alias;

	if (def->alias) {
		expr_t      e;
		e.file = def->file;
		e.line = def->line;
		internal_error (&e, "aliasing an alias def");
	}
	if (type_size (type) > type_size (def->type))
		internal_error (0, "aliasing a def to a larger type");
	if (offset < 0 || offset + type_size (type) > type_size (def->type))
		internal_error (0, "invalid alias offset");
	for (alias = def->alias_defs; alias; alias = alias->next) {
		if (alias->type == type && alias->offset == offset)
			return alias;
	}
	ALLOC (16384, def_t, defs, alias);
	alias->name = save_string (va (0, "[%s:%d]", def->name, offset));
	alias->return_addr = __builtin_return_address (0);
	alias->offset = offset;
	alias->offset_reloc = 1;
	alias->type = type;
	alias->alias = def;
	alias->line = pr.source_line;
	alias->file = pr.source_file;
	alias->next = def->alias_defs;
	alias->reg = def->reg;
	def->alias_defs = alias;
	return alias;
}

def_t *
temp_def (type_t *type)
{
	def_t      *temp;
	defspace_t *space = current_func->locals->space;
	int         size = type_size (type);
	int         alignment = type->alignment;

	if (size < 1 || size > MAX_DEF_SIZE) {
		internal_error (0, "%d invalid size for temp def", size);
	}
	if (alignment < 1) {
		internal_error (0, "temp type has no alignment");
	}
	if ((temp = current_func->temp_defs[size - 1])) {
		current_func->temp_defs[size - 1] = temp->temp_next;
		temp->temp_next = 0;
	} else {
		ALLOC (16384, def_t, defs, temp);
		temp->offset = defspace_alloc_aligned_loc (space, size, alignment);
		*space->def_tail = temp;
		space->def_tail = &temp->next;
		temp->name = save_string (va (0, ".tmp%d", current_func->temp_num++));
	}
	temp->return_addr = __builtin_return_address (0);
	temp->type = type;
	temp->file = pr.source_file;
	temp->line = pr.source_line;
	set_storage_bits (temp, sc_local);
	temp->space = space;
	temp->reg = current_func->temp_reg;
	return temp;
}

void
free_temp_def (def_t *temp)
{
	int         size = type_size (temp->type) - 1;
	temp->temp_next = current_func->temp_defs[size];
	current_func->temp_defs[size] = temp;
}

void
free_def (def_t *def)
{
	if (!def->return_addr)
		internal_error (0, "double free of def");
	if (def->alias) {
		def_t     **a;

		// pull the alias out of the base def's list of alias defs to avoid
		// messing up the list when the alias def is freed.
		for (a = &def->alias->alias_defs; *a; a = &(*a)->next) {
			if (*a == def) {
				*a = def->next;
				break;
			}
		}
	} else if (def->space) {
		def_t     **d;

		for (d = &def->space->defs; *d && *d != def; d = &(*d)->next)
			;
		if (!*d)
			internal_error (0, "freeing unlinked def %s", def->name);
		*d = def->next;
		if (&def->next == def->space->def_tail)
			def->space->def_tail = d;
		if (!def->external)
			defspace_free_loc (def->space, def->offset, type_size (def->type));
	}
	def->return_addr = 0;
	def->free_addr = __builtin_return_address (0);
	FREE (defs, def);
}

void
def_to_ddef (def_t *def, ddef_t *ddef, int aux)
{
	const type_t *type = unalias_type (def->type);

	if (aux)
		type = type->t.fldptr.type;	// aux is true only for fields
	ddef->type = type->type;
	ddef->ofs = def->offset;
	ddef->name = ReuseString (def->name);
}

static int
zero_memory (expr_t *local_expr, def_t *def, type_t *zero_type,
			 int init_size, int init_offset)
{
	int         zero_size = type_size (zero_type);
	expr_t     *zero = convert_nil (new_nil_expr (), zero_type);
	expr_t     *dst;

	for (; init_offset < init_size + 1 - zero_size; init_offset += zero_size) {
		dst = new_def_expr (def);
		dst = new_offset_alias_expr (zero_type, dst, init_offset);
		append_expr (local_expr, assign_expr (dst, zero));
	}
	return init_offset;
}

static void
init_elements_nil (def_t *def)
{
	if (def->local && local_expr) {
		// memset to 0
		int         init_size = type_size (def->type);
		int         init_offset = 0;

		if (options.code.progsversion != PROG_ID_VERSION) {
			init_offset = zero_memory (local_expr, def, &type_zero,
									   init_size, init_offset);
		}
		// probably won't happen any time soon, but who knows...
		if (options.code.progsversion != PROG_ID_VERSION
			&& init_size - init_offset >= type_size (&type_quaternion)) {
			init_offset = zero_memory (local_expr, def, &type_quaternion,
									   init_size, init_offset);
		}
		if (init_size - init_offset >= type_size (&type_vector)) {
			init_offset = zero_memory (local_expr, def, &type_vector,
									   init_size, init_offset);
		}
		if (options.code.progsversion != PROG_ID_VERSION
			&& init_size - init_offset >= type_size (&type_double)) {
			init_offset = zero_memory (local_expr, def, &type_double,
									   init_size, init_offset);
		}
		if (init_size - init_offset >= type_size (type_default)) {
			zero_memory (local_expr, def, type_default,
						 init_size, init_offset);
		}
	}
	// it's a global, so already initialized to 0
}

static void
init_elements (struct def_s *def, expr_t *eles)
{
	expr_t     *c;
	pr_type_t  *g;
	element_chain_t element_chain;
	element_t  *element;

	if (eles->type == ex_nil) {
		init_elements_nil (def);
		return;
	}

	element_chain.head = 0;
	element_chain.tail = &element_chain.head;
	build_element_chain (&element_chain, def->type, eles, 0);

	if (def->local && local_expr) {
		expr_t     *dst = new_def_expr (def);
		assign_elements (local_expr, dst, &element_chain);
	} else {
		def_t       dummy = *def;
		for (element = element_chain.head; element; element = element->next) {
			if (!element->expr
				|| ((c = constant_expr (element->expr))->type == ex_nil)) {
				// nil is type agnostic 0 and defspaces are initialized to
				// 0 on creation
				continue;
			}
			if (c->type == ex_nil) {
				c = convert_nil (c, element->type);
			}
			dummy.offset = def->offset + element->offset;
			g = D_POINTER (pr_type_t, &dummy);
			if (c->type == ex_labelref) {
				// reloc_def_* use only the def's offset and space, so dummy
				// is ok
				reloc_def_op (c->e.labelref.label, &dummy);
				continue;
			} else if (c->type == ex_value) {
				type_t     *ctype = get_type (c);
				if (ctype != element->type
					&& type_assignable (element->type, ctype)) {
					if (!c->implicit
						&& !type_promotes (element->type, ctype)) {
						warning (c, "initialization of %s with %s"
								 " (use a cast)\n)",
								 get_type_string (element->type),
								 get_type_string (ctype));
					}
					expr_t     *n = cast_expr (element->type, c);
					n->line = c->line;
					n->file = c->line;
					c = n;
				}
				if (get_type (c) != element->type) {
					error (c, "type mismatch in initializer");
					continue;
				}
			} else {
				if (!def->local || !local_expr) {
					error (c, "non-constant initializer");
					continue;
				}
			}
			if (c->type != ex_value) {
				internal_error (c, "bogus expression type in init_elements()");
			}
			if (c->e.value->lltype == ev_string) {
				EMIT_STRING (def->space, *(pr_string_t *) g,
							 c->e.value->v.string_val);
			} else {
				memcpy (g, &c->e.value->v, type_size (get_type (c)) * 4);
			}
		}
	}

	free_element_chain (&element_chain);
}

void
init_vector_components (symbol_t *vector_sym, int is_field, symtab_t *symtab)
{
	expr_t     *vector_expr;
	int         i;
	static const char *fields[] = { "x", "y", "z" };

	vector_expr = new_symbol_expr (vector_sym);
	for (i = 0; i < 3; i++) {
		expr_t     *expr = 0;
		symbol_t   *sym;
		const char *name;

		name = va (0, "%s_%s", vector_sym->name, fields[i]);
		sym = symtab_lookup (symtab, name);
		if (sym) {
			if (sym->table == symtab) {
				if (sym->sy_type != sy_expr) {
					error (0, "%s redefined", name);
					sym = 0;
				} else {
					expr = sym->s.expr;
					if (is_field) {
						if (expr->type != ex_value
							|| expr->e.value->lltype != ev_field) {
							error (0, "%s redefined", name);
							sym = 0;
						} else {
							expr->e.value->v.pointer.def = vector_sym->s.def;
							expr->e.value->v.pointer.val = i;
						}
					}
				}
			} else {
				sym = 0;
			}
		}
		if (!sym)
			sym = new_symbol (name);
		if (!expr) {
			if (is_field) {
				expr = new_field_expr (i, &type_float, vector_sym->s.def);
			} else {
				expr = field_expr (vector_expr,
								   new_symbol_expr (new_symbol (fields[i])));
			}
		}
		sym->sy_type = sy_expr;
		sym->s.expr = expr;
		if (!sym->table)
			symtab_addsymbol (symtab, sym);
	}
}

static void
init_field_def (def_t *def, expr_t *init, storage_class_t storage,
				symtab_t *symtab)
{
	type_t     *type = (type_t *) dereference_type (def->type);//FIXME cast
	def_t      *field_def;
	symbol_t   *field_sym;
	reloc_t    *relocs = 0;

	if (!init)  {
		field_sym = symtab_lookup (pr.entity_fields, def->name);
		if (!field_sym)
			field_sym = new_symbol_type (def->name, type);
		if (field_sym->s.def && field_sym->s.def->external) {
			//FIXME this really is not the right way
			relocs = field_sym->s.def->relocs;
			free_def (field_sym->s.def);
			field_sym->s.def = 0;
		}
		if (!field_sym->s.def) {
			field_sym->s.def = new_def (def->name, type, pr.entity_data, storage);
			reloc_attach_relocs (relocs, &field_sym->s.def->relocs);
			field_sym->s.def->nosave = 1;
		}
		field_def = field_sym->s.def;
		if (!field_sym->table)
			symtab_addsymbol (pr.entity_fields, field_sym);
		if (storage != sc_extern) {
			D_INT (def) = field_def->offset;
			reloc_def_field (field_def, def);
			def->constant = 1;
			def->nosave = 1;
		}
		// no support for initialized field vector componets (yet?)
		if (is_vector(type) && options.code.vector_components)
			init_vector_components (field_sym, 1, symtab);
	} else if (init->type == ex_symbol) {
		symbol_t   *sym = init->e.symbol;
		symbol_t   *field = symtab_lookup (pr.entity_fields, sym->name);
		if (field) {
			expr_t     *new = new_field_expr (0, field->type, field->s.def);
			init->type = new->type;
			init->e = new->e;
		}
	}
}

static int
num_elements (expr_t *e)
{
	int         count = 0;
	for (e = e->e.block.head; e; e = e->next) {
		count++;
	}
	return count;
}

void
initialize_def (symbol_t *sym, expr_t *init, defspace_t *space,
				storage_class_t storage, symtab_t *symtab)
{
	symbol_t   *check = symtab_lookup (symtab, sym->name);
	reloc_t    *relocs = 0;

	if (check && check->table == symtab) {
		if (check->sy_type != sy_var || !type_same (check->type, sym->type)) {
			error (0, "%s redefined", sym->name);
		} else {
			// is var and same type
			if (!check->s.def)
				internal_error (0, "half defined var");
			if (storage == sc_extern) {
				if (init)
					error (0, "initializing external variable");
				return;
			}
			if (init && check->s.def->initialized) {
				error (0, "%s redefined", sym->name);
				return;
			}
			sym = check;
		}
	}
	sym->sy_type = sy_var;
	if (!sym->table)
		symtab_addsymbol (symtab, sym);

	if (sym->s.def && sym->s.def->external) {
		//FIXME this really is not the right way
		relocs = sym->s.def->relocs;
		free_def (sym->s.def);
		sym->s.def = 0;
	}
	if (!sym->s.def) {
		if (is_array (sym->type) && !type_size (sym->type)
			&& init && init->type == ex_compound) {
			sym->type = array_type (sym->type->t.array.type,
									num_elements (init));
		}
		sym->s.def = new_def (sym->name, sym->type, space, storage);
		reloc_attach_relocs (relocs, &sym->s.def->relocs);
	}
	if (is_vector(sym->type) && options.code.vector_components)
		init_vector_components (sym, 0, symtab);
	if (sym->type->type == ev_field && storage != sc_local
		&& storage != sc_param)
		init_field_def (sym->s.def, init, storage, symtab);
	if (storage == sc_extern) {
		if (init)
			error (0, "initializing external variable");
		return;
	}
	if (!init)
		return;
	convert_name (init);
	if (init->type == ex_error)
		return;
	if ((is_array (sym->type) || is_struct (sym->type)
		 || is_nonscalar (sym->type))
		&& ((init->type == ex_compound)
			|| init->type == ex_nil)) {
		init_elements (sym->s.def, init);
		sym->s.def->initialized = 1;
	} else {
		type_t     *init_type;
		if (init->type == ex_nil) {
			convert_nil (init, sym->type);
		}
		init_type = get_type (init);
		if (!type_assignable (sym->type, init_type)) {
			error (init, "type mismatch in initializer: %s = %s",
				   get_type_string (sym->type), get_type_string (init_type));
			return;
		}
		if (storage == sc_local && local_expr) {
			sym->s.def->initialized = 1;
			init = assign_expr (new_symbol_expr (sym), init);
			// fold_constants takes care of int/float conversions
			append_expr (local_expr, fold_constants (init));
		} else {
			if (!is_constant (init)) {
				error (init, "non-constant initializier");
				return;
			}
			while (init->type == ex_alias) {
				init = init->e.alias.expr;
			}
			if (init->type != ex_value) {	//FIXME enum etc
				internal_error (0, "initializier not a value");
				return;
			}
			if (init->e.value->lltype == ev_ptr
				|| init->e.value->lltype == ev_field) {
				// FIXME offset pointers
				D_INT (sym->s.def) = init->e.value->v.pointer.val;
				if (init->e.value->v.pointer.def)
					reloc_def_field (init->e.value->v.pointer.def, sym->s.def);
			} else {
				ex_value_t *v = init->e.value;
				if (!init->implicit
					&& is_double (init_type)
					&& (is_integral (sym->type) || is_float (sym->type))) {
					warning (init, "assigning double to %s in initializer "
							 "(use a cast)", sym->type->name);
				}
				if (!type_same (sym->type, init_type))
					v = convert_value (v, sym->type);
				if (v->lltype == ev_string) {
					EMIT_STRING (sym->s.def->space, D_STRING (sym->s.def),
								 v->v.string_val);
				} else {
					memcpy (D_POINTER (pr_type_t, sym->s.def), &v->v,
							type_size (sym->type) * sizeof (pr_type_t));
				}
			}
			sym->s.def->initialized = 1;
			if (options.code.const_initializers) {
				sym->s.def->constant = 1;
				sym->s.def->nosave = 1;
			}
		}
	}
	sym->s.def->initializer = init;
}

int
def_overlap (def_t *d1, def_t *d2)
{
	int         offs1, size1;
	int         offs2, size2;
	defspace_t *s1 = d1->space;
	defspace_t *s2 = d2->space;

	if (d1->alias)
		s1 = d1->alias->space;
	if (d2->alias)
		s2 = d2->alias->space;
	/// Defs in different spaces never overlap.
	if (s1 != s2)
		return 0;

	offs1 = d1->offset;
	if (d1->alias)
		offs1 += d1->alias->offset;
	size1 = type_size (d1->type);

	offs2 = d2->offset;
	if (d2->alias)
		offs2 += d2->alias->offset;
	size2 = type_size (d2->type);

	if (offs1 <= offs2 && offs1 + size1 >= offs2 + size2)
		return 2;	// d1 fully overlaps d2
	if (offs1 < offs2 + size2 && offs2 < offs1 + size1)
		return 1;	// d1 and d2 at least partially overlap
	return 0;
}

int
def_offset (def_t *def)
{
	int         offset = def->offset;
	if (def->alias)
		offset += def->alias->offset;
	return offset;
}

int
def_size (def_t *def)
{
	return type_size (def->type);
}

int
def_visit_all (def_t *def, int overlap,
			   int (*visit) (def_t *, void *), void *data)
{
	def_t      *start_def = def;
	int         ret;

	if ((ret = visit (def, data)))
		return ret;
	if (def->alias) {
		def = def->alias;
		if ((ret = visit (def, data)))
			return ret;
	} else {
		overlap = 0;
	}
	for (def = def->alias_defs; def; def = def->next) {
		if (def == start_def)
			continue;
		if (overlap && def_overlap (def, start_def) < overlap)
			continue;
		if ((ret = visit (def, data)))
			return ret;
	}
	return 0;
}
