diff options
author | Mike Stump <mrs@gcc.gnu.org> | 1996-10-29 01:25:27 +0000 |
---|---|---|
committer | Mike Stump <mrs@gcc.gnu.org> | 1996-10-29 01:25:27 +0000 |
commit | 2ed18e63193acfa7eb61e7ffefb7cfe76debcacf (patch) | |
tree | 10099a17eb880ca79f27cc60680069299664d142 /gcc/except.c | |
parent | 3c377a2a6a981ef37ce06a5534d90034c40612f5 (diff) | |
download | gcc-2ed18e63193acfa7eb61e7ffefb7cfe76debcacf.zip gcc-2ed18e63193acfa7eb61e7ffefb7cfe76debcacf.tar.gz gcc-2ed18e63193acfa7eb61e7ffefb7cfe76debcacf.tar.bz2 |
Updates from Bob.
From-SVN: r13061
Diffstat (limited to 'gcc/except.c')
-rw-r--r-- | gcc/except.c | 249 |
1 files changed, 192 insertions, 57 deletions
diff --git a/gcc/except.c b/gcc/except.c index e5407e3..af3f50c 100644 --- a/gcc/except.c +++ b/gcc/except.c @@ -55,10 +55,27 @@ Boston, MA 02111-1307, USA. */ to any arbitrary depth. Also, exception regions cannot cross function boundaries. - Each object file that is compiled with exception handling contains a - static array of exception handlers named __EXCEPTION_TABLE__. Each entry - contains the starting and ending addresses of the exception region, - and the address of the handler designated for that region. + Exception handlers can either be specified by the user (which we + will call a "user-defined handler") or generated by the compiler + (which we will designate as a "cleanup"). Cleanups are used to + perform tasks such as destruction of objects allocated on the + stack. + + In the current implementaion, cleanups are handled by allocating an + exception region for the area that the cleanup is designated for, + and the handler for the region performs the cleanup and then + rethrows the exception to the outer exception region. From the + standpoint of the current implementation, there is little + distinction made between a cleanup and a user-defined handler, and + the phrase "exception handler" can be used to refer to either one + equally well. (The section "Future Directions" below discusses how + this will change). + + Each object file that is compiled with exception handling contains + a static array of exception handlers named __EXCEPTION_TABLE__. + Each entry contains the starting and ending addresses of the + exception region, and the address of the handler designated for + that region. At program startup each object file invokes a function named __register_exceptions with the address of its local @@ -69,87 +86,130 @@ Boston, MA 02111-1307, USA. */ The function __throw () is actually responsible for doing the throw. In the C++ frontend, __throw () is generated on a per-object-file basis for each source file compiled with - -fexceptions. + -fexceptions. Before __throw () is invoked, the current context + of the throw needs to be placed in the global variable __eh_pc. __throw () attempts to find the appropriate exception handler for the PC value stored in __eh_pc by calling __find_first_exception_table_match - (which is defined in libgcc2.c). If an appropriate handler is - found, __throw jumps directly to it. + (which is defined in libgcc2.c). If __find_first_exception_table_match + finds a relevant handler, __throw jumps directly to it. - If a handler for the address being thrown from can't be found, + If a handler for the context being thrown from can't be found, __throw is responsible for unwinding the stack, determining the address of the caller of the current function (which will be used - as the new context to throw from), and then searching for a handler - for the new context. __throw may also call abort () if it is unable - to unwind the stack, and can also call an external library function - named __terminate if it reaches the top of the stack without - finding an appropriate handler. - - Note that some of the regions and handlers are implicitly - generated. The handlers for these regions perform necessary - cleanups (in C++ these cleanups are responsible for invoking - necessary object destructors) before rethrowing the exception to - the outer exception region. + as the new context to throw from), and then restarting the process + of searching for a handler for the new context. __throw may also + call abort () if it is unable to unwind the stack, and can also + call an external library function named __terminate if it reaches + the top of the stack without finding an appropriate handler. (By + default __terminate () invokes abort (), but this behavior can be + changed by the user to perform some sort of cleanup behavior before + exiting). Internal implementation details: - The start of an exception region is indicated by calling - expand_eh_region_start (). expand_eh_region_end (handler) is - subsequently invoked to end the region and to associate a handler - with the region. This is used to create a region that has an - associated cleanup routine for performing tasks like object - destruction. - To associate a user-defined handler with a block of statements, the function expand_start_try_stmts () is used to mark the start of the block of statements with which the handler is to be associated - (which is usually known as a "try block"). All statements that - appear afterwards will be associated with the try block. - - A call to expand_start_all_catch () will mark the end of the try - block, and also marks the start of the "catch block" associated - with the try block. This catch block will only be invoked if an - exception is thrown through the try block. The instructions for the - catch block are kept as a separate sequence, and will be emitted at - the end of the function along with the handlers specified via - expand_eh_region_end (). The end of the catch block is marked with - expand_end_all_catch (). + (which is known as a "try block"). All statements that appear + afterwards will be associated with the try block. + + A call to expand_start_all_catch () marks the end of the try block, + and also marks the start of the "catch block" (the user-defined + handler) associated with the try block. + + This user-defined handler will be invoked for *every* exception + thrown with the context of the try block. It is up to the handler + to decide whether or not it wishes to handle any given exception, + as there is currently no mechanism in this implementation for doing + this. (There are plans for conditionally processing an exception + based on its "type", which will provide a language-independent + mechanism). + + If the handler chooses not to process the exception (perhaps by + looking at an "exception type" or some other additional data + supplied with the exception), it can fall through to the end of the + handler. expand_end_all_catch () and expand_leftover_cleanups () + add additional code to the end of each handler to take care of + rethrowing to the outer exception handler. + + The handler also has the option to continue with "normal flow of + code", or in other words to resume executing at the statement + immediately after the end of the exception region. The variable + caught_return_label_stack contains a stack of labels, and jumping + to the topmost entry's label via expand_goto () will resume normal + flow to the statement immediately after the end of the exception + region. If the handler falls through to the end, the exception will + be rethrown to the outer exception region. + + The instructions for the catch block are kept as a separate + sequence, and will be emitted at the end of the function along with + the handlers specified via expand_eh_region_end (). The end of the + catch block is marked with expand_end_all_catch (). Any data associated with the exception must currently be handled by some external mechanism maintained in the frontend. For example, the C++ exception mechanism passes an arbitrary value along with the exception, and this is handled in the C++ frontend by using a - global variable to hold the value. - - Internally-generated exception regions are marked by calling - expand_eh_region_start () to mark the start of the region, and - expand_eh_region_end () is used to both designate the end of the - region and to associate a handler/cleanup with the region. These - functions generate the appropriate RTL sequences to mark the start - and end of the exception regions and ensure that an appropriate - exception region entry will be added to the exception region table. - expand_eh_region_end () also queues the provided handler to be - emitted at the end of the current function. + global variable to hold the value. (This will be changing in the + future.) + + The mechanism in C++ for handling data associated with the + exception is clearly not thread-safe. For a thread-based + environment, another mechanism must be used (possibly using a + per-thread allocation mechanism if the size of the area that needs + to be allocated isn't known at compile time.) + + Internally-generated exception regions (cleanups) are marked by + calling expand_eh_region_start () to mark the start of the region, + and expand_eh_region_end (handler) is used to both designate the + end of the region and to associate a specified handler/cleanup with + the region. The rtl code in HANDLER will be invoked whenever an + exception occurs in the region between the calls to + expand_eh_region_start and expand_eh_region_end. After HANDLER is + executed, additional code is emitted to handle rethrowing the + exception to the outer exception handler. The code for HANDLER will + be emitted at the end of the function. TARGET_EXPRs can also be used to designate exception regions. A TARGET_EXPR gives an unwind-protect style interface commonly used in functional languages such as LISP. The associated expression is - evaluated, and if it (or any of the functions that it calls) throws - an exception it is caught by the associated cleanup. The backend - also takes care of the details of associating an exception table - entry with the expression and generating the necessary code. + evaluated, and whether or not it (or any of the functions that it + calls) throws an exception, the protect expression is always + invoked. This implementation takes care of the details of + associating an exception table entry with the expression and + generating the necessary code (it actually emits the protect + expression twice, once for normal flow and once for the exception + case). As for the other handlers, the code for the exception case + will be emitted at the end of the function. + + Cleanups can also be specified by using add_partial_entry (handler) + and end_protect_partials (). add_partial_entry creates the start of + a new exception region; HANDLER will be invoked if an exception is + thrown with the context of the region between the calls to + add_partial_entry and end_protect_partials. end_protect_partials is + used to mark the end of these regions. add_partial_entry can be + called as many times as needed before calling end_protect_partials. + However, end_protect_partials should only be invoked once for each + group of calls to add_partial_entry () as the entries are queued + and all of the outstanding entries are processed simultaneously + when end_protect_partials is invoked. Similarly to the other + handlers, the code for HANDLER will be emitted at the end of the + function. The generated RTL for an exception region includes NOTE_INSN_EH_REGION_BEG and NOTE_INSN_EH_REGION_END notes that mark the start and end of the exception region. A unique label is also - generated at the start of the exception region. + generated at the start of the exception region, which is available + by looking at the ehstack variable. The topmost entry corresponds + to the current region. In the current implementation, an exception can only be thrown from a function call (since the mechanism used to actually throw an exception involves calling __throw). If an exception region is created but no function calls occur within that region, the region - can be safely optimized away since no exceptions can ever be caught - in that region. + can be safely optimized away (along with its exception handlers) + since no exceptions can ever be caught in that region. Unwinding the stack: @@ -165,7 +225,15 @@ Boston, MA 02111-1307, USA. */ definition for __unwind_function (), inlined unwinders will be used instead. The main tradeoff here is in text space utilization. Obviously, if inline unwinders have to be generated repeatedly, - this uses more space than if a single routine is used. + this uses much more space than if a single routine is used. + + However, it is simply not possible on some platforms to write a + generalized routine for doing stack unwinding without having some + form of additional data associated with each function. The current + implementation encodes this data in the form of additional machine + instructions. This is clearly not desirable, as it is extremely + inefficient. The next implementation will provide a set of metadata + for each function that will provide the needed information. The backend macro DOESNT_NEED_UNWINDER is used to conditionalize whether or not per-function unwinders are needed. If DOESNT_NEED_UNWINDER @@ -175,7 +243,74 @@ Boston, MA 02111-1307, USA. */ On some platforms it is possible that neither __unwind_function () nor inlined unwinders are available. For these platforms it is not possible to throw through a function call, and abort () will be - invoked instead of performing the throw. */ + invoked instead of performing the throw. + + Future directions: + + Currently __throw () makes no differentiation between cleanups and + user-defined exception regions. While this makes the implementation + simple, it also implies that it is impossible to determine if a + user-defined exception handler exists for a given exception without + completely unwinding the stack in the process. This is undesirable + from the standpoint of debugging, as ideally it would be possible + to trap unhandled exceptions in the debugger before the process of + unwinding has even started. + + This problem can be solved by marking user-defined handlers in a + special way (probably by adding additional bits to exception_table_list). + A two-pass scheme could then be used by __throw () to iterate + through the table. The first pass would search for a relevant + user-defined handler for the current context of the throw, and if + one is found, the second pass would then invoke all needed cleanups + before jumping to the user-defined handler. + + Many languages (including C++ and Ada) make execution of a + user-defined handler conditional on the "type" of the exception + thrown. (The type of the exception is actually the type of the data + that is thrown with the exception.) It will thus be necessary for + __throw () to be able to determine if a given user-defined + exception handler will actually be executed, given the type of + exception. + + One scheme is to add additional information to exception_table_list + as to the types of exceptions accepted by each handler. __throw () + can do the type comparisons and then determine if the handler is + actually going to be executed. + + There is currently no significant level of debugging support + available, other than to place a breakpoint on __throw (). While + this is sufficient in most cases, it would be helpful to be able to + know where a given exception was going to be thrown to before it is + actually thrown, and to be able to choose between stopping before + every exception region (including cleanups), or just user-defined + exception regions. This should be possible to do in the two-pass + scheme by adding additional labels to __throw () for appropriate + breakpoints, and additional debugger commands could be added to + query various state variables to determine what actions are to be + performed next. + + Another major problem that is being worked on is the issue with + stack unwinding on various platforms. Currently the only platform + that has support for __unwind_function () is the Sparc; all other + ports require per-function unwinders, which causes large amounts of + code bloat. + + Ideally it would be possible to store a small set of metadata with + each function that would then make it possible to write a + __unwind_function () for every platform. This would eliminate the + need for per-function unwinders. + + The main reason the data is needed is that on some platforms the + order and types of data stored on the stack can vary depending on + the type of function, its arguments and returned values, and the + compilation options used (optimization versus non-optimization, + -fomit-frame-pointer, processor variations, etc). + + Unfortunately, this also means that throwing through functions that + aren't compiled with exception handling support will still not be + possible on some platforms. This problem is currently being + investigated, but no solutions have been found that do not imply + some unacceptable performance penalties. */ #include "config.h" |