///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1997, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
// 
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------
//
//	Functions to control floating point exceptions.
//
//------------------------------------------------------------------------

#include "IexMathFpu.h"

#include <stdint.h>
#include <IlmBaseConfig.h>
#include <stdio.h>

#if 0
    #include <iostream>
    #define debug(x) (std::cout << x << std::flush)
#else
    #define debug(x)
#endif

#if defined(HAVE_UCONTEXT_H) && (defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86))

#include <ucontext.h>
#include <signal.h>
#include <iostream>
#include <stdint.h>


IEX_INTERNAL_NAMESPACE_SOURCE_ENTER



namespace FpuControl
{

//-------------------------------------------------------------------
//
//    Modern x86 processors and all AMD64 processors have two
//    sets of floating-point control/status registers: cw and sw
//    for legacy x87 stack-based arithmetic, and mxcsr for
//    SIMD arithmetic.  When setting exception masks or checking
//    for exceptions, we must set/check all relevant registers,
//    since applications may contain code that uses either FP
//    model.
//
//    These functions handle both FP models for x86 and AMD64.
//
//-------------------------------------------------------------------

//-------------------------------------------------------------------
//
//    Restore the control register state from a signal handler
//    user context, optionally clearing the exception bits
//    in the restored control register, if applicable.
//
//-------------------------------------------------------------------

void restoreControlRegs (const ucontext_t & ucon,
			 bool clearExceptions = false);


//------------------------------------------------------------
//
//    Set exception mask bits in the control register state.
//    A value of 1 means the exception is masked, a value of
//    0 means the exception is enabled.
//
//    setExceptionMask returns the previous mask value.  If
//    the 'exceptions' pointer is non-null, it returns in 
//    this argument the FPU exception bits.
//
//------------------------------------------------------------

const int INVALID_EXC   = (1<<0);
const int DENORMAL_EXC  = (1<<1);
const int DIVZERO_EXC   = (1<<2);
const int OVERFLOW_EXC  = (1<<3);
const int UNDERFLOW_EXC = (1<<4);
const int INEXACT_EXC   = (1<<5);
const int ALL_EXC       = INVALID_EXC  | DENORMAL_EXC  | DIVZERO_EXC |
                          OVERFLOW_EXC | UNDERFLOW_EXC | INEXACT_EXC;

int setExceptionMask (int mask, int * exceptions = 0);
int getExceptionMask ();


//---------------------------------------------
//
//    Get/clear the exception bits in the FPU.
//
//---------------------------------------------

int  getExceptions ();
void clearExceptions ();


//------------------------------------------------------------------
//
//    Everything below here is implementation.  Do not use these
//    constants or functions in your applications or libraries.
//    This is not the code you're looking for.  Move along.
//
//    Optimization notes -- on a Pentium 4, at least, it appears
//    to be faster to get the mxcsr first and then the cw; and to
//    set the cw first and then the mxcsr.  Also, it seems to
//    be faster to clear the sw exception bits after setting
//    cw and mxcsr.
//
//------------------------------------------------------------------

static inline uint16_t
getSw ()
{
    uint16_t sw;
    asm volatile ("fnstsw %0" : "=m" (sw) : );
    return sw;
}

static inline void
setCw (uint16_t cw)
{
    asm volatile ("fldcw %0" : : "m" (cw) );
}

static inline uint16_t
getCw ()
{
    uint16_t cw;
    asm volatile ("fnstcw %0" : "=m" (cw) : );
    return cw;
}

static inline void
setMxcsr (uint32_t mxcsr, bool clearExceptions)
{
    mxcsr &= clearExceptions ? 0xffffffc0 : 0xffffffff;
    asm volatile ("ldmxcsr %0" : : "m" (mxcsr) );
}

static inline uint32_t
getMxcsr ()
{
    uint32_t mxcsr;
    asm volatile ("stmxcsr %0" : "=m" (mxcsr) : );
    return mxcsr;
}

static inline int
calcMask (uint16_t cw, uint32_t mxcsr)
{
    //
    // Hopefully, if the user has been using FpuControl functions,
    // the masks are the same, but just in case they're not, we
    // AND them together to report the proper subset of the masks.
    //

    return (cw & ALL_EXC) & ((mxcsr >> 7) & ALL_EXC);
}

inline int
setExceptionMask (int mask, int * exceptions)
{
    uint16_t cw = getCw ();
    uint32_t mxcsr = getMxcsr ();
    
    if (exceptions)
	*exceptions = (mxcsr & ALL_EXC) | (getSw () & ALL_EXC);

    int oldmask = calcMask (cw, mxcsr);

    //
    // The exception constants are chosen very carefully so that
    // we can do a simple mask and shift operation to insert
    // them into the control words.  The mask operation is for 
    // safety, in case the user accidentally set some other
    // bits in the exception mask.
    //

    mask &= ALL_EXC;
    cw = (cw & ~ALL_EXC) | mask;
    mxcsr = (mxcsr & ~(ALL_EXC << 7)) | (mask << 7);

    setCw (cw);
    setMxcsr (mxcsr, false);

    return oldmask;
}

inline int
getExceptionMask ()
{
    uint32_t mxcsr = getMxcsr ();
    uint16_t cw = getCw ();
    return calcMask (cw, mxcsr);
}

inline int
getExceptions ()
{
    return (getMxcsr () | getSw ()) & ALL_EXC;
}

void
clearExceptions ()
{
    uint32_t mxcsr = getMxcsr () & 0xffffffc0;
    asm volatile ("ldmxcsr %0\n"
		  "fnclex"
		  : : "m" (mxcsr) );
}

// If the fpe was taken while doing a float-to-int cast using the x87,
// the rounding mode and possibly the precision will be wrong.  So instead
// of restoring to the state as of the fault, we force the rounding mode
// to be 'nearest' and the precision to be double extended.
//
// rounding mode is in bits 10-11, value 00 == round to nearest
// precision is in bits 8-9, value 11 == double extended (80-bit)
//
const uint16_t cwRestoreMask = ~((3 << 10) | (3 << 8));
const uint16_t cwRestoreVal = (0 << 10) | (3 << 8);


#ifdef ILMBASE_HAVE_CONTROL_REGISTER_SUPPORT

inline void
restoreControlRegs (const ucontext_t & ucon, bool clearExceptions)
{
    setCw ((ucon.uc_mcontext.fpregs->cwd & cwRestoreMask) | cwRestoreVal);
    setMxcsr (ucon.uc_mcontext.fpregs->mxcsr, clearExceptions);
}

#else

//
// Ugly, the mxcsr isn't defined in GNU libc ucontext_t, but
// it's passed to the signal handler by the kernel.  Use
// the kernel's version of the ucontext to get it, see
// <asm/sigcontext.h>
//

#include <asm/sigcontext.h>

inline void
restoreControlRegs (const ucontext_t & ucon, bool clearExceptions)
{
    setCw ((ucon.uc_mcontext.fpregs->cw & cwRestoreMask) | cwRestoreVal);
    
    _fpstate * kfp = reinterpret_cast<_fpstate *> (ucon.uc_mcontext.fpregs);
    setMxcsr (kfp->magic == 0 ? kfp->mxcsr : 0, clearExceptions);
}

#endif

} // namespace FpuControl


namespace {

volatile FpExceptionHandler fpeHandler = 0;

extern "C" void
catchSigFpe (int sig, siginfo_t *info, ucontext_t *ucon)
{
    debug ("catchSigFpe (sig = "<< sig << ", ...)\n");

    FpuControl::restoreControlRegs (*ucon, true);

    if (fpeHandler == 0)
	return;

    if (info->si_code == SI_USER)
    {
	fpeHandler (0, "Floating-point exception, caused by "
		       "a signal sent from another process.");
	return;
    }

    if (sig == SIGFPE)
    {
	switch (info->si_code)
	{
	  //
	  // IEEE 754 floating point exceptions:
	  //

	  case FPE_FLTDIV:
	    fpeHandler (IEEE_DIVZERO, "Floating-point division by zero.");
	    return;

	  case FPE_FLTOVF:
	    fpeHandler (IEEE_OVERFLOW, "Floating-point overflow.");
	    return;

	  case FPE_FLTUND:
	    fpeHandler (IEEE_UNDERFLOW, "Floating-point underflow.");
	    return;

	  case FPE_FLTRES:
	    fpeHandler (IEEE_INEXACT, "Inexact floating-point result.");
	    return;

	  case FPE_FLTINV:
	    fpeHandler (IEEE_INVALID, "Invalid floating-point operation.");
	    return;

	  //
	  // Other arithmetic exceptions which can also
	  // be trapped by the operating system:
	  //

	  case FPE_INTDIV:
	    fpeHandler (0, "Integer division by zero.");
	    break;

	  case FPE_INTOVF:
	    fpeHandler (0, "Integer overflow.");
	    break;

	  case FPE_FLTSUB:
	    fpeHandler (0, "Subscript out of range.");
	    break;
	}
    }

    fpeHandler (0, "Floating-point exception.");
}

} // namespace

void
setFpExceptions (int when)
{
    int mask = FpuControl::ALL_EXC;

    if (when & IEEE_OVERFLOW)
	mask &= ~FpuControl::OVERFLOW_EXC;
    if (when & IEEE_UNDERFLOW)
	mask &= ~FpuControl::UNDERFLOW_EXC;
    if (when & IEEE_DIVZERO)
	mask &= ~FpuControl::DIVZERO_EXC;
    if (when & IEEE_INEXACT)
	mask &= ~FpuControl::INEXACT_EXC;
    if (when & IEEE_INVALID)
	mask &= ~FpuControl::INVALID_EXC;

    //
    // The Linux kernel apparently sometimes passes
    // incorrect si_info to signal handlers unless
    // the exception flags are cleared.
    //
    // XXX is this still true on 2.4+ kernels?
    //
    
    FpuControl::setExceptionMask (mask);
    FpuControl::clearExceptions ();
}


int
fpExceptions ()
{
    int mask = FpuControl::getExceptionMask ();

    int when = 0;

    if (!(mask & FpuControl::OVERFLOW_EXC))
	when |= IEEE_OVERFLOW;
    if (!(mask & FpuControl::UNDERFLOW_EXC))
	when |= IEEE_UNDERFLOW;
    if (!(mask & FpuControl::DIVZERO_EXC))
	when |= IEEE_DIVZERO;
    if (!(mask & FpuControl::INEXACT_EXC))
	when |= IEEE_INEXACT;
    if (!(mask & FpuControl::INVALID_EXC))
	when |= IEEE_INVALID;

    return when;
}

void
handleExceptionsSetInRegisters()
{
    if (fpeHandler == 0)
	return;

    int mask = FpuControl::getExceptionMask ();

    int exc = FpuControl::getExceptions();

    if (!(mask & FpuControl::DIVZERO_EXC) && (exc & FpuControl::DIVZERO_EXC))
    {
        fpeHandler(IEEE_DIVZERO, "Floating-point division by zero.");
        return;
    }

    if (!(mask & FpuControl::OVERFLOW_EXC) && (exc & FpuControl::OVERFLOW_EXC))
    {
        fpeHandler(IEEE_OVERFLOW, "Floating-point overflow.");
        return;
    }

    if (!(mask & FpuControl::UNDERFLOW_EXC) && (exc & FpuControl::UNDERFLOW_EXC))
    {
        fpeHandler(IEEE_UNDERFLOW, "Floating-point underflow.");
        return;
    }

    if (!(mask & FpuControl::INEXACT_EXC) && (exc & FpuControl::INEXACT_EXC))
    {
        fpeHandler(IEEE_INEXACT, "Inexact floating-point result.");
        return;
    }

    if (!(mask & FpuControl::INVALID_EXC) && (exc & FpuControl::INVALID_EXC))
    {
        fpeHandler(IEEE_INVALID, "Invalid floating-point operation.");
        return;
    }
}


void
setFpExceptionHandler (FpExceptionHandler handler)
{
    if (fpeHandler == 0)
    {
	struct sigaction action;
	sigemptyset (&action.sa_mask);
	action.sa_flags = SA_SIGINFO | SA_NOMASK;
	action.sa_sigaction = (void (*) (int, siginfo_t *, void *)) catchSigFpe;
	action.sa_restorer = 0;

	sigaction (SIGFPE, &action, 0);
    }

    fpeHandler = handler;
}


IEX_INTERNAL_NAMESPACE_SOURCE_EXIT


#else

#include <signal.h>
#include <assert.h>

IEX_INTERNAL_NAMESPACE_SOURCE_ENTER


namespace 
{
	volatile FpExceptionHandler fpeHandler = 0;
	void fpExc_(int x)
	{
	    if (fpeHandler != 0)
	    {
		fpeHandler(x, "");
	    }
	    else
	    {
		assert(0 != "Floating point exception");
	    }
	}
}

void
setFpExceptions( int )
{
}


void
setFpExceptionHandler (FpExceptionHandler handler)
{
    // improve floating point exception handling nanoscopically above "nothing at all"
    fpeHandler = handler;
    signal(SIGFPE, fpExc_);
}

int
fpExceptions()
{
    return 0;
}

void
handleExceptionsSetInRegisters()
{
    // No implementation on this platform
}

IEX_INTERNAL_NAMESPACE_SOURCE_EXIT

#endif
