/*
 * Copyright (C)2005-2022 Haxe Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "neko_mod.h"
#include "vm.h"
#define PARAMETER_TABLE
#define STACK_TABLE
#include "opcodes.h"

DEFINE_KIND(neko_kind_module);

/* Endianness macros. */
#ifdef NEKO_BSD
#	include <sys/endian.h>
#endif

/* *_TO_LE(X) converts (X) to little endian. */
#ifdef NEKO_LITTLE_ENDIAN
#	define LONG_TO_LE(X) (X)
#	define SHORT_TO_LE(X) (X)
#else
#	define LONG_TO_LE(X) ((((X) >> 24) & 0xff) | \
		(((X) >> 8) & 0xff00) | (((X) & 0xff00) << 8) | \
	       	(((X) & 0xff) << 24))
#	define SHORT_TO_LE(X) ((((X) >> 8) & 0xff) | (((X) & 0xff) << 8))
#endif

#define MAXSIZE 0x100
#define ERROR() { free(tmp); return NULL; }
#define READ(buf,len) if( r(p,buf,len) == -1 ) ERROR()

#ifdef NEKO_64BITS

static void read_long( reader r, readp p, unsigned int *i ) {
	unsigned char c[4];
	int n;
	r(p,c,4);
	n = c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24);
	*i = LONG_TO_LE(n);
}

static void read_short( reader r, readp p, unsigned short *i ) {
	unsigned char c[2];
	int n;
	r(p,c,2);
	n = c[0] | (c[1] << 8);
	*i = SHORT_TO_LE(n);
}

#	define READ_LONG(var) read_long(r,p,&(var))
#	define READ_SHORT(var) read_short(r,p,&(var))

#else

#	define READ_LONG(var) READ(&(var), 4); var = LONG_TO_LE(var)
#	define READ_SHORT(var) READ(&(var), 2); var = SHORT_TO_LE(var)

#endif

extern field id_loader;
extern field id_exports;
extern value *neko_builtins;
extern value neko_alloc_module_function( void *m, int_val pos, int nargs );
extern void neko_module_jit( neko_module *m );

EXTERN int neko_is_big_endian() {
#ifdef NEKO_LITTLE_ENDIAN
	return 0;
#else
	return 1;
#endif
}

static int read_string( reader r, readp p, char *buf ) {
	int i = 0;
	char c;
	while( i < MAXSIZE ) {
		if( r(p,&c,1) == -1 )
			return -1;
		buf[i++] = c;
		if( c == 0 )
			return i;
	}
	return -1;
}

static value get_builtin( neko_module *m, field id ) {
	value f = val_field(*neko_builtins,id);
	if( val_is_null(f) ) {
		unsigned int i;
		for(i=0;i<m->nfields;i++)
			if( val_id(val_string(m->fields[i])) == id ) {
				buffer b = alloc_buffer("Builtin not found : ");
				val_buffer(b,m->fields[i]);
				bfailure(b);
			}
		failure("Builtin not found");
	}
	return f;
}

#define UNKNOWN  ((unsigned char)-1)

static int neko_check_stack( neko_module *m, unsigned char *tmp, unsigned int i, int stack, int istack ) {
	unsigned int itmp;
	while( true ) {
		int c = (int)m->code[i];
		int s = stack_table[c];
		if( tmp[i] == UNKNOWN )
			tmp[i] = stack;
		else if( tmp[i] != stack )
			return 0;
		else
			return 1;
		if( s == P )
			stack += (int)m->code[i+1];
		else if( s == -P )
			stack -= (int)m->code[i+1];
		else
			stack += s;
		// 4 because it's the size of a push-infos needed in case of subcall
		if( stack < istack || stack >= MAX_STACK_PER_FUNCTION - 4 )
			return 0;
		switch( c ) {
		case Jump:
		case JumpIf:
		case JumpIfNot:
		case Trap:
			itmp = (int)(((int_val*)m->code[i+1]) - m->code);
			if( tmp[itmp] == UNKNOWN ) {
				if( c == Trap )
					stack -= s;
				if( !neko_check_stack(m,tmp,itmp,stack,istack) )
					return 0;
				if( c == Trap )
					stack += s;
			}
			else if( tmp[itmp] != stack )
				return 0;
			if( c == Jump )
				return 1;
			break;
		case JumpTable:
			itmp = (int)m->code[i+1];
			i += itmp;
			while( itmp > 0 ) {
				itmp -= 2;
				if( m->code[i - itmp] != Jump )
					return 0;
				if( !neko_check_stack(m,tmp,i - itmp,stack,istack) )
					return 0;
			}
			break;
		case AccStack:
		case SetStack:
			if( m->code[i+1] >= stack )
				return 0;
			break;
		case AccStack0:
			if( 0 >= stack )
				return 0;
			break;
		case AccStack1:
			if( 1 >= stack )
				return 0;
			break;
		case Last:
			if( stack != 0 )
				return 0;
			return 1;
		case Ret:
			if( m->code[i+1] != stack )
				return 0;
			return 1;
		case ObjCall:
			stack--;
			if( stack < istack )
				return 0;
			break;
		case TailCall:
			if( stack - (m->code[i+1] & 7) < istack || (m->code[i+1]>>3) != stack )
				return 0;
			return 1;
		}
		i += parameter_table[c]?2:1;
	}
	return 1;
}

static void append_array( value *arr, int pos, value v ) {
	int len = val_array_size(*arr);
	if( pos >= len ) {
		value a2 = alloc_array((len * 3) / 2);
		memcpy(val_array_ptr(a2),val_array_ptr(*arr),len * sizeof(value));
		*arr = a2;
	}
	val_array_ptr(*arr)[pos] = v;
}

#define SETBIT(b) { \
	if( (i & 31) == 0 ) { \
		bits++; \
		bits->base = pos_index; \
		bits->bits = 0; \
	} else \
		bits->bits |= b << (31 - (i & 31)); \
	i++; \
}

static void *read_debug_infos( reader r, readp p, char *tmp, neko_module *m  ) {
	unsigned int i;
	int curline = 0;
	value curfile;
	unsigned int npos;
	unsigned int nfiles;
	unsigned char c,c2;
	value files;
	value positions, pp;
	neko_debug *bits;
	int pos_index = -1;
	int lot_of_files = 0;
	READ(&c,1);
	if( c >= 0x80 ) {
		READ(&c2,1);
		nfiles = ((c & 0x7F) << 8) | c2;
		lot_of_files = 1;
	} else
		nfiles = c;
	if( nfiles == 0 )
		ERROR();
	files = alloc_array(nfiles);
	for(i=0;i<nfiles;i++) {
		if( read_string(r,p,tmp) == -1 )
			ERROR();
		val_array_ptr(files)[i] = alloc_string(tmp);
	}
	READ_LONG(npos);
	if( npos != m->codesize )
		ERROR();
	curfile = val_array_ptr(files)[0];
	positions = alloc_array(2 + (npos / 20));
	bits = (neko_debug *)alloc_private(sizeof(neko_debug) * ((npos + 31) >> 5));
	m->dbgidxs = bits;
	bits--;
	i = 0;
	pp = NULL;
	while( i < npos ) {
		READ(&c,1);
		if( c & 1 ) {
			c >>= 1;
			if( lot_of_files ) {
				READ(&c2,1);
				nfiles = (c << 8) | c2;
			} else
				nfiles = c;
			if( nfiles >= (unsigned int)val_array_size(files) )
				ERROR();
			curfile = val_array_ptr(files)[nfiles];
			pp = NULL;
		} else if( c & 2 ) {
			int delta = c >> 6;
			int count = (c >> 2) & 15;
			if( i + count > npos )
				ERROR();
			if( pp == NULL ) {
				pp = alloc_array(2);
				val_array_ptr(pp)[0] = curfile;
				val_array_ptr(pp)[1] = alloc_int(curline);
				append_array(&positions,++pos_index,pp);
				SETBIT(1);
				count--;
			}
			while( count-- )
				SETBIT(0);
			if( delta ) {
				curline += delta;
				pp = NULL;
			}
		} else if( c & 4 ) {
			curline += c >> 3;
			pp = alloc_array(2);
			val_array_ptr(pp)[0] = curfile;
			val_array_ptr(pp)[1] = alloc_int(curline);
			append_array(&positions,++pos_index,pp);
			SETBIT(1);
		} else {
			unsigned char b2;
			unsigned char b3;
			READ(&b2,1);
			READ(&b3,1);
			curline = (c >> 3) | (b2 << 5) | (b3 << 13);
			pp = alloc_array(2);
			val_array_ptr(pp)[0] = curfile;
			val_array_ptr(pp)[1] = alloc_int(curline);
			append_array(&positions,++pos_index,pp);
			SETBIT(1);
		}
	}
	// table copy
	pos_index++;
	m->dbgtbl = alloc_array(pos_index);
	memcpy(val_array_ptr(m->dbgtbl),val_array_ptr(positions),pos_index * sizeof(value));
	return m;
}

neko_module *neko_read_module( reader r, readp p, value loader ) {
	register unsigned int i;
	unsigned int itmp;
	unsigned char t;
	unsigned short stmp;
	register char *tmp = NULL;
	unsigned char version = 1;
	register neko_module *m = (neko_module*)alloc(sizeof(neko_module));
	neko_vm *vm = NEKO_VM();
	READ_LONG(itmp);
	if( itmp != 0x4F4B454E )
		ERROR();
	READ_LONG(m->nglobals);
	READ_LONG(m->nfields);
	READ_LONG(m->codesize);
	if( (int)m->nglobals < 0 || m->nglobals > 0xFFFF || (int)m->nfields < 0 || m->nfields > 0xFFFF || (int)m->codesize < 0 || m->codesize > 0xFFFFFF )
		ERROR();
	tmp = (char*)malloc(sizeof(char)*(((m->codesize+1)>MAXSIZE)?(m->codesize+1):MAXSIZE));
	m->jit = NULL;
	m->jit_gc = NULL;
	m->dbgtbl = val_null;
	m->dbgidxs = NULL;
	m->globals = (value*)alloc(m->nglobals * sizeof(value));
	m->fields = (value*)alloc(sizeof(value*)*m->nfields);
	m->loader = loader;
	m->exports = alloc_object(NULL);
	if( vm->fstats ) vm->fstats(vm,"neko_read_module_data",1);
	alloc_field(m->exports,neko_id_module,alloc_abstract(neko_kind_module,m));
	// Init global table
	for(i=0;i<m->nglobals;i++) {
		READ(&t,1);
		switch( t ) {
		case 1:
			if( read_string(r,p,tmp) == -1 )
				ERROR();
			m->globals[i] = val_null;
			break;
		case 2:
			READ_LONG(itmp);
			if( (itmp & 0xFFFFFF) >= m->codesize )
				ERROR();
			m->globals[i] = neko_alloc_module_function(m,(itmp&0xFFFFFF),(itmp >> 24));
			break;
		case 3:
			READ_SHORT(stmp);
			m->globals[i] = alloc_empty_string(stmp);
			READ(val_string(m->globals[i]),stmp);
			break;
		case 4:
			if( read_string(r,p,tmp) == -1 )
				ERROR();
			m->globals[i] = alloc_float( atof(tmp) );
			break;
		case 5:
			if( !read_debug_infos(r,p,tmp,m) ) {
				tmp = NULL; // already free in read_debug_infos
				ERROR();
			}
			m->globals[i] = val_null;
			break;
		case 6:
			READ(&version,1);
			m->globals[i] = val_null;
			break;
		default:
			ERROR();
			break;
		}
	}
	for(i=0;i<m->nfields;i++) {
		if( read_string(r,p,tmp) == -1 )
			ERROR();
		m->fields[i] = alloc_string(tmp);
	}
	if( vm->fstats ) {
		vm->fstats(vm,"neko_read_module_data",0);
		vm->fstats(vm,"neko_read_module_code",1);
	}
	#ifdef NEKO_PROF
		if( m->codesize >= PROF_SIZE )
			ERROR();
		m->code = (int_val*)alloc_private(sizeof(int_val)*(m->codesize+PROF_SIZE));
		memset(m->code+PROF_SIZE,0,m->codesize*sizeof(int_val));
	#else
		m->code = (int_val*)alloc_private(sizeof(int_val)*(m->codesize+1));
	#endif
	i = 0;
	// Unpack opcodes
	while( i < m->codesize ) {
		READ(&t,1);
		tmp[i] = 1;
		switch( t & 3 ) {
		case 0:
			m->code[i++] = (t >> 2);
			break;
		case 1:
			m->code[i++] = (t >> 3);
			tmp[i] = 0;
			m->code[i++] = (t >> 2) & 1;
			break;
		case 2:
			m->code[i++] = (t >> 2);
			if( t == 2 ) {
				// extra opcodes
				READ(&t,1);
				m->code[i-1] = t;
			} else {
				READ(&t,1);
				tmp[i] = 0;
				m->code[i++] = t;
			}
			break;
		case 3:
			m->code[i++] = (t >> 2);
			READ_LONG(itmp);
			tmp[i] = 0;
			m->code[i++] = (int)itmp;
			break;
		}
	}
	tmp[i] = 1;
	m->code[i] = Last;
	if( vm->fstats ) {
		vm->fstats(vm,"neko_read_module_code",0);
		vm->fstats(vm,"neko_read_module_check",1);
	}
	// Check bytecode
	for(i=0;i<m->codesize;i++) {
		register int c = (int)m->code[i];
		itmp = (unsigned int)m->code[i+1];
		if( c >= Last || tmp[i+1] == parameter_table[c] )
			ERROR();
		// Additional checks and optimizations
		switch( m->code[i] ) {
		case AccGlobal:
		case SetGlobal:
			if( itmp >= m->nglobals )
				ERROR();
			m->code[i+1] = (int_val)(m->globals + itmp);
			break;
		case Jump:
		case JumpIf:
		case JumpIfNot:
		case Trap:
			itmp += i;
			if( itmp > m->codesize || !tmp[itmp] )
				ERROR();
			m->code[i+1] = (int_val)(m->code + itmp);
			break;
		case AccInt:
			if( need_32_bits((int)itmp) )
				m->code[i] = AccInt32;
			else
				m->code[i+1] = (int_val)alloc_int((int)itmp);
			break;
		case AccIndex:
			m->code[i+1] += 2;
			break;
		case AccStack:
			m->code[i+1] += 2;
			itmp = (unsigned int)m->code[i+1];
		case SetStack:
			if( ((int)itmp) < 0 )
				ERROR();
			break;
		case Ret:
		case Pop:
		case AccEnv:
		case SetEnv:
			if( ((int)itmp) < 0 )
				ERROR();
			break;
		case AccBuiltin: {
			field f = (field)(int_val)itmp;
			if( f == id_loader )
				m->code[i+1] = (int_val)loader;
			else if( f == id_exports )
				m->code[i+1] = (int_val)m->exports;
			else
				m->code[i+1] = (int_val)get_builtin(m,f);
			}
			break;
		case Call:
		case ObjCall:
			if( itmp > CALL_MAX_ARGS )
				ERROR();
			break;
		case TailCall:
			if( (itmp&7) > CALL_MAX_ARGS )
				ERROR();
			break;
		case Apply:
			if( itmp == 0 || itmp >= CALL_MAX_ARGS )
				ERROR();
			break;
		case MakeEnv:
			if( itmp > 0xFF )
				failure("Too much big environment");
			break;
		case MakeArray:
			if( itmp > 0x10000 )
				failure("Too much big array");
			if( version >= 2 )
				m->code[i] = MakeArray2;
			break;
		case JumpTable:
			if( itmp > 512 || i + 1 + itmp * 2 >= m->codesize )
				ERROR();
			m->code[i+1] <<= 1;
			break;
		}
		if( !tmp[i+1] )
			i++;
	}
	// Check stack preservation
	{
		unsigned char *stmp = (unsigned char*)malloc(m->codesize+1);
		unsigned int prev = 0;
		memset(stmp,UNKNOWN,m->codesize+1);
		if( !vm->trusted_code && !neko_check_stack(m,stmp,0,0,0) ) {
			free(stmp);
			failure("Stack check failed for global scope");
		}
		for(i=0;i<m->nglobals;i++) {
			vfunction *f = (vfunction*)m->globals[i];
			if( val_type(f) == VAL_FUNCTION ) {
				itmp = (unsigned int)(int_val)f->addr;
				if( itmp >= m->codesize || !tmp[itmp] || itmp < prev ) {
					free(stmp);
					ERROR();
				}
				if( !vm->trusted_code && !neko_check_stack(m,stmp,itmp,f->nargs,f->nargs) ) {
					free(stmp);
					failure("Stack check failed for function scope");
				}
				f->addr = m->code + itmp;
				prev = itmp;
			}
		}
		free(stmp);
	}
	free(tmp);
	if( vm->fstats ) vm->fstats(vm,"neko_read_module_check",0);
	// jit ?
	if( vm->run_jit ) {
		if( vm->fstats ) vm->fstats(vm,"neko_read_module_jit",1);
		neko_module_jit(m);
		if( vm->fstats ) vm->fstats(vm,"neko_read_module_jit",0);
	}
#	ifdef NEKO_DIRECT_THREADED
	{
		int_val *jtbl = neko_get_ttable();
		if( vm->fstats ) vm->fstats(vm,"neko_read_module_thread",1);
		for(i=0;i<=m->codesize;i++) {
			int_val c = m->code[i];
			m->code[i] = jtbl[c];
			i += parameter_table[c];
		}
		if( vm->fstats ) vm->fstats(vm,"neko_read_module_thread",0);
	}
#	endif
	return m;
}

int neko_file_reader( readp p, void *buf, int size ) {
	int len = 0;
	while( size > 0 ) {
		int l;
		POSIX_LABEL(fread_again);
		l = (int)fread(buf,1,size,(FILE*)p);
		if( l <= 0 ) {
			HANDLE_FINTR((FILE*)p,fread_again);
			return len;
		}
		size -= l;
		len += l;
		buf = (char*)buf+l;
	}
	return len;
}

int neko_string_reader( readp p, void *buf, int size ) {
	string_pos *sp = (string_pos*)p;
	int delta = (sp->len >= size)?size:sp->len;
	memcpy(buf,sp->p,delta);
	sp->p += delta;
	sp->len -= delta;
	return delta;
}

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