Bill Wendling
2012-Mar-23 00:29 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Mar 22, 2012, at 11:40 AM, Paul J. Lucas <paul at lucasmail.org> wrote:> On Mar 22, 2012, at 12:28 AM, Bill Wendling wrote: > >> On Mar 20, 2012, at 7:38 PM, Paul J. Lucas wrote: >> >>> I've read the docs on LLVM exceptions, but I don't see any examples. A little help? >> >> I don't think this has anything to do with LLVM's IR-level exception system. It sounds to me like you just need a way to handle C++ exceptions inside of the C++ code and then rethrow so that the JIT's caller can do its thing. (Right?) > > Right. The call sequence is: > > my_lib(1) -> JIT_code -> C_thunk -> my_lib(2) > > The JIT code creates Functions that create C++ objects on their stacks (by using alloca instructions then calling a C thunk that calls the C++ object's constructor via placement new). If an exception is thrown in my_lib(2), then somewhere between there and when the stack unwinds to my_lib(1), the C++ objects that were created on the stack must have their destructors called (also via C thunks). Hence, some code somewhere between my_lib(1) and C_thunk has to catch all exceptions, call the destructors, and rethrow the exceptions. > >> You could move the C++ code into a C++ function that catches all exceptions. The C functions you provide would call the small bit of C++ code that would then execute the "real" functionality. You would have to wrap/unwrap the variables, of course. (There are examples of wrapping/unwrapping of variables in LLVM's source tree.) That way you will get to use C++'s exception handling system instead of creating your own, which is a huge massive undertaking full of pitfalls. When you rethrow the exception, it will propagate past the C function to the code calling the JIT'ed code. > > 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. -bw> extern "C" bool thunk_iterator_M_next( void *v_that, void *v_result, > dtor_pairs *dtors ) { > try { > item_iterator *const that = static_cast<item_iterator*>( v_that ); > item *const result = static_cast<item*>( v_result ); > return that->next( result ); > } > catch ( ... ) { > run_dtors( dtors ); > throw; > } > } > > - Paul >
Paul J. Lucas
2012-Mar-23 13:27 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
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? - Paul
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
Reasonably Related 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