/****************************************************************
 *								*
 *	Copyright 2001, 2014 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_string.h"

#include <errno.h>

#include "compiler.h"
#include <rtnhdr.h>
#include "obj_gen.h"
#include "cgp.h"
#include "mdq.h"
#include "cmd_qlf.h"
#include "objlabel.h"	/* needed for masscomp.h */
#include "masscomp.h"
#include "stringpool.h"
#include "parse_file.h"
#include "gtm_fcntl.h"
#include "gtm_unistd.h"
#include "gtmio.h"
#include "mmemory.h"
#include "obj_file.h"

LITREF char gtm_release_name[];
LITREF int4 gtm_release_name_len;

GBLREF mliteral 	literal_chain;
GBLREF char 		source_file_name[];
GBLREF unsigned short 	source_name_len;

GBLREF command_qualifier cmd_qlf;
GBLREF mident		routine_name;
GBLREF mident		module_name;
GBLREF boolean_t	run_time;
GBLREF int4		mlmax, mvmax;
GBLREF int4		code_size, lit_addrs, lits_size;
GBLREF spdesc		stringpool;

GBLDEF int4	psect_use_tab[GTM_LASTPSECT];	/* bytes of each psect in this module */
GBLREF char	object_file_name[];
GBLREF short	object_name_len;
GBLREF int	object_file_des;

static short int current_psect;
static char emit_buff[OBJ_EMIT_BUF_SIZE];	/* buffer for emit output */
static short int emit_buff_used;		/* number of chars in emit_buff */

GBLREF uint4 txtrel_cnt;
static uint4 cdlits;
static struct rel_table *data_rel, *data_rel_end;
static struct rel_table *text_rel, *text_rel_end;
static struct sym_table *symbols;
DEBUG_ONLY(static uint4 txtrel_cnt_in_hdr;)

error_def(ERR_OBJFILERR);
error_def(ERR_STRINGOFLOW);

void create_object_file(rhdtyp *rhead)
{
	struct exec	hdr;

	assert(!run_time);
	init_object_file_name(); /* inputs: cmd_qlf.object_file, module_name; outputs: object_file_name, object_name_len */
	object_file_des = mk_tmp_object_file(object_file_name, object_name_len);
	memcpy(&rhead->jsb[0], "GTM_CODE", SIZEOF(rhead->jsb));
	emit_addr((char *)&rhead->src_full_name.addr - (char *)rhead,
		(int4)rhead->src_full_name.addr, (int4 *)&rhead->src_full_name.addr);
	emit_addr((char *)&rhead->routine_name.addr - (char *)rhead,
		(int4)rhead->routine_name.addr, (int4 *)&rhead->routine_name.addr);
	txtrel_cnt += 2;
	DEBUG_ONLY(txtrel_cnt_in_hdr = txtrel_cnt;)
	set_psect(GTM_CODE, 0);
	hdr.a_magic = OMAGIC;
	hdr.a_stamp = OBJ_LABEL;
	hdr.a_entry = 0;
	hdr.a_bss = 0;
	hdr.a_text = code_size;
	assert(0 == PADLEN(lits_size, NATIVE_WSIZE));
	hdr.a_data = lits_size;		/* and pad to even # */
	hdr.a_syms = (mlmax + cdlits) * SIZEOF(struct nlist);
	hdr.a_trsize = txtrel_cnt * SIZEOF(struct relocation_info);
	hdr.a_drsize = lit_addrs * SIZEOF(struct relocation_info);
	emit_immed((char *)&hdr, SIZEOF(hdr));
	memset(psect_use_tab, 0, SIZEOF(psect_use_tab));
	emit_immed((char *)rhead, SIZEOF(*rhead));
}

void finish_object_file(void)
{
	assert(0 == PADLEN(lits_size, NATIVE_WSIZE));
	resolve_sym();
	output_relocation();
	output_symbol();
	if (emit_buff_used)
		buff_emit();
	if ((off_t)-1 == lseek(object_file_des, (off_t)0, SEEK_SET))
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_OBJFILERR, 2, object_name_len, object_file_name, errno);
}

void drop_object_file(void)
{
	int	rc;

        if (FD_INVALID != object_file_des)
        {
		UNLINK(object_file_name);
		CLOSEFILE_RESET(object_file_des, rc);	/* resets "object_file_des" to FD_INVALID */
        }
}

void emit_addr(int4 refaddr, int4 offset, int4 *result)
{
	struct rel_table *newrel;

	if (run_time)
	{
		unsigned char *ptr;
		ptr = stringpool.free;
		*result = offset - (int4) ptr;
	} else
	{	*result = offset + code_size;
		newrel = (struct rel_table *) mcalloc(SIZEOF(struct rel_table));
		newrel->next = (struct rel_table *) 0;
		newrel->resolve = 0;
		newrel->r.r_address = refaddr;
		newrel->r.r_symbolnum = N_DATA;
		newrel->r.r_pcrel = 0;
		newrel->r.r_length = 2;
		newrel->r.r_extern = 0;
		newrel->r.r_pad = 0;
		if (!text_rel)
			text_rel = text_rel_end = newrel;
		else
		{	text_rel_end->next = newrel;
			text_rel_end = newrel;
		}
	}
	return;
}

void emit_pidr(int4 refoffset, int4 data_offset, int4 *result)
{
	struct rel_table *newrel;

	assert(!run_time);
	refoffset += code_size;
	data_offset += code_size;
	*result = data_offset;
	newrel = (struct rel_table *) mcalloc(SIZEOF(struct rel_table));
	newrel->next = (struct rel_table *)0;
	newrel->resolve = 0;
	newrel->r.r_address = refoffset;
	newrel->r.r_symbolnum = N_DATA;
	newrel->r.r_pcrel = 0;
	newrel->r.r_length = 2;
	newrel->r.r_extern = 0;
	newrel->r.r_pad = 0;
	if (!data_rel)
		data_rel = data_rel_end = newrel;
	else
	{	data_rel_end->next = newrel;
		data_rel_end = newrel;
	}
}

void emit_reference(uint4 refaddr, mstr *name, uint4 *result)
{
	struct sym_table *sym;
	struct rel_table *newrel;

	sym = define_symbol(0, name, 0);
	assert(sym);
	if (sym->n.n_type == (N_TEXT | N_EXT))
		*result = sym->n.n_value;
	else
	{
		newrel = (struct rel_table *) mcalloc(SIZEOF(struct rel_table));
		newrel->next = (struct rel_table *)0;
		newrel->resolve = 0;
		newrel->r.r_address = refaddr;
		newrel->r.r_symbolnum = 0;
		newrel->r.r_pcrel = 0;
		newrel->r.r_length = 2;
		newrel->r.r_extern = 1;
		newrel->r.r_pad = 0;
		if (!text_rel)
			text_rel = text_rel_end = newrel;
		else
		{	text_rel_end->next = newrel;
			text_rel_end = newrel;
		}
		if (sym->resolve)
			newrel->resolve = sym->resolve;
		sym->resolve = newrel;
		*result = 0;
	}
}

/*
 *	emit_immed
 *
 *	Args:  buffer of executable code, and byte count to be output.
 */
void emit_immed(char *source, uint4 size)
{
	short int write;

	if (run_time)
	{
		if (!IS_STP_SPACE_AVAILABLE(size))
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STRINGOFLOW);
		memcpy(stringpool.free, source, size);
		stringpool.free += size;
	} else
	{	while(size > 0)
		{
			write = SIZEOF(emit_buff) - emit_buff_used;
			write = size < write ? size : write;
			memcpy(emit_buff + emit_buff_used, source, write);
			size -= write;
			source += write;
			emit_buff_used += write;
			psect_use_tab[current_psect] += write;
			if (size)
				buff_emit();
		}
	}
}

/*
 *	buff_emit
 *
 *	Args:  buffer pointer, number of bytes to emit
 */
void buff_emit(void)
{
	uint4 stat;

	if (-1 == write(object_file_des, emit_buff, emit_buff_used))
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_OBJFILERR, 2, object_name_len, object_file_name, errno);
	emit_buff_used = 0;
}

void set_psect(unsigned char psect,unsigned char offset)
{
	current_psect = psect;
	return;
}

/*
 *	define_symbol
 *
 *	Args:  psect index, symbol name, symbol value.
 *
 *	Description:  Buffers a definition of a global symbol with the
 *		given name and value in the given psect.
 */
struct sym_table *define_symbol(unsigned char psect, mstr *name, int4 value)
{
	int cmp;
	struct sym_table *sym, *sym1, *newsym;

	sym = symbols;
	sym1 = 0;
	while(sym)
	{
		if ((cmp = memvcmp(name->addr, name->len, &sym->name[0], sym->name_len - 1)) <= 0)
			break;
		sym1 = sym;
		sym = sym->next;
	}
	if (cmp || !sym)
	{	newsym = (struct sym_table *) mcalloc(SIZEOF(struct sym_table) + name->len);
		newsym->name_len = name->len + 1;
		memcpy(&newsym->name[0], name->addr, name->len);
		newsym->name[ name->len ] = 0;
		newsym->n.n_strx = 0;
		newsym->n.n_type = N_EXT;
		if (psect == GTM_CODE)
			newsym->n.n_type |= N_TEXT;	/* if symbol is in GTM_CODE, it is defined */
		else
			txtrel_cnt++;
		newsym->n.n_other = 0;
		newsym->n.n_desc = 0;
		newsym->n.n_value = value;
		newsym->resolve = 0;
		newsym->next = sym;
		if (sym1)
			sym1->next = newsym;
		else
			symbols = newsym;
		cdlits++;
		return 0;
	}
	if (!(sym->n.n_type & N_TEXT))
		txtrel_cnt++;
	return sym;
}

void resolve_sym(void)
{
	uint4 symnum;
	struct sym_table *sym;
	struct rel_table *rel;

	symnum = 0;
	sym = symbols;
	while (sym)
	{	if (sym->resolve)
		{	rel = sym->resolve;
			while (rel)
			{	rel->r.r_symbolnum = symnum;
				rel = rel->resolve;
			}
		}
		symnum++;
		sym = sym->next;
	}
}

void output_relocation(void)
{
	struct rel_table 	*rel;
	DEBUG_ONLY(int	cnt;)

	DEBUG_ONLY(cnt = 0;)
	rel = text_rel;
	while (rel)
	{
		emit_immed((char *)&rel->r, SIZEOF(rel->r));
		rel = rel->next;
		DEBUG_ONLY(cnt++;)
	}
	assert(cnt == txtrel_cnt_in_hdr);

	DEBUG_ONLY(cnt = 0;)
	rel = data_rel;
	while (rel)
	{
		emit_immed((char *)&rel->r, SIZEOF(rel->r));
		rel = rel->next;
		DEBUG_ONLY(cnt++;)
	}
	assert(cnt == lit_addrs);
}

void output_symbol(void)
{
	uint4 string_length;
	struct sym_table *sym;

	string_length = SIZEOF(int4);
	sym = symbols;
	while (sym)
	{
		sym->n.n_strx = string_length;
		emit_immed((char *)&sym->n, SIZEOF(sym->n));
		string_length += sym->name_len;
		sym = sym->next;
	}
	emit_immed((char *)&string_length, SIZEOF(string_length));
	sym = symbols;
	while (sym)
	{
		emit_immed((char *)&sym->name[0], sym->name_len);
		sym = sym->next;
	}
}

void obj_init(void)
{
	cdlits = txtrel_cnt = 0;
	data_rel = text_rel = data_rel_end = text_rel_end = 0;
	symbols = 0;
}

void emit_literals(void)
{
	uint4 offset, padsize;
	mliteral *p;

	set_psect(GTM_LITERALS, 0);
	offset = stringpool.free - stringpool.base;
	emit_immed((char *)stringpool.base, offset);
	/* comp_lits aligns the start of source path on a NATIVE_WSIZE boundary.*/
	padsize = PADLEN(offset, NATIVE_WSIZE);
	if (padsize)
	{
		emit_immed(PADCHARS, padsize);
		offset += padsize;
	}
	emit_immed(source_file_name, source_name_len);
	offset += source_name_len;
	/* comp_lits aligns the start of routine_name on a NATIVE_WSIZE boundary.*/
	padsize = PADLEN(offset, NATIVE_WSIZE);
	if (padsize)
	{
		emit_immed(PADCHARS, padsize);
		offset += padsize;
	}
	emit_immed(routine_name.addr, routine_name.len);
	offset += routine_name.len;
	/* comp_lits aligns the start of the literal area on a NATIVE_WSIZE boundary.*/
	padsize = PADLEN(offset, NATIVE_WSIZE);
	if (padsize)
	{
		emit_immed(PADCHARS, padsize);
		offset += padsize;
	}

	dqloop(&literal_chain, que, p)
	{
		assert (p->rt_addr == offset);
		MV_FORCE_NUMD(&p->v);
		if (p->v.str.len)
			emit_pidr(p->rt_addr + ((char *) &p->v.str.addr - (char *)&p->v),
				 p->v.str.addr - (char *) stringpool.base, (int4 *)&p->v.str.addr);
		else
			p->v.str.addr = 0;
		emit_immed((char *)&p->v, SIZEOF(p->v));
		offset += SIZEOF(p->v);
	}
	assert(lits_size == offset);
}
