ldc/gen/trycatch.cpp

428 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===-- 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<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);
}
#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::CatchBlock> &
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<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(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 = 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, 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 = irs.insertBB("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, 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 =
irs.insertBB(beginBB->getName() + llvm::Twine(".after.cleanup"));
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 =
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(irs.funcGen().ehSelectorSlot),
ehTypeId),
cb.bodyBB, mismatchBB, cb.branchWeights);
irs.scope() = IRScope(mismatchBB);
}
}
// No catch matched. Execute all finallys and resume unwinding.
auto resumeUnwindBlock = irs.funcGen().getOrCreateResumeUnwindBlock();
if (lastCleanup > 0) {
landingPad->setCleanup(true);
scopes.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;
}
#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