Bill Wendling
2012-Mar-23 22:25 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Mar 23, 2012, at 6:27 AM, Paul J. Lucas wrote:> On Mar 22, 2012, at 5:29 PM, Bill Wendling wrote: > >> On Mar 22, 2012, at 11:40 AM, Paul J. Lucas <paul at lucasmail.org> wrote: >> >>> Unfortunately, I'm not following. How is having the code that catches all exceptions in a separate function different from what I proposed (putting the try/catch in the thunks)? (Ideally, I want to minimize layers of function calls.) Again for reference: >> >> No reason. But if you have the 'try{}catch(...){}', then it should run the d'tors for you. There's no reason for you to have a "run_dtors" function there. > > How does the C++ implementation "know" to run the d'tors for me since the C++ objects that were created on the stack were created by JIT'd code, first via alloca to allocate StructTypes of the right size (char[sizeof(T)]) then calling a thunk of the form: > > extern "C" void thunk_item_M_new( void *addr ) { > new( addr ) item; > } > > where "addr" is the address returned by alloca? To me, it would seem that if you're right, that I shouldn't need any try/catch at all. > > When I put tracer print statements in my class's destructors, I can see that they are called only if I explicitly call them via JIT'd code that calls the relevant thunk, e.g.: > > extern "C" void thunk_item_M_delete( void *v_that ) { > item *const that = static_cast<item*>( v_that ); > that->~item(); > } > > Given that I have to call the thunks to run the d'tors manually in the normal control-flow case, it would seem that I would also have to call them in the exception-thrown control-flow case -- right? >In the cases above, you've allocated in some capacity. The d'tor of a pointer is...nothing. It doesn't do anything. You have to manually call "delete" on the pointer. E.g.: Bar *foo() { Bar *b = new Bar(); return b; // b goes out of scope, but b::~b isn't called. } void qux() { Bar *b = foo(); // do stuff with b. delete b; // Now we can delete the stuff. } Let's take your example. You will have code that looks like this: extern "C" void thunk_item_M_delete( void *v_that ) { item *that = 0; try { that = static_cast<item*>( v_that ); that->~item(); } catch (...) { that->~item(); } } The unwinding mechanism "knows" how to call the stuff in the 'catch' clause because of the exception handling metadata that's produced for this function (which is beyond the scope of this thread). Now the point I was making is that all of the locally scoped variables will have their d'tors called automatically by the unwinding mechanism. So something like this: void foo() { try { Bar b; mux(b); // may throw an exception. } catch (...) { // The d'tor for 'b' is called before the code is is executed. } } -bw
Paul J. Lucas
2012-Mar-23 23:29 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Mar 23, 2012, at 3:25 PM, Bill Wendling wrote:> Let's take your example. You will have code that looks like this: > > extern "C" void thunk_item_M_delete( void *v_that ) { > item *that = 0; > try { > that = static_cast<item*>( v_that ); > that->~item(); > } catch (...) { > that->~item(); > } > }No I wouldn't since destructors should never throw exceptions: http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.9> Now the point I was making is that all of the locally scoped variables will have their d'tors called automatically by the unwinding mechanism. So something like this: > > void foo() { > try { > Bar b; > mux(b); // may throw an exception. > } catch (...) { > // The d'tor for 'b' is called before the code is is executed. > } > }Except I don't have code like that. Again, the object is *not* constructed by a normal function, but a JIT'd function. Since LLVM knows nothing about C++ or destructors, I certainly have to call the destructor manually, e.g., obj->~Class();, for the case where no exception is thrown. My JIT'd code looks like: %item = type { [16 x i8] } %singleton_iterator = type { [24 x i8] } %add_iterator = type { [24 x i8] } define void @program(void* %result) { entry: ; alloc raw space for x on the stack %x = alloca %item, align 8 ; alloc raw space for y on the stack %y = alloca %item, align 8 ; call item::item(1) constructor for x as new(%0) item(1); %0 = bitcast %item* %x to void* call void @thunk_item_M_new_i(void* %0, i32 1) ; call item::item(2) constructor for y as new(%1) item(2); %1 = bitcast %item* %y to void* call void @thunk_item_M_new_i(void* %1, i32 2) ; Do something with x & y via another thunk_ function that MIGHT throw an exception. ; If an exception is thrown, the code below will never be reached. ; call destructor for x as %1->!item(); call void @thunk_item_M_delete(void* %1) ; call destructor for y as %1->!item(); call void @thunk_item_M_delete(void* %0) ret void } Clearly, I *must* call the destructors manually for the case where no exception is thrown (again, because LLVM knows nothing about C++ or destructors). If I do nothing else and an exception is thrown during the use of x & y, then control flow will never reach my JIT'd code that calls the destructors manually. I've tested this. It behaves as I describe it. So, to ensure the destructors are called, I have to catch the exception, call the destructors, and rethrow. I can't see any other way despite what you've been saying. If I'm still wrong, I'd really appreciate your continued patience in explaining where. - Paul
Bill Wendling
2012-Mar-23 23:46 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Mar 23, 2012, at 4:29 PM, Paul J. Lucas wrote:> On Mar 23, 2012, at 3:25 PM, Bill Wendling wrote: > >> Let's take your example. You will have code that looks like this: >> >> extern "C" void thunk_item_M_delete( void *v_that ) { >> item *that = 0; >> try { >> that = static_cast<item*>( v_that ); >> that->~item(); >> } catch (...) { >> that->~item(); >> } >> } > > No I wouldn't since destructors should never throw exceptions:You're missing the point.>> Now the point I was making is that all of the locally scoped variables will have their d'tors called automatically by the unwinding mechanism. So something like this: >> >> void foo() { >> try { >> Bar b; >> mux(b); // may throw an exception. >> } catch (...) { >> // The d'tor for 'b' is called before the code is is executed. >> } >> } > > Except I don't have code like that. Again, the object is *not* constructed by a normal function, but a JIT'd function. Since LLVM knows nothing about C++ or destructors, I certainly have to call the destructor manually, e.g., obj->~Class();, for the case where no exception is thrown. My JIT'd code looks like: > > %item = type { [16 x i8] } > %singleton_iterator = type { [24 x i8] } > %add_iterator = type { [24 x i8] } > > define void @program(void* %result) { > entry: > ; alloc raw space for x on the stack > %x = alloca %item, align 8 > > ; alloc raw space for y on the stack > %y = alloca %item, align 8 > > ; call item::item(1) constructor for x as new(%0) item(1); > %0 = bitcast %item* %x to void* > call void @thunk_item_M_new_i(void* %0, i32 1) > > ; call item::item(2) constructor for y as new(%1) item(2); > %1 = bitcast %item* %y to void* > call void @thunk_item_M_new_i(void* %1, i32 2) > > ; Do something with x & y via another thunk_ function that MIGHT throw an exception. > ; If an exception is thrown, the code below will never be reached. > > ; call destructor for x as %1->!item(); > call void @thunk_item_M_delete(void* %1) > > ; call destructor for y as %1->!item(); > call void @thunk_item_M_delete(void* %0) > ret void > } > > Clearly, I *must* call the destructors manually for the case where no exception is thrown (again, because LLVM knows nothing about C++ or destructors). If I do nothing else and an exception is thrown during the use of x & y, then control flow will never reach my JIT'd code that calls the destructors manually. I've tested this. It behaves as I describe it. > > So, to ensure the destructors are called, I have to catch the exception, call the destructors, and rethrow. I can't see any other way despite what you've been saying. If I'm still wrong, I'd really appreciate your continued patience in explaining where. >You need to look at how the equivalent code in C++ looks in LLVM IR. The "do something with x & y" part will be an 'invoke' that lands on at landing pad, where you will then call the d'tor to your allocated object. -bw
Maybe Matching Threads
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing