diff --git a/gen/funcgenstate.cpp b/gen/funcgenstate.cpp index 032b026128..577aa3a85e 100644 --- a/gen/funcgenstate.cpp +++ b/gen/funcgenstate.cpp @@ -25,12 +25,6 @@ GotoJump::GotoJump(Loc loc, llvm::BasicBlock *sourceBlock, : sourceLoc(std::move(loc)), sourceBlock(sourceBlock), tentativeTarget(tentativeTarget), targetLabel(targetLabel) {} -CatchScope::CatchScope(llvm::Constant *classInfoPtr, - llvm::BasicBlock *bodyBlock, CleanupCursor cleanupScope, - llvm::MDNode *branchWeights) - : classInfoPtr(classInfoPtr), bodyBlock(bodyBlock), - cleanupScope(cleanupScope), branchWeights(branchWeights) {} - namespace { #if LDC_LLVM_VER >= 308 @@ -348,58 +342,17 @@ void ScopeStack::popCleanups(CleanupCursor targetScope) { } } -void ScopeStack::pushCatch(llvm::Constant *classInfoPtr, - llvm::BasicBlock *bodyBlock, - llvm::MDNode *matchWeights) { - if (useMSVCEH()) { -#if LDC_LLVM_VER >= 308 - assert(isCatchSwitchBlock(bodyBlock)); - pushCleanup(bodyBlock, bodyBlock); -#endif - } else { - catchScopes.emplace_back(classInfoPtr, bodyBlock, currentCleanupScope(), - matchWeights); +void ScopeStack::pushTryCatch(TryCatchStatement *stmt, + llvm::BasicBlock *endbb) { + tryCatchScopes.push(stmt, endbb); + if (!useMSVCEH()) currentLandingPads().push_back(nullptr); - } } -void ScopeStack::popCatch() { - if (useMSVCEH()) { -#if LDC_LLVM_VER >= 308 - assert(isCatchSwitchBlock(cleanupScopes.back().beginBlock)); - popCleanups(currentCleanupScope() - 1); -#endif - } else { - catchScopes.pop_back(); +void ScopeStack::popTryCatch() { + tryCatchScopes.pop(); + if (!useMSVCEH()) currentLandingPads().pop_back(); - } -} - -void ScopeStack::pushTryBlock(bool catchesNonExceptions) { - catchingNonExceptions.push_back(catchesNonExceptions); -} - -void ScopeStack::popTryBlock() { - catchingNonExceptions.pop_back(); -} - -bool ScopeStack::isCatchingNonExceptions() const { - bool hasCatchScopes = !catchScopes.empty(); - if (useMSVCEH()) { -#if LDC_LLVM_VER >= 308 - hasCatchScopes = std::any_of( - cleanupScopes.begin(), cleanupScopes.end(), - [](const CleanupScope &c) { return isCatchSwitchBlock(c.beginBlock); }); -#else - hasCatchScopes = false; -#endif - } - - if (!hasCatchScopes) - return false; - - assert(!catchingNonExceptions.empty()); - return catchingNonExceptions.back(); } void ScopeStack::pushLoopTarget(Statement *loopStatement, @@ -499,142 +452,11 @@ llvm::BasicBlock *&ScopeStack::getLandingPadRef(CleanupCursor scope) { llvm::BasicBlock *ScopeStack::getLandingPad() { llvm::BasicBlock *&landingPad = getLandingPadRef(currentCleanupScope() - 1); - if (!landingPad) { -#if LDC_LLVM_VER >= 308 - if (useMSVCEH()) { - assert(currentCleanupScope() > 0); - landingPad = emitLandingPadMSVCEH(currentCleanupScope() - 1); - } else -#endif - landingPad = emitLandingPad(); - } + if (!landingPad) + landingPad = tryCatchScopes.emitLandingPad(); return landingPad; } -namespace { -llvm::LandingPadInst *createLandingPadInst(IRState &irs) { - LLType *retType = - LLStructType::get(LLType::getInt8PtrTy(irs.context()), - LLType::getInt32Ty(irs.context()), nullptr); -#if LDC_LLVM_VER >= 307 - LLFunction *currentFunction = irs.func()->func; - if (!currentFunction->hasPersonalityFn()) { - LLFunction *personalityFn = - getRuntimeFunction(Loc(), irs.module, "_d_eh_personality"); - currentFunction->setPersonalityFn(personalityFn); - } - return irs.ir->CreateLandingPad(retType, 0); -#else - LLFunction *personalityFn = - getRuntimeFunction(Loc(), irs.module, "_d_eh_personality"); - return irs.ir->CreateLandingPad(retType, personalityFn, 0); -#endif -} -} - -#if LDC_LLVM_VER >= 308 -llvm::BasicBlock *ScopeStack::emitLandingPadMSVCEH(CleanupCursor scope) { - - LLFunction *currentFunction = irs.func()->func; - if (!currentFunction->hasPersonalityFn()) { - const char *personality = "__CxxFrameHandler3"; - LLFunction *personalityFn = - getRuntimeFunction(Loc(), irs.module, personality); - currentFunction->setPersonalityFn(personalityFn); - } - - if (scope == 0) - return runCleanupPad(scope, nullptr); - - llvm::BasicBlock *&pad = getLandingPadRef(scope - 1); - if (!pad) - pad = emitLandingPadMSVCEH(scope - 1); - - return runCleanupPad(scope, pad); -} -#endif - -llvm::BasicBlock *ScopeStack::emitLandingPad() { - // save and rewrite scope - IRScope savedIRScope = irs.scope(); - - llvm::BasicBlock *beginBB = - llvm::BasicBlock::Create(irs.context(), "landingPad", irs.topfunc()); - irs.scope() = IRScope(beginBB); - - llvm::LandingPadInst *landingPad = createLandingPadInst(irs); - - // Stash away the exception object pointer and selector value into their - // stack slots. - llvm::Value *ehPtr = DtoExtractValue(landingPad, 0); - irs.ir->CreateStore(ehPtr, irs.funcGen().getOrCreateEhPtrSlot()); - - llvm::Value *ehSelector = DtoExtractValue(landingPad, 1); - if (!irs.funcGen().ehSelectorSlot) { - irs.funcGen().ehSelectorSlot = - DtoRawAlloca(ehSelector->getType(), 0, "eh.selector"); - } - irs.ir->CreateStore(ehSelector, irs.funcGen().ehSelectorSlot); - - // Add landingpad clauses, emit finallys and 'if' chain to catch the - // exception. - CleanupCursor lastCleanup = currentCleanupScope(); - for (auto it = catchScopes.rbegin(), end = catchScopes.rend(); it != end; - ++it) { - // Insert any cleanups in between the last catch we ran (i.e. tested for - // and found that the type does not match) and this one. - assert(lastCleanup >= it->cleanupScope); - if (lastCleanup > it->cleanupScope) { - landingPad->setCleanup(true); - llvm::BasicBlock *afterCleanupBB = llvm::BasicBlock::Create( - irs.context(), beginBB->getName() + llvm::Twine(".after.cleanup"), - irs.topfunc()); - runCleanups(lastCleanup, it->cleanupScope, afterCleanupBB); - irs.scope() = IRScope(afterCleanupBB); - lastCleanup = it->cleanupScope; - } - - // Add the ClassInfo reference to the landingpad instruction so it is - // emitted to the EH tables. - landingPad->addClause(it->classInfoPtr); - - llvm::BasicBlock *mismatchBB = llvm::BasicBlock::Create( - irs.context(), beginBB->getName() + llvm::Twine(".mismatch"), - irs.topfunc()); - - // "Call" llvm.eh.typeid.for, which gives us the eh selector value to - // compare the landing pad selector value with. - llvm::Value *ehTypeId = - irs.ir->CreateCall(GET_INTRINSIC_DECL(eh_typeid_for), - DtoBitCast(it->classInfoPtr, getVoidPtrType())); - - // Compare the selector value from the unwinder against the expected - // one and branch accordingly. - irs.ir->CreateCondBr( - irs.ir->CreateICmpEQ(irs.ir->CreateLoad(irs.funcGen().ehSelectorSlot), - ehTypeId), - it->bodyBlock, mismatchBB, it->branchWeights); - irs.scope() = IRScope(mismatchBB); - } - - // No catch matched. Execute all finallys and resume unwinding. - if (lastCleanup > 0) { - landingPad->setCleanup(true); - runCleanups(lastCleanup, 0, irs.funcGen().getOrCreateResumeUnwindBlock()); - } else if (!catchScopes.empty()) { - // Directly convert the last mismatch branch into a branch to the - // unwind resume block. - irs.scopebb()->replaceAllUsesWith( - irs.funcGen().getOrCreateResumeUnwindBlock()); - irs.scopebb()->eraseFromParent(); - } else { - irs.ir->CreateBr(irs.funcGen().getOrCreateResumeUnwindBlock()); - } - - irs.scope() = savedIRScope; - return beginBB; -} - llvm::BasicBlock *SwitchCaseTargets::get(Statement *stmt) { auto it = targetBBs.find(stmt); assert(it != targetBBs.end()); diff --git a/gen/funcgenstate.h b/gen/funcgenstate.h index 34f8244cd7..2983a56831 100644 --- a/gen/funcgenstate.h +++ b/gen/funcgenstate.h @@ -17,6 +17,7 @@ #include "gen/irstate.h" #include "gen/pgo.h" +#include "gen/trycatch.h" #include "llvm/ADT/DenseMap.h" #include "llvm/IR/CallSite.h" #include @@ -163,29 +164,6 @@ public: std::vector cleanupBlocks; }; -/// Stores information to be able to branch to a catch clause if it matches. -/// -/// Each catch body is emitted only once, but may be target from many landing -/// pads (in case of nested catch or cleanup scopes). -struct CatchScope { - /// The ClassInfo reference corresponding to the type to match the - /// exception object against. - llvm::Constant *classInfoPtr = nullptr; - - /// The block to branch to if the exception type matches. - llvm::BasicBlock *bodyBlock = nullptr; - - /// The cleanup scope stack level corresponding to this catch. - CleanupCursor cleanupScope; - - // PGO branch weights for the exception type match branch. - // (first weight is for match, second is for mismatch) - llvm::MDNode *branchWeights = nullptr; - - CatchScope(llvm::Constant *classInfoPtr, llvm::BasicBlock *bodyBlock, - CleanupCursor cleanupScope, llvm::MDNode *branchWeights = nullptr); -}; - /// Keeps track of active (abstract) scopes in a function that influence code /// generation of their contents. This includes cleanups (finally blocks, /// destructors), try/catch blocks and labels for goto/break/continue. @@ -203,8 +181,10 @@ struct CatchScope { /// the rest of the ScopeStack API, as it (in contrast to goto) never requires /// resolving forward references across cleanup scopes. class ScopeStack { + friend class TryCatchScopes; + public: - explicit ScopeStack(IRState &irs) : irs(irs) {} + explicit ScopeStack(IRState &irs) : irs(irs), tryCatchScopes(irs) {} ~ScopeStack(); /// Registers a piece of cleanup code to be run. @@ -246,30 +226,11 @@ public: /// popped. CleanupCursor currentCleanupScope() { return cleanupScopes.size(); } - /// Registers a catch block to be taken into consideration when an exception - /// is thrown within the current scope. - /// - /// When a potentially throwing function call is emitted, a landing pad will - /// be emitted to compare the dynamic type info of the exception against the - /// given ClassInfo constant and to branch to the given body block if it - /// matches. The registered catch blocks are maintained on a stack, with the - /// top-most (i.e. last pushed, innermost) taking precedence. - void pushCatch(llvm::Constant *classInfoPtr, llvm::BasicBlock *bodyBlock, - llvm::MDNode *matchWeights = nullptr); + /// Registers a try-catch scope. + void pushTryCatch(TryCatchStatement *stmt, llvm::BasicBlock *endbb); - /// Unregisters the last registered catch block. - void popCatch(); - - /// Registers a try block and the info whether non-Exceptions (Errors and - /// other Throwables) can be caught. - void pushTryBlock(bool catchingNonExceptions); - - /// Unregisters the last registered try block. - void popTryBlock(); - - /// Indicates whether there are any registered catch blocks that handle - /// non-Exception Throwables. - bool isCatchingNonExceptions() const; + /// Unregisters the last registered try-catch scope. + void popTryCatch(); #if LDC_LLVM_VER >= 308 /// MSVC: catch and cleanup code is emitted as funclets and need @@ -362,13 +323,6 @@ private: llvm::BasicBlock *&getLandingPadRef(CleanupCursor scope); - /// Emits a landing pad to honor all the active cleanups and catches. - llvm::BasicBlock *emitLandingPad(); - -#if LDC_LLVM_VER >= 308 - llvm::BasicBlock *emitLandingPadMSVCEH(CleanupCursor scope); -#endif - /// Unified implementation for labeled break/continue. void jumpToStatement(std::vector &targets, Statement *loopOrSwitchStatement); @@ -396,10 +350,7 @@ private: std::vector cleanupScopes; /// - std::vector catchScopes; - - /// - std::vector catchingNonExceptions; + TryCatchScopes tryCatchScopes; /// Gotos which we were not able to resolve to any cleanup scope, but which /// might still be defined later in the function at top level. If there are @@ -423,10 +374,10 @@ llvm::CallSite ScopeStack::callOrInvoke(llvm::Value *callee, const T &args, // to our advantage. llvm::Function *calleeFn = llvm::dyn_cast(callee); - // Ignore 'nothrow' inside try-blocks with at least 1 catch block handling a - // non-Exception Throwable. - if (isNothrow) - isNothrow = !isCatchingNonExceptions(); + // Ignore 'nothrow' if there are active catch blocks handling non-Exception + // Throwables. + if (isNothrow && tryCatchScopes.isCatchingNonExceptions()) + isNothrow = false; // Intrinsics don't support invoking and 'nounwind' functions don't need it. const bool doesNotThrow = @@ -440,7 +391,7 @@ llvm::CallSite ScopeStack::callOrInvoke(llvm::Value *callee, const T &args, BundleList.push_back(llvm::OperandBundleDef("funclet", funclet)); #endif - if (doesNotThrow || (cleanupScopes.empty() && catchScopes.empty())) { + if (doesNotThrow || (cleanupScopes.empty() && tryCatchScopes.empty())) { llvm::CallInst *call = irs.ir->CreateCall(callee, args, #if LDC_LLVM_VER >= 308 BundleList, diff --git a/gen/statements.cpp b/gen/statements.cpp index e36d8c64fa..07bb2e995a 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -24,7 +24,6 @@ #include "gen/logger.h" #include "gen/runtime.h" #include "gen/tollvm.h" -#include "gen/ms-cxx-helper.h" #include "ir/irfunction.h" #include "ir/irmodule.h" #include "llvm/IR/CFG.h" @@ -795,89 +794,7 @@ public: irs->funcGen().scopes.popCleanups(cleanupBefore); } -////////////////////////////////////////////////////////////////////////// - -#if LDC_LLVM_VER >= 308 - void emitBeginCatchMSVCEH(Catch *ctch, llvm::BasicBlock *endbb, - llvm::CatchSwitchInst *catchSwitchInst) { - VarDeclaration *var = ctch->var; - // The MSVC/x86 build uses C++ exception handling - // This needs a series of catch pads to match the exception - // and the catch handler must be terminated by a catch return instruction - LLValue *exnObj = nullptr; - LLValue *cpyObj = nullptr; - LLValue *typeDesc = nullptr; - LLValue *clssInfo = nullptr; - if (var) { - // alloca storage for the variable, it always needs a place on the stack - // do not initialize, this will be done by the C++ exception handler - var->_init = nullptr; - - // redirect scope to avoid the generation of debug info before the - // catchpad - IRScope save = irs->scope(); - irs->scope() = IRScope(gIR->topallocapoint()->getParent()); - irs->scope().builder.SetInsertPoint(gIR->topallocapoint()); - DtoDeclarationExp(var); - - // catch handler will be outlined, so always treat as a nested reference - exnObj = getIrValue(var); - - if (var->nestedrefs.dim) { - // if variable needed in a closure, use a stack temporary and copy it - // when caught - cpyObj = exnObj; - exnObj = DtoAlloca(var->type, "exnObj"); - } - irs->scope() = save; - irs->DBuilder.EmitStopPoint(ctch->loc); // re-set debug loc after the - // SetInsertPoint(allocaInst) call - } else if (ctch->type) { - // catch without var - exnObj = DtoAlloca(ctch->type, "exnObj"); - } else { - // catch all - exnObj = LLConstant::getNullValue(getVoidPtrType()); - } - - if (ctch->type) { - ClassDeclaration *cd = ctch->type->toBasetype()->isClassHandle(); - typeDesc = getTypeDescriptor(*irs, cd); - clssInfo = getIrAggr(cd)->getClassInfoSymbol(); - } else { - // catch all - typeDesc = LLConstant::getNullValue(getVoidPtrType()); - clssInfo = LLConstant::getNullValue(DtoType(Type::typeinfoclass->type)); - } - - // "catchpad within %switch [TypeDescriptor, 0, &caughtObject]" must be - // first instruction - int flags = var ? 0 : 64; // just mimicking clang here - LLValue *args[] = {typeDesc, DtoConstUint(flags), exnObj}; - auto catchpad = irs->ir->CreateCatchPad( - catchSwitchInst, llvm::ArrayRef(args), ""); - catchSwitchInst->addHandler(irs->scopebb()); - - if (cpyObj) { - // assign the caught exception to the location in the closure - auto val = irs->ir->CreateLoad(exnObj); - irs->ir->CreateStore(val, cpyObj); - exnObj = cpyObj; - } - - // Exceptions are never rethrown by D code (but thrown again), so - // we can leave the catch handler right away and continue execution - // outside the catch funclet - llvm::BasicBlock *catchhandler = llvm::BasicBlock::Create( - irs->context(), "catchhandler", irs->topfunc()); - llvm::CatchReturnInst::Create(catchpad, catchhandler, irs->scopebb()); - irs->scope() = IRScope(catchhandler); - auto enterCatchFn = - getRuntimeFunction(Loc(), irs->module, "_d_eh_enter_catch"); - irs->CreateCallOrInvoke(enterCatchFn, DtoBitCast(exnObj, getVoidPtrType()), - clssInfo); - } -#endif + ////////////////////////////////////////////////////////////////////////// void visit(TryCatchStatement *stmt) LLVM_OVERRIDE { IF_LOG Logger::println("TryCatchStatement::toIR(): %s", @@ -885,7 +802,6 @@ public: LOG_SCOPE; auto &PGO = irs->funcGen().pgo; - auto entryCount = PGO.setCurrentStmt(stmt); // Emit dwarf stop point irs->DBuilder.EmitStopPoint(stmt->loc); @@ -899,185 +815,26 @@ public: llvm::BasicBlock *endbb = llvm::BasicBlock::Create( irs->context(), "try.success.or.caught", irs->topfunc()); - assert(stmt->catches); - - struct CatchBlock { - ClassDeclaration *classdecl; - llvm::BasicBlock *BB; - uint64_t catchcount; - }; - - llvm::SmallVector catchBlocks; - catchBlocks.reserve(stmt->catches->dim); - -#if LDC_LLVM_VER >= 308 - if (useMSVCEH()) { - auto &scopes = irs->funcGen().scopes; - auto catchSwitchBlock = llvm::BasicBlock::Create( - irs->context(), "catch.dispatch", irs->topfunc()); - llvm::BasicBlock *unwindto = - scopes.currentCleanupScope() > 0 ? scopes.getLandingPad() : nullptr; - auto funclet = scopes.getFunclet(); - auto catchSwitchInst = llvm::CatchSwitchInst::Create( - funclet ? funclet : llvm::ConstantTokenNone::get(irs->context()), - unwindto, stmt->catches->dim, "", catchSwitchBlock); - - for (auto c : *stmt->catches) { - auto catchBB = llvm::BasicBlock::Create( - irs->context(), llvm::Twine("catch.") + c->type->toChars(), - irs->topfunc(), endbb); - - irs->scope() = IRScope(catchBB); - irs->DBuilder.EmitBlockStart(c->loc); - PGO.emitCounterIncrement(c); - - emitBeginCatchMSVCEH(c, endbb, catchSwitchInst); - - // Emit handler, if there is one. The handler is zero, for instance, - // when building 'catch { debug foo(); }' in non-debug mode. - if (c->handler) { - Statement_toIR(c->handler, irs); - } - - if (!irs->scopereturned()) { - irs->ir->CreateBr(endbb); - } - - irs->DBuilder.EmitBlockEnd(); - } - - // TODO: PGO has not yet been implemented for MSVC EH, set catchCount - // temporarily to 0 - uint64_t catchCount = 0; - - CatchBlock cb = {nullptr, catchSwitchBlock, catchCount}; - catchBlocks.push_back(cb); // just for cleanup - scopes.pushCatch(nullptr, catchSwitchBlock); - - // if no landing pad is created, the catch blocks are unused, but - // the verifier complains if there are catchpads without personality - // so we can just set it unconditionally - if (!irs->func()->func->hasPersonalityFn()) { - const char *personality = "__CxxFrameHandler3"; - LLFunction *personalityFn = - getRuntimeFunction(Loc(), irs->module, personality); - irs->func()->func->setPersonalityFn(personalityFn); - } - } else -#endif - { - for (auto it = stmt->catches->rbegin(), end = stmt->catches->rend(); - it != end; ++it) { - auto catchBB = llvm::BasicBlock::Create( - irs->context(), llvm::Twine("catch.") + (*it)->type->toChars(), - irs->topfunc(), endbb); - - irs->scope() = IRScope(catchBB); - irs->DBuilder.EmitBlockStart((*it)->loc); - PGO.emitCounterIncrement(*it); - - const auto enterCatchFn = - getRuntimeFunction(Loc(), irs->module, "_d_eh_enter_catch"); - auto ptr = DtoLoad(irs->funcGen().getOrCreateEhPtrSlot()); - auto throwableObj = irs->ir->CreateCall(enterCatchFn, ptr); - - // For catches that use the Throwable object, create storage for it. - // We will set it in the code that branches from the landing pads - // (there might be more than one) to catchBB. - auto var = (*it)->var; - if (var) { - // This will alloca if we haven't already and take care of nested refs - // if there are any. - DtoDeclarationExp(var); - - // Copy the exception reference over from the _d_eh_enter_catch return - // value. - DtoStore(DtoBitCast(throwableObj, DtoType((*it)->var->type)), - getIrLocal(var)->value); - } - - // Emit handler, if there is one. The handler is zero, for instance, - // when building 'catch { debug foo(); }' in non-debug mode. - if ((*it)->handler) { - Statement_toIR((*it)->handler, irs); - } - - if (!irs->scopereturned()) { - irs->ir->CreateBr(endbb); - } - - irs->DBuilder.EmitBlockEnd(); - - // PGO information, currently unused - auto catchCount = PGO.getRegionCount(*it); - - CatchBlock cb = {(*it)->type->toBasetype()->isClassHandle(), catchBB, - catchCount}; - catchBlocks.push_back(cb); - } - - // Total number of uncaught exceptions is equal to the execution count at - // the start of the try block minus the one after the continuation. - // uncaughtCount keeps track of the exception type mismatch count while - // iterating through the catchBlocks list. - auto uncaughtCount = entryCount - PGO.getRegionCount(stmt); - - // Only after emitting all the catch bodies, register the catch scopes. - // This is so that (re)throwing inside a catch does not match later - // catches. - for (const auto &cb : catchBlocks) { - auto matchWeights = - PGO.createProfileWeights(cb.catchcount, uncaughtCount); - // Add this exception type's match count to the uncaughtCount, because - // these failed to match the exception types of the remaining - // iterations. - uncaughtCount += cb.catchcount; - - DtoResolveClass(cb.classdecl); - - irs->funcGen().scopes.pushCatch( - getIrAggr(cb.classdecl)->getClassInfoSymbol(), cb.BB, matchWeights); - } - } + irs->funcGen().scopes.pushTryCatch(stmt, endbb); // Emit the try block. irs->scope() = IRScope(trybb); - const bool isCatchingNonExceptions = - std::any_of(stmt->catches->begin(), stmt->catches->end(), [](Catch *c) { - bool isException = false; - for (auto cd = c->type->toBasetype()->isClassHandle(); cd; - cd = cd->baseClass) { - if (cd == ClassDeclaration::exception) { - isException = true; - break; - } - } - return !isException; - }); - - irs->func()->scopes->pushTryBlock(isCatchingNonExceptions); - assert(stmt->_body); irs->DBuilder.EmitBlockStart(stmt->_body->loc); stmt->_body->accept(this); irs->DBuilder.EmitBlockEnd(); - irs->func()->scopes->popTryBlock(); - if (!irs->scopereturned()) llvm::BranchInst::Create(endbb, irs->scopebb()); - // Now that we have done the try block, remove the catches and continue - // codegen in the end block the try and all the catches branch to. - for (size_t i = 0; i < catchBlocks.size(); ++i) { - irs->funcGen().scopes.popCatch(); - } + irs->funcGen().scopes.popTryCatch(); // Move end block after all generated blocks endbb->moveAfter(&irs->topfunc()->back()); irs->scope() = IRScope(endbb); + // PGO counter tracks the continuation of the try statement PGO.emitCounterIncrement(stmt); } diff --git a/gen/trycatch.cpp b/gen/trycatch.cpp new file mode 100644 index 0000000000..fce42e9459 --- /dev/null +++ b/gen/trycatch.cpp @@ -0,0 +1,438 @@ +//===-- trycatch.cpp --------------------------------------------*- C++ -*-===// +// +// LDC – the LLVM D compiler +// +// This file is distributed under the BSD-style LDC license. See the LICENSE +// file for details. +// +//===----------------------------------------------------------------------===// + +#include "gen/trycatch.h" + +#include "statement.h" +#include "gen/classes.h" +#include "gen/funcgenstate.h" +#include "gen/llvmhelpers.h" +#include "gen/ms-cxx-helper.h" +#include "gen/runtime.h" +#include "gen/tollvm.h" +#include "ir/irfunction.h" + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +#if LDC_LLVM_VER >= 308 +void emitBeginCatchMSVC(IRState &irs, Catch *ctch, llvm::BasicBlock *endbb, + llvm::CatchSwitchInst *catchSwitchInst) { + VarDeclaration *var = ctch->var; + // The MSVC/x86 build uses C++ exception handling + // This needs a series of catch pads to match the exception + // and the catch handler must be terminated by a catch return instruction + LLValue *exnObj = nullptr; + LLValue *cpyObj = nullptr; + LLValue *typeDesc = nullptr; + LLValue *clssInfo = nullptr; + if (var) { + // alloca storage for the variable, it always needs a place on the stack + // do not initialize, this will be done by the C++ exception handler + var->_init = nullptr; + + // redirect scope to avoid the generation of debug info before the + // catchpad + IRScope save = irs.scope(); + irs.scope() = IRScope(gIR->topallocapoint()->getParent()); + irs.scope().builder.SetInsertPoint(gIR->topallocapoint()); + DtoDeclarationExp(var); + + // catch handler will be outlined, so always treat as a nested reference + exnObj = getIrValue(var); + + if (var->nestedrefs.dim) { + // if variable needed in a closure, use a stack temporary and copy it + // when caught + cpyObj = exnObj; + exnObj = DtoAlloca(var->type, "exnObj"); + } + irs.scope() = save; + irs.DBuilder.EmitStopPoint(ctch->loc); // re-set debug loc after the + // SetInsertPoint(allocaInst) call + } else if (ctch->type) { + // catch without var + exnObj = DtoAlloca(ctch->type, "exnObj"); + } else { + // catch all + exnObj = LLConstant::getNullValue(getVoidPtrType()); + } + + if (ctch->type) { + ClassDeclaration *cd = ctch->type->toBasetype()->isClassHandle(); + typeDesc = getTypeDescriptor(irs, cd); + clssInfo = getIrAggr(cd)->getClassInfoSymbol(); + } else { + // catch all + typeDesc = LLConstant::getNullValue(getVoidPtrType()); + clssInfo = LLConstant::getNullValue(DtoType(Type::typeinfoclass->type)); + } + + // "catchpad within %switch [TypeDescriptor, 0, &caughtObject]" must be + // first instruction + int flags = var ? 0 : 64; // just mimicking clang here + LLValue *args[] = {typeDesc, DtoConstUint(flags), exnObj}; + auto catchpad = irs.ir->CreateCatchPad(catchSwitchInst, + llvm::ArrayRef(args), ""); + catchSwitchInst->addHandler(irs.scopebb()); + + if (cpyObj) { + // assign the caught exception to the location in the closure + auto val = irs.ir->CreateLoad(exnObj); + irs.ir->CreateStore(val, cpyObj); + exnObj = cpyObj; + } + + // Exceptions are never rethrown by D code (but thrown again), so + // we can leave the catch handler right away and continue execution + // outside the catch funclet + llvm::BasicBlock *catchhandler = + llvm::BasicBlock::Create(irs.context(), "catchhandler", irs.topfunc()); + llvm::CatchReturnInst::Create(catchpad, catchhandler, irs.scopebb()); + irs.scope() = IRScope(catchhandler); + auto enterCatchFn = + getRuntimeFunction(Loc(), irs.module, "_d_eh_enter_catch"); + irs.CreateCallOrInvoke(enterCatchFn, DtoBitCast(exnObj, getVoidPtrType()), + clssInfo); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// + +llvm::LandingPadInst *createLandingPadInst(IRState &irs) { + LLType *retType = + LLStructType::get(LLType::getInt8PtrTy(irs.context()), + LLType::getInt32Ty(irs.context()), nullptr); +#if LDC_LLVM_VER >= 307 + LLFunction *currentFunction = irs.func()->func; + if (!currentFunction->hasPersonalityFn()) { + LLFunction *personalityFn = + getRuntimeFunction(Loc(), irs.module, "_d_eh_personality"); + currentFunction->setPersonalityFn(personalityFn); + } + return irs.ir->CreateLandingPad(retType, 0); +#else + LLFunction *personalityFn = + getRuntimeFunction(Loc(), irs.module, "_d_eh_personality"); + return irs.ir->CreateLandingPad(retType, personalityFn, 0); +#endif +} +} + +//////////////////////////////////////////////////////////////////////////////// + +TryCatchScope::TryCatchScope(TryCatchStatement *stmt, llvm::BasicBlock *endbb, + size_t cleanupScope) + : stmt(stmt), endbb(endbb), cleanupScope(cleanupScope) { + assert(stmt->catches); + catchesNonExceptions = + std::any_of(stmt->catches->begin(), stmt->catches->end(), [](Catch *c) { + for (auto cd = c->type->toBasetype()->isClassHandle(); cd; + cd = cd->baseClass) { + if (cd == ClassDeclaration::exception) + return false; + } + return true; + }); +} + +const std::vector & +TryCatchScope::getCatchBlocks() const { + assert(!catchBlocks.empty()); + return catchBlocks; +} + +void TryCatchScope::emitCatchBodies(IRState &irs) { + assert(catchBlocks.empty()); + +#if LDC_LLVM_VER >= 308 + if (useMSVCEH()) { + emitCatchBodiesMSVC(irs); + return; + } +#endif + + auto &PGO = irs.funcGen().pgo; + const auto entryCount = PGO.setCurrentStmt(stmt); + + struct CBPrototype { + ClassDeclaration *cd; + llvm::BasicBlock *catchBB; + uint64_t catchCount; + uint64_t uncaughtCount; + }; + llvm::SmallVector cbPrototypes; + cbPrototypes.reserve(stmt->catches->dim); + + for (auto c : *stmt->catches) { + auto catchBB = llvm::BasicBlock::Create( + irs.context(), llvm::Twine("catch.") + c->type->toChars(), + irs.topfunc(), endbb); + + irs.scope() = IRScope(catchBB); + irs.DBuilder.EmitBlockStart(c->loc); + PGO.emitCounterIncrement(c); + + const auto enterCatchFn = + getRuntimeFunction(Loc(), irs.module, "_d_eh_enter_catch"); + auto ptr = DtoLoad(irs.funcGen().getOrCreateEhPtrSlot()); + auto throwableObj = irs.ir->CreateCall(enterCatchFn, ptr); + + // For catches that use the Throwable object, create storage for it. + // We will set it in the code that branches from the landing pads + // (there might be more than one) to catchBB. + if (c->var) { + // This will alloca if we haven't already and take care of nested refs + // if there are any. + DtoDeclarationExp(c->var); + + // Copy the exception reference over from the _d_eh_enter_catch return + // value. + DtoStore(DtoBitCast(throwableObj, DtoType(c->var->type)), + getIrLocal(c->var)->value); + } + + // Emit handler, if there is one. The handler is zero, for instance, + // when building 'catch { debug foo(); }' in non-debug mode. + if (c->handler) + Statement_toIR(c->handler, &irs); + + if (!irs.scopereturned()) + irs.ir->CreateBr(endbb); + + irs.DBuilder.EmitBlockEnd(); + + // PGO information, currently unused + auto catchCount = PGO.getRegionCount(c); + // uncaughtCount is handled in a separate pass below + + auto cd = c->type->toBasetype()->isClassHandle(); + cbPrototypes.push_back({cd, catchBB, catchCount, 0}); + } + + // Total number of uncaught exceptions is equal to the execution count at + // the start of the try block minus the one after the continuation. + // uncaughtCount keeps track of the exception type mismatch count while + // iterating through the catch block prototypes in reversed order. + auto uncaughtCount = entryCount - PGO.getRegionCount(stmt); + for (auto it = cbPrototypes.rbegin(), end = cbPrototypes.rend(); it != end; + ++it) { + it->uncaughtCount = uncaughtCount; + // Add this catch block's match count to the uncaughtCount, because these + // failed to match the remaining (lexically preceding) catch blocks. + uncaughtCount += it->catchCount; + } + + catchBlocks.reserve(stmt->catches->dim); + + for (const auto &p : cbPrototypes) { + auto branchWeights = + PGO.createProfileWeights(p.catchCount, p.uncaughtCount); + DtoResolveClass(p.cd); + auto ci = getIrAggr(p.cd)->getClassInfoSymbol(); + catchBlocks.push_back({ci, p.catchBB, branchWeights}); + } +} + +#if LDC_LLVM_VER >= 308 +void TryCatchScope::emitCatchBodiesMSVC(IRState &irs) { + auto &PGO = irs.funcGen().pgo; + auto &scopes = irs.funcGen().scopes; + + auto catchSwitchBlock = + llvm::BasicBlock::Create(irs.context(), "catch.dispatch", irs.topfunc()); + llvm::BasicBlock *unwindto = + scopes.currentCleanupScope() > 0 ? scopes.getLandingPad() : nullptr; + auto funclet = scopes.getFunclet(); + auto catchSwitchInst = llvm::CatchSwitchInst::Create( + funclet ? funclet : llvm::ConstantTokenNone::get(irs.context()), unwindto, + stmt->catches->dim, "", catchSwitchBlock); + + for (auto c : *stmt->catches) { + auto catchBB = llvm::BasicBlock::Create( + irs.context(), llvm::Twine("catch.") + c->type->toChars(), + irs.topfunc(), endbb); + + irs.scope() = IRScope(catchBB); + irs.DBuilder.EmitBlockStart(c->loc); + PGO.emitCounterIncrement(c); + + emitBeginCatchMSVC(irs, c, endbb, catchSwitchInst); + + // Emit handler, if there is one. The handler is zero, for instance, + // when building 'catch { debug foo(); }' in non-debug mode. + if (c->handler) + Statement_toIR(c->handler, &irs); + + if (!irs.scopereturned()) + irs.ir->CreateBr(endbb); + + irs.DBuilder.EmitBlockEnd(); + } + + scopes.pushCleanup(catchSwitchBlock, catchSwitchBlock); + + // if no landing pad is created, the catch blocks are unused, but + // the verifier complains if there are catchpads without personality + // so we can just set it unconditionally + if (!irs.func()->func->hasPersonalityFn()) { + const char *personality = "__CxxFrameHandler3"; + LLFunction *personalityFn = + getRuntimeFunction(Loc(), irs.module, personality); + irs.func()->func->setPersonalityFn(personalityFn); + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////// + +void TryCatchScopes::push(TryCatchStatement *stmt, llvm::BasicBlock *endbb) { + TryCatchScope scope(stmt, endbb, irs.funcGen().scopes.currentCleanupScope()); + // Only after emitting all the catch bodies, register the catch scopes. + // This is so that (re)throwing inside a catch does not match later + // catches. + scope.emitCatchBodies(irs); + tryCatchScopes.push_back(scope); +} + +void TryCatchScopes::pop() { + tryCatchScopes.pop_back(); + if (useMSVCEH()) { + auto &scopes = irs.funcGen().scopes; + assert(isCatchSwitchBlock(scopes.cleanupScopes.back().beginBlock)); + scopes.popCleanups(scopes.currentCleanupScope() - 1); + } +} + +bool TryCatchScopes::isCatchingNonExceptions() const { + return std::any_of( + tryCatchScopes.begin(), tryCatchScopes.end(), + [](const TryCatchScope &tc) { return tc.isCatchingNonExceptions(); }); +} + +llvm::BasicBlock *TryCatchScopes::emitLandingPad() { + auto &scopes = irs.funcGen().scopes; + +#if LDC_LLVM_VER >= 308 + if (useMSVCEH()) { + auto currentCleanupScope = scopes.currentCleanupScope(); + assert(currentCleanupScope > 0); + return emitLandingPadMSVC(currentCleanupScope - 1); + } +#endif + + // save and rewrite scope + IRScope savedIRScope = irs.scope(); + + llvm::BasicBlock *beginBB = + llvm::BasicBlock::Create(irs.context(), "landingPad", irs.topfunc()); + irs.scope() = IRScope(beginBB); + + llvm::LandingPadInst *landingPad = createLandingPadInst(irs); + + // Stash away the exception object pointer and selector value into their + // stack slots. + llvm::Value *ehPtr = DtoExtractValue(landingPad, 0); + irs.ir->CreateStore(ehPtr, irs.funcGen().getOrCreateEhPtrSlot()); + + llvm::Value *ehSelector = DtoExtractValue(landingPad, 1); + if (!irs.funcGen().ehSelectorSlot) { + irs.funcGen().ehSelectorSlot = + DtoRawAlloca(ehSelector->getType(), 0, "eh.selector"); + } + irs.ir->CreateStore(ehSelector, irs.funcGen().ehSelectorSlot); + + // Add landingpad clauses, emit finallys and 'if' chain to catch the + // exception. + size_t lastCleanup = scopes.currentCleanupScope(); + for (auto it = tryCatchScopes.rbegin(), end = tryCatchScopes.rend(); + it != end; ++it) { + const auto &tryCatchScope = *it; + + // Insert any cleanups in between the previous (inner-more) try-catch scope + // and this one. + const auto newCleanup = tryCatchScope.getCleanupScope(); + assert(lastCleanup >= newCleanup); + if (lastCleanup > newCleanup) { + landingPad->setCleanup(true); + llvm::BasicBlock *afterCleanupBB = llvm::BasicBlock::Create( + irs.context(), beginBB->getName() + llvm::Twine(".after.cleanup"), + irs.topfunc()); + scopes.runCleanups(lastCleanup, newCleanup, afterCleanupBB); + irs.scope() = IRScope(afterCleanupBB); + lastCleanup = newCleanup; + } + + for (const auto &cb : tryCatchScope.getCatchBlocks()) { + // Add the ClassInfo reference to the landingpad instruction so it is + // emitted to the EH tables. + landingPad->addClause(cb.classInfoPtr); + + llvm::BasicBlock *mismatchBB = llvm::BasicBlock::Create( + irs.context(), beginBB->getName() + llvm::Twine(".mismatch"), + irs.topfunc()); + + // "Call" llvm.eh.typeid.for, which gives us the eh selector value to + // compare the landing pad selector value with. + llvm::Value *ehTypeId = + irs.ir->CreateCall(GET_INTRINSIC_DECL(eh_typeid_for), + DtoBitCast(cb.classInfoPtr, getVoidPtrType())); + + // Compare the selector value from the unwinder against the expected + // one and branch accordingly. + irs.ir->CreateCondBr( + irs.ir->CreateICmpEQ(irs.ir->CreateLoad(irs.funcGen().ehSelectorSlot), + ehTypeId), + cb.bodyBB, mismatchBB, cb.branchWeights); + irs.scope() = IRScope(mismatchBB); + } + } + + // No catch matched. Execute all finallys and resume unwinding. + if (lastCleanup > 0) { + landingPad->setCleanup(true); + scopes.runCleanups(lastCleanup, 0, + irs.funcGen().getOrCreateResumeUnwindBlock()); + } else if (!tryCatchScopes.empty()) { + // Directly convert the last mismatch branch into a branch to the + // unwind resume block. + irs.scopebb()->replaceAllUsesWith( + irs.funcGen().getOrCreateResumeUnwindBlock()); + irs.scopebb()->eraseFromParent(); + } else { + irs.ir->CreateBr(irs.funcGen().getOrCreateResumeUnwindBlock()); + } + + irs.scope() = savedIRScope; + return beginBB; +} + +#if LDC_LLVM_VER >= 308 +llvm::BasicBlock *TryCatchScopes::emitLandingPadMSVC(size_t cleanupScope) { + auto &scopes = irs.funcGen().scopes; + + LLFunction *currentFunction = irs.func()->func; + if (!currentFunction->hasPersonalityFn()) { + const char *personality = "__CxxFrameHandler3"; + LLFunction *personalityFn = + getRuntimeFunction(Loc(), irs.module, personality); + currentFunction->setPersonalityFn(personalityFn); + } + + if (cleanupScope == 0) + return scopes.runCleanupPad(cleanupScope, nullptr); + + llvm::BasicBlock *&pad = scopes.getLandingPadRef(cleanupScope - 1); + if (!pad) + pad = emitLandingPadMSVC(cleanupScope - 1); + + return scopes.runCleanupPad(cleanupScope, pad); +} +#endif diff --git a/gen/trycatch.h b/gen/trycatch.h new file mode 100644 index 0000000000..79111c27e0 --- /dev/null +++ b/gen/trycatch.h @@ -0,0 +1,88 @@ +//===-- gen/trycatch.h - Try-catch scopes -----------------------*- C++ -*-===// +// +// LDC – the LLVM D compiler +// +// This file is distributed under the BSD-style LDC license. See the LICENSE +// file for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LDC_GEN_TRYCATCH_H +#define LDC_GEN_TRYCATCH_H + +#include +#include + +struct IRState; +class TryCatchStatement; + +namespace llvm { +class BasicBlock; +class GlobalVariable; +class MDNode; +} + +//////////////////////////////////////////////////////////////////////////////// + +class TryCatchScope { +public: + /// Stores information to be able to branch to a catch clause if it matches. + /// + /// Each catch body is emitted only once, but may be target from many landing + /// pads (in case of nested catch or cleanup scopes). + struct CatchBlock { + /// The ClassInfo reference corresponding to the type to match the + /// exception object against. + llvm::GlobalVariable *classInfoPtr; + /// The block to branch to if the exception type matches. + llvm::BasicBlock *bodyBB; + // PGO branch weights for the exception type match branch. + // (first weight is for match, second is for mismatch) + llvm::MDNode *branchWeights; + }; + + TryCatchScope(TryCatchStatement *stmt, llvm::BasicBlock *endbb, + size_t cleanupScope); + + size_t getCleanupScope() const { return cleanupScope; } + bool isCatchingNonExceptions() const { return catchesNonExceptions; } + + void emitCatchBodies(IRState &irs); + const std::vector &getCatchBlocks() const; + +private: + TryCatchStatement *stmt; + llvm::BasicBlock *endbb; + size_t cleanupScope; + bool catchesNonExceptions; + + std::vector catchBlocks; + + void emitCatchBodiesMSVC(IRState &irs); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TryCatchScopes { +public: + TryCatchScopes(IRState &irs) : irs(irs) {} + + void push(TryCatchStatement *stmt, llvm::BasicBlock *endbb); + void pop(); + bool empty() const { return tryCatchScopes.empty(); } + + /// Indicates whether there are any active catch blocks that handle + /// non-Exception Throwables. + bool isCatchingNonExceptions() const; + + /// Emits a landing pad to honor all the active cleanups and catches. + llvm::BasicBlock *emitLandingPad(); + +private: + IRState &irs; + std::vector tryCatchScopes; + + llvm::BasicBlock *emitLandingPadMSVC(size_t cleanupScope); +}; + +#endif diff --git a/tests/PGO/exceptions.d b/tests/PGO/exceptions.d index 3f8ce8e6e9..2a9490b81f 100644 --- a/tests/PGO/exceptions.d +++ b/tests/PGO/exceptions.d @@ -78,13 +78,14 @@ void try_catch() { if (i) {} // 1 : 1 (branch taken) } - // Exception handlers (only the first BBs): - // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 4 + // ExceptionTwo 1st BB: // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 3 - // ExceptionThree 2nd BB: - // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 8 - // ExceptionTwo 2nd BB: + // ExceptionThree 1st BB: + // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 4 + // ExceptionTwo 2nd BB: if(i) // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 7 + // ExceptionThree 2nd BB: if(i) + // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 8 // Try body 2nd BB: if(i < 2) // PROFGEN: store {{.*}} @[[TC]], i64 0, i64 5 // Landingpad stuff: @@ -97,8 +98,8 @@ void try_catch() { // Try body: if(i < 2) // PROFUSE: br {{.*}} !prof ![[TC5:[0-9]+]] // Exception handlers: if(i){} - // PROFUSE: br {{.*}} !prof ![[TC8:[0-9]+]] // PROFUSE: br {{.*}} !prof ![[TC7:[0-9]+]] + // PROFUSE: br {{.*}} !prof ![[TC8:[0-9]+]] // More try body: if(i < 5) // PROFUSE: br {{.*}} !prof ![[TC6:[0-9]+]] // Landingpad stuff: