Jun 12, 2017, 3:30:08 PM (7 years ago)
Andrew Beach <ajbeach@…>
ADT, aaron-thesis, arm-eh, ast-experimental, cleanup-dtors, deferred_resn, demangler, enum, forall-pointer-decay, jacob/cs343-translation, jenkins-sandbox, master, new-ast, new-ast-unique-expr, new-env, no_list, persistent-indexer, pthread-emulation, qualifiedEnum, resolv-new, with_gc
8d50e34, a724ac1

Update to the exception handling mechanism plan.

1 edited


  • doc/working/exception/design.txt

    rca78437 r465ed18  
    11Design of Exceptions and EHM in Cforall:
    3 Currently this is a combination of ideas and big questions that still have to
    4 be addressed. It also includes some other error handling options, how they
    5 interact and compare to exceptions.
     4Exception Instances:
     5Currently, exceptions are integers (like errno).
     7They are planned to be the new "tagged structures", which allows them to
     8exist in a simple hierarchy which shared functionality throughout. However
     9the tagged structures are not yet implemented so that will wait.
     11A single built in exception is at the top of the hierarchy and all other
     12exceptions are its children. When you match against an exception, you match
     13for an exception and its children, so the top of the hierarchy is used as a
     14catch-all option.
     16The shared functionality across exceptions has not been finalized, but will
     17probably include things like human readable descriptions and default handlers.
    8 What is an Exception:
     21There are currently two kinds of throws, "throw" for termination and
     22"throwResume" for resumption. Both keywords can be used to create a throw
     23statement. The kind of throw decides what handlers may catch the exception
     24and weither control flow can return to the throw site.
    10 In other words what do we throw? What is matched against, how does it carry
    11 data with it? A very important question that has not been answered.
     27"throw" exception ";"
     28"throwResume" exception ";"
    13 Option 1: Strutures
     30Non-local throws are allowed for resumption only. A target is an object with
     31a stack, with which it may propagate and handle the exception.
    15 Considering the current state of Cforall the most natural form of the
    16 exception would likely be a struture, implementing a trait repersenting the
    17 minimum features of an exception. This has many advantages, including arbitray
    18 fields, some polymorphism and it matches exceptations of many current systems.
     34"throwResume" exception "_At" target ";"
    20 The main issue with this is matching, without OOP inheritance there is no
    21 exception hierarchy. Meaning all handling has to happen on the exact exception
    22 without the ease of grouping parents. There are several ways to attempt to
    23 recover this.
    25 The first is with conditional matching (a check after the type has been
    26 matched) which allows for matching particular values of a known type. However
    27 this does not dynamically expand and requires an extra step (as opposed to
    28 mearly allowing one). I would not recomend this as the primary method.
    30 Second is to try and use type casts/conversions to create an implicate
    31 hierachy, so that a catch clause catches anything of the given type or
    32 anything that converts to the given type.
    34 Plan9 (from what I know of it) would be a powerful tool here. Even with it,
    35 creating a hierarchy of types at runtime might be too expencive. Esecially
    36 since they are less likely to be tree like at that point.
    38 Option 2: Tags
    40 The other option is to introduce a new construct into the language. A tag
    41 repersents a type of exception, it is not a structure or variable (or even
    42 a normal type). It may be usable in some of those contexts.
    44 Tags can declare an existing tag as its parent. Tags can be caught by handlers
    45 that catch their parents. (There is a single base_exception that all other
    46 exceptions are children of eventually.) This allows for grouping of exceptions
    47 that repersent similar errors.
    49 Tags should also have some assotiated data, where and on what did the error
    50 occur. Keeping with the otherness of exception tags and allowing them to be
    51 expanded, using a parameter list. Each exception can have a list of paramters
    52 given to it on a throw. Each tag would have a declared list of parameters
    53 (which could be treated more like a set of fields as well). Child tags must
    54 use their parent's list as a prefix to their own, so that the parameters can
    55 be accessed when the child tag is matched against the parent.
    57 Option N: ...
    59 This list is not complete.
     36Termination throws unwind the stack until a handler is reached, control moves
     37onwards from the end of the handler. Resumption throws do not unwind, if a
     38handler is found and control will return to the throw after the exception is
    62 Seperating Termination and Resumption:
     43The catch and handle of an exception is preformed with a try statement, which
     44also can have finally clauses to exceute on exit from the scope.
    64 Differentating the types of exceptions based on exception would be hard with
    65 exceptions as structures. It is possible with exceptions as tags by having
    66 two base exceptions, one for each type of throw. However recompining them
    67 to dual types would be harder.
     48        try-block
     49( ("catch" | "catchResume")
     50  "(" exception_type [identifier] [";" conditional_expression] ")"
     51        catch-block
     54        finally-block
    69 Reguardless, using different keywords would also be useful for clarity, even
    70 if it does not add functality. Using the C++ like keywords would be a good
    71 base. Resumption exceptions could use a different set (ex. raise->handle) or
    72 use resume as a qualifier on the existing statements.
     57Either at least 1 handler clause or the finally clasue must be given on each
     58try block. Each handler clause handles 1 of the two types of throws. Each
     59handler also specifies a type of exception it handles, and will handle all
     60children exceptions as well. In addition, a conditional expression which, if
     61included, must be true for the handler to catch the exception.
     63The two types of handlers may be intermixed. Multiple handlers catching the
     64same type may also be used, to allow for fallbacks on false conditionals.
    75 Conditional Matching:
     67Implementation Overview:
    77 A possible useful feature, it allows for arbitrary checks on a catch block
    78 instead of merely matching a type. However there are few use cases that
    79 cannot be avoided with proper use of a type hierarchy, and this shrinks even
    80 further with a good use of re-throws.
     69The implementation has two main parts. The first is just a collection of the
     70support definitions we need, the data types and functions used within the
     71exception handling code. Second is a translation from Cforall code to C code
     72that uses those definitions to throw, catch and handle exceptions.
    82 Also it assumes one sweep, that might also be a problem. But might also give
    83 it an advantage over re-throws.
     74Termination handlers call a specially annotated function, passing it inner
     75functions that act as the varius sub-blocks. Termination throws use the
     76unwind library that checks the underlying code for those annotations. Each
     77time one is found some magic is used to check for a matching handler, if one
     78is found control goes to the special function which excecutes the handler and
     81Resumption handlers maintain a linked list of stack allocated nodes that have
     82the handler functions attached. Throwing a resumption exception traverses this
     83list, and calls each handler, the handlers handle the exception if they can
     84and return if they did or not.
     86Finally clauses just use stack cleanup to force a nested function, which has
     87the code from the finally clause, to execute when we leave that section.
    86 Alternatives: Implicate Handlers & Return Unions
     90Alternative Error Handling: Return Unions
    88 Both act as a kind of local version of an exception. Implicate handlers act as
    89 resumption exceptions and return unions like termination exceptions. By local
    90 I mean they work well at one or two levels of calls, but do not cover N levels
    91 as cleanly.
     92Return unions (Maybe and Result), are types that can encode a success or
     93other result in a single value. Maybe stores a value or nothing, Result stores
     94a value or an error.
    93 Implicate handles require a growing number of function pointers (which should
    94 not be used very often) to be passed to functions, creating and additional
    95 preformance cost. Return unions have to be checked and handled at every level,
    96 which has some preformance cost, but also can significantly clutter code.
    97 Proper tools can help with the latter.
     96For errors that are usually handled quite close to where they occur, these
     97can replace exceptions.
    99 However, they may work better at that local level as they do not require stack
    100 walking or unwinding. In addition they are closer to regular control flow and
    101 are easier to staticly check. So even if they can't replace exceptions
    102 (directly) they may still be worth using together.
    104 For instance, consider the Python iterator interface. It uses a single
    105 function, __next__, to access the next value and to signal the end of the
    106 sequence. If a value is returned, it is the next value, if the StopIteration
    107 exception is thrown the sequence has finished.
    109 However almost every use of an iterator will add a try-except block around the
    110 call site (possibly through for or next) to catch and handle the exception
    111 immediately, ignoring the advantages of more distant exception handling.
    113 In this case it may be cleaner to use a Maybe for both cases (as in Rust)
    114 which gives similar results without having to jump to the exception handler.
    115 This will likely handle the error case more efficiently and the success case a
    116 bit less so.
    118 It also mixes the error and regular control flow, which can hurt readablity,
    119 but very little if the handling is simple, for instance providing a default
    120 value. Similarly, if the error (or alternate outcome) is common enough
    121 encoding it in the function signature may be good commuication.
     99They tend to be faster and require similar or less amounts of code to handle.
     100However they can slow down the normal path with some extra conditionals and
     101can mix the normal and exceptional control flow path. If handling the error
     102is simple, and happens relatively frequently, this might be prefered but in
     103other cases it just hurts speed and readability.
    123105In short, these errors seem to be more effective when errors are likely and
    125107be handled locally, might be better off using these instead of exceptions.
    127 Also the implicate handlers and return unions could use exceptions as well.
    128 For instance, a useful default might handler might be to throw an exception,
    129 seaching up the stack for a solution if one is not locally provided.
    131 Or here is a possible helper for unpacking a Result value:
     109Also the return unions could use exceptions as well. Getting the improper
     110side of a return union might throw an exception. Or we can provide helpers
     111for results withe exceptions as in:
    132112                forall(otype T, otype E | exception(E))
    133113                T get_or_throw (Result(T, E) * this) {
    134                         if (is_success(this)) {
    135                                 return get_success(this);
     114                        if (has_value(this)) {
     115                                return get_value(this);
    136116                        } else {
    137                                 throw get_failure(this);
     117                                throw get_error(this);
    138118                        }
    139119                }
    140 So they can feed off of each other.
Note: See TracChangeset for help on using the changeset viewer.