saving FPU state in setjmp/longjmp
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.
- 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
- 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
> - 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.
More information about the freebsd-amd64