/* Copyright (C) 2000-2005 Claus-Justus Heine
 *                         <claus@mathematik.uni-freiburg.de>
 */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif

#include <stdio.h>
#include <string.h>
#include <fenv.h>
#include <signal.h>
#include <stdlib.h>

#ifndef MSG_TRIGGER
# define MSG_TRIGGER 1000000
#endif

static int count;

#if HAVE_STRUCT_SIGACTION_SA_SIGACTION

static const char *fpe_sigaction_strerr(int code, char *buffer)
{
#define CASE_CPERRSTR(arg, descr) \
	case arg: sprintf(buffer, #arg"(%d): \""descr"\"", arg); break
#define FPE_NONE 0
  switch (code) {
    CASE_CPERRSTR(FPE_NONE,   "no error");
    CASE_CPERRSTR(FPE_INTDIV, "integer divide by zero");
    CASE_CPERRSTR(FPE_INTOVF, "integer overflow");
    CASE_CPERRSTR(FPE_FLTDIV, "floating point divide by zero");
    CASE_CPERRSTR(FPE_FLTOVF, "floating point overflow");
    CASE_CPERRSTR(FPE_FLTUND, "floating point underflow");
    CASE_CPERRSTR(FPE_FLTRES, "floating point inexact result");
    CASE_CPERRSTR(FPE_FLTINV, "floating point invalid operation");
    CASE_CPERRSTR(FPE_FLTSUB, "subscript out of range");
  }
  return buffer;
}

static void sigfpe_handler(int sig, siginfo_t *sinfo, void *p)
{
  char buffer[1024];

  if ((count ++) % MSG_TRIGGER == 0) {
    fprintf(stderr,
	    "Caught floating point exception: %s. Total number %d\n",
	    fpe_sigaction_strerr(sinfo->si_code, buffer), count);
    fprintf(stderr, "Faulting address: @%p, my address: @%p\n",
	    sinfo->si_addr, (void *)(unsigned long)sigfpe_handler);
  }
#if 0
  /* how to clear the error???
   *
   * As the signal handler starts with a clean FPU environment, it
   * just doesn't help a bit to clear the error state here, i.e. to
   * write directly to the FPU-registers. It is possible to modify the
   * signal stack, so that the fpu-error state is cleared on return
   * from the signal handler. This is -- of course -- not the nice way
   * to do it.
   *
   * Acutally, the only useful solution would be to parse the opcode
   * at si_addr and it's operands and then to decide what to do.
   *
   * We could shut-up the xmm unit by masking all exceptions.
   */
#else
  /* doesn't make any sense, as the insulting instruction is just
   * executed again if we do not do anything 'bout it, i.e. do a
   * longjmp() to a defined code-place or something like that.
   */
  exit(1);
#endif
}

#else /* old signal handler */

static const char *fpe_strerr(int raised, char *buffer)
{
  *buffer = '\0';

#ifdef FE_INEXACT
  if (raised & FE_INEXACT) {
    strcat(buffer, "FE_INEXACT ");
  }
#endif
#ifdef FE_DIVBYZERO
  if (raised & FE_DIVBYZERO) {
    strcat(buffer, "FE_DIVBYZERO ");
  }
#endif
#ifdef FE_UNDERFLOW
  if (raised & FE_UNDERFLOW) {
    strcat(buffer, "FE_UNDERFLOW ");
  }
#endif
#ifdef FE_OVERFLOW
  if (raised & FE_OVERFLOW) {
    strcat(buffer, "FE_OVERFLOW ");
  }
#endif
#ifdef FE_INVALID
  if (raised & FE_INVALID) {
    strcat(buffer, "FE_INVALID ");
  }
#endif
  if (*buffer != '\0') {
    buffer[strlen(buffer)-1] = '\0';
  }
  return buffer;
}

static void sigfpe_handler(int sig)
{
  int raised;
  char buffer[1024];

  raised = fetestexcept(FE_ALL_EXCEPT);
  if ((count ++) % MSG_TRIGGER == 0) {
    fprintf(stderr,
	    "Caught floating point exception: %s (0x%04x). Total number %d\n",
	    fpe_strerr(raised, buffer), raised, count);
  }
  /* Doesn't help, see comments for sigaction SIGFPE-handler.
   */
  feclearexcept(FE_ALL_EXCEPT);
}
#endif

void sigfpe_init(int unmask)
{
  struct sigaction fpe_sigaction;
  sigset_t signals;
  
  memset(&fpe_sigaction, 0, sizeof(struct sigaction));
#if HAVE_STRUCT_SIGACTION_SA_SIGACTION
  fpe_sigaction.sa_sigaction = sigfpe_handler;
  fpe_sigaction.sa_flags = SA_RESTART|SA_SIGINFO;
#else
  fpe_sigaction.sa_handler = sigfpe_handler;
  fpe_sigaction.sa_flags = SA_RESTART;
#endif
  sigemptyset(&fpe_sigaction.sa_mask);

  sigaction(SIGFPE, &fpe_sigaction, NULL);

  fesetenv(FE_DFL_ENV);
  if (unmask) {
    feclearexcept(FE_ALL_EXCEPT);
#ifdef FE_NOMASK_ENV
    feenableexcept(FE_ALL_EXCEPT);
    fedisableexcept(FE_INEXACT);
#endif
  }

  /* unblock SIGFPE, unnecessary 'cause it is unblocked by default */
  sigemptyset(&signals);
  sigaddset(&signals, SIGFPE);
  sigprocmask(SIG_UNBLOCK, &signals, NULL);
}

#if TEST_SIGFPE

volatile double foobar = 0.0;

static void exit_handler(int signo)
{
  fprintf(stderr, "Caught signal %d\n", signo);
  exit(1);
}

static void print_count(void)
{
  fprintf(stderr, "Final floating point error count: %d\n", count);
}

int main(int argc, char *argv[])
{
  atexit(print_count);
  signal(SIGTERM, exit_handler);
  signal(SIGKILL, exit_handler);
  signal(SIGINT, exit_handler);
  sigfpe_init(1);
  feenableexcept(FE_ALL_EXCEPT);
  foobar = 1.0/foobar;
  pause();
}
#endif

/*
 * Local variables: ***
 *  c-basic-offset: 2 ***
 *  compile-command: "make CFLAGS='-DTEST_SIGFPE=1 -DHAVE_STRUCT_SIGACTION_SA_SIGACTION=1 -DHAVE_ASM_I386_UCONTEXT_H=1 -DMSG_TRIGGER=1000000 -D_GNU_SOURCE -march=pentium4 -mfpmath=sse' LDFLAGS='-lm' sigfpe" ***
 * End: ***
 */
