/** @file
 *  Sources of the "INTEGER ARITHMETIC" group instructions
 */

/* $Id$ */

#include	"em_abs.h"
#include	"logging.h"
#include	"global.h"
#include	"log.h"
#include	"mem.h"
#include	"trap.h"
#include	"warn.h"
#include	"text.h"
#include	"fra.h"
#include	"switch.h"

PRIVATE long adi(long, long, size), sbi(long, long, size), dvi(long, long, size);
PRIVATE long mli(long, long, size), rmi(long, long), ngi(long, size);
PRIVATE long sli(long, long, size), sri(long, long, size);

/** ADI w: Addition (*) */
void DoADI(register size l)
{
	register long t = spop(arg_wi(l));

	LOG(("@I6 DoADI(%ld)", l));
	spoilFRA();
	npush(adi(spop(l), t, l), l);
}

/** SBI w: Subtraction (*) */
void DoSBI(register size l)
{
	register long t = spop(arg_wi(l));

	LOG(("@I6 DoSBI(%ld)", l));
	spoilFRA();
	npush(sbi(spop(l), t, l), l);
}

/** MLI w: Multiplication (*) */
void DoMLI(register size l)
{
	register long t = spop(arg_wi(l));

	LOG(("@I6 DoMLI(%ld)", l));
	spoilFRA();
	npush(mli(spop(l), t, l), l);
}

/** DVI w: Division (*) */
void DoDVI(register size l)
{
	register long t = spop(arg_wi(l));

	LOG(("@I6 DoDVI(%ld)", l));
	spoilFRA();
	npush(dvi(spop(l), t, l), l);
}

/** RMI w: Remainder (*) */
void DoRMI(register size l)
{
	register long t = spop(arg_wi(l));

	LOG(("@I6 DoRMI(%ld)", l));
	spoilFRA();
	npush(rmi(spop(l), t), l);
}

/** NGI w: Negate (two's complement) (*) */
void DoNGI(register size l)
{
	LOG(("@I6 DoNGI(%ld)", l));
	spoilFRA();
	l = arg_wi(l);
	npush(ngi(spop(l), l), l);
}

/** SLI w: Shift left (*) */
void DoSLI(register size l)
{
	register long t = swpop();

	LOG(("@I6 DoSLI(%ld)", l));
	spoilFRA();
	l = arg_wi(l);
	npush(sli(spop(l), t, l), l);
}

/** SRI w: Shift right (*) */
void DoSRI(register size l)
{
	register long t = swpop();

	LOG(("@I6 DoSRI(%ld)", l));
	spoilFRA();
	l = arg_wi(l);
	npush(sri(spop(l), t, l), l);
}

#define	i_maxs(n)		((n == 2) ? I_MAXS2 : I_MAXS4)
#define	i_mins(n)		((n == 2) ? I_MINS2 : I_MINS4)

/** Returns "w1" + "w2". */
PRIVATE long adi(long w1, long w2, size nbytes)
{
	if (must_test && !(IgnMask&BIT(EIOVFL))) {
		if (w1 > 0 && w2 > 0) {
			if (i_maxs(nbytes) - w1 < w2)
				trap(EIOVFL);
		}
		else if (w1 < 0 && w2 < 0) {
			if (i_mins(nbytes) - w1 > w2)
				trap(EIOVFL);
		}
	}
	return (w1 + w2);
}

/** Returns "w1" - "w2" */
PRIVATE long sbi(long w1, long w2, size nbytes)
{
	if (must_test && !(IgnMask&BIT(EIOVFL))) {
		if (w2 < 0 && w1 > 0) {
			if (i_maxs(nbytes) + w2 < w1)
				trap(EIOVFL);
		}
		else if (w2 > 0 && w1 < 0) {
			if (i_mins(nbytes) + w2 > w1) {
				trap(EIOVFL);
			}
		}
	}
	return (w1 - w2);
}

#define	labs(w)		((w < 0) ? (-w) : w)

/** Returns "w1" * "w2" */
PRIVATE long mli(long w1, long w2, size nbytes)
{
	if (w1 == 0 || w2 == 0)
		return (0L);

	if (must_test && !(IgnMask&BIT(EIOVFL))) {
		if ((w1 > 0 && w2 > 0) || (w2 < 0 && w1 < 0)) {
			if (	w1 == i_mins(nbytes) || w2 == i_mins(nbytes)
			||	(i_maxs(nbytes) / labs(w1)) < labs(w2)
			) {
				trap(EIOVFL);
			}
		}
		else if (w1 > 0) {
			if (i_mins(nbytes) / w1 > w2)
				trap(EIOVFL);
		}
		else if (i_mins(nbytes) / w2 > w1) {
			trap(EIOVFL);
		}
	}
	return (w1 * w2);
}

PRIVATE long dvi(long w1, long w2, size nbytes)
{
	if (w2 == 0) {
		if (!(IgnMask&BIT(EIDIVZ))) {
			trap(EIDIVZ);
		}
		else	return (0L);
	}

	/* Check for division overflow. */
	if ((w1 == i_mins(nbytes)) && (w2 == -1))
	{
		if (must_test && !(IgnMask&BIT(EIOVFL)))
		{
			trap(EIOVFL);
		} else return i_mins(nbytes);
	}


	return (w1 / w2);
}

PRIVATE long rmi(long w1, long w2)
{
	if (w2 == 0) {
		if (!(IgnMask&BIT(EIDIVZ))) {
			trap(EIDIVZ);
		}
		else	return (0L);
	}
	return (w1 % w2);
}

PRIVATE long ngi(long w1, size nbytes)
{
	if (must_test && !(IgnMask&BIT(EIOVFL))) {
		if (w1 == i_mins(nbytes)) {
			trap(EIOVFL);
		}
	}
	return (-w1);
}

/** "w1" << "w2" */
PRIVATE long sli(long w1, long w2, size nbytes)
{
	if (must_test) {
#ifdef	LOGGING
		/* check shift distance */
		if (w2 < 0)	{
			warning(WSHNEG);
			w2 = 0;
		}
		if (w2 >= nbytes*8)	{
			warning(WSHLARGE);
			w2 = nbytes*8 - 1;
		}
#endif	/* LOGGING */
	
		if (!(IgnMask&BIT(EIOVFL))) {
			/* check overflow */

			/* If the value is positive, then check, this is taken
			 * from rule INT32-C of SEI website.
			 */
			if ((w1 >= 0) && (w1 > (i_maxs(nbytes) >> w2)))
			{
				trap(EIOVFL);
			}

			if ((w1 < 0) && (w1 < (i_mins(nbytes) >> w2)))
			{
				trap(EIOVFL);
			}

		}
	}	

	/* calculate result */
	return (w1 << w2);
}

/*ARGSUSED*/
PRIVATE long sri(long w1, long w2, size nbytes)	/* w1 >> w2 */
{
#ifdef	LOGGING
	if (must_test) {
		/* check shift distance */
		if (w2 < 0)	{
			warning(WSHNEG);
			w2 = 0;
		}
		if (w2 >= nbytes*8)	{
			warning(WSHLARGE);
			w2 = nbytes*8 - 1;
		}
	}
#endif	/* LOGGING */
	
	/* calculate result */
	return (w1 >> w2);
}

