/***************************************************************************

	jit.c

	(c) 2000-2018 Benoît Minisini <benoit.minisini@gambas-basic.org>

	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 2, 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, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
	MA 02110-1301, USA.

***************************************************************************/

#define __JIT_C

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>

#include "gb_str.h"
#include "jit.h"

typedef
	struct {
		const char *name;
		char type;
	}
	CLASS_TYPE;

CLASS *JIT_class;
char *JIT_prefix;
bool JIT_last_print_is_label;
void **JIT_pointers = NULL;
bool JIT_trace;

char *JIT_buffer;

static bool JIT_cache;

static char *_buffer = NULL;
static char *_buffer_decl = NULL;
static char *_buffer_body = NULL;

static bool _decl_null_string = FALSE;
static bool _decl_null_date = FALSE;
static bool _decl_null_object = FALSE;
static bool _decl_null_variant = FALSE;

static const char *_type_name[] =
{
	"V" , "b", "c", "h", "i", "l", "g", "f",
	"d", "s", "t", "p", "v", "F", "C", "n",
	"o", "u"
};

static const char *_gtype_name[] = 
{
	"GB_T_VOID" , "GB_T_BOOLEAN", "GB_T_BYTE", "GB_T_SHORT", "GB_T_INTEGER", "GB_T_LONG", "GB_T_SINGLE", "GB_T_FLOAT",
	"GB_T_DATE", "GB_T_STRING", "GB_T_CSTRING", "GB_T_POINTER", "GB_T_VARIANT", "?", "GB_T_CLASS", "?", 
	"GB_T_OBJECT"
};

static const char *_ctype_name[] = 
{
	"void" , "bool", "uchar", "short", "int", "int64_t", "float", "double",
	"GB_DATE", "GB_STRING", "GB_STRING", "intptr_t", "GB_VARIANT", "?", "void *", "?", 
	"GB_OBJECT", "GB_VALUE", "?"
};

const char *JIT_get_type(TYPE type)
{
	return _type_name[TYPEID(type)];
}

const char *JIT_get_gtype(TYPE type)
{
	return _gtype_name[TYPEID(type)];
}

const char *JIT_get_ctype(TYPE type)
{
	return _ctype_name[TYPEID(type)];
}

TYPE JIT_ctype_to_type(CLASS *class, CTYPE ctype)
{
	if (ctype.id == T_OBJECT && ctype.value >= 0)
		return (TYPE)(class->load->class_ref[ctype.value]);
	else if (ctype.id == TC_ARRAY)
	{
		CLASS_ARRAY *desc = class->load->array[ctype.value];
		return (TYPE)JIT.get_array_class(class, *(JIT_CTYPE *)&desc->ctype);
	}
	else if (ctype.id == TC_STRUCT)
		return (TYPE)(class->load->class_ref[ctype.value]);
	else
		return (TYPE)(ctype.id);
}


static void JIT_vprint(char **buffer, const char *fmt, va_list args)
{
	int len, add;
	va_list copy;

	va_copy(copy, args);
	add = vsnprintf(NULL, 0, fmt, copy);
	va_end(copy);

	len = GB.StringLength(*buffer);

	*buffer = GB.ExtendString(*buffer, len + add);

	vsprintf(*buffer + len, fmt, args);

	// TODO: move that upper
	JIT_last_print_is_label = (strncmp(fmt, "__L", 3) == 0);
}


static void JIT_print_at(char **buffer, const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
	JIT_vprint(buffer, fmt, args);
	va_end(args);
}


static void JIT_begin(void)
{
	char *p;
	
	JIT_prefix = STR_lower(JIT_class->name);
	
	p = JIT_prefix;
	while (*p)
	{
		if (*p == ':')
			*p = '$';
		p++;
	}
	
	GB.Alloc(POINTER(&JIT_buffer), MAX_SYMBOL_LEN + 256);
	
	_buffer = NULL;
	_buffer_decl = NULL;

	GB.NewArray(POINTER(&JIT_pointers), sizeof(void *), 0);

	JIT_print("\n//////// %s\n\n", JIT_class->name);
	if (JIT_cache) JIT_print("\nextern const void *const _jit_%s_ptr[];\n\n", JIT_prefix);
}

static void JIT_end(char **result, char **pointers)
{
	char *buffer = NULL;
	int i;

	*result = _buffer;
	_buffer = NULL;

	if (GB.Count(JIT_pointers) > 0)
	{
		JIT_print_at(&buffer, "const void *const _jit_%s_ptr[] = { ", JIT_prefix);
		JIT_print_at(&buffer, "(void *)%p", JIT_pointers[0]);
		for (i = 1; i < GB.Count(JIT_pointers); i++)
			JIT_print_at(&buffer, ", (void *)%p", JIT_pointers[i]);
		JIT_print_at(&buffer, " };\n");
	}

	*pointers = buffer;

	GB.FreeArray(POINTER(&JIT_pointers));
	GB.Free(POINTER(&JIT_buffer));
	STR_free(JIT_prefix);
}

static void declare_implementation(FUNCTION *func, int index)
{
	int i;
	int nopt;
	int opt;
	const char *vol = func->error ? "volatile " : "";
	
	JIT_print("static %s jit_%s_%d_(", JIT_get_ctype(func->type), JIT_prefix, index);
	
	for (i = 0; i < func->npmin; i++)
	{
		if (i) JIT_print(",");
		JIT_print("%s%s p%d", vol, JIT_get_ctype(func->param[i].type), i);
	}
	
	if (i < func->n_param)
	{
		opt = nopt = 0;
		
		for (; i < func->n_param; i++)
		{
			if (i) JIT_print(",");
			
			if (nopt == 0)
			{
				JIT_print("uchar o%d,", opt);
				opt++;
			}
			
			JIT_print("%s%s p%d", vol, JIT_get_ctype(func->param[i].type), i);
			
			nopt++;
			if (nopt >= 8)
				nopt = 0;
		}
	}
	
	if (func->vararg)
	{
		if (func->n_param)
			JIT_print(",");
		JIT_print("uchar nv,GB_VALUE *v");
	}
	
	JIT_print(")");
}


void JIT_declare_func(FUNCTION *func, int index)
{
	JIT_print("void jit_%s_%d(uchar n);\n", JIT_prefix, index);
	
	declare_implementation(func, index);
	JIT_print(";\n");
}


const char *JIT_get_default_value(TYPE type)
{
	switch(TYPEID(type))
	{
		case T_DATE:

			if (!_decl_null_date)
			{
				JIT_print_decl("  const GB_DATE null_date = {GB_T_DATE};\n");
				_decl_null_date = TRUE;
			}
			return "null_date";
			
		case T_STRING:
		
			if (!_decl_null_string)
			{
				JIT_print_decl("  const GB_STRING null_string = {GB_T_STRING};\n");
				_decl_null_string = TRUE;
			}
			return "null_string";
			
		case T_OBJECT:
			
			if (!_decl_null_object)
			{
				JIT_print_decl("  const GB_OBJECT null_object = {GB_T_OBJECT};\n");
				_decl_null_object = TRUE;
			}
			return "null_object";
			
		case T_VARIANT: 
			if (!_decl_null_variant)
			{
				JIT_print_decl("  const GB_VARIANT null_variant = {GB_T_VARIANT,{GB_T_NULL}};\n");
				_decl_null_variant = TRUE;
			}
			return "null_variant";
			
		default:
			return "0";
	}
}


static bool JIT_translate_func(FUNCTION *func, int index)
{
	int i;
	TYPE type;
	int nopt;
	const char *def;
	const char *vol = func->error ? "volatile " : "";
		
	if (func->debug)
		JIT_section(func->debug->name);
	
	JIT_print("void jit_%s_%d(uchar n)\n{\n", JIT_prefix, index);
	
	if (func->n_param || func->vararg)
		JIT_print("  VALUE *sp = *((VALUE **)%s);\n", JITPTR(JIT.sp));
	
	JIT_print("  ");
	
	if (!TYPE_is_void(func->type))
		JIT_print("RETURN_%s(", JIT_get_type(func->type));
	
	JIT_print("jit_%s_%d_(", JIT_prefix, index);
	
	for (i = 0; i < func->npmin; i++)
	{
		if (i) JIT_print(",");
		type = func->param[i].type;
		if (TYPE_is_pure_object(type))
			JIT_print("PARAM_O(%d, CLASS(%s))", i, JITPTR(type));
		else
			JIT_print("PARAM_%s(%d)", JIT_get_type(type), i);
	}
	
	if (i < func->n_param)
	{
		nopt = 0;
		
		for (; i < func->n_param; i++)
		{
		if (i) JIT_print(",");
		
			if (nopt == 0)
				JIT_print("OPT(%d,%d),", i, Min(func->n_param, i + 8) - i);
			
			type = func->param[i].type;
			if (TYPE_is_pure_object(type))
				JIT_print("PARAM_OPT_O(%d, CLASS(%s))", i, JITPTR(type));
			else
				JIT_print("PARAM_OPT_%s(%d)", JIT_get_type(type), i);
			
			nopt++;
			if (nopt >= 8)
				nopt = 0;
		}
	}
	
	if (func->vararg)
	{
		if (func->n_param)
			JIT_print(",");
		JIT_print("n - %d,&sp[-n+%d]", i, i);
	}
	
	if (!TYPE_is_void(func->type))
		JIT_print(")");
	
	JIT_print(");\n");
	JIT_print("}\n\n");
	
	declare_implementation(func, index);
	JIT_print("\n{\n");

	_buffer_decl = NULL;
	_buffer_body = NULL;
	_decl_null_date = FALSE;
	_decl_null_string = FALSE;
	_decl_null_object = FALSE;
	_decl_null_variant = FALSE;
	
	for (i = -1; i < func->n_local; i++)
	{
		if (i < 0)
		{
			if (TYPE_is_void(func->type))
				continue;
			type = func->type;
			def = JIT_get_default_value(type);
			JIT_print_decl("  %s r = ", JIT_get_ctype(type));
		}
		else
		{
			type = JIT_ctype_to_type(JIT_class, func->local[i].type);
			def = JIT_get_default_value(type);
			JIT_print_decl("  %s%s l%d = ", vol, JIT_get_ctype(type), i);
		}
		
		JIT_print_decl(def);
		JIT_print_decl(";\n");
	}
	
	for (i = 0; i < func->n_param; i++)
	{
		type = func->param[i].type;
		switch(TYPEID(type))
		{
			case T_STRING: case T_OBJECT: case T_VARIANT:
				JIT_print_body("  BORROW_%s(p%d);\n", JIT_get_type(type), i);
		}
	}
	
	if (JIT_translate_body(func, index))
		return TRUE;
	
	if (!TYPE_is_void(func->type))
	{
		switch(TYPEID(func->type))
		{
			case T_STRING:
			case T_OBJECT:
			case T_VARIANT:
				JIT_print_body("  JIT.unborrow((GB_VALUE *)&r);\n");
				break;
		}
		
		JIT_print_body("  return r;\n");
	}
	else
		JIT_print_body("  return;\n");
	
	_buffer = GB.AddString(_buffer, _buffer_decl, GB.StringLength(_buffer_decl));
	JIT_print("\n");
	_buffer = GB.AddString(_buffer, _buffer_body, GB.StringLength(_buffer_body));
	
	GB.FreeString(&_buffer_decl);
	GB.FreeString(&_buffer_body);
	
	JIT_print("}\n\n");
	
	return FALSE;
}


void JIT_print(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
	JIT_vprint(&_buffer, fmt, args);
	va_end(args);
}


void JIT_print_decl(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
	JIT_vprint(&_buffer_decl, fmt, args);
	va_end(args);
}


void JIT_print_body(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
	JIT_vprint(&_buffer_body, fmt, args);
	va_end(args);
}


void JIT_section(const char *str)
{
	JIT_print("\n// %s\n\n", str);
}


void JIT_declare(TYPE type, const char *fmt, ...)
{
	va_list args;
	const char *def;
	
	def = JIT_get_default_value(type);
	
	JIT_print_decl("  %s ", JIT_get_ctype(type));
	
  va_start(args, fmt);
	JIT_vprint(&_buffer_decl, fmt, args);
	va_end(args);
	
	switch (TYPEID(type))
	{
		case T_STRING:
		case T_OBJECT:
		case T_VARIANT:
			JIT_print_decl(" = %s", def);
			break;
	}
	
	JIT_print_decl(";\n");
}



bool JIT_translate(const char *name, const char *from, bool trace, bool cache, char **result, char **pointers)
{
	CLASS *class;
	int i;
	FUNCTION *func;
	
	JIT_trace = trace;
	JIT_cache = cache;

	JIT_class = class = (CLASS *)GB.LoadClassFrom(name, from);
	
	JIT_begin();
	
	for (i = 0; i < class->load->n_func; i++)
	{
		func = &class->load->func[i];
		if (!func->fast)
			continue;
		JIT_declare_func(func, i);
	}
	
	for (i = 0; i < class->load->n_func; i++)
	{
		func = &class->load->func[i];
		if (!func->fast)
			continue;
		
		JIT_last_print_is_label = FALSE;
		if (JIT_translate_func(func, i))
			return TRUE;
	}
	
	JIT_end(result, pointers);
	return FALSE;
}


void JIT_panic(const char *fmt, ...)
{
	va_list args;
	fprintf(stderr, "gb.jit: panic: ");
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	fputc('\n', stderr);
	va_end(args);
	fputc('\n', stderr);
	fputs(_buffer, stderr);
	if (_buffer_decl) fputs(_buffer_decl, stderr);
	if (_buffer_body) fputs(_buffer_body, stderr);
	fputc('\n', stderr);
	abort();
}


int JIT_get_code_size(FUNCTION *func)
{
	ushort *code = func->code;

	if (func->n_label)
		code -= func->n_label + 1;

	int size = ((int *)code)[-1] / sizeof(ushort);
	
	if (code[size - 1] == 0)
		size--;

	return size;
}


void JIT_load_class_without_init(CLASS *class)
{
	void *save_cp;
	
	if (class->loaded)
		return;
	
	if (class->ready || class->in_load)
		return;
	
	save_cp = JIT.exec->cp;
	JIT.exec->cp = JIT_class;
	
	//fprintf(stderr, "gb.jit: load class: %s (%p)\n", class->name, class);
	JIT.load_class_without_init(class);
	
	JIT.exec->cp = save_cp;
}


int JIT_find_symbol(CLASS *class, const char *name)
{
	JIT_load_class_without_init(class);
	if (class->loaded)
		return JIT.find_symbol(class->table, class->sort, class->n_desc, sizeof(CLASS_DESC_SYMBOL), TF_IGNORE_CASE, name, strlen(name), NULL);
	else
		return NO_SYMBOL;
}


const char *JIT_pointer(void *p)
{
	int n;

	if (!p)
		return "NULL";

	n = sprintf(JIT_buffer, "_jit_%s_ptr[%d]", JIT_prefix, GB.Count(JIT_pointers));

	*(void **)GB.Add(&JIT_pointers) = p;

	return GB.TempString(JIT_buffer, n);
}

