1 | \chapter{\texorpdfstring{Unwinding in \CFA}{Unwinding in Cforall}} |
---|
2 | |
---|
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. |
---|
46 | |
---|
47 | \section{libunwind Usage} |
---|
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. |
---|
53 | |
---|
54 | The raise-exception function uses both phases. It starts by searching for a |
---|
55 | handler, and if found, performs a clean-up phase to unwind the stack to the |
---|
56 | handler. If a handler is not found, control returns allowing the |
---|
57 | exception-handling policy for unhandled exception to be executed. During both |
---|
58 | phases, the raise-exception function searches down the stack, calling each |
---|
59 | function's \emph{personality function}. |
---|
60 | |
---|
61 | A personality function performs three tasks, although not all have to be |
---|
62 | present. The tasks performed are decided by the actions provided. |
---|
63 | @_Unwind_Action@ is a bitmask of possible actions and an argument of this type |
---|
64 | is passed into the personality function. |
---|
65 | \begin{itemize} |
---|
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@. |
---|
83 | \end{itemize} |
---|
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 |
---|
93 | installed. |
---|
94 | |
---|
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. |
---|
99 | |
---|
100 | The stop function is called one more time at the end of the stack after all |
---|
101 | stack frames have been removed. The standard API marks this frame by setting |
---|
102 | the stack pointer inside the context passed to the stop function. However both |
---|
103 | GCC and Clang add an extra action for this case @_UA_END_OF_STACK@. |
---|
104 | |
---|
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. |
---|
107 | % Is there a reason that NO_REASON is used instead of CONTINUE_UNWIND? |
---|
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. |
---|
111 | |
---|
112 | \section{\texorpdfstring{\CFA Implementation}{Cforall Implementation}} |
---|
113 | |
---|
114 | To use libunwind, \CFA provides several wrappers, its own storage, personality |
---|
115 | functions, and a stop function. |
---|
116 | |
---|
117 | The wrappers perform three tasks: set-up, clean-up and controlling the |
---|
118 | unwinding. The set-up allocates a copy of the \CFA exception into a handler to |
---|
119 | control its lifetime, and stores it in the exception context. Clean-up -- run |
---|
120 | when control exits a catch clause and returns to normal code -- frees the |
---|
121 | exception copy. |
---|
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 | |
---|
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 |
---|
127 | if one is found. If no handler is found and raise-exception returns, then |
---|
128 | forced-unwind is called to run all destructors on the stack before terminating |
---|
129 | the process. |
---|
130 | |
---|
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. |
---|
134 | % Yeah, this is going to have to change. |
---|
135 | |
---|
136 | The personality routine is more complex because it has to obtain information |
---|
137 | about the function by scanning the Language Specific Data Area (LSDA). This |
---|
138 | step allows a single personality function to be used for multiple functions and |
---|
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. |
---|
142 | % Not that we do that yet. |
---|
143 | |
---|
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 |
---|
167 | % Maybe I shouldn't quote that, it isn't its actual name. |
---|
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. |
---|
172 | |
---|
173 | During the search phase the personality function gets the pointer to the match |
---|
174 | function and calls it. If the match function returns a handler index, the |
---|
175 | personality function saves it and reports that the handler has been found, |
---|
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?} |
---|