From: Daniel Dunbar Date: Tue, 30 Sep 2008 01:06:03 +0000 (+0000) Subject: Add infrastructure for proper @finally support. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=898d508d4c9e9d45914952473e39196b20830a9f;p=clang Add infrastructure for proper @finally support. - Provides a basic primitive to jump to an arbitrary basic block, through the finally code. - Only used for return statements and rethrow currently. Still need to handle break, continue and goto. - Code still needs to be shuffled around to live elsewhere. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@56827 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/CodeGen/CGObjCMac.cpp b/lib/CodeGen/CGObjCMac.cpp index a35e90f8ac..42fad09cee 100644 --- a/lib/CodeGen/CGObjCMac.cpp +++ b/lib/CodeGen/CGObjCMac.cpp @@ -1406,12 +1406,14 @@ The basic framework for a @try-catch-finally is as follows: // fell off end, rethrow. _rethrow = _caught; + ... jump-through-finally to finally_rethrow ... } else { // exception in catch block _rethrow = objc_exception_extract(&d); - goto finally_no_exit; + ... jump-through-finally_no_exit to finally_rethrow ... } } + ... jump-through-finally to finally_end ... finally: // match either the initial try_enter or the catch try_enter, @@ -1419,18 +1421,18 @@ finally: objc_exception_try_exit(&d); finally_no_exit: ... finally block .... - if (_rethrow) - objc_exception_throw(_rethrow); + ... dispatch to finally destination ... + +finally_rethrow: + objc_exception_throw(_rethrow); + +finally_end: } This framework differs slightly from the one gcc uses, in that gcc -uses _rethrow to determine if objc_exception_try_exit should be -called. This breaks in the face of throwing nil and introduces an -unnecessary branch. Note that our framework still does not properly -handle throwing nil, as a nil object will not be rethrown. - -FIXME: Determine if _rethrow should be integrated into the other -architecture for selecting paths out of the finally block. +uses _rethrow to determine if objc_exception_try_exit should be called +and if the object should be rethrown. This breaks in the face of +throwing nil and introduces unnecessary branches. We specialize this framework for a few particular circumstances: @@ -1450,19 +1452,47 @@ Support for implicit rethrows and jumping through the finally block is handled by storing the current exception-handling context in ObjCEHStack. +In order to implement proper @finally semantics, we support one basic +mechanism for jumping through the finally block to an arbitrary +destination. Constructs which generate exits from a @try or @catch +block use this mechanism to implement the proper semantics by chaining +jumps, as necessary. + +This mechanism works like the one used for indirect goto: we +arbitrarily assign an ID to each destination and store the ID for the +destination in a variable prior to entering the finally block. At the +end of the finally block we simply create a switch to the proper +destination. + */ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, - const ObjCAtTryStmt &S) -{ - // Allocate exception data. + const ObjCAtTryStmt &S) { + // Create various blocks we refer to for handling @finally. + llvm::BasicBlock *FinallyBlock = llvm::BasicBlock::Create("finally"); + llvm::BasicBlock *FinallyNoExit = llvm::BasicBlock::Create("finally.noexit"); + llvm::BasicBlock *FinallyRethrow = llvm::BasicBlock::Create("finally.throw"); + llvm::BasicBlock *FinallyEnd = llvm::BasicBlock::Create("finally.end"); + llvm::Value *DestCode = + CGF.CreateTempAlloca(llvm::Type::Int32Ty, "finally.dst"); + + // Generate jump code. Done here so we can directly add things to + // the switch instruction. + llvm::BasicBlock *FinallyJump = llvm::BasicBlock::Create("finally.jump"); + llvm::SwitchInst *FinallySwitch = + llvm::SwitchInst::Create(new llvm::LoadInst(DestCode, "", FinallyJump), + FinallyEnd, 10, FinallyJump); + + // Push an EH context entry, used for handling rethrows and jumps + // through finally. + CodeGenFunction::ObjCEHEntry EHEntry(FinallyBlock, FinallyNoExit, + FinallySwitch, DestCode); + CGF.ObjCEHStack.push_back(&EHEntry); + + // Allocate memory for the exception data and rethrow pointer. llvm::Value *ExceptionData = CGF.CreateTempAlloca(ObjCTypes.ExceptionDataTy, "exceptiondata.ptr"); - - // Allocate memory for the rethrow pointer. - llvm::Value *RethrowPtr = CGF.CreateTempAlloca(ObjCTypes.ObjectPtrTy); - CGF.Builder.CreateStore(llvm::Constant::getNullValue(ObjCTypes.ObjectPtrTy), - RethrowPtr); + llvm::Value *RethrowPtr = CGF.CreateTempAlloca(ObjCTypes.ObjectPtrTy, "_rethrow"); // Enter a new try block and call setjmp. CGF.Builder.CreateCall(ObjCTypes.ExceptionTryEnterFn, ExceptionData); @@ -1471,25 +1501,16 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, JmpBufPtr = CGF.Builder.CreateStructGEP(JmpBufPtr, 0, "tmp"); llvm::Value *SetJmpResult = CGF.Builder.CreateCall(ObjCTypes.SetJmpFn, JmpBufPtr, "result"); - - - llvm::BasicBlock *FinallyBlock = llvm::BasicBlock::Create("finally"); - llvm::BasicBlock *FinallyNoExit = llvm::BasicBlock::Create("finally.noexit"); - + llvm::BasicBlock *TryBlock = llvm::BasicBlock::Create("try"); llvm::BasicBlock *TryHandler = llvm::BasicBlock::Create("try.handler"); - CGF.Builder.CreateCondBr(CGF.Builder.CreateIsNonNull(SetJmpResult, "threw"), TryHandler, TryBlock); - // Push an EH context entry for use by rethrow and - // jumps-through-finally. - CGF.ObjCEHStack.push_back(CodeGenFunction::ObjCEHEntry(FinallyBlock)); - // Emit the @try block. CGF.EmitBlock(TryBlock); CGF.EmitStmt(S.getTryBody()); - CGF.Builder.CreateBr(FinallyBlock); + CGF.EmitJumpThroughFinally(&EHEntry, FinallyEnd); // Emit the "exception in @try" block. CGF.EmitBlock(TryHandler); @@ -1499,7 +1520,7 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, llvm::Value *Caught = CGF.Builder.CreateCall(ObjCTypes.ExceptionExtractFn, ExceptionData, "caught"); - CGF.ObjCEHStack.back().Exception = Caught; + EHEntry.Exception = Caught; if (const ObjCAtCatchStmt* CatchStmt = S.getCatchStmts()) { // Enter a new exception try block (in case a @catch block throws // an exception). @@ -1520,7 +1541,7 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, // so. bool AllMatched = false; for (; CatchStmt; CatchStmt = CatchStmt->getNextCatchStmt()) { - llvm::BasicBlock *NextCatchBlock = llvm::BasicBlock::Create("nextcatch"); + llvm::BasicBlock *NextCatchBlock = llvm::BasicBlock::Create("catch"); const DeclStmt *CatchParam = cast_or_null(CatchStmt->getCatchParamStmt()); @@ -1549,7 +1570,7 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, } CGF.EmitStmt(CatchStmt->getCatchBody()); - CGF.Builder.CreateBr(FinallyBlock); + CGF.EmitJumpThroughFinally(&EHEntry, FinallyEnd); break; } @@ -1579,7 +1600,7 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, CGF.Builder.CreateStore(Tmp, CGF.GetAddrOfLocalVar(VD)); CGF.EmitStmt(CatchStmt->getCatchBody()); - CGF.Builder.CreateBr(FinallyBlock); + CGF.EmitJumpThroughFinally(&EHEntry, FinallyEnd); CGF.EmitBlock(NextCatchBlock); } @@ -1588,7 +1609,7 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, // None of the handlers caught the exception, so store it to be // rethrown at the end of the @finally block. CGF.Builder.CreateStore(Caught, RethrowPtr); - CGF.Builder.CreateBr(FinallyBlock); + CGF.EmitJumpThroughFinally(&EHEntry, FinallyRethrow); } // Emit the exception handler for the @catch blocks. @@ -1596,41 +1617,37 @@ void CGObjCMac::EmitTryStmt(CodeGen::CodeGenFunction &CGF, CGF.Builder.CreateStore(CGF.Builder.CreateCall(ObjCTypes.ExceptionExtractFn, ExceptionData), RethrowPtr); - CGF.Builder.CreateBr(FinallyNoExit); + CGF.EmitJumpThroughFinally(&EHEntry, FinallyRethrow, false); } else { CGF.Builder.CreateStore(Caught, RethrowPtr); - CGF.Builder.CreateBr(FinallyNoExit); + CGF.EmitJumpThroughFinally(&EHEntry, FinallyRethrow, false); } + // Pop the exception-handling stack entry. It is important to do + // this now, because the code in the @finally block is not in this + // context. + CGF.ObjCEHStack.pop_back(); + // Emit the @finally block. CGF.EmitBlock(FinallyBlock); CGF.Builder.CreateCall(ObjCTypes.ExceptionTryExitFn, ExceptionData); CGF.EmitBlock(FinallyNoExit); - if (const ObjCAtFinallyStmt* FinallyStmt = S.getFinallyStmt()) CGF.EmitStmt(FinallyStmt->getFinallyBody()); - llvm::Value *Rethrow = CGF.Builder.CreateLoad(RethrowPtr); - - llvm::BasicBlock *RethrowBlock = llvm::BasicBlock::Create("rethrow"); - llvm::BasicBlock *FinallyEndBlock = llvm::BasicBlock::Create("finally.end"); - - // If necessary, rethrow the exception. - CGF.Builder.CreateCondBr(CGF.Builder.CreateIsNonNull(Rethrow, "rethrow.test"), - RethrowBlock, FinallyEndBlock); - CGF.EmitBlock(RethrowBlock); - CGF.Builder.CreateCall(ObjCTypes.ExceptionThrowFn, Rethrow); + CGF.EmitBlock(FinallyJump); + + CGF.EmitBlock(FinallyRethrow); + CGF.Builder.CreateCall(ObjCTypes.ExceptionThrowFn, + CGF.Builder.CreateLoad(RethrowPtr)); CGF.Builder.CreateUnreachable(); - - CGF.ObjCEHStack.pop_back(); - - CGF.EmitBlock(FinallyEndBlock); + + CGF.EmitBlock(FinallyEnd); } void CGObjCMac::EmitThrowStmt(CodeGen::CodeGenFunction &CGF, - const ObjCAtThrowStmt &S) -{ + const ObjCAtThrowStmt &S) { llvm::Value *ExceptionAsObject; if (const Expr *ThrowExpr = S.getThrowExpr()) { @@ -1638,9 +1655,9 @@ void CGObjCMac::EmitThrowStmt(CodeGen::CodeGenFunction &CGF, ExceptionAsObject = CGF.Builder.CreateBitCast(Exception, ObjCTypes.ObjectPtrTy, "tmp"); } else { - assert((!CGF.ObjCEHStack.empty() && CGF.ObjCEHStack.back().Exception) && + assert((!CGF.ObjCEHStack.empty() && CGF.ObjCEHStack.back()->Exception) && "Unexpected rethrow outside @catch block."); - ExceptionAsObject = CGF.ObjCEHStack.back().Exception; + ExceptionAsObject = CGF.ObjCEHStack.back()->Exception; } CGF.Builder.CreateCall(ObjCTypes.ExceptionThrowFn, ExceptionAsObject); @@ -1648,6 +1665,35 @@ void CGObjCMac::EmitThrowStmt(CodeGen::CodeGenFunction &CGF, CGF.EmitBlock(llvm::BasicBlock::Create("bb")); } +void CodeGenFunction::EmitJumpThroughFinally(ObjCEHEntry *E, + llvm::BasicBlock *Dst, + bool ExecuteTryExit) { + llvm::BasicBlock *Src = Builder.GetInsertBlock(); + + if (isDummyBlock(Src)) + return; + + // Find the destination code for this block. We always use 0 for the + // fallthrough block (default destination). + llvm::SwitchInst *SI = E->FinallySwitch; + llvm::ConstantInt *ID; + if (Dst == SI->getDefaultDest()) { + ID = llvm::ConstantInt::get(llvm::Type::Int32Ty, 0); + } else { + ID = SI->findCaseDest(Dst); + if (!ID) { + // No code found, get a new unique one by just using the number + // of switch successors. + ID = llvm::ConstantInt::get(llvm::Type::Int32Ty, SI->getNumSuccessors()); + SI->addCase(ID, Dst); + } + } + + // Set the destination code and branch. + Builder.CreateStore(ID, E->DestCode); + Builder.CreateBr(ExecuteTryExit ? E->FinallyBlock : E->FinallyNoExit); +} + /* *** Private Interface *** */ /// EmitImageInfo - Emit the image info marker used to encode some module diff --git a/lib/CodeGen/CGStmt.cpp b/lib/CodeGen/CGStmt.cpp index eb9dac1569..96550d6d53 100644 --- a/lib/CodeGen/CGStmt.cpp +++ b/lib/CodeGen/CGStmt.cpp @@ -437,6 +437,15 @@ void CodeGenFunction::EmitReturnStmt(const ReturnStmt &S) { EmitAggExpr(RV, ReturnValue, false); } + if (!ObjCEHStack.empty()) { + for (ObjCEHStackType::reverse_iterator i = ObjCEHStack.rbegin(), + e = ObjCEHStack.rend(); i != e; ++i) { + llvm::BasicBlock *ReturnPad = llvm::BasicBlock::Create("return.pad"); + EmitJumpThroughFinally(*i, ReturnPad); + EmitBlock(ReturnPad); + } + } + Builder.CreateBr(ReturnBlock); // Emit a block after the branch so that dead code after a return has some diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index 6aa2e4e79d..8058c342e7 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -31,6 +31,7 @@ namespace llvm { class BasicBlock; class Module; + class SwitchInst; } namespace clang { @@ -88,13 +89,42 @@ public: // inside @catch blocks and which @finally block exits from an EH // scope should be chained through. struct ObjCEHEntry { - ObjCEHEntry(llvm::BasicBlock *fb) - : Exception(0), FinallyBlock(fb) {} + ObjCEHEntry(llvm::BasicBlock *fb, llvm::BasicBlock *fne, + llvm::SwitchInst *fs, llvm::Value *dc) + : FinallyBlock(fb), FinallyNoExit(fne), FinallySwitch(fs), + DestCode(dc), Exception(0) {} - llvm::Value *Exception; - llvm::BasicBlock *FinallyBlock; + /// Entry point to the finally block. + llvm::BasicBlock *FinallyBlock; + + /// Entry point to the finally block which skips execution of the + /// try_exit runtime function. + llvm::BasicBlock *FinallyNoExit; + + /// Switch instruction which runs at the end of the finally block + /// to forward jumps through the finally block. + llvm::SwitchInst *FinallySwitch; + + /// Variable holding the code for the destination of a jump + /// through the @finally block. + llvm::Value *DestCode; + + /// The exception object being handled, during IR generation for a + /// @catch block. + llvm::Value *Exception; }; - llvm::SmallVector ObjCEHStack; + + typedef llvm::SmallVector ObjCEHStackType; + ObjCEHStackType ObjCEHStack; + + /// EmitJumpThroughFinally - Emit a branch from the current insert + /// point through the finally handling code for \arg Entry and then + /// on to \arg Dest. + /// + /// \param ExecuteTryExit - When true, the try_exit runtime function + /// should be called prior to executing the finally code. + void EmitJumpThroughFinally(ObjCEHEntry *Entry, llvm::BasicBlock *Dest, + bool ExecuteTryExit=true); private: /// LabelIDs - Track arbitrary ids assigned to labels for use in