aboutsummaryrefslogtreecommitdiff
path: root/gcc/except.c
diff options
context:
space:
mode:
authorMike Stump <mrs@gcc.gnu.org>1996-10-29 01:25:27 +0000
committerMike Stump <mrs@gcc.gnu.org>1996-10-29 01:25:27 +0000
commit2ed18e63193acfa7eb61e7ffefb7cfe76debcacf (patch)
tree10099a17eb880ca79f27cc60680069299664d142 /gcc/except.c
parent3c377a2a6a981ef37ce06a5534d90034c40612f5 (diff)
downloadgcc-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.c249
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"