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