/* $Id: type.c,v 1.77 2012/02/23 15:11:54 vrsieh Exp $ 
 *
 * Copyright (C) 2008-2009 FAUcc Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "setup.h"
#include "type.h"
#include "expr.h"
#include "arch_i286_gen.h"
#include "arch_i386_gen.h"
#include "cc1.h"

const char *type_info[TYPE_MAX] = {
	[TYPE_NONE] = "none",
	[TYPE_ELIPSIS] = "elipsis",
	[TYPE_VOID] = "void",
	[TYPE_VA_LIST] = "va_list",
	[TYPE_INT8] = "int8",
	[TYPE_UINT8] = "uint8",
	[TYPE_INT16] = "int16",
	[TYPE_UINT16] = "uint16",
	[TYPE_INT32] = "int32",
	[TYPE_UINT32] = "uint32",
	[TYPE_INT64] = "int64",
	[TYPE_UINT64] = "uint64",
	[TYPE_FLOAT32] = "float32",
	[TYPE_FLOAT64] = "float64",
	[TYPE_FLOAT80] = "float80",
	[TYPE_STRUCT] = "struct",
	[TYPE_UNION] = "union",
	[TYPE_ENUM] = "enum",
	[TYPE_POINTER] = "pointer",
	[TYPE_ARRAY] = "array",
	[TYPE_FUNCTION] = "function",
};

void
type_rename_structunionenum(
	struct type *t,
	enum type_type type,
	const char *old,
	const char *new
)
{
	switch (t->type) {
	case TYPE_POINTER:
	case TYPE_ARRAY:
	case TYPE_FUNCTION:
		type_rename_structunionenum(t->declarator, type, old, new);
		break;
	default:
		if (t->type == type
		 && strcmp(t->identifier, old) == 0) {
			t->identifier = new;
		}
		break;
	}
}

void
type_rename_type(struct type *t, const char *old, const char *new)
{
	switch (t->type) {
	case TYPE_POINTER:
	case TYPE_ARRAY:
	case TYPE_FUNCTION:
		type_rename_type(t->declarator, old, new);
		break;
	default:
		break;
	}
}

int
type_is_type_spec(struct type *t)
{
	return t->type != TYPE_POINTER
	    && t->type != TYPE_ARRAY
	    && t->type != TYPE_FUNCTION;
}

int
type_is_void(struct type *t)
{
	return t->type == TYPE_VOID;
}

int
type_is_integer(struct type *t)
{
	if (TYPE_INT8 <= t->type
	 && t->type <= TYPE_UINT64) {
		return 1;
	} else {
		return 0;
	}
}

int
type_is_struct(struct type *t)
{
	return t->type == TYPE_STRUCT;
}

int
type_is_union(struct type *t)
{
	return t->type == TYPE_UNION;
}

int
type_is_ptrdiff(struct type *t)
{
	return (t == type_ptrdiff()
	     || t == type_uintptr_t());
}

int
type_is_pointer(struct type *t)
{
	return t->type == TYPE_POINTER
	    || t->type == TYPE_ARRAY;
}

int
type_is_array(struct type *t)
{
	return t->type == TYPE_ARRAY;
}

int
type_is_function(struct type *t)
{
	return t->type == TYPE_FUNCTION;
}

int
type_equal(struct type *ts0, struct type *ts1)
{
	for (;;) {
		if (ts0->type != ts1->type) {
			return 0;
		}
		if (ts0->type == TYPE_VOID
		 || ts0->type == TYPE_VA_LIST
		 || (TYPE_INT8 <= ts0->type && ts0->type <= TYPE_FLOAT80)) {
			return 1;
		}
		if (ts0->type == TYPE_STRUCT
		 || ts0->type == TYPE_UNION) {
			return strcmp(ts0->identifier, ts1->identifier) == 0;
		}

		assert(ts0->type == TYPE_POINTER
		    || ts0->type == TYPE_ARRAY
		    || ts0->type == TYPE_FUNCTION);

		ts0 = ts0->declarator;
		ts1 = ts1->declarator;
	}
}

void
type_align_size(
	struct scope *scope,
	struct type *t,
	unsigned int *alignp,
	unsigned int *sizep
)
{
	struct declaration *dion;
	unsigned int global_align;
	unsigned int align;
	unsigned int size;
	unsigned int offset;

	switch (t->type) {
	case TYPE_NONE:
	case TYPE_ENUM:
	case TYPE_MAX:
		assert(0);
	case TYPE_ELIPSIS:
		assert(0);
	case TYPE_VOID:
		assert(0);

	case TYPE_INT8:
	case TYPE_UINT8:
	case TYPE_INT16:
	case TYPE_UINT16:
	case TYPE_INT32:
	case TYPE_UINT32:
	case TYPE_INT64:
	case TYPE_UINT64:
	case TYPE_FLOAT32:
	case TYPE_FLOAT64:
	case TYPE_FLOAT80:
	case TYPE_VA_LIST:
	case TYPE_POINTER:
		switch (opt_b) {
		case TARGET_I286:
			arch_i286_align_size(scope, t, alignp, sizep);
			break;
		case TARGET_I386:
			arch_i386_align_size(scope, t, alignp, sizep);
			break;
		default:
			assert(0); /* FIXME */
		}
		break;
		
	case TYPE_STRUCT:
		if (! t->scope) {
			int ret;

			ret = scope_lookup_structunionenum(scope,
					t->type, t->identifier, &t);
			assert(0 <= ret);
		}
		scope = t->scope;

		global_align = 1;
		offset = 0;
		for (dion = scope->declaration_first; dion; dion = dion->next) {
			type_align_size(scope, dion->type_name,
					&align, &size);

			if (t->attr_packed) {
				align = 1;
			}

			if (global_align < align) {
				global_align = align;
			}

			offset += align - 1;
			offset &= ~(align - 1);

			offset += size;
		}

		offset += global_align - 1;
		offset &= ~(global_align - 1);

		if (t->attr_aligned) {
			*alignp = t->attr_aligned;
		} else {
			*alignp = global_align;
		}
		*sizep = offset;
		break;

	case TYPE_UNION:
		if (! t->scope) {
			int ret;

			ret = scope_lookup_structunionenum(scope,
					t->type, t->identifier, &t);
			assert(0 <= ret);
		}
		scope = t->scope;

		global_align = 1;
		offset = 0;
		for (dion = scope->declaration_first; dion; dion = dion->next) {
			type_align_size(scope, dion->type_name,
					&align, &size);

			if (t->attr_packed) {
				align = 1;
			}

			if (global_align < align) {
				global_align = align;
			}

			if (offset < size) {
				offset = size;
			}
		}

		if (t->attr_aligned) {
			*alignp = t->attr_aligned;
		} else {
			*alignp = global_align;
		}
		*sizep = offset;
		break;

	case TYPE_ARRAY:
		assert(t->dimension);
		assert(t->dimension->type == EXPR_INTEGER);
	
		type_align_size(scope, t->declarator, &align, &size);

		*alignp = align;
		*sizep = size * t->dimension->integer;
		break;

	case TYPE_FUNCTION:
		assert(0);
	}
}

unsigned int
type_sizeof(struct scope *scope, struct type *t)
{
	unsigned int align;
	unsigned int size;

	type_align_size(scope, t, &align, &size);

	return size;
}

unsigned int
type_offsetof(struct scope *scope, struct type *t, const char *mem)
{
	struct declaration *dion;
	unsigned int offset;
	unsigned int align;
	unsigned int size;

	offset = 0;
	for (dion = t->scope->declaration_first; ; dion = dion->next) {
		assert(dion);

		type_align_size(t->scope, dion->type_name, &align, &size);

		if (t->attr_packed) {
			align = 1;
		}

		offset += align - 1;
		offset &= ~(align - 1);

		if (strcmp(dion->identifier, mem) == 0) {
			break;
		}

		offset += size;
	}

	return offset;
}

struct expr *
type_dimension_get(struct type *ad)
{
	assert(ad->type == TYPE_ARRAY);

	return ad->dimension;
}

struct scope *
type_parameter_get(struct type *ad)
{
	assert(ad->type == TYPE_FUNCTION);
	assert(ad->parameter);

	return ad->parameter;
}

struct type *
type_new(void)
{
	struct type *d;

	d = malloc(sizeof(*d));
	assert(d);

	memset(d, 0, sizeof(*d));

	return d;
}

struct type *
type_type_spec(void)
{
	return NULL;
}

struct type *
type_gen(enum type_type type)
{
	static struct type *t[TYPE_MAX] = { NULL, NULL /* ... */ };

	assert(type < TYPE_MAX);

	if (t[type] == NULL) {
		t[type] = type_new();
		t[type]->type = type;
	}

	return t[type];
}

struct type *
type_void(void)
{
	return type_gen(TYPE_VOID);
}

struct type *
type_int8(void)
{
	return type_gen(TYPE_INT8);
}

struct type *
type_uint8(void)
{
	return type_gen(TYPE_UINT8);
}

struct type *
type_int16(void)
{
	return type_gen(TYPE_INT16);
}

struct type *
type_uint16(void)
{
	return type_gen(TYPE_UINT16);
}

struct type *
type_int32(void)
{
	return type_gen(TYPE_INT32);
}

struct type *
type_uint32(void)
{
	return type_gen(TYPE_UINT32);
}

struct type *
type_int64(void)
{
	return type_gen(TYPE_INT64);
}

struct type *
type_uint64(void)
{
	return type_gen(TYPE_UINT64);
}

struct type *
type_float32(void)
{
	return type_gen(TYPE_FLOAT32);
}

struct type *
type_float64(void)
{
	return type_gen(TYPE_FLOAT64);
}

struct type *
type_float80(void)
{
	return type_gen(TYPE_FLOAT80);
}

struct type *
type_signed_char(void)
{
	return type_int8();
}

struct type *
type_unsigned_char(void)
{
	return type_uint8();
}

struct type *
type_char(void)
{
	if (SIGNED_CHAR) {
		return type_signed_char();
	} else {
		return type_unsigned_char();
	}
}

struct type *
type_short_int(void)
{
	if (opt_f_sizeof_short_int == 2) {
		return type_gen(TYPE_INT16);
	} else {
		assert(0);
	}
}

struct type *
type_unsigned_short_int(void)
{
	if (opt_f_sizeof_short_int == 2) {
		return type_gen(TYPE_UINT16);
	} else {
		assert(0);
	}
}

struct type *
type_int(void)
{
	if (opt_f_sizeof_int == 2) {
		return type_gen(TYPE_INT16);
	} else if (opt_f_sizeof_int == 4) {
		return type_gen(TYPE_INT32);
	} else {
		assert(0);
	}
}

struct type *
type_unsigned_int(void)
{
	if (opt_f_sizeof_int == 2) {
		return type_gen(TYPE_UINT16);
	} else if (opt_f_sizeof_int == 4) {
		return type_gen(TYPE_UINT32);
	} else {
		assert(0);
	}
}

struct type *
type_long_int(void)
{
	if (opt_f_sizeof_long_int == 4) {
		return type_gen(TYPE_INT32);
	} else if (opt_f_sizeof_long_int == 8) {
		return type_gen(TYPE_INT64);
	} else {
		assert(0);
	}
}

struct type *
type_unsigned_long_int(void)
{
	if (opt_f_sizeof_long_int == 4) {
		return type_gen(TYPE_UINT32);
	} else if (opt_f_sizeof_long_int == 8) {
		return type_gen(TYPE_UINT64);
	} else {
		assert(0);
	}
}

struct type *
type_long_long_int(void)
{
	if (opt_f_sizeof_long_long_int == 8) {
		return type_gen(TYPE_INT64);
	} else {
		assert(0);
	}
}

struct type *
type_unsigned_long_long_int(void)
{
	if (opt_f_sizeof_long_long_int == 8) {
		return type_gen(TYPE_UINT64);
	} else {
		assert(0);
	}
}

struct type *
type_ptrdiff(void)
{
	switch (opt_b) {
	case TARGET_I286:
		return arch_i286_type_intptr_t();
	case TARGET_I386:
		return arch_i386_type_intptr_t();
	default:
		assert(0);
	}
}

struct type *
type_uintptr_t(void)
{
	switch (opt_b) {
	case TARGET_I286:
		return arch_i286_type_uintptr_t();
	case TARGET_I386:
		return arch_i386_type_uintptr_t();
	default:
		assert(0);
	}
}

struct type *
type_float(void)
{
	return type_float32();
}

struct type *
type_double(void)
{
	return type_float64();
}

struct type *
type_long_double(void)
{
	return type_float80();
}

struct type *
type_const_char_pointer(void)
{
	static struct type *t = NULL;

	if (! t) {
		t = type_new();
		t->type = TYPE_POINTER;
		t->declarator = type_const(type_char());
	}

	return t;
}

struct type *
type_const_char_array(unsigned int dim)
{
	struct type *t;

	t = type_new();
	t->type = TYPE_ARRAY;
	t->dimension = expr_integer(dim);
	t->declarator = type_const(type_char());

	return t;
}

struct type *
type_add_pointer(struct type *t, int const_volatile)
{
	struct type *nt;

	nt = type_new();

	nt->type = TYPE_POINTER;
	nt->declarator = NULL;
	if (const_volatile & 2) {
		nt->mod_const = 1;
	}
	if (const_volatile & 1) {
		nt->mod_volatile = 1;
	}

	return type_add_user(t, nt);
}

struct type *
type_add_array(struct type *t, struct expr *dimension)
{
	struct type *nt;

	nt = type_new();

	nt->type = TYPE_ARRAY;
	nt->dimension = dimension;
	nt->declarator = NULL;

	return type_add_user(t, nt);
}

struct type *
type_add_function(struct type *t, struct scope *parameter)
{
	struct type *nt;

	nt = type_new();

	nt->type = TYPE_FUNCTION;
	nt->parameter = parameter;
	nt->declarator = NULL;

	return type_add_user(t, nt);
}

struct type *
type_add_user(struct type *t, struct type *t_user)
{
	struct type *last;

	if (! t) {
		return t_user;

	} else {
		for (last = t;
		    last->declarator;
		    last = last->declarator) {
		}
		assert(! last->declarator);

		last->declarator = t_user;
	}

	return t;
}

struct type *
type_const(struct type *t)
{
	if (! t->constof) {
		struct type *nt;

		nt = type_new();
		assert(nt);

		memcpy(nt, t, sizeof *nt);
		nt->mod_const = 1;

		t->constof = nt;
	}

	return t->constof;
}

struct type *
type_volatile(struct type *t)
{
	if (! t->volatileof) {
		struct type *nt;

		nt = type_new();
		assert(nt);

		memcpy(nt, t, sizeof *nt);
		nt->mod_volatile = 1;

		t->volatileof = nt;
	}

	return t->volatileof;
}

struct type *
type_aligned(struct scope *scope, struct type *t, unsigned int aligned)
{
	struct type *nt;
	int ret;

	assert(t->type == TYPE_STRUCT);

	ret = scope_lookup_structunionenum(scope,
			t->type, t->identifier, &nt);
	assert(0 <= ret);
	assert(nt->scope);
	
	nt->attr_aligned = aligned;

	return t;
}

struct type *
type_packed(struct scope *scope, struct type *t)
{
	struct type *nt;
	int ret;

	assert(t->type == TYPE_STRUCT);

	ret = scope_lookup_structunionenum(scope,
			t->type, t->identifier, &nt);
	assert(0 <= ret);
	assert(nt->scope);
	
	nt->attr_packed = 1;

	return t;
}

struct type *
type_pure(struct type *t)
{
	if (! t->pureof) {
		struct type *tp;

		tp = type_new();

		tp->type = t->type;

		switch (t->type) {
		case TYPE_NONE:
		case TYPE_ENUM:
		case TYPE_MAX:
			assert(0);

		case TYPE_ELIPSIS:
		case TYPE_VA_LIST:
			break;

		case TYPE_VOID:
		case TYPE_INT8:
		case TYPE_UINT8:
		case TYPE_INT16:
		case TYPE_UINT16:
		case TYPE_INT32:
		case TYPE_UINT32:
		case TYPE_INT64:
		case TYPE_UINT64:
			break;

		case TYPE_FLOAT32:
		case TYPE_FLOAT64:
		case TYPE_FLOAT80:
			break;

		case TYPE_STRUCT:
		case TYPE_UNION:
			tp->identifier = t->identifier;
			tp->scope = NULL;
			break;

		case TYPE_POINTER:
			tp->declarator = t->declarator;
			break;

		case TYPE_ARRAY:
			tp->declarator = t->declarator;
			tp->dimension = t->dimension;
			break;

		case TYPE_FUNCTION:
			tp->declarator = t->declarator;
			tp->parameter = t->parameter;
			break;
		}

		if (type_is_array(tp)) {
			tp = type_amphersand(type_array_access(tp));
		} else if (type_is_function(tp)) {
			tp = type_amphersand(tp);
		}

		t->pureof = tp;
	}

	return t->pureof;
}

struct type *
type_amphersand(struct type *t)
{
	if (! t->addrof) {
		struct type *ndor;

		ndor = type_new();
		ndor->type = TYPE_POINTER;
		ndor->declarator = t;

		t->addrof = ndor;
	}

	return t->addrof;
}

struct type *
type_star(struct type *t)
{
	assert(t->type == TYPE_POINTER
	    || t->type == TYPE_ARRAY);

	return t->declarator;
}

struct type *
type_array_access(struct type *t)
{
	assert(t->type == TYPE_POINTER
	    || t->type == TYPE_ARRAY);

	return t->declarator;
}

struct type *
type_call(struct type *t)
{
	assert(t->type == TYPE_FUNCTION);

	return t->declarator;
}

struct type *
type_arithmetic(struct type *t0, struct type *t1)
{
	enum type_type res0;
	enum type_type res1;
	enum type_type res;

	if (t0->type == TYPE_POINTER
	 && t0->declarator->type != TYPE_VOID) {
		assert(t1->type == TYPE_POINTER
		    || (TYPE_INT8 <= t1->type && t1->type <= TYPE_UINT64));
		return t0;
	}
	if (t1->type == TYPE_POINTER
	 && t1->declarator->type != TYPE_VOID) {
		assert(t0->type == TYPE_POINTER
		    || (TYPE_INT8 <= t0->type && t0->type <= TYPE_UINT64));
		return t1;
	}
	if (t0->type == TYPE_POINTER) {
		assert(t1->type == TYPE_POINTER
		    || (TYPE_INT8 <= t1->type && t1->type <= TYPE_UINT64));
		return t0;
	}
	if (t1->type == TYPE_POINTER) {
		assert(t0->type == TYPE_POINTER
		    || (TYPE_INT8 <= t0->type && t0->type <= TYPE_UINT64));
		return t1;
	}
	if (t0->type == TYPE_VA_LIST) {
		return t0;
	}
	if (t1->type == TYPE_VA_LIST) {
		return t1;
	}

	res0 = t0->type;
	res1 = t1->type;

	assert(TYPE_INT8 <= res0
	    && res0 <= TYPE_FLOAT80);
	assert(TYPE_INT8 <= res1
	    && res1 <= TYPE_FLOAT80);

	res = (res0 < res1) ? res1 : res0;
	if (opt_f_sizeof_int == 2) {
		if (res < TYPE_INT16) {
			res = TYPE_INT16;
		}
	} else if (opt_f_sizeof_int == 4) {
		if (res < TYPE_INT32) {
			res = TYPE_INT32;
		}
	} else {
		assert(0);
	}
	res = (res == TYPE_FLOAT32) ? TYPE_FLOAT64 : res;

	return type_gen(res);
}

struct type *
type_add(struct type *t0, struct type *t1)
{
	if (t0->type == TYPE_POINTER
	 || t0->type == TYPE_ARRAY) {
		assert(t0->declarator->type != TYPE_VOID);
		assert(TYPE_INT8 <= t1->type
		    && t1->type <= TYPE_UINT64);
		return t0;

	} else if (t1->type == TYPE_POINTER
		|| t1->type == TYPE_ARRAY) {
		assert(t1->declarator->type != TYPE_VOID);
		assert(TYPE_INT8 <= t0->type
		    && t0->type <= TYPE_UINT64);
		return t1;

	} else {
		return type_arithmetic(t0, t1);
	}
}

struct type *
type_sub(struct type *t0, struct type *t1)
{
	if ((t0->type == TYPE_POINTER
	  || t0->type == TYPE_ARRAY)
	 && (t1->type == TYPE_POINTER
	  || t1->type == TYPE_ARRAY)) {
		assert(t0->declarator->type != TYPE_VOID);
		assert(t1->declarator->type != TYPE_VOID);
		return type_ptrdiff();

	} else if (t0->type == TYPE_POINTER
		|| t0->type == TYPE_ARRAY) {
		assert(t0->declarator->type != TYPE_VOID);
		assert(TYPE_INT8 <= t1->type
		    && t1->type <= TYPE_UINT64);
		return t0;

	} else {
		return type_arithmetic(t0, t1);
	}
}

struct type *
type_pointer_to_ptrdiff(struct type *t)
{
	struct type *t1;
	struct type *t2;
	struct declaration *dion;

	switch (t->type) {
	case TYPE_NONE:
	case TYPE_MAX:
		assert(0);

	case TYPE_ELIPSIS:
	case TYPE_VA_LIST:
	case TYPE_VOID:
	case TYPE_INT8:
	case TYPE_UINT8:
	case TYPE_INT16:
	case TYPE_UINT16:
	case TYPE_INT32:
	case TYPE_UINT32:
	case TYPE_INT64:
	case TYPE_UINT64:
	case TYPE_FLOAT32:
	case TYPE_FLOAT64:
	case TYPE_FLOAT80:
		break;

	case TYPE_POINTER:
		t = type_ptrdiff();
		break;

	case TYPE_STRUCT:
	case TYPE_UNION:
		if (t->scope) {
			for (dion = t->scope->declaration_first;
			    dion;
			    dion = dion->next) {
				dion->type_name = type_pointer_to_ptrdiff(
						dion->type_name);
			}
		}
		break;

	case TYPE_ENUM:
		break;

	case TYPE_ARRAY:
		t1 = type_pointer_to_ptrdiff(t->declarator);
		if (t1 != t->declarator) {
			t2 = type_new();
			t2->type = TYPE_ARRAY;
			t2->dimension = expr_dup(t->dimension);
			t2->declarator = t1;
			t = t2;
		}
		break;

	case TYPE_FUNCTION:
		t1 = type_pointer_to_ptrdiff(t->declarator);
		if (t1 != t->declarator) {
			t2 = type_new();
			t2->type = TYPE_FUNCTION;
			t2->parameter = t->parameter;
			t2->declarator = t1;
			t = t2;
		}
		for (dion = t->parameter->declaration_first;
		    dion;
		    dion = dion->next) {
			dion->type_name = type_pointer_to_ptrdiff(
					dion->type_name);
		}
		break;
	}
	return t;
}

void
type_free(struct type *t)
{
	/* FIXME */
}
