HardenedBSD/sys/gnu/i386/fpemul/fpu_trig.c
Rich Murphey b95c0fbacd Copyright changes per the author.
Added specific permissions for redistribution with FreeBSD and NetBSD.
Fixed author's email address.
1994-06-10 07:45:04 +00:00

1368 lines
32 KiB
C

/*
* 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.3 1994/04/29 21:16:25 gclarkii Exp $
*
*/
#include "param.h"
#include "proc.h"
#include "machine/cpu.h"
#include "machine/pcb.h"
#include "fpu_emu.h"
#include "fpu_system.h"
#include "exception.h"
#include "status_w.h"
#include "reg_constant.h"
#include "control_w.h"
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 */
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]) ();
}