blob: b546301d35a40467067176bbd7735fe78e3e3065 [file] [log] [blame]
Chris Lattner00950542001-06-06 20:29:01 +00001Meeting notes: Implementation idea: Exception Handling in C++/Java
2
3The 5/18/01 meeting discussed ideas for implementing exceptions in LLVM.
4We decided that the best solution requires a set of library calls provided by
5the VM, as well as an extension to the LLVM function invocation syntax.
6
7The LLVM function invocation instruction previously looks like this (ignoring
8types):
9
10 call func(arg1, arg2, arg3)
11
12The extension discussed today adds an optional "with" clause that
13associates a label with the call site. The new syntax looks like this:
14
15 call func(arg1, arg2, arg3) with funcCleanup
16
17This funcHandler always stays tightly associated with the call site (being
18encoded directly into the call opcode itself), and should be used whenever
19there is cleanup work that needs to be done for the current function if
20an exception is thrown by func (or if we are in a try block).
21
22To support this, the VM/Runtime provide the following simple library
23functions (all syntax in this document is very abstract):
24
25typedef 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
37bool 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
46Label *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
50doNonLocalBranch(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
56Here is a motivating example that illustrates how these facilities could be
57used to implement the C++ exception model:
58
59void 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
77This function would compile to approximately the following code (heavy
78pseudo code follows):
79
80Func:
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();
95EndTry: // 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
101Note that this is a very straight forward and literal translation: exactly
102what we want for zero cost (when unused) exception handling. Especially on
103platforms with many registers (ie, the IA64) setjmp/longjmp style exception
104handling is *very* impractical. Also, the "with" clauses describe the
105control flow paths explicitly so that analysis is not adversly effected.
106
107The foo/barCleanup labels are implemented as:
108
109TryCleanup: // Executed if an exception escapes the try block
110 c->~C()
111barCleanup: // Executed if an exception escapes from bar()
112 // fall through
113fooCleanup: // 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
119Which does the work one would expect. getThreadLocalException is a function
120implemented by the C++ support library. It returns the current exception
121object for the current thread. Note that we do not attempt to recycle the
122shutdown code from before, because performance of the mainline code is
123critically important. Also, obviously fooCleanup and barCleanup may be
124merged and one of them eliminated. This just shows how the code generator
125would most likely emit code.
126
127The bazCleanup label is more interesting. Because the exception may be caught
128by the try block, we must dispatch to its handler... but it does not exist
129on the call stack (it does not have a VM Call->Label mapping installed), so
130we must dispatch statically with a goto. The bazHandler thus appears as:
131
132bazHandler:
133 d->~D(); // destruct D as it goes out of scope when entering catch clauses
134 goto TryHandler
135
136In general, TryHandler is not the same as bazHandler, because multiple
137function calls could be made from the try block. In this case, trivial
138optimization could merge the two basic blocks. TryHandler is the code
139that actually determines the type of exception, based on the Exception object
140itself. For this discussion, assume that the exception object contains *at
141least*:
142
1431. A pointer to the RTTI info for the contained object
1442. A pointer to the dtor for the contained object
1453. The contained object itself
146
Misha Brukman5560c9d2003-08-18 14:43:39 +0000147Note that it is necessary to maintain #1 & #2 in the exception object itself
Chris Lattner00950542001-06-06 20:29:01 +0000148because objects without virtual function tables may be thrown (as in this
149example). Assuming this, TryHandler would look something like this:
150
151TryHandler:
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
170And that is all there is to it.
171
172The throw(E) function would then be implemented like this (which may be
173inlined into the caller through standard optimization):
174
175function 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
195That's a brief rundown of how C++ exception handling could be implemented in
196llvm. Java would be very similar, except it only uses destructors to unlock
197synchronized blocks, not to destroy data. Also, it uses two stack walks: a
198nondestructive walk that builds a stack trace, then a destructive walk that
199unwinds the stack as shown here.
200
201It would be trivial to get exception interoperability between C++ and Java.
202