/*
 *  fpu_trig.c
 *
 * Implementation of the FPU "transcendental" functions.
 *
 *
 * Copyright (C) 1992,1993,1994
 *                       W. Metzenthen, 22 Parker St, Ormond, Vic 3163,
 *                       Australia.  E-mail   billm@vaxc.cc.monash.edu.au
 * All rights reserved.
 *
 * This copyright notice covers the redistribution and use of the
 * FPU emulator developed by W. Metzenthen. It covers only its use
 * in the 386BSD, FreeBSD and NetBSD operating systems. Any other
 * use is not permitted under this copyright.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must include information specifying
 *    that source code for the emulator is freely available and include
 *    either:
 *      a) an offer to provide the source code for a nominal distribution
 *         fee, or
 *      b) list at least two alternative methods whereby the source
 *         can be obtained, e.g. a publically accessible bulletin board
 *         and an anonymous ftp site from which the software can be
 *         downloaded.
 * 3. All advertising materials specifically mentioning features or use of
 *    this emulator must acknowledge that it was developed by W. Metzenthen.
 * 4. The name of W. Metzenthen may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * W. METZENTHEN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * The purpose of this copyright, based upon the Berkeley copyright, is to
 * ensure that the covered software remains freely available to everyone.
 *
 * The software (with necessary differences) is also available, but under
 * the terms of the GNU copyleft, for the Linux operating system and for
 * the djgpp ms-dos extender.
 *
 * W. Metzenthen   June 1994.
 *
 *
 *    $Id: fpu_trig.c,v 1.9 1997/06/14 15:11:04 bde Exp $
 *
 */


#include <sys/param.h>
#ifdef DEBUG
#include <sys/systm.h>		/* for printf() in EXCEPTION() */
#endif
#include <sys/proc.h>
#include <machine/pcb.h>

#include <gnu/i386/fpemul/fpu_emu.h>
#include <gnu/i386/fpemul/fpu_system.h>
#include <gnu/i386/fpemul/exception.h>
#include <gnu/i386/fpemul/status_w.h>
#include <gnu/i386/fpemul/reg_constant.h>
#include <gnu/i386/fpemul/control_w.h>

static void convert_l2reg(long *arg, FPU_REG * dest);

static int
trig_arg(FPU_REG * X)
{
	FPU_REG tmp, quot;
	int     rv;
	long long q;
	int     old_cw = control_word;

	control_word &= ~CW_RC;
	control_word |= RC_CHOP;

	reg_move(X, &quot);
	reg_div(&quot, &CONST_PI2, &quot, FULL_PRECISION);

	reg_move(&quot, &tmp);
	round_to_int(&tmp);
	if (tmp.sigh & 0x80000000)
		return -1;	/* |Arg| is >= 2^63 */
	tmp.exp = EXP_BIAS + 63;
	q = *(long long *) &(tmp.sigl);
	normalize(&tmp);

	reg_sub(&quot, &tmp, X, FULL_PRECISION);
	rv = q & 7;

	control_word = old_cw;
	return rv;;
}


/* Convert a long to register */
static void
convert_l2reg(long *arg, FPU_REG * dest)
{
	long    num = *arg;

	if (num == 0) {
		reg_move(&CONST_Z, dest);
		return;
	}
	if (num > 0)
		dest->sign = SIGN_POS;
	else {
		num = -num;
		dest->sign = SIGN_NEG;
	}

	dest->sigh = num;
	dest->sigl = 0;
	dest->exp = EXP_BIAS + 31;
	dest->tag = TW_Valid;
	normalize(dest);
}


static void
single_arg_error(void)
{
	switch (FPU_st0_tag) {
		case TW_NaN:
		if (!(FPU_st0_ptr->sigh & 0x40000000)) {	/* Signaling ? */
			EXCEPTION(EX_Invalid);
			/* Convert to a QNaN */
			FPU_st0_ptr->sigh |= 0x40000000;
		}
		break;		/* return with a NaN in st(0) */
	case TW_Empty:
		stack_underflow();	/* Puts a QNaN in st(0) */
		break;
#ifdef PARANOID
	default:
		EXCEPTION(EX_INTERNAL | 0x0112);
#endif				/* PARANOID */
	}
}


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

static void
f2xm1(void)
{
	switch (FPU_st0_tag) {
		case TW_Valid:
		{
			FPU_REG rv, tmp;

#ifdef DENORM_OPERAND
			if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
				return;
#endif				/* DENORM_OPERAND */

			if (FPU_st0_ptr->sign == SIGN_POS) {
				/* poly_2xm1(x) requires 0 < x < 1. */
				if (poly_2xm1(FPU_st0_ptr, &rv))
					return;	/* error */
				reg_mul(&rv, FPU_st0_ptr, FPU_st0_ptr, FULL_PRECISION);
			} else {
/* **** Should change poly_2xm1() to at least handle numbers near 0 */
				/* poly_2xm1(x) doesn't handle negative
				 * numbers. */
				/* So we compute (poly_2xm1(x+1)-1)/2, for -1
				 * < x < 0 */
				reg_add(FPU_st0_ptr, &CONST_1, &tmp, FULL_PRECISION);
				poly_2xm1(&tmp, &rv);
				reg_mul(&rv, &tmp, &tmp, FULL_PRECISION);
				reg_sub(&tmp, &CONST_1, FPU_st0_ptr, FULL_PRECISION);
				FPU_st0_ptr->exp--;
				if (FPU_st0_ptr->exp <= EXP_UNDER)
					arith_underflow(FPU_st0_ptr);
			}
			return;
		}
	case TW_Zero:
		return;
	case TW_Infinity:
		if (FPU_st0_ptr->sign == SIGN_NEG) {
			/* -infinity gives -1 (p16-10) */
			reg_move(&CONST_1, FPU_st0_ptr);
			FPU_st0_ptr->sign = SIGN_NEG;
		}
		return;
	default:
		single_arg_error();
	}
}

static void
fptan(void)
{
	FPU_REG *st_new_ptr;
	int     q;
	char    arg_sign = FPU_st0_ptr->sign;

	if (STACK_OVERFLOW) {
		stack_overflow();
		return;
	}
	switch (FPU_st0_tag) {
	case TW_Valid:

#ifdef DENORM_OPERAND
		if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		FPU_st0_ptr->sign = SIGN_POS;
		if ((q = trig_arg(FPU_st0_ptr)) != -1) {
			if (q & 1)
				reg_sub(&CONST_1, FPU_st0_ptr, FPU_st0_ptr, FULL_PRECISION);

			poly_tan(FPU_st0_ptr, FPU_st0_ptr);

			FPU_st0_ptr->sign = (q & 1) ^ arg_sign;

			if (FPU_st0_ptr->exp <= EXP_UNDER)
				arith_underflow(FPU_st0_ptr);

			push();
			reg_move(&CONST_1, FPU_st0_ptr);
			setcc(0);
		} else {
			/* Operand is out of range */
			setcc(SW_C2);
			FPU_st0_ptr->sign = arg_sign;	/* restore st(0) */
			return;
		}
		break;
	case TW_Infinity:
		/* Operand is out of range */
		setcc(SW_C2);
		FPU_st0_ptr->sign = arg_sign;	/* restore st(0) */
		return;
	case TW_Zero:
		push();
		reg_move(&CONST_1, FPU_st0_ptr);
		setcc(0);
		break;
	default:
		single_arg_error();
		break;
	}
}


static void
fxtract(void)
{
	FPU_REG *st_new_ptr;
	register FPU_REG *st1_ptr = FPU_st0_ptr;	/* anticipate */

	if (STACK_OVERFLOW) {
		stack_overflow();
		return;
	}
	if (!(FPU_st0_tag ^ TW_Valid)) {
		long    e;

#ifdef DENORM_OPERAND
		if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		push();
		reg_move(st1_ptr, FPU_st0_ptr);
		FPU_st0_ptr->exp = EXP_BIAS;
		e = st1_ptr->exp - EXP_BIAS;
		convert_l2reg(&e, st1_ptr);
		return;
	} else
		if (FPU_st0_tag == TW_Zero) {
			char    sign = FPU_st0_ptr->sign;
			divide_by_zero(SIGN_NEG, FPU_st0_ptr);
			push();
			reg_move(&CONST_Z, FPU_st0_ptr);
			FPU_st0_ptr->sign = sign;
			return;
		} else
			if (FPU_st0_tag == TW_Infinity) {
				char    sign = FPU_st0_ptr->sign;
				FPU_st0_ptr->sign = SIGN_POS;
				push();
				reg_move(&CONST_INF, FPU_st0_ptr);
				FPU_st0_ptr->sign = sign;
				return;
			} else
				if (FPU_st0_tag == TW_NaN) {
					if (!(FPU_st0_ptr->sigh & 0x40000000)) {	/* Signaling ? */
						EXCEPTION(EX_Invalid);
						/* Convert to a QNaN */
						FPU_st0_ptr->sigh |= 0x40000000;
					}
					push();
					reg_move(st1_ptr, FPU_st0_ptr);
					return;
				} else
					if (FPU_st0_tag == TW_Empty) {
						/* Is this the correct
						 * behaviour? */
						if (control_word & EX_Invalid) {
							stack_underflow();
							push();
							stack_underflow();
						} else
							EXCEPTION(EX_StackUnder);
					}
#ifdef PARANOID
					else
						EXCEPTION(EX_INTERNAL | 0x119);
#endif				/* PARANOID */
}


static void
fdecstp(void)
{
	top--;			/* FPU_st0_ptr will be fixed in math_emulate()
				 * before the next instr */
}

static void
fincstp(void)
{
	top++;			/* FPU_st0_ptr will be fixed in math_emulate()
				 * before the next instr */
}


static void
fsqrt_(void)
{
	if (!(FPU_st0_tag ^ TW_Valid)) {
		int     expon;

		if (FPU_st0_ptr->sign == SIGN_NEG) {
			arith_invalid(FPU_st0_ptr);	/* sqrt(negative) is
							 * invalid */
			return;
		}
#ifdef DENORM_OPERAND
		if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		expon = FPU_st0_ptr->exp - EXP_BIAS;
		FPU_st0_ptr->exp = EXP_BIAS + (expon & 1);	/* make st(0) in  [1.0
								 * .. 4.0) */

		wm_sqrt(FPU_st0_ptr, control_word);	/* Do the computation */

		FPU_st0_ptr->exp += expon >> 1;
		FPU_st0_ptr->sign = SIGN_POS;
	} else
		if (FPU_st0_tag == TW_Zero)
			return;
		else
			if (FPU_st0_tag == TW_Infinity) {
				if (FPU_st0_ptr->sign == SIGN_NEG)
					arith_invalid(FPU_st0_ptr);	/* sqrt(-Infinity) is
									 * invalid */
				return;
			} else {
				single_arg_error();
				return;
			}

}


static void
frndint_(void)
{
	if (!(FPU_st0_tag ^ TW_Valid)) {
		if (FPU_st0_ptr->exp > EXP_BIAS + 63)
			return;

#ifdef DENORM_OPERAND
		if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		round_to_int(FPU_st0_ptr);	/* Fortunately, this can't
						 * overflow to 2^64 */
		FPU_st0_ptr->exp = EXP_BIAS + 63;
		normalize(FPU_st0_ptr);
		return;
	} else
		if ((FPU_st0_tag == TW_Zero) || (FPU_st0_tag == TW_Infinity))
			return;
		else
			single_arg_error();
}


static void
fsin(void)
{
	char    arg_sign = FPU_st0_ptr->sign;

	if (FPU_st0_tag == TW_Valid) {
		int     q;
		FPU_st0_ptr->sign = SIGN_POS;

#ifdef DENORM_OPERAND
		if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		if ((q = trig_arg(FPU_st0_ptr)) != -1) {
			FPU_REG rv;

			if (q & 1)
				reg_sub(&CONST_1, FPU_st0_ptr, FPU_st0_ptr, FULL_PRECISION);

			poly_sine(FPU_st0_ptr, &rv);

			setcc(0);
			if (q & 2)
				rv.sign ^= SIGN_POS ^ SIGN_NEG;
			rv.sign ^= arg_sign;
			reg_move(&rv, FPU_st0_ptr);

			if (FPU_st0_ptr->exp <= EXP_UNDER)
				arith_underflow(FPU_st0_ptr);

			set_precision_flag_up();	/* We do not really know
							 * if up or down */

			return;
		} else {
			/* Operand is out of range */
			setcc(SW_C2);
			FPU_st0_ptr->sign = arg_sign;	/* restore st(0) */
			return;
		}
	} else
		if (FPU_st0_tag == TW_Zero) {
			setcc(0);
			return;
		} else
			if (FPU_st0_tag == TW_Infinity) {
				/* Operand is out of range */
				setcc(SW_C2);
				FPU_st0_ptr->sign = arg_sign;	/* restore st(0) */
				return;
			} else
				single_arg_error();
}


static int
f_cos(FPU_REG * arg)
{
	char    arg_sign = arg->sign;

	if (arg->tag == TW_Valid) {
		int     q;

#ifdef DENORM_OPERAND
		if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
			return 1;
#endif				/* DENORM_OPERAND */

		arg->sign = SIGN_POS;
		if ((q = trig_arg(arg)) != -1) {
			FPU_REG rv;

			if (!(q & 1))
				reg_sub(&CONST_1, arg, arg, FULL_PRECISION);

			poly_sine(arg, &rv);

			setcc(0);
			if ((q + 1) & 2)
				rv.sign ^= SIGN_POS ^ SIGN_NEG;
			reg_move(&rv, arg);

			set_precision_flag_up();	/* We do not really know
							 * if up or down */

			return 0;
		} else {
			/* Operand is out of range */
			setcc(SW_C2);
			arg->sign = arg_sign;	/* restore st(0) */
			return 1;
		}
	} else
		if (arg->tag == TW_Zero) {
			reg_move(&CONST_1, arg);
			setcc(0);
			return 0;
		} else
			if (FPU_st0_tag == TW_Infinity) {
				/* Operand is out of range */
				setcc(SW_C2);
				arg->sign = arg_sign;	/* restore st(0) */
				return 1;
			} else {
				single_arg_error();	/* requires arg ==
							 * &st(0) */
				return 1;
			}
}


static void
fcos(void)
{
	f_cos(FPU_st0_ptr);
}


static void
fsincos(void)
{
	FPU_REG *st_new_ptr;
	FPU_REG arg;

	if (STACK_OVERFLOW) {
		stack_overflow();
		return;
	}
	reg_move(FPU_st0_ptr, &arg);
	if (!f_cos(&arg)) {
		fsin();
		push();
		reg_move(&arg, FPU_st0_ptr);
	}
}


/*---------------------------------------------------------------------------*/
/* The following all require two arguments: st(0) and st(1) */

/* remainder of st(0) / st(1) */
/* Assumes that st(0) and st(1) are both TW_Valid */
static void
fprem_kernel(int round)
{
	FPU_REG *st1_ptr = &st(1);
	char    st1_tag = st1_ptr->tag;

	if (!((FPU_st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid))) {
		FPU_REG tmp;
		int     old_cw = control_word;
		int     expdif = FPU_st0_ptr->exp - (st1_ptr)->exp;

#ifdef DENORM_OPERAND
		if (((FPU_st0_ptr->exp <= EXP_UNDER) ||
			(st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		control_word &= ~CW_RC;
		control_word |= round;

		if (expdif < 64) {
			/* This should be the most common case */
			long long q;
			int     c = 0;

			reg_div(FPU_st0_ptr, st1_ptr, &tmp, FULL_PRECISION);

			round_to_int(&tmp);	/* Fortunately, this can't
						 * overflow to 2^64 */
			tmp.exp = EXP_BIAS + 63;
			q = *(long long *) &(tmp.sigl);
			normalize(&tmp);

			reg_mul(st1_ptr, &tmp, &tmp, FULL_PRECISION);
			reg_sub(FPU_st0_ptr, &tmp, FPU_st0_ptr, FULL_PRECISION);

			if (q & 4)
				c |= SW_C3;
			if (q & 2)
				c |= SW_C1;
			if (q & 1)
				c |= SW_C0;

			setcc(c);
		} else {
			/* There is a large exponent difference ( >= 64 ) */
			int     N_exp;

			reg_div(FPU_st0_ptr, st1_ptr, &tmp, FULL_PRECISION);
			/* N is 'a number between 32 and 63' (p26-113) */
			N_exp = (tmp.exp & 31) + 32;
			tmp.exp = EXP_BIAS + N_exp;

			round_to_int(&tmp);	/* Fortunately, this can't
						 * overflow to 2^64 */
			tmp.exp = EXP_BIAS + 63;
			normalize(&tmp);

			tmp.exp = EXP_BIAS + expdif - N_exp;

			reg_mul(st1_ptr, &tmp, &tmp, FULL_PRECISION);
			reg_sub(FPU_st0_ptr, &tmp, FPU_st0_ptr, FULL_PRECISION);

			setcc(SW_C2);
		}
		control_word = old_cw;

		if (FPU_st0_ptr->exp <= EXP_UNDER)
			arith_underflow(FPU_st0_ptr);
		return;
	} else
		if ((FPU_st0_tag == TW_Empty) | (st1_tag == TW_Empty)) {
			stack_underflow();
			return;
		} else
			if (FPU_st0_tag == TW_Zero) {
				if (st1_tag == TW_Valid) {

#ifdef DENORM_OPERAND
					if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
						return;
#endif				/* DENORM_OPERAND */

					setcc(0);
					return;
				} else
					if (st1_tag == TW_Zero) {
						arith_invalid(FPU_st0_ptr);
						return;
					}
				/* fprem(?,0) always invalid */
					else
						if (st1_tag == TW_Infinity) {
							setcc(0);
							return;
						}
			} else
				if (FPU_st0_tag == TW_Valid) {
					if (st1_tag == TW_Zero) {
						arith_invalid(FPU_st0_ptr);	/* fprem(Valid,Zero) is
										 * invalid */
						return;
					} else
						if (st1_tag != TW_NaN) {
#ifdef DENORM_OPERAND
							if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */

							if (st1_tag == TW_Infinity) {
								/* fprem(Valid,
								 * Infinity)
								 * is o.k. */
								setcc(0);
								return;
							}
						}
				} else
					if (FPU_st0_tag == TW_Infinity) {
						if (st1_tag != TW_NaN) {
							arith_invalid(FPU_st0_ptr);	/* fprem(Infinity,?) is
											 * invalid */
							return;
						}
					}
	/* One of the registers must contain a NaN is we got here. */

#ifdef PARANOID
	if ((FPU_st0_tag != TW_NaN) && (st1_tag != TW_NaN))
		EXCEPTION(EX_INTERNAL | 0x118);
#endif				/* PARANOID */

	real_2op_NaN(FPU_st0_ptr, st1_ptr, FPU_st0_ptr);

}


/* ST(1) <- ST(1) * log ST;  pop ST */
static void
fyl2x(void)
{
	FPU_REG *st1_ptr = &st(1);
	char    st1_tag = st1_ptr->tag;

	if (!((FPU_st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid))) {
		if (FPU_st0_ptr->sign == SIGN_POS) {
			int     saved_control, saved_status;

#ifdef DENORM_OPERAND
			if (((FPU_st0_ptr->exp <= EXP_UNDER) ||
				(st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()))
				return;
#endif				/* DENORM_OPERAND */

			/* We use the general purpose arithmetic, so we need
			 * to save these. */
			saved_status = status_word;
			saved_control = control_word;
			control_word = FULL_PRECISION;

			poly_l2(FPU_st0_ptr, FPU_st0_ptr);

			/* Enough of the basic arithmetic is done now */
			control_word = saved_control;
			status_word = saved_status;

			/* Let the multiply set the flags */
			reg_mul(FPU_st0_ptr, st1_ptr, st1_ptr, FULL_PRECISION);

			pop();
			FPU_st0_ptr = &st(0);
		} else {
			/* negative	 */
			pop();
			FPU_st0_ptr = &st(0);
			arith_invalid(FPU_st0_ptr);	/* st(0) cannot be
							 * negative */
			return;
		}
	} else
		if ((FPU_st0_tag == TW_Empty) || (st1_tag == TW_Empty)) {
			stack_underflow_pop(1);
			return;
		} else
			if ((FPU_st0_tag == TW_NaN) || (st1_tag == TW_NaN)) {
				real_2op_NaN(FPU_st0_ptr, st1_ptr, st1_ptr);
				pop();
				return;
			} else
				if ((FPU_st0_tag <= TW_Zero) && (st1_tag <= TW_Zero)) {
					/* one of the args is zero, the other
					 * valid, or both zero */
					if (FPU_st0_tag == TW_Zero) {
						pop();
						FPU_st0_ptr = &st(0);
						if (FPU_st0_ptr->tag == TW_Zero)
							arith_invalid(FPU_st0_ptr);	/* Both args zero is
											 * invalid */
#ifdef PECULIAR_486
						/* This case is not
						 * specifically covered in the
						 * manual, but divide-by-zero
						 * would seem to be the best
						 * response. However, a real
						 * 80486 does it this way... */
						else
							if (FPU_st0_ptr->tag == TW_Infinity) {
								reg_move(&CONST_INF, FPU_st0_ptr);
								return;
							}
#endif				/* PECULIAR_486 */
							else
								divide_by_zero(st1_ptr->sign ^ SIGN_NEG ^ SIGN_POS, FPU_st0_ptr);
						return;
					} else {
						/* st(1) contains zero, st(0)
						 * valid <> 0 */
						/* Zero is the valid answer */
						char    sign = st1_ptr->sign;

#ifdef DENORM_OPERAND
						if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
							return;
#endif				/* DENORM_OPERAND */
						if (FPU_st0_ptr->sign == SIGN_NEG) {
							pop();
							FPU_st0_ptr = &st(0);
							arith_invalid(FPU_st0_ptr);	/* log(negative) */
							return;
						}
						if (FPU_st0_ptr->exp < EXP_BIAS)
							sign ^= SIGN_NEG ^ SIGN_POS;
						pop();
						FPU_st0_ptr = &st(0);
						reg_move(&CONST_Z, FPU_st0_ptr);
						FPU_st0_ptr->sign = sign;
						return;
					}
				}
	/* One or both arg must be an infinity */
				else
					if (FPU_st0_tag == TW_Infinity) {
						if ((FPU_st0_ptr->sign == SIGN_NEG) || (st1_tag == TW_Zero)) {
							pop();
							FPU_st0_ptr = &st(0);
							arith_invalid(FPU_st0_ptr);	/* log(-infinity) or
											 * 0*log(infinity) */
							return;
						} else {
							char    sign = st1_ptr->sign;

#ifdef DENORM_OPERAND
							if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */

							pop();
							FPU_st0_ptr = &st(0);
							reg_move(&CONST_INF, FPU_st0_ptr);
							FPU_st0_ptr->sign = sign;
							return;
						}
					}
	/* st(1) must be infinity here */
					else
						if ((FPU_st0_tag == TW_Valid) && (FPU_st0_ptr->sign == SIGN_POS)) {
							if (FPU_st0_ptr->exp >= EXP_BIAS) {
								if ((FPU_st0_ptr->exp == EXP_BIAS) &&
								    (FPU_st0_ptr->sigh == 0x80000000) &&
								    (FPU_st0_ptr->sigl == 0)) {
									/* st(0
									 * )
									 * hold
									 * s
									 * 1.0 */
									pop();
									FPU_st0_ptr = &st(0);
									arith_invalid(FPU_st0_ptr);	/* infinity*log(1) */
									return;
								}
								/* st(0) is
								 * positive
								 * and > 1.0 */
								pop();
							} else {
								/* st(0) is
								 * positive
								 * and < 1.0 */

#ifdef DENORM_OPERAND
								if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
									return;
#endif				/* DENORM_OPERAND */

								st1_ptr->sign ^= SIGN_NEG;
								pop();
							}
							return;
						} else {
							/* st(0) must be zero
							 * or negative */
							if (FPU_st0_ptr->tag == TW_Zero) {
								pop();
								FPU_st0_ptr = st1_ptr;
								st1_ptr->sign ^= SIGN_NEG ^ SIGN_POS;
								/* This should
								 * be invalid,
								 * but a real
								 * 80486 is
								 * happy with
								 * it. */
#ifndef PECULIAR_486
								divide_by_zero(st1_ptr->sign, FPU_st0_ptr);
#endif				/* PECULIAR_486 */
							} else {
								pop();
								FPU_st0_ptr = st1_ptr;
								arith_invalid(FPU_st0_ptr);	/* log(negative) */
							}
							return;
						}
}


static void
fpatan(void)
{
	FPU_REG *st1_ptr = &st(1);
	char    st1_tag = st1_ptr->tag;

	if (!((FPU_st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid))) {
		int     saved_control, saved_status;
		FPU_REG sum;
		int     quadrant = st1_ptr->sign | ((FPU_st0_ptr->sign) << 1);

#ifdef DENORM_OPERAND
		if (((FPU_st0_ptr->exp <= EXP_UNDER) ||
			(st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		/* We use the general purpose arithmetic so we need to save
		 * these. */
		saved_status = status_word;
		saved_control = control_word;
		control_word = FULL_PRECISION;

		st1_ptr->sign = FPU_st0_ptr->sign = SIGN_POS;
		if (compare(st1_ptr) == COMP_A_lt_B) {
			quadrant |= 4;
			reg_div(FPU_st0_ptr, st1_ptr, &sum, FULL_PRECISION);
		} else
			reg_div(st1_ptr, FPU_st0_ptr, &sum, FULL_PRECISION);

		poly_atan(&sum);

		if (quadrant & 4) {
			reg_sub(&CONST_PI2, &sum, &sum, FULL_PRECISION);
		}
		if (quadrant & 2) {
			reg_sub(&CONST_PI, &sum, &sum, FULL_PRECISION);
		}
		if (quadrant & 1)
			sum.sign ^= SIGN_POS ^ SIGN_NEG;

		/* All of the basic arithmetic is done now */
		control_word = saved_control;
		status_word = saved_status;

		reg_move(&sum, st1_ptr);
	} else
		if ((FPU_st0_tag == TW_Empty) || (st1_tag == TW_Empty)) {
			stack_underflow_pop(1);
			return;
		} else
			if ((FPU_st0_tag == TW_NaN) || (st1_tag == TW_NaN)) {
				real_2op_NaN(FPU_st0_ptr, st1_ptr, st1_ptr);
				pop();
				return;
			} else
				if ((FPU_st0_tag == TW_Infinity) || (st1_tag == TW_Infinity)) {
					char    sign = st1_ptr->sign;
					if (FPU_st0_tag == TW_Infinity) {
						if (st1_tag == TW_Infinity) {
							if (FPU_st0_ptr->sign == SIGN_POS) {
								reg_move(&CONST_PI4, st1_ptr);
							} else
								reg_add(&CONST_PI4, &CONST_PI2, st1_ptr, FULL_PRECISION);
						} else {

#ifdef DENORM_OPERAND
							if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */

							if (FPU_st0_ptr->sign == SIGN_POS) {
								reg_move(&CONST_Z, st1_ptr);
								pop();
								return;
							} else
								reg_move(&CONST_PI, st1_ptr);
						}
					} else {
						/* st(1) is infinity, st(0)
						 * not infinity */
#ifdef DENORM_OPERAND
						if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
							return;
#endif				/* DENORM_OPERAND */

						reg_move(&CONST_PI2, st1_ptr);
					}
					st1_ptr->sign = sign;
				} else
					if (st1_tag == TW_Zero) {
						/* st(0) must be valid or zero */
						char    sign = st1_ptr->sign;

#ifdef DENORM_OPERAND
						if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
							return;
#endif				/* DENORM_OPERAND */

						if (FPU_st0_ptr->sign == SIGN_POS) {
							reg_move(&CONST_Z, st1_ptr);
							pop();
							return;
						} else
							reg_move(&CONST_PI, st1_ptr);
						st1_ptr->sign = sign;
					} else
						if (FPU_st0_tag == TW_Zero) {
							/* st(1) must be
							 * TW_Valid here */
							char    sign = st1_ptr->sign;

#ifdef DENORM_OPERAND
							if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */

							reg_move(&CONST_PI2, st1_ptr);
							st1_ptr->sign = sign;
						}
#ifdef PARANOID
						else
							EXCEPTION(EX_INTERNAL | 0x220);
#endif				/* PARANOID */

	pop();
	set_precision_flag_up();/* We do not really know if up or down */
}


static void
fprem(void)
{
	fprem_kernel(RC_CHOP);
}


static void
fprem1(void)
{
	fprem_kernel(RC_RND);
}


static void
fyl2xp1(void)
{
	FPU_REG *st1_ptr = &st(1);
	char    st1_tag = st1_ptr->tag;

	if (!((FPU_st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid))) {
		int     saved_control, saved_status;

#ifdef DENORM_OPERAND
		if (((FPU_st0_ptr->exp <= EXP_UNDER) ||
			(st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		/* We use the general purpose arithmetic so we need to save
		 * these. */
		saved_status = status_word;
		saved_control = control_word;
		control_word = FULL_PRECISION;

		if (poly_l2p1(FPU_st0_ptr, FPU_st0_ptr)) {
			arith_invalid(st1_ptr);	/* poly_l2p1() returned
						 * invalid */
			pop();
			return;
		}
		/* Enough of the basic arithmetic is done now */
		control_word = saved_control;
		status_word = saved_status;

		/* Let the multiply set the flags */
		reg_mul(FPU_st0_ptr, st1_ptr, st1_ptr, FULL_PRECISION);

		pop();
	} else
		if ((FPU_st0_tag == TW_Empty) | (st1_tag == TW_Empty)) {
			stack_underflow_pop(1);
			return;
		} else
			if (FPU_st0_tag == TW_Zero) {
				if (st1_tag <= TW_Zero) {

#ifdef DENORM_OPERAND
					if ((st1_tag == TW_Valid) && (st1_ptr->exp <= EXP_UNDER) &&
					    (denormal_operand()))
						return;
#endif				/* DENORM_OPERAND */

					st1_ptr->sign ^= FPU_st0_ptr->sign;
					reg_move(FPU_st0_ptr, st1_ptr);
				} else
					if (st1_tag == TW_Infinity) {
						arith_invalid(st1_ptr);	/* Infinity*log(1) */
						pop();
						return;
					} else
						if (st1_tag == TW_NaN) {
							real_2op_NaN(FPU_st0_ptr, st1_ptr, st1_ptr);
							pop();
							return;
						}
#ifdef PARANOID
						else {
							EXCEPTION(EX_INTERNAL | 0x116);
							return;
						}
#endif				/* PARANOID */
				pop();
				return;
			} else
				if (FPU_st0_tag == TW_Valid) {
					if (st1_tag == TW_Zero) {
						if (FPU_st0_ptr->sign == SIGN_NEG) {
							if (FPU_st0_ptr->exp >= EXP_BIAS) {
								/* st(0) holds
								 * <= -1.0 */
								arith_invalid(st1_ptr);	/* infinity*log(1) */
								pop();
								return;
							}
#ifdef DENORM_OPERAND
							if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */
							st1_ptr->sign ^= SIGN_POS ^ SIGN_NEG;
							pop();
							return;
						}
#ifdef DENORM_OPERAND
						if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
							return;
#endif				/* DENORM_OPERAND */
						pop();
						return;
					}
					if (st1_tag == TW_Infinity) {
						if (FPU_st0_ptr->sign == SIGN_NEG) {
							if ((FPU_st0_ptr->exp >= EXP_BIAS) &&
							    !((FPU_st0_ptr->sigh == 0x80000000) &&
								(FPU_st0_ptr->sigl == 0))) {
								/* st(0) holds
								 * < -1.0 */
								arith_invalid(st1_ptr);
								pop();
								return;
							}
#ifdef DENORM_OPERAND
							if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */
							st1_ptr->sign ^= SIGN_POS ^ SIGN_NEG;
							pop();
							return;
						}
#ifdef DENORM_OPERAND
						if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
							return;
#endif				/* DENORM_OPERAND */
						pop();
						return;
					}
					if (st1_tag == TW_NaN) {
						real_2op_NaN(FPU_st0_ptr, st1_ptr, st1_ptr);
						pop();
						return;
					}
				} else
					if (FPU_st0_tag == TW_NaN) {
						real_2op_NaN(FPU_st0_ptr, st1_ptr, st1_ptr);
						pop();
						return;
					} else
						if (FPU_st0_tag == TW_Infinity) {
							if (st1_tag == TW_NaN) {
								real_2op_NaN(FPU_st0_ptr, st1_ptr, st1_ptr);
								pop();
								return;
							} else
								if ((FPU_st0_ptr->sign == SIGN_NEG) ||
								    (st1_tag == TW_Zero)) {
									arith_invalid(st1_ptr);	/* log(infinity) */
									pop();
									return;
								}
							/* st(1) must be valid
							 * here. */

#ifdef DENORM_OPERAND
							if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
								return;
#endif				/* DENORM_OPERAND */

							/* The Manual says
							 * that log(Infinity)
							 * is invalid, but a
							 * real 80486 sensibly
							 * says that it is
							 * o.k. */
							{
								char    sign = st1_ptr->sign;
								reg_move(&CONST_INF, st1_ptr);
								st1_ptr->sign = sign;
							}
							pop();
							return;
						}
#ifdef PARANOID
						else {
							EXCEPTION(EX_INTERNAL | 0x117);
						}
#endif				/* PARANOID */
}


static void
emu_fscale(void)
{
	FPU_REG *st1_ptr = &st(1);
	char    st1_tag = st1_ptr->tag;
	int     old_cw = control_word;

	if (!((FPU_st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid))) {
		long    scale;
		FPU_REG tmp;

#ifdef DENORM_OPERAND
		if (((FPU_st0_ptr->exp <= EXP_UNDER) ||
			(st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()))
			return;
#endif				/* DENORM_OPERAND */

		if (st1_ptr->exp > EXP_BIAS + 30) {
			/* 2^31 is far too large, would require 2^(2^30) or
			 * 2^(-2^30) */
			char    sign;

			if (st1_ptr->sign == SIGN_POS) {
				EXCEPTION(EX_Overflow);
				sign = FPU_st0_ptr->sign;
				reg_move(&CONST_INF, FPU_st0_ptr);
				FPU_st0_ptr->sign = sign;
			} else {
				EXCEPTION(EX_Underflow);
				sign = FPU_st0_ptr->sign;
				reg_move(&CONST_Z, FPU_st0_ptr);
				FPU_st0_ptr->sign = sign;
			}
			return;
		}
		control_word &= ~CW_RC;
		control_word |= RC_CHOP;
		reg_move(st1_ptr, &tmp);
		round_to_int(&tmp);	/* This can never overflow here */
		control_word = old_cw;
		scale = st1_ptr->sign ? -tmp.sigl : tmp.sigl;
		scale += FPU_st0_ptr->exp;
		FPU_st0_ptr->exp = scale;

		/* Use round_reg() to properly detect under/overflow etc */
		round_reg(FPU_st0_ptr, 0, control_word);

		return;
	} else
		if (FPU_st0_tag == TW_Valid) {
			if (st1_tag == TW_Zero) {

#ifdef DENORM_OPERAND
				if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
					return;
#endif				/* DENORM_OPERAND */

				return;
			}
			if (st1_tag == TW_Infinity) {
				char    sign = st1_ptr->sign;

#ifdef DENORM_OPERAND
				if ((FPU_st0_ptr->exp <= EXP_UNDER) && (denormal_operand()))
					return;
#endif				/* DENORM_OPERAND */

				if (sign == SIGN_POS) {
					reg_move(&CONST_INF, FPU_st0_ptr);
				} else
					reg_move(&CONST_Z, FPU_st0_ptr);
				FPU_st0_ptr->sign = sign;
				return;
			}
			if (st1_tag == TW_NaN) {
				real_2op_NaN(FPU_st0_ptr, st1_ptr, FPU_st0_ptr);
				return;
			}
		} else
			if (FPU_st0_tag == TW_Zero) {
				if (st1_tag == TW_Valid) {

#ifdef DENORM_OPERAND
					if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
						return;
#endif				/* DENORM_OPERAND */

					return;
				} else
					if (st1_tag == TW_Zero) {
						return;
					} else
						if (st1_tag == TW_Infinity) {
							if (st1_ptr->sign == SIGN_NEG)
								return;
							else {
								arith_invalid(FPU_st0_ptr);	/* Zero scaled by
												 * +Infinity */
								return;
							}
						} else
							if (st1_tag == TW_NaN) {
								real_2op_NaN(FPU_st0_ptr, st1_ptr, FPU_st0_ptr);
								return;
							}
			} else
				if (FPU_st0_tag == TW_Infinity) {
					if (st1_tag == TW_Valid) {

#ifdef DENORM_OPERAND
						if ((st1_ptr->exp <= EXP_UNDER) && (denormal_operand()))
							return;
#endif				/* DENORM_OPERAND */

						return;
					}
					if (((st1_tag == TW_Infinity) && (st1_ptr->sign == SIGN_POS))
					    || (st1_tag == TW_Zero))
						return;
					else
						if (st1_tag == TW_Infinity) {
							arith_invalid(FPU_st0_ptr);	/* Infinity scaled by
											 * -Infinity */
							return;
						} else
							if (st1_tag == TW_NaN) {
								real_2op_NaN(FPU_st0_ptr, st1_ptr, FPU_st0_ptr);
								return;
							}
				} else
					if (FPU_st0_tag == TW_NaN) {
						if (st1_tag != TW_Empty) {
							real_2op_NaN(FPU_st0_ptr, st1_ptr, FPU_st0_ptr);
							return;
						}
					}
#ifdef PARANOID
	if (!((FPU_st0_tag == TW_Empty) || (st1_tag == TW_Empty))) {
		EXCEPTION(EX_INTERNAL | 0x115);
		return;
	}
#endif

	/* At least one of st(0), st(1) must be empty */
	stack_underflow();

}


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

static FUNC trig_table_a[] = {
	f2xm1, fyl2x, fptan, fpatan, fxtract, fprem1, fdecstp, fincstp
};

void
trig_a(void)
{
	(trig_table_a[FPU_rm]) ();
}


static FUNC trig_table_b[] =
{
	fprem, fyl2xp1, fsqrt_, fsincos, frndint_, emu_fscale, fsin, fcos
};

void
trig_b(void)
{
	(trig_table_b[FPU_rm]) ();
}
