#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fpu_implode.c,v 1.24 2022/09/14 05:55:08 rin Exp $");
#include <sys/types.h>
#include <sys/systm.h>
#include <powerpc/instr.h>
#include <machine/fpu.h>
#include <machine/ieee.h>
#include <machine/reg.h>
#include <powerpc/fpu/fpu_arith.h>
#include <powerpc/fpu/fpu_emu.h>
#include <powerpc/fpu/fpu_extern.h>
static int round(struct fpemu *, struct fpn *, int *);
static int toinf(struct fpemu *, int);
static int round_int(struct fpn *, int *, int, int, int);
static u_int fpu_ftoi(struct fpemu *, struct fpn *, int *, int);
static uint64_t fpu_ftox(struct fpemu *, struct fpn *, int *, int);
static u_int fpu_ftos(struct fpemu *, struct fpn *, int *);
static uint64_t fpu_ftod(struct fpemu *, struct fpn *, int *);
static int
round(struct fpemu *fe, struct fpn *fp, int *cx)
{
u_int m0, m1, m2, m3;
int gr, s;
FPU_DECL_CARRY;
m0 = fp->fp_mant[0];
m1 = fp->fp_mant[1];
m2 = fp->fp_mant[2];
m3 = fp->fp_mant[3];
gr = m3 & 3;
s = fp->fp_sticky;
m3 = (m3 >> FP_NG) | (m2 << (32 - FP_NG));
m2 = (m2 >> FP_NG) | (m1 << (32 - FP_NG));
m1 = (m1 >> FP_NG) | (m0 << (32 - FP_NG));
m0 >>= FP_NG;
if ((gr | s) == 0)
goto rounddown;
*cx |= FPSCR_FI;
switch ((fe->fe_fpscr) & FPSCR_RN) {
case FSR_RD_RN:
default:
if ((gr & 2) == 0)
goto rounddown;
if ((gr & 1) || fp->fp_sticky || (m3 & 1))
break;
goto rounddown;
case FSR_RD_RZ:
goto rounddown;
case FSR_RD_RM:
if (fp->fp_sign)
break;
goto rounddown;
case FSR_RD_RP:
if (!fp->fp_sign)
break;
goto rounddown;
}
*cx |= FPSCR_FR;
FPU_ADDS(m3, m3, 1);
FPU_ADDCS(m2, m2, 0);
FPU_ADDCS(m1, m1, 0);
FPU_ADDC(m0, m0, 0);
fp->fp_mant[0] = m0;
fp->fp_mant[1] = m1;
fp->fp_mant[2] = m2;
fp->fp_mant[3] = m3;
return (1);
rounddown:
fp->fp_mant[0] = m0;
fp->fp_mant[1] = m1;
fp->fp_mant[2] = m2;
fp->fp_mant[3] = m3;
return (0);
}
static int
toinf(struct fpemu *fe, int sign)
{
int inf;
switch ((fe->fe_fpscr) & FPSCR_RN) {
default:
case FSR_RD_RN:
inf = 1;
break;
case FSR_RD_RZ:
inf = 0;
break;
case FSR_RD_RP:
inf = sign == 0;
break;
case FSR_RD_RM:
inf = sign;
break;
}
return (inf);
}
static int
round_int(struct fpn *fp, int *cx, int rn, int sign, int odd)
{
int g, rs;
g = fp->fp_mant[3] & 0x80000000;
rs = (fp->fp_mant[3] & 0x7fffffff) | fp->fp_sticky;
if ((g | rs) == 0)
return 0;
*cx |= FPSCR_FI;
switch (rn) {
case FSR_RD_RN:
if (g && (rs | odd))
break;
return 0;
case FSR_RD_RZ:
return 0;
case FSR_RD_RP:
if (!sign)
break;
return 0;
case FSR_RD_RM:
if (sign)
break;
return 0;
}
*cx |= FPSCR_FR;
return 1;
}
static u_int
fpu_ftoi(struct fpemu *fe, struct fpn *fp, int *cx, int rn)
{
u_int i;
int sign, exp, tmp_cx;
sign = fp->fp_sign;
switch (fp->fp_class) {
case FPC_SNAN:
*cx |= FPSCR_VXSNAN;
case FPC_QNAN:
sign = 1;
break;
case FPC_ZERO:
return (0);
case FPC_NUM:
if ((exp = fp->fp_exp) >= 32)
break;
(void)fpu_shr(fp, FP_NMANT - 32 - 1 - exp);
i = fp->fp_mant[2];
tmp_cx = 0;
i += round_int(fp, &tmp_cx, rn, sign, i & 1);
if (i >= ((u_int)0x80000000 + sign))
break;
*cx |= tmp_cx;
return (sign ? -i : i);
case FPC_INF:
break;
}
*cx |= FPSCR_VXCVI;
return (0x7fffffff + sign);
}
static uint64_t
fpu_ftox(struct fpemu *fe, struct fpn *fp, int *cx, int rn)
{
uint64_t i;
int sign, exp, tmp_cx;
sign = fp->fp_sign;
switch (fp->fp_class) {
case FPC_SNAN:
*cx |= FPSCR_VXSNAN;
case FPC_QNAN:
sign = 1;
break;
case FPC_ZERO:
return (0);
case FPC_NUM:
if ((exp = fp->fp_exp) >= 64)
break;
(void)fpu_shr(fp, FP_NMANT - 32 - 1 - exp);
i = ((uint64_t)fp->fp_mant[1] << 32) | fp->fp_mant[2];
tmp_cx = 0;
i += round_int(fp, &tmp_cx, rn, sign, i & 1);
if (i >= ((uint64_t)0x8000000000000000LL + sign))
break;
*cx |= tmp_cx;
return (sign ? -i : i);
case FPC_INF:
break;
}
*cx |= FPSCR_VXCVI;
return (0x7fffffffffffffffLL + sign);
}
#define FPRF_SIGN(sign) ((sign) ? FPSCR_FL : FPSCR_FG)
static u_int
fpu_ftos(struct fpemu *fe, struct fpn *fp, int *cx)
{
u_int sign = fp->fp_sign << 31;
int exp;
#define SNG_EXP(e) ((e) << SNG_FRACBITS)
#define SNG_MASK (SNG_EXP(1) - 1)
if (ISNAN(fp)) {
*cx |= FPSCR_C | FPSCR_FU;
(void) fpu_shr(fp, FP_NMANT - 1 - SNG_FRACBITS);
exp = SNG_EXP_INFNAN;
goto done;
}
if (ISINF(fp)) {
*cx |= FPRF_SIGN(sign) | FPSCR_FU;
return (sign | SNG_EXP(SNG_EXP_INFNAN));
}
if (ISZERO(fp)) {
*cx |= FPSCR_FE;
if (sign)
*cx |= FPSCR_C;
return (sign);
}
if ((exp = fp->fp_exp + SNG_EXP_BIAS) <= 0) {
(void) fpu_shr(fp, FP_NMANT - FP_NG - SNG_FRACBITS - exp);
if (round(fe, fp, cx) && fp->fp_mant[3] == SNG_EXP(1)) {
*cx |= FPRF_SIGN(sign);
return (sign | SNG_EXP(1) | 0);
}
if (*cx & FPSCR_FI) {
*cx |= FPSCR_UX;
if (fp->fp_mant[3] == 0) {
*cx |= FPSCR_FE;
return sign;
}
}
*cx |= FPSCR_C | FPRF_SIGN(sign);
return (sign | SNG_EXP(0) | fp->fp_mant[3]);
}
(void) fpu_shr(fp, FP_NMANT - FP_NG - 1 - SNG_FRACBITS);
#ifdef DIAGNOSTIC
if ((fp->fp_mant[3] & SNG_EXP(1 << FP_NG)) == 0)
panic("fpu_ftos");
#endif
if (round(fe, fp, cx) && fp->fp_mant[3] == SNG_EXP(2))
exp++;
if (exp >= SNG_EXP_INFNAN) {
*cx |= FPSCR_OX | FPSCR_FI;
if (toinf(fe, sign)) {
*cx |= FPRF_SIGN(sign) | FPSCR_FU;
return (sign | SNG_EXP(SNG_EXP_INFNAN));
}
*cx |= FPRF_SIGN(sign);
return (sign | SNG_EXP(SNG_EXP_INFNAN - 1) | SNG_MASK);
}
*cx |= FPRF_SIGN(sign);
done:
return (sign | SNG_EXP(exp) | (fp->fp_mant[3] & SNG_MASK));
}
static uint64_t
fpu_ftod(struct fpemu *fe, struct fpn *fp, int *cx)
{
u_int sign = fp->fp_sign << 31;
int exp;
#define DBL_EXP(e) ((e) << (DBL_FRACBITS & 31))
#define DBL_MASK (DBL_EXP(1) - 1)
#define HI_WORD(i) ((uint64_t)(i) << 32)
#define LO_WORD(i) ((uint32_t)(i))
if (ISNAN(fp)) {
*cx |= FPSCR_C | FPSCR_FU;
(void) fpu_shr(fp, FP_NMANT - 1 - DBL_FRACBITS);
exp = DBL_EXP_INFNAN;
goto done;
}
if (ISINF(fp)) {
*cx |= FPRF_SIGN(sign) | FPSCR_FU;
return HI_WORD(sign | DBL_EXP(DBL_EXP_INFNAN));
}
if (ISZERO(fp)) {
*cx |= FPSCR_FE;
if (sign)
*cx |= FPSCR_C;
return HI_WORD(sign);
}
if ((exp = fp->fp_exp + DBL_EXP_BIAS) <= 0) {
(void) fpu_shr(fp, FP_NMANT - FP_NG - DBL_FRACBITS - exp);
if (round(fe, fp, cx) && fp->fp_mant[2] == DBL_EXP(1)) {
*cx |= FPRF_SIGN(sign);
return HI_WORD(sign | DBL_EXP(1) | 0);
}
if (*cx & FPSCR_FI) {
*cx |= FPSCR_UX;
if ((fp->fp_mant[2] & DBL_MASK) == 0 &&
fp->fp_mant[3] == 0) {
*cx |= FPSCR_FE;
return HI_WORD(sign);
}
}
*cx |= FPSCR_C | FPRF_SIGN(sign);
exp = 0;
goto done;
}
(void) fpu_shr(fp, FP_NMANT - FP_NG - 1 - DBL_FRACBITS);
if (round(fe, fp, cx) && fp->fp_mant[2] == DBL_EXP(2))
exp++;
if (exp >= DBL_EXP_INFNAN) {
*cx |= FPSCR_OX | FPSCR_FI;
if (toinf(fe, sign)) {
*cx |= FPRF_SIGN(sign) | FPSCR_FU;
return HI_WORD(sign | DBL_EXP(DBL_EXP_INFNAN) | 0);
}
*cx |= FPRF_SIGN(sign);
return HI_WORD(sign | DBL_EXP(DBL_EXP_INFNAN - 1) | DBL_MASK) |
LO_WORD(~0);
}
*cx |= FPRF_SIGN(sign);
done:
return HI_WORD(sign | DBL_EXP(exp) | (fp->fp_mant[2] & DBL_MASK)) |
LO_WORD(fp->fp_mant[3]);
}
void
fpu_implode(struct fpemu *fe, struct fpn *fp, int type, uint64_t *p)
{
u_int *hi, *lo;
int cx, rn;
bool fpscr;
hi = (u_int *)p;
lo = hi + 1;
if (type & FTYPE_RD_RZ)
rn = FSR_RD_RZ;
else
rn = fe->fe_fpscr & FPSCR_RN;
fpscr = type & FTYPE_FPSCR;
type &= ~FTYPE_FLAG_MASK;
cx = 0;
switch (type) {
case FTYPE_LNG:
*p = fpu_ftox(fe, fp, &cx, rn);
DPRINTF(FPE_REG, ("fpu_implode: long %x %x\n", *hi, *lo));
break;
case FTYPE_INT:
*hi = 0;
*lo = fpu_ftoi(fe, fp, &cx, rn);
DPRINTF(FPE_REG, ("fpu_implode: int %x\n", *lo));
break;
case FTYPE_SNG:
*hi = fpu_ftos(fe, fp, &cx);
*lo = 0;
DPRINTF(FPE_REG, ("fpu_implode: single %x\n", *hi));
break;
case FTYPE_DBL:
*p = fpu_ftod(fe, fp, &cx);
DPRINTF(FPE_REG, ("fpu_implode: double %x %x\n", *hi, *lo));
break;
default:
panic("fpu_implode: invalid type %d", type);
}
if (fpscr) {
fe->fe_fpscr &= ~(FPSCR_FR | FPSCR_FI | FPSCR_FPRF);
fe->fe_cx |= cx;
if (cx & FPSCR_FI)
fe->fe_cx |= FPSCR_XX;
}
}