diff --git a/gen/dibuilder.cpp b/gen/dibuilder.cpp index b6a9cc44be2..f9a09f65adf 100644 --- a/gen/dibuilder.cpp +++ b/gen/dibuilder.cpp @@ -645,23 +645,70 @@ ldc::DISubprogram ldc::DIBuilder::EmitSubProgram(FuncDeclaration *fd) { ldc::DIFile file(CreateFile(fd->loc)); + std::string mangledName(getIrFunc(fd)->func->getName()); + // Create subroutine type ldc::DISubroutineType DIFnType = - CreateFunctionType(static_cast(fd->type)); + CreateFunctionType(static_cast(fd->type)); + + // FIXME: duplicates? + return DBuilder.createFunction( + CU, // context + fd->toPrettyChars(), // name + mangledName, // linkage name + file, // file + fd->loc.linnum, // line no + DIFnType, // type + fd->protection.kind == PROTprivate, // is local to unit + true, // isdefinition + fd->loc.linnum, // FIXME: scope line + DIFlags::FlagPrototyped, // Flags + isOptimizationEnabled() // isOptimized +#if LDC_LLVM_VER < 308 + , + getIrFunc(fd)->func +#endif + ); +} + +ldc::DISubprogram ldc::DIBuilder::EmitThunk(llvm::Function *Thunk, + FuncDeclaration *fd) { + if (!global.params.symdebug) { +#if LDC_LLVM_VER >= 307 + return nullptr; +#else + return llvm::DISubprogram(); +#endif + } + + Logger::println("Thunk to dwarf subprogram"); + LOG_SCOPE; + + ldc::DICompileUnit CU(GetCU()); + assert(CU && + "Compilation unit missing or corrupted in DIBuilder::EmitThunk"); + + ldc::DIFile file(CreateFile(fd->loc)); + + // Create subroutine type (thunk has same type as wrapped function) + ldc::DISubroutineType DIFnType = CreateFunctionType(fd->type); + + std::string name(fd->toPrettyChars()); + name.append(".__thunk"); // FIXME: duplicates? return DBuilder.createFunction( - CU, // context - fd->toPrettyChars(), // name - mangleExact(fd), // linkage name - file, // file - fd->loc.linnum, // line no - DIFnType, // type - fd->protection.kind == PROTprivate, // is local to unit - true, // isdefinition - fd->loc.linnum, // FIXME: scope line - DIFlags::FlagPrototyped, // Flags - isOptimizationEnabled() // isOptimized + CU, // context + name, // name + Thunk->getName(), // linkage name + file, // file + fd->loc.linnum, // line no + DIFnType, // type + fd->protection.kind == PROTprivate, // is local to unit + true, // isdefinition + fd->loc.linnum, // FIXME: scope line + DIFlags::FlagPrototyped, // Flags + isOptimizationEnabled() // isOptimized #if LDC_LLVM_VER < 308 , getIrFunc(fd)->func diff --git a/gen/dibuilder.h b/gen/dibuilder.h index 14fba85198b..73d27d3e609 100644 --- a/gen/dibuilder.h +++ b/gen/dibuilder.h @@ -99,6 +99,13 @@ class DIBuilder { /// \returns the Dwarf subprogram global. DISubprogram EmitSubProgram(FuncDeclaration *fd); // FIXME + /// \brief Emit the Dwarf subprogram global for a thunk. + /// \param Thunk llvm::Function pointer. + /// \param fd The function wrapped by this thunk. + /// \returns the Dwarf subprogram global. + DISubprogram EmitThunk(llvm::Function *Thunk, + FuncDeclaration *fd); // FIXME + /// \brief Emit the Dwarf subprogram global for a module ctor. /// This is used for generated functions like moduleinfoctors, /// module ctors/dtors and unittests. diff --git a/gen/irstate.h b/gen/irstate.h index 09bc607ac7b..deb93866498 100644 --- a/gen/irstate.h +++ b/gen/irstate.h @@ -182,6 +182,12 @@ struct IRState { #else llvm::SmallVector LinkerMetadataArgs; #endif + +#if LDC_LLVM_VER >= 308 + // MS C++ compatible type descriptors + llvm::DenseMap TypeDescriptorTypeMap; + llvm::DenseMap TypeDescriptorMap; +#endif }; void Statement_toIR(Statement *s, IRState *irs); diff --git a/gen/ms-cxx-helper.cpp b/gen/ms-cxx-helper.cpp new file mode 100644 index 00000000000..4b900f3a9fa --- /dev/null +++ b/gen/ms-cxx-helper.cpp @@ -0,0 +1,189 @@ +//===-- ms-cxx-helper.cpp -------------------------------------------------===// +// +// LDC – the LLVM D compiler +// +// This file is distributed under the BSD-style LDC license. See the LICENSE +// file for details. +// +//===----------------------------------------------------------------------===// + +#include "gen/ms-cxx-helper.h" +#include "gen/llvm.h" +#include "gen/llvmhelpers.h" +#include "gen/irstate.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" + +#if LDC_LLVM_VER >= 308 +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/IR/CFG.h" + +llvm::BasicBlock *getUnwindDest(llvm::Instruction *I) { + if (auto II = llvm::dyn_cast (I)) + return II->getUnwindDest(); + else if (auto CSI = llvm::dyn_cast (I)) + return CSI->getUnwindDest(); + else if (auto CRPI = llvm::dyn_cast (I)) + return CRPI->getUnwindDest(); + return nullptr; +} + +// return all basic blocks that are reachable from bb, but don't pass through +// ebb and don't follow unwinding target +void findSuccessors(std::vector &blocks, + llvm::BasicBlock *bb, llvm::BasicBlock *ebb) { + blocks.push_back(bb); + if (bb != ebb) { + assert(bb->getTerminator()); + for (size_t pos = 0; pos < blocks.size(); ++pos) { + bb = blocks[pos]; + if (auto term = bb->getTerminator()) { + llvm::BasicBlock *unwindDest = getUnwindDest(term); + unsigned cnt = term->getNumSuccessors(); + for (unsigned s = 0; s < cnt; s++) { + llvm::BasicBlock *succ = term->getSuccessor(s); + if (succ != ebb && succ != unwindDest && + std::find(blocks.begin(), blocks.end(), succ) == blocks.end()) { + blocks.push_back(succ); + } + } + } + } + blocks.push_back(ebb); + } +} + +// remap values in all instructions of all blocks +void remapBlocks(std::vector &blocks, + llvm::ValueToValueMapTy &VMap, llvm::BasicBlock *unwindTo, + llvm::Value *funclet) { + for (llvm::BasicBlock *bb : blocks) + for (llvm::BasicBlock::iterator I = bb->begin(); I != bb->end(); ++I) { + llvm::RemapInstruction(&*I, VMap, llvm::RF_IgnoreMissingEntries | + llvm::RF_NoModuleLevelChanges); + } +} + +void remapBlocksValue(std::vector &blocks, + llvm::Value *from, llvm::Value *to) { + llvm::ValueToValueMapTy VMap; + VMap[from] = to; + remapBlocks(blocks, VMap, nullptr, nullptr); +} + +// make a copy of all blocks and instructions in srcblocks +// - map values to clones +// - redirect srcTarget to continueWith +// - set "funclet" attribute inside catch/cleanup pads +// - inside funclets, replace "unreachable" with "branch cleanupret" +// - disable inlining inside a funclet +void cloneBlocks(const std::vector &srcblocks, + std::vector &blocks, + llvm::BasicBlock *continueWith, llvm::BasicBlock *unwindTo, + llvm::Value *funclet) { + llvm::ValueToValueMapTy VMap; + // map the terminal branch to the new target + if (continueWith) + if (auto term = srcblocks.back()->getTerminator()) + if (auto succ = term->getSuccessor(0)) + VMap[succ] = continueWith; + + for (size_t b = 0; b < srcblocks.size(); b++) { + llvm::BasicBlock *bb = srcblocks[b]; + llvm::Function* F = bb->getParent(); + + auto nbb = llvm::BasicBlock::Create(bb->getContext(), bb->getName()); + // Loop over all instructions, and copy them over. + for (auto II = bb->begin(), IE = bb->end(); II != IE; ++II) { + llvm::Instruction *Inst = &*II; + llvm::Instruction *newInst = nullptr; + if (funclet && !llvm::isa(Inst)) { // IntrinsicInst? + if (auto IInst = llvm::dyn_cast (Inst)) { + auto invoke = llvm::InvokeInst::Create( + IInst, llvm::OperandBundleDef("funclet", funclet)); + invoke->setIsNoInline(); + newInst = invoke; + } else if (auto CInst = llvm::dyn_cast (Inst)) { + auto call = llvm::CallInst::Create( + CInst, llvm::OperandBundleDef("funclet", funclet)); + call->setIsNoInline(); + newInst = call; + } else if (funclet && llvm::isa(Inst)) { + newInst = llvm::BranchInst::Create(continueWith); // to cleanupret + } + } + if (!newInst) + newInst = Inst->clone(); + + nbb->getInstList().push_back(newInst); + VMap[Inst] = newInst; // Add instruction map to value. + if (unwindTo) + if (auto dest = getUnwindDest(Inst)) + VMap[dest] = unwindTo; + } + nbb->insertInto(F, continueWith); + VMap[bb] = nbb; + blocks.push_back(nbb); + } + + remapBlocks(blocks, VMap, unwindTo, funclet); +} + +bool isCatchSwitchBlock(llvm::BasicBlock* bb) { + if (bb->empty()) + return false; + return llvm::dyn_cast(&bb->front()); +} + +// copy from clang/.../MicrosoftCXXABI.cpp + +// routines for constructing the llvm types for MS RTTI structs. +llvm::StructType *getTypeDescriptorType(IRState &irs, + llvm::Constant *classInfoPtr, + llvm::StringRef TypeInfoString) { + llvm::SmallString<256> TDTypeName("rtti.TypeDescriptor"); + TDTypeName += llvm::utostr(TypeInfoString.size()); + llvm::StructType *&TypeDescriptorType = + irs.TypeDescriptorTypeMap[TypeInfoString.size()]; + if (TypeDescriptorType) + return TypeDescriptorType; + auto int8Ty = LLType::getInt8Ty(gIR->context()); + llvm::Type *FieldTypes[] = { + classInfoPtr->getType(), // CGM.Int8PtrPtrTy, + getPtrToType(int8Ty), // CGM.Int8PtrTy, + llvm::ArrayType::get(int8Ty, TypeInfoString.size() + 1)}; + TypeDescriptorType = + llvm::StructType::create(gIR->context(), FieldTypes, TDTypeName); + return TypeDescriptorType; +} + +llvm::GlobalVariable *getTypeDescriptor(IRState &irs, ClassDeclaration *cd) { + + auto classInfoPtr = getIrAggr(cd, true)->getClassInfoSymbol(); + llvm::GlobalVariable *&Var = irs.TypeDescriptorMap[classInfoPtr]; + if (Var) + return Var; + + // first character skipped in debugger output, so we add 'D' as prefix + std::string TypeNameString = "D"; + TypeNameString.append(cd->toPrettyChars()); + std::string TypeDescName = TypeNameString + "@TypeDescriptor"; + + // Declare and initialize the TypeDescriptor. + llvm::Constant *Fields[] = { + classInfoPtr, // VFPtr + llvm::ConstantPointerNull::get( + LLType::getInt8PtrTy(gIR->context())), // Runtime data + llvm::ConstantDataArray::getString(gIR->context(), TypeNameString)}; + llvm::StructType *TypeDescriptorType = + getTypeDescriptorType(irs, classInfoPtr, TypeNameString); + Var = new llvm::GlobalVariable( + gIR->module, TypeDescriptorType, /*Constant=*/false, + LLGlobalVariable::InternalLinkage, // getLinkageForRTTI(Type), + llvm::ConstantStruct::get(TypeDescriptorType, Fields), TypeDescName); + return Var; +} + +#endif // LDC_LLVM_VER >= 308 diff --git a/gen/ms-cxx-helper.h b/gen/ms-cxx-helper.h new file mode 100644 index 00000000000..28633df435c --- /dev/null +++ b/gen/ms-cxx-helper.h @@ -0,0 +1,33 @@ +//===-- ms-cxx-helper.h ---------------------------------------------------===// +// +// LDC – the LLVM D compiler +// +// This file is distributed under the BSD-style LDC license. See the LICENSE +// file for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LDC_GEN_MS_CXX_HELPER_H +#define LDC_GEN_MS_CXX_HELPER_H + +#include "gen/irstate.h" + +llvm::StructType *getTypeDescriptorType(IRState &irs, + llvm::Constant *classInfoPtr, + llvm::StringRef TypeInfoString); +llvm::GlobalVariable *getTypeDescriptor(IRState &irs, ClassDeclaration *cd); + +void findSuccessors(std::vector &blocks, + llvm::BasicBlock *bb, llvm::BasicBlock *ebb); + +void remapBlocksValue(std::vector &blocks, + llvm::Value *from, llvm::Value *to); + +void cloneBlocks(const std::vector &srcblocks, + std::vector &blocks, + llvm::BasicBlock *continueWith, llvm::BasicBlock *unwindTo, + llvm::Value *funclet); + +bool isCatchSwitchBlock(llvm::BasicBlock* bb); + +#endif // LDC_GEN_MS_CXX_HELPER_H diff --git a/gen/runtime.cpp b/gen/runtime.cpp index 22e536a4f95..f4804d004da 100644 --- a/gen/runtime.cpp +++ b/gen/runtime.cpp @@ -24,6 +24,7 @@ #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/tollvm.h" +#include "ir/irfunction.h" #include "ir/irtype.h" #include "ir/irtypefunction.h" #include "llvm/Bitcode/ReaderWriter.h" @@ -620,9 +621,11 @@ static void buildRuntimeModule() { // int _d_eh_personality(...) { if (global.params.targetTriple->isWindowsMSVCEnvironment()) { + const char *fname = + useMSVCEH() ? "__CxxFrameHandler3" : "_d_eh_personality"; // (ptr ExceptionRecord, ptr EstablisherFrame, ptr ContextRecord, // ptr DispatcherContext) - createFwdDecl(LINKc, intTy, {"_d_eh_personality"}, + createFwdDecl(LINKc, intTy, {fname}, {voidPtrTy, voidPtrTy, voidPtrTy, voidPtrTy}); } else if (global.params.targetTriple->getArch() == llvm::Triple::arm) { // (int state, ptr ucb, ptr context) @@ -635,12 +638,25 @@ static void buildRuntimeModule() { } } - // void _d_eh_resume_unwind(ptr) - createFwdDecl(LINKc, voidTy, {"_d_eh_resume_unwind"}, {voidPtrTy}); + if (useMSVCEH()) { + // _d_enter_cleanup(ptr frame) + createFwdDecl(LINKc, boolTy, {"_d_enter_cleanup"}, {voidPtrTy}); - // Object _d_eh_enter_catch(ptr) - createFwdDecl(LINKc, objectTy, {"_d_eh_enter_catch"}, {voidPtrTy}, {}, - Attr_NoUnwind); + // _d_leave_cleanup(ptr frame) + createFwdDecl(LINKc, voidTy, {"_d_leave_cleanup"}, {voidPtrTy}); + + // Object _d_eh_enter_catch(ptr exception, ClassInfo catchType) + createFwdDecl(LINKc, objectTy, {"_d_eh_enter_catch"}, + {voidPtrTy, classInfoTy}, {}); + } else { + + // void _d_eh_resume_unwind(ptr) + createFwdDecl(LINKc, voidTy, {"_d_eh_resume_unwind"}, {voidPtrTy}); + + // Object _d_eh_enter_catch(ptr) + createFwdDecl(LINKc, objectTy, {"_d_eh_enter_catch"}, {voidPtrTy}, {}, + Attr_NoUnwind); + } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// diff --git a/gen/statements.cpp b/gen/statements.cpp index e8f4eee0494..9ddd1f52000 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -23,6 +23,7 @@ #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" @@ -709,6 +710,85 @@ class ToIRVisitor : public Visitor { ////////////////////////////////////////////////////////////////////////// +#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; + } 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", stmt->loc.toChars()); @@ -733,59 +813,113 @@ class ToIRVisitor : public Visitor { CatchBlocks catchBlocks; catchBlocks.reserve(stmt->catches->dim); - for (Catches::reverse_iterator it = stmt->catches->rbegin(), - end = stmt->catches->rend(); - it != end; ++it) { - llvm::BasicBlock *catchBB = llvm::BasicBlock::Create( +#if LDC_LLVM_VER >= 308 + if (useMSVCEH()) { + ScopeStack *scopes = irs->func()->scopes; + auto catchSwitchBlock = llvm::BasicBlock::Create( + irs->context(), "catch.dispatch", irs->topfunc()); + llvm::BasicBlock *unwindto = + scopes->currentCleanupScope() > 0 || scopes->currentCatchScope() > 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 it = stmt->catches->begin(), end = stmt->catches->end(); + it != end; ++it) { + llvm::BasicBlock *catchBB = llvm::BasicBlock::Create( irs->context(), llvm::Twine("catch.") + (*it)->type->toChars(), irs->topfunc(), endbb); - irs->scope() = IRScope(catchBB); - irs->DBuilder.EmitBlockStart((*it)->loc); - - const auto enterCatchFn = - getRuntimeFunction(Loc(), irs->module, "_d_eh_enter_catch"); - auto ptr = DtoLoad(irs->func()->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); - } + irs->scope() = IRScope(catchBB); + irs->DBuilder.EmitBlockStart((*it)->loc); - // 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); - } + emitBeginCatchMSVCEH(*it, endbb, catchSwitchInst); - if (!irs->scopereturned()) { - irs->ir->CreateBr(endbb); - } + // 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); + } - irs->DBuilder.EmitBlockEnd(); + if (!irs->scopereturned()) { + irs->ir->CreateBr(endbb); + } + irs->DBuilder.EmitBlockEnd(); + + } catchBlocks.push_back( - std::make_pair((*it)->type->toBasetype()->isClassHandle(), catchBB)); - } + std::make_pair(nullptr, catchSwitchBlock)); // 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 (Catches::reverse_iterator it = stmt->catches->rbegin(), + end = stmt->catches->rend(); + it != end; ++it) { + llvm::BasicBlock *catchBB = llvm::BasicBlock::Create( + irs->context(), llvm::Twine("catch.") + (*it)->type->toChars(), + irs->topfunc(), endbb); + + irs->scope() = IRScope(catchBB); + irs->DBuilder.EmitBlockStart((*it)->loc); + + const auto enterCatchFn = + getRuntimeFunction(Loc(), irs->module, "_d_eh_enter_catch"); + auto ptr = DtoLoad(irs->func()->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); + } - // 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 &pair : catchBlocks) { - DtoResolveClass(pair.first); - irs->func()->scopes->pushCatch( - getIrAggr(pair.first)->getClassInfoSymbol(), pair.second); + // 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(); + + catchBlocks.push_back(std::make_pair( + (*it)->type->toBasetype()->isClassHandle(), catchBB)); + } + // 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 &pair : catchBlocks) { + DtoResolveClass(pair.first); + irs->func()->scopes->pushCatch( + getIrAggr(pair.first)->getClassInfoSymbol(), pair.second); + } } // Emit the try block. diff --git a/gen/target.cpp b/gen/target.cpp index 2002983037b..c4bee629b8c 100644 --- a/gen/target.cpp +++ b/gen/target.cpp @@ -41,9 +41,8 @@ void Target::_init() { c_longsize = global.params.is64bit ? 8 : 4; c_long_doublesize = realsize; - // according to DMD, only for 32-bit MSVC++: - reverseCppOverloads = !global.params.is64bit && - global.params.targetTriple->isWindowsMSVCEnvironment(); + // according to DMD, only for MSVC++: + reverseCppOverloads = global.params.targetTriple->isWindowsMSVCEnvironment(); } /****************************** diff --git a/gen/tocall.cpp b/gen/tocall.cpp index 49acfe3e5ba..a4ed2171f5d 100644 --- a/gen/tocall.cpp +++ b/gen/tocall.cpp @@ -987,6 +987,10 @@ DValue *DtoCallFunction(Loc &loc, Type *resulttype, DValue *fnval, } else { call.setCallingConv(callconv); } + // merge in function attributes set in callOrInvoke + attrlist = attrlist.addAttributes( + gIR->context(), llvm::AttributeSet::FunctionIndex, call.getAttributes()); + call.setAttributes(attrlist); // Special case for struct constructor calls: For temporaries, using the diff --git a/ir/irclass.cpp b/ir/irclass.cpp index e980e64667f..e37941c03ec 100644 --- a/ir/irclass.cpp +++ b/ir/irclass.cpp @@ -350,11 +350,34 @@ llvm::GlobalVariable *IrAggr::getInterfaceVtbl(BaseClass *b, bool new_instance, // function has. thunk->setUnnamedAddr(true); +#if LDC_LLVM_VER >= 307 + // thunks don't need exception handling themselves + thunk->setPersonalityFn(nullptr); +#endif + + // it is necessary to add debug information to the thunk + // in case it is subject to inlining. See https://llvm.org/bugs/show_bug.cgi?id=26833 + IF_LOG Logger::println("Doing function body for thunk to: %s", fd->toChars()); + + // create a dummy FuncDeclaration with enough information to satisfy the DIBuilder + FuncDeclaration *thunkFd = reinterpret_cast(memcpy( + new char[sizeof(FuncDeclaration)], fd, sizeof(FuncDeclaration))); + thunkFd->ir = new IrDsymbol(); + auto thunkFunc = getIrFunc(thunkFd, true); // create the IrFunction + thunkFunc->func = thunk; + thunkFunc->type = irFunc->type; + gIR->functions.push_back(thunkFunc); + + // debug info + thunkFunc->diSubprogram = gIR->DBuilder.EmitThunk(thunk, thunkFd); + // create entry and end blocks llvm::BasicBlock *beginbb = llvm::BasicBlock::Create(gIR->context(), "", thunk); gIR->scopes.push_back(IRScope(beginbb)); + gIR->DBuilder.EmitFuncStart(thunkFd); + // Copy the function parameters, so later we can pass them to the // real function and set their names from the original function (the // latter being just for IR readablilty). @@ -376,6 +399,10 @@ llvm::GlobalVariable *IrAggr::getInterfaceVtbl(BaseClass *b, bool new_instance, thisArg = DtoGEP1(thisArg, DtoConstInt(-b->offset), true); thisArg = DtoBitCast(thisArg, targetThisType); + // all calls that might be subject to inlining into a caller with debug info + // should have debug info, too + gIR->DBuilder.EmitStopPoint(fd->loc); + // call the real vtbl function. llvm::CallSite call = gIR->ir->CreateCall(irFunc->func, args); call.setCallingConv(irFunc->func->getCallingConv()); @@ -388,8 +415,12 @@ llvm::GlobalVariable *IrAggr::getInterfaceVtbl(BaseClass *b, bool new_instance, beginbb); } + gIR->DBuilder.EmitFuncEnd(thunkFd); + // clean up gIR->scopes.pop_back(); + + gIR->functions.pop_back(); } constants.push_back(thunk); diff --git a/ir/irfunction.cpp b/ir/irfunction.cpp index bfdd7136a28..d710ee6757b 100644 --- a/ir/irfunction.cpp +++ b/ir/irfunction.cpp @@ -12,6 +12,7 @@ #include "gen/irstate.h" #include "gen/runtime.h" #include "gen/tollvm.h" +#include "gen/ms-cxx-helper.h" #include "ir/irdsymbol.h" #include "ir/irfunction.h" #include @@ -31,10 +32,81 @@ CatchScope::CatchScope(llvm::Constant *classInfoPtr, : classInfoPtr(classInfoPtr), bodyBlock(bodyBlock), cleanupScope(cleanupScope) {} +bool useMSVCEH() { +#if LDC_LLVM_VER >= 308 + return global.params.targetTriple->isWindowsMSVCEnvironment() && + !global.params.targetTriple->isArch64Bit(); +#else + return false; +#endif +} + namespace { + +#if LDC_LLVM_VER >= 308 + +// MSVC/x86 uses C++ exception handling that puts cleanup blocks into funclets. +// This means that we cannot use a branch selector and conditional branches +// at cleanup exit to continue with different targets. +// Instead we make a full copy of the cleanup code for every target +// +// Return the beginning basic block of the cleanup code +llvm::BasicBlock *executeCleanupCopying(IRState *irs, CleanupScope &scope, + llvm::BasicBlock *sourceBlock, + llvm::BasicBlock *continueWith, + llvm::BasicBlock *unwindTo, + llvm::Value* funclet) { + if (isCatchSwitchBlock(scope.beginBlock)) + return continueWith; + if (scope.cleanupBlocks.empty()) { + // figure out the list of blocks used by this cleanup step + findSuccessors(scope.cleanupBlocks, scope.beginBlock, scope.endBlock); + if (!scope.endBlock->getTerminator()) + // Set up the unconditional branch at the end of the cleanup + llvm::BranchInst::Create(continueWith, scope.endBlock); + } else { + // check whether we have an exit target with the same continuation + for (CleanupExitTarget &tgt : scope.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 : scope.exitTargets) + useOriginal = useOriginal && tgt.cleanupBlocks.front() != scope.beginBlock; + + // append new target + scope.exitTargets.push_back(CleanupExitTarget(continueWith)); + scope.exitTargets.back().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 = scope.endBlock->getTerminator()) + if (auto succ = term->getSuccessor(0)) + if (succ != continueWith) { + remapBlocksValue(scope.cleanupBlocks, succ, continueWith); + } + scope.exitTargets.back().cleanupBlocks = scope.cleanupBlocks; + } else { + // clone the code + cloneBlocks(scope.cleanupBlocks, scope.exitTargets.back().cleanupBlocks, + continueWith, unwindTo, funclet); + } + return scope.exitTargets.back().cleanupBlocks.front(); +} + +#endif // LDC_LLVM_VER >= 308 + void executeCleanup(IRState *irs, CleanupScope &scope, llvm::BasicBlock *sourceBlock, llvm::BasicBlock *continueWith) { + assert(!useMSVCEH()); // should always use executeCleanupCopying + if (scope.exitTargets.empty() || (scope.exitTargets.size() == 1 && scope.exitTargets[0].branchTarget == continueWith)) { @@ -134,6 +206,12 @@ void ScopeStack::pushCleanup(llvm::BasicBlock *beginBlock, void ScopeStack::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) { @@ -154,6 +232,77 @@ void ScopeStack::runCleanups(CleanupCursor sourceScope, } } +#if LDC_LLVM_VER >= 308 +void ScopeStack::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 = executeCleanupCopying(irs, cleanupScopes[i], irs->scopebb(), + continueWith, nullptr, nullptr); + + // Insert the unconditional branch to the first cleanup block. + irs->ir->CreateBr(continueWith); +} + +llvm::BasicBlock *ScopeStack::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 = + llvm::BasicBlock::Create(irs->context(), "cleanuppad", irs->topfunc()); + auto cleanuppad = + llvm::CleanupPadInst::Create(getFuncletToken(), {}, "", cleanupbb); + + llvm::BasicBlock *cleanupret = + llvm::BasicBlock::Create(irs->context(), "cleanupret", irs->topfunc()); + + // 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 endFn = getRuntimeFunction(Loc(), irs->module, "_d_leave_cleanup"); + llvm::CallInst::Create(endFn, frame, + {llvm::OperandBundleDef("funclet", cleanuppad)}, "", + cleanupret); + llvm::CleanupReturnInst::Create(cleanuppad, unwindTo, cleanupret); + + auto copybb = executeCleanupCopying(irs, cleanupScopes[scope], cleanupbb, + cleanupret, unwindTo, cleanuppad); + + auto beginFn = getRuntimeFunction(Loc(), irs->module, "_d_enter_cleanup"); + auto exec = llvm::CallInst::Create( + beginFn, frame, {llvm::OperandBundleDef("funclet", cleanuppad)}, "", + cleanupbb); + llvm::BranchInst::Create(copybb, cleanupret, exec, cleanupbb); + + return cleanupbb; +} +#endif + void ScopeStack::runAllCleanups(llvm::BasicBlock *continueWith) { runCleanups(0, continueWith); } @@ -170,11 +319,24 @@ void ScopeStack::popCleanups(CleanupCursor targetScope) { for (const auto &gotoJump : currentUnresolvedGotos()) { // Make the source resp. last cleanup branch to this one. llvm::BasicBlock *tentative = gotoJump.tentativeTarget; - tentative->replaceAllUsesWith(cleanupScopes[i].beginBlock); +#if LDC_LLVM_VER >= 308 + if (useMSVCEH()) { + llvm::BasicBlock *continueWith = + llvm::BasicBlock::Create(irs->context(), "jumpcleanup", irs->topfunc()); + auto startCleanup = + executeCleanupCopying(irs, cleanupScopes[i], gotoJump.sourceBlock, + continueWith, nullptr, nullptr); + tentative->replaceAllUsesWith(startCleanup); + llvm::BranchInst::Create(tentative, continueWith); + } else +#endif + { + tentative->replaceAllUsesWith(cleanupScopes[i].beginBlock); - // And continue execution with the tentative target (we simply reuse - // it because there is no reason not to). - executeCleanup(irs, cleanupScopes[i], gotoJump.sourceBlock, tentative); + // And continue execution with the tentative target (we simply reuse + // it because there is no reason not to). + executeCleanup(irs, cleanupScopes[i], gotoJump.sourceBlock, tentative); + } } std::vector &nextUnresolved = @@ -190,13 +352,27 @@ void ScopeStack::popCleanups(CleanupCursor targetScope) { void ScopeStack::pushCatch(llvm::Constant *classInfoPtr, llvm::BasicBlock *bodyBlock) { - catchScopes.emplace_back(classInfoPtr, bodyBlock, currentCleanupScope()); - currentLandingPads().push_back(nullptr); + if (useMSVCEH()) { +#if LDC_LLVM_VER >= 308 + assert(isCatchSwitchBlock(bodyBlock)); + pushCleanup(bodyBlock, bodyBlock); +#endif + } else { + catchScopes.emplace_back(classInfoPtr, bodyBlock, currentCleanupScope()); + currentLandingPads().push_back(nullptr); + } } void ScopeStack::popCatch() { - catchScopes.pop_back(); - currentLandingPads().pop_back(); + if (useMSVCEH()) { +#if LDC_LLVM_VER >= 308 + assert(isCatchSwitchBlock(cleanupScopes.back().beginBlock)); + popCleanups(currentCleanupScope() - 1); +#endif + } else { + catchScopes.pop_back(); + currentLandingPads().pop_back(); + } } void ScopeStack::pushLoopTarget(Statement *loopStatement, @@ -282,6 +458,32 @@ std::vector &ScopeStack::currentLandingPads() { : cleanupScopes.back().landingPads; } +llvm::BasicBlock *&ScopeStack::getLandingPadRef(CleanupCursor scope) { + auto &pads = cleanupScopes.empty() ? topLevelLandingPads + : cleanupScopes[scope].landingPads; + 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(); +} + +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(); + } + return landingPad; +} + namespace { llvm::LandingPadInst *createLandingPadInst(IRState *irs) { LLType *retType = @@ -303,6 +505,28 @@ llvm::LandingPadInst *createLandingPadInst(IRState *irs) { } } +#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(); diff --git a/ir/irfunction.h b/ir/irfunction.h index 32adf45353e..cbd751d2dd8 100644 --- a/ir/irfunction.h +++ b/ir/irfunction.h @@ -98,6 +98,9 @@ struct CleanupExitTarget { /// stores to the branch selector variable when converting from one to two /// targets. std::vector sourceBlocks; + + /// MSVC: The basic blocks that are executed when going this route + std::vector cleanupBlocks; }; /// Represents a scope (in abstract terms, not curly braces) that requires a @@ -148,6 +151,10 @@ class CleanupScope { /// and popped again once it is left. If the corresponding landing pad has /// not been generated yet (this is done lazily), the pointer is null. std::vector landingPads; + + /// MSVC: The original basic blocks that are executed for beginBlock to + /// endBlock + std::vector cleanupBlocks; }; /// Stores information to be able to branch to a catch clause if it matches. @@ -210,6 +217,13 @@ class ScopeStack { /// reached. void runAllCleanups(llvm::BasicBlock *continueWith); +#if LDC_LLVM_VER >= 308 + void runCleanupCopies(CleanupCursor sourceScope, CleanupCursor targetScope, + llvm::BasicBlock *continueWith); + llvm::BasicBlock *runCleanupPad(CleanupCursor scope, + llvm::BasicBlock *unwindTo); +#endif + /// Pops all the cleanups between the current scope and the target cursor. /// /// This does not insert any cleanup calls, use #runCleanups() beforehand. @@ -235,6 +249,28 @@ class ScopeStack { /// Unregisters the last registered catch block. void popCatch(); + size_t currentCatchScope() { return catchScopes.size(); } + +#if LDC_LLVM_VER >= 308 + /// MSVC: catch and cleanup code is emitted as funclets and need + /// to be referenced from inner pads and calls + void pushFunclet(llvm::Value *funclet) { + funclets.push_back(funclet); + } + + void popFunclet() { + funclets.pop_back(); + } + + llvm::Value *getFunclet() { + return funclets.empty() ? nullptr : funclets.back(); + } + llvm::Value *getFuncletToken() { + return funclets.empty() ? llvm::ConstantTokenNone::get(irs->context()) + : funclets.back(); + } +#endif + /// Registers a loop statement to be used as a target for break/continue /// statements in the current scope. void pushLoopTarget(Statement *loopStatement, @@ -295,6 +331,9 @@ class ScopeStack { /// the way there. void breakToClosest() { jumpToClosest(breakTargets); } + /// get exisiting or emit new landing pad + llvm::BasicBlock *getLandingPad(); + private: /// Internal version that allows specifying the scope at which to start /// emitting the cleanups. @@ -305,9 +344,15 @@ class ScopeStack { std::vector ¤tLandingPads(); + 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); @@ -347,6 +392,9 @@ class ScopeStack { /// (null if not yet emitted, one element is pushed to/popped from the back /// on entering/leaving a catch block). std::vector topLevelLandingPads; + + /// MSVC: stack of currently built catch/cleanup funclets + std::vector funclets; }; template @@ -360,31 +408,35 @@ llvm::CallSite ScopeStack::callOrInvoke(llvm::Value *callee, const T &args, const bool doesNotThrow = calleeFn && (calleeFn->isIntrinsic() || calleeFn->doesNotThrow()); +#if LDC_LLVM_VER >= 308 + // calls inside a funclet must be annotated with its value + llvm::SmallVector BundleList; + if (auto funclet = getFunclet()) + BundleList.push_back(llvm::OperandBundleDef("funclet", funclet)); +#endif + if (doesNotThrow || (cleanupScopes.empty() && catchScopes.empty())) { - llvm::CallInst *call = irs->ir->CreateCall(callee, args, name); + llvm::CallInst *call = irs->ir->CreateCall(callee, args, +#if LDC_LLVM_VER >= 308 + BundleList, +#endif + name); if (calleeFn) { call->setAttributes(calleeFn->getAttributes()); } return call; } - if (currentLandingPads().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. - currentLandingPads().push_back(nullptr); - } - - llvm::BasicBlock *&landingPad = currentLandingPads().back(); - if (!landingPad) { - landingPad = emitLandingPad(); - } + llvm::BasicBlock* landingPad = getLandingPad(); llvm::BasicBlock *postinvoke = llvm::BasicBlock::Create( irs->context(), "postinvoke", irs->topfunc(), landingPad); llvm::InvokeInst *invoke = - irs->ir->CreateInvoke(callee, postinvoke, landingPad, args, name); - + irs->ir->CreateInvoke(callee, postinvoke, landingPad, args, +#if LDC_LLVM_VER >= 308 + BundleList, +#endif + name); if (calleeFn) { invoke->setAttributes(calleeFn->getAttributes()); } @@ -474,5 +526,6 @@ struct IrFunction { IrFunction *getIrFunc(FuncDeclaration *decl, bool create = false); bool isIrFuncCreated(FuncDeclaration *decl); +bool useMSVCEH(); #endif