semantics of loating point exception handling

J

juergen perlinger

Hello out there.

sometimes I need to have proper control of the floating point arithmetic of
the C(and C++) runtime system, and using the f.p. exception handling of the
C99 standard is quite handy for that purpose. The only problem when dealing
with f.p. exception signals is that there is (afaik) no specification *when*
the f.p. exception is raised, with one notable exception:
'feraiseexcept(int)' raises the exceptions passed in the argument mask
before it returns.

Imagine a series of operations that might fail with an FPU exception
(SIGFPE, if your UN*X bound). The FPU might excute the instructions on her
own schedule, not too tightly coupled to the CPU. At certain points I must
be sure that there are no more pending f.p. exceptions slumbering in the
FPU, so you need to enforce a synchronisation between CPU and FPU. To
execute 'feraiseexcept(fetestexcept(FE_ALL_EXCEPT))' just to make sure of
that seems quite heavy-handed.

After working through the documentation of the open group and the
implementation of
Cygnus/ReadHat 'newlib' as well as the GNU libc source I still found no
other portable way to make sure that there are no pending exceptions after a
synchronisation point.

Perhaps I'm just misinterpreting standards; but if there's anything I've
learned about standards it's one thing: *NEVER*ASSUME*ANYTHING*. So, could
someone give some more hints about the synchronization properties of the
functions defined in the C99 <fenv.h> header and the underlying FPU
hardware, if there are any?

regards
pearly

--never use a computer when an abacus is sufficient--
 
K

Kevin Bracey

In message <[email protected]>
juergen perlinger said:
Hello out there.

sometimes I need to have proper control of the floating point arithmetic of
the C(and C++) runtime system, and using the f.p. exception handling of the
C99 standard is quite handy for that purpose. The only problem when dealing
with f.p. exception signals is that there is (afaik) no specification
*when* the f.p. exception is raised, with one notable exception:
'feraiseexcept(int)' raises the exceptions passed in the argument mask
before it returns.

As I read the standard, fetestexcept() is required to accurately report
exceptions raised by preceding code, so it must be a synchronising operation.
 
J

juergen perlinger

Kevin said:
In message <[email protected]>



As I read the standard, fetestexcept() is required to accurately report
exceptions raised by preceding code, so it must be a synchronising operation.
Thanks for reply. Seems my wording was not accurate; I'm not a native
speaker, and the selection of words is important here. What I actually
tried to express was: Have unmasked execeptions been signalled to the
CPU and the associated handler been executed? (Some INTEL machines have
the annoying property to actually do the interrupt on the NEXT f.p.
instruction. This is clearly a machine dependency, but to understand if
an implementation is according to the standard I should have clear
understanding of the standard first...)

It seems to me that RAISING the exception and EXECUTING the exception
are different aspects and EXECUTING is not the scope of the standard,
though the OpenGroup documentation says:

>The functionality described on this reference page is aligned with the
>ISO C standard. Any conflict between the requirements described here
>and the ISO C standard is unintentional. This volume of IEEE Std
>1003.1-2001 defers to the ISO C standard.
>The feraiseexcept() function shall attempt to raise the supported
>floating-point exceptions represented by the argument excepts. The
>order in which these floating-point exceptions are raised is
>unspecified. Whether the feraiseexcept() function additionally raises
>the inexact floating-point exception whenever it raises the overflow
or >underflow floating-point exception is implementation-defined.
</quote>

That "the order in which exceptions are raised is undefined" implies in
my understanding that the exception is actually handled, which in turn
tells me that the exception must have been signaled to the CPU.

The OpenGroup says about 'fetestexcept()':
>The fetestexcept() function shall determine which of a specified
subset >of the floating-point exception flags are currently set. The
excepts >argument specifies the floating-point status flags to be queried.

That implies to me that only there *IS* a difference between flagged
exceptions and raised exceptions.

But as I said my grasp of english might be a bit flaky when it comes to
such minutes, and hence a clarification about this would be highly
appreciated. And I do *not* have a copy of the final standard, so I can
only hope that the OpenGroup cites the standard correctly...

regards
 
C

Chris Torek

... I'm not a native
speaker, and the selection of words is important here. What I actually
tried to express was: Have unmasked execeptions been signalled to the
CPU and the associated handler been executed? ....
The OpenGroup says about 'fetestexcept()':
subset >of the floating-point exception flags are currently set. The
excepts >argument specifies the floating-point status flags to be queried.

That implies to me that only there *IS* a difference between flagged
exceptions and raised exceptions.

In IEEE floating point (which is optional even in C99, though rather
strongly encouraged there), there *is* a difference, but perhaps not
the one you are concerned with. In particular, consider masked vs
unmasked exceptions. Suppose "invalid" exceptions are masked
while "zero divide" exceptions are not. Then the sequence:

operate on invalid operand;
divide by zero;
wait for asynchronous FP exceptions;

might "notice" both exceptions "simultaneously", as it were. Now
both are "flagged", but only the "divide by zero" one is "raised".
You can find out that the invalid operand occurred by means of an
explicit test; meanwhile, the "raised" divide by zero will run the
zero-divide exception handler.

The C99 draft I have does not mention anything about actually
running exception handlers, but I would expect real implementations
to run them once they are "noticed" (e.g., with an "fp wait for
exceptions" instruction a la Intel, or "exception barrier" instruction
a la Alpha, or at the next safe instruction boundary, or whatever),
provided there is a handler for SIGFPE and the exception (and signal
as well, on POSIX-y systems) is not masked.
 
J

juergen perlinger

Chris said:
The C99 draft I have does not mention anything about actually
running exception handlers, but I would expect real implementations
to run them once they are "noticed" (e.g., with an "fp wait for
exceptions" instruction a la Intel, or "exception barrier" instruction
a la Alpha, or at the next safe instruction boundary, or whatever),
provided there is a handler for SIGFPE and the exception (and signal
as well, on POSIX-y systems) is not masked.

Hmmm... that's goes a long way with my expectations. It's only that the
implementations I've checked (glib, newlib) implement the API according
to C99 but they do *not* use the FWAIT for Intel *except* in the
implementation of 'feraiseexcept()' and 'fesetenv()'. (I didn't check
the implementation of other CPU's.) It seems that this is going to be an
implementation issue.

Thanks for the discussion; I'll try to carry on in newsgroups about the
implementation.
 
K

Kevin Bracey

In message <[email protected]>
juergen perlinger said:
Thanks for reply. Seems my wording was not accurate; I'm not a native
speaker, and the selection of words is important here. What I actually
tried to express was: Have unmasked execeptions been signalled to the
CPU and the associated handler been executed?

The C standard doesn't really have much to say about FP exceptions being
trapped; it says that they should be non-trapping by default (if following
Annex F), and offers no functions to control trapping behaviour. There is a
small document about trapping on WG14's site by Fred Tydeman though.

Trapped IEEE exceptions can be a pain. Particularly underflow ones, as
trapped underflow is different from untrapped underflow. For example, does
sin(<some subnormal number>) generate an underflow trap, even if you would
normally use the "loss of accuracy" form? I haven't the foggiest, as the C
standard is written in terms of underflow being in its "untrapped" guise.
Anyway, that's not relevant to you...
(Some INTEL machines have the annoying property to actually do the
interrupt on the NEXT f.p. instruction.

So do other architectures, such as the ARM FPA.
It seems to me that RAISING the exception and EXECUTING the exception
are different aspects and EXECUTING is not the scope of the standard,
though the OpenGroup documentation says:

The terminology here is that the "exception" is the overflow, or whatever,
and this is "raised". When the exception is raised, if its trap is disabled,
the cumulative exception flag is set. If its trap is enabled, then the trap
handler is called.

Now, with FP hardware, the exception may have been internally flagged in the
FPU, but not have actually caused any external effect, and it won't until the
next FP instruction occurs (or some other synchronising event happens).

For untrapped exceptions, the distinction doesn't matter, as the only way
of finding out about them is to read the cumulative exception flags, which
is obviously an FP instruction, so you can be sure the flags are up-to-date.
(And if the FP hardware doesn't do this automatically, it will be the
implementor's job to ensure a synchronising instruction is included in
fetestexcept).

For trapped exceptions, the trap handler is liable to not be called until
another FP instruction happens. Because the C standard doesn't consider trap
handlers, it's not totally clear what the "correct" behaviour is.

Section F.8.1 says

This specification does not require support for trap handlers that
maintain information about the order or count of floating-point
exceptions. Therefore, between function calls, floating-point exceptions
need not be precise: the actual order and number of occurrences of
floating point exceptions (> 1) may vary from what the source code
expresses.

So as they go on to say, the compiler may choose to optimise a loop so that
it only raises an exception once. Thus your trap handler may only be called
once, although the implication is that your trap handler should be called
before the next function call. But frankly, I doubt that many implementions
would take the pains to do that, at least by default.

But it would seem to me that the act of calling fetestexcept() would be
sufficient in all real implementations to make sure any pending traps
get sent off to handlers, as whatever synchronisation is done to ensure the
flags are up-to-date should also lead to pending traps being taken.

If you really need precise information on each trap, you will probably need
compiler support via some sort of pragma or command-line switch to add a
synchronising instruction after each FP instruction, unless the FPU has a
"synchronous operation" mode that you can enable. Both of these will
obviously impair performance.
</quote>

That "the order in which exceptions are raised is undefined" implies in
my understanding that the exception is actually handled, which in turn
tells me that the exception must have been signaled to the CPU.

Well, I don't see that this is any different to any other exception; indeed
it says that "the effect is intended to be similar to that of floating-point
exceptions raised by arithmetic operations". For example, my implementation
of feraiseexcept() just calculates "DBL_MAX*2.0" when asked to raise
FE_OVERFLOW. And this is subject to the same pending exception behaviour as
any other FP operation. I think other implementations may do the same, so I
wouldn't be certain that the trap handler will actually be called by the time
feraiseexcept() returns.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,142
Messages
2,570,818
Members
47,362
Latest member
eitamoro

Latest Threads

Top