[b1f225e5] | 1 | Exception Update Proposal
|
---|
| 2 | =========================
|
---|
| 3 |
|
---|
| 4 | Exceptions have pretty constant since the end of Andrew's project (*), with
|
---|
| 5 | the one major addition being some convenience macros. Experience, though, has
|
---|
| 6 | highlighted problems, particular some difficult to understand sections.
|
---|
| 7 |
|
---|
| 8 | (* I'm Andrew, I am writing this in third person so other people can update
|
---|
| 9 | it easily, but I'm not beating on myself.)
|
---|
| 10 |
|
---|
| 11 | A lot of this should be viewed in the context of the "Proposal For Use of
|
---|
| 12 | Virtual Tables" (see `vtable.md`), and will refer to parts of it. In fact,
|
---|
| 13 | a lot of what to be discussed about exceptions is actually how it uses
|
---|
| 14 | that proposal, and at the same time, enabling exceptions is the primary
|
---|
| 15 | motivation behind virtual tables, with others being largely abstract.
|
---|
| 16 |
|
---|
| 17 | Note that the following problem areas are also inter-related with each other,
|
---|
| 18 | the virtual table code (as above) and the exception feature list.
|
---|
| 19 |
|
---|
| 20 | Type/Function Binding
|
---|
| 21 | ---------------------
|
---|
| 22 | Exceptions (and the virtual table system) attempt to use the location based
|
---|
| 23 | binding like the rest of Cforall. However, because exceptions are created and
|
---|
| 24 | used in very different places, without stepping through all the intermediate
|
---|
| 25 | locations. This is the largest user issue the system faces right now.
|
---|
| 26 |
|
---|
| 27 | The full list of things you have to do:
|
---|
| 28 | + Define the structure (in the header).
|
---|
| 29 | + Implement the required functions (in implementation).
|
---|
| 30 | + Forward declare a virtual table instance (in the header).
|
---|
| 31 | + Define the virtual table (in implementation).
|
---|
| 32 | + Pass in the table reference to a constructed exception (at usage).
|
---|
| 33 |
|
---|
| 34 | Even this example has some automation. For example, defining the virtual
|
---|
| 35 | table automatically defines some of the associated functions. This means that
|
---|
| 36 | you cannot control the implementation of those functions.
|
---|
| 37 |
|
---|
| 38 | Helper macros have been plastered all over this process to try and reduce
|
---|
| 39 | the number of steps. It is annoying, repetitive and error prone. It does open
|
---|
| 40 | up new options for customization, similar to call-site binding, but this does
|
---|
| 41 | not get used very often (as of yet, no examples have come up naturally).
|
---|
| 42 |
|
---|
| 43 | It turns out that you usually, almost always even, want a single binding
|
---|
| 44 | between a type and its implementing function. Call-site binding has some
|
---|
| 45 | similar issues, but the extra work for the flexibility is almost entirely
|
---|
| 46 | on the compiler. (See Stack Unwinding for information on why we can't just
|
---|
| 47 | resolve at the throw site.) We have also currently fixed implementations for
|
---|
| 48 | that one binding because of all the short-cut.
|
---|
| 49 |
|
---|
| 50 | There are a couple of directions to go with this, but the root cause is just
|
---|
| 51 | that contextual binding does not work great with large shifts in context.
|
---|
| 52 | There are a couple of ways
|
---|
| 53 | + Add universal (non-contextual) type/function binding. If a given type
|
---|
| 54 | always have the same assertions associated with it, which then must be
|
---|
| 55 | static, this problem goes away. This is a slight loss of functionality,
|
---|
| 56 | but is the strategy used by most type-class systems as well as in
|
---|
| 57 | object-orientated programming, which fits well with exceptions.
|
---|
| 58 | + Improve the user interface. It may not be a logical change so much as
|
---|
| 59 | just a user interface improvement to the system. The limits around
|
---|
| 60 | life-time, memory allocation and resolution timing remain, but new tools
|
---|
| 61 | could at least make the simple use cases easier and less error prone.
|
---|
| 62 | + Remove the need for a stable binding entirely. This would allow the
|
---|
| 63 | contextual binding to be used as it is in the rest of Cforall. However,
|
---|
| 64 | it would likely require the largest changes to exceptions. It would be
|
---|
| 65 | something like separately resolving the assertions at both sites.
|
---|
| 66 |
|
---|
| 67 | Stack Unwinding and Lifetime
|
---|
| 68 | ----------------------------
|
---|
| 69 | Termination exceptions unwind the stack between the throw and the catch.
|
---|
| 70 | This runs all the destructors and releases stored memory. The memory used
|
---|
| 71 | by an exception must remain in scope past the unwind while the handler is
|
---|
| 72 | run.
|
---|
| 73 |
|
---|
| 74 | There are two main ways to make sure memory/data remains live. First, is to
|
---|
| 75 | make sure that the data already has a sufficient life-time. Because it is
|
---|
| 76 | usually not known where an exception will be caught, that usually just means
|
---|
| 77 | something with a static life-time. The second option is to manually extend
|
---|
| 78 | the life-time by copying the data. Most exception handling mechanisms (EHMs)
|
---|
| 79 | use both of these, copying some core data around that might refer to out to
|
---|
| 80 | static information (usually code).
|
---|
| 81 |
|
---|
| 82 | The problem in Cforall, assertions can often be in neither of these areas.
|
---|
| 83 | While the value of the assertion itself is a local value that could be
|
---|
| 84 | copied around, but they often refer to stack allocated thunks, created when
|
---|
| 85 | a polymorphic function was specialized (monomorphized) to a less polymorphic
|
---|
| 86 | form. These are not static and we don't have the layout information to copy
|
---|
| 87 | them either.
|
---|
| 88 |
|
---|
| 89 | The explicit virtual tables is an attempt to address this, because they
|
---|
| 90 | create a minimum on the life-time of the assertions and thunks. This still
|
---|
| 91 | could be unwound if the table is on the stack, but it explicitly shows where
|
---|
| 92 | that limit is, unlike the automatic monomorphiation.
|
---|
| 93 |
|
---|
| 94 | This problem is really dependant on the constraints on it. There are minor
|
---|
| 95 | improvements to be made, however large improvements may only be possible
|
---|
| 96 | by changing the problem. Making non-contextual bindings
|
---|
| 97 | (see Type/Function Bindings) means the information can be static which could
|
---|
| 98 | allow it to be automatically located and reduce the issue.
|
---|
| 99 |
|
---|
| 100 | The only way to remove the life-time limit would be to avoid unwinding the
|
---|
| 101 | stack. If the exception handling code only removes stack frames during
|
---|
| 102 | clean-up, most of these problems go away.
|
---|
| 103 | Resumption exceptions already does this. Termination exceptions might be able
|
---|
| 104 | to work if you unwind after the handler runs.
|
---|
| 105 |
|
---|
| 106 | Exception Matching and Hierarchy
|
---|
| 107 | --------------------------------
|
---|
| 108 | When exceptions are caught, the primary tool to see if a given handler
|
---|
| 109 | should handle a given exception is hierarchical matching.
|
---|
| 110 |
|
---|
| 111 | In terms of the underlying paradigms, this is the most fundamental mismatch,
|
---|
| 112 | because it is borrowed from object-orientated programming, and Cforall is not
|
---|
| 113 | an object-orientated programming language. But we added a limited system that
|
---|
| 114 | mimics that, the virtual type hierarchy, and currently exceptions are the
|
---|
| 115 | only supported use of that system. Also it isn't entirely implemented and
|
---|
| 116 | would require a lot of features with relatively narrow use cases to implement it.
|
---|
| 117 |
|
---|
| 118 | Which does make updating it based on experience a bit harder, so far the only
|
---|
| 119 | real problem is allocating memory in the header. (Currently addressed by the
|
---|
| 120 | cfa_linkonce attribute.) But the missing implementation highlights that maybe
|
---|
| 121 | we don't need the full hierarchy.
|
---|
| 122 |
|
---|
| 123 | If you can handle the exception fully, you can catch the leaf exception and
|
---|
| 124 | run the handling code. If it doesn't matter because the guarded code is
|
---|
| 125 | isolated, then you catch any exception, log it, and then move on.
|
---|
| 126 | In other words, without the hierarchy and just exact matches and a separate
|
---|
| 127 | catch all, that could handle the vast majority of cases.
|
---|
| 128 |
|
---|
| 129 | Even within this solution, there are variants in what do these two cases
|
---|
| 130 | look like. The exact match case is fairly simple, it may not even have to
|
---|
| 131 | pass assertions because the concrete type is known at both locations.
|
---|
| 132 | The catch-all case is the tricky one, because we still have to manipulate
|
---|
| 133 | the exception in a generic form. It does however mean there is a single
|
---|
| 134 | generic interface in that case, which might still be simpler than the current
|
---|
| 135 | system.
|
---|
| 136 |
|
---|
| 137 | (Also in this two layer system, it might also be worth revisiting checked
|
---|
| 138 | exceptions for the exact match cases, because there is a more direct than
|
---|
| 139 | usual logical binding there.)
|
---|