From: bradley@marley.think.com (Bradley Kuszmaul)
Newsgroups: comp.arch,comp.lang.misc
Subject: Re: CLU break and continue (Was: Re: A challenge to the anti-goto)
Date: 9 Nov 92 10:18:22
Organization: Thinking Machines Corporation, Cambridge MA, USA
	<1992Nov3.165005.18037@midway.uchicago.edu>
	<184123@pyramid.pyramid.com> <721054332.26680@minster.york.ac.uk>
	<1992Nov9.114323.22688@rdg.dec.com>
NNTP-Posting-Host: marley.think.com
In-reply-to: jch@rdg.dec.com's message of Mon, 9 Nov 1992 11:43:23 GMT

In article <1992Nov9.114323.22688@rdg.dec.com> jch@rdg.dec.com (John Haxby) writes:

   ...
   It's not surprising that the Ada exception mechanism is similar to CLU's
   since CLU is one of Ada's ancestors.

   CLU has two exception mechanisms for slightly different purposes.  One
   mechanism is for returning an exception from a procedure call: a procedure
   can signal an exception to be caught by its caller (the exception is not
   propagated up the procedure call stack) or by a generic handler for
   unhandled exceptions.  The other mechanism is a local form that allows a
   block (eg in a loop or if-statement) to exit to an enclosing block.
   Apart from the local/procedure-call difference the two mechanisms are
   identical, although `signal' tends to be expensive and `exit' is simply
   an unconditional branch. ...

The signal is not as expensive as you might think.  This is because the
exception is not propagated up the call stack.  I remember descriptions of
two implementations of signal, both of which had the property that
 - if the signal is not raised, there is zero runtime cost (e.g., to set up the
   signal handler.
 - if the signal is raised, only a few instructions need to execute to
   figure out where the signal handler is and to jump to it.
 - if the appropriate handler is not defined, it only takes a few more
   instructions to figure out where the `failure' handler is and to jump to
   it.
Both implementations take advantage of the fact that the return address on
the call stack uniquely identifies the handler for each signal.

Basically the idea is that you use the return address to look up the signal
handler in a hash table, if you don't find anything then you have to signal
a `failure', which is handled the same way, but the compiler always sets up a
`failure' handler for every call.  The hash lookup is very fast, typically
only a few instructions.

This is in contrast to Ada, in which, for all the implementations I can
think of 
 - you pay something every time you set up a handler.
 - when a signal is raised, you have to do some search up the call stack
   for the right handler, doing some sort of hashtable lookup (or linear
   search or ...) at every activation frame.
I can imagine some hardware support for Ada style exceptions:  I believe
that the Symbolics lisp machines provide some support for this (since these
trace-up-the-call-stack-style-exceptions appear in LISP.)  (For that matter
I can imagine hardware support for CLU style exceptions too:  it would
probably be less hardware and would run faster...)  (There ..., I included
something relevant to comp.arch.)

My opinion is that from a software engineering point of view, CLU got it
right and Ada got it wrong.  The standard argument, by example, is this:
  Suppose that you implement a "stack" abstraction, implemented using some
  sort of "array" abstraction.  You might write
     ...
     pop = proc(s : stack[T]) returns(T) signals (empty) 
       index : int
       index := s.index
       if(index=0) signal empty
       else s.index = index-1
            return s.array[index]
       end
     end
         
   Suppose that you make a mistake implementing your stack, and you forgot
   that your "array" abstraction provides FORTRAN arrays (that are indexed
   from 1 instead of from 0), so that the array indexing operation raises
   a `bounds' signal (i.e., the bound was invalid).

   The code for `pop' does not handle the `bounds' exception.  What should
   be done?
    In CLU the `pop' does not handle a `bounds' exception, so `pop' raises
    `failure', meaning that the code failed.  That is exactly what
    happened.  If you are writing code that needs to be reliable, you can  
    handle failure in various ways (e.g., by using three implementations
    and voting on the answer:  If one of the implementations fails, then just
    use an answer from one of the other two), so it is much better to know
    right away that your code failed than to have the signal go
    who-knows-where.

    In Ada, the `bounds' is then raised by `pop', but the `pop' interface
    for `pop' does not mention `bounds', so the abstraction is broken.
    Higher up the call tree somewhere, someone who was handling a `bounds'
    for some completely other reason (e.g., because in some football-field
    abstraction, the ball might leave the playing field), gets this completely
    bogus `bounds' signal and the referee abstraction gets completely
    confused.... 

However, the Ada committee did not buy this argument, so ...
-Bradley

Xref: sparky comp.arch:10458 comp.lang.misc:3525