[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