/*
 * il_values.h - Primitive value types for the IL interpreter.
 *
 * Copyright (C) 2001  Southern Storm Software, Pty Ltd.
 *
 * 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 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef	_IL_VALUES_H
#define	_IL_VALUES_H

/*
 * Determine what kind of Win32 system we are running on.
 */
#if defined(__CYGWIN__) || defined(__CYGWIN32__)
#define	IL_WIN32_CYGWIN		1
#define	IL_WIN32_PLATFORM	1
#elif defined(_WIN32) || defined(WIN32)
#define	IL_WIN32_NATIVE		1
#define	IL_WIN32_PLATFORM	1
#endif

#include "il_config.h"
#include "il_profile.h"

#ifdef	__cplusplus
extern	"C" {
#endif

/*
 * Define the 8-bit and 16-bit numeric types.
 */
typedef signed char					ILInt8;
typedef unsigned char				ILUInt8;
typedef short						ILInt16;
typedef unsigned short				ILUInt16;
typedef ILInt8 						ILBool;

/*
 * Determine which types should be used for 32-bit numeric values.
 */
#ifdef SIZEOF_INT
	#if SIZEOF_INT == 4
		typedef int					ILInt32;
		typedef unsigned int		ILUInt32;
		#define	IL_HAVE_INT32
	#endif
#endif
#ifndef	IL_HAVE_INT32
	typedef long					ILInt32;
	typedef unsigned long			ILUInt32;
#endif

/*
 * Determine which types should be used for 64-bit numeric values.
 */
#if defined(IL_WIN32_NATIVE) && defined(_MSC_VER)
	typedef __int64					ILInt64;
	typedef unsigned __int64		ILUInt64;
	#define IL_HAVE_INT64
#endif
#if (!defined(IL_HAVE_INT64)) && defined(SIZEOF_LONG_LONG)
	#if SIZEOF_LONG_LONG == 8
		typedef long long			ILInt64;
		typedef unsigned long long	ILUInt64;
		#define	IL_HAVE_INT64
	#endif
#endif
#if (!defined(IL_HAVE_INT64)) && defined(SIZEOF_LONG)
	#if SIZEOF_LONG == 8
		typedef long				ILInt64;
		typedef unsigned long		ILUInt64;
		#define	IL_HAVE_INT64
	#endif
#endif
#if (!defined(IL_HAVE_INT64)) && defined(IL_WIN32_NATIVE)
	typedef __int64					ILInt64;
	typedef unsigned __int64		ILUInt64;
	#define IL_HAVE_INT64
#endif
#ifndef IL_HAVE_INT64
	#error "Could not detect the 64-bit integer type on this compiler"
#endif

/*
 * Native integers are the same size as native pointers.
 */
#ifdef SIZEOF_VOID_P
	#if SIZEOF_VOID_P == 8
		typedef ILInt64				ILNativeInt;
		typedef ILUInt64			ILNativeUInt;
		#define	IL_NATIVE_INT64
	#endif
#endif
#ifndef	IL_NATIVE_INT64
	typedef ILInt32					ILNativeInt;
	typedef ILUInt32				ILNativeUInt;
	#define	IL_NATIVE_INT32
#endif

/*
 * Determine which type should be used for 32-bit floating point values.
 */
#ifdef SIZEOF_FLOAT
	#if SIZEOF_FLOAT == 4
		typedef float				ILFloat;
		#define	IL_HAVE_FLOAT
	#else
		#error "Compiler's float is not 32 bits!"
	#endif
#endif
#ifndef	IL_HAVE_FLOAT
	typedef	float					ILFloat;
#endif

/*
 * Determine which type should be used for 64-bit floating point values.
 */
#ifdef SIZEOF_DOUBLE
	#if SIZEOF_DOUBLE == 8
		typedef double				ILDouble;
		#define	IL_HAVE_DOUBLE
	#else
		#error "Compiler's double is not 64 bits!"
	#endif
#endif
#ifndef	IL_HAVE_DOUBLE
	typedef	double					ILDouble;
#endif

/*
 * Determine the best native floating point format.
 *
 * Note: we check that sizeof(long double) is different from
 * sizeof(double) because some versions of gcc complain about
 * "long double" if the underlying CPU doesn't really have it.
 */
#ifdef SIZEOF_LONG_DOUBLE
	#if SIZEOF_LONG_DOUBLE != 0
		#if SIZEOF_LONG_DOUBLE != SIZEOF_DOUBLE
			typedef long double			ILNativeFloat;
			#define	IL_NATIVE_FLOAT
		#endif
	#endif
#endif
#ifndef	IL_NATIVE_FLOAT
	typedef ILDouble				ILNativeFloat;
#endif

/*
 * Useful min/max constants for the above types.
 */
#define	IL_MIN_INT32				((ILInt32)0x80000000L)
#define	IL_MAX_INT32				((ILInt32)0x7FFFFFFFL)
#define	IL_MAX_UINT32				((ILUInt32)0xFFFFFFFFL)
#if defined(IL_WIN32_NATIVE) && defined(_MSC_VER)
#define	IL_MIN_INT64				((ILInt64)0x8000000000000000L)
#define	IL_MAX_INT64				((ILInt64)0x7FFFFFFFFFFFFFFFL)
#define	IL_MAX_UINT64				((ILUInt64)0xFFFFFFFFFFFFFFFFUL)
#else
#define	IL_MIN_INT64				((ILInt64)0x8000000000000000LL)
#define	IL_MAX_INT64				((ILInt64)0x7FFFFFFFFFFFFFFFLL)
#define	IL_MAX_UINT64				((ILUInt64)0xFFFFFFFFFFFFFFFFLL)
#endif
#ifdef IL_NATIVE_INT32
#define	IL_MIN_NATIVE_INT			IL_MIN_INT32
#define	IL_MAX_NATIVE_INT			IL_MAX_INT32
#define	IL_MAX_NATIVE_UINT			IL_MAX_UINT32
#else
#define	IL_MIN_NATIVE_INT			IL_MIN_INT64
#define	IL_MAX_NATIVE_INT			IL_MAX_INT64
#define	IL_MAX_NATIVE_UINT			IL_MAX_UINT64
#endif

/*
 * Read little-endian values of various sizes from memory buffers.
 */
#define	_IL_READ_BYTE(buf,offset)	\
			((ILUInt32)(ILUInt8)(((ILUInt8 *)(buf))[(offset)]))
#define	_IL_READ_BYTE_SHIFT(buf,offset,shift)	\
			(((ILUInt32)(ILUInt8)(((ILUInt8 *)(buf))[(offset)])) << (shift))
#if defined(__i386) || defined(__i386__)
/* The i386 family of CPU's can read little-endian values on any
   byte boundary, so we can implement the macros more simply */
#define	IL_READ_INT16(buf)	(*((ILInt16 *)(buf)))
#define	IL_READ_UINT16(buf)	(*((ILUInt16 *)(buf)))
#define	IL_READ_INT32(buf)	(*((ILInt32 *)(buf)))
#define	IL_READ_UINT32(buf)	(*((ILUInt32 *)(buf)))
#define	IL_READ_INT64(buf)	(*((ILInt64 *)(buf)))
#define	IL_READ_UINT64(buf)	(*((ILUInt64 *)(buf)))
#else
#define	IL_READ_INT16(buf)	((ILInt16)(_IL_READ_BYTE((buf), 0) | \
									   _IL_READ_BYTE_SHIFT((buf), 1, 8)))
#define	IL_READ_UINT16(buf)	((ILUInt16)(_IL_READ_BYTE((buf), 0) | \
									    _IL_READ_BYTE_SHIFT((buf), 1, 8)))
#define	IL_READ_INT32(buf)	((ILInt32)(_IL_READ_BYTE((buf), 0) | \
									   _IL_READ_BYTE_SHIFT((buf), 1, 8) | \
									   _IL_READ_BYTE_SHIFT((buf), 2, 16) | \
									   _IL_READ_BYTE_SHIFT((buf), 3, 24)))
#define	IL_READ_UINT32(buf)	((ILUInt32)(_IL_READ_BYTE((buf), 0) | \
									    _IL_READ_BYTE_SHIFT((buf), 1, 8) | \
									    _IL_READ_BYTE_SHIFT((buf), 2, 16) | \
									    _IL_READ_BYTE_SHIFT((buf), 3, 24)))
#define	IL_READ_INT64(buf)	\
			(((ILInt64)(IL_READ_UINT32((buf)))) | \
			 (((ILInt64)(IL_READ_INT32(((ILUInt8 *)(buf)) + 4))) << 32))
#define	IL_READ_UINT64(buf)	\
			(((ILUInt64)(IL_READ_UINT32((buf)))) | \
			 (((ILUInt64)(IL_READ_UINT32(((ILUInt8 *)(buf)) + 4))) << 32))
#endif
ILNativeFloat _ILReadFloat(const unsigned char *buf);
#define	IL_READ_FLOAT(buf)	(_ILReadFloat((buf)))
ILNativeFloat _ILReadDouble(const unsigned char *buf);
#define	IL_READ_DOUBLE(buf)	(_ILReadDouble((buf)))

/*
 * Write little-endian values of various sizes to memory buffers.
 */
#if defined(__i386) || defined(__i386__)
/* The i386 family of CPU's can write little-endian values on any
   byte boundary, so we can implement the macros more simply */
#define	IL_WRITE_UINT16(buf, value)	(*((ILUInt16 *)(buf)) = (ILUInt16)(value))
#define	IL_WRITE_INT16(buf, value)	(*((ILInt16 *)(buf)) = (ILInt16)(value))
#define	IL_WRITE_UINT32(buf, value)	(*((ILUInt32 *)(buf)) = (ILUInt32)(value))
#define	IL_WRITE_INT32(buf, value)	(*((ILInt32 *)(buf)) = (ILInt32)(value))
#define	IL_WRITE_UINT64(buf, value)	(*((ILUInt64 *)(buf)) = (ILUInt64)(value))
#define	IL_WRITE_INT64(buf, value)	(*((ILInt64 *)(buf)) = (ILInt64)(value))
#else
#define	IL_WRITE_UINT16(buf, value)	\
			do { \
				(buf)[0] = (unsigned char)(value); \
				(buf)[1] = (unsigned char)((value) >> 8); \
			} while (0)
#define	IL_WRITE_INT16(buf, value)	IL_WRITE_UINT16((buf), (ILUInt16)(value))
#define	IL_WRITE_UINT32(buf, value)	\
			do { \
				(buf)[0] = (unsigned char)(value); \
				(buf)[1] = (unsigned char)((value) >> 8); \
				(buf)[2] = (unsigned char)((value) >> 16); \
				(buf)[3] = (unsigned char)((value) >> 24); \
			} while (0)
#define	IL_WRITE_INT32(buf, value)	IL_WRITE_UINT32((buf), (ILUInt32)(value))
#define	IL_WRITE_UINT64(buf, value)	\
			do { \
				(buf)[0] = (unsigned char)(value); \
				(buf)[1] = (unsigned char)((value) >> 8); \
				(buf)[2] = (unsigned char)((value) >> 16); \
				(buf)[3] = (unsigned char)((value) >> 24); \
				(buf)[4] = (unsigned char)((value) >> 32); \
				(buf)[5] = (unsigned char)((value) >> 40); \
				(buf)[6] = (unsigned char)((value) >> 48); \
				(buf)[7] = (unsigned char)((value) >> 56); \
			} while (0)
#define	IL_WRITE_INT64(buf, value)	IL_WRITE_UINT64((buf), (ILUInt64)(value))
#endif
void _ILWriteFloat(unsigned char *buf, ILFloat value);
#define	IL_WRITE_FLOAT(buf,value)	(_ILWriteFloat((buf), (value)))
void _ILWriteDouble(unsigned char *buf, ILDouble value);
#define	IL_WRITE_DOUBLE(buf,value)	(_ILWriteDouble((buf), (value)))

/*
 * Multiply 64-bit values with overflow testing.
 * Returns zero on overflow.
 */
int ILInt64MulOvf(ILInt64 *product, ILInt64 value1, ILInt64 value2);
int ILUInt64MulOvf(ILUInt64 *product, ILUInt64 value1, ILUInt64 value2);

/*
 * Compute the floating-point remainder of two values.
 */
ILNativeFloat ILNativeFloatRem(ILNativeFloat value1, ILNativeFloat value2);

/*
 * Value testing for floating-point values.
 */
int ILNativeFloatIsNaN(ILNativeFloat value);
int ILNativeFloatIsFinite(ILNativeFloat value);
int ILNativeFloatIsInf(ILNativeFloat value);

/*
 * Perform difficult numeric conversions.
 */
ILNativeFloat ILUInt64ToFloat(ILUInt64 value);
ILUInt64 ILFloatToUInt64(ILNativeFloat value);
int ILFloatToInt64Ovf(ILInt64 *result, ILNativeFloat value);
int ILFloatToUInt64Ovf(ILUInt64 *result, ILNativeFloat value);

#ifdef	__cplusplus
};
#endif

#endif	/* _IL_VALUES_H */
