/*****************************************************************************/
/*                                                                           */
/*  midipix target interfaces                                                */
/*                                                                           */
/*  Copyright (C)  2014,2015  Z. Gilboa                                      */
/*                                                                           */
/*  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 3 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, see <http://www.gnu.org/licenses/>.     */
/*                                                                           */
/*****************************************************************************/


#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "rtl.h"
#include "output.h"
#include "tree.h"
#include "flags.h"
#include "target.h"
#include "i386-protos.h"
#include "function.h"
#include "cgraph.h"

/* got support */
int flag_got_established_section	= 0;

/* common specs */
const int TARGET_NOP_FUN_DLLIMPORT	= 0;
const int use_pe_aligned_common		= 1;
const int flag_writable_rel_rdata	= 0;

/* pointer annotation */
static const char winnt_ptrsize_long[] 	= ".long";
static const char winnt_ptrsize_quad[] 	= ".quad";

/* target SEH providers */
#define TARGET_SEH_WINNT			(TARGET_SEH && TARGET_NT64)
#define TARGET_SEH_MIDIPIX			(TARGET_SEH && TARGET_NT32)

/* 64-bit seh provided by winnt */
void winnt_x86_64_pe_seh_init(FILE *);
void winnt_x86_64_pe_seh_end_prologue(FILE *);
void winnt_x86_64_pe_seh_unwind_emit(FILE *, rtx);
void winnt_x86_64_pe_seh_emit_except_personality(rtx);
void winnt_x86_64_pe_seh_init_sections(void);


/* 32-bit seh provided by midipix */
void midipix_i386_pe_seh_init(FILE *);
void midipix_i386_pe_seh_end_prologue(FILE *);
void midipix_i386_pe_seh_unwind_emit(FILE *, rtx);
void midipix_i386_pe_seh_emit_except_personality(rtx);
void midipix_i386_pe_seh_init_sections(void);


/* debugging */
unsigned int midipix_dbx_get_register_number(unsigned int n)
{
	if (TARGET_NT64 )
		return dbx64_register_map[n];
	else if (write_symbols == DWARF2_DEBUG)
		return svr4_dbx_register_map[n];
	else
		return dbx_register_map[n];
}


unsigned int midipix_dbx_get_dwarf_frame_register_number(unsigned int n)
{
	if (TARGET_NT64)
		return dbx64_register_map[n];
	else
		return svr4_dbx_register_map[n];
}


void midipix_asm_output_dwarf_offset(
	FILE *		gas_exhaled,
	unsigned	size,
	const char *	label,
	section * section __attribute__((unused)))
{
	#define PE_SIZE_IDENTIFIER	4
	#define PEP_SIZE_IDENTIFIER	8

	extern void assemble_name (FILE *, const char *);

	if ((size == PE_SIZE_IDENTIFIER) || (size == PEP_SIZE_IDENTIFIER)) {
		fputs ("\t.secrel32\t", gas_exhaled);
		assemble_name(gas_exhaled, label);

		/* in PE32+, section relocation need to be zero-extended */
		if (size == PEP_SIZE_IDENTIFIER)
			fputs ("\n\t.int\t0", gas_exhaled);
	} else
		gcc_unreachable();
}


/* assembler labels */
void midipix_asm_output_label_ref (FILE * gas_exhaled, const char * name)
{
	extern const char * user_label_prefix;

	/* in __fastcall labels the @ prefix is already present */
	if (*name != FASTCALL_PREFIX)
		fputs(user_label_prefix, gas_exhaled);

	fputs(name, gas_exhaled);
}


void midipix_asm_generate_internal_label (
	char * strbuf, const char * prefix, unsigned long number)
{
	sprintf (strbuf, "*%s%s%ld",
			LOCAL_LABEL_PREFIX,
			prefix,
			number);
}


static void midipix_pe_create_got_entry(FILE * stream, const char * name, tree decl)
{
	const char * ptrsize = TARGET_64BIT
		? winnt_ptrsize_quad
		: winnt_ptrsize_long;

	fputs("\n",stream);
	fputs(GAS_SECTION,stream);

	switch (decl->decl_with_vis.visibility) {
		case VISIBILITY_HIDDEN:
			fputs(HIDDEN_SECTION_NAME,stream);
			break;

		case VISIBILITY_INTERNAL:
			fputs(INTERNAL_SECTION_NAME,stream);
			break;

		default:
			fputs(GOT_SECTION_NAME,stream);
			break;
	}

	fputs("$",stream);
	assemble_name(stream,name);
	fputs(GOT_SECTION_ATTR,stream);

	if (decl->decl_with_vis.weak_flag)
		fputs("\n\t.global __imp_",stream);
	else
		fputs("\n\t.global __imp_",stream);

	assemble_name(stream,name);
	fputs("\n__imp_",stream);
	assemble_name(stream,name);
	fputs(":\n\t",stream);
	fputs(ptrsize,stream);
	fputs("\t",stream);
	assemble_name(stream,name);
	fputs("\n",stream);
	fputs("\t.linkonce discard\n\n",stream);
}


void midipix_asm_output_got_entry(
	FILE * 		asmout,
	const char * 	name,
	tree 		decl,
	section * 	sect)
{
	if (!decl->base.public_flag)
		return;

	/* .got entry */
	midipix_pe_create_got_entry(asmout,name,decl);

	/* re-establish generic named section */
	if (in_section && in_section->common.flags & (SECTION_STYLE_MASK == SECTION_NAMED))
		targetm.asm_out.named_section(
			in_section->named.name,
			in_section->named.common.flags,
			in_section->named.decl);

	/* re-establish .bss section */
	else if ((sect == bss_noswitch_section) || (in_section == bss_noswitch_section))
		fputs("\t.bss\n",asmout);

	/* re-establish .data section */
	else if ((sect == data_section) || (sect == data_section))
		fputs("\t.data\n",asmout);

	/* re-establish .text section (default) */
	else
		fputs("\t.text\n",asmout);

	flag_got_established_section = 1;
}


void midipix_asm_declare_object_name(FILE * gas_exhaled, const char * name, tree decl)
{
	extern void assemble_name (FILE *, const char *);
	extern void i386_pe_maybe_record_exported_symbol (tree, const char *, int);

	i386_pe_maybe_record_exported_symbol(decl, name, true);
	assemble_name(gas_exhaled, name);
	fputs (":\n", gas_exhaled);
}


void midipix_asm_drectve_section(void)
{
	extern FILE *    asm_out_file;
	extern section * in_section;

	in_section = (section *)0;

	fprintf (asm_out_file, "%s%s\n",
			GAS_SECTION,
			DRECTVE_SECTION_NAME);
}


int midipix_target_use_local_thunk_alias(tree decl)
{
	return !DECL_ONE_ONLY(decl);
}


void midipix_asm_output_external(
	FILE *		asmout __attribute__((unused)),
	tree		decl,
	const char *	name)
{
	if (decl->base.code == FUNCTION_DECL)
		i386_pe_record_external_function(
			decl,name);
}


void midipix_asm_output_external_libcall (FILE * asmout, rtx fn)
{
	i386_pe_declare_function_type(
		asmout,
		XSTR(fn,0),
		true);
}


void midipix_asm_output_def_from_decls(
	FILE *	asmout,
	tree	decl,
	tree	target)
{
	const char * sym;

	sym = (const char *)decl->decl_with_vis.assembler_name->identifier.id.str;

	i386_pe_maybe_record_exported_symbol(
		decl,sym,false);

	if (decl->base.code == FUNCTION_DECL)
		i386_pe_declare_function_type(
			asmout,sym,decl->base.public_flag);

	ASM_OUTPUT_DEF(
		asmout,
		sym,
		IDENTIFIER_POINTER(target));

	if (!decl->decl_with_vis.weak_flag)
		midipix_asm_output_got_entry(asmout,sym,decl,0);
}


/* visibility */
void midipix_i386_pe_assemble_visibility (
	tree	decl	__attribute__((unused)),
	int	visible __attribute__((unused)))
{
	/* cf. midipix_pe_create_got_entry */
}


/* seh hook selectors */
void midipix_seh_hook__pe_seh_init(FILE * f)
{
	if (TARGET_SEH_WINNT)
		winnt_x86_64_pe_seh_init(f);
	else if (TARGET_SEH_MIDIPIX)
		midipix_i386_pe_seh_init(f);
}


void midipix_seh_hook__pe_seh_end_prologue(FILE * f)
{
	if (TARGET_SEH_WINNT)
		winnt_x86_64_pe_seh_end_prologue(f);
	else if (TARGET_SEH_MIDIPIX)
		midipix_i386_pe_seh_end_prologue(f);
}


void midipix_seh_hook__pe_seh_unwind_emit(FILE * f, rtx r)
{
	if (TARGET_SEH_WINNT)
		winnt_x86_64_pe_seh_unwind_emit(f,r);
	else if (TARGET_SEH_MIDIPIX)
		midipix_i386_pe_seh_unwind_emit(f,r);
}



void midipix_seh_hook__pe_seh_emit_except_personality(rtx r)
{
	if (TARGET_SEH_WINNT)
		winnt_x86_64_pe_seh_emit_except_personality(r);
	else if (TARGET_SEH_MIDIPIX)
		midipix_i386_pe_seh_emit_except_personality(r);
}


void midipix_seh_hook__pe_seh_init_sections(void)
{
	if (TARGET_SEH_WINNT)
		winnt_x86_64_pe_seh_init_sections();
	else if (TARGET_SEH_MIDIPIX)
		midipix_i386_pe_seh_init_sections();
}



/* 64-bit seh hooks (winnt) */
void winnt_x86_64_pe_seh_init(FILE * f)
{
	i386_pe_seh_init(f);
}


void winnt_x86_64_pe_seh_end_prologue(FILE * f)
{
	i386_pe_seh_end_prologue(f);
}


void winnt_x86_64_pe_seh_unwind_emit(FILE * f, rtx r)
{
	i386_pe_seh_unwind_emit(f,r);
}


void winnt_x86_64_pe_seh_emit_except_personality(rtx r __attribute__((unused)))
{
	/* i386_pe_seh_emit_except_personality(r); */
}


void winnt_x86_64_pe_seh_init_sections(void)
{
	/* i386_pe_seh_init_sections(); */
}



/* 32-bit seh hooks (midipix) */
void midipix_i386_pe_seh_init(FILE * f __attribute__((unused)))
{
}


void midipix_i386_pe_seh_end_prologue(FILE * f __attribute__((unused)))
{
}


void midipix_i386_pe_seh_unwind_emit(
	FILE *	f __attribute__((unused)),
	rtx	r __attribute__((unused)))
{
}


void midipix_i386_pe_seh_emit_except_personality(rtx r __attribute__((unused)))
{
}


void midipix_i386_pe_seh_init_sections(void)
{
}


section * midipix_i386_pe_function_section (
	tree			decl,
	enum node_frequency	freq,
	bool			startup,
	bool			exit)
{
	extern section * default_function_section (
		tree			decl,
		enum node_frequency	freq,
		bool			startup,
		bool			exit);

	const char *	fname;
	bool		weak;
	section *	asm_section;
	char *		secname;
	size_t		size;

	/* .got entry */
	fname = (const char *)decl->decl_with_vis.assembler_name->identifier.id.str;
	weak  = (decl->decl_with_vis.dllimport_flag && decl->decl_with_vis.weak_flag);

	/* section entry */
	#ifdef MIDIPIX_TARGET_DEBUG
	fputs ("\t# <", asm_out_file);
	assemble_name (asm_out_file, fname);
	fputs (": section>\n", asm_out_file);
	#endif

	if (weak) {
		#ifdef MIDIPIX_TARGET_DEBUG
		fputs("\t# (weak symbol section)\n",asm_out_file);
		#endif

		size  = strlen(fname) + 6; /* .text$ */

		if (!(secname = (char *)xmalloc(size)))
			gcc_unreachable();

		sprintf(secname,".text$%s",fname);
		asm_section = get_named_section(decl, secname, 1);
		free(secname);
	} else if (decl) {
		#ifdef MIDIPIX_TARGET_DEBUG
		fputs("\t# (strong symbol section)\n",asm_out_file);
		#endif

		asm_section = default_function_section(decl, freq, startup, exit);
	} else {
		fputs("\t# should never get here?\n",asm_out_file);
		asm_section = (section *)0;
	}

	#ifdef MIDIPIX_TARGET_DEBUG
	fputs ("\t# </", asm_out_file);
	assemble_name (asm_out_file, fname);
	fputs (": section>\n", asm_out_file);
	#endif

	return asm_section;
}


void midipix_i386_pe_asm_output_aligned_decl_common(
	FILE *		stream,
	tree 		decl,
	const char *	name,
	HOST_WIDE_INT 	size,
	HOST_WIDE_INT 	align)
{
	i386_pe_asm_output_aligned_decl_common(
		stream,decl,name,size,align);
}


void midipix_i386_pe_start_function (FILE * gas_exhaled, const char *name, tree decl)
{
	#ifdef MIDIPIX_TARGET_DEBUG
	fputs ("\t# <", gas_exhaled);
	assemble_name (gas_exhaled, name);
	fputs (">\n", gas_exhaled);
	#endif

	i386_pe_start_function (gas_exhaled, name, decl);
}


void midipix_i386_pe_end_function (FILE * gas_exhaled, const char * name, tree decl)
{
	if (cfun->is_thunk)
		return;

	if (TARGET_SEH)
		fputs ("\t.seh_endproc\n", gas_exhaled);

	/* avoid duplicate GOT entries */
	if (decl && !decl->decl_with_vis.weak_flag)
		midipix_asm_output_got_entry(gas_exhaled,name,decl,0);
}


void midipix_i386_pe_asm_weaken_decl(
	FILE *		gas_exhaled,
	tree		decl,
	const char *	name,
	const char *	alias __attribute__((unused)))
{
	midipix_asm_output_got_entry(gas_exhaled,name,decl,0);

	fputs ("\t.weak\t", gas_exhaled);
	assemble_name (gas_exhaled, name);
	fputs ("\n", gas_exhaled);
}


void midipix_i386_pe_asm_weaken_label(FILE * gas_exhaled, const char * name)
{
	midipix_i386_pe_asm_weaken_decl(
		gas_exhaled,
		(tree)0,
		name,
		(const char *)0);
}

int midipix_symbol_ref_dllimport_p(rtx symbol)
{
	tree decl = SYMBOL_REF_DECL(symbol);

	return decl && decl->base.public_flag &&
		(!decl->decl_with_vis.visibility_specified
			|| (decl->decl_with_vis.visibility == VISIBILITY_DEFAULT));
}