-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exceptions and Exception Handling #172
Comments
It's exceptionally important for Fortran to include support for exception handling as a modern language must. Coders can then decide whether to make use of it and where and how in their codes just as it is with IEEE exception handling introduced in Fortran starting with the 2003 standard. One of my biggest disappointments is with WG5 and how WG5 has reduced the work-list for Fortran 202X to drop items such as exception handling and generics. Interested readers can see this link at comp,lang.fortran. As seen in this link, many a Fortranner - with no real voice or representation due to their physical/virtual locale which is well outside the 2 to 5 country-based and rather institutionally controlled org structure - have been requesting support for exception handling but to no avail. The way things function with an ISO-driven process and how arcane, archaic, and inefficient the process is when it comes to language advancement, it could very well be decades from now before the ducks get in a row for the standard process to finally work attentively toward exception handling in Fortran. And even then there will always be the argument Fortran has survived that long without explicit exception handling support for coders, so why bother!? In the meantime, there is another issue that too gets overlooked with this topic: in the industry where I work and the computing domains I am familiar with, code base after code base has moved away Fortran, a lack of support for exception handling and the demands that such a scenario places on the most important resource - developer time and productivity - is among the reasons for the flight away from Fortran. For a simple example, there is a particular formula based on the pioneering scientific advancements by Gibbs and van der Waals which literally gets computed billions of times by engineers and scientists where I work. This formula, y = f(x1, x2, .. , xN), can be written in a few lines of code and computed trivially. Except when the values of the N independent variables x1 thru' xN are outside the physically acceptable range for practicable design and manufacturing. The occurrence of this is truly an exceptional circumstance but which needs to be handled very carefully, for otherwise there are considerable consequences including that of safety. Owing to the lack of exception handling support in language, the Fortran codes were rendered exceptionally complex and verbose with developers having paid incalculable amount of attention to the handling of exceptional situations where the input values are outside the physically acceptable range. This was so much so that a new engineer/scientist tasked to work on the code (and mostly with background in MATLAB, Python, C++, Java, .NET) would entirely fail to notice the important formula in the subprogram, it was a mind-blowing departure from the concept of Formula Translation and black-board abstraction. As I mentioned earlier, the computations involving this formula are now performed in any number of compute environments where Fortran plays little to no role. The code written in other languages is rather compact, readable and maintainable, albeit full of curly braces!! For the purse-holders and budget managers though, the new code is overall better performant in spite of the use of structured exception handling. So they can't be happier with their push away from Fortran. This is an aspect where implementations such as LFortran, LLVM, etc. can be of great help with prototyping. |
I think it is clear that many (if not the most) people in the wide Fortran community want some form of better exception handling. I also agree that the Committee in general is not handling these requests efficiently. As you know I am running for the WG5 Convenor position and in my platform I propose that we work on these proposals continuously and when they are ready, we will consider them for inclusion. However, no matter who gets selected in the end, let us work on these things, and when they are ready, I will bring them to the Committee to consider. So let's get to work. There are several approaches to exception handling. The above proposal is one. Here are other issues with other proposals: #6, #66. There are more. Ultimately, this must be prototyped in a compiler to show that there is no performance overhead, and also to iron out corner cases, and to see how difficult it is to implement it. We will be able to do such prototypes very soon (if things go well, hopefully in a year or so). Until then, let's at least narrow down the options of what ideas should be prototyped. Also, let's collect the most common objections and have solid answers to them. Here are some off top of my head:
And there is a lot more, see the thread at the J3 mailinglist starting with this email: https://mailman.j3-fortran.org/pipermail/j3/2019-February/011256.html We have to collect all the objections there and have a good answer, before we bring it back to the committee. Otherwise the same objections will be given and we will not move the discussion forward. This is the real work that we have to do. I currently don't have time to drive this particular effort, but if somebody is interested in leading this, I will help as much as I can. |
Here is what I think Fortran can do.
The Why I favor this approach:
TBD:
|
@klausler thanks for this proposal. What I like about it is that it is essentially equivalent to manual alternate returns, and thus does not require any magic at runtime with stack frames or runtime library. At the February 2019 meeting, we fleshed out two similar / alternative proposals: https://mailman.j3-fortran.org/pipermail/j3/2019-February/011257.html See the links at the bottom of the email:
it is also equivalent to manual alternate returns, no magic. I'll try to do this example using your proposal also, so that we can see how it would look like in a code. |
I uploaded a complete exception-handling proposal a few days ago. I
don't remember the number.
1. Exceptions are identified by enumerators of an extensible
enumeration type.
2. Instead of a new construct, allow ENABLE ( <exception-list> )
suffix on BLOCK, CHANGE TEAM, DO CONCURENT, CRITICAL, SUBROUTINE and
FUNCTION statements.
3. Allow any number of HANDLE ( <exception-list> ) ... <block> sub
constructs before the END of a construct that has an ENABLE suffix. The
<exception-list> in the HANDLE should be required to be a subset of the
list in the ENABLE statement. The <exception-lists> in HANDLE blocks
shall be disjoint.
4. The HANDLE block accesses its containing construct or program
unit by host association, so the containing scope is NOT finalized
before the exception handler starts.
5. When the exception handler finishes, its scope, and the
containing scope, are finalized.
6. Users can define exceptions by extending the EXCEPTION type. Any
exception, intrinsic or user-defined, can be explicitly raised by a
RAISE statement.
7. If a scoping unit does not handle an exception, it is raised in
the enclosing scoping unit. If there is no enclosing scoping unit, it
is raised in the invoking scoping unit, if any. If it is raised in the
main program, the rutime support handles it, probably by printing a
message and ERROR STOPping with a non-zero status.
8. Interaction with interoperability TBD.
9. More details in the uploaded paper.
…On Wed, 2020-05-13 at 13:40 -0700, Peter Klausler wrote:
Here is what I think Fortran can do.
Add a statement (say FAIL) that signifies failure with an error
code. As you will see, it can appear only within a dynamic context
that can handle the error. It causes transfer of control to the most
deeply nested handler for its error code. Memory allocations are
automatically cleaned up between the FAIL and its handler.
Add a new specifier (say FAILING) to ALLOCATE statements, I/O
statements, coarray references, &c. to give them permission to fail
on error, END, and EOR conditions, thus allowing these conditions to
be handled by the same handlers as if a FAIL statement had been
executed.
Add a construct (say TRY <block> [ CATCH (error) <block> ]... [ CATCH
DEFAULT <block> ] END TRY) that establishes an error handling
context. If a FAIL statement is executed, the appropriate handler
block is executed. A TRY construct with no CATCH DEFAULT, or with
fallible code in its CATCH DEFAULT block, may appear only in a
dynamic context that can handle the uncaught errors.
Add a attribute (I'll spell it FALLIBLE here) to procedures. It
signifies that the procedure may fail without catching all errors. A
reference to a FALLIBLE procedure may appear only where a FAIL
statement could appear -- i.e., a hermetic TRY construct or in
another FALLIBLE procedure. The use of FALLIBLE implies an explicit
interface.
The FAIL statement, and references to FALLIBLE procedures, may appear
only within a hermetic TRY construct if they occur in a DO CONCURRENT
loop or main program.
Why I favor this approach:
It has no effect on existing code.
Static checking can guarantee that all errors will be caught.
Runtime overhead is cheap; no more expensive than alternate returns.
Implementation would be straightforward; no magic in the linker or
runtime library.
TBD:
interaction with finalization
whether ALLOCATE statements, I/O statements, coarray references, &c.
should automatically be fallible if they appear in a TRY construct.
how distinct user error codes would be defined
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
[
{
***@***.***": "http://schema.org",
***@***.***": "EmailMessage",
"potentialAction": {
***@***.***": "ViewAction",
"target": "
#172 (comment)
",
"url": "
#172 (comment)
",
"name": "View Issue"
},
"description": "View this Issue on GitHub",
"publisher": {
***@***.***": "Organization",
"name": "GitHub",
"url": "https://github.com"
}
}
]
|
Looks a lot to me like the current C++ exception feature that many significant codebases specifically go out of their way to disable and that C++ is working hard to replace. |
@klausler I implemented an example using your proposal, please let me know if I got it right:
|
@vansnyder I have implemented an example using your proposal, please let me know if I got it right:
|
On Wed, 2020-05-13 at 17:17 -0700, Peter Klausler wrote:
Looks a lot to me like the current C++ exception feature that many
significant codebases specifically go out of their way to disable and
that C++ is working hard to replace.
It's based on the Ada exception mechanism, and John Reid's proposal.
One important difference from John's proposal is that John's
represented exceptions by integers.
… —
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
[
{
***@***.***": "http://schema.org",
***@***.***": "EmailMessage",
"potentialAction": {
***@***.***": "ViewAction",
"target": "
#172 (comment)
",
"url": "
#172 (comment)
",
"name": "View Issue"
},
"description": "View this Issue on GitHub",
"publisher": {
***@***.***": "Organization",
"name": "GitHub",
"url": "https://github.com"
}
}
]
|
The fallible subroutine can't be called at line 15 from the main program outside an error handling context. |
I updated the gist (still the same link), you can see the changes in the "Revisions" tab. I was wondering about this --- I can see both ways here. Your proposal disallows calling the Two questions:
try
call mysum(5, s)
catch default
error stop
end try Which is quite verbose given that you don't want to handle the error. Would it make sense to create a shortcut for the above code as follows: try mysum(5, s) Which would be exactly equivalent to the above code. So in practice, if you have an existing code that has |
The implementation would be very much like that of alternate returns, except that it would also work for functions (not just subroutines). The value returned has to be an error code, not just a simple flag. If a call fails, the caller either handles the failure, or fails itself. In the latter case, the caller's return is almost immediate -- it still deallocates its local state. |
I like the sound of this. Plus @klausler point about "The implementation would be very much like that of alternate returns," is really good to read. For I've always had a simpleton's view the alternate return facility (given it's still part of the standard even if labeled obsolescent) in a given implementation for Fortran might be able to assist with illustration and prototyping. Re: @klausler other point, "The value returned has to be an error code," it will be really beneficial and user friendly from a coder perspective if the return value is of an enumeration type as proposed by @vansnyder above. |
@certik wrote on 12 May 2020:
Thank you @certik , your feedback and suggestions are very inclusive of the broader needs of the community and as such, they are very helpful to achieve progress in the Fortran language with this important feature. As I mentioned above, my experience in industry is along the same lines as the IEEE Spectrum ranking in terms of the popular languages employed for computing and where each and every one of the top languages offers support for exception handling. In addition to C++, the languages used and all of whom support exception handling include C# and Java and Python and R and also some new languages such as Swift which has gained a lot of influence due to the influence of iOS ecosystem: https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html. But not to be overlooked are other recent languages such as Julia which do compete with C++ and Fortran in the high-performance computing space also e.g., https://docs.julialang.org/en/v1/manual/control-flow/#Exception-Handling-1. And as mentioned by @vansnyder, Ada with its focus on embedded devices and controls and task-based concurrent programming with highly demanding environments such as aircraft systems has also provided robust support for exception handling. Thus it appears many paths are available beyond that taken by early C++and I hope Fortran can also quickly learn from other approaches such as Julia, Swift, C#, etc. and offer up some performant facility. |
@FortranFan great links. The Swift link is I think very close if not exactly what we want. Very similar to Peter's proposal, although with different syntax. |
@certik, the example you provided above can also be analyzed using alternate return facility in Fortran as follows: module exception_m
type :: exception_enum_t
integer :: e = 0
end type
type(exception_enum_t), parameter :: OBJECT_ALREADY_ALLOCATED = exception_enum_t( e=1 )
type(exception_enum_t), parameter :: INSUFFICIENT_MEMORY = exception_enum_t( e=2 )
end module
module mysum_m
use exception_m
interface mysum
module procedure mysum_with_noexception
module procedure mysum_with_exception
end interface
type(exception_enum_t) :: mysum_exception = exception_enum_t()
private :: mysum_with_noexception
private :: mysum_with_exception
contains
subroutine mysum_with_noexception(n, s)
integer, intent(in) :: n
real, intent(out) :: s
real, allocatable :: a(:)
if ( n == 42 ) call foo( a )
allocate( a(n) )
call random_number( a )
s = sum(a)
return
end subroutine
subroutine mysum_with_exception(n, s, *)
integer, intent(in) :: n
real, intent(out) :: s
real, allocatable :: a(:)
integer :: istat
if ( n == 42 ) call foo( a )
allocate( a(n), stat=istat )
if ( istat /= 0 ) then
select case ( istat )
case ( 151, 5014 )
call throw_exception( OBJECT_ALREADY_ALLOCATED )
case default
call throw_exception( INSUFFICIENT_MEMORY )
end select
return 1
end if
call random_number( a )
s = sum(a)
return
end subroutine
subroutine foo( a )
real, allocatable, intent(out) :: a(:)
a = [ real :: ]
end subroutine foo
subroutine throw_exception( ex )
type(exception_enum_t), intent(in) :: ex
mysum_exception = ex
end subroutine
end module
program p
use mysum_m
real :: s
integer :: i
do i = 41, 42
print *, "i = ", i
try: block
use exception_m
call mysum(i, s, *99)
print *, "call sum(i, s, *99): s = ", s
exit try
99 continue ! catch
print *, "Program exception in sum"
select case ( mysum_exception%e )
case ( OBJECT_ALREADY_ALLOCATED%e )
print *, "Exception raised: allocatable object is already allocated."
case ( INSUFFICIENT_MEMORY%e )
print *, "Exception raised: insufficient memory to allocate object."
case default
print *, "Exception raised: unidentified error."
end select
print *, "Resume execution"
end block try
call mysum(i, s)
print *, "call sum(i, s): s = ", s
end do
end program p You can try above code with either gfortran or Intel Fortran and you should get output as follows:
As mimicked in this example with a 'throw' invocation, it will be nice if the return value of a raised exception is an enumeration type. |
On Thu, 2020-05-14 at 14:15 -0700, FortranFan wrote:
The implementation would be very much like that of alternate
returns, except that it would also work for functions (not
just subroutines). The value returned has to be an error code,
not just a simple flag.
If a call fails, the caller either handles the failure, or
fails itself. In the latter case, the caller's return is
almost immediate -- it still deallocates its local state.
I like the sound of this. Plus @klausler other point about "The
implementation would be very much like that of alternate returns," is
really good to read. For I've always had a simpleton's view the
alternate return facility (given it's still part of the standard even
if labeled obsolescent) in a given implementation for Fortran might be
able to assist with illustration and prototyping.
I haven't written a compiler for a "real" language such as Fortran (but
I have written a bunch for "little" languages). I imagine that one way
that processors might propagate exceptions that aren't locally handled
is equivalent to alternate returns.
It's important to be able to catch both system exceptions and exceptions
raised explicitly by the program.
Re: @klausler point, "The value returned has to be an error code," it
will be really beneficial and user friendly from a coder perspective
if the return value is of an enumeration type as proposed by
@vansnyder above.
In my complete exception-handler proposal, there is more to the
representation of an exception than a value of an enumeration type. The
handler can also receive an object of class EXCEPTION_DATA, which would
include the STATUS (or IOSTAT) value, and the MESSAGE (or IOMSG) value.
… —
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
@certik wrote on May 14, 2020, 5:54 PM EDT:
@certik, @klausler, and to everyone with Fortran compiler developer experience: Prima facie, Swift language error handing facility does indeed appear close to what can benefit Fortran coders. Swift also appears to be mindful of performance in terms of its semantics of a throw statement being made comparable to a return, consider
Given then, what would it take to get a project going to prototype a facility in one or more compiler implementations, perhaps building on @klausler's ideas? What kind of support can non-compiler developers provide to compiler developers such as yourself to get such an effort rolling? Also, any comments on the Swift approach to avoid unwinding of the stack vis-a-vis Fortran and its language semantics? Is it a non-starter or a non-issue given current Fortran standard and its legacy, or can it be an option worth a consideration with Fortran? |
Performance of exception handling shouldn't be an issue. Exceptions are
supposed to be exceptional. If you have performance problems because of
the expense of exception handling, you're abusing exception handling.
Many processors have very low overhead due to the presence of the
ability to handle exceptions. Two processors for which I've had
correspondence with the developers are Verdix Ada and GNU Ada. Verdix
ada has very low overhead unless an exception occurs. GNU Ada has zero
overhead unless an exception occurs.
I proposed that a BLOCK construct, CRITICAL construct, CHANGE TEAM
construct, or procedure header could have an ENABLE suffix that
specifies which exceptions can be handled. This actually means which
exceptions the construct or procedure will detect if the program is
compiled with checking for those exceptional conditions, such as
subscripts out of bounds, turned on. I think this is the same as "try"
in other languages. Then a construct or subprogram can have any number
of HANDLE blocks, each one indicating which exceptions it handles. Two
handlers in a particular construct or subprogram cannot handle the same
exception. Users can raise an exception with a RAISE statement that
specifies an exception. A handler can pass an exception along to the
enclosing or calling scope's handler using a RAISE statement that
doesn't specify an exception, meaning "whatever exception got you to
this handler." I think that's the same as "throw" in other languages.
If an exception isn't handled in a block, it's raised in the enclosing
block,. If there's no enclosing block, it's raised in the calling scope.
One way that I can imagine to propagate exceptions to a calling scope is
for the processor to create the same sort of code as for an alternate
return, except hidden, the way some F77 compilers passed character
lengths. In the calling scope, the corresponding argument is the "label"
of a dispatcher that looks at the exception and either gives it to a
local handler or re-raises it.
…On 5/23/20 5:57 PM, FortranFan wrote:
@certik <https://github.com/certik> wrote on May 14, 2020, 5:54 PM EDT:
.. The Swift link is I think very close if not exactly what we
want. Very similar to Peter's proposal, although with different
syntax.
@certik <https://github.com/certik>, @klausler
<https://github.com/klausler>, and to everyone with Fortran compiler
developer experience:
Prima facie, Swift language error handing facility
<https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html>
does indeed appear close to what can benefit Fortran coders. Swift
also appears to be mindful of performance in terms of its semantics of
a *throw* statement being made comparable to a *return*, consider
NOTE
Error handling in Swift resembles exception handling in other
languages, with the use of the try,
catch and throw keywords. Unlike exception handling in many
languages—including Objective-C
error handling in Swift does not involve unwinding the call stack,
a process that can be
computationally expensive. As such, the performance
characteristics of a throw statement are
comparable to those of a return statement.
Given then, what would it take to get a project going to prototype a
facility in one or more compiler implementations, perhaps building on
@klausler <https://github.com/klausler>'s ideas? What kind of support
can non-compiler developers provide to compiler developers such as
yourself to get such an effort rolling?
Also, any comments on the Swift approach to avoid unwinding of the
stack vis-a-vis Fortran and its language semantics? Is it a
non-starter or a non-issue given current Fortran standard and its
legacy, or can it be an option worth a consideration with Fortran?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#172 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AFMNQ63WYK33CS76DTPJKLDRTBWHVANCNFSM4M46DTBA>.
|
@vansnyder wrote May 23, 2020 11:42 PM EDT:
All this makes sense, the question really is how to gain traction and start to "move the needle" on this feature? "Performance" considerations keep coming up again and again in the context of this feature, either by vendors or by some who think it's been this way since FORTRAN I so why change. Even though I personally think the concerns with performance are either overstated or an attempt at misdirection, it seems to hinder any progress on this work item with Fortran. So then, how does one either put the "performance" issue to bed once and for all, or illustrate what can work with Fortran and what semantics to avoid? Separately, I think syntax is the easier part though the debates might get "passionate" and agreement on the terms may prove elusive. Does one go with verbs that are similar in sound and meaning as most other popular languages in scientific and technical programming and which will make it easier for younger Fortranners to relate to and employ the facility e.g., try, throw/throws, catch, defer, etc? Or does Fortran end up with terms that are deliberately intended to make it stand apart and even sound archaic ("classic" in some minds) such as enable, raise, handle, fallible/fall and so forth. However there are a couple of big bridges to cross before Fortran can arrive at the "world of fun" with syntax. How does one get started? @certik, with your vision toward WG5, would more liberal and robust use of the "Technical Specification (TS)" approach be worth a consideration to push progress on certain long-standing but dormant items such as exception handling? |
@FortranFan I think we have identified 3 proposals worth prototyping above, and if people propose more proposals in the future, we can prototype those too. I think the next step is to prototype this in a compiler, preferably two compilers. That is the only way to settle the performance worries and to truly understand the complexity of this feature and to help nail down various design issues. I truly believe at least Flang and LFortran will be able to do this in a relatively near future (in a year or so). Once we settle on the underlying mechanism / semantics and convince ourselves that this is doable and we understand the performance implications, then we have to agree on the final syntax and after that we should write a paper and propose this either directly to the standard, or using a TS. I am positive we can agree on the best way forward once we get to this point. I am now concentrating my efforts into LFortran to bring it to the state that people can start using it, and also so that we can prototype such features, and the Flang team is making good progress on their compiler, and I truly believe we can achieve the goal of having at least two independent prior implementations of such wide ranging features such as exceptions. |
@certik wrote May 25, 2020 3:55 PM EDT:
Thanks much for your feedback, that's really good. It'll be really interesting to see how things work out. Fyi here's a paper by @sblionel at a J3 meeting last year on user and library exceptions: https://j3-fortran.org/doc/year/19/19-207.txt |
Block-structured exception handling has been provided in other languages for decades. According to "Programming Languages: Principles and Practice," 2nd edition, by Kenneth C. Louden (a notable textbook on programming languages), ``Exception handling was pioneered by the language PL/I in the 1960s and significantly advanced in CLU in the 1970s. However, it was only in the 1980s and early 1990s that design questions were largely resolved.'' Ada 83, for which development began in 1976, is probably the first language that provided block-structured exception handling in the modern form. It is generally agreed that exception handling is commonplace in all modern languages.
It has been observed that the CHANGE TEAM construct described in the 6 November 2014 draft of TS 18508 (ISO/IEC JTC1/SC22/WG5 paper N2033) is, in effect, an exception block, but with an intrinsic (and invisible) exception handler that only manages necessary synchronization and
deallocations.
It's time for Fortran to have a complete block-structured exception handling mechanism. Block-structured exception handling has been proposed for Fortran, but has never been implemented.
Objections have been raised to block-structured exception handling, usually citing performance degradation. While some implementations of block-structured exception handling impose expense even if an exception does not occur, block-structured exception handling does not inevitably
impose a significant execution-time penalty if an exception does not occur. For example, the Janus and Verdix Ada compilers' block-structured exception handling mechanisms impose very low cost if an exception does not occur, and the exception handling mechanism provided by the GNU Ada Translator (GNAT) imposes zero cost if an exception does not occur.
The cost of exception handling should not be confused with the cost of exception detection. An exception cannot be handled unless it is detected. Most processors have methods to specify whether certain exceptions, such as subscripts out of bounds, are detected. The cost of an exception handler is the additional cost to provide for handling an exception, if one is detected. Providing a mechanism to handle an exception does not require or imply that the processor is instructed to detect it. If the processor does detect it, the additional overhead to handle it is very
small, or nonexistent, until the exception is detected. If the processor does not detect it, the additional overhead to handle an exception that cannot occur is very small, or nonexistent.
Exceptions_3.pdf
The text was updated successfully, but these errors were encountered: