saving FPU state in setjmp/longjmp

Bruce Evans brde at optusnet.com.au
Wed Jun 18 05:39:03 UTC 2008


On Mon, 16 Jun 2008, David Schultz wrote:

> Are setjmp/longjmp supposed to save and restore the FPU control
> word (rounding mode, exception masks, etc.)? They're specifically
> not supposed to touch the status word, and one would think that

s/specifically not supposed to/unspecifically supposed to/
(C99 footnote 212 -- see below)

> they shouldn't touch the control word either. From a pragmatic
> point of view, most apps don't touch the FP control word anyway,
> and the few that do are better off with fegetenv/fesetenv and friends.

All my tests would fail if longjmp() didn't restore the control word.
longjmp() from a signal handler can't possibly work unless the control
word is restored, and my tests reduce to little more than testing this.
longjmp() from a signal handler also needs to touch the status word
to work -- it needs to at least clear the exception flags; on i386 it
has used fninit to reset the entire FP status word for 15+ years since
I fixed it, since this seemed to be the fastest way of resetting enough.

More details:
- long ago, the FP state in signal handlers was fairly broken -- it was
   the same as the preempted state except for special breakage (clearing)
   of the exception flags.  My tests were written at this time.
   - longjump() from a signal handler thus restored the control word at
     the time of the setjmp().  The fninit in the i386 version of it
     prevents it keeping the (specially broken) status word at the time
     of the signal, so the exception flags are normally already clear.
   - Return from a signal handler (which gives undefined behaviour in
     most cases including all returns from SIGFPU handlers, especially
     in plain C99) gave reasonable behaviour -- both the control and
     (broken) status word at the time of the signal were kept.  The
     broken status word was required for signal handlers to use floating
     point without having to clear the exception flags themself (but
     any useful use of FP in a signal handler caused undefined behaviour
     slightly before the signal handler returned; specifically, it could
     trap due to the settings of the preempted state, and it could
     corrupt the preempted state), and as a side effect this kept the
     cleared flags on return so that the preempted code could also
     continue using floating point without clearing the flags.
   - A copy of status word at the time of the exception was kept in the
     "exception status word" in the pcb and passed to signal handlers
     in in the signal context.  gdb supported printing the copy in the
     pcb only.  To restore and/or fix up the original state, the e.s.w.
     had to be merged with the active s.w.  I don't know of any software
     that did this.
- now, signal handlers get a private (reset) state.  This fixes some of
   the bugs described above and helps implement the following new ones:
   - longjmp() from a signal handler has unchanged behaviour, but now it
     is even more necessary to restore the control word at the time of
     the setjmp() and to do something to not keep the current status word,
     since the current control and status word are private to the signal
     handler.  If longjmp() didn't touch the FP state, then the only
     way to get back to the preempted state after the longjmp() would be
     for the signal handler to restore the preempted state before the
     longjmp().  Signal handlers would have a hard time supporting this
     unportable complication, epsecially if they are not SIGFPE handers.
   - Return from a signal handler now restores the preempted FP state,
     modulo the breakage of the exception flags.  When signal handlers
     started getting a reset state, I thought that the exception flags
     didn't need to be broken and changed npxtrap() to not break them,
     but this broke my tests of returning from SIGFPE handlers -- the
     tests just set a flag and return, but this no longer works since
     nothing including the tests clears the exception flags.  (The tests
     also change the control word so that SIGFPE's for FP exceptions
     actually occur if the relevant exception flags are set; then if
     they remain set the fault repeats like an integer divide-by-0
     SIGFPE.)
   - The exception status word and gdb's support for it have been lost
     (except in cvs history).  Before clearing the flags, the flags are
     encoded in a number in the sigcontext.  Then some info is lost when
     the flags are cleared.

> A brief survey of setjmp/longjmp implementations indicates:
> - freebsd/arm saves and restores the FPU status word, which is wrong.

I think this is least broken.  On i386, it is just an optimization to
not save/restore the status word.  Save/restore of it at least keeps any
exception flags that are set at the time of the setjmp().

> - freebsd/i386 and freebsd/amd64 save and restore the x87 control word
>  but not the SSE control word, which is half wrong in one direction or
>  the other.

These also reset the status word.  This is mainly an optimization.  When
they were written, there was no support for FP state in C, so
setjmp()/longjmp() only had to preserve as much FP state as a normal
function.

> - freebsd/everything-else don't touch the FPU.
> - linux doesn't touch the FPU.
> - solaris doesn't touch the FPU.

All of these are broken.

C99 unspecifically requires the arm behaviour:
- in n869.txt, it requires restoring the "calling environment of the
   abstract machine" at the time of the setjmp().
- in n1124.pdf footnote 212, it specifically says that FP status flags
   are part of the calling env.  Footnotes are not normative, and it
   doesn't seem to say anything specifically about the control word,
   but clearly the calling environment is intended to include all FP
   state.  I must have interpreted the corresponding requirement in
   C90 loosely to justify not preserving any FP status when I fixed the
   i386 version to restore the control word.  The abstract machine in
   C90 required little or nothing of FP.

> So the real question is whether to remove the part of setjmp and
> longjmp that fiddle with the x87 control word, or whether to
> extend these functions to also save and restore the SSE control
> word. The fact that SSE has been around for a while and nobody has
> noticed the breakage suggests that either change should have
> minimal impact on compatibility.

According to C99, all FP state must be preserved, but we can still
avoid preserving data registers and some perhaps other details that
aren't part of the abstract machine.

Breakage of the exception flags is rarely observed since no one uses
these :-).  Breakage of SIGFPE handlers is even more rarely observed
since no one unmasks FP exceptions :-), and signals for FP exceptions
give undefined behaviour anyway.  The most interesting case is for
longjmp() from a signal handler for non-FP signal.  Some cases should
work, without the signal handler knowing anything about FP.  When the
signal occurs, the FP state may be arbitrarily exotic, and it must
become normal when longjmp() returns.  The current i386 implementation
gives this except it messes up the C99 exception flags.

Bruce


More information about the freebsd-standards mailing list