[Cfp-interest] Background on exception handling

David Hough CFP pcfp at oakapple.net
Mon Apr 14 14:32:12 PDT 2014


Some of the motivation for the attributes pragma design is contained
in the following contribution to a Berkeley seminar.     I hope to have
some more concrete proposals for the May meeting.



Handling Floating-Point Exceptions

They're called exceptions because no matter what one does by default, somebody
will take exception to it, because it's wrong for his program.

IEEE 754-1985 talked about exceptions, flags, and traps.
IEEE 754-2008 still talks about exceptions and flags but replaced traps
with a selection of alternate exception handling methods.

I think that flags and traps should be thought of as low-level implementation
mechanisms and programming languages should just talk about exceptions 
anticipated to arise in blocks of code, and what should happen if they do,
without reference to those implementation mechanisms.

However debugging unanticipated exceptions in compiled code 
that can't be readily recompiled changes
the picture somewhat.     Within debuggers, one would like to enable traps
(really alternate exception handling that interrupts the user program and
passes control to the debugger) and examine flags, either upon such
a trap or when the user manually interrupts execution.

Thus anticipated or unanticipated exceptions have different handling paradigms.
If one writes snrm2, the Euclidean norm BLAS,
as a fast loop that detects overflow or underflow and
switches to a slower scaled loop if either happens, then those overflows and
underflows are anticipated.    It is not helpful to the rest of
the program to be bothered by those exceptions either as traps or as
raised flags, since they are fully accounted for in the logic of snrm2.

Perhaps the programmer has determined, rightly or wrongly, that overflow
or underflow can never occur anywhere else in his program - or has not
considered the possibility.    If they do occur anyway, because he was
wrong or because of a misoptimization or because of a hardware error, they
become unanticipated exceptions.     He might want the program to stop if
they arise (trap and abort) or perhaps he would like to observe flags 
standing at the end of execution for unanticipated exceptions that occurred.

Furthermore, running under the control of a debugger calls for a different kind
of alternate exception handling: one that interrupts execution to the debugger,
to allow the programmer to take a look around before deciding whether to 
continue or abort,
but then allows execution to be continued with the same default result and flag
that would have been the case if default execution had not been interrupted.
This, and other kinds of alternate exception handling mentioned in 
754-2008, contrast with the fully general exception-trap-handling facility 
provided e.g. by Oracle Solaris Studio:

http://docs.oracle.com/cd/E19422-01/819-3693/ncg_handle.html#pgfId-10432

whose fully general power comes at the cost of fully general complexity.
Experience suggests that almost no application programmers are motivated
to master that power and complexity, especially when the details are 
different on every platform. 
So such a facility is best viewed as an implementation mechanism for system
programmers providing less general but simpler alternate exception handlers
for use by application programmers.

Even if a programmer has and provided suitable handling for 
all exceptions that he anticipates,
he might not have thought to enable trap-and-abort on all the other 
exceptions, 
nor to check for standing flags at the end of execution.    What should happen
by default, to deal with exceptions that were never thought about?

The Oracle Solaris Studio Fortran-77 compiler generates code to print 
standing flags at the end of execution (which IEEE 754 suggests)
but does not enable any traps by default (which IEEE 754 requires).
In contrast, the Fortran-90 and -95 compilers enable overflow, division by
zero, and invalid trap-and-abort by default, and do not mention standing
flags at the end of execution.     If unanticipated exceptions trap-and-abort,
the programmer might be thankful, at least during debugging.    
If anticipated exceptions trap-and-abort, the programmer will not thank the
compiler for ignoring the IEEE 754 requirement and forcing him to learn
how to avoid the trap-and-abort.     Likewise retrospective diagnostics
about anticipated exceptions that arose, or about
unanticipated exceptions deemed harmless, such as many underflows, are not
appreciated.     If any users were grateful to be warned of unanticipated
exceptions, they usually expressed their gratitude silently, unlike other
users who were annoyed by retrospective diagnostics that did not arise on
other platforms, and which had to be suppressed by platform-specific means.

What should be specified in a high-level language like C?
Ideally one would like to attach a definition of alternate exception handling
to an individual operation or to a "block" of code - where "block" is
an aggregation of statements that can be readily treated as a unit within
the context of a particular language.

For instance, one could follow the model of OpenMP 3.0, in

http://www.openmp.org/mp-documents/OpenMP3.0-SummarySpec.pdf

where we find

 #pragma omp parallel [ clause[ [ , ]clause] ...] new-line
 structured-block

where "structured-block" is defined as 

 a single statement or a compound statement with a single entry
 at the top and a single exit at the bottom

This paradigm does not extend to specifying an individual operation 
except by modifying
the program source so that it appears as its own "structured block."
Maybe that's preferable to damaging the syntactic structure of the C
language.

So one can imagine something like:

 #pragma fpe underflow(abrupt); invalid(abort); division-by-zero(goto label);

controlling a structured-block.    Note that no flags or traps are mentioned;
how this is to be accomplished is up to the language run-time implementation.
For instance, abrupt underflow might be implemented with a hardware mode bit;
invalid abort might be implemented with a hardware trap; 
division-by-zero might be implemented by checking a flag after every
potentially troublesome division.    The compiler or interpreter should take
care of insulating the rest of the program from any flags or hardware trap
enables whose scope is limited to this structured-block.

What about functions called within the structured-block?    Should they
be affected by these alternate exception handling specifications?
On the one hand, they might be completely separately compiled - perhaps in
a different language.     On the other hand, they might be inlined in the
caller's code.     To avoid imposing uneconomical performance burdens,
754-2008 left the specification of scope up to language standards to define.

When do "abort" and "goto" take effect?    One can imagine checking at the
end of the block, using flags, or implementing with traps, unpredictably
in the middle of the block.     If one wants deterministic execution, 
control transfers should probably occur at the end of the block, with
default exception handling up until that point.     If determinism can
be sacrificed for performance, then traps are better, but then neither 754
nor the language standard can specify in what state variables will be 
after the abort or goto takes place.



More information about the Cfp-interest mailing list