[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing

Paul J. Lucas paul at lucasmail.org
Wed Apr 4 21:32:21 PDT 2012


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





More information about the llvm-dev mailing list