[4706098c] | 1 | \chapter{\texorpdfstring{Unwinding in \CFA}{Unwinding in Cforall}} |
---|
[2c052c0] | 2 | |
---|
[4f3a4d75] | 3 | Stack unwinding is the process of removing stack frames (activations) from the |
---|
| 4 | stack. On function entry and return, unwinding is handled directly by the code |
---|
| 5 | embedded in the function. Usually, the stack-frame size is known statically |
---|
| 6 | based on parameters and local variable declarations. For dynamically-sized |
---|
| 7 | local variables, a runtime computation is necessary to know the frame |
---|
| 8 | size. Finally, a function's frame-size may change during execution as local |
---|
| 9 | variables (static or dynamic sized) go in and out of scope. |
---|
| 10 | Allocating/deallocating stack space is usually an $O(1)$ operation achieved by |
---|
| 11 | bumping the hardware stack-pointer up or down as needed. |
---|
| 12 | |
---|
| 13 | Unwinding across multiple stack frames is more complex because individual stack |
---|
| 14 | management code associated with each frame is bypassed. That is, the location |
---|
| 15 | of a function's frame code is largely unknown and dispersed throughout the |
---|
| 16 | function, hence the current stack-frame size managed by that code is also |
---|
| 17 | unknown. Hence, code unwinding across frames does not have direct knowledge |
---|
| 18 | about what is on the stack, and hence, how much of the stack needs to be |
---|
| 19 | removed. |
---|
| 20 | |
---|
| 21 | The traditional unwinding mechanism for C is implemented by saving a snap-shot |
---|
| 22 | of a function's state with @setjmp@ and restoring that snap-shot with |
---|
| 23 | @longjmp@. This approach bypasses the need to know stack details by simply |
---|
| 24 | reseting to a snap-shot of an arbitrary but existing function frame on the |
---|
| 25 | stack. It is up to the programmer to ensure the snap-shot is valid when it is |
---|
| 26 | reset, making the code fragile with potential errors that are difficult to |
---|
| 27 | debug because the stack becomes corrupted. |
---|
| 28 | |
---|
| 29 | However, many languages define cleanup actions that have to be taken when |
---|
| 30 | something is deallocated from the stack or blocks end, such as running a |
---|
| 31 | variable's destructor or a @try@ statement's @finally@ clause. Handling these |
---|
| 32 | mechanisms requires walking the stack and checking each stack frame for these |
---|
| 33 | potential actions. |
---|
| 34 | |
---|
| 35 | For exceptions, it must be possible to walk the stack frames in search of try |
---|
| 36 | statements with handlers to perform exception matching. For termination |
---|
| 37 | exceptions, it must be possible to unwind all stack frames from the throw to |
---|
| 38 | the matching catch, and each of these frames must be checked for cleanup |
---|
| 39 | actions. Stack walking is where the most of the complexity and expense of |
---|
| 40 | exception handling comes from. |
---|
| 41 | |
---|
| 42 | One of the most popular tools for stack management is libunwind, a low level |
---|
| 43 | library that provides tools for stack walking and unwinding. What follows is an |
---|
| 44 | overview of all the relevant features of libunwind and how \CFA uses them to |
---|
| 45 | implement its exception handling. |
---|
[2c052c0] | 46 | |
---|
| 47 | \section{libunwind Usage} |
---|
[4f3a4d75] | 48 | \CFA uses two primary functions in libunwind to create most of its exceptional |
---|
| 49 | control-flow: @_Unwind_RaiseException@ and @_Unwind_ForcedUnwind@. Their |
---|
| 50 | operation is divided into two phases: search and clean-up. The search phase -- |
---|
| 51 | phase 1 -- is used to scan the stack but not unwinding it. The clean-up phase |
---|
| 52 | -- phase 2 -- is used for unwinding. |
---|
[2c052c0] | 53 | |
---|
[2655031] | 54 | The raise-exception function uses both phases. It starts by searching for a |
---|
[21f914e] | 55 | handler, and if found, performs a clean-up phase to unwind the stack to the |
---|
[2655031] | 56 | handler. If a handler is not found, control returns allowing the |
---|
[c72ea7a] | 57 | exception-handling policy for unhandled exception to be executed. During both |
---|
[2655031] | 58 | phases, the raise-exception function searches down the stack, calling each |
---|
[21f914e] | 59 | function's \emph{personality function}. |
---|
[2c052c0] | 60 | |
---|
[2655031] | 61 | A personality function performs three tasks, although not all have to be |
---|
| 62 | present. The tasks performed are decided by the actions provided. |
---|
[4f3a4d75] | 63 | @_Unwind_Action@ is a bitmask of possible actions and an argument of this type |
---|
| 64 | is passed into the personality function. |
---|
[2c052c0] | 65 | \begin{itemize} |
---|
[4f3a4d75] | 66 | \item |
---|
| 67 | \begin{sloppypar} |
---|
| 68 | @_UA_SEARCH_PHASE@ is passed in for the search phase and tells the personality |
---|
| 69 | function to check for handlers. If there is a handler in a stack frame, as |
---|
| 70 | defined by the language, the personality function returns @_URC_HANDLER_FOUND@; |
---|
| 71 | otherwise it return @_URC_CONTINUE_UNWIND@. |
---|
| 72 | \end{sloppypar} |
---|
| 73 | \item |
---|
| 74 | @_UA_CLEANUP_PHASE@ is passed in during the clean-up phase and means part or |
---|
| 75 | all of the stack frame is removed. The personality function does whatever |
---|
| 76 | clean-up the language defines (such as running destructors/finalizers) and then |
---|
| 77 | generally returns @_URC_CONTINUE_UNWIND@. |
---|
| 78 | \item |
---|
| 79 | @_UA_HANDLER_FRAME@ means the personality function must install a handler. It |
---|
| 80 | is also passed in during the clean-up phase and is in addition to the clean-up |
---|
| 81 | action. libunwind provides several helpers for the personality function. Once |
---|
| 82 | it is done, the personality function returns @_URC_INSTALL_CONTEXT@. |
---|
[2c052c0] | 83 | \end{itemize} |
---|
[4f3a4d75] | 84 | The personality function is given a number of other arguments. Some arguments |
---|
| 85 | are for compatibility, and there is the @struct _Unwind_Context@ pointer which |
---|
| 86 | is passed to many helpers to get information about the current stack frame. |
---|
| 87 | |
---|
| 88 | For cancellation, forced-unwind only performs the clean-up phase. It takes |
---|
| 89 | three arguments: a pointer to the exception, a pointer to the stop function and |
---|
| 90 | a pointer to the stop parameter. It does most of the same actions as phase two |
---|
| 91 | of raise-exception but passes in an extra action to the personality function on |
---|
| 92 | each stack frame, @_UA_FORCE_UNWIND@, which means a handler cannot be |
---|
[c72ea7a] | 93 | installed. |
---|
| 94 | |
---|
[4f3a4d75] | 95 | As well, forced-unwind calls the stop function each time it steps into a frame, |
---|
| 96 | before calling the personality function. The stop function receives all the |
---|
| 97 | same arguments as the personality function and the stop parameter supplied to |
---|
| 98 | forced-unwind. |
---|
[c72ea7a] | 99 | |
---|
| 100 | The stop function is called one more time at the end of the stack after all |
---|
[4f3a4d75] | 101 | stack frames have been removed. The standard API marks this frame by setting |
---|
[c72ea7a] | 102 | the stack pointer inside the context passed to the stop function. However both |
---|
[f28fdee] | 103 | GCC and Clang add an extra action for this case @_UA_END_OF_STACK@. |
---|
[c72ea7a] | 104 | |
---|
[4f3a4d75] | 105 | Each time the stop function is called, it can do one or two things. When it is |
---|
| 106 | not the end of the stack it can return @_URC_NO_REASON@ to continue unwinding. |
---|
[2c052c0] | 107 | % Is there a reason that NO_REASON is used instead of CONTINUE_UNWIND? |
---|
[4f3a4d75] | 108 | The other option is to use some other means to transfer control elsewhere and |
---|
| 109 | never return to its caller. libunwind provides no additional tools for |
---|
| 110 | alternate transfers of control. |
---|
[2c052c0] | 111 | |
---|
[4706098c] | 112 | \section{\texorpdfstring{\CFA Implementation}{Cforall Implementation}} |
---|
[2c052c0] | 113 | |
---|
[4f3a4d75] | 114 | To use libunwind, \CFA provides several wrappers, its own storage, personality |
---|
| 115 | functions, and a stop function. |
---|
[2c052c0] | 116 | |
---|
[21f914e] | 117 | The wrappers perform three tasks: set-up, clean-up and controlling the |
---|
[2655031] | 118 | unwinding. The set-up allocates a copy of the \CFA exception into a handler to |
---|
[c72ea7a] | 119 | control its lifetime, and stores it in the exception context. Clean-up -- run |
---|
[2655031] | 120 | when control exits a catch clause and returns to normal code -- frees the |
---|
| 121 | exception copy. |
---|
[2c052c0] | 122 | % It however does not set up the unwind exception so we can't use any inter- |
---|
| 123 | % runtime/language features. Also the exception context is global. |
---|
| 124 | |
---|
[c72ea7a] | 125 | The core control code is called every time a throw -- after set-up -- or |
---|
| 126 | re-throw is run. It uses raise-exception to search for a handler and to run it |
---|
[4f3a4d75] | 127 | if one is found. If no handler is found and raise-exception returns, then |
---|
[c72ea7a] | 128 | forced-unwind is called to run all destructors on the stack before terminating |
---|
| 129 | the process. |
---|
[2c052c0] | 130 | |
---|
[4f3a4d75] | 131 | The stop function is simple. It checks for the end of stack flag to see if |
---|
| 132 | unwinding is finished. If so, it calls @exit@ to end the process, otherwise it |
---|
| 133 | returns with no-reason to continue unwinding. |
---|
[2c052c0] | 134 | % Yeah, this is going to have to change. |
---|
| 135 | |
---|
[2655031] | 136 | The personality routine is more complex because it has to obtain information |
---|
[4f3a4d75] | 137 | about the function by scanning the Language Specific Data Area (LSDA). This |
---|
[2655031] | 138 | step allows a single personality function to be used for multiple functions and |
---|
[4f3a4d75] | 139 | lets that personality function figure out exactly where in the function |
---|
| 140 | execution is, what is currently in the stack frame, and what handlers should be |
---|
| 141 | checked. |
---|
[2c052c0] | 142 | % Not that we do that yet. |
---|
| 143 | |
---|
[4f3a4d75] | 144 | It is also necessary to generate the LSDA, which is difficult. It requires |
---|
| 145 | knowledge about the location of the instruction pointer and stack layout, which |
---|
| 146 | varies with compiler and optimization levels. Fortunately, for frames where |
---|
| 147 | there are only destructors, GCC's attribute cleanup with the @-fexception@ flag |
---|
| 148 | is sufficient to handle unwinding. |
---|
| 149 | |
---|
| 150 | The only functions that require more information are those containing @try@ |
---|
| 151 | statements. Specifically, only @try@ statements with @catch@ clauses need to be |
---|
| 152 | transformed. The @try@ statement is converted into a series of closures that |
---|
| 153 | can access other parts of the function according to scoping rules but can be |
---|
| 154 | passed around. The @catch@ clauses are converted into two functions: the match |
---|
| 155 | function and the handler function. |
---|
| 156 | |
---|
| 157 | Together the match function and the catch function form the code that runs when |
---|
| 158 | an exception passes out of the guarded block for a try statement. The match |
---|
| 159 | function is used during the search phase: it is passed an exception and checks |
---|
| 160 | each handler to see if the raised exception matches the handler exception. It |
---|
| 161 | returns an index that represents which handler matched or there is no |
---|
| 162 | match. The catch function is used during the clean-up phase, it is passed an |
---|
| 163 | exception and the index of a handler. It casts the exception to the exception |
---|
| 164 | type declared in that handler and then runs the handler's body. |
---|
| 165 | |
---|
| 166 | These three functions are passed to @try_terminate@, which is an |
---|
[c72ea7a] | 167 | % Maybe I shouldn't quote that, it isn't its actual name. |
---|
[4f3a4d75] | 168 | internal hand-written function that has its own personality function and custom |
---|
| 169 | assembly LSDA for doing the exception handling in \CFA. During normal |
---|
| 170 | execution, this function calls the try function and then return. It is only |
---|
| 171 | when exceptions are thrown that anything interesting happens. |
---|
[c72ea7a] | 172 | |
---|
| 173 | During the search phase the personality function gets the pointer to the match |
---|
[4f3a4d75] | 174 | function and calls it. If the match function returns a handler index, the |
---|
[c72ea7a] | 175 | personality function saves it and reports that the handler has been found, |
---|
[4f3a4d75] | 176 | otherwise unwinding continues. During the clean-up phase, the personality |
---|
| 177 | function only performs an action, when a handler is found in a frame. For each |
---|
| 178 | found frame, the personality function installs the handler, which sets the |
---|
| 179 | instruction pointer in @try_terminate@ to an otherwise unused section that |
---|
| 180 | calls the catch function, passing it the current exception and handler index. |
---|
| 181 | @try_terminate@ returns as soon as the catch function returns. At this point |
---|
| 182 | control has returned to normal control flow. |
---|
| 183 | |
---|
| 184 | {\color{blue}PAB: Maybe a diagram would be helpful?} |
---|