macemu/BasiliskII/src/uae_cpu_2021/fpu/fpu_mpfr.cpp

2117 lines
48 KiB
C++

/*
* fpu_mpfr.cpp - emulate 68881/68040 fpu with mpfr
*
* Copyright (c) 2012, 2013 Andreas Schwab
*
* ARAnyM is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* ARAnyM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ARAnyM; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "sysdeps.h"
#ifdef FPU_MPFR
#include <cstdio>
#include <cstdint>
#include "memory.h"
#include "readcpu.h"
#include "newcpu.h"
#include "main.h"
#define FPU_IMPLEMENTATION
#include "fpu/fpu.h"
#include "fpu/flags.h"
#include "fpu/exceptions.h"
#include "fpu/rounding.h"
#include "fpu/impl.h"
#define SINGLE_PREC 24
#define SINGLE_MIN_EXP -126
#define SINGLE_MAX_EXP 127
#define SINGLE_BIAS 127
#define DOUBLE_PREC 53
#define DOUBLE_MIN_EXP -1022
#define DOUBLE_MAX_EXP 1023
#define DOUBLE_BIAS 1023
#define EXTENDED_PREC 64
#define EXTENDED_MIN_EXP -16383
#define EXTENDED_MAX_EXP 16383
#define EXTENDED_BIAS 16383
fpu_t fpu;
// The constant ROM
// Constants 48 to 63 are mapped to index 16 to 31
const int num_fpu_constants = 32;
static mpfr_t fpu_constant_rom[num_fpu_constants];
#define FPU_CONSTANT_ONE fpu_constant_rom[18]
// Exceptions generated during execution in addition to the ones
// maintained by mpfr
static uae_u32 cur_exceptions;
static uaecptr cur_instruction_address;
static void
set_format (int prec)
{
// MPFR represents numbers as 0.m*2^e
switch (prec)
{
case SINGLE_PREC:
mpfr_set_emin (SINGLE_MIN_EXP + 1 - (SINGLE_PREC - 1));
mpfr_set_emax (SINGLE_MAX_EXP + 1);
break;
case DOUBLE_PREC:
mpfr_set_emin (DOUBLE_MIN_EXP + 1 - (DOUBLE_PREC - 1));
mpfr_set_emax (DOUBLE_MAX_EXP + 1);
break;
case EXTENDED_PREC:
mpfr_set_emin (EXTENDED_MIN_EXP + 1 - (EXTENDED_PREC - 1));
mpfr_set_emax (EXTENDED_MAX_EXP + 1);
break;
}
}
static mpfr_rnd_t
get_cur_rnd ()
{
switch (get_rounding_mode ())
{
default:
case FPCR_ROUND_NEAR:
return MPFR_RNDN;
case FPCR_ROUND_ZERO:
return MPFR_RNDZ;
case FPCR_ROUND_MINF:
return MPFR_RNDD;
case FPCR_ROUND_PINF:
return MPFR_RNDU;
}
}
static mpfr_prec_t
get_cur_prec ()
{
switch (get_rounding_precision ())
{
default:
case FPCR_PRECISION_EXTENDED:
return EXTENDED_PREC;
case FPCR_PRECISION_SINGLE:
return SINGLE_PREC;
case FPCR_PRECISION_DOUBLE:
return DOUBLE_PREC;
}
}
#define DEFAULT_NAN_BITS 0xffffffffffffffffULL
static void
set_nan (fpu_register &reg, uae_u64 nan_bits, int nan_sign)
{
mpfr_set_nan (reg.f);
reg.nan_bits = nan_bits;
reg.nan_sign = nan_sign;
}
static void
set_nan (fpu_register &reg)
{
set_nan (reg, DEFAULT_NAN_BITS, 0);
}
static bool fpu_inited;
void
fpu_init (bool integral_68040)
{
fpu.is_integral = integral_68040;
mpfr_set_default_prec (EXTENDED_PREC);
mpfr_set_default_rounding_mode (MPFR_RNDN);
set_format (EXTENDED_PREC);
for (int i = 0; i < 8; i++)
mpfr_init (fpu.registers[i].f);
mpfr_init (fpu.result.f);
// Initialize constant ROM
for (int i = 0; i < num_fpu_constants; i++)
mpfr_init (fpu_constant_rom[i]);
// 0: pi
mpfr_const_pi (fpu_constant_rom[0], MPFR_RNDN);
// 11: log10 (2)
mpfr_set_ui (fpu_constant_rom[11], 2, MPFR_RNDN);
mpfr_log10 (fpu_constant_rom[11], fpu_constant_rom[11], MPFR_RNDZ);
// 12: e
mpfr_set_ui (fpu_constant_rom[12], 1, MPFR_RNDN);
mpfr_exp (fpu_constant_rom[12], fpu_constant_rom[12], MPFR_RNDZ);
// 13: log2 (e)
mpfr_log2 (fpu_constant_rom[13], fpu_constant_rom[12], MPFR_RNDU);
// 14: log10 (e)
mpfr_log10 (fpu_constant_rom[14], fpu_constant_rom[12], MPFR_RNDU);
// 15: 0
mpfr_set_zero (fpu_constant_rom[15], 0);
// 48: ln (2)
mpfr_const_log2 (fpu_constant_rom[16], MPFR_RNDN);
// 49: ln (10)
mpfr_set_ui (fpu_constant_rom[17], 10, MPFR_RNDN);
mpfr_log (fpu_constant_rom[17], fpu_constant_rom[17], MPFR_RNDN);
// 50 to 63: powers of 10
mpfr_set_ui (fpu_constant_rom[18], 1, MPFR_RNDN);
for (int i = 19; i < 32; i++)
{
mpfr_set_ui (fpu_constant_rom[i], 1L << (i - 19) , MPFR_RNDN);
mpfr_exp10 (fpu_constant_rom[i], fpu_constant_rom[i], MPFR_RNDN);
}
fpu_inited = true;
fpu_reset ();
}
void
fpu_exit ()
{
if (!fpu_inited) return;
for (int i = 0; i < 8; i++)
mpfr_clear (fpu.registers[i].f);
mpfr_clear (fpu.result.f);
for (int i = 0; i < num_fpu_constants; i++)
mpfr_clear (fpu_constant_rom[i]);
}
void
fpu_reset ()
{
set_fpcr (0);
set_fpsr (0);
fpu.instruction_address = 0;
for (int i = 0; i < 8; i++)
set_nan (fpu.registers[i]);
}
fpu_register::operator long double ()
{
return mpfr_get_ld (f, MPFR_RNDN);
}
fpu_register &
fpu_register::operator= (long double x)
{
mpfr_set_ld (f, x, MPFR_RNDN);
nan_bits = DEFAULT_NAN_BITS;
nan_sign = 0;
return *this;
}
static bool
get_fp_addr (uae_u32 opcode, uae_u32 *addr, bool write)
{
uaecptr pc;
int mode;
int reg;
mode = (opcode >> 3) & 7;
reg = opcode & 7;
switch (mode)
{
case 0:
case 1:
return false;
case 2:
*addr = m68k_areg (regs, reg);
break;
case 3:
*addr = m68k_areg (regs, reg);
break;
case 4:
*addr = m68k_areg (regs, reg);
break;
case 5:
*addr = m68k_areg (regs, reg) + (uae_s16) next_iword();
break;
case 6:
*addr = get_disp_ea_020 (m68k_areg (regs, reg), next_iword());
break;
case 7:
switch (reg)
{
case 0:
*addr = (uae_s16) next_iword();
break;
case 1:
*addr = next_ilong();
break;
case 2:
if (write)
return false;
pc = m68k_getpc ();
*addr = pc + (uae_s16) next_iword();
break;
case 3:
if (write)
return false;
pc = m68k_getpc ();
*addr = get_disp_ea_020 (pc, next_iword());
break;
default:
return false;
}
}
return true;
}
static void
set_from_single (fpu_register &value, uae_u32 data)
{
int s = data >> 31;
int e = (data >> 23) & 0xff;
uae_u32 m = data & 0x7fffff;
if (e == 0xff)
{
if (m != 0)
{
if (!(m & 0x400000))
cur_exceptions |= FPSR_EXCEPTION_SNAN;
set_nan (value, (uae_u64) (m | 0xc00000) << (32 + 8), s);
}
else
mpfr_set_inf (value.f, 0);
}
else
{
if (e != 0)
// Add integer bit
m |= 0x800000;
else
e++;
// Remove bias
e -= SINGLE_BIAS;
mpfr_set_ui_2exp (value.f, m, e - (SINGLE_PREC - 1), MPFR_RNDN);
}
mpfr_setsign (value.f, value.f, s, MPFR_RNDN);
}
static void
set_from_double (fpu_register &value, uae_u32 words[2])
{
int s = words[0] >> 31;
int e = (words[0] >> 20) & 0x7ff;
uae_u32 m = words[0] & 0xfffff;
if (e == 0x7ff)
{
if ((m | words[1]) != 0)
{
if (!(m & 0x80000))
cur_exceptions |= FPSR_EXCEPTION_SNAN;
set_nan (value, (((uae_u64) (m | 0x180000) << (32 + 11))
| ((uae_u64) words[1] << 11)), s);
}
else
mpfr_set_inf (value.f, 0);
}
else
{
if (e != 0)
// Add integer bit
m |= 0x100000;
else
e++;
// Remove bias
e -= DOUBLE_BIAS;
mpfr_set_uj_2exp (value.f, ((uintmax_t) m << 32) | words[1],
e - (DOUBLE_PREC - 1), MPFR_RNDN);
}
mpfr_setsign (value.f, value.f, s, MPFR_RNDN);
}
static void
set_from_extended (fpu_register &value, uae_u32 words[3], bool check_snan)
{
int s = words[0] >> 31;
int e = (words[0] >> 16) & 0x7fff;
if (e == 0x7fff)
{
if (((words[1] & 0x7fffffff) | words[2]) != 0)
{
if (check_snan)
{
if ((words[1] & 0x40000000) == 0)
cur_exceptions |= FPSR_EXCEPTION_SNAN;
words[1] |= 0x40000000;
}
set_nan (value, ((uae_u64) words[1] << 32) | words[2], s);
}
else
mpfr_set_inf (value.f, 0);
}
else
{
// Remove bias
e -= EXTENDED_BIAS;
mpfr_set_uj_2exp (value.f, ((uintmax_t) words[1] << 32) | words[2],
e - (EXTENDED_PREC - 1), MPFR_RNDN);
}
mpfr_setsign (value.f, value.f, s, MPFR_RNDN);
}
#define from_bcd(d) ((d) < 10 ? (d) : (d) - 10)
static void
set_from_packed (fpu_register &value, uae_u32 words[3])
{
char str[32], *p = str;
int sm = words[0] >> 31;
int se = (words[0] >> 30) & 1;
int i;
if (((words[0] >> 16) & 0x7fff) == 0x7fff)
{
if ((words[1] | words[2]) != 0)
{
if ((words[1] & 0x40000000) == 0)
cur_exceptions |= FPSR_EXCEPTION_SNAN;
set_nan (value, ((uae_u64) (words[1] | 0x40000000) << 32) | words[2],
sm);
}
else
mpfr_set_inf (value.f, 0);
}
else
{
if (sm)
*p++ = '-';
*p++ = from_bcd (words[0] & 15) + '0';
*p++ = '.';
for (i = 0; i < 8; i++)
{
p[i] = from_bcd ((words[1] >> (28 - i * 4)) & 15) + '0';
p[i + 8] = from_bcd ((words[2] >> (28 - i * 4)) & 15) + '0';
}
p += 16;
*p++ = 'e';
if (se)
*p++ = '-';
*p++ = from_bcd ((words[0] >> 24) & 15) + '0';
*p++ = from_bcd ((words[0] >> 20) & 15) + '0';
*p++ = from_bcd ((words[0] >> 16) & 15) + '0';
*p = 0;
mpfr_set_str (value.f, str, 10, MPFR_RNDN);
}
mpfr_setsign (value.f, value.f, sm, MPFR_RNDN);
}
static bool
get_fp_value (uae_u32 opcode, uae_u32 extra, fpu_register &value)
{
int mode, reg, size;
uaecptr pc;
uae_u32 addr;
uae_u32 words[3];
static const int sz1[8] = {4, 4, 12, 12, 2, 8, 1, 0};
static const int sz2[8] = {4, 4, 12, 12, 2, 8, 2, 0};
if ((extra & 0x4000) == 0)
{
mpfr_set (value.f, fpu.registers[(extra >> 10) & 7].f, MPFR_RNDN);
value.nan_bits = fpu.registers[(extra >> 10) & 7].nan_bits;
value.nan_sign = fpu.registers[(extra >> 10) & 7].nan_sign;
/* Check for SNaN. */
if (mpfr_nan_p (value.f) && (value.nan_bits & (1ULL << 62)) == 0)
{
value.nan_bits |= 1ULL << 62;
cur_exceptions |= FPSR_EXCEPTION_SNAN;
}
return true;
}
mode = (opcode >> 3) & 7;
reg = opcode & 7;
size = (extra >> 10) & 7;
switch (mode)
{
case 0:
switch (size)
{
case 6:
mpfr_set_si (value.f, (uae_s8) m68k_dreg (regs, reg), MPFR_RNDN);
break;
case 4:
mpfr_set_si (value.f, (uae_s16) m68k_dreg (regs, reg), MPFR_RNDN);
break;
case 0:
mpfr_set_si (value.f, (uae_s32) m68k_dreg (regs, reg), MPFR_RNDN);
break;
case 1:
set_from_single (value, m68k_dreg (regs, reg));
break;
default:
return false;
}
return true;
case 1:
return false;
case 2:
case 3:
addr = m68k_areg (regs, reg);
break;
case 4:
addr = m68k_areg (regs, reg) - (reg == 7 ? sz2[size] : sz1[size]);
break;
case 5:
addr = m68k_areg (regs, reg) + (uae_s16) next_iword ();
break;
case 6:
addr = get_disp_ea_020 (m68k_areg (regs, reg), next_iword ());
break;
case 7:
switch (reg)
{
case 0:
addr = (uae_s16) next_iword ();
break;
case 1:
addr = next_ilong ();
break;
case 2:
pc = m68k_getpc ();
addr = pc + (uae_s16) next_iword ();
break;
case 3:
pc = m68k_getpc ();
addr = get_disp_ea_020 (pc, next_iword ());
break;
case 4:
addr = m68k_getpc ();
m68k_incpc (sz2[size]);
if (size == 6) // Immediate byte
addr++;
break;
default:
return false;
}
}
switch (size)
{
case 0:
mpfr_set_si (value.f, (uae_s32) get_long (addr), MPFR_RNDN);
break;
case 1:
set_from_single (value, get_long (addr));
break;
case 2:
words[0] = get_long (addr);
words[1] = get_long (addr + 4);
words[2] = get_long (addr + 8);
set_from_extended (value, words, true);
break;
case 3:
words[0] = get_long (addr);
words[1] = get_long (addr + 4);
words[2] = get_long (addr + 8);
set_from_packed (value, words);
break;
case 4:
mpfr_set_si (value.f, (uae_s16) get_word (addr), MPFR_RNDN);
break;
case 5:
words[0] = get_long (addr);
words[1] = get_long (addr + 4);
set_from_double (value, words);
break;
case 6:
mpfr_set_si (value.f, (uae_s8) get_byte (addr), MPFR_RNDN);
break;
default:
return false;
}
switch (mode)
{
case 3:
m68k_areg (regs, reg) += reg == 7 ? sz2[size] : sz1[size];
break;
case 4:
m68k_areg (regs, reg) -= reg == 7 ? sz2[size] : sz1[size];
break;
}
return true;
}
static void
update_exceptions ()
{
uae_u32 exc, aexc;
exc = cur_exceptions;
// Add any mpfr detected exceptions
if (mpfr_underflow_p ())
exc |= FPSR_EXCEPTION_UNFL;
if (mpfr_overflow_p ())
exc |= FPSR_EXCEPTION_OVFL;
if (mpfr_inexflag_p ())
exc |= FPSR_EXCEPTION_INEX2;
set_exception_status (exc);
aexc = get_accrued_exception ();
if (exc & (FPSR_EXCEPTION_SNAN|FPSR_EXCEPTION_OPERR))
aexc |= FPSR_ACCR_IOP;
if (exc & FPSR_EXCEPTION_OVFL)
aexc |= FPSR_ACCR_OVFL;
if ((exc & (FPSR_EXCEPTION_UNFL|FPSR_EXCEPTION_INEX2))
== (FPSR_EXCEPTION_UNFL|FPSR_EXCEPTION_INEX2))
aexc |= FPSR_ACCR_UNFL;
if (exc & FPSR_EXCEPTION_DZ)
aexc |= FPSR_ACCR_DZ;
if (exc & (FPSR_EXCEPTION_INEX1|FPSR_EXCEPTION_INEX2|FPSR_EXCEPTION_OVFL))
aexc |= FPSR_ACCR_INEX;
set_accrued_exception (aexc);
if ((fpu.fpcr & exc) != 0)
{
fpu.instruction_address = cur_instruction_address;
// TODO: raise exceptions
// Problem: FPSP040 depends on proper FPU stack frames, it would suffer
// undefined behaviour with our dummy FSAVE implementation
}
}
static void
set_fp_register (int reg, mpfr_t value, uae_u64 nan_bits, int nan_sign,
int t, mpfr_rnd_t rnd, bool do_flags)
{
mpfr_subnormalize (value, t, rnd);
mpfr_set (fpu.registers[reg].f, value, rnd);
fpu.registers[reg].nan_bits = nan_bits;
fpu.registers[reg].nan_sign = nan_sign;
if (do_flags)
{
uae_u32 flags = 0;
if (mpfr_zero_p (fpu.registers[reg].f))
flags |= FPSR_CCB_ZERO;
if (mpfr_signbit (fpu.registers[reg].f))
flags |= FPSR_CCB_NEGATIVE;
if (mpfr_nan_p (fpu.registers[reg].f))
flags |= FPSR_CCB_NAN;
if (mpfr_inf_p (fpu.registers[reg].f))
flags |= FPSR_CCB_INFINITY;
set_fpccr (flags);
}
}
static void
set_fp_register (int reg, mpfr_t value, int t, mpfr_rnd_t rnd, bool do_flags)
{
set_fp_register (reg, value, DEFAULT_NAN_BITS, 0, t, rnd, do_flags);
}
static void
set_fp_register (int reg, fpu_register &value, int t, mpfr_rnd_t rnd,
bool do_flags)
{
set_fp_register (reg, value.f, value.nan_bits, value.nan_sign, t, rnd,
do_flags);
}
static uae_u32
extract_to_single (fpu_register &value)
{
uae_u32 word;
int t;
mpfr_rnd_t rnd = get_cur_rnd ();
MPFR_DECL_INIT (single, SINGLE_PREC);
set_format (SINGLE_PREC);
// Round to single
t = mpfr_set (single, value.f, rnd);
t = mpfr_check_range (single, t, rnd);
mpfr_subnormalize (single, t, rnd);
set_format (EXTENDED_PREC);
if (mpfr_inf_p (single))
word = 0x7f800000;
else if (mpfr_nan_p (single))
{
if ((value.nan_bits & (1ULL << 62)) == 0)
{
value.nan_bits |= 1ULL << 62;
cur_exceptions |= FPSR_EXCEPTION_SNAN;
}
word = 0x7f800000 | ((value.nan_bits >> (32 + 8)) & 0x7fffff);
if (value.nan_sign)
word |= 0x80000000;
}
else if (mpfr_zero_p (single))
word = 0;
else
{
int e;
mpz_t f;
mpz_init (f);
word = 0;
// Get exponent and mantissa
e = mpfr_get_z_2exp (f, single);
// Move binary point
e += SINGLE_PREC - 1;
// Add bias
e += SINGLE_BIAS;
if (e <= 0)
{
// Denormalized number
mpz_tdiv_q_2exp (f, f, -e + 1);
e = 0;
}
mpz_export (&word, 0, 1, 4, 0, 0, f);
// Remove integer bit
word &= 0x7fffff;
word |= e << 23;
mpz_clear (f);
}
if (mpfr_signbit (single))
word |= 0x80000000;
return word;
}
static void
extract_to_double (fpu_register &value, uint32_t *words)
{
int t;
mpfr_rnd_t rnd = get_cur_rnd ();
MPFR_DECL_INIT (dbl, DOUBLE_PREC);
set_format (DOUBLE_PREC);
// Round to double
t = mpfr_set (dbl, value.f, rnd);
t = mpfr_check_range (dbl, t, rnd);
mpfr_subnormalize (dbl, t, rnd);
set_format (EXTENDED_PREC);
if (mpfr_inf_p (dbl))
{
words[0] = 0x7ff00000;
words[1] = 0;
}
else if (mpfr_nan_p (dbl))
{
if ((value.nan_bits & (1ULL << 62)) == 0)
{
value.nan_bits |= 1ULL << 62;
cur_exceptions |= FPSR_EXCEPTION_SNAN;
}
words[0] = 0x7ff00000 | ((value.nan_bits >> (32 + 11)) & 0xfffff);
words[1] = value.nan_bits >> 11;
if (value.nan_sign)
words[0] |= 0x80000000;
}
else if (mpfr_zero_p (dbl))
{
words[0] = 0;
words[1] = 0;
}
else
{
int e, off = 0;
mpz_t f;
mpz_init (f);
words[0] = words[1] = 0;
// Get exponent and mantissa
e = mpfr_get_z_2exp (f, dbl);
// Move binary point
e += DOUBLE_PREC - 1;
// Add bias
e += DOUBLE_BIAS;
if (e <= 0)
{
// Denormalized number
mpz_tdiv_q_2exp (f, f, -e + 1);
if (e <= -20)
// No more than 32 bits left
off = 1;
e = 0;
}
mpz_export (&words[off], 0, 1, 4, 0, 0, f);
// Remove integer bit
words[0] &= 0xfffff;
words[0] |= e << 20;
mpz_clear (f);
}
if (mpfr_signbit (dbl))
words[0] |= 0x80000000;
}
static void
extract_to_extended (fpu_register &value, uint32_t *words)
{
if (mpfr_inf_p (value.f))
{
words[0] = 0x7fff0000;
words[1] = 0;
words[2] = 0;
}
else if (mpfr_nan_p (value.f))
{
words[0] = 0x7fff0000;
words[1] = value.nan_bits >> 32;
words[2] = value.nan_bits;
if (value.nan_sign)
words[0] |= 0x80000000;
}
else if (mpfr_zero_p (value.f))
{
words[0] = 0;
words[1] = 0;
words[2] = 0;
}
else
{
int e, off = 0;
mpz_t f;
mpz_init (f);
words[0] = words[1] = words[2] = 0;
// Get exponent and mantissa
e = mpfr_get_z_2exp (f, value.f);
// Move binary point
e += EXTENDED_PREC - 1;
// Add bias
e += EXTENDED_BIAS;
if (e < 0)
{
// Denormalized number
mpz_tdiv_q_2exp (f, f, -e);
if (e <= -32)
// No more than 32 bits left
off = 1;
e = 0;
}
mpz_export (&words[1 + off], 0, 1, 4, 0, 0, f);
words[0] = e << 16;
mpz_clear (f);
}
if (mpfr_signbit (value.f))
words[0] |= 0x80000000;
}
static void
extract_to_packed (fpu_register &value, int k, uae_u32 *words)
{
if (mpfr_inf_p (value.f))
{
words[0] = 0x7fff0000;
words[1] = 0;
words[2] = 0;
}
else if (mpfr_nan_p (value.f))
{
words[0] = 0x7fff0000;
words[1] = value.nan_bits >> 32;
words[2] = value.nan_bits;
if (value.nan_sign)
words[0] |= 0x80000000;
}
else if (mpfr_zero_p (value.f))
{
words[0] = 0;
words[1] = 0;
words[2] = 0;
}
else
{
char str[100], *p = str;
mpfr_exp_t e;
mpfr_rnd_t rnd = get_cur_rnd ();
words[0] = words[1] = words[2] = 0;
if (k >= 64)
k -= 128;
else if (k >= 18)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
if (k <= 0)
{
MPFR_DECL_INIT (temp, 16);
mpfr_log10 (temp, value.f, rnd);
k = mpfr_get_si (temp, MPFR_RNDZ) - k + 1;
}
if (k <= 0)
k = 1;
else if (k >= 18)
k = 17;
mpfr_get_str (str, &e, 10, k, value.f, rnd);
e--;
if (*p == '-')
p++;
// Pad to 17 digits
while (k < 17)
p[k++] = '0';
if (e < 0)
{
words[0] |= 0x40000000;
e = -e;
}
words[0] |= (e % 10) << 16;
e /= 10;
words[0] |= (e % 10) << 20;
e /= 10;
words[0] |= (e % 10) << 24;
e /= 10;
if (e)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
words[0] |= e << 12;
words[0] |= *p++ & 15;
for (k = 0; k < 8; k++)
words[1] = (words[1] << 4) | (*p++ & 15);
for (k = 0; k < 8; k++)
words[2] = (words[2] << 4) | (*p++ & 15);
}
if (mpfr_signbit (value.f))
words[0] |= 0x80000000;
}
static long
extract_to_integer (mpfr_t value, long min, long max)
{
long result;
mpfr_rnd_t rnd = get_cur_rnd ();
if (mpfr_fits_slong_p (value, rnd))
{
result = mpfr_get_si (value, rnd);
if (result > max)
{
result = max;
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (result < min)
{
result = min;
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
}
else
{
if (!mpfr_signbit (value))
result = max;
else
result = min;
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
return result;
}
static bool
fpuop_fmove_memory (uae_u32 opcode, uae_u32 extra)
{
int mode, reg, size;
uaecptr pc;
uae_u32 addr;
uae_u32 words[3];
static const int sz1[8] = {4, 4, 12, 12, 2, 8, 1, 0};
static const int sz2[8] = {4, 4, 12, 12, 2, 8, 2, 0};
mpfr_clear_flags ();
cur_exceptions = 0;
mode = (opcode >> 3) & 7;
reg = opcode & 7;
size = (extra >> 10) & 7;
fpu_register &value = fpu.registers[(extra >> 7) & 7];
switch (mode)
{
case 0:
switch (size)
{
case 0:
m68k_dreg (regs, reg) = extract_to_integer (value.f, -0x7fffffff-1, 0x7fffffff);
break;
case 1:
m68k_dreg (regs, reg) = extract_to_single (value);
break;
case 4:
m68k_dreg (regs, reg) &= ~0xffff;
m68k_dreg (regs, reg) |= extract_to_integer (value.f, -32768, 32767) & 0xffff;
break;
case 6:
m68k_dreg (regs, reg) &= ~0xff;
m68k_dreg (regs, reg) |= extract_to_integer (value.f, -128, 127) & 0xff;
break;
default:
return false;
}
update_exceptions ();
return true;
case 1:
return false;
case 2:
addr = m68k_areg (regs, reg);
break;
case 3:
addr = m68k_areg (regs, reg);
break;
case 4:
addr = m68k_areg (regs, reg) - (reg == 7 ? sz2[size] : sz1[size]);
break;
case 5:
addr = m68k_areg (regs, reg) + (uae_s16) next_iword();
break;
case 6:
addr = get_disp_ea_020 (m68k_areg (regs, reg), next_iword());
break;
case 7:
switch (reg)
{
case 0:
addr = (uae_s16) next_iword();
break;
case 1:
addr = next_ilong();
break;
case 2:
pc = m68k_getpc ();
addr = pc + (uae_s16) next_iword();
break;
case 3:
pc = m68k_getpc ();
addr = get_disp_ea_020 (pc, next_iword ());
break;
case 4:
addr = m68k_getpc ();
m68k_incpc (sz2[size]);
break;
default:
return false;
}
}
switch (size)
{
case 0:
put_long (addr, extract_to_integer (value.f, -0x7fffffff-1, 0x7fffffff));
break;
case 1:
put_long (addr, extract_to_single (value));
break;
case 2:
extract_to_extended (value, words);
put_long (addr, words[0]);
put_long (addr + 4, words[1]);
put_long (addr + 8, words[2]);
break;
case 3:
extract_to_packed (value, extra & 0x7f, words);
put_long (addr, words[0]);
put_long (addr + 4, words[1]);
put_long (addr + 8, words[2]);
break;
case 4:
put_word (addr, extract_to_integer (value.f, -32768, 32767));
break;
case 5:
extract_to_double (value, words);
put_long (addr, words[0]);
put_long (addr + 4, words[1]);
break;
case 6:
put_byte (addr, extract_to_integer (value.f, -128, 127));
break;
case 7:
extract_to_packed (value, m68k_dreg (regs, (extra >> 4) & 7) & 0x7f, words);
put_long (addr, words[0]);
put_long (addr + 4, words[1]);
put_long (addr + 8, words[2]);
break;
}
switch (mode)
{
case 3:
m68k_areg (regs, reg) += reg == 7 ? sz2[size] : sz1[size];
break;
case 4:
m68k_areg (regs, reg) -= reg == 7 ? sz2[size] : sz1[size];
break;
}
update_exceptions ();
return true;
}
static bool
fpuop_fmovem_control (uae_u32 opcode, uae_u32 extra)
{
int list, mode, reg;
uae_u32 addr;
list = (extra >> 10) & 7;
mode = (opcode >> 3) & 7;
reg = opcode & 7;
if (list == 0)
return false;
if (extra & 0x2000)
{
// FMOVEM to <ea>
if (mode == 0)
{
switch (list)
{
case 1:
m68k_dreg (regs, reg) = fpu.instruction_address;
break;
case 2:
m68k_dreg (regs, reg) = get_fpsr ();
break;
case 4:
m68k_dreg (regs, reg) = get_fpcr ();
break;
default:
return false;
}
}
else if (mode == 1)
{
if (list != 1)
return false;
m68k_areg (regs, reg) = fpu.instruction_address;
}
else
{
int nwords;
if (!get_fp_addr (opcode, &addr, true))
return false;
nwords = (list & 1) + ((list >> 1) & 1) + ((list >> 2) & 1);
if (mode == 4)
addr -= nwords * 4;
if (list & 4)
{
put_long (addr, get_fpcr ());
addr += 4;
}
if (list & 2)
{
put_long (addr, get_fpsr ());
addr += 4;
}
if (list & 1)
{
put_long (addr, fpu.instruction_address);
addr += 4;
}
if (mode == 4)
m68k_areg (regs, reg) = addr - nwords * 4;
else if (mode == 3)
m68k_areg (regs, reg) = addr;
}
}
else
{
// FMOVEM from <ea>
if (mode == 0)
{
switch (list)
{
case 1:
fpu.instruction_address = m68k_dreg (regs, reg);
break;
case 2:
set_fpsr (m68k_dreg (regs, reg));
break;
case 4:
set_fpcr (m68k_dreg (regs, reg));
break;
default:
return false;
}
}
else if (mode == 1)
{
if (list != 1)
return false;
fpu.instruction_address = m68k_areg (regs, reg);
}
else if ((opcode & 077) == 074)
{
switch (list)
{
case 1:
fpu.instruction_address = next_ilong ();
break;
case 2:
set_fpsr (next_ilong ());
break;
case 4:
set_fpcr (next_ilong ());
break;
default:
return false;
}
}
else
{
int nwords;
if (!get_fp_addr (opcode, &addr, false))
return false;
nwords = (list & 1) + ((list >> 1) & 1) + ((list >> 2) & 1);
if (mode == 4)
addr -= nwords * 4;
if (list & 4)
{
set_fpcr (get_long (addr));
addr += 4;
}
if (list & 2)
{
set_fpsr (get_long (addr));
addr += 4;
}
if (list & 1)
{
fpu.instruction_address = get_long (addr);
addr += 4;
}
if (mode == 4)
m68k_areg (regs, reg) = addr - nwords * 4;
else if (mode == 3)
m68k_areg (regs, reg) = addr;
}
}
return true;
}
static bool
fpuop_fmovem_register (uae_u32 opcode, uae_u32 extra)
{
uae_u32 addr;
uae_u32 words[3];
int list;
int i;
set_format (EXTENDED_PREC);
if (!get_fp_addr (opcode, &addr, extra & 0x2000))
return false;
if (extra & 0x800)
list = m68k_dreg (regs, (extra >> 4) & 7) & 0xff;
else
list = extra & 0xff;
if (extra & 0x2000)
{
// FMOVEM to memory
switch (opcode & 070)
{
case 030:
return false;
case 040:
if (extra & 0x1000)
return false;
for (i = 7; i >= 0; i--)
if (list & (1 << i))
{
extract_to_extended (fpu.registers[i], words);
addr -= 12;
put_long (addr, words[0]);
put_long (addr + 4, words[1]);
put_long (addr + 8, words[2]);
}
m68k_areg (regs, opcode & 7) = addr;
break;
default:
if ((extra & 0x1000) == 0)
return false;
for (i = 0; i < 8; i++)
if (list & (0x80 >> i))
{
extract_to_extended (fpu.registers[i], words);
put_long (addr, words[0]);
put_long (addr + 4, words[1]);
put_long (addr + 8, words[2]);
addr += 12;
}
if ((opcode & 070) == 030)
m68k_areg (regs, opcode & 7) = addr;
break;
}
}
else
{
// FMOVEM from memory
if ((opcode & 070) == 040)
return false;
if ((extra & 0x1000) == 0)
return false;
for (i = 0; i < 8; i++)
if (list & (0x80 >> i))
{
words[0] = get_long (addr);
words[1] = get_long (addr + 4);
words[2] = get_long (addr + 8);
addr += 12;
set_from_extended (fpu.registers[i], words, false);
}
if ((opcode & 070) == 030)
m68k_areg (regs, opcode & 7) = addr;
}
return true;
}
static int
do_getexp (mpfr_t value, mpfr_rnd_t rnd)
{
int t = 0;
if (mpfr_inf_p (value))
{
mpfr_set_nan (value);
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (!mpfr_nan_p (value) && !mpfr_zero_p (value))
t = mpfr_set_si (value, mpfr_get_exp (value) - 1, rnd);
return t;
}
static int
do_getman (mpfr_t value)
{
if (mpfr_inf_p (value))
{
mpfr_set_nan (value);
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (!mpfr_nan_p (value) && !mpfr_zero_p (value))
mpfr_set_exp (value, 1);
return 0;
}
static int
do_scale (mpfr_t value, mpfr_t reg, mpfr_rnd_t rnd)
{
long scale;
int t = 0;
if (mpfr_nan_p (value))
;
else if (mpfr_inf_p (value))
{
mpfr_set_nan (value);
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (mpfr_fits_slong_p (value, rnd))
{
scale = mpfr_get_si (value, MPFR_RNDZ);
mpfr_clear_inexflag ();
t = mpfr_mul_2si (value, reg, scale, rnd);
}
else
mpfr_set_inf (value, -mpfr_signbit (value));
return t;
}
static int
do_remainder (mpfr_t value, mpfr_t reg, mpfr_rnd_t rnd)
{
long quo;
int t = 0;
if (mpfr_nan_p (value) || mpfr_nan_p (reg))
;
else if (mpfr_zero_p (value) || mpfr_inf_p (reg))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_remquo (value, &quo, reg, value, rnd);
if (quo < 0)
quo = (-quo & 0x7f) | 0x80;
else
quo &= 0x7f;
fpu.fpsr.quotient = quo << 16;
return t;
}
// Unfortunately, mpfr_fmod does not return the quotient bits, so we
// have to reimplement it here
static int
mpfr_rem1 (mpfr_t rem, int *quo, mpfr_t x, mpfr_t y, mpfr_rnd_t rnd)
{
mpfr_exp_t ex, ey;
int inex, sign, signx = mpfr_signbit (x);
mpz_t mx, my, r;
mpz_init (mx);
mpz_init (my);
mpz_init (r);
ex = mpfr_get_z_2exp (mx, x); /* x = mx*2^ex */
ey = mpfr_get_z_2exp (my, y); /* y = my*2^ey */
/* to get rid of sign problems, we compute it separately:
quo(-x,-y) = quo(x,y), rem(-x,-y) = -rem(x,y)
quo(-x,y) = -quo(x,y), rem(-x,y) = -rem(x,y)
thus quo = sign(x/y)*quo(|x|,|y|), rem = sign(x)*rem(|x|,|y|) */
sign = (signx != mpfr_signbit (y));
mpz_abs (mx, mx);
mpz_abs (my, my);
/* divide my by 2^k if possible to make operations mod my easier */
{
unsigned long k = mpz_scan1 (my, 0);
ey += k;
mpz_fdiv_q_2exp (my, my, k);
}
if (ex <= ey)
{
/* q = x/y = mx/(my*2^(ey-ex)) */
mpz_mul_2exp (my, my, ey - ex); /* divide mx by my*2^(ey-ex) */
/* 0 <= |r| <= |my|, r has the same sign as mx */
mpz_tdiv_qr (mx, r, mx, my);
/* mx is the quotient */
mpz_tdiv_r_2exp (mx, mx, 7);
*quo = mpz_get_si (mx);
}
else /* ex > ey */
{
/* to get the low 7 more bits of the quotient, we first compute
R = X mod Y*2^7, where X and Y are defined below. Then the
low 7 of the quotient are floor(R/Y). */
mpz_mul_2exp (my, my, 7); /* 2^7*Y */
mpz_set_ui (r, 2);
mpz_powm_ui (r, r, ex - ey, my); /* 2^(ex-ey) mod my */
mpz_mul (r, r, mx);
mpz_mod (r, r, my);
/* now 0 <= r < 2^7*Y */
mpz_fdiv_q_2exp (my, my, 7); /* back to Y */
mpz_tdiv_qr (mx, r, r, my);
/* oldr = mx*my + newr */
*quo = mpz_get_si (mx);
/* now 0 <= |r| < |my| */
}
if (mpz_cmp_ui (r, 0) == 0)
{
inex = mpfr_set_ui (rem, 0, MPFR_RNDN);
/* take into account sign of x */
if (signx)
mpfr_neg (rem, rem, MPFR_RNDN);
}
else
{
/* take into account sign of x */
if (signx)
mpz_neg (r, r);
inex = mpfr_set_z_2exp (rem, r, ex > ey ? ey : ex, rnd);
}
if (sign)
*quo |= 0x80;
mpz_clear (mx);
mpz_clear (my);
mpz_clear (r);
return inex;
}
static int
do_fmod (mpfr_t value, mpfr_t reg, mpfr_rnd_t rnd)
{
int t = 0;
if (mpfr_nan_p (value) || mpfr_nan_p (reg))
mpfr_set_nan (value);
else if (mpfr_zero_p (value) || mpfr_inf_p (reg))
{
mpfr_set_nan (value);
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (mpfr_zero_p (reg) || mpfr_inf_p (value))
{
fpu.fpsr.quotient = 0;
t = mpfr_set (value, reg, rnd);
}
else
{
int quo;
t = mpfr_rem1 (value, &quo, reg, value, rnd);
fpu.fpsr.quotient = quo << 16;
}
return t;
}
static void
do_fcmp (mpfr_t source, mpfr_t dest)
{
uae_u32 flags = 0;
if (mpfr_nan_p (source) || mpfr_nan_p (dest))
flags |= FPSR_CCB_NAN;
else
{
int cmp = mpfr_cmp (dest, source);
if (cmp < 0)
flags |= FPSR_CCB_NEGATIVE;
else if (cmp == 0)
{
flags |= FPSR_CCB_ZERO;
if ((mpfr_zero_p (dest) || mpfr_inf_p (dest)) && mpfr_signbit (dest))
flags |= FPSR_CCB_NEGATIVE;
}
}
set_fpccr (flags);
}
static void
do_ftst (mpfr_t value)
{
uae_u32 flags = 0;
if (mpfr_signbit (value))
flags |= FPSR_CCB_NEGATIVE;
if (mpfr_nan_p (value))
flags |= FPSR_CCB_NAN;
else if (mpfr_zero_p (value))
flags |= FPSR_CCB_ZERO;
else if (mpfr_inf_p (value))
flags |= FPSR_CCB_INFINITY;
set_fpccr (flags);
}
static bool
fpuop_general (uae_u32 opcode, uae_u32 extra)
{
mpfr_prec_t prec = get_cur_prec ();
mpfr_rnd_t rnd = get_cur_rnd ();
int reg = (extra >> 7) & 7;
int t = 0;
fpu_register value;
bool ret;
mpfr_init2 (value.f, prec);
value.nan_bits = DEFAULT_NAN_BITS;
value.nan_sign = 0;
mpfr_clear_flags ();
set_format (prec);
cur_exceptions = 0;
cur_instruction_address = m68k_getpc () - 4;
if ((extra & 0xfc00) == 0x5c00)
{
// FMOVECR
int rom_index = extra & 0x7f;
if (rom_index == 0 || (rom_index >= 11 && rom_index <= 15))
t = mpfr_set (value.f, fpu_constant_rom[rom_index], rnd);
else if (rom_index >= 48 && rom_index <= 63)
t = mpfr_set (value.f, fpu_constant_rom[rom_index - 32], rnd);
else
mpfr_set_zero (value.f, 0);
set_fp_register (reg, value, t, rnd, true);
}
else if (extra & 0x40)
{
static const char valid[64] =
{
1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 1, 1, 0, 1, 1,
1, 0, 0, 0, 1, 0, 0, 0
};
if (extra & 4)
// FD...
prec = DOUBLE_PREC;
else
// FS...
prec = SINGLE_PREC;
set_format (prec);
MPFR_DECL_INIT (value2, prec);
if (!fpu.is_integral)
{
ret = false;
goto out;
}
if (!valid[extra & 0x3b])
{
ret = false;
goto out;
}
if (!get_fp_value (opcode, extra, value))
{
ret = false;
goto out;
}
switch (extra & 0x3f)
{
case 0: // FSMOVE
case 4: // FDMOVE
mpfr_set (value2, value.f, rnd);
break;
case 1: // FSSQRT
case 5: // FDSQRT
if (mpfr_sgn (value.f) < 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_sqrt (value2, value.f, rnd);
break;
case 24: // FSABS
case 28: // FDABS
t = mpfr_abs (value2, value.f, rnd);
break;
case 26: // FSNEG
case 30: // FDNEG
t = mpfr_neg (value2, value.f, rnd);
break;
case 32: // FSDIV
case 36: // FDDIV
if (mpfr_zero_p (value.f))
{
if (mpfr_regular_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (mpfr_zero_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (mpfr_inf_p (value.f) && mpfr_inf_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_div (value2, fpu.registers[reg].f, value.f, rnd);
break;
case 34: // FSADD
case 38: // FDADD
if (mpfr_inf_p (fpu.registers[reg].f) && mpfr_inf_p (value.f)
&& mpfr_signbit (fpu.registers[reg].f) != mpfr_signbit (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_add (value2, fpu.registers[reg].f, value.f, rnd);
break;
case 35: // FSMUL
case 39: // FDMUL
if ((mpfr_zero_p (value.f) && mpfr_inf_p (fpu.registers[reg].f))
|| (mpfr_inf_p (value.f) && mpfr_zero_p (fpu.registers[reg].f)))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_mul (value2, fpu.registers[reg].f, value.f, rnd);
break;
case 40: // FSSUB
case 44: // FDSUB
if (mpfr_inf_p (fpu.registers[reg].f) && mpfr_inf_p (value.f)
&& mpfr_signbit (fpu.registers[reg].f) == mpfr_signbit (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_sub (value2, fpu.registers[reg].f, value.f, rnd);
break;
}
set_fp_register (reg, value2, t, rnd, true);
}
else if ((extra & 0x30) == 0x30)
{
if ((extra & 15) > 10 || (extra & 15) == 9)
{
ret = false;
goto out;
}
if (!get_fp_value (opcode, extra, value))
{
ret = false;
goto out;
}
if ((extra & 15) < 8)
{
// FSINCOS
int reg2 = extra & 7;
MPFR_DECL_INIT (value2, prec);
if (mpfr_inf_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_sin_cos (value.f, value2, value.f, rnd);
if (reg2 != reg)
set_fp_register (reg2, value2, t >> 2, rnd, false);
set_fp_register (reg, value, t & 3, rnd, true);
}
else if ((extra & 15) == 8)
// FCMP
do_fcmp (value.f, fpu.registers[reg].f);
else
// FTST
do_ftst (value.f);
}
else
{
static const char valid[64] =
{
1, 1, 1, 1, 1, 0, 1, 0,
1, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 0,
1, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1
};
if (!valid[extra & 0x3f])
{
ret = false;
goto out;
}
if (!get_fp_value (opcode, extra, value))
{
ret = false;
goto out;
}
switch (extra & 0x3f)
{
case 0: // FMOVE
break;
case 1: // FINT
t = mpfr_rint (value.f, value.f, rnd);
break;
case 2: // FSINH
t = mpfr_sinh (value.f, value.f, rnd);
break;
case 3: // FINTRZ
t = mpfr_rint (value.f, value.f, MPFR_RNDZ);
break;
case 4: // FSQRT
if (mpfr_sgn (value.f) < 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_sqrt (value.f, value.f, rnd);
break;
case 6: // FLOGNP1
if (!mpfr_nan_p (value.f))
{
int cmp = mpfr_cmp_si (value.f, -1);
if (cmp == 0)
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (cmp < 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
t = mpfr_log1p (value.f, value.f, rnd);
break;
case 8: // FETOXM1
t = mpfr_expm1 (value.f, value.f, rnd);
break;
case 9: // FTANH
t = mpfr_tanh (value.f, value.f, rnd);
break;
case 10: // FATAN
t = mpfr_atan (value.f, value.f, rnd);
break;
case 12: // FASIN
if (mpfr_cmpabs (value.f, FPU_CONSTANT_ONE) > 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_asin (value.f, value.f, rnd);
break;
case 13: // FATANH
if (mpfr_cmpabs (value.f, FPU_CONSTANT_ONE) > 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_atanh (value.f, value.f, rnd);
break;
case 14: // FSIN
if (mpfr_inf_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_sin (value.f, value.f, rnd);
break;
case 15: // FTAN
if (mpfr_inf_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_tan (value.f, value.f, rnd);
break;
case 16: // FETOX
t = mpfr_exp (value.f, value.f, rnd);
break;
case 17: // FTWOTOX
t = mpfr_ui_pow (value.f, 2, value.f, rnd);
break;
case 18: // FTENTOX
t = mpfr_ui_pow (value.f, 10, value.f, rnd);
break;
case 20: // FLOGN
if (mpfr_zero_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (mpfr_sgn (value.f) < 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_log (value.f, value.f, rnd);
break;
case 21: // FLOG10
if (mpfr_zero_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (mpfr_sgn (value.f) < 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_log10 (value.f, value.f, rnd);
break;
case 22: // FLOG2
if (mpfr_zero_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (mpfr_sgn (value.f) < 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_log2 (value.f, value.f, rnd);
break;
case 24: // FABS
t = mpfr_abs (value.f, value.f, rnd);
value.nan_sign = 0;
break;
case 25: // FCOSH
t = mpfr_cosh (value.f, value.f, rnd);
break;
case 26: // FNEG
t = mpfr_neg (value.f, value.f, rnd);
value.nan_sign = !value.nan_sign;
break;
case 28: // FACOS
if (mpfr_cmpabs (value.f, FPU_CONSTANT_ONE) > 0)
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_acos (value.f, value.f, rnd);
break;
case 29: // FCOS
if (mpfr_inf_p (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_cos (value.f, value.f, rnd);
break;
case 30: // FGETEXP
t = do_getexp (value.f, rnd);
break;
case 31: // FGETMAN
t = do_getman (value.f);
break;
case 32: // FDIV
if (mpfr_zero_p (value.f))
{
if (mpfr_regular_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (mpfr_zero_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (mpfr_inf_p (value.f) && mpfr_inf_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_div (value.f, fpu.registers[reg].f, value.f, rnd);
break;
case 33: // FMOD
t = do_fmod (value.f, fpu.registers[reg].f, rnd);
break;
case 34: // FADD
if (mpfr_inf_p (fpu.registers[reg].f) && mpfr_inf_p (value.f)
&& mpfr_signbit (fpu.registers[reg].f) != mpfr_signbit (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_add (value.f, fpu.registers[reg].f, value.f, rnd);
break;
case 35: // FMUL
if ((mpfr_zero_p (value.f) && mpfr_inf_p (fpu.registers[reg].f))
|| (mpfr_inf_p (value.f) && mpfr_zero_p (fpu.registers[reg].f)))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_mul (value.f, fpu.registers[reg].f, value.f, rnd);
break;
case 36: // FSGLDIV
{
MPFR_DECL_INIT (value2, SINGLE_PREC);
set_format (SINGLE_PREC);
if (mpfr_zero_p (value.f))
{
if (mpfr_regular_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_DZ;
else if (mpfr_zero_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
}
else if (mpfr_inf_p (value.f) && mpfr_inf_p (fpu.registers[reg].f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_div (value2, fpu.registers[reg].f, value.f, rnd);
mpfr_set (value.f, value2, rnd);
}
break;
case 37: // FREM
t = do_remainder (value.f, fpu.registers[reg].f, rnd);
break;
case 38: // FSCALE
t = do_scale (value.f, fpu.registers[reg].f, rnd);
break;
case 39: // FSGLMUL
{
MPFR_DECL_INIT (value2, SINGLE_PREC);
set_format (SINGLE_PREC);
if ((mpfr_zero_p (value.f) && mpfr_inf_p (fpu.registers[reg].f))
|| (mpfr_inf_p (value.f) && mpfr_zero_p (fpu.registers[reg].f)))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_mul (value2, fpu.registers[reg].f, value.f, rnd);
mpfr_set (value.f, value2, rnd);
}
break;
case 40: // FSUB
if (mpfr_inf_p (fpu.registers[reg].f) && mpfr_inf_p (value.f)
&& mpfr_signbit (fpu.registers[reg].f) == mpfr_signbit (value.f))
cur_exceptions |= FPSR_EXCEPTION_OPERR;
t = mpfr_sub (value.f, fpu.registers[reg].f, value.f, rnd);
break;
}
set_fp_register (reg, value, t, rnd, true);
}
update_exceptions ();
ret = true;
out:
mpfr_clear (value.f);
return ret;
}
void
fpuop_arithmetic (uae_u32 opcode, uae_u32 extra)
{
bool valid;
switch ((extra >> 13) & 7)
{
case 3:
valid = fpuop_fmove_memory (opcode, extra);
break;
case 4:
case 5:
valid = fpuop_fmovem_control (opcode, extra);
break;
case 6:
case 7:
valid = fpuop_fmovem_register (opcode, extra);
break;
case 0:
case 2:
valid = fpuop_general (opcode, extra);
break;
default:
valid = false;
break;
}
if (!valid)
{
m68k_setpc (m68k_getpc () - 4);
op_illg (opcode);
}
}
static bool
check_fp_cond (uae_u32 pred)
{
uae_u32 fpcc = get_fpccr ();
if ((pred & 16) != 0 && (fpcc & FPSR_CCB_NAN) != 0)
{
// IEEE non-aware test
set_exception_status (get_exception_status () | FPSR_EXCEPTION_BSUN);
set_accrued_exception (get_accrued_exception () | FPSR_ACCR_IOP);
}
switch (pred & 15)
{
case 0: // F / SF
return false;
case 1: // EQ /SEQ
return (fpcc & FPSR_CCB_ZERO) != 0;
case 2: // OGT / GT
return (fpcc & (FPSR_CCB_NAN | FPSR_CCB_ZERO | FPSR_CCB_NEGATIVE)) == 0;
case 3: // OGE / GE
return (fpcc & FPSR_CCB_ZERO) != 0 || (fpcc & (FPSR_CCB_NAN | FPSR_CCB_NEGATIVE)) == 0;
case 4: // OLT / LT
return (fpcc & (FPSR_CCB_NEGATIVE | FPSR_CCB_NAN | FPSR_CCB_ZERO)) == FPSR_CCB_NEGATIVE;
case 5: // OLE / LE
return (fpcc & FPSR_CCB_ZERO) != 0 || (fpcc & (FPSR_CCB_NEGATIVE | FPSR_CCB_NAN)) == FPSR_CCB_NEGATIVE;
case 6: // OGL / GL
return (fpcc & (FPSR_CCB_NAN | FPSR_CCB_ZERO)) == 0;
case 7: // OR / GLE
return (fpcc & FPSR_CCB_NAN) == 0;
case 8: // UN / NGLE
return (fpcc & FPSR_CCB_NAN) != 0;
case 9: // UEQ / NGL
return (fpcc & (FPSR_CCB_NAN | FPSR_CCB_ZERO)) != 0;
case 10: // UGT / NLE
return (fpcc & FPSR_CCB_NAN) != 0 || (fpcc & (FPSR_CCB_NEGATIVE | FPSR_CCB_ZERO)) == 0;
case 11: // UGE / NLT
return (fpcc & (FPSR_CCB_NEGATIVE | FPSR_CCB_NAN | FPSR_CCB_ZERO)) != FPSR_CCB_NEGATIVE;
case 12: // ULT / NGE
return (fpcc & FPSR_CCB_NAN) != 0 || (fpcc & (FPSR_CCB_NEGATIVE | FPSR_CCB_ZERO)) == FPSR_CCB_NEGATIVE;
case 13: // ULE / NGT
return (fpcc & (FPSR_CCB_NAN | FPSR_CCB_ZERO | FPSR_CCB_NEGATIVE)) != 0;
case 14: // NE / SNE
return (fpcc & FPSR_CCB_ZERO) == 0;
case 15: // T / ST
return true;
default:
return false;
}
}
void
fpuop_bcc (uae_u32 opcode, uaecptr pc, uae_u32 disp)
{
if (check_fp_cond (opcode))
{
if (!(opcode & (1 << 6)))
disp = (uae_s16) disp;
m68k_setpc (pc + disp);
}
}
void
fpuop_scc (uae_u32 opcode, uae_u32 extra)
{
uae_u32 addr;
int value = check_fp_cond (extra) ? 0xff : 0;
if ((opcode & 070) == 0)
{
int reg = opcode & 7;
m68k_dreg (regs, reg) = (m68k_dreg (regs, reg) & ~0xff) | value;
}
else if (!get_fp_addr (opcode, &addr, true))
{
m68k_setpc (m68k_getpc () - 4);
op_illg (opcode);
}
else
{
switch (opcode & 070)
{
case 030:
m68k_areg (regs, opcode & 7) += (opcode & 7) == 7 ? 2 : 1;
break;
case 040:
addr -= (opcode & 7) == 7 ? 2 : 1;
m68k_areg (regs, opcode & 7) = addr;
}
put_byte (addr, value);
}
}
void
fpuop_dbcc (uae_u32 opcode, uae_u32 extra)
{
uaecptr pc = m68k_getpc ();
uae_s16 disp = next_iword ();
if (!check_fp_cond (extra))
{
int reg = opcode & 7;
uae_u16 cnt = (m68k_dreg (regs, reg) & 0xffff) - 1;
m68k_dreg (regs, reg) = (m68k_dreg (regs, reg) & ~0xffff) | cnt;
if (cnt != 0xffff)
m68k_setpc (pc + disp);
}
}
void
fpuop_trapcc (uae_u32, uaecptr oldpc, uae_u32 extra)
{
if (check_fp_cond (extra))
Exception (7, oldpc - 2);
}
void
fpuop_save (uae_u32 opcode)
{
uae_u32 addr;
if ((opcode & 070) == 030
|| !get_fp_addr (opcode, &addr, true))
{
m68k_setpc (m68k_getpc () - 2);
op_illg (opcode);
return;
}
if (fpu.is_integral)
{
// 4 byte 68040 IDLE frame
// FIXME: generate proper FPU stack frames that does not result
// in undefined behaviour from FPSP040
if ((opcode & 070) == 040)
{
addr -= 4;
m68k_areg (regs, opcode & 7) = addr;
}
put_long (addr, 0x41000000);
}
else
{
// 28 byte 68881 IDLE frame
if ((opcode & 070) == 040)
{
addr -= 28;
m68k_areg (regs, opcode & 7) = addr;
}
put_long (addr, 0x1f180000);
for (int i = 0; i < 6; i++)
{
addr += 4;
put_long (addr, 0);
}
}
}
void
fpuop_restore (uae_u32 opcode)
{
uae_u32 addr;
uae_u32 format;
if ((opcode & 070) == 040
|| !get_fp_addr (opcode, &addr, false))
{
m68k_setpc (m68k_getpc () - 2);
op_illg (opcode);
return;
}
format = get_long (addr);
addr += 4;
if ((format & 0xff000000) == 0)
// NULL frame
fpu_reset ();
else
addr += (format & 0xff0000) >> 16;
if ((opcode & 070) == 030)
m68k_areg (regs, opcode & 7) = addr;
}
void fpu_set_fpsr(uae_u32 new_fpsr)
{
set_fpsr(new_fpsr);
}
uae_u32 fpu_get_fpsr(void)
{
return get_fpsr();
}
void fpu_set_fpcr(uae_u32 new_fpcr)
{
set_fpcr(new_fpcr);
}
uae_u32 fpu_get_fpcr(void)
{
return get_fpcr();
}
#endif