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
Paul J. Lucas
2012-Apr-05 04:32 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Mar 23, 2012, at 4:46 PM, Bill Wendling wrote:> 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.OK, after a lot of tinkering (and looking at the ExceptionDemo.cpp file), I have something that works using invoke and landingpad. The code in ExceptionDemo.cpp seems more involved than I (think I) need it to be in that I don't care what exception is thrown or whether it's "foreign" (again, I think). I just want to implement try/finally semantics and propagate whatever exception was thrown back up the call stack. I've written a function to add what I think is all the necessary code for constructing/destructing an object:> AllocaInst* construct_obj( char const *obj_name, Type *obj_type, > Function *ctor, vector<Value*> &ctor_args, > Function *dtor ) { > > // Allocate object's storage and insert 'this' ptr into ctor args. > AllocaInst *const obj_ptr = createEntryBlockAlloca( fn, obj_name, obj_type ); > Value *const void_obj_ptr = builder.CreateBitCast( obj_ptr, void_ptr_type ); > ctor_args.insert( ctor_args.begin(), void_obj_ptr ); > > // Call object's constructor. > BasicBlock *const normal_blk = BasicBlock::Create( ctx, "normal", fn ); > BasicBlock *const unwind_blk = BasicBlock::Create( ctx, "unwind", fn ); > builder.CreateInvoke( ctor, normal_blk, unwind_blk, ctor_args ); > > // Call object's destructor. > BasicBlock *const dtor_blk = BasicBlock::Create( ctx, "dtor", fn ); > builder.SetInsertPoint( dtor_blk ); > builder.CreateCall( dtor, void_obj_ptr ); > builder.CreateBr( unwind_blks_.back() ); > > // Catch exception, if any. > builder.SetInsertPoint( unwind_blk ); > LandingPadInst *const lpad = builder.CreateLandingPad( > caught_result_type_, personality_fn_, 0 > ); > lpad->setCleanup( true ); > builder.CreateStore( lpad, caught_result_storage_ ); > builder.CreateStore( exception_thrown_state_, exception_caught_flag_ ); > builder.CreateBr( unwind_blks_.back() ); > > unwind_blks_.push_back( dtor_blk ); > > builder.SetInsertPoint( normal_blk ); > return obj_ptr; > }The unwind_blks_ is a vector<BasicBlock*> that contains the blocks of code that should be executed in reverse-iterator order as the function is unwinding (either due to an exception being thrown or simply normally). The value of unwind_blks_[0] is the exit block that's created by:> builder.SetInsertPoint( exit_blk_ ); > builder.CreateCondBr( > // If we caught an exception ... > builder.CreateICmpNE( > builder.CreateLoad( exception_caught_flag_ ), > exception_not_thrown_state_ > ), > // ... then branch to the unwind-resume block > unwind_resume_blk, > // ... else branch to the return block > return_blk > );The rest of the unwind_blks_ are the dtor_blk's that are created to call the object's destructors. The unwind_resume_blk referenced in the exit_blk is created by:> BasicBlock *const unwind_resume_blk = BasicBlock::Create( ctx, "unwind_resume", fn ); > builder.SetInsertPoint( unwind_resume_blk ); > builder.CreateResume( builder.CreateLoad( caught_result_storage_ );and the return_blk is simply a CreateRetVoid(). This all seems to work just fine. I can throw a C++ exception either in a C++ object's constructor or in an ordinary member function and the stack unwinds correctly (the object's destructors are called) and the exception is propagated back up the C++ code that called the JIT'd code. So is this the right way to do this? Am I missing anything? - Paul
Bill Wendling
2012-Apr-08 11:20 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Apr 4, 2012, at 9:32 PM, Paul J. Lucas wrote:> On Mar 23, 2012, at 4:46 PM, Bill Wendling wrote:[...]> This all seems to work just fine. I can throw a C++ exception either in a C++ object's constructor or in an ordinary member function and the stack unwinds correctly (the object's destructors are called) and the exception is propagated back up the C++ code that called the JIT'd code. > > So is this the right way to do this? Am I missing anything? >Hi Paul, This looks like roughly what I would expect the code to be. I'd have to see the LLVM IR it generated to be sure. But it looks okay to me. (And if it's working for you, all the better. ;-) ) -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