/*---------------------------------------------------------------------------+
 |  reg_compare.c                                                            |
 |                                                                           |
 | Compare two floating point registers                                      |
 |                                                                           |
 | Copyright (C) 1992,1993,1994,1997                                         |
 |                  W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia |
 |                  E-mail   billm@suburbia.net                              |
 |                                                                           |
 |                                                                           |
 +---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------+
 | compare() is the core FPU_REG comparison function                         |
 +---------------------------------------------------------------------------*/

#include "fpu_system.h"
#include "exception.h"
#include "fpu_emu.h"
#include "control_w.h"
#include "status_w.h"

static int compare(FPU_REG const *b, int tagb)
{
	int diff, exp0, expb;
	u_char st0_tag;
	FPU_REG *st0_ptr;
	FPU_REG x, y;
	u_char st0_sign, signb = getsign(b);

	st0_ptr = &st(0);
	st0_tag = FPU_gettag0();
	st0_sign = getsign(st0_ptr);

	if (tagb == TAG_Special)
		tagb = FPU_Special(b);
	if (st0_tag == TAG_Special)
		st0_tag = FPU_Special(st0_ptr);

	if (((st0_tag != TAG_Valid) && (st0_tag != TW_Denormal))
	    || ((tagb != TAG_Valid) && (tagb != TW_Denormal))) {
		if (st0_tag == TAG_Zero) {
			if (tagb == TAG_Zero)
				return COMP_A_eq_B;
			if (tagb == TAG_Valid)
				return ((signb ==
					 SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B);
			if (tagb == TW_Denormal)
				return ((signb ==
					 SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B)
				    | COMP_Denormal;
		} else if (tagb == TAG_Zero) {
			if (st0_tag == TAG_Valid)
				return ((st0_sign ==
					 SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B);
			if (st0_tag == TW_Denormal)
				return ((st0_sign ==
					 SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B)
				    | COMP_Denormal;
		}

		if (st0_tag == TW_Infinity) {
			if ((tagb == TAG_Valid) || (tagb == TAG_Zero))
				return ((st0_sign ==
					 SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B);
			else if (tagb == TW_Denormal)
				return ((st0_sign ==
					 SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B)
				    | COMP_Denormal;
			else if (tagb == TW_Infinity) {
				/* The 80486 book says that infinities can be equal! */
				return (st0_sign == signb) ? COMP_A_eq_B :
				    ((st0_sign ==
				      SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B);
			}
			/* Fall through to the NaN code */
		} else if (tagb == TW_Infinity) {
			if ((st0_tag == TAG_Valid) || (st0_tag == TAG_Zero))
				return ((signb ==
					 SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B);
			if (st0_tag == TW_Denormal)
				return ((signb ==
					 SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B)
				    | COMP_Denormal;
			/* Fall through to the NaN code */
		}

		/* The only possibility now should be that one of the arguments
		   is a NaN */
		if ((st0_tag == TW_NaN) || (tagb == TW_NaN)) {
			int signalling = 0, unsupported = 0;
			if (st0_tag == TW_NaN) {
				signalling =
				    (st0_ptr->sigh & 0xc0000000) == 0x80000000;
				unsupported = !((exponent(st0_ptr) == EXP_OVER)
						&& (st0_ptr->
						    sigh & 0x80000000));
			}
			if (tagb == TW_NaN) {
				signalling |=
				    (b->sigh & 0xc0000000) == 0x80000000;
				unsupported |= !((exponent(b) == EXP_OVER)
						 && (b->sigh & 0x80000000));
			}
			if (signalling || unsupported)
				return COMP_No_Comp | COMP_SNaN | COMP_NaN;
			else
				/* Neither is a signaling NaN */
				return COMP_No_Comp | COMP_NaN;
		}

		EXCEPTION(EX_Invalid);
	}

	if (st0_sign != signb) {
		return ((st0_sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B)
		    | (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ?
		       COMP_Denormal : 0);
	}

	if ((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) {
		FPU_to_exp16(st0_ptr, &x);
		FPU_to_exp16(b, &y);
		st0_ptr = &x;
		b = &y;
		exp0 = exponent16(st0_ptr);
		expb = exponent16(b);
	} else {
		exp0 = exponent(st0_ptr);
		expb = exponent(b);
	}

#ifdef PARANOID
	if (!(st0_ptr->sigh & 0x80000000))
		EXCEPTION(EX_Invalid);
	if (!(b->sigh & 0x80000000))
		EXCEPTION(EX_Invalid);
#endif /* PARANOID */

	diff = exp0 - expb;
	if (diff == 0) {
		diff = st0_ptr->sigh - b->sigh;	/* Works only if ms bits are
						   identical */
		if (diff == 0) {
			diff = st0_ptr->sigl > b->sigl;
			if (diff == 0)
				diff = -(st0_ptr->sigl < b->sigl);
		}
	}

	if (diff > 0) {
		return ((st0_sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B)
		    | (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ?
		       COMP_Denormal : 0);
	}
	if (diff < 0) {
		return ((st0_sign == SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B)
		    | (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ?
		       COMP_Denormal : 0);
	}

	return COMP_A_eq_B
	    | (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ?
	       COMP_Denormal : 0);

}

/* This function requires that st(0) is not empty */
int FPU_compare_st_data(FPU_REG const *loaded_data, u_char loaded_tag)
{
	int f = 0, c;

	c = compare(loaded_data, loaded_tag);

	if (c & COMP_NaN) {
		EXCEPTION(EX_Invalid);
		f = SW_C3 | SW_C2 | SW_C0;
	} else
		switch (c & 7) {
		case COMP_A_lt_B:
			f = SW_C0;
			break;
		case COMP_A_eq_B:
			f = SW_C3;
			break;
		case COMP_A_gt_B:
			f = 0;
			break;
		case COMP_No_Comp:
			f = SW_C3 | SW_C2 | SW_C0;
			break;
#ifdef PARANOID
		default:
			EXCEPTION(EX_INTERNAL | 0x121);
			f = SW_C3 | SW_C2 | SW_C0;
			break;
#endif /* PARANOID */
		}
	setcc(f);
	if (c & COMP_Denormal) {
		return denormal_operand() < 0;
	}
	return 0;
}

static int compare_st_st(int nr)
{
	int f = 0, c;
	FPU_REG *st_ptr;

	if (!NOT_EMPTY(0) || !NOT_EMPTY(nr)) {
		setcc(SW_C3 | SW_C2 | SW_C0);
		/* Stack fault */
		EXCEPTION(EX_StackUnder);
		return !(control_word & CW_Invalid);
	}

	st_ptr = &st(nr);
	c = compare(st_ptr, FPU_gettagi(nr));
	if (c & COMP_NaN) {
		setcc(SW_C3 | SW_C2 | SW_C0);
		EXCEPTION(EX_Invalid);
		return !(control_word & CW_Invalid);
	} else
		switch (c & 7) {
		case COMP_A_lt_B:
			f = SW_C0;
			break;
		case COMP_A_eq_B:
			f = SW_C3;
			break;
		case COMP_A_gt_B:
			f = 0;
			break;
		case COMP_No_Comp:
			f = SW_C3 | SW_C2 | SW_C0;
			break;
#ifdef PARANOID
		default:
			EXCEPTION(EX_INTERNAL | 0x122);
			f = SW_C3 | SW_C2 | SW_C0;
			break;
#endif /* PARANOID */
		}
	setcc(f);
	if (c & COMP_Denormal) {
		return denormal_operand() < 0;
	}
	return 0;
}

static int compare_u_st_st(int nr)
{
	int f = 0, c;
	FPU_REG *st_ptr;

	if (!NOT_EMPTY(0) || !NOT_EMPTY(nr)) {
		setcc(SW_C3 | SW_C2 | SW_C0);
		/* Stack fault */
		EXCEPTION(EX_StackUnder);
		return !(control_word & CW_Invalid);
	}

	st_ptr = &st(nr);
	c = compare(st_ptr, FPU_gettagi(nr));
	if (c & COMP_NaN) {
		setcc(SW_C3 | SW_C2 | SW_C0);
		if (c & COMP_SNaN) {	/* This is the only difference between
					   un-ordered and ordinary comparisons */
			EXCEPTION(EX_Invalid);
			return !(control_word & CW_Invalid);
		}
		return 0;
	} else
		switch (c & 7) {
		case COMP_A_lt_B:
			f = SW_C0;
			break;
		case COMP_A_eq_B:
			f = SW_C3;
			break;
		case COMP_A_gt_B:
			f = 0;
			break;
		case COMP_No_Comp:
			f = SW_C3 | SW_C2 | SW_C0;
			break;
#ifdef PARANOID
		default:
			EXCEPTION(EX_INTERNAL | 0x123);
			f = SW_C3 | SW_C2 | SW_C0;
			break;
#endif /* PARANOID */
		}
	setcc(f);
	if (c & COMP_Denormal) {
		return denormal_operand() < 0;
	}
	return 0;
}

/*---------------------------------------------------------------------------*/

void fcom_st(void)
{
	/* fcom st(i) */
	compare_st_st(FPU_rm);
}

void fcompst(void)
{
	/* fcomp st(i) */
	if (!compare_st_st(FPU_rm))
		FPU_pop();
}

void fcompp(void)
{
	/* fcompp */
	if (FPU_rm != 1) {
		FPU_illegal();
		return;
	}
	if (!compare_st_st(1))
		poppop();
}

void fucom_(void)
{
	/* fucom st(i) */
	compare_u_st_st(FPU_rm);

}

void fucomp(void)
{
	/* fucomp st(i) */
	if (!compare_u_st_st(FPU_rm))
		FPU_pop();
}

void fucompp(void)
{
	/* fucompp */
	if (FPU_rm == 1) {
		if (!compare_u_st_st(1))
			poppop();
	} else
		FPU_illegal();
}
