mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-01 07:30:43 +03:00
831 lines
29 KiB
C++
831 lines
29 KiB
C++
//===-- trycatchfinally.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/trycatchfinally.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"
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
TryCatchScope::TryCatchScope(IRState &irs, llvm::Value *ehPtrSlot,
|
||
TryCatchStatement *stmt, llvm::BasicBlock *endbb)
|
||
: stmt(stmt), endbb(endbb) {
|
||
assert(stmt->catches);
|
||
|
||
cleanupScope = irs.funcGen().scopes.currentCleanupScope();
|
||
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;
|
||
});
|
||
|
||
#if LDC_LLVM_VER >= 308
|
||
if (useMSVCEH()) {
|
||
emitCatchBodiesMSVC(irs, ehPtrSlot);
|
||
return;
|
||
}
|
||
#endif
|
||
emitCatchBodies(irs, ehPtrSlot);
|
||
}
|
||
|
||
const std::vector<TryCatchScope::CatchBlock> &
|
||
TryCatchScope::getCatchBlocks() const {
|
||
assert(!catchBlocks.empty());
|
||
return catchBlocks;
|
||
}
|
||
|
||
void TryCatchScope::emitCatchBodies(IRState &irs, llvm::Value *ehPtrSlot) {
|
||
assert(catchBlocks.empty());
|
||
|
||
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<CBPrototype, 8> cbPrototypes;
|
||
cbPrototypes.reserve(stmt->catches->dim);
|
||
|
||
for (auto c : *stmt->catches) {
|
||
auto catchBB =
|
||
irs.insertBBBefore(endbb, llvm::Twine("catch.") + c->type->toChars());
|
||
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(ehPtrSlot);
|
||
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
|
||
|
||
namespace {
|
||
void emitBeginCatchMSVC(IRState &irs, Catch *ctch,
|
||
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<LLValue *>(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 = irs.insertBB("catchhandler");
|
||
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);
|
||
}
|
||
}
|
||
|
||
void TryCatchScope::emitCatchBodiesMSVC(IRState &irs, llvm::Value *) {
|
||
assert(catchBlocks.empty());
|
||
|
||
auto &scopes = irs.funcGen().scopes;
|
||
auto &PGO = irs.funcGen().pgo;
|
||
|
||
auto catchSwitchBlock = irs.insertBBBefore(endbb, "catch.dispatch");
|
||
llvm::BasicBlock *unwindto =
|
||
scopes.currentCleanupScope() > 0 ? scopes.getLandingPad() : nullptr;
|
||
auto catchSwitchInst = llvm::CatchSwitchInst::Create(
|
||
llvm::ConstantTokenNone::get(irs.context()), unwindto, stmt->catches->dim,
|
||
"", catchSwitchBlock);
|
||
|
||
for (auto c : *stmt->catches) {
|
||
auto catchBB =
|
||
irs.insertBBBefore(endbb, llvm::Twine("catch.") + c->type->toChars());
|
||
|
||
irs.scope() = IRScope(catchBB);
|
||
irs.DBuilder.EmitBlockStart(c->loc);
|
||
PGO.emitCounterIncrement(c);
|
||
|
||
emitBeginCatchMSVC(irs, c, 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()->hasLLVMPersonalityFn()) {
|
||
const char *personality = "__CxxFrameHandler3";
|
||
irs.func()->setLLVMPersonalityFn(
|
||
getRuntimeFunction(Loc(), irs.module, personality));
|
||
}
|
||
}
|
||
|
||
#endif // LDC_LLVM_VER >= 308
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
CleanupScope::CleanupScope(llvm::BasicBlock *beginBlock,
|
||
llvm::BasicBlock *endBlock) {
|
||
#if LDC_LLVM_VER >= 308
|
||
if (useMSVCEH()) {
|
||
findSuccessors(blocks, beginBlock, endBlock);
|
||
return;
|
||
}
|
||
#endif
|
||
blocks.push_back(beginBlock);
|
||
if (endBlock != beginBlock)
|
||
blocks.push_back(endBlock);
|
||
}
|
||
|
||
llvm::BasicBlock *CleanupScope::run(IRState &irs, llvm::BasicBlock *sourceBlock,
|
||
llvm::BasicBlock *continueWith) {
|
||
#if LDC_LLVM_VER >= 308
|
||
if (useMSVCEH())
|
||
return runCopying(irs, sourceBlock, continueWith);
|
||
#endif
|
||
|
||
if (exitTargets.empty() || (exitTargets.size() == 1 &&
|
||
exitTargets[0].branchTarget == continueWith)) {
|
||
// We didn't need a branch selector before and still don't need one.
|
||
assert(!branchSelector);
|
||
|
||
// Set up the unconditional branch at the end of the cleanup if we have
|
||
// not done so already.
|
||
if (exitTargets.empty()) {
|
||
exitTargets.emplace_back(continueWith);
|
||
llvm::BranchInst::Create(continueWith, endBlock());
|
||
}
|
||
exitTargets.front().sourceBlocks.push_back(sourceBlock);
|
||
return beginBlock();
|
||
}
|
||
|
||
// We need a branch selector if we are here...
|
||
if (!branchSelector) {
|
||
// ... and have not created one yet, so do so now.
|
||
branchSelector = new llvm::AllocaInst(llvm::Type::getInt32Ty(irs.context()),
|
||
#if LDC_LLVM_VER >= 500
|
||
irs.module.getDataLayout().getAllocaAddrSpace(),
|
||
#endif
|
||
llvm::Twine("branchsel.") +
|
||
beginBlock()->getName(),
|
||
irs.topallocapoint());
|
||
|
||
// Now we also need to store 0 to it to keep the paths that go to the
|
||
// only existing branch target the same.
|
||
for (auto bb : exitTargets.front().sourceBlocks) {
|
||
new llvm::StoreInst(DtoConstUint(0), branchSelector, bb->getTerminator());
|
||
}
|
||
|
||
// And convert the BranchInst to the existing branch target to a
|
||
// SelectInst so we can append the other cases to it.
|
||
endBlock()->getTerminator()->eraseFromParent();
|
||
llvm::Value *sel = new llvm::LoadInst(branchSelector, "", endBlock());
|
||
llvm::SwitchInst::Create(
|
||
sel, exitTargets[0].branchTarget,
|
||
1, // Expected number of branches, only for pre-allocating.
|
||
endBlock());
|
||
}
|
||
|
||
// If we already know this branch target, figure out the branch selector
|
||
// value and simply insert the store into the source block (prior to the
|
||
// last instruction, which is the branch to the first cleanup).
|
||
for (unsigned i = 0; i < exitTargets.size(); ++i) {
|
||
CleanupExitTarget &t = exitTargets[i];
|
||
if (t.branchTarget == continueWith) {
|
||
new llvm::StoreInst(DtoConstUint(i), branchSelector,
|
||
sourceBlock->getTerminator());
|
||
|
||
// Note: Strictly speaking, keeping this up to date would not be
|
||
// needed right now, because we never to any optimizations that
|
||
// require changes to the source blocks after the initial conversion
|
||
// from one to two branch targets. Keeping this around for now to
|
||
// ease future development, but may be removed to save some work.
|
||
t.sourceBlocks.push_back(sourceBlock);
|
||
|
||
return beginBlock();
|
||
}
|
||
}
|
||
|
||
// We don't know this branch target yet, so add it to the SwitchInst...
|
||
llvm::ConstantInt *const selectorVal = DtoConstUint(exitTargets.size());
|
||
llvm::cast<llvm::SwitchInst>(endBlock()->getTerminator())
|
||
->addCase(selectorVal, continueWith);
|
||
|
||
// ... insert the store into the source block...
|
||
new llvm::StoreInst(selectorVal, branchSelector,
|
||
sourceBlock->getTerminator());
|
||
|
||
// ... and keep track of it (again, this is unnecessary right now as
|
||
// discussed in the above note).
|
||
exitTargets.emplace_back(continueWith);
|
||
exitTargets.back().sourceBlocks.push_back(sourceBlock);
|
||
|
||
return beginBlock();
|
||
}
|
||
|
||
#if LDC_LLVM_VER >= 308
|
||
llvm::BasicBlock *CleanupScope::runCopying(IRState &irs,
|
||
llvm::BasicBlock *sourceBlock,
|
||
llvm::BasicBlock *continueWith,
|
||
llvm::BasicBlock *unwindTo,
|
||
llvm::Value *funclet) {
|
||
if (isCatchSwitchBlock(beginBlock()))
|
||
return continueWith;
|
||
if (exitTargets.empty()) {
|
||
if (!endBlock()->getTerminator())
|
||
// Set up the unconditional branch at the end of the cleanup
|
||
llvm::BranchInst::Create(continueWith, endBlock());
|
||
} else {
|
||
// check whether we have an exit target with the same continuation
|
||
for (CleanupExitTarget &tgt : exitTargets)
|
||
if (tgt.branchTarget == continueWith) {
|
||
tgt.sourceBlocks.push_back(sourceBlock);
|
||
return tgt.cleanupBlocks.front();
|
||
}
|
||
}
|
||
|
||
// reuse the original IR if not unwinding and not already used
|
||
bool useOriginal = unwindTo == nullptr && funclet == nullptr;
|
||
for (CleanupExitTarget &tgt : exitTargets) {
|
||
if (tgt.cleanupBlocks.front() == beginBlock()) {
|
||
useOriginal = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// append new target
|
||
exitTargets.emplace_back(continueWith);
|
||
auto &exitTarget = exitTargets.back();
|
||
exitTarget.sourceBlocks.push_back(sourceBlock);
|
||
|
||
if (useOriginal) {
|
||
// change the continuation target if the initial branch was created
|
||
// by another instance with unwinding
|
||
if (continueWith)
|
||
if (auto term = endBlock()->getTerminator())
|
||
if (auto succ = term->getSuccessor(0))
|
||
if (succ != continueWith)
|
||
remapBlocksValue(blocks, succ, continueWith);
|
||
exitTarget.cleanupBlocks = blocks;
|
||
} else {
|
||
// clone the code
|
||
cloneBlocks(blocks, exitTarget.cleanupBlocks, continueWith, unwindTo,
|
||
funclet);
|
||
}
|
||
return exitTarget.cleanupBlocks.front();
|
||
}
|
||
#endif
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
TryCatchFinallyScopes::TryCatchFinallyScopes(IRState &irs) : irs(irs) {
|
||
// create top-level stacks
|
||
unresolvedGotosPerCleanupScope.emplace_back();
|
||
landingPadsPerCleanupScope.emplace_back();
|
||
}
|
||
|
||
TryCatchFinallyScopes::~TryCatchFinallyScopes() {
|
||
assert(currentCleanupScope() == 0);
|
||
// If there are still unresolved gotos left, it means that they were either
|
||
// down or "sideways" (i.e. down another branch) of the tree of all
|
||
// cleanup scopes, both of which are not allowed in D.
|
||
if (!currentUnresolvedGotos().empty()) {
|
||
for (const auto &i : currentUnresolvedGotos()) {
|
||
error(i.sourceLoc, "`goto` into `try`/`finally` scope is not allowed");
|
||
}
|
||
fatal();
|
||
}
|
||
}
|
||
|
||
void TryCatchFinallyScopes::pushTryCatch(TryCatchStatement *stmt,
|
||
llvm::BasicBlock *endbb) {
|
||
TryCatchScope scope(irs, getOrCreateEhPtrSlot(), stmt, endbb);
|
||
// 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.
|
||
tryCatchScopes.push_back(scope);
|
||
|
||
if (!useMSVCEH())
|
||
landingPadsPerCleanupScope[currentCleanupScope()].push_back(nullptr);
|
||
}
|
||
|
||
void TryCatchFinallyScopes::popTryCatch() {
|
||
tryCatchScopes.pop_back();
|
||
if (useMSVCEH()) {
|
||
#if LDC_LLVM_VER >= 308
|
||
assert(isCatchSwitchBlock(cleanupScopes.back().beginBlock()));
|
||
#endif
|
||
popCleanups(currentCleanupScope() - 1);
|
||
} else {
|
||
landingPadsPerCleanupScope[currentCleanupScope()].pop_back();
|
||
}
|
||
}
|
||
|
||
bool TryCatchFinallyScopes::isCatchingNonExceptions() const {
|
||
return std::any_of(
|
||
tryCatchScopes.begin(), tryCatchScopes.end(),
|
||
[](const TryCatchScope &tc) { return tc.isCatchingNonExceptions(); });
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
void TryCatchFinallyScopes::pushCleanup(llvm::BasicBlock *beginBlock,
|
||
llvm::BasicBlock *endBlock) {
|
||
cleanupScopes.emplace_back(beginBlock, endBlock);
|
||
unresolvedGotosPerCleanupScope.emplace_back();
|
||
landingPadsPerCleanupScope.emplace_back();
|
||
}
|
||
|
||
void TryCatchFinallyScopes::popCleanups(CleanupCursor targetScope) {
|
||
assert(targetScope <= currentCleanupScope());
|
||
if (targetScope == currentCleanupScope())
|
||
return;
|
||
|
||
for (CleanupCursor i = currentCleanupScope(); i-- > targetScope;) {
|
||
// Any gotos that are still unresolved necessarily leave this scope.
|
||
// Thus, the cleanup needs to be executed.
|
||
for (const auto &gotoJump : currentUnresolvedGotos()) {
|
||
// Replace all branches to the tentative target by branches to the cleanup
|
||
// and continue with the tentative target (we simply reuse it because
|
||
// there is no reason not to).
|
||
llvm::BasicBlock *tentative = gotoJump.tentativeTarget;
|
||
// 1) Replace all branches to the tentative target by branches to a
|
||
// temporary placeholder BB.
|
||
llvm::BasicBlock *dummy = irs.insertBB("");
|
||
tentative->replaceAllUsesWith(dummy);
|
||
// 2) We need a cleanup instance which continues execution with the
|
||
// tentative target.
|
||
auto startCleanup =
|
||
cleanupScopes[i].run(irs, gotoJump.sourceBlock, tentative);
|
||
// 3) Replace all branches to the placeholder BB by branches to the
|
||
// cleanup.
|
||
dummy->replaceAllUsesWith(startCleanup);
|
||
dummy->eraseFromParent();
|
||
}
|
||
|
||
Gotos &nextUnresolved = unresolvedGotosPerCleanupScope[i];
|
||
nextUnresolved.insert(nextUnresolved.end(),
|
||
currentUnresolvedGotos().begin(),
|
||
currentUnresolvedGotos().end());
|
||
|
||
cleanupScopes.pop_back();
|
||
unresolvedGotosPerCleanupScope.pop_back();
|
||
landingPadsPerCleanupScope.pop_back();
|
||
}
|
||
}
|
||
|
||
void TryCatchFinallyScopes::runCleanups(CleanupCursor targetScope,
|
||
llvm::BasicBlock *continueWith) {
|
||
runCleanups(currentCleanupScope(), targetScope, continueWith);
|
||
}
|
||
|
||
void TryCatchFinallyScopes::runCleanups(CleanupCursor sourceScope,
|
||
CleanupCursor targetScope,
|
||
llvm::BasicBlock *continueWith) {
|
||
#if LDC_LLVM_VER >= 308
|
||
if (useMSVCEH()) {
|
||
runCleanupCopies(sourceScope, targetScope, continueWith);
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
assert(targetScope <= sourceScope);
|
||
|
||
if (targetScope == sourceScope) {
|
||
// No cleanups to run, just branch to the next block.
|
||
irs.ir->CreateBr(continueWith);
|
||
return;
|
||
}
|
||
|
||
// Insert the unconditional branch to the first cleanup block.
|
||
irs.ir->CreateBr(cleanupScopes[sourceScope - 1].beginBlock());
|
||
|
||
// Update all the control flow in the cleanups to make sure we end up where
|
||
// we want.
|
||
for (CleanupCursor i = sourceScope; i-- > targetScope;) {
|
||
llvm::BasicBlock *nextBlock =
|
||
(i > targetScope) ? cleanupScopes[i - 1].beginBlock() : continueWith;
|
||
cleanupScopes[i].run(irs, irs.scopebb(), nextBlock);
|
||
}
|
||
}
|
||
|
||
#if LDC_LLVM_VER >= 308
|
||
void TryCatchFinallyScopes::runCleanupCopies(CleanupCursor sourceScope,
|
||
CleanupCursor targetScope,
|
||
llvm::BasicBlock *continueWith) {
|
||
assert(targetScope <= sourceScope);
|
||
|
||
// work through the blocks in reverse execution order, so we
|
||
// can merge cleanups that end up at the same continuation target
|
||
for (CleanupCursor i = targetScope; i < sourceScope; ++i)
|
||
continueWith =
|
||
cleanupScopes[i].runCopying(irs, irs.scopebb(), continueWith);
|
||
|
||
// Insert the unconditional branch to the first cleanup block.
|
||
irs.ir->CreateBr(continueWith);
|
||
}
|
||
#endif
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
std::vector<GotoJump> &TryCatchFinallyScopes::currentUnresolvedGotos() {
|
||
return unresolvedGotosPerCleanupScope[currentCleanupScope()];
|
||
}
|
||
|
||
void TryCatchFinallyScopes::registerUnresolvedGoto(Loc loc,
|
||
Identifier *labelName) {
|
||
llvm::BasicBlock *target = irs.insertBB("goto.unresolved");
|
||
irs.ir->CreateBr(target);
|
||
currentUnresolvedGotos().push_back({loc, irs.scopebb(), target, labelName});
|
||
}
|
||
|
||
void TryCatchFinallyScopes::tryResolveGotos(Identifier *labelName,
|
||
llvm::BasicBlock *targetBlock) {
|
||
auto &unresolved = currentUnresolvedGotos();
|
||
size_t i = 0;
|
||
while (i < unresolved.size()) {
|
||
if (unresolved[i].targetLabel != labelName) {
|
||
++i;
|
||
continue;
|
||
}
|
||
|
||
unresolved[i].tentativeTarget->replaceAllUsesWith(targetBlock);
|
||
unresolved[i].tentativeTarget->eraseFromParent();
|
||
unresolved.erase(unresolved.begin() + i);
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
llvm::BasicBlock *TryCatchFinallyScopes::getLandingPad() {
|
||
llvm::BasicBlock *&landingPad = getLandingPadRef(currentCleanupScope());
|
||
if (!landingPad)
|
||
landingPad = emitLandingPad();
|
||
return landingPad;
|
||
}
|
||
|
||
llvm::BasicBlock *&
|
||
TryCatchFinallyScopes::getLandingPadRef(CleanupCursor scope) {
|
||
auto &pads = landingPadsPerCleanupScope[scope];
|
||
if (pads.empty()) {
|
||
// Have not encountered any catches (for which we would push a scope) or
|
||
// calls to throwing functions (where we would have already executed
|
||
// this if) in this cleanup scope yet.
|
||
pads.push_back(nullptr);
|
||
}
|
||
return pads.back();
|
||
}
|
||
|
||
namespace {
|
||
llvm::LandingPadInst *createLandingPadInst(IRState &irs) {
|
||
LLType *retType =
|
||
LLStructType::get(LLType::getInt8PtrTy(irs.context()),
|
||
LLType::getInt32Ty(irs.context())
|
||
#if LDC_LLVM_VER < 500
|
||
, nullptr
|
||
#endif
|
||
);
|
||
if (!irs.func()->hasLLVMPersonalityFn()) {
|
||
irs.func()->setLLVMPersonalityFn(
|
||
getRuntimeFunction(Loc(), irs.module, "_d_eh_personality"));
|
||
}
|
||
return irs.ir->CreateLandingPad(retType, 0);
|
||
}
|
||
}
|
||
|
||
llvm::BasicBlock *TryCatchFinallyScopes::emitLandingPad() {
|
||
#if LDC_LLVM_VER >= 308
|
||
if (useMSVCEH()) {
|
||
assert(currentCleanupScope() > 0);
|
||
return emitLandingPadMSVC(currentCleanupScope() - 1);
|
||
}
|
||
#endif
|
||
|
||
// save and rewrite scope
|
||
IRScope savedIRScope = irs.scope();
|
||
|
||
// insert landing pads at the end of the function, in emission order,
|
||
// to improve human-readability of the IR
|
||
llvm::BasicBlock *beginBB = irs.insertBBBefore(nullptr, "landingPad");
|
||
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, getOrCreateEhPtrSlot());
|
||
|
||
llvm::Value *ehSelector = DtoExtractValue(landingPad, 1);
|
||
if (!ehSelectorSlot)
|
||
ehSelectorSlot = DtoRawAlloca(ehSelector->getType(), 0, "eh.selector");
|
||
irs.ir->CreateStore(ehSelector, ehSelectorSlot);
|
||
|
||
// Add landingpad clauses, emit finallys and 'if' chain to catch the
|
||
// exception.
|
||
CleanupCursor lastCleanup = 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 =
|
||
irs.insertBB(beginBB->getName() + llvm::Twine(".after.cleanup"));
|
||
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 =
|
||
irs.insertBB(beginBB->getName() + llvm::Twine(".mismatch"));
|
||
|
||
// "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(ehSelectorSlot), ehTypeId),
|
||
cb.bodyBB, mismatchBB, cb.branchWeights);
|
||
irs.scope() = IRScope(mismatchBB);
|
||
}
|
||
}
|
||
|
||
// No catch matched. Execute all finallys and resume unwinding.
|
||
auto resumeUnwindBlock = getOrCreateResumeUnwindBlock();
|
||
if (lastCleanup > 0) {
|
||
landingPad->setCleanup(true);
|
||
runCleanups(lastCleanup, 0, resumeUnwindBlock);
|
||
} else if (!tryCatchScopes.empty()) {
|
||
// Directly convert the last mismatch branch into a branch to the
|
||
// unwind resume block.
|
||
irs.scopebb()->replaceAllUsesWith(resumeUnwindBlock);
|
||
irs.scopebb()->eraseFromParent();
|
||
} else {
|
||
irs.ir->CreateBr(resumeUnwindBlock);
|
||
}
|
||
|
||
irs.scope() = savedIRScope;
|
||
return beginBB;
|
||
}
|
||
|
||
llvm::AllocaInst *TryCatchFinallyScopes::getOrCreateEhPtrSlot() {
|
||
if (!ehPtrSlot)
|
||
ehPtrSlot = DtoRawAlloca(getVoidPtrType(), 0, "eh.ptr");
|
||
return ehPtrSlot;
|
||
}
|
||
|
||
llvm::BasicBlock *TryCatchFinallyScopes::getOrCreateResumeUnwindBlock() {
|
||
if (!resumeUnwindBlock) {
|
||
resumeUnwindBlock = irs.insertBB("eh.resume");
|
||
|
||
llvm::BasicBlock *oldBB = irs.scopebb();
|
||
irs.scope() = IRScope(resumeUnwindBlock);
|
||
|
||
llvm::Function *resumeFn =
|
||
getRuntimeFunction(Loc(), irs.module, "_d_eh_resume_unwind");
|
||
irs.ir->CreateCall(resumeFn, DtoLoad(getOrCreateEhPtrSlot()));
|
||
irs.ir->CreateUnreachable();
|
||
|
||
irs.scope() = IRScope(oldBB);
|
||
}
|
||
return resumeUnwindBlock;
|
||
}
|
||
|
||
#if LDC_LLVM_VER >= 308
|
||
llvm::BasicBlock *
|
||
TryCatchFinallyScopes::emitLandingPadMSVC(CleanupCursor cleanupScope) {
|
||
if (!irs.func()->hasLLVMPersonalityFn()) {
|
||
const char *personality = "__CxxFrameHandler3";
|
||
irs.func()->setLLVMPersonalityFn(
|
||
getRuntimeFunction(Loc(), irs.module, personality));
|
||
}
|
||
|
||
if (cleanupScope == 0)
|
||
return runCleanupPad(cleanupScope, nullptr);
|
||
|
||
llvm::BasicBlock *&pad = getLandingPadRef(cleanupScope);
|
||
if (!pad)
|
||
pad = emitLandingPadMSVC(cleanupScope - 1);
|
||
|
||
return runCleanupPad(cleanupScope, pad);
|
||
}
|
||
|
||
llvm::BasicBlock *
|
||
TryCatchFinallyScopes::runCleanupPad(CleanupCursor scope,
|
||
llvm::BasicBlock *unwindTo) {
|
||
// a catch switch never needs to be cloned and is an unwind target itself
|
||
if (isCatchSwitchBlock(cleanupScopes[scope].beginBlock()))
|
||
return cleanupScopes[scope].beginBlock();
|
||
|
||
// each cleanup block is bracketed by a pair of cleanuppad/cleanupret
|
||
// instructions, any unwinding should also just continue at the next
|
||
// cleanup block, e.g.:
|
||
//
|
||
// cleanuppad:
|
||
// %0 = cleanuppad within %funclet[]
|
||
// %frame = nullptr
|
||
// if (!_d_enter_cleanup(%frame)) br label %cleanupret
|
||
// else br label %copy
|
||
//
|
||
// copy:
|
||
// invoke _dtor to %cleanupret unwind %unwindTo [ "funclet"(token %0) ]
|
||
//
|
||
// cleanupret:
|
||
// _d_leave_cleanup(%frame)
|
||
// cleanupret %0 unwind %unwindTo
|
||
//
|
||
llvm::BasicBlock *cleanupbb = irs.insertBB("cleanuppad");
|
||
auto funcletToken = llvm::ConstantTokenNone::get(irs.context());
|
||
auto cleanuppad =
|
||
llvm::CleanupPadInst::Create(funcletToken, {}, "", cleanupbb);
|
||
|
||
llvm::BasicBlock *cleanupret = irs.insertBBAfter(cleanupbb, "cleanupret");
|
||
|
||
// preparation to allocate some space on the stack where _d_enter_cleanup
|
||
// can place an exception frame (but not done here)
|
||
auto frame = getNullPtr(getVoidPtrType());
|
||
|
||
auto savedInsertBlock = irs.ir->GetInsertBlock();
|
||
auto savedInsertPoint = irs.ir->GetInsertPoint();
|
||
auto savedDbgLoc = irs.DBuilder.GetCurrentLoc();
|
||
|
||
auto endFn = getRuntimeFunction(Loc(), irs.module, "_d_leave_cleanup");
|
||
irs.ir->SetInsertPoint(cleanupret);
|
||
irs.DBuilder.EmitStopPoint(irs.func()->decl->loc);
|
||
irs.ir->CreateCall(endFn, frame,
|
||
{llvm::OperandBundleDef("funclet", cleanuppad)}, "");
|
||
llvm::CleanupReturnInst::Create(cleanuppad, unwindTo, cleanupret);
|
||
|
||
auto copybb = cleanupScopes[scope].runCopying(irs, cleanupbb, cleanupret,
|
||
unwindTo, cleanuppad);
|
||
|
||
auto beginFn = getRuntimeFunction(Loc(), irs.module, "_d_enter_cleanup");
|
||
irs.ir->SetInsertPoint(cleanupbb);
|
||
irs.DBuilder.EmitStopPoint(irs.func()->decl->loc);
|
||
auto exec = irs.ir->CreateCall(
|
||
beginFn, frame, {llvm::OperandBundleDef("funclet", cleanuppad)}, "");
|
||
llvm::BranchInst::Create(copybb, cleanupret, exec, cleanupbb);
|
||
|
||
irs.ir->SetInsertPoint(savedInsertBlock, savedInsertPoint);
|
||
irs.DBuilder.EmitStopPoint(savedDbgLoc);
|
||
|
||
return cleanupbb;
|
||
}
|
||
#endif
|