/*
 * method-builder.c: Functions for creating IL methods at runtime.
 * 
 * Author:
 *   Paolo Molaro (lupus@ximian.com)
 *
 * (C) 2002 Ximian, Inc.  http://www.ximian.com
 *
 */

#include "config.h"
#include "loader.h"
#include "mono/metadata/method-builder.h"
#include "mono/metadata/tabledefs.h"
#include "mono/metadata/exception.h"
#include "mono/metadata/appdomain.h"
#include "mono/metadata/debug-helpers.h"
#include "mono/metadata/metadata-internals.h"
#include "mono/metadata/domain-internals.h"
#include <string.h>
#include <errno.h>

/* #define DEBUG_RUNTIME_CODE */

#define OPDEF(a,b,c,d,e,f,g,h,i,j) \
	a = i,

enum {
#include "mono/cil/opcode.def"
	LAST = 0xff
};
#undef OPDEF

#ifdef DEBUG_RUNTIME_CODE
static char*
indenter (MonoDisHelper *dh, MonoMethod *method, guint32 ip_offset)
{
	return g_strdup (" ");
}

static MonoDisHelper marshal_dh = {
	"\n",
	"IL_%04x: ",
	"IL_%04x",
	indenter, 
	NULL,
	NULL
};
#endif 

static MonoMethodBuilder *
mono_mb_new_base (MonoClass *klass, MonoWrapperType type)
{
	MonoMethodBuilder *mb;
	MonoMethod *m;

	g_assert (klass != NULL);

	mb = g_new0 (MonoMethodBuilder, 1);

	mb->method = m = (MonoMethod *)g_new0 (MonoMethodWrapper, 1);

	m->klass = klass;
	m->inline_info = 1;
	m->wrapper_type = type;

	mb->code_size = 40;
	mb->code = g_malloc (mb->code_size);
	
	return mb;
}

MonoMethodBuilder *
mono_mb_new_no_dup_name (MonoClass *klass, const char *name, MonoWrapperType type)
{
	MonoMethodBuilder *mb = mono_mb_new_base (klass, type);
	mb->name = (char*)name;
	mb->no_dup_name = TRUE;
	return mb;
}

MonoMethodBuilder *
mono_mb_new (MonoClass *klass, const char *name, MonoWrapperType type)
{
	MonoMethodBuilder *mb = mono_mb_new_base (klass, type);
	mb->name = g_strdup (name);
	return mb;
}

void
mono_mb_free (MonoMethodBuilder *mb)
{
	g_list_free (mb->locals_list);
	if (!mb->dynamic) {
		g_free (mb->method);
		if (!mb->no_dup_name)
			g_free (mb->name);
		g_free (mb->code);
	}
	g_free (mb);
}

int
mono_mb_add_local (MonoMethodBuilder *mb, MonoType *type)
{
	int res;

	g_assert (mb != NULL);
	g_assert (type != NULL);

	res = mb->locals;
	mb->locals_list = g_list_append (mb->locals_list, type);
	mb->locals++;

	return res;
}

/**
 * mono_mb_create_method:
 *
 * Create a MonoMethod from this method builder.
 * Returns: the newly created method.
 *
 * LOCKING: Takes the loader lock.
 */
MonoMethod *
mono_mb_create_method (MonoMethodBuilder *mb, MonoMethodSignature *signature, int max_stack)
{
	MonoMethodHeader *header;
	MonoMethodWrapper *mw;
	MonoMemPool *mp;
	MonoMethod *method;
	GList *l;
	int i;

	g_assert (mb != NULL);

	mp = mb->method->klass->image->mempool;

	mono_loader_lock ();
	if (mb->dynamic) {
		method = mb->method;

		method->name = mb->name;
		method->dynamic = TRUE;

		((MonoMethodNormal *)method)->header = header = (MonoMethodHeader *) 
			g_malloc0 (sizeof (MonoMethodHeader) + mb->locals * sizeof (MonoType *));

		header->code = mb->code;

		for (i = 0, l = mb->locals_list; l; l = l->next, i++) {
			header->locals [i] = mono_metadata_type_dup (NULL, (MonoType*)l->data);
		}
	} else {
		/* Realloc the method info into a mempool */

		method = mono_mempool_alloc0 (mp, sizeof (MonoMethodWrapper));
		memcpy (method, mb->method, sizeof (MonoMethodWrapper));

		if (mb->no_dup_name)
			method->name = mb->name;
		else
			method->name = mono_mempool_strdup (mp, mb->name);

		((MonoMethodNormal *)method)->header = header = (MonoMethodHeader *) 
			mono_mempool_alloc0 (mp, sizeof (MonoMethodHeader) + mb->locals * sizeof (MonoType *));

		header->code = mono_mempool_alloc (mp, mb->pos);
		memcpy ((char*)header->code, mb->code, mb->pos);

		for (i = 0, l = mb->locals_list; l; l = l->next, i++) {
			header->locals [i] = (MonoType *)l->data;
		}
	}

	if (max_stack < 8)
		max_stack = 8;

	header->max_stack = max_stack;

	method->signature = signature;

	header->code_size = mb->pos;
	header->num_locals = mb->locals;
	header->init_locals = TRUE;

	mw = (MonoMethodWrapper*) mb->method;
	i = g_list_length (mw->method_data);
	if (i) {
		GList *tmp;
		void **data;
		l = g_list_reverse (mw->method_data);
		if (method->dynamic)
			data = g_malloc (sizeof (gpointer) * (i + 1));
		else
			data = mono_mempool_alloc (mp, sizeof (gpointer) * (i + 1));
		/* store the size in the first element */
		data [0] = GUINT_TO_POINTER (i);
		i = 1;
		for (tmp = l; tmp; tmp = tmp->next) {
			data [i++] = tmp->data;
		}
		g_list_free (l);

		((MonoMethodWrapper*)method)->method_data = data;
	}
	/*{
		static int total_code = 0;
		static int total_alloc = 0;
		total_code += mb->pos;
		total_alloc += mb->code_size;
		g_print ("code size: %d of %d (allocated: %d)\n", mb->pos, total_code, total_alloc);
	}*/

#ifdef DEBUG_RUNTIME_CODE
	printf ("RUNTIME CODE FOR %s\n", mono_method_full_name (method, TRUE));
	printf ("%s\n", mono_disasm_code (&marshal_dh, method, mb->code, mb->code + mb->pos));
#endif

	mono_loader_unlock ();
	return method;
}

guint32
mono_mb_add_data (MonoMethodBuilder *mb, gpointer data)
{
	MonoMethodWrapper *mw;

	g_assert (mb != NULL);

	mw = (MonoMethodWrapper *)mb->method;

	/* one O(n) is enough */
	mw->method_data = g_list_prepend (mw->method_data, data);

	return g_list_length (mw->method_data);
}

void
mono_mb_patch_addr (MonoMethodBuilder *mb, int pos, int value)
{
	mb->code [pos] = value & 0xff;
	mb->code [pos + 1] = (value >> 8) & 0xff;
	mb->code [pos + 2] = (value >> 16) & 0xff;
	mb->code [pos + 3] = (value >> 24) & 0xff;
}

void
mono_mb_patch_addr_s (MonoMethodBuilder *mb, int pos, gint8 value)
{
	*((gint8 *)(&mb->code [pos])) = value;
}

void
mono_mb_emit_byte (MonoMethodBuilder *mb, guint8 op)
{
	if (mb->pos >= mb->code_size) {
		mb->code_size += mb->code_size >> 1;
		mb->code = g_realloc (mb->code, mb->code_size);
	}

	mb->code [mb->pos++] = op;
}

void
mono_mb_emit_ldflda (MonoMethodBuilder *mb, gint32 offset)
{
        mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
        mono_mb_emit_byte (mb, CEE_MONO_OBJADDR);

	if (offset) {
		mono_mb_emit_icon (mb, offset);
		mono_mb_emit_byte (mb, CEE_ADD);
	}
}

void
mono_mb_emit_i4 (MonoMethodBuilder *mb, gint32 data)
{
	if ((mb->pos + 4) >= mb->code_size) {
		mb->code_size += mb->code_size >> 1;
		mb->code = g_realloc (mb->code, mb->code_size);
	}

	mono_mb_patch_addr (mb, mb->pos, data);
	mb->pos += 4;
}

void
mono_mb_emit_i2 (MonoMethodBuilder *mb, gint16 data)
{
	if ((mb->pos + 2) >= mb->code_size) {
		mb->code_size += mb->code_size >> 1;
		mb->code = g_realloc (mb->code, mb->code_size);
	}

	mb->code [mb->pos] = data & 0xff;
	mb->code [mb->pos + 1] = (data >> 8) & 0xff;
	mb->pos += 2;
}

void
mono_mb_emit_op (MonoMethodBuilder *mb, guint8 op, gpointer data)
{
	mono_mb_emit_byte (mb, op);
	mono_mb_emit_i4 (mb, mono_mb_add_data (mb, data));
}

void
mono_mb_emit_ldstr (MonoMethodBuilder *mb, char *str)
{
	mono_mb_emit_op (mb, CEE_LDSTR, str);
}

void
mono_mb_emit_ldarg (MonoMethodBuilder *mb, guint argnum)
{
	if (argnum < 4) {
 		mono_mb_emit_byte (mb, CEE_LDARG_0 + argnum);
	} else if (argnum < 256) {
		mono_mb_emit_byte (mb, CEE_LDARG_S);
		mono_mb_emit_byte (mb, argnum);
	} else {
		mono_mb_emit_byte (mb, CEE_PREFIX1);
		mono_mb_emit_byte (mb, CEE_LDARG);
		mono_mb_emit_i2 (mb, argnum);
	}
}

void
mono_mb_emit_ldarg_addr (MonoMethodBuilder *mb, guint argnum)
{
	if (argnum < 256) {
		mono_mb_emit_byte (mb, CEE_LDARGA_S);
		mono_mb_emit_byte (mb, argnum);
	} else {
		mono_mb_emit_byte (mb, CEE_PREFIX1);
		mono_mb_emit_byte (mb, CEE_LDARGA);
		mono_mb_emit_i2 (mb, argnum);
	}
}

void
mono_mb_emit_ldloc_addr (MonoMethodBuilder *mb, guint locnum)
{
	if (locnum < 256) {
		mono_mb_emit_byte (mb, CEE_LDLOCA_S);
		mono_mb_emit_byte (mb, locnum);
	} else {
		mono_mb_emit_byte (mb, CEE_PREFIX1);
		mono_mb_emit_byte (mb, CEE_LDLOCA);
		mono_mb_emit_i2 (mb, locnum);
	}
}

void
mono_mb_emit_ldloc (MonoMethodBuilder *mb, guint num)
{
	if (num < 4) {
 		mono_mb_emit_byte (mb, CEE_LDLOC_0 + num);
	} else if (num < 256) {
		mono_mb_emit_byte (mb, CEE_LDLOC_S);
		mono_mb_emit_byte (mb, num);
	} else {
		mono_mb_emit_byte (mb, CEE_PREFIX1);
		mono_mb_emit_byte (mb, CEE_LDLOC);
		mono_mb_emit_i2 (mb, num);
	}
}

void
mono_mb_emit_stloc (MonoMethodBuilder *mb, guint num)
{
	if (num < 4) {
 		mono_mb_emit_byte (mb, CEE_STLOC_0 + num);
	} else if (num < 256) {
		mono_mb_emit_byte (mb, CEE_STLOC_S);
		mono_mb_emit_byte (mb, num);
	} else {
		mono_mb_emit_byte (mb, CEE_PREFIX1);
		mono_mb_emit_byte (mb, CEE_STLOC);
		mono_mb_emit_i2 (mb, num);
	}
}

void
mono_mb_emit_icon (MonoMethodBuilder *mb, gint32 value)
{
	if (value >= -1 && value < 8) {
		mono_mb_emit_byte (mb, CEE_LDC_I4_0 + value);
	} else if (value >= -128 && value <= 127) {
		mono_mb_emit_byte (mb, CEE_LDC_I4_S);
		mono_mb_emit_byte (mb, value);
	} else {
		mono_mb_emit_byte (mb, CEE_LDC_I4);
		mono_mb_emit_i4 (mb, value);
	}
}

int
mono_mb_get_label (MonoMethodBuilder *mb)
{
	return mb->pos;
}

int
mono_mb_get_pos (MonoMethodBuilder *mb)
{
	return mb->pos;
}

guint32
mono_mb_emit_branch (MonoMethodBuilder *mb, guint8 op)
{
	guint32 res;
	mono_mb_emit_byte (mb, op);
	res = mb->pos;
	mono_mb_emit_i4 (mb, 0);
	return res;
}

guint32
mono_mb_emit_short_branch (MonoMethodBuilder *mb, guint8 op)
{
	guint32 res;
	mono_mb_emit_byte (mb, op);
	res = mb->pos;
	mono_mb_emit_byte (mb, 0);

	return res;
}

void
mono_mb_emit_branch_label (MonoMethodBuilder *mb, guint8 op, guint32 label)
{
	mono_mb_emit_byte (mb, op);
	mono_mb_emit_i4 (mb, label - (mb->pos + 4));
}

void
mono_mb_patch_branch (MonoMethodBuilder *mb, guint32 pos)
{
	mono_mb_patch_addr (mb, pos, mb->pos - (pos + 4));
}

void
mono_mb_patch_short_branch (MonoMethodBuilder *mb, guint32 pos)
{
	mono_mb_patch_addr_s (mb, pos, mb->pos - (pos + 1));
}

void
mono_mb_emit_ptr (MonoMethodBuilder *mb, gpointer ptr)
{
	mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
	mono_mb_emit_op (mb, CEE_MONO_LDPTR, ptr);
}

void
mono_mb_emit_calli (MonoMethodBuilder *mb, MonoMethodSignature *sig)
{
	mono_mb_emit_op (mb, CEE_CALLI, sig);
}

void
mono_mb_emit_managed_call (MonoMethodBuilder *mb, MonoMethod *method, MonoMethodSignature *opt_sig)
{
	mono_mb_emit_op (mb, CEE_CALL, method);
}

void
mono_mb_emit_native_call (MonoMethodBuilder *mb, MonoMethodSignature *sig, gpointer func)
{
	mono_mb_emit_ptr (mb, func);
	mono_mb_emit_calli (mb, sig);
}

void
mono_mb_emit_icall (MonoMethodBuilder *mb, gpointer func)
{
	mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
	mono_mb_emit_op (mb, CEE_MONO_ICALL, func);
}

void
mono_mb_emit_exception_full (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg)
{
	MonoMethod *ctor = NULL;

	MonoClass *mme = mono_class_from_name (mono_defaults.corlib, exc_nspace, exc_name);
	mono_class_init (mme);
	ctor = mono_class_get_method_from_name (mme, ".ctor", 0);
	g_assert (ctor);
	mono_mb_emit_op (mb, CEE_NEWOBJ, ctor);
	if (msg != NULL) {
		mono_mb_emit_byte (mb, CEE_DUP);
		mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoException, message));
		mono_mb_emit_ldstr (mb, (char*)msg);
		mono_mb_emit_byte (mb, CEE_STIND_REF);
	}
	mono_mb_emit_byte (mb, CEE_THROW);
}

void
mono_mb_emit_exception (MonoMethodBuilder *mb, const char *exc_name, const char *msg)
{
	mono_mb_emit_exception_full (mb, "System", exc_name, msg);
}

void
mono_mb_emit_add_to_local (MonoMethodBuilder *mb, guint16 local, gint32 incr)
{
	mono_mb_emit_ldloc (mb, local); 
	mono_mb_emit_icon (mb, incr);
	mono_mb_emit_byte (mb, CEE_ADD);
	mono_mb_emit_stloc (mb, local); 
}
