/*------------------------------------------------------------------*/
/* 								    */
/* Name        - tramp.c					    */
/* 								    */
/* Function    - Create trampolines to invoke arbitrary functions.  */
/* 								    */
/* Name	       - Neale Ferguson.				    */
/* 								    */
/* Date        - October, 2002					    */
/* 								    */
/* 								    */
/*------------------------------------------------------------------*/

/*------------------------------------------------------------------*/
/*                 D e f i n e s                                    */
/*------------------------------------------------------------------*/

#define PROLOG_INS 	24	/* Size of emitted prolog	    */
#define CALL_INS   	4	/* Size of emitted call 	    */
#define EPILOG_INS 	18	/* Size of emitted epilog	    */

#define DEBUG(x)

/*========================= End of Defines =========================*/

/*------------------------------------------------------------------*/
/*                 I n c l u d e s                                  */
/*------------------------------------------------------------------*/

#ifdef NEED_MPROTECT
# include <sys/mman.h>
# include <limits.h>    /* for PAGESIZE */
# ifndef PAGESIZE
#  define PAGESIZE 4096
# endif
#endif

#include "config.h"
#include <stdlib.h>
#include <string.h>
#include "s390x-codegen.h"
#include "mono/metadata/class.h"
#include "mono/metadata/tabledefs.h"
#include "mono/interpreter/interp.h"
#include "mono/metadata/appdomain.h"
#include "mono/metadata/marshal.h"

/*========================= End of Includes ========================*/

/*------------------------------------------------------------------*/
/*                 T y p e d e f s                                  */
/*------------------------------------------------------------------*/

/*------------------------------------------------------------------*/
/* Structure used to accummulate size of stack, code, and locals    */
/*------------------------------------------------------------------*/
typedef struct {
	guint stack_size,
	      local_size,
	      code_size,
	      retStruct;
} size_data;	

/*========================= End of Typedefs ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- add_general                                       */
/*                                                                  */
/* Function	- Determine code and stack size incremements for a  */
/* 		  parameter.                                        */
/*                                                                  */
/*------------------------------------------------------------------*/

static void inline
add_general (guint *gr, size_data *sz, gboolean simple)
{
	if (simple) {
		if (*gr >= GENERAL_REGS) {
			sz->stack_size += sizeof(long);
			sz->code_size  += 12;    
		} else {
			sz->code_size += 8;    
		}
	} else {
		if (*gr >= GENERAL_REGS - 1) {
			sz->stack_size += 8 + (sz->stack_size % 8);
			sz->code_size  += 10;   
		} else {
			sz->code_size += 8;
		}
		(*gr) ++;
	}
	(*gr) ++;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- calculate_sizes                                   */
/*                                                                  */
/* Function	- Determine the amount of space required for code   */
/* 		  and stack. In addition determine starting points  */
/*		  for stack-based parameters, and area for struct-  */
/*		  ures being returned on the stack.		    */
/*                                                                  */
/*------------------------------------------------------------------*/

static void inline
calculate_sizes (MonoMethodSignature *sig, size_data *sz, 
		 gboolean string_ctor)
{
	guint i, fr, gr, size;
	guint32 simpletype, align;

	fr             = 0;
	gr             = 2;
	sz->retStruct  = 0;
	sz->stack_size = S390_MINIMAL_STACK_SIZE;
	sz->code_size  = (PROLOG_INS + CALL_INS + EPILOG_INS);
	sz->local_size = 0;

	if (sig->hasthis) {
		add_general (&gr, sz, TRUE);
	}

	/*----------------------------------------------------------*/
	/* We determine the size of the return code/stack in case we*/
	/* need to reserve a register to be used to address a stack */
	/* area that the callee will use.			    */
	/*----------------------------------------------------------*/

	if (sig->ret->byref || string_ctor) {
		sz->code_size += 8;
	} else {
		simpletype = sig->ret->type;
enum_retvalue:
		switch (simpletype) {
		case MONO_TYPE_BOOLEAN:
		case MONO_TYPE_I1:
		case MONO_TYPE_U1:
		case MONO_TYPE_I2:
		case MONO_TYPE_U2:
		case MONO_TYPE_CHAR:
		case MONO_TYPE_I4:
		case MONO_TYPE_U4:
		case MONO_TYPE_I:
		case MONO_TYPE_U:
		case MONO_TYPE_CLASS:
		case MONO_TYPE_OBJECT:
		case MONO_TYPE_R4:
		case MONO_TYPE_R8:
		case MONO_TYPE_PTR:
		case MONO_TYPE_SZARRAY:
		case MONO_TYPE_ARRAY:
		case MONO_TYPE_STRING:
			sz->code_size += 4;
			break;
		case MONO_TYPE_I8:
			sz->code_size += 4;
			break;
		case MONO_TYPE_VALUETYPE:
			if (sig->ret->data.klass->enumtype) {
				simpletype = sig->ret->data.klass->enum_basetype->type;
				goto enum_retvalue;
			}
			gr++;
			if (sig->pinvoke)
				size = mono_class_native_size (sig->ret->data.klass, &align);
			else
                        	size = mono_class_value_size (sig->ret->data.klass, &align);
			if (align > 1)
				sz->code_size += 10;
			switch (size) {
				/*----------------------------------*/
				/* On S/390, structures of size 1,  */
				/* 2, 4, and 8 bytes are returned   */
				/* in (a) register(s).		    */
				/*----------------------------------*/
				case 1:
				case 2:
				case 4:
				case 8:
					sz->code_size  += 16;
					sz->stack_size += 4;
					break;
				default:
					sz->retStruct   = 1;
					sz->code_size  += 32;
			}
                        break;
		case MONO_TYPE_VOID:
			break;
		default:
			g_error ("Can't handle as return value 0x%x", sig->ret->type);
		}
	}

	/*----------------------------------------------------------*/
	/* We determine the size of the parameter code and stack    */
	/* requirements by checking the types and sizes of the      */
	/* parameters.						    */
	/*----------------------------------------------------------*/

	for (i = 0; i < sig->param_count; ++i) {
		if (sig->params [i]->byref) {
			add_general (&gr, sz, TRUE);
			continue;
		}
		simpletype = sig->params [i]->type;
	enum_calc_size:
		switch (simpletype) {
		case MONO_TYPE_BOOLEAN:
		case MONO_TYPE_CHAR:
		case MONO_TYPE_I1:
		case MONO_TYPE_U1:
		case MONO_TYPE_I2:
		case MONO_TYPE_U2:
		case MONO_TYPE_I4:
		case MONO_TYPE_U4:
		case MONO_TYPE_I:
		case MONO_TYPE_U:
		case MONO_TYPE_PTR:
		case MONO_TYPE_CLASS:
		case MONO_TYPE_OBJECT:
		case MONO_TYPE_STRING:
			add_general (&gr, sz, TRUE);
			break;
		case MONO_TYPE_SZARRAY:
			add_general (&gr, sz, TRUE);
			break;
		case MONO_TYPE_VALUETYPE:
			if (sig->params [i]->data.klass->enumtype) {
				simpletype = sig->params [i]->data.klass->enum_basetype->type;
				goto enum_calc_size;
			}
			if (sig->pinvoke)
				size = mono_class_native_size (sig->params [i]->data.klass, &align);
			else
				size = mono_class_value_size (sig->params [i]->data.klass, &align);
			DEBUG(printf("%d typesize: %d (%d)\n",i,size,align));
			switch (size) {
				/*----------------------------------*/
				/* On S/390, structures of size 1,  */
				/* 2, 4, and 8 bytes are passed in  */
				/* (a) register(s).		    */
				/*----------------------------------*/
				case 0:
				case 1:
				case 2:
				case 4:
					add_general(&gr, sz, TRUE);
					break;
				case 8:
					add_general(&gr, sz, FALSE);
					break;
				default:
					sz->local_size += (size + (size % align));
					sz->code_size  += 40;
			}
			break;
		case MONO_TYPE_I8:
			add_general (&gr, sz, FALSE);
			break;
		case MONO_TYPE_R4:
			if (fr < FLOAT_REGS) {
				sz->code_size += 4;
				fr++;
			}
			else {
				sz->code_size  += 4;
				sz->stack_size += 8;
			}
			break;
		case MONO_TYPE_R8:
			if (fr < FLOAT_REGS) {
				sz->code_size += 4;
				fr++;
			} else {
				sz->code_size  += 4;
				sz->stack_size += 8 + (sz->stack_size % 8);
			}
			break;
		default:
			g_error ("Can't trampoline 0x%x", sig->params [i]->type);
		}
	}


	/* align stack size to 8 */
	DEBUG (printf ("      stack size: %d (%d)\n"
		       "       code size: %d\n" 
		       "      local size: %d\n",
		      (sz->stack_size + 8) & ~8, sz->stack_size, 
		      (sz->code_size),(sz->local_size + 8) & ~8));
	sz->stack_size = (sz->stack_size + 8) & ~8;
	sz->local_size = (sz->local_size + 8) & ~8;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- emit_prolog                                       */
/*                                                                  */
/* Function	- Create the instructions that implement the stand- */
/* 		  ard function prolog according to the S/390 ABI.   */
/*                                                                  */
/*------------------------------------------------------------------*/

static inline guint8 *
emit_prolog (guint8 *p, MonoMethodSignature *sig, size_data *sz)
{
	guint stack_size;

	stack_size = sz->stack_size + sz->local_size;

	/* function prolog */
	s390_stmg(p, s390_r6, s390_r14, STK_BASE, S390_REG_SAVE_OFFSET);
	s390_lg	 (p, s390_r7, 0, STK_BASE, MINV_POS);
	s390_lgr (p, s390_r11, STK_BASE);
	s390_aghi(p, STK_BASE, -stack_size);
	s390_stg (p, s390_r11, 0, STK_BASE, 0);

	/*-----------------------------------------*/
	/* Save:				   */
	/* - address of "callme"                   */
	/* - address of "retval"		   */
	/* - address of "arguments"		   */
	/*-----------------------------------------*/
	s390_lgr (p, s390_r9, s390_r2);
	s390_lgr (p, s390_r8, s390_r3);
	s390_lgr (p, s390_r10, s390_r5);

	return p;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- emit_save_parameters                              */
/*                                                                  */
/* Function	- Create the instructions that load registers with  */ 
/*		  parameters, place others on the stack according   */
/* 		  to the S/390 ABI.				    */
/*                                                                  */
/*		  The resulting function takes the form:	    */
/* 		  void func (void (*callme)(), void *retval, 	    */
/*			     void *this_obj, stackval *arguments);  */
/*                                                                  */
/*------------------------------------------------------------------*/

inline static guint8*
emit_save_parameters (guint8 *p, MonoMethodSignature *sig, size_data *sz)
{
	guint i, fr, gr, act_strs, align, 
	      stack_par_pos, size, local_pos;
	guint32 simpletype;

	/*----------------------------------------------------------*/
	/* If a structure on stack is being returned, reserve r2    */ 
	/* to point to an area where it can be passed.              */
	/*----------------------------------------------------------*/
	if (sz->retStruct)
		gr    = 1;
	else
		gr    = 0;
	fr            = 0;
	act_strs      = 0;
	stack_par_pos = S390_MINIMAL_STACK_SIZE;
	local_pos     = sz->stack_size;

	if (sig->hasthis) {
		s390_lr (p, s390_r2 + gr, s390_r4);
		gr++;
	}

	act_strs = 0;
	for (i = 0; i < sig->param_count; ++i) {
		DEBUG(printf("par: %d type: %d ref: %d\n",i,sig->params[i]->type,sig->params[i]->byref));
		if (sig->params [i]->byref) {
			if (gr < GENERAL_REGS) {
				s390_lg (p, s390_r2 + gr, 0, ARG_BASE, STKARG);
				gr ++;
			} else {
				s390_lg (p, s390_r0, 0, ARG_BASE, STKARG);
				s390_stg(p, s390_r0, 0, STK_BASE, stack_par_pos);
				stack_par_pos += sizeof(long);
			}
			continue;
		}
		simpletype = sig->params [i]->type;
	enum_calc_size:
		switch (simpletype) {
		case MONO_TYPE_BOOLEAN:
		case MONO_TYPE_I1:
		case MONO_TYPE_U1:
		case MONO_TYPE_I2:
		case MONO_TYPE_U2:
		case MONO_TYPE_CHAR:
		case MONO_TYPE_I4:
		case MONO_TYPE_U4:
		case MONO_TYPE_I:
		case MONO_TYPE_U:
		case MONO_TYPE_PTR:
		case MONO_TYPE_CLASS:
		case MONO_TYPE_OBJECT:
		case MONO_TYPE_STRING:
		case MONO_TYPE_SZARRAY:
			if (gr < GENERAL_REGS) {
				s390_lg (p, s390_r2 + gr, 0, ARG_BASE, STKARG);
				gr ++;
			} else {
				s390_lg (p, s390_r0, 0, ARG_BASE, STKARG);
				s390_stg(p, s390_r0, 0, STK_BASE, stack_par_pos);
			        stack_par_pos += sizeof(long);
			}
			break;
		case MONO_TYPE_VALUETYPE:
			if (sig->params [i]->data.klass->enumtype) {
				simpletype = sig->params [i]->data.klass->enum_basetype->type;
				goto enum_calc_size;
			}
			if (sig->pinvoke) 
				size = mono_class_native_size (sig->params [i]->data.klass, &align);
			else
				size = mono_class_value_size (sig->params [i]->data.klass, &align);
			DEBUG(printf("parStruct - size %d pinvoke: %d\n",size,sig->pinvoke));
			switch (size) {
				case 0:
				case 1:
				case 2:
				case 4:
					if (gr < GENERAL_REGS) {
						s390_lg (p, s390_r2 + gr, 0,ARG_BASE, STKARG);
						s390_lgf(p, s390_r2 + gr, 0, s390_r2 + gr, 0);
						gr++;
					} else {
						stack_par_pos += (stack_par_pos % align);
						s390_lg (p, s390_r10, 0,ARG_BASE, STKARG);
						s390_lgf(p, s390_r10, 0, s390_r10, 0);
						s390_st (p, s390_r10, 0, STK_BASE, stack_par_pos);
						stack_par_pos += sizeof(long);
					}
					break;
				case 8:
					if (gr < GENERAL_REGS) {
						s390_lg (p, s390_r2 + gr, 0, ARG_BASE, STKARG);
						s390_lg (p, s390_r2 + gr, 0, s390_r2 + gr, 0);
					} else {
						stack_par_pos += (stack_par_pos % align);
						s390_lg  (p, s390_r10, 0, ARG_BASE, STKARG);
						s390_mvc (p, sizeof(long long), STK_BASE, stack_par_pos, s390_r10, 0);
						stack_par_pos += sizeof(long long);
					}
					break;	
				default:
					if (size <= 256) {
						local_pos += (local_pos % align);
						s390_lg  (p, s390_r13, 0, ARG_BASE, STKARG);
						s390_mvc (p, size, STK_BASE, local_pos, s390_r13, 0);
						s390_la	 (p, s390_r13, 0, STK_BASE, local_pos);
						local_pos += size;
					} else {
						local_pos += (local_pos % align);
						s390_bras (p, s390_r13, 4);
						s390_llong(p, size);
						s390_lg   (p, s390_r1, 0, s390_r13, 0);
						s390_lg   (p, s390_r0, 0, ARG_BASE, STKARG);
						s390_lgr  (p, s390_r14, s390_r12);
						s390_la   (p, s390_r12, 0, STK_BASE, local_pos);
						s390_lgr  (p, s390_r13, s390_r1);
						s390_mvcl (p, s390_r12, s390_r0);
						s390_lgr  (p, s390_r12, s390_r14);
						s390_la	  (p, s390_r13, 0, STK_BASE, local_pos);
						local_pos += size;
					}
					if (gr < GENERAL_REGS) {
						s390_lgr(p, s390_r2 + gr, s390_r13);
						gr++;
					} else {
						s390_stg(p, s390_r13, 0, STK_BASE, stack_par_pos);
						stack_par_pos += sizeof(long);
					}
			}
			break;
		case MONO_TYPE_I8:
			if (gr < GENERAL_REGS) {
				s390_lg (p, s390_r2 + gr, 0, ARG_BASE, STKARG); 
				gr += 2;
			} else {
				*(guint32 *) p += 7;
				*(guint32 *) p &= ~7;
				s390_mvc (p, sizeof(long long), STK_BASE, stack_par_pos, ARG_BASE, STKARG); 
				stack_par_pos += sizeof(long long) + (stack_par_pos % sizeof(long long));
			}
			break;
		case MONO_TYPE_R4:
			if (fr < FLOAT_REGS) {
				s390_le  (p, s390_r0 + fr, 0, ARG_BASE, STKARG);
				fr++;
			} else {
				s390_mvc  (p, sizeof(float), STK_BASE, stack_par_pos, ARG_BASE, STKARG);
				stack_par_pos += sizeof(float);
			}
			break;
		case MONO_TYPE_R8:
			if (fr < FLOAT_REGS) {
				s390_ld  (p, s390_r0 + fr, 0, ARG_BASE, STKARG);
				fr++;
			} else {
				*(guint32 *) p += 7;
				*(guint32 *) p &= ~7;
				s390_mvc  (p, sizeof(double), STK_BASE, stack_par_pos, ARG_BASE, STKARG);
				stack_par_pos += sizeof(long long) + (stack_par_pos % sizeof(long long));
			}
			break;
		default:
			g_error ("Can't trampoline 0x%x", sig->params [i]->type);
		}
	}

	/*----------------------------------------------------------*/
	/* If we're returning a structure but not in a register     */ 
	/* then point the result area for the called routine        */
	/*----------------------------------------------------------*/
	if (sz->retStruct) {
		s390_lg (p, s390_r2, 0, s390_r8, 0);
	}

	return p;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- alloc_code_memory				    */
/*                                                                  */
/* Function	- Allocate space to place the emitted code.	    */
/*                                                                  */
/*------------------------------------------------------------------*/

static inline guint8 *
alloc_code_memory (guint code_size)
{
	guint8 *p;

#ifdef NEED_MPROTECT
	p = g_malloc (code_size + PAGESIZE - 1);

	/* Align to a multiple of PAGESIZE, assumed to be a power of two */
	p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
#else
	p = g_malloc (code_size);
#endif
	DEBUG (printf ("           align: %p (%d)\n", p, (guint)p % 4));

	return p;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- emit_call_and_store_retval			    */
/*                                                                  */
/* Function	- Emit code that will implement the call to the     */
/*	          desired function, and unload the result according */
/*		  to the S390 ABI for the type of value returned    */
/*                                                                  */
/*------------------------------------------------------------------*/

static inline guint8 *
emit_call_and_store_retval (guint8 *p, MonoMethodSignature *sig, 
			    size_data *sz, gboolean string_ctor)
{
	guint32 simpletype;
	guint   retSize, align;

	/* call "callme" */
	s390_basr (p, s390_r14, s390_r9); 

	/* get return value */
	if (sig->ret->byref || string_ctor) {
		s390_stg(p, s390_r2, 0, s390_r8, 0);
	} else {
		simpletype = sig->ret->type;
enum_retvalue:
		switch (simpletype) {
		case MONO_TYPE_BOOLEAN:
		case MONO_TYPE_I1:
		case MONO_TYPE_U1:
			s390_stc (p, s390_r2, 0, s390_r8, 0);
			break;
		case MONO_TYPE_I2:
		case MONO_TYPE_U2:
		case MONO_TYPE_CHAR:
			s390_sth (p, s390_r2, 0, s390_r8, 0);
			break;
		case MONO_TYPE_I4:
		case MONO_TYPE_U4:
		case MONO_TYPE_I:
		case MONO_TYPE_U:
		case MONO_TYPE_CLASS:
		case MONO_TYPE_OBJECT:
		case MONO_TYPE_SZARRAY:
		case MONO_TYPE_ARRAY:
		case MONO_TYPE_STRING:
			s390_st (p, s390_r2, 0, s390_r8, 0);
			break;
		case MONO_TYPE_R4:
			s390_ste (p, s390_f0, 0, s390_r8, 0);
			break;
		case MONO_TYPE_R8:
			s390_std (p, s390_f0, 0, s390_r8, 0);
			break;
		case MONO_TYPE_I8:
			s390_stg (p, s390_r2, 0, s390_r8, 0);
			break;
		case MONO_TYPE_VALUETYPE:
			if (sig->ret->data.klass->enumtype) {
				simpletype = sig->ret->data.klass->enum_basetype->type;
				goto enum_retvalue;
			}
			if (sig->pinvoke) 
				retSize = mono_class_native_size (sig->ret->data.klass, &align);
			else
				retSize = mono_class_value_size (sig->ret->data.klass, &align);
printf("Returning %d bytes for type %d (%d)\n",retSize,simpletype,sig->pinvoke);
			switch(retSize) {
			case 0:
				break;
			case 1:
				s390_stc (p, s390_r2, 0, s390_r8, 0);
				break;
			case 2:
				s390_sth (p, s390_r2, 0, s390_r8, 0);
				break;
			case 4:
				s390_st (p, s390_r2, 0, s390_r8, 0);
				break;
			case 8:
				s390_stg (p, s390_r2, 0, s390_r8, 0);
				break;
			default: ;
				/*------------------------------------------*/
				/* The callee has already placed the result */
				/* in the required area			    */
				/*------------------------------------------*/
			}
			break;
		case MONO_TYPE_VOID:
			break;
		default:
			g_error ("Can't handle as return value 0x%x", 
				 sig->ret->type);
		}
	}

	return p;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- emit_epilog                                       */
/*                                                                  */
/* Function	- Create the instructions that implement the stand- */
/* 		  ard function epilog according to the S/390 ABI.   */
/*                                                                  */
/*------------------------------------------------------------------*/

static inline guint8 *
emit_epilog (guint8 *p, MonoMethodSignature *sig, size_data *sz)
{
	/* function epilog */
	s390_lg  (p, STK_BASE, 0, STK_BASE, 0);
	s390_lg  (p, s390_r4, 0, STK_BASE, S390_RET_ADDR_OFFSET);
	s390_lmg (p, s390_r6, STK_BASE, STK_BASE, S390_REG_SAVE_OFFSET);
	s390_br  (p, s390_r4);

	return p;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- mono_arch_create_trampoline.			    */
/*                                                                  */
/* Function	- Create the code that will allow a mono method to  */
/* 		  invoke a system subroutine.			    */
/*                                                                  */
/*------------------------------------------------------------------*/

MonoPIFunc
mono_arch_create_trampoline (MonoMethodSignature *sig, gboolean string_ctor)
{
	guint8 *p, *code_buffer;
	size_data sz;

	DEBUG (printf ("\nPInvoke [start emiting]\n"));
	calculate_sizes (sig, &sz, string_ctor);

	p = code_buffer = alloc_code_memory (sz.code_size);
	p = emit_prolog (p, sig, &sz);
	p = emit_save_parameters (p, sig, &sz);
	p = emit_call_and_store_retval (p, sig, &sz, string_ctor);
	p = emit_epilog (p, sig, &sz);

#ifdef NEED_MPROTECT
	if (mprotect (code_buffer, 1024, PROT_READ | PROT_WRITE | PROT_EXEC)) {
		g_error ("Cannot mprotect trampoline\n");
	}
#endif

	DEBUG (printf ("emited code size: %d\n", p - code_buffer));

	DEBUG (printf ("PInvoke [end emiting]\n"));

	return (MonoPIFunc) code_buffer;
}

/*========================= End of Function ========================*/

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name		- mono_arch_create_method_pointer		    */
/*                                                                  */
/* Function	- Returns a pointer to a native function that can   */
/* 		  be used to call the specified method. 	    */
/*                                                                  */
/*		  The function created will receive the arguments   */
/*		  according to the calling convention specified in  */
/*                in the method. 				    */
/*		  						    */
/*		  This function works by creating a MonoInvocation  */
/*		  structure, filling the fields in and calling      */
/*		  ves_exec_method() on it.			    */
/*								    */
/* Logic:							    */
/* ------							    */
/*  mono_arch_create_method_pointer (MonoMethod *method)	    */
/*	create the unmanaged->managed wrapper		 	    */
/*	register it with mono_jit_info_table_add()		    */
/*								    */
/*  What does the unmanaged->managed wrapper do?	   	    */
/*	allocate a MonoInvocation structure (inv) on the stack	    */
/*      allocate an array of stackval on the stack with length =    */
/*          method->signature->param_count + 1 [call it stack_args] */
/*	set inv->ex, inv->ex_handler, inv->parent to NULL	    */
/*	set inv->method to method				    */
/*	if method is an instance method, set inv->obj to the 	    */
/*	    'this' argument (the first argument) else set to NULL   */
/*	for each argument to the method call:			    */
/*		stackval_from_data (sig->params[i], &stack_args[i], */
/*				    arg, sig->pinvoke);		    */
/*		Where:						    */
/*		------						    */
/*		sig 		- is method->signature		    */
/*	        &stack_args[i]	- is the pointer to the ith element */
/*				  in the stackval array		    */
/*		arg		- is a pointer to the argument re-  */
/*				  ceived by the function according  */
/*				  to the call convention. If it     */
/*				  gets passed in a register, save   */
/*				  on the stack first.		    */
/*								    */
/*	set inv->retval to the address of the last element of       */
/*	    stack_args [recall we allocated param_count+1 of them]  */
/*	call ves_exec_method(inv)				    */
/*	copy the returned value from inv->retval where the calling  */
/* 	    convention expects to find it on return from the wrap-  */
/*	    per [if it's a structure, use stackval_to_data]	    */
/*                                                                  */
/*------------------------------------------------------------------*/

void *
mono_arch_create_method_pointer (MonoMethod *method)
{
	MonoMethodSignature *sig;
	MonoJitInfo *ji;
	guint8 *p, *code_buffer;
	guint i, align = 0, simple_type, retSize, reg_save = 0,
		stackval_arg_pos, local_pos, float_pos,
		local_start, reg_param = 0, stack_param,
		this_flag, arg_pos, fpr_param, parSize;
	guint32 simpletype;
	size_data sz;
	int *vtbuf, cpos, vt_cur;

	sz.code_size   = 1024;
	sz.stack_size  = 1024;
	stack_param     = 0;
	fpr_param       = 0;
	arg_pos	        = 0;

	sig = method->signature;

	p = code_buffer = g_malloc (sz.code_size);

	DEBUG (printf ("\nDelegate [start emiting] %s at 0x%08x\n", 
		       method->name,p));

	/*----------------------------------------------------------*/ 
	/* prolog 					     	    */
	/*----------------------------------------------------------*/ 
	s390_stmg(p, s390_r6, STK_BASE, STK_BASE, S390_REG_SAVE_OFFSET);
	s390_lg  (p, s390_r7, 0, STK_BASE, MINV_POS);
	s390_lgr (p, s390_r0, STK_BASE);
	s390_aghi(p, STK_BASE, -(sz.stack_size+MINV_POS));
	s390_stg (p, s390_r0, 0, STK_BASE, 0);
	s390_la	 (p, s390_r8, 0, STK_BASE, 4);
	s390_lgr (p, s390_r10, s390_r8);
	s390_lghi(p, s390_r9, sz.stack_size+92);
	s390_lghi(p, s390_r11, 0);
	s390_mvcl(p, s390_r8, s390_r10);

	/*----------------------------------------------------------*/ 
	/* Let's fill MonoInvocation - first zero some fields 	    */
	/*----------------------------------------------------------*/ 
	s390_lghi (p, s390_r0, 0);
	s390_stg  (p, s390_r0, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, ex)));
	s390_stg  (p, s390_r0, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, ex_handler)));
	s390_stg  (p, s390_r0, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, parent)));
	s390_lghi (p, s390_r0, 1);
	s390_stg  (p, s390_r0, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, invoke_trap)));

	/*----------------------------------------------------------*/ 
	/* set method pointer 					    */
	/*----------------------------------------------------------*/ 
	s390_bras (p, s390_r13, 4);
	s390_llong(p, method);
	s390_lg	  (p, s390_r0, 0, s390_r13, 0);
	s390_stg  (p, s390_r0, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, method)));

	local_start = local_pos = MINV_POS + 
		      sizeof (MonoInvocation) + (sig->param_count + 1) * sizeof (stackval);
	this_flag   = (sig->hasthis ? 1 : 0);

	/*----------------------------------------------------------*/
	/* if we are returning a structure, checks it's length to   */
	/* see if there's a "hidden" parameter that points to the   */
	/* area. If necessary save this hidden parameter for later  */
	/*----------------------------------------------------------*/
	if (MONO_TYPE_ISSTRUCT(sig->ret)) {
		if (sig->pinvoke) 
			retSize = mono_class_native_size (sig->ret->data.klass, &align);
		else
			retSize = mono_class_value_size (sig->ret->data.klass, &align);
		switch(retSize) {
			case 0:
			case 1:
			case 2:
			case 4:
			case 8:
				sz.retStruct = 0;
				break;
			default:
				sz.retStruct = 1;
				s390_lgr(p, s390_r8, s390_r2);
				reg_save = 1;
		}
	} else  {
		reg_save = 0;
	}

	if (this_flag) {
		s390_stg (p, s390_r2 + reg_save, 0, STK_BASE, 
			  (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, obj)));
		reg_param++;
	} else {
		s390_stg (p, s390_r2 + reg_save, 0, STK_BASE, local_pos);
		local_pos += sizeof(int);
		s390_stg (p, s390_r0, 0, STK_BASE, 
			 (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, obj)));
	}

	s390_stmg (p, s390_r3 + reg_param, s390_r6, STK_BASE, local_pos);
	local_pos += 4 * sizeof(long);
	float_pos  = local_pos;
	s390_std (p, s390_f0, 0, STK_BASE, local_pos);
	local_pos += sizeof(double);
	s390_std (p, s390_f2, 0, STK_BASE, local_pos);
	local_pos += sizeof(double);

	/*----------------------------------------------------------*/ 
	/* prepare space for valuetypes			     	    */
	/*----------------------------------------------------------*/ 
	vt_cur = local_pos;
	vtbuf  = alloca (sizeof(int)*sig->param_count);
	cpos   = 0;
	for (i = 0; i < sig->param_count; i++) {
		MonoType *type = sig->params [i];
		vtbuf [i] = -1;
		DEBUG(printf("par: %d type: %d ref: %d\n",i,type->type,type->byref));
		if (type->type == MONO_TYPE_VALUETYPE) {
			MonoClass *klass = type->data.klass;
			gint size;

			if (klass->enumtype)
				continue;
			size = mono_class_native_size (klass, &align);
			cpos += align - 1;
			cpos &= ~(align - 1);
			vtbuf [i] = cpos;
			cpos += size;
		}
	}
	cpos += 3;
	cpos &= ~3;

	local_pos += cpos;

	/*----------------------------------------------------------*/ 
	/* set MonoInvocation::stack_args			    */
	/*----------------------------------------------------------*/ 
	stackval_arg_pos = MINV_POS + sizeof (MonoInvocation);
	s390_la  (p, s390_r0, 0, STK_BASE, stackval_arg_pos);
	s390_stg (p, s390_r0, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, stack_args)));

	/*----------------------------------------------------------*/ 
	/* add stackval arguments 				    */	
	/*----------------------------------------------------------*/ 
	for (i = 0; i < sig->param_count; ++i) {
		if (sig->params [i]->byref) {
			ADD_ISTACK_PARM(0, 1);
		} else {
			simple_type = sig->params [i]->type;
		enum_savechk:
			switch (simple_type) {
			case MONO_TYPE_I8:
				ADD_ISTACK_PARM(-1, 2);
				break;
			case MONO_TYPE_R4:
				ADD_RSTACK_PARM(1);
				break;
			case MONO_TYPE_R8:
				ADD_RSTACK_PARM(2);
				break;
			case MONO_TYPE_VALUETYPE:
				if (sig->params [i]->data.klass->enumtype) {
					simple_type = sig->params [i]->data.klass->enum_basetype->type;
					goto enum_savechk;
				}
				if (sig->pinvoke)
					parSize = mono_class_native_size (sig->params [i]->data.klass, &align);
				else
					parSize = mono_class_value_size (sig->params [i]->data.klass, &align);
				switch(parSize) {
				case 0:
				case 1:
				case 2:
				case 4:
					ADD_PSTACK_PARM(0, 1);
					break;
				case 8:
					ADD_PSTACK_PARM(-1, 2);
					break;
				default:
					ADD_TSTACK_PARM;
				}
				break;
			default:
				ADD_ISTACK_PARM(0, 1);
			}
		}
				
		if (vtbuf [i] >= 0) {
			s390_la	 (p, s390_r3, 0, STK_BASE, vt_cur);
			s390_stg (p, s390_r3, 0, STK_BASE, stackval_arg_pos);
			s390_la	 (p, s390_r3, 0, STK_BASE, stackval_arg_pos);
			vt_cur += vtbuf [i];
		} else {
			s390_la  (p, s390_r3, 0, STK_BASE, stackval_arg_pos);
		}

		/*--------------------------------------*/
		/* Load the parameter registers for the */
		/* call to stackval_from_data		*/
		/*--------------------------------------*/
		s390_bras (p, s390_r13, 8);
		s390_llong(p, sig->params [i]);
		s390_llong(p, sig->pinvoke);
		s390_llong(p, stackval_from_data);
		s390_lg   (p, s390_r2, 0, s390_r13, 0);
		s390_lg	  (p, s390_r5, 0, s390_r13, 4);
		s390_lg	  (p, s390_r1, 0, s390_r13, 8);
		s390_basr (p, s390_r14, s390_r1);

		stackval_arg_pos += sizeof(stackval);

		/* fixme: alignment */
		DEBUG (printf ("arg_pos %d --> ", arg_pos));
		if (sig->pinvoke)
			arg_pos += mono_type_native_stack_size (sig->params [i], &align);
		else
			arg_pos += mono_type_stack_size (sig->params [i], &align);
		
		DEBUG (printf ("%d\n", stackval_arg_pos));
	}

	/*----------------------------------------------------------*/ 
	/* Set return area pointer. 				    */
	/*----------------------------------------------------------*/ 
	s390_la (p, s390_r10, 0, STK_BASE, stackval_arg_pos);
	s390_stg(p, s390_r10, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, retval)));
	if (sig->ret->type == MONO_TYPE_VALUETYPE && !sig->ret->byref) {
		MonoClass *klass  = sig->ret->data.klass;
		if (!klass->enumtype) {
			s390_la (p, s390_r9, 0, s390_r10, sizeof(stackval));
			s390_st (p, s390_r9, 0,STK_BASE, stackval_arg_pos);
			stackval_arg_pos += sizeof(stackval);
		}
	}

	/*----------------------------------------------------------*/ 
	/* call ves_exec_method					    */
	/*----------------------------------------------------------*/ 
	s390_bras (p, s390_r13, 4);
	s390_llong(p, ves_exec_method);
	s390_lg	  (p, s390_r1, 0, s390_r13, 0);
	s390_la	  (p, s390_r2, 0, STK_BASE, MINV_POS);
	s390_basr (p, s390_r14, s390_r1);

	/*----------------------------------------------------------*/ 
	/* move retval from stackval to proper place (r3/r4/...)    */
	/*----------------------------------------------------------*/ 
	DEBUG(printf("retType: %d byRef: %d\n",sig->ret->type,sig->ret->byref));
	if (sig->ret->byref) {
		DEBUG (printf ("ret by ref\n"));
		s390_stg(p, s390_r2, 0, s390_r10, 0);
	} else {
	enum_retvalue:
		switch (sig->ret->type) {
		case MONO_TYPE_VOID:
			break;
		case MONO_TYPE_BOOLEAN:
		case MONO_TYPE_U1:
			s390_lghi(p, s390_r2, 0);
			s390_ic  (p, s390_r2, 0, s390_r10, 0);
			break;
		case MONO_TYPE_I2:
		case MONO_TYPE_U2:
			s390_lh (p, s390_r2, 0,s390_r10, 0);
			break;
		case MONO_TYPE_I4:
		case MONO_TYPE_U4:
		case MONO_TYPE_I:
		case MONO_TYPE_U:
			s390_lgf(p, s390_r2, 0, s390_r10, 0);
			break;
		case MONO_TYPE_OBJECT:
		case MONO_TYPE_STRING:
		case MONO_TYPE_CLASS:
		case MONO_TYPE_I8:
			s390_lg (p, s390_r2, 0, s390_r10, 0);
			break;
		case MONO_TYPE_R4:
			s390_le (p, s390_f0, 0, s390_r10, 0);
			break;
		case MONO_TYPE_R8:
			s390_ld (p, s390_f0, 0, s390_r10, 0);
			break;
		case MONO_TYPE_VALUETYPE:
			if (sig->ret->data.klass->enumtype) {
				simpletype = sig->ret->data.klass->enum_basetype->type;
				goto enum_retvalue;
			}
			/*---------------------------------*/
			/* Call stackval_to_data to return */
			/* the structure		   */
			/*---------------------------------*/
			s390_bras (p, s390_r13, 8);
			s390_llong(p, sig->ret);
			s390_llong(p, sig->pinvoke);
			s390_llong(p, stackval_to_data);
			s390_lg	  (p, s390_r2, 0, s390_r13, 0);
			s390_lg	  (p, s390_r3, 0, STK_BASE, (MINV_POS + G_STRUCT_OFFSET (MonoInvocation, retval)));
			if (sz.retStruct) {	
				/*------------------------------------------*/
				/* Get stackval_to_data to set result area  */
				/*------------------------------------------*/
				s390_lgr (p, s390_r4, s390_r8);
			} else {		
				/*------------------------------------------*/
				/* Give stackval_to_data a temp result area */
				/*------------------------------------------*/ 
				s390_la (p, s390_r4, 0, STK_BASE, stackval_arg_pos);
			}
			s390_lg   (p, s390_r5, 0,s390_r13, 4);
			s390_lg   (p, s390_r1, 0, s390_r13, 8);
			s390_basr (p, s390_r14, s390_r1);
			switch (retSize) {
				case 0:
					break;
				case 1:
					s390_lghi(p, s390_r2, 0);
					s390_ic  (p, s390_r2, 0, s390_r10, 0);
					break;
				case 2:
					s390_lh (p, s390_r2, 0, s390_r10, 0);
					break;
				case 4:
					s390_lgf(p, s390_r2, 0, s390_r10, 0);
					break;
				case 8:
					s390_lg (p, s390_r2, 0, s390_r10, 0);
					break;
				default: ;
					/*-------------------------------------------------*/
					/* stackval_to_data has placed data in result area */
					/*-------------------------------------------------*/
			}
			break;
		default:
			g_error ("Type 0x%x not handled yet in thunk creation", 
				 sig->ret->type);
			break;
		}
	}

	/*----------------------------------------------------------*/ 
	/* epilog 						    */
	/*----------------------------------------------------------*/ 
	s390_lg   (p, STK_BASE, 0, STK_BASE, 0);
	s390_lg   (p, s390_r4, 0, STK_BASE, S390_RET_ADDR_OFFSET);
	s390_lmg  (p, s390_r6, STK_BASE, STK_BASE, S390_REG_SAVE_OFFSET);
	s390_br   (p, s390_r4);

	DEBUG (printf ("emited code size: %d\n", p - code_buffer));

	DEBUG (printf ("Delegate [end emiting]\n"));

	ji = g_new0 (MonoJitInfo, 1);
	ji->method = method;
	ji->code_size = p - code_buffer;
	ji->code_start = code_buffer;

	mono_jit_info_table_add (mono_get_root_domain (), ji);

	return ji->code_start;
}

/*========================= End of Function ========================*/
