/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*******************************************************************************
*                                                                              *
*     File:  fenv.c                                                             *
*                                                                              *
*     Contains:   C source code for PowerPC implementations of floating-point  *
*     environmental functions defined in C99.                                  *
*                                                                              *
*     Copyright © 1992-2001 Apple Computer, Inc.  All rights reserved.         *
*                                                                              *
*     Written by Stephen C. Peters, started in November 2001.                  *
*                                                                              *
*     A MathLib v5 file.                                                       *
*                                                                              *
*     Change History (most recent first):                                      *
*                                                                              *
*     21 Nov 01   scp   First derived from v3MathLib's fenv.c.                 *
*                                                                              *
*     A version of gcc higher than 932 is required.                            *
*                                                                              *
*     GCC compiler options:                                                    *
*           optimization level 3 (-O3)                                         *
*           -fschedule-insns -finline-functions -funroll-all-loops             *
*                                                                              *
*******************************************************************************/

#include "fenv.h"

/*  rounding direction mode bits  */
#define      FE_ALL_RND           0x00000c00

/* iapx-v1 Figure 7-13 */
typedef struct {
    unsigned short __control;
    unsigned short __reserved1;
    unsigned short __status;
    unsigned short __reserved2;
    unsigned int __private3;
    unsigned int __private4;
    unsigned int __private5;
    unsigned int __private6;
    unsigned int __private7;
} __fpustate_t;

/******************************************************************************
*   Implementations of functions which provide access to the exception flags. *
*   The "int" input argument is constructed by bitwise ORs of the exception   *
*   macros defined in fenv.h:  for example, FE_OVERFLOW | FE_INEXACT.         *
******************************************************************************/

static void _fegetexceptflag ( fexcept_t *flagp, int excepts )
{
    int state;
    
    asm volatile ("fnstsw %0" : "=m" (state));
    
    *flagp = state & excepts & FE_ALL_EXCEPT;
}

static void _fesetexceptflag ( const fexcept_t *flagp, int excepts )
{
    int state;
    __fpustate_t currfpu;
    
    asm volatile ("fnstenv %0" : "=m" (currfpu));

    state = currfpu.__status;
    state &= ~( excepts & FE_ALL_EXCEPT ); 	   // clear just the bits indicated
    state |= ( *flagp & excepts & FE_ALL_EXCEPT ); // latch the specified bits
    currfpu.__status = state; 

    asm volatile ("fldenv %0" : : "m" (currfpu));
}

#if defined(BUILDING_FOR_CARBONCORE_LEGACY)
   
/* default environment object        */
const fenv_t _FE_DFL_ENV = (const fenv_t) { FE_TONEAREST | FE_ALL_EXCEPT , 0 }; 

/****************************************************************************
   the "feclearexcept" function clears the exceptions represented by its
   argument. 
****************************************************************************/

int feclearexcept ( int excepts )
{
    fexcept_t zero = 0;
    _fesetexceptflag ( &zero, excepts );
    return 0;
}

/****************************************************************************
   the "feraiseexcept" function raises the exceptions represented by its
   argument.
****************************************************************************/

int feraiseexcept ( int excepts )
{
    fexcept_t t = excepts;
    
    _fesetexceptflag ( &t, excepts );
    asm volatile ("fwait"); 			// and raise the exception(s)
    return 0;
}
      
/****************************************************************************
   The function "fetestexcept" determines which of the specified subset of
   the exception flags are currently set.  It returns the bitwise OR of a
   subset of the exception macros included in "excepts".
****************************************************************************/

int fetestexcept ( int excepts )
{
    int state;
    
    asm volatile ("fnstsw %0" : "=m" (state));
    
    return (state & excepts & FE_ALL_EXCEPT);
}


/*  The following functions provide control of rounding direction modes. */

/****************************************************************************
   The function "fegetround" returns the value of the rounding direction
   macro which represents the current rounding direction.
****************************************************************************/
int
fegetround (void)
{
    int state;
    
    asm volatile ("fnstcw %0" : "=m" (state));
    
    // FE_* rounding enums conveniently mapped to HW bits
    return (state & FE_ALL_RND);
}

/****************************************************************************
  The function "fesetround" establishes the rounding direction
   represented by its argument.  It returns zero if and only if
   the argument matches a rounding direction macro.  If not, the 
   rounding direction is not changed.
****************************************************************************/

int
fesetround (int round)
{    
    if ((round & ~FE_ALL_RND))
        return 1;
    else
    {
        int state;
        
        asm volatile ("fnstcw %0" : "=m" (state));
        
        state &= ~FE_ALL_RND;
        state |= ( round & FE_ALL_RND ); 
    
        asm volatile ("fldcw %0" : : "m" (state));
        return 0;
    }
}

/*    The following functions manage the floating-point environment---
      exception flags and dynamic modes---as one entity.                  */

/****************************************************************************
   The function "fgetenv" stores the current environment in the
   object pointed to by its pointer argument "envp".
****************************************************************************/
   
int fegetenv ( fenv_t *envp )
{
    __fpustate_t currfpu;
    
    asm volatile ("fnstenv %0" : "=m" (currfpu));
    
    envp->__control = currfpu.__control;
    envp->__status = currfpu.__status;
    return 0;
}


/****************************************************************************
   The function "feholdexcept" saves the current environment in
   the object pointed to by its argument "envp" and clears the
   exception flags.  It returns zero.  This function supersedes
   the SANE function "procentry".
****************************************************************************/
   
int feholdexcept ( fenv_t *envp )
{
    int state;
    
    fegetenv ( envp );
    
    state = envp->__control;
    state |= FE_ALL_EXCEPT; // FPU shall handle all exceptions

    asm volatile ("fldcw %0" : : "m" (state));
    
    return 0;
}


/****************************************************************************
   The function "fesetenv" establishes the floating-point environment
   represented by the object pointed to by its argument "envp".  The
   value of "*env_p" must be set by a call to "fegetenv" or
   "feholdexcept", by the macro "FE_DFL_ENV", or by an implementation-
   defined macro of type "fenv_t".
****************************************************************************/
   
int fesetenv ( const fenv_t *envp )
{
    __fpustate_t currfpu;
    asm volatile ("fnstenv %0" : "=m" (currfpu));

    currfpu.__control &= ~( FE_ALL_RND | FE_ALL_EXCEPT );
    currfpu.__control |= ( envp->__control & ( FE_ALL_RND | FE_ALL_EXCEPT ) );
    
    currfpu.__status &= ~FE_ALL_EXCEPT;
    currfpu.__status |= ( envp->__status & FE_ALL_EXCEPT );
    
    asm volatile ("fldenv %0" : : "m" (currfpu));
    return 0;
}

/****************************************************************************
   The function "feupdateenv" saves the current exceptions in its
   automatic storage, installs the environment pointed to by its
   pointer argument "envp", and then re-raises the saved exceptions.
   This function, which supersedes the SANE function "procexit", can
   be used in conjunction with "feholdexcept" to write routines which
   hide spurious exceptions from their callers.
****************************************************************************/
   
int feupdateenv ( const fenv_t *envp )
{
    __fpustate_t currfpu;
    asm volatile ("fnstenv %0" : "=m" (currfpu));
    
    currfpu.__control &= ~( FE_ALL_RND | FE_ALL_EXCEPT );
    currfpu.__control |= ( envp->__control & ( FE_ALL_RND | FE_ALL_EXCEPT ) );
    
    currfpu.__status |= ( envp->__status & FE_ALL_EXCEPT ); // add envp's to current excepts
    
    asm volatile ("fldenv %0" : : "m" (currfpu)); // install envp's control word, preserving status word
    asm volatile ("fwait"); 			  // and raise the exception(s)
    return 0;
}


/* Legacy entry point */
void fegetexcept ( fexcept_t *flagp, int excepts )
{
    _fegetexceptflag (flagp, excepts );
}

/* Legacy entry point */
void fesetexcept ( fexcept_t *flagp, int excepts )
{
    _fesetexceptflag ( flagp, excepts );
}

#else /* !BUILDING_FOR_CARBONCORE_LEGACY */

/****************************************************************************
   "fegetexceptflag" stores a representation of the exception flags indicated
   by the argument "excepts" through the pointer argument "flagp".
****************************************************************************/

int fegetexceptflag ( fexcept_t *flagp, int excepts )
{
    _fegetexceptflag (flagp, excepts );
    return 0;
}

/****************************************************************************
   "fesetexceptflag" sets the exception flags indicated by the argument "excepts",
   to the corresponding state represented in the object pointed to by "flagp".
   This function does not raise exceptions, but only sets the state of the
   flags.
****************************************************************************/

int fesetexceptflag ( const fexcept_t *flagp, int excepts )
{
    _fesetexceptflag ( flagp, excepts );
    return 0;
}

/****************************************************************************
    The float.h macro FLT_ROUNDS has a value derived from fegetround() --
    Addition rounds to 0: zero, 1: nearest, 2: +inf, 3: -inf, -1: unknown 
****************************************************************************/

int __fegetfltrounds( void ) 
{
    switch ( fegetround() )
    {
    case FE_TONEAREST:
        return 1;
    case FE_TOWARDZERO:
        return 0;
    case FE_UPWARD:
        return 2;
    case FE_DOWNWARD:
        return 3;
    default:
        return -1;
    }
}

#endif /* !BUILDING_FOR_CARBONCORE_LEGACY */
