/* Functions for controlling the floating-point environment as a whole.
   Copyright (C) 1997-2024 Free Software Foundation, Inc.

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file 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 Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Based on glibc/sysdeps/<cpu>/{fegetenv.c,fesetenv.c}
   together with glibc/sysdeps/<cpu>/{fpu_control.h,fenv_private.h,fenv_libc.h}.  */

#include <config.h>

/* Specification.  */
#include <fenv.h>

#include "fenv-private.h"
#include "verify.h"

/* The big problem of the fegetenv/fesetenv API is that it is not well-defined
   which parts of the floating-point environment need to be restored by fesetenv
   and which don't.  For example:
     - On x86_64 and i386, some platforms restore all the x86_387_fenv_t,
       whereas others restore only the control word and the status words.
     - On m68k, musl libc restores the fpiar register value, whereas glibc
       doesn't.
   Some platforms don't even do this consistently.  For example, musl libc on
   sh4 restores the precision mode bit (fpscr bit 19) during an fesetenv of a
   previously saved environment, but not during an fesetenv (FE_DFL_ENV)
   invocation.  */

#if defined _AIX && defined __powerpc__ /* AIX */

/* On AIX, fenv_t is a struct { unsigned short rmode; unsigned int fpstat, trapstate; }.  */

/* On AIX, the register fpscr is augmented with a 32-bit word named fpscrx
   in thread-local storage.  Therefore AIX does complex things behind the scene,
   and its best to use the system-provided fegetenv() and fesetenv() functions
   and just add the missing behaviour, namely saving and restoring the exception
   trap bits.  */

# include <float.h>
# include <fpxcp.h>

# include <fptrap.h>

int
fegetenv (fenv_t *envp)
#undef fegetenv
{
  /* Invoke the original fegetenv(), and additionally save the exception trap
     bits (fpscr bits 7..3).  */
  union { unsigned long long u; double f; } memenv;
  _FPU_GETCW_AS_DOUBLE (memenv.f);
  fegetenv (envp);
  /* AIX's original fegetenv() sets envp->trapstate = 0.  */
  envp->trapstate = FE_ALL_EXCEPT & ((unsigned int) memenv.u << 22);
  return 0;
}

int
fesetenv (fenv_t const *envp)
#undef fesetenv
{
  /* Invoke the original fesetenv(), and additionally restore the exception trap
     bits (fpscr bits 7..3).  */
  if (fesetenv (envp) != 0)
    return -1;
  union { unsigned long long u; double f; } orig_memenv, memenv;
  _FPU_GETCW_AS_DOUBLE (orig_memenv.f);
  unsigned int trapbits;
  if (envp == FE_DFL_ENV)
    trapbits = 0;
  else
    trapbits = (envp->trapstate & FE_ALL_EXCEPT) >> 22;
  memenv.u = (orig_memenv.u & ~0x000000f8) | trapbits;
  if (!(memenv.u == orig_memenv.u))
    {
      if ((orig_memenv.u & 0x000000f8) == 0 && (memenv.u & 0x000000f8) != 0)
        {
          /* Put the thread into precise trapping mode.  */
          /* Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine>  */
          fp_trap (FP_TRAP_SYNC);
        }
      _FPU_SETCW_AS_DOUBLE (memenv.f);
      if ((orig_memenv.u & 0x000000f8) != 0 && (memenv.u & 0x000000f8) == 0)
        {
          /* Put the thread into no-trapping mode.  */
          /* Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine>  */
          fp_trap (FP_TRAP_OFF);
        }
    }
  return 0;
}

#elif defined __GNUC__ || defined __clang__ || defined _MSC_VER

# if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)

/* On all OSes except MSVC, macOS, Solaris, fenv_t is binary-equivalent to
     - either a x86_387_fenv_t (7 'unsigned int' words)
       where mxcsr is stored:
         - for glibc/i386: in __eip = more[1].
         - for musl libc/i386: the lower 6 bits ORed into __status_word.
         - for FreeBSD/i386, Haiku/i386, mingw/i386:
           the upper 16 bits in __reserved1, the lower 16 bits in __reserved2.
     - or a struct { x86_387_fenv_t 387; unsigned int mxcsr; }
   On MSVC, it's a
     struct { unsigned int _Fe_ctl, _Fe_stat; }, where
     _Fe_ctl bits 9..8 are the rounding direction,
     _Fe_ctl bits 4..0 are the exception trap bits (inverted),
     _Fe_stat bits 4..0 are the exception flags.
   On macOS, it's a
     struct { unsigned short __control, __status; unsigned int __mxcsr; ... }.
   On Solaris, it's a
     struct { __fex_handler_t __handlers; unsigned long __fsr; }.  */

/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL.  */
#  define _FE_ALL_EXCEPT (0x3F * FE_INVALID)

int
fegetenv (fenv_t *envp)
{
#  if defined _MSC_VER

  unsigned int mxcsr;
  _FPU_GETSSECW (mxcsr);
  envp->_Fe_ctl = ((mxcsr & 0x6000) >> 5) /* rounding direction */
                  /* exception trap bits (inverted):  */
                  | (mxcsr & (1 << 7) ? FE_INVALID : 0)
                  | (mxcsr & (1 << 9) ? FE_DIVBYZERO : 0)
                  | (mxcsr & (1 << 10) ? FE_OVERFLOW : 0)
                  | (mxcsr & (1 << 11) ? FE_UNDERFLOW : 0)
                  | (mxcsr & (1 << 12) ? FE_INEXACT : 0);
  envp->_Fe_stat = /* exception flags:  */
                     (mxcsr & (1 << 0) ? FE_INVALID : 0)
                   | (mxcsr & (1 << 2) ? FE_DIVBYZERO : 0)
                   | (mxcsr & (1 << 3) ? FE_OVERFLOW : 0)
                   | (mxcsr & (1 << 4) ? FE_UNDERFLOW : 0)
                   | (mxcsr & (1 << 5) ? FE_INEXACT : 0);

#  elif defined __APPLE__ && defined __MACH__ /* macOS */

  _FPU_GETCW (envp->__control);
  _FPU_GETSTAT (envp->__status);
  _FPU_GETSSECW (envp->__mxcsr);

#  elif defined __sun /* Solaris */

  fex_getexcepthandler (&envp->__handlers, FEX_ALL);

  unsigned short fctrl;
  unsigned short fstat;
  unsigned int mxcsr;
  _FPU_GETCW (fctrl);
  _FPU_GETSTAT (fstat);
  _FPU_GETSSECW (mxcsr);
  envp->__fsr =
    (((unsigned int) (fctrl ^ 0x3F) & ~0xE0C0U) << 16)
    | ((unsigned int) fstat | (mxcsr & 0x3F));

#  else

  verify (sizeof (fenv_t) >= sizeof (x86_387_fenv_t));

  __asm__ __volatile__ ("fnstenv %0" : "=m" (* (x86_387_fenv_t *) envp));
  /* Note: fnstenv masks all floating-point exceptions until the fldcw
     below.  */
  __asm__ __volatile__ ("fldcw %0" : : "m" (((x86_387_fenv_t *) envp)->__control_word));

  unsigned int mxcsr;
  _FPU_GETSSECW (mxcsr);
  if (sizeof (fenv_t) > sizeof (x86_387_fenv_t))
    {
      /* Store mxcsr in the struct field after the x86_387_fenv_t.  */
      * (unsigned int *) ((x86_387_fenv_t *) envp + 1) = mxcsr;
    }
  else
    {
#   if defined __GLIBC__
      ((x86_387_fenv_t *) envp)->more[1] = mxcsr;
#   elif MUSL_LIBC
      ((x86_387_fenv_t *) envp)->__status_word |= mxcsr & 0x3F;
#   else
      ((x86_387_fenv_t *) envp)->__reserved1 = mxcsr >> 16;
      ((x86_387_fenv_t *) envp)->__reserved2 = mxcsr & 0xFFFF;
#   endif
    }

#  endif
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
#  if defined _MSC_VER

  unsigned int mxcsr;
  _FPU_GETSSECW (mxcsr);
  mxcsr &= ~(0x6000 | (0x3F << 7) | 0x3F);
  /* On MSVC, FE_DFL_ENV is the address of a global variable.
     Let's ignore this variable.  */
  if (envp == FE_DFL_ENV)
    /* Default: rounding direction = FE_TONEAREST, exception trap bits = 0,
       exception flags = 0.  */
    mxcsr = mxcsr | (0x3F << 7);
  else
    mxcsr = mxcsr
            | ((envp->_Fe_ctl & 0x300) << 5) /* rounding direction */
            /* exception trap bits (inverted):  */
            | (envp->_Fe_ctl & FE_INVALID   ? 1 << 7 : 0)
            | (envp->_Fe_ctl & FE_DIVBYZERO ? 1 << 9 : 0)
            | (envp->_Fe_ctl & FE_OVERFLOW  ? 1 << 10 : 0)
            | (envp->_Fe_ctl & FE_UNDERFLOW ? 1 << 11 : 0)
            | (envp->_Fe_ctl & FE_INEXACT   ? 1 << 12 : 0)
            /* exception flags:  */
            | (envp->_Fe_stat & FE_INVALID   ? 1 << 0 : 0)
            | (envp->_Fe_stat & FE_DIVBYZERO ? 1 << 2 : 0)
            | (envp->_Fe_stat & FE_OVERFLOW  ? 1 << 3 : 0)
            | (envp->_Fe_stat & FE_UNDERFLOW ? 1 << 4 : 0)
            | (envp->_Fe_stat & FE_INEXACT   ? 1 << 5 : 0);
  _FPU_SETSSECW (mxcsr);

#  elif defined __APPLE__ && defined __MACH__ /* macOS */

  unsigned short fctrl;
  unsigned short fstat;
  unsigned int mxcsr;
  /* On macOS, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    {
      fctrl = 0x3F;
      fstat = 0;
      mxcsr = 0x3F << 7;
    }
  else
    {
      fctrl = envp->__control;
      fstat = envp->__status;
      mxcsr = envp->__mxcsr;
    }
  /* In the SSE unit.  */
  _FPU_SETSSECW (mxcsr);

  /* In the 387 unit.  */
  x86_387_fenv_t env387;
  __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env387));
  /* Note: fnstenv masks all floating-point exceptions until the fldenv
     below.  */
  env387.__control_word = fctrl;
  env387.__status_word = fstat;
  __asm__ __volatile__ ("fldenv %0" : : "m" (*&env387));

#  elif defined __sun /* Solaris */

  unsigned short env_fctrl;
  unsigned int env_mxcsr;
  /* On Solaris, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    {
      env_fctrl = 0x3F;
      env_mxcsr = 0x3F << 7;
    }
  else
    {
      fex_setexcepthandler (&envp->__handlers, FEX_ALL);
      env_fctrl = (envp->__fsr >> 16) ^ 0x3F;
      env_mxcsr = envp->__fsr & 0x3F;
    }

  /* Store the exception flags in mxcsr, not in env387.__status_word.  */

  /* In the SSE unit.  */
  unsigned int mxcsr;
  _FPU_GETSSECW (mxcsr);
  mxcsr = (mxcsr & ~0x3F) | env_mxcsr;
  _FPU_SETSSECW (mxcsr);

  /* In the 387 unit.  */
  x86_387_fenv_t env387;
  __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env387));
  /* Note: fnstenv masks all floating-point exceptions until the fldenv
     below.  */
  env387.__control_word = env_fctrl;
  env387.__status_word &= ~0x3F;
  __asm__ __volatile__ ("fldenv %0" : : "m" (*&env387));

#  else

  verify (sizeof (fenv_t) >= sizeof (x86_387_fenv_t));

  unsigned short env_fctrl;
  unsigned short env_fstat;
  unsigned int env_mxcsr;
  /* On *BSD, Solaris, Cygwin, Android, Haiku, Minix, FE_DFL_ENV is the address
     of a global variable; no special code is needed in this case.
     But on mingw, FE_DFL_ENV is NULL.  */
  if (envp == FE_DFL_ENV)
    {
      env_fctrl = 0x3F;
      env_fstat = 0;
      env_mxcsr = 0x3F << 7;
    }
  else
    {
      env_fctrl = ((x86_387_fenv_t const *) envp)->__control_word;
      env_fstat = ((x86_387_fenv_t const *) envp)->__status_word;
      if (sizeof (fenv_t) > sizeof (x86_387_fenv_t))
        env_mxcsr = * (unsigned int const *) ((x86_387_fenv_t const *) envp + 1);
      else
        {
#   if defined __GLIBC__
          env_mxcsr = ((x86_387_fenv_t const *) envp)->more[1];
#   elif MUSL_LIBC
          env_mxcsr = env_fstat & 0x3F;
#   else
          env_mxcsr = (((x86_387_fenv_t const *) envp)->__reserved1 << 16)
                      | ((x86_387_fenv_t const *) envp)->__reserved2;
#   endif
        }
    }

  /* In the SSE unit.  */
  unsigned int mxcsr = env_mxcsr;
  _FPU_SETSSECW (mxcsr);

  /* In the 387 unit.  */
  x86_387_fenv_t env387;
  __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env387));
  /* Note: fnstenv masks all floating-point exceptions until the fldenv
     below.  */
  env387.__control_word = env_fctrl;
  env387.__status_word = env_fstat;
  __asm__ __volatile__ ("fldenv %0" : : "m" (*&env387));

#  endif

  return 0;
}

# elif defined __aarch64__ /* arm64 */

/* On all OSes except FreeBSD and macOS, fenv_t is binary-equivalent to a
   struct { unsigned int fpcr, fpsr; }.
   On FreeBSD, it's binary-equivalent to a
   struct { unsigned int fpsr, fpcr; }, i.e. the fields are swapped.
   On macOS, it's binary-equivalent to a
   struct { unsigned long fpsr, fpcr; }.  */

int
fegetenv (fenv_t *envp)
{
  unsigned long fpcr;
  unsigned long fpsr;
  _FPU_GETCW (fpcr);
  _FPU_GETFPSR (fpsr);
#  if defined __APPLE__ && defined __MACH__
  ((unsigned long *) envp)[0] = fpsr;
  ((unsigned long *) envp)[1] = fpcr;
#  elif defined __FreeBSD__
  ((unsigned int *) envp)[0] = fpsr;
  ((unsigned int *) envp)[1] = fpcr;
#  else
  ((unsigned int *) envp)[0] = fpcr;
  ((unsigned int *) envp)[1] = fpsr;
#  endif
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned long orig_fpcr, fpcr;
  unsigned long orig_fpsr, fpsr;
  _FPU_GETCW (orig_fpcr);
  _FPU_GETFPSR (orig_fpsr);
  /* On *BSD and Android, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    {
      /* Default: rounding direction = FE_TONEAREST, exceptions trap bits = 0. */
      fpcr = (orig_fpcr & 0xFE0FE0F8U) | 0x00000000U;
      /* Default: exception flags = 0.  */
      fpsr = (orig_fpsr & 0x0FFFFFE0U) | 0x00000000U;
    }
  else
    {
#  if defined __APPLE__ && defined __MACH__
      fpsr = ((unsigned long const *) envp)[0];
      fpcr = ((unsigned long const *) envp)[1];
#  elif defined __FreeBSD__
      fpsr = ((unsigned int const *) envp)[0];
      fpcr = ((unsigned int const *) envp)[1];
#  else
      fpcr = ((unsigned int const *) envp)[0];
      fpsr = ((unsigned int const *) envp)[1];
#  endif
    }
  if (fpcr != orig_fpcr)
    _FPU_SETCW (fpcr);
  if (fpsr != orig_fpsr)
    _FPU_SETFPSR (fpsr);
  return 0;
}

# elif defined __arm__

/* On all OSes except OpenBSD, fenv_t is binary-equivalent to an 'unsigned int'.
   On OpenBSD, it's a struct { unsigned int __sticky, __mask, __round; }.  */

int
fegetenv (fenv_t *envp)
{
#  ifdef __SOFTFP__
  return -1;
#  else
  unsigned int fpscr;
  _FPU_GETCW (fpscr);
#   if defined __OpenBSD__
  envp->__sticky = fpscr & FE_ALL_EXCEPT;
  envp->__mask = (fpscr >> 8) & FE_ALL_EXCEPT;
  envp->__round = (fpscr >> 22) & 3;
#   else
  * (unsigned int *) envp = fpscr;
#   endif
  return 0;
#  endif
}

int
fesetenv (fenv_t const *envp)
{
#  ifdef __SOFTFP__
  return -1;
#  else
  unsigned int fpscr, orig_fpscr;
  _FPU_GETCW (orig_fpscr);
  /* On *BSD and Android, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    /* Default: rounding direction = FE_TONEAREST, exception trap bits = 0,
       exception flags = 0.  */
    fpscr = (orig_fpscr & 0x00086060) | 0x00000000;
  else
    /* Here, glibc ignores orig_fpscr entirely...  */
    fpscr = (orig_fpscr & ~((3U << 22) | (FE_ALL_EXCEPT << 8) | FE_ALL_EXCEPT))
#   if defined __OpenBSD__
      | ((envp->__round & 3U) << 22)
      | ((envp->__mask & FE_ALL_EXCEPT) << 8)
      | (envp->__sticky & FE_ALL_EXCEPT)
#   else
      | (* (unsigned int const *) envp & ((3U << 22) | (FE_ALL_EXCEPT << 8) | FE_ALL_EXCEPT))
#   endif
      ;
  if (((fpscr ^ orig_fpscr) & ~0xF0000000) != 0)
    _FPU_SETCW (fpscr);
  return 0;
#  endif
}

# elif defined __alpha

/* On all OSes except OpenBSD, fenv_t is binary-equivalent to an 'unsigned long'.
   On OpenBSD, it's a struct { unsigned int __sticky, __mask, __round; }.  */

/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL.  */
#  define _FE_ALL_EXCEPT (0x3FUL * FE_INVALID)

int
fegetenv (fenv_t *envp)
{
  unsigned long swcr = __ieee_get_fp_control ();
  unsigned long fpcr;
  _FPU_GETCW (fpcr);
#   if defined __OpenBSD__
  envp->__sticky = (swcr >> 17) & _FE_ALL_EXCEPT;
  envp->__mask = (swcr >> 1) & _FE_ALL_EXCEPT;
  envp->__round = (fpcr >> 58) & 0x3UL;
#   else
  * (unsigned long *) envp =
    (fpcr & 0x0C00000000000000UL) /* rounding direction */
    | (swcr & 0x007E307EUL); /* exception flags,
                                mapping of denormal inputs and outputs,
                                exception trap bits */
#   endif
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned long env;
  /* On *BSD, FE_DFL_ENV is the address of a global variable;
     on glibc, it is a magic address.  */
  if (envp == FE_DFL_ENV)
    /* Default: rounding direction = FE_TONEAREST, exception flags = 0,
       mapping = 0, exception trap bits = 0.  */
    env = 0x0800000000000000UL;
  else
    {
#   if defined __OpenBSD__
      env = ((unsigned long) (envp->__round & 0x3UL) << 58)
            | ((envp->__sticky & _FE_ALL_EXCEPT) << 17)
            | ((envp->__mask & _FE_ALL_EXCEPT) << 1);
#   else
      env = * (unsigned long const *) envp;
#   endif
    }

  /* Set the rounding direction.  */
  unsigned long fpcr, orig_fpcr;
  _FPU_GETCW (orig_fpcr);
  fpcr = (orig_fpcr & ~0x0C00000000000000UL) | (env & 0x0C00000000000000UL);
  if (fpcr != orig_fpcr)
    _FPU_SETCW (fpcr);
  /* Set the exception flags, the mapping of denormal inputs and outputs, and
     the exception trap bits.  */
  __ieee_set_fp_control (env & 0x007E307EUL);

  return 0;
}

# elif defined __hppa

/* On all OSes except glibc, fenv_t is binary-equivalent to an 'unsigned int'.
   On glibc, it's a struct { unsigned int __status_word, __exception[7]; },
   of which only the __status_word field is used.  */

int
fegetenv (fenv_t *envp)
{
  unsigned int fpstatus;
  _FPU_GETCW (fpstatus);
  * (unsigned int *) envp = fpstatus;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int fpstatus, orig_fpstatus;
  _FPU_GETCW (orig_fpstatus);
  /* On *BSD, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    /* Default: exception flags = 0, rounding direction = FE_TONEAREST,
       exception trap bits = 0.  */
    fpstatus = 0x00000000U;
  else
    {
      unsigned int env = * (unsigned int const *) envp;
      fpstatus = (orig_fpstatus & ~0xF800061FU) | (env & 0xF800061FU);
    }
  if (fpstatus != orig_fpstatus)
    _FPU_SETCW (fpstatus);
  return 0;
}

# elif defined __ia64__

/* On all OSes, fenv_t is binary-equivalent to an 'unsigned long'.  */

/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL.  */
#  define _FE_ALL_EXCEPT (0x3FUL * FE_INVALID)

int
fegetenv (fenv_t *envp)
{
  unsigned long fpsr;
  _FPU_GETCW (fpsr);
  * (unsigned long *) envp = fpsr;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned long env;
  /* On NetBSD, FE_DFL_ENV is the address of a global variable;
     on glibc, it is a magic address.  */
  if (envp == FE_DFL_ENV)
    /* Default:
       sf0 = 0b0000000001100, sf1 = 0b0000001001110, sf2 = sf3 = 0000001001100
       i.e. precision control = 80-bits "extended",
            rounding direction = FE_TONEAREST,
            exception flags = 0,
       exceptions trap bits = all 1, i.e. all masked.  */
    env = 0x0009804C0270033FUL;
  else
    env = * (unsigned long const *) envp;
  unsigned long fpsr = env;
  _FPU_SETCW (fpsr);
  return 0;
}

# elif defined __m68k__

/* On all OSes, fenv_t is binary-equivalent to a struct
   { unsigned int control_register, status_register, instruction_address; }.  */

#  define _FPU_GETFPIAR(cw) __asm__ __volatile__ ("fmove%.l %/fpiar, %0" : "=dm" (cw))
#  define _FPU_SETFPIAR(cw) __asm__ __volatile__ ("fmove%.l %0, %/fpiar" : : "dm" (cw))

int
fegetenv (fenv_t *envp)
{
  unsigned int fpcr;
  unsigned int fpsr;
  unsigned int fpiar;
  _FPU_GETCW (fpcr);
  _FPU_GETFPSR (fpsr);
  _FPU_GETFPIAR (fpiar);
  ((unsigned int *) envp)[0] = fpcr;
  ((unsigned int *) envp)[1] = fpsr;
  ((unsigned int *) envp)[2] = fpiar;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int env_fpcr;
  unsigned int env_fpsr;
  if (envp == FE_DFL_ENV)
    {
      /* Default: exceptions trap bits = 0, rounding direction = FE_TONEAREST.  */
      env_fpcr = 0;
      /* Default: exception flags = 0.  */
      env_fpsr = 0;
    }
  else
    {
      /* No need to restore the instruction address here.  */
      env_fpcr = ((unsigned int const *) envp)[0];
      env_fpsr = ((unsigned int const *) envp)[1];
    }
  unsigned int fpcr, orig_fpcr;
  unsigned int fpsr, orig_fpsr;
  unsigned int fpiar, orig_fpiar;
  _FPU_GETCW (orig_fpcr);
  _FPU_GETFPSR (orig_fpsr);
  _FPU_GETFPIAR (orig_fpiar);
  /* glibc uses 0x3E30U here.  I think 0xFF30U is a better choice.  */
  fpcr = (orig_fpcr & ~0xFF30U) | (env_fpcr & 0xFF30U);
  fpsr = (orig_fpsr & ~0x00F8U) | (env_fpsr & 0x00F8U);
  fpiar = orig_fpiar;
  _FPU_SETCW (fpcr);
  _FPU_SETFPSR (fpsr);
  _FPU_SETFPIAR (fpiar);
  return 0;
}

# elif defined __mips__

/* On all OSes, fenv_t is binary-equivalent to an 'unsigned int'.  */

int
fegetenv (fenv_t *envp)
{
  unsigned int fcsr;
  _FPU_GETCW (fcsr);
  * (unsigned int *) envp = fcsr;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int env;
  /* On *BSD, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    /* Default: exceptions trap bits = 0, exception flags = 0,
       rounding direction = FE_TONEAREST.  */
    env = 0x00000000U;
  else
    env = * (unsigned int const *) envp;
  unsigned int fcsr = env;
  _FPU_SETCW (fcsr);
  return 0;
}

# elif defined __loongarch__

/* On all OSes, fenv_t is binary-equivalent to an 'unsigned int'.  */

int
fegetenv (fenv_t *envp)
{
  unsigned int fcsr;
  _FPU_GETCW (fcsr);
  * (unsigned int *) envp = fcsr;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int env;
  if (envp == FE_DFL_ENV)
    /* Default: exception flags = 0, rounding direction = FE_TONEAREST,
       exceptions trap bits = 0.  */
    env = 0x00000000U;
  else
    env = * (unsigned int const *) envp;
  unsigned int fcsr = env;
  _FPU_SETCW (fcsr);
  return 0;
}

# elif defined __powerpc__

/* On all OSes except *BSD and AIX, fenv_t is a 'double'.
   On *BSD, it's an 'unsigned int'.
   On AIX, it's a struct { unsigned short rmode; unsigned int fpstat, trapstate; }.
   But AIX has already been dealt with above.  */

#  if defined __linux__
#   include <sys/prctl.h>
#  endif

int
fegetenv (fenv_t *envp)
{
#  if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
  union { unsigned long long u; double f; } memenv;
  _FPU_GETCW_AS_DOUBLE (memenv.f);
  * (unsigned int *) envp = (unsigned int) memenv.u;
#  else
  _FPU_GETCW_AS_DOUBLE (*envp);
#  endif
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  union { unsigned long long u; double f; } orig_memenv, memenv;
  _FPU_GETCW_AS_DOUBLE (orig_memenv.f);
  /* On glibc and *BSD, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    memenv.u = 0xFFF8000000000000ULL;
  else
#  if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
    memenv.u = * (unsigned int const *) envp;
#  else
    memenv.f = *envp;
#  endif
  if (!(memenv.u == orig_memenv.u))
    {
      if ((orig_memenv.u & 0x000000f8) == 0 && (memenv.u & 0x000000f8) != 0)
        {
          /* Put the thread into precise trapping mode.  */
#  if defined __linux__ || defined __NetBSD__
          prctl (PR_SET_FPEXC, PR_FP_EXC_PRECISE);
#  endif
        }
      _FPU_SETCW_AS_DOUBLE (memenv.f);
      if ((orig_memenv.u & 0x000000f8) != 0 && (memenv.u & 0x000000f8) == 0)
        {
          /* Put the thread into no-trapping mode.  */
#  if defined __linux__ || defined __NetBSD__
          prctl (PR_SET_FPEXC, PR_FP_EXC_DISABLED);
#  endif
        }
    }
  return 0;
}

# elif defined __riscv

/* On all OSes except FreeBSD, fenv_t is binary-equivalent to an 'unsigned int'.
   On FreeBSD, it's binary-equivalent to an 'unsigned long'.  */

#  define _FPU_GETCW(cw) __asm__ __volatile__ ("frcsr %0" : "=r" (cw))
#  define _FPU_SETCW(cw) __asm__ __volatile__ ("fscsr %z0" : : "rJ" (cw))

int
fegetenv (fenv_t *envp)
{
  unsigned int fcsr;
  _FPU_GETCW (fcsr);
#  if defined __FreeBSD__
  * (unsigned long *) envp = fcsr;
#  else
  * (unsigned int *) envp = fcsr;
#  endif
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int env;
  /* On *BSD and Android, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    /* Default: rounding direction = FE_TONEAREST, exception flags = 0.  */
    env = 0x00000000U;
  else
#  if defined __FreeBSD__
    env = * (unsigned long const *) envp;
#  else
    env = * (unsigned int const *) envp;
#  endif
  unsigned int fcsr = env;
  _FPU_SETCW (fcsr);
  return 0;
}

# elif defined __s390__ || defined __s390x__

/* On all OSes, fenv_t is binary-equivalent to a struct whose first (and only
   relevant) field is an 'unsigned int'.  */

int
fegetenv (fenv_t *envp)
{
  unsigned int fpc;
  _FPU_GETCW (fpc);
  * (unsigned int *) envp = fpc;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int env;
  if (envp == FE_DFL_ENV)
    /* Default: exceptions trap bits = 0, exception flags = 0,
       rounding direction = FE_TONEAREST.  */
    env = 0x00000000U;
  else
    env = * (unsigned int const *) envp;
  unsigned int fpc = env;
  _FPU_SETCW (fpc);
  return 0;
}

# elif defined __sh__

/* On all OSes, fenv_t is binary-equivalent to an 'unsigned int'.  */

int
fegetenv (fenv_t *envp)
{
  unsigned int fpscr;
  _FPU_GETCW (fpscr);
  * (unsigned int *) envp = fpscr;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned int env;
  /* On OpenBSD, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    /* Default: PR = 1, exceptions trap bits = 0, exception flags = 0,
       rounding direction = FE_TONEAREST.  */
    env = 0x00080000U;
  else
    env = * (unsigned int const *) envp;
  unsigned int fpscr = env;
  _FPU_SETCW (fpscr);
  return 0;
}

# elif defined __sparc

/* On all OSes except Solaris, fenv_t is binary-equivalent to an 'unsigned long'.
   On Solaris, it's a struct { __fex_handler_t __handlers; unsigned long __fsr; }.  */

int
fegetenv (fenv_t *envp)
{
  unsigned long fsr;
  _FPU_GETCW (fsr);
#  if defined __sun
  fex_getexcepthandler (&envp->__handlers, FEX_ALL);
  envp->__fsr = fsr;
#  else
  * (unsigned long *) envp = fsr;
#  endif
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned long fsr;
  /* On *BSD and Solaris, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    {
      /* Default: rounding direction = FE_TONEAREST, exceptions trap bits = 0,
         exception flags = 0.  */
      fsr = 0x00000000U;
    }
  else
    {
#  if defined __sun
      fex_setexcepthandler (&envp->__handlers, FEX_ALL);
      fsr = envp->__fsr;
#  else
      fsr = * (unsigned long const *) envp;
#  endif
    }
  _FPU_SETCW (fsr);
  return 0;
}

# else

#  if defined __GNUC__ || defined __clang__
#   warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>."
#  endif
#  define NEED_FALLBACK 1

# endif

#else

/* The compiler does not support __asm__ statements or equivalent
   intrinsics.  */

# if defined __sun && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)) && defined __SUNPRO_C
/* Solaris/i386, Solaris/x86_64.  */

/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL.  */
#  define _FE_ALL_EXCEPT (0x3F * FE_INVALID)

/* Accessors for the 387 unit's special registers.  */

static void
getfctrl (unsigned short *fctrl_p)
{
#  if defined __x86_64__ || defined _M_X64
  asm ("fnstcw (%rdi)");
#  else
  /* The compiler generates a stack frame.  Therefore the first argument is in
     8(%ebp), not in 4(%esp).  */
  asm ("movl 8(%ebp),%eax");
  asm ("fnstcw (%eax)");
#  endif
}

static void
getfstat (unsigned short *fstat_p)
{
#  if defined __x86_64__ || defined _M_X64
  asm ("fnstsw (%rdi)");
#  else
  /* The compiler generates a stack frame.  Therefore the first argument is in
     8(%ebp), not in 4(%esp).  */
  asm ("movl 8(%ebp),%eax");
  asm ("fnstsw (%eax)");
#  endif
}

static void
getenv387 (x86_387_fenv_t *env387_p)
{
#  if defined __x86_64__ || defined _M_X64
  asm ("fnstenv (%rdi)");
#  else
  /* The compiler generates a stack frame.  Therefore the first argument is in
     8(%ebp), not in 4(%esp).  */
  asm ("movl 8(%ebp),%eax");
  asm ("fnstenv (%eax)");
#  endif
}

static void
setenv387 (x86_387_fenv_t const *env387_p)
{
#  if defined __x86_64__ || defined _M_X64
  asm ("fldenv (%rdi)");
#  else
  /* The compiler generates a stack frame.  Therefore the first argument is in
     8(%ebp), not in 4(%esp).  */
  asm ("movl 8(%ebp),%eax");
  asm ("fldenv (%eax)");
#  endif
}

/* Accessors for the mxcsr register.  */

static void
getssecw (unsigned int *mxcsr_p)
{
#  if defined __x86_64__ || defined _M_X64
  asm ("stmxcsr (%rdi)");
#  else
  /* The compiler generates a stack frame.  Therefore the first argument is in
     8(%ebp), not in 4(%esp).  */
  asm ("movl 8(%ebp),%eax");
  asm ("stmxcsr (%eax)");
#  endif
}

static void
setssecw (unsigned int const *mxcsr_p)
{
#  if defined __x86_64__ || defined _M_X64
  asm ("ldmxcsr (%rdi)");
#  else
  /* The compiler generates a stack frame.  Therefore the first argument is in
     8(%ebp), not in 4(%esp).  */
  asm ("movl 8(%ebp),%eax");
  asm ("ldmxcsr (%eax)");
#  endif
}

int
fegetenv (fenv_t *envp)
{
  fex_getexcepthandler (&envp->__handlers, FEX_ALL);

  unsigned short fctrl;
  unsigned short fstat;
  unsigned int mxcsr;
  getfctrl (&fctrl);
  getfstat (&fstat);
  getssecw (&mxcsr);
  envp->__fsr =
    (((unsigned int) (fctrl ^ 0x3F) & ~0xE0C0U) << 16)
    | ((unsigned int) fstat | (mxcsr & 0x3F));

  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned short env_fctrl;
  unsigned int env_mxcsr;
  /* On Solaris, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    {
      env_fctrl = 0x3F;
      env_mxcsr = 0x3F << 7;
    }
  else
    {
      fex_setexcepthandler (&envp->__handlers, FEX_ALL);
      env_fctrl = (envp->__fsr >> 16) ^ 0x3F;
      env_mxcsr = envp->__fsr & 0x3F;
    }

  /* Store the exception flags in mxcsr, not in env387.__status_word.  */

  /* In the SSE unit.  */
  unsigned int mxcsr;
  getssecw (&mxcsr);
  mxcsr = (mxcsr & ~0x3F) | env_mxcsr;
  setssecw (&mxcsr);

  /* In the 387 unit.  */
  x86_387_fenv_t env387;
  getenv387 (&env387);
  /* Note: fnstenv masks all floating-point exceptions until the setenv387
     below.  */
  env387.__control_word = env_fctrl;
  env387.__status_word &= ~0x3F;
  setenv387 (&env387);

  return 0;
}

#elif defined __sun && defined __sparc && defined __SUNPRO_C
/* Solaris/sparc.  */

/* Accessors for the fsr register.  */

static void
getfsr (unsigned long *fsr_p)
{
#  if defined __sparcv9 || defined __arch64__ /* sparc64 */
  asm ("stx %fsr,[%i0]");
#  else
  asm ("st %fsr,[%i0]");
#  endif
}

static void
setfsr (unsigned long const *fsr_p)
{
#  if defined __sparcv9 || defined __arch64__ /* sparc64 */
  asm ("ldx [%i0],%fsr");
#  else
  asm ("ld [%i0],%fsr");
#  endif
}

int
fegetenv (fenv_t *envp)
{
  unsigned long fsr;
  getfsr (&fsr);
  fex_getexcepthandler (&envp->__handlers, FEX_ALL);
  envp->__fsr = fsr;
  return 0;
}

int
fesetenv (fenv_t const *envp)
{
  unsigned long fsr;
  /* On *BSD and Solaris, FE_DFL_ENV is the address of a global variable;
     no special code is needed in this case.  */
  if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV)
    {
      /* Default: rounding direction = FE_TONEAREST, exceptions trap bits = 0,
         exception flags = 0.  */
      fsr = 0x00000000U;
    }
  else
    {
      fex_setexcepthandler (&envp->__handlers, FEX_ALL);
      fsr = envp->__fsr;
    }
  setfsr (&fsr);
  return 0;
}

# else

#  define NEED_FALLBACK 1

# endif

#endif

#if NEED_FALLBACK

/* A dummy fallback.  */

int
fegetenv (fenv_t *envp)
{
  return -1;
}

int
fesetenv (fenv_t const *envp)
{
  return -1;
}

#endif
