Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
R
riscv-gcc-1
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lvzhengyang
riscv-gcc-1
Commits
2ed18e63
Commit
2ed18e63
authored
Oct 29, 1996
by
Mike Stump
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updates from Bob.
From-SVN: r13061
parent
3c377a2a
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
192 additions
and
57 deletions
+192
-57
gcc/except.c
+192
-57
No files found.
gcc/except.c
View file @
2ed18e63
...
...
@@ -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
f
ound
, __throw jumps directly to it.
(which is defined in libgcc2.c). If
__find_first_exception_table_match
f
inds 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"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment