~pali/+junk/llvm-toolchain-3.7

« back to all changes in this revision

Viewing changes to docs/HistoricalNotes/2001-05-18-ExceptionHandling.txt

  • Committer: Package Import Robot
  • Author(s): Sylvestre Ledru
  • Date: 2015-07-15 17:51:08 UTC
  • Revision ID: package-import@ubuntu.com-20150715175108-l8mynwovkx4zx697
Tags: upstream-3.7~+rc2
ImportĀ upstreamĀ versionĀ 3.7~+rc2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
Meeting notes: Implementation idea: Exception Handling in C++/Java
 
2
 
 
3
The 5/18/01 meeting discussed ideas for implementing exceptions in LLVM.
 
4
We decided that the best solution requires a set of library calls provided by
 
5
the VM, as well as an extension to the LLVM function invocation syntax.
 
6
 
 
7
The LLVM function invocation instruction previously looks like this (ignoring
 
8
types):
 
9
 
 
10
  call func(arg1, arg2, arg3)
 
11
 
 
12
The extension discussed today adds an optional "with" clause that 
 
13
associates a label with the call site.  The new syntax looks like this:
 
14
 
 
15
  call func(arg1, arg2, arg3) with funcCleanup
 
16
 
 
17
This funcHandler always stays tightly associated with the call site (being
 
18
encoded directly into the call opcode itself), and should be used whenever
 
19
there is cleanup work that needs to be done for the current function if 
 
20
an exception is thrown by func (or if we are in a try block).
 
21
 
 
22
To support this, the VM/Runtime provide the following simple library 
 
23
functions (all syntax in this document is very abstract):
 
24
 
 
25
typedef struct { something } %frame;
 
26
  The VM must export a "frame type", that is an opaque structure used to 
 
27
  implement different types of stack walking that may be used by various
 
28
  language runtime libraries. We imagine that it would be typical to 
 
29
  represent a frame with a PC and frame pointer pair, although that is not 
 
30
  required.
 
31
 
 
32
%frame getStackCurrentFrame();
 
33
  Get a frame object for the current function.  Note that if the current
 
34
  function was inlined into its caller, the "current" frame will belong to
 
35
  the "caller".
 
36
 
 
37
bool isFirstFrame(%frame f);
 
38
  Returns true if the specified frame is the top level (first activated) frame
 
39
  for this thread.  For the main thread, this corresponds to the main() 
 
40
  function, for a spawned thread, it corresponds to the thread function.
 
41
 
 
42
%frame getNextFrame(%frame f);
 
43
  Return the previous frame on the stack.  This function is undefined if f
 
44
  satisfies the predicate isFirstFrame(f).
 
45
 
 
46
Label *getFrameLabel(%frame f);
 
47
  If a label was associated with f (as discussed below), this function returns
 
48
  it.  Otherwise, it returns a null pointer.
 
49
 
 
50
doNonLocalBranch(Label *L);
 
51
  At this point, it is not clear whether this should be a function or 
 
52
  intrinsic.  It should probably be an intrinsic in LLVM, but we'll deal with
 
53
  this issue later.
 
54
 
 
55
 
 
56
Here is a motivating example that illustrates how these facilities could be
 
57
used to implement the C++ exception model:
 
58
 
 
59
void TestFunction(...) {
 
60
  A a; B b;
 
61
  foo();        // Any function call may throw
 
62
  bar();
 
63
  C c;
 
64
 
 
65
  try {
 
66
    D d;
 
67
    baz();
 
68
  } catch (int) {
 
69
    ...int Stuff...
 
70
    // execution continues after the try block: the exception is consumed
 
71
  } catch (double) {
 
72
    ...double stuff...
 
73
   throw;            // Exception is propogated
 
74
  }
 
75
}
 
76
 
 
77
This function would compile to approximately the following code (heavy 
 
78
pseudo code follows):
 
79
 
 
80
Func:
 
81
  %a = alloca A
 
82
  A::A(%a)        // These ctors & dtors could throw, but we ignore this 
 
83
  %b = alloca B   // minor detail for this example
 
84
  B::B(%b)
 
85
 
 
86
  call foo() with fooCleanup // An exception in foo is propogated to fooCleanup
 
87
  call bar() with barCleanup // An exception in bar is propogated to barCleanup
 
88
 
 
89
  %c = alloca C
 
90
  C::C(c)
 
91
  %d = alloca D
 
92
  D::D(d)
 
93
  call baz() with bazCleanup // An exception in baz is propogated to bazCleanup
 
94
  d->~D();
 
95
EndTry:                   // This label corresponds to the end of the try block
 
96
  c->~C()       // These could also throw, these are also ignored
 
97
  b->~B()
 
98
  a->~A()
 
99
  return
 
100
 
 
101
Note that this is a very straight forward and literal translation: exactly
 
102
what we want for zero cost (when unused) exception handling.  Especially on
 
103
platforms with many registers (ie, the IA64) setjmp/longjmp style exception
 
104
handling is *very* impractical.  Also, the "with" clauses describe the 
 
105
control flow paths explicitly so that analysis is not adversly effected.
 
106
 
 
107
The foo/barCleanup labels are implemented as:
 
108
 
 
109
TryCleanup:          // Executed if an exception escapes the try block  
 
110
  c->~C()
 
111
barCleanup:          // Executed if an exception escapes from bar()
 
112
  // fall through
 
113
fooCleanup:          // Executed if an exception escapes from foo()
 
114
  b->~B()
 
115
  a->~A()
 
116
  Exception *E = getThreadLocalException()
 
117
  call throw(E)      // Implemented by the C++ runtime, described below
 
118
 
 
119
Which does the work one would expect.  getThreadLocalException is a function
 
120
implemented by the C++ support library.  It returns the current exception 
 
121
object for the current thread.  Note that we do not attempt to recycle the 
 
122
shutdown code from before, because performance of the mainline code is 
 
123
critically important.  Also, obviously fooCleanup and barCleanup may be 
 
124
merged and one of them eliminated.  This just shows how the code generator 
 
125
would most likely emit code.
 
126
 
 
127
The bazCleanup label is more interesting.  Because the exception may be caught
 
128
by the try block, we must dispatch to its handler... but it does not exist
 
129
on the call stack (it does not have a VM Call->Label mapping installed), so 
 
130
we must dispatch statically with a goto.  The bazHandler thus appears as:
 
131
 
 
132
bazHandler:
 
133
  d->~D();    // destruct D as it goes out of scope when entering catch clauses
 
134
  goto TryHandler
 
135
 
 
136
In general, TryHandler is not the same as bazHandler, because multiple 
 
137
function calls could be made from the try block.  In this case, trivial 
 
138
optimization could merge the two basic blocks.  TryHandler is the code 
 
139
that actually determines the type of exception, based on the Exception object
 
140
itself.  For this discussion, assume that the exception object contains *at
 
141
least*:
 
142
 
 
143
1. A pointer to the RTTI info for the contained object
 
144
2. A pointer to the dtor for the contained object
 
145
3. The contained object itself
 
146
 
 
147
Note that it is necessary to maintain #1 & #2 in the exception object itself
 
148
because objects without virtual function tables may be thrown (as in this 
 
149
example).  Assuming this, TryHandler would look something like this:
 
150
 
 
151
TryHandler: 
 
152
  Exception *E = getThreadLocalException();
 
153
  switch (E->RTTIType) {
 
154
  case IntRTTIInfo:
 
155
    ...int Stuff...       // The action to perform from the catch block
 
156
    break;
 
157
  case DoubleRTTIInfo:
 
158
    ...double Stuff...    // The action to perform from the catch block
 
159
    goto TryCleanup       // This catch block rethrows the exception
 
160
    break;                // Redundant, eliminated by the optimizer
 
161
  default:
 
162
    goto TryCleanup       // Exception not caught, rethrow
 
163
  }
 
164
 
 
165
  // Exception was consumed
 
166
  if (E->dtor)
 
167
    E->dtor(E->object)    // Invoke the dtor on the object if it exists
 
168
  goto EndTry             // Continue mainline code...
 
169
 
 
170
And that is all there is to it.
 
171
 
 
172
The throw(E) function would then be implemented like this (which may be 
 
173
inlined into the caller through standard optimization):
 
174
 
 
175
function throw(Exception *E) {
 
176
  // Get the start of the stack trace...
 
177
  %frame %f = call getStackCurrentFrame()
 
178
 
 
179
  // Get the label information that corresponds to it
 
180
  label * %L = call getFrameLabel(%f)
 
181
  while (%L == 0 && !isFirstFrame(%f)) {
 
182
    // Loop until a cleanup handler is found
 
183
    %f = call getNextFrame(%f)
 
184
    %L = call getFrameLabel(%f)
 
185
  }
 
186
 
 
187
  if (%L != 0) {
 
188
    call setThreadLocalException(E)   // Allow handlers access to this...
 
189
    call doNonLocalBranch(%L)
 
190
  }
 
191
  // No handler found!
 
192
  call BlowUp()         // Ends up calling the terminate() method in use
 
193
}
 
194
 
 
195
That's a brief rundown of how C++ exception handling could be implemented in
 
196
llvm.  Java would be very similar, except it only uses destructors to unlock
 
197
synchronized blocks, not to destroy data.  Also, it uses two stack walks: a
 
198
nondestructive walk that builds a stack trace, then a destructive walk that
 
199
unwinds the stack as shown here. 
 
200
 
 
201
It would be trivial to get exception interoperability between C++ and Java.
 
202