Skip to content

Commit

Permalink
MSVC EH C++ catch support (both for Calypso and vanilla DMD/LDC's C++…
Browse files Browse the repository at this point in the history
… support), make LDC emit catch bodies within catchpad funclets.

For heap-allocated D exceptions LDC was able to emit minimal catchpads, but C++ exceptions may be classes/structs allocated on the stack, which cease to exist at the end of the catchpad.
  • Loading branch information
Syniurge committed Nov 2, 2019
1 parent 00ab3f5 commit 6b8ca11
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 25 deletions.
3 changes: 2 additions & 1 deletion dmd/cpp/calypso.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ cl::list<std::string> cppArgs("cpp-args",
cl::desc("Clang arguments (space separated) passed during PCH generation. If the list begins with '$', interpret as a single argument"));

cl::opt<bool> cppNoDefaultArgs("cpp-nodefaultargs",
cl::desc("Do not pass default arguments to Clang (by default \"-c -x c++\" in Unix-like environments, and \"--driver-mode=cl /TP\" in MSVC environments)."));
cl::desc("Do not pass default arguments to Clang (by default \"-c -x c++\" in Unix-like environments, and \"--driver-mode=cl /TP /EHsc\" in MSVC environments)."));

cl::opt<std::string> cppCacheDir("cpp-cachedir",
cl::desc("Write Calypso cache files to <dir>"),
Expand Down Expand Up @@ -1346,6 +1346,7 @@ void LangPlugin::_init()
} else {
clangArgv.push_back("--driver-mode=cl");
clangArgv.push_back("/TP");
clangArgv.push_back("/EHsc");
}
}

Expand Down
2 changes: 2 additions & 0 deletions dmd/cpp/calypso.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ class LangPlugin final : public ::LangPlugin, public ::ForeignCodeGen
void toEndCatch(IRState& irs, ::Catch *cj) override;
llvm::GlobalVariable* toCatchScopeType(IRState& irs, Type *t) override;

llvm::Constant *getTypeDescriptorMSVC(IRState &irs, Type *t, int& flags) override;

llvm::DIType* DIGetRecordType(AggregateDeclaration* ad) override;

// ==== ==== ====
Expand Down
4 changes: 2 additions & 2 deletions gen/arrays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ bool validCompareWithMemcmp(DValue *l, DValue *r) {
}

// Create a call instruction to memcmp.
llvm::CallInst *callMemcmp(Loc &loc, IRState &irs, LLValue *l_ptr,
llvm::Instruction *callMemcmp(Loc &loc, IRState &irs, LLValue *l_ptr,
LLValue *r_ptr, LLValue *numElements) {
assert(l_ptr && r_ptr && numElements);
LLFunction *fn = getRuntimeFunction(loc, gIR->module, "memcmp");
Expand All @@ -1085,7 +1085,7 @@ llvm::CallInst *callMemcmp(Loc &loc, IRState &irs, LLValue *l_ptr,
// Call memcmp.
LLValue *args[] = {DtoBitCast(l_ptr, getVoidPtrType()),
DtoBitCast(r_ptr, getVoidPtrType()), sizeInBytes};
return irs.ir->CreateCall(fn, args);
return irs.funcGen().callOrInvoke(fn, args, "", true).getInstruction();
}

/// Compare `l` and `r` using memcmp. No checks are done for validity.
Expand Down
2 changes: 2 additions & 0 deletions gen/cgforeign.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class ForeignCodeGen
virtual void toEndCatch(IRState& irs, Catch *cj) = 0;
virtual llvm::GlobalVariable* toCatchScopeType(IRState& irs, Type *t) = 0;

virtual llvm::Constant *getTypeDescriptorMSVC(IRState &irs, Type *t, int& flags) = 0;

// Debug info
virtual llvm::DIType* DIGetRecordType(AggregateDeclaration* ad) = 0;
};
15 changes: 15 additions & 0 deletions gen/cpp/cppeh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "gen/tollvm.h"

#include "clang/lib/CodeGen/Address.h"
#include "clang/lib/CodeGen/CGCleanup.h"
#include "clang/lib/CodeGen/CGCXXABI.h"
#include "clang/lib/CodeGen/CGException.h"
#include "clang/lib/CodeGen/CodeGenFunction.h"
Expand Down Expand Up @@ -130,5 +131,19 @@ llvm::GlobalVariable *LangPlugin::toCatchScopeType(IRState& irs, Type *t)
return wrapper;
}

llvm::Constant *LangPlugin::getTypeDescriptorMSVC(IRState &irs, Type *t, int& flags)
{
auto loc = irs.func()->decl->loc;
auto ThrowType = DeclMapper(nullptr, nullptr).toType(loc, t, irs.func()->decl->_scope);

auto CTI = CGM->getCXXABI().getAddrOfCXXCatchHandlerType(ThrowType, /*CatchHandlerType=*/ ThrowType);

flags = CTI.Flags;
if (isAggregate(t))
flags |= 8; // by ref

return CTI.RTTI;
}

}

1 change: 1 addition & 0 deletions gen/cpp/cpptoir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,7 @@ DValue* LangPlugin::toCallFunction(Loc& loc, Type* resulttype, DValue* fnval,
"postinvoke", gIR->topfunc(), invokeDest);
}

CGF()->CurrentFuncletPad = gIR->funcGen().funcletPad;
updateCGFInsertPoint(); // emitLandingPad() may have run the cleanups and call C++ dtors, hence changing the insert point

clangCG::CGCalleeInfo calleeInfo(FD->getType()->getAs<clang::FunctionProtoType>(), GD);
Expand Down
21 changes: 20 additions & 1 deletion gen/funcgenstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ FuncGenState::FuncGenState(IrFunction &irFunc, IRState &irs)
: irFunc(irFunc), scopes(irs), jumpTargets(scopes), switchTargets(),
irs(irs) {}

llvm::SmallVector<llvm::OperandBundleDef, 1> FuncGenState::getBundlesForFunclet(llvm::Value *callee) {
// copy from from clang/.../CGCall.cpp
llvm::SmallVector<llvm::OperandBundleDef, 1> BundleList;

// There is no need for a funclet operand bundle if we aren't inside a
// funclet.
if (!funcletPad)
return BundleList;

// Skip intrinsics which cannot throw.
auto *CalleeFn = llvm::dyn_cast<llvm::Function>(callee->stripPointerCasts());
if (CalleeFn && CalleeFn->isIntrinsic() && CalleeFn->doesNotThrow())
return BundleList;

BundleList.emplace_back("funclet", funcletPad);
return BundleList;
}

llvm::CallSite FuncGenState::callOrInvoke(llvm::Value *callee,
llvm::ArrayRef<llvm::Value *> args,
const char *name, bool isNothrow) {
Expand All @@ -121,7 +139,8 @@ llvm::CallSite FuncGenState::callOrInvoke(llvm::Value *callee,
(calleeFn && (calleeFn->isIntrinsic() || calleeFn->doesNotThrow()));

// calls inside a funclet must be annotated with its value
llvm::SmallVector<llvm::OperandBundleDef, 2> BundleList;
llvm::SmallVector<llvm::OperandBundleDef, 1> BundleList =
getBundlesForFunclet(callee);

if (doesNotThrow || scopes.empty()) {
llvm::CallInst *call = irs.ir->CreateCall(callee, args, BundleList, name);
Expand Down
5 changes: 5 additions & 0 deletions gen/funcgenstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ class FuncGenState {
/// value.
llvm::AllocaInst *retValSlot = nullptr;

/// The catchpad funclet being emitted (MSVC)
llvm::Instruction *funcletPad = nullptr;

/// Emits a call or invoke to the given callee, depending on whether there
/// are catches/cleanups active or not.
llvm::CallSite callOrInvoke(llvm::Value *callee,
Expand All @@ -206,4 +209,6 @@ class FuncGenState {

private:
IRState &irs;

llvm::SmallVector<llvm::OperandBundleDef, 1> getBundlesForFunclet(llvm::Value *callee);
};
2 changes: 1 addition & 1 deletion gen/tollvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ LLValue *DtoMemCmp(LLValue *lhs, LLValue *rhs, LLValue *nbytes) {
lhs = DtoBitCast(lhs, VoidPtrTy);
rhs = DtoBitCast(rhs, VoidPtrTy);

return gIR->ir->CreateCall(fn, {lhs, rhs, nbytes});
return gIR->CreateCallOrInvoke(fn, {lhs, rhs, nbytes}, "", true).getInstruction();
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
41 changes: 23 additions & 18 deletions gen/trycatchfinally.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ void TryCatchScope::emitCatchBodies(IRState &irs, llvm::Value *ehPtrSlot) {

namespace {
void emitBeginCatchMSVC(IRState &irs, Catch *ctch,
llvm::CatchSwitchInst *catchSwitchInst) {
llvm::CatchSwitchInst *catchSwitchInst, llvm::BasicBlock* endbb) {
VarDeclaration *var = ctch->var;
// The MSVC/x86 build uses C++ exception handling
// This needs a series of catch pads to match the exception
Expand Down Expand Up @@ -263,46 +263,58 @@ void emitBeginCatchMSVC(IRState &irs, Catch *ctch,
exnObj = LLConstant::getNullValue(getVoidPtrType());
}

int flags = 0;
bool isCPPclass = false;
if (ctch->type) {
if (auto lp = ctch->langPlugin()) { // CALYPSO
typeDesc = lp->codegen()->getTypeDescriptorMSVC(irs, ctch->type, flags);
} else {
ClassDeclaration *cd = ctch->type->toBasetype()->isClassHandle();
typeDesc = getTypeDescriptor(irs, cd);
isCPPclass = cd->isCPPclass();
if (!isCPPclass)
clssInfo = getIrAggr(cd)->getClassInfoSymbol();
else
flags = 8; // by ref (for C++ class exceptions created on the stack)
}
} else {
// catch all
typeDesc = LLConstant::getNullValue(getVoidPtrType());
clssInfo = LLConstant::getNullValue(DtoType(getClassInfoType()));
flags = 0x40;
}

// "catchpad within %switch [TypeDescriptor, 0, &caughtObject]" must be
// first instruction
int flags = var ? (isCPPclass ? 8 : 0) : 64; // just mimicking clang here
LLValue *args[] = {typeDesc, DtoConstUint(flags), exnObj};
auto catchpad = irs.ir->CreateCatchPad(catchSwitchInst, args, "");
catchSwitchInst->addHandler(irs.scopebb());

irs.funcGen().funcletPad = catchpad;

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);
irs.funcGen().pgo.emitCounterIncrement(ctch);
if (!isCPPclass) {
if (!isCPPclass && !ctch->langPlugin()) { // CALYPSO
auto enterCatchFn =
getRuntimeFunction(ctch->loc, irs.module, "_d_eh_enter_catch");
irs.CreateCallOrInvoke(enterCatchFn, DtoBitCast(exnObj, getVoidPtrType()),
clssInfo);
}

// Emit handler, if there is one. The handler is zero, for instance,
// when building 'catch { debug foo(); }' in non-debug mode.
if (ctch->handler)
Statement_toIR(ctch->handler, &irs);

irs.funcGen().funcletPad = nullptr;

llvm::CatchReturnInst::Create(catchpad, endbb, irs.scopebb());
}
}

Expand All @@ -325,15 +337,8 @@ void TryCatchScope::emitCatchBodiesMSVC(IRState &irs, llvm::Value *) {
irs.scope() = IRScope(catchBB);
irs.DBuilder.EmitBlockStart(c->loc);

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);
emitBeginCatchMSVC(irs, c, catchSwitchInst, endbb);
assert(irs.scopereturned());

irs.DBuilder.EmitBlockEnd();
}
Expand Down
10 changes: 8 additions & 2 deletions tests/calypso/eh_catch.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#include <exception>

const unsigned magic_num = 0x12345678;

class ooops : public std::exception
{
public:
unsigned a;
const char* what() const throw() { return "Ooops!"; }

ooops() { a = 0x12345678; }
const char* what() const throw() {
return (a == magic_num) ? "Ooops!" : "<INVALID>";
}

ooops() { a = magic_num; }
~ooops() { a = 0; }
};

namespace test
Expand Down

0 comments on commit 6b8ca11

Please sign in to comment.