Subject: floating point in signal handlers
To: None <tech-kern@netbsd.org>
From: Dr. Lex Wennmacher <wennmach@geo.Uni-Koeln.DE>
List: tech-kern
Date: 10/16/1999 21:58:12
--jRHKVT23PllUwdXP
Content-Type: text/plain; charset=us-ascii

Hi Folks,

currently it's not possible to do floating point arithmetic in a signal
handler. I'd like to see this restriction removed.

I've prepared an example, extract the attached shar archive and type "make"
(the example is for i386 only). Run "signal_nosave_fpu". This program installs
a signal handler that does floating point arithmetic. Watch what happens
when the signal handler gets executed. Depending on where the interrupt
occurs, you'll get a floating point exception, NaNs or Infs, or nothing
happens.

I see several possible ways to remove the no-floating-point-in-a-signal-hander-
restriction:

1) the kernel always saves and restores the FPU for the process when calling a
   signal handler;
2) the kernel does lazy saving of the FPU;
3) libc provides routines to save and restore the FPU. It is the responsibility
   of the programmer to call these if floating point arithmetic is done in a
   signal handler.
[4) I'm not shure about the following: in function calls, the compiler could
    implement saving of the FPU by the callee (as an option)?]

Option 1) may have performace impacts (on slow architectures). Option 2)
is not possible on all architectures, I was told, because on some archs
the FPU gets reset on disabling it. Option 2) could be made the default
on archs that support lazy saving of the FPU, while on the other archs
option 1 (probably as kernel or sysctl option) gets implemented.

A simple userland solution is option 3). This option could also be used as
interim solution until a kernel solution becomes available. 
As an i386 example, see signal_save_fpu.c and fpu.{c,h} in the attached shar
archive. 

<machine/fpu.h> provides a struct fpu_regs large enough to store all
FPU registers. libc provides two (self explanatory) routines:
void fpu_save(struct fpu_regs *)
void fpu_restore(struct fpu_regs *)

We'd just need these (quite simple) routines for all archs and we're set.
In addition to not require kernel changes, this option has the advantage
that I can use it directly in my userland checkpointing library :-)
[ chkpt currently suffers from not supporting floating point restore on
  all architectures ]

Comments?


Cheers
Lex

--jRHKVT23PllUwdXP
Content-Type: application/x-shar
Content-Disposition: attachment; filename="fpinhandler.shar"

# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	.
#	./signal_nosave_fpu.c
#	./Makefile
#	./signal_save_fpu.c
#	./fpu.c
#	./fpu.h
#
echo c - .
mkdir -p . > /dev/null 2>&1
echo x - ./signal_nosave_fpu.c
sed 's/^X//' >./signal_nosave_fpu.c << 'END-of-./signal_nosave_fpu.c'
X#include <sys/time.h>
X#include <math.h>
X#include <stdio.h>
X#include <signal.h>
X
Xvoid fp_in_handler_is_evil(int);
Xvoid main(void);
X
Xint count;
Xdouble x = -M_PI, y, z, t;
X
Xvoid fp_in_handler_is_evil(sig)
Xint sig;
X{
X        static double r = 0.0, s = 0.0, t = 0.0;
X        switch(sig) {
X        case SIGALRM:
X                printf("received SIGALRM, count = %d\n", count);
X                break;
X        default:
X                printf("unknown signal %d\n", sig); 
X                exit(-1);
X       }
X/* Eat up all FPU registers */
X       r += 0.000123;
X       s += r;
X       t = exp(-r*r) + r*s + log(exp(r) + s);
X       r -= 0.000122;
X       return;
X}
X
Xvoid main()
X{
X       struct sigaction act;
X       sigset_t set, oset;
X       struct itimerval value;
X       int i;
X
X       act.sa_handler = &fp_in_handler_is_evil;
X       sigemptyset(&act.sa_mask);
X       sigaddset(&act.sa_mask, SIGALRM);
X       act.sa_flags = 0;
X
X       if (sigaction(SIGALRM, &act, NULL) == 0)
X              printf("signal handler for SIGALRM installed\n");
X       else {
X              printf("installation of signal handler for SIGALRM failed\n");
X              exit(-1);
X        }
X
X       value.it_interval.tv_sec = value.it_value.tv_sec = 10;
X       value.it_interval.tv_usec = value.it_value.tv_usec = 0;
X       if (setitimer(ITIMER_REAL, &value, NULL) == 0)
X              printf("real timer set to 10 s\n");
X       else {
X              printf("failed to set real timer\n");
X              exit(-1);
X       }
X
X       for (count = 100000000 ; count > -100000000; count-- ) {
X/* try to use as much FPU registers as possible */
X                x += 0.000001;
X                if (x > M_PI) x = -M_PI;
X                y = cos(x);
X                z = sin(x);
X                t += 1.0 - sqrt(y*y + z*z + 0.01*y*z) + exp(x-y) + log(exp(y));
X                if (fabs(t) > 100000.0) t = 0.0;
X                if (count % 100000 == 0) {
X                        printf("%d %f %f %f %f\n", count, x, y, z, t);
X                        fflush(stdout);
X                }
X       }
X
X       exit(0);
X}
END-of-./signal_nosave_fpu.c
echo x - ./Makefile
sed 's/^X//' >./Makefile << 'END-of-./Makefile'
XARCH!= uname -m
Xall:: check_arch signal_save_fpu signal_nosave_fpu
X
Xcheck_arch:
X.if (${ARCH} != "i386")
X	@echo "Sorry, wrong architecture. This example is only for NetBSD/i386"
X	@false
X.endif
X
Xsignal_nosave_fpu:
X	cc -o signal_nosave_fpu signal_nosave_fpu.c -lm
X
Xsignal_save_fpu:
X	cc -o signal_save_fpu signal_save_fpu.c fpu.c -lm
X
Xclean:
X	rm -f signal_nosave_fpu signal_save_fpu *.core
END-of-./Makefile
echo x - ./signal_save_fpu.c
sed 's/^X//' >./signal_save_fpu.c << 'END-of-./signal_save_fpu.c'
X#include <sys/time.h>
X#include <math.h>
X#include <stdio.h>
X#include <signal.h>
X#include "fpu.h"
X
Xvoid fp_in_handler_is_cool(int);
Xvoid main(void);
X
Xint count;
Xdouble x = -M_PI, y, z, t;
Xstatic fpu_regs fpu;
X
Xvoid fp_in_handler_is_cool(sig)
Xint sig;
X{
X        static double r = 0.0, s = 0.0, t = 0.0;
X        fpu_save(&fpu);
X        switch(sig) {
X        case SIGALRM:
X                printf("received SIGALRM, count = %d\n", count);
X                break;
X        default:
X                printf("unknown signal %d\n", sig); 
X                exit(-1);
X       }
X/* Eat up all FPU registers */
X       r += 0.000123;
X       s += r;
X       t = exp(-r*r) + r*s + log(exp(r) + s);
X       r -= 0.000122;
X       fpu_restore(&fpu);
X       return;
X}
X
Xvoid main()
X{
X       struct sigaction act;
X       sigset_t set, oset;
X       struct itimerval value;
X       int i;
X
X       act.sa_handler = &fp_in_handler_is_cool;
X       sigemptyset(&act.sa_mask);
X       sigaddset(&act.sa_mask, SIGALRM);
X       act.sa_flags = 0;
X
X       if (sigaction(SIGALRM, &act, NULL) == 0)
X              printf("signal handler for SIGALRM installed\n");
X       else {
X              printf("installation of signal handler for SIGALRM failed\n");
X              exit(-1);
X        }
X
X       value.it_interval.tv_sec = value.it_value.tv_sec = 10;
X       value.it_interval.tv_usec = value.it_value.tv_usec = 0;
X       if (setitimer(ITIMER_REAL, &value, NULL) == 0)
X              printf("real timer set to 10 s\n");
X       else {
X              printf("failed to set real timer\n");
X              exit(-1);
X       }
X
X       for (count = 100000000 ; count > -100000000; count-- ) {
X/* try to use as much FPU registers as possible */
X                x += 0.000001;
X                if (x > M_PI) x = -M_PI;
X                y = cos(x);
X                z = sin(x);
X                t += 1.0 - sqrt(y*y + z*z + 0.01*y*z) + exp(x-y) + log(exp(y));
X                if (fabs(t) > 100000.0) t = 0.0;
X                if (count % 100000 == 0) {
X                        printf("%d %f %f %f %f\n", count, x, y, z, t);
X                        fflush(stdout);
X                }
X       }
X
X       exit(0);
X}
END-of-./signal_save_fpu.c
echo x - ./fpu.c
sed 's/^X//' >./fpu.c << 'END-of-./fpu.c'
X/* fpu.c for NetBSD/i386, partly stolen from npx.c */
X#include "fpu.h"
X
Xvoid
Xfpu_save(fpu_regs *fpu)
X{
X        fnsave(fpu);
X        fwait();
X        frstor(fpu);
X        fwait();
X        return;
X}
X
Xvoid
Xfpu_restore(fpu_regs *fpu)
X{
X        fninit();
X        frstor(fpu);
X        fwait();
X        return;
X}
END-of-./fpu.c
echo x - ./fpu.h
sed 's/^X//' >./fpu.h << 'END-of-./fpu.h'
X/* fpu.h for NetBSD/i386, partly stolen from npx.c */
X#include <sys/types.h>
X#include <machine/npx.h>
X
Xtypedef struct save87 fpu_regs;
X
X#define fninit()                __asm("fninit")
X#define fnsave(addr)            __asm("fnsave %0" : "=m" (*addr))
X#define frstor(addr)            __asm("frstor %0" : : "m" (*addr))
X#define fwait()                 __asm("fwait")
X
Xvoid fpu_save __P((fpu_regs *));
Xvoid fpu_restore __P((fpu_regs *));
END-of-./fpu.h
exit


--jRHKVT23PllUwdXP--