diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index d3fad58fcf5..77153f85a93 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -1764,6 +1764,7 @@ public: class LabelStmt : public ValueStmt { LabelDecl *TheDecl; Stmt *SubStmt; + bool SideEntry = false; public: /// Build a label statement. @@ -1799,6 +1800,9 @@ public: static bool classof(const Stmt *T) { return T->getStmtClass() == LabelStmtClass; } + + bool isSideEntry() const { return SideEntry; } + void setSideEntry(bool SE) { SideEntry = SE; } }; /// Represents an attribute applied to a statement. diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 70f68d664bb..1e87b455db8 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -128,6 +128,7 @@ LANGOPT(ZVector , 1, 0, "System z vector extensions") LANGOPT(Exceptions , 1, 0, "exception handling") LANGOPT(ObjCExceptions , 1, 0, "Objective-C exceptions") LANGOPT(CXXExceptions , 1, 0, "C++ exceptions") +LANGOPT(EHAsynch , 1, 0, "C/C++ EH Asynch exceptions") LANGOPT(DWARFExceptions , 1, 0, "dwarf exception handling") LANGOPT(SjLjExceptions , 1, 0, "setjmp-longjump exception handling") LANGOPT(SEHExceptions , 1, 0, "SEH .xdata exception handling") diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 966cb907b7e..dc7b38c254f 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -889,6 +889,8 @@ def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group, Group; defm cxx_exceptions: OptInFFlag<"cxx-exceptions", "Enable C++ exceptions">; +def feh_asynch: Flag<["-"], "feh-asynch">, Group, + HelpText<"Enable EH Asynchronous exceptions">, Flags<[CC1Option]>; def fcxx_modules : Flag <["-"], "fcxx-modules">, Group, Flags<[DriverOption]>; def fdebug_pass_arguments : Flag<["-"], "fdebug-pass-arguments">, Group; @@ -1505,6 +1507,7 @@ def fno_common : Flag<["-"], "fno-common">, Group, Flags<[CC1Option]>, def fno_constant_cfstrings : Flag<["-"], "fno-constant-cfstrings">, Group, Flags<[CC1Option]>, HelpText<"Disable creation of CodeFoundation-type constant strings">; +def fno_eh_asynch: Flag<["-"], "fno-eh-asynch">, Group; def fno_cxx_modules : Flag <["-"], "fno-cxx-modules">, Group, Flags<[DriverOption]>; def fno_diagnostics_fixit_info : Flag<["-"], "fno-diagnostics-fixit-info">, Group, diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp index 4bd00ece86a..7cc6089b711 100644 --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -1444,6 +1444,7 @@ void JSONNodeDumper::VisitCaseStmt(const CaseStmt *CS) { void JSONNodeDumper::VisitLabelStmt(const LabelStmt *LS) { JOS.attribute("name", LS->getName()); JOS.attribute("declId", createPointerRepresentation(LS->getDecl())); + attributeOnlyIfTrue("sideEntry", LS->isSideEntry()); } void JSONNodeDumper::VisitGotoStmt(const GotoStmt *GS) { JOS.attribute("targetLabelDeclId", diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index 5b0a0ac392c..0d5e9418fb7 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -916,6 +916,8 @@ void TextNodeDumper::VisitWhileStmt(const WhileStmt *Node) { void TextNodeDumper::VisitLabelStmt(const LabelStmt *Node) { OS << " '" << Node->getName() << "'"; + if (Node->isSideEntry()) + OS << " side_entry"; } void TextNodeDumper::VisitGotoStmt(const GotoStmt *Node) { diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp index ad543ef86c1..3b69bb20b06 100644 --- a/clang/lib/CodeGen/CGCleanup.cpp +++ b/clang/lib/CodeGen/CGCleanup.cpp @@ -194,6 +194,11 @@ void *EHScopeStack::pushCleanup(CleanupKind Kind, size_t Size) { if (IsLifetimeMarker) Scope->setLifetimeMarker(); + // With Windows -EHa, Invoke llvm.seh.scope.begin() for EHCleanup + if (CGF->getLangOpts().EHAsynch && IsEHCleanup && + CGF->getTarget().getCXXABI().isMicrosoft()) + CGF->EmitSehCppScopeBegin(); + return Scope->getCleanupBuffer(); } @@ -759,14 +764,31 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) { if (Scope.isEHCleanup()) cleanupFlags.setIsEHCleanupKind(); + // Under -EHa, invoke seh.scope.end() to mark scope end before dtor + bool IsEHa = getLangOpts().EHAsynch && !Scope.isLifetimeMarker(); + const EHPersonality& Personality = EHPersonality::get(*this); if (!RequiresNormalCleanup) { + // Mark CPP scope end for passed-by-value Arg temp + // per Windows ABI which is "normally" Cleanup in callee + if (IsEHa && getInvokeDest()) { + if (Personality.isMSVCXXPersonality()) + EmitSehCppScopeEnd(); + } destroyOptimisticNormalEntry(*this, Scope); EHStack.popCleanup(); } else { // If we have a fallthrough and no other need for the cleanup, // emit it directly. - if (HasFallthrough && !HasPrebranchedFallthrough && - !HasFixups && !HasExistingBranches) { + if (HasFallthrough && !HasPrebranchedFallthrough && !HasFixups && + !HasExistingBranches) { + + // mark SEH scope end for fall-through flow + if (IsEHa && getInvokeDest()) { + if (Personality.isMSVCXXPersonality()) + EmitSehCppScopeEnd(); + else + EmitSehTryScopeEnd(); + } destroyOptimisticNormalEntry(*this, Scope); EHStack.popCleanup(); @@ -801,6 +823,14 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) { // should already be branched to it. EmitBlock(NormalEntry); + // intercept normal cleanup to mark SEH scope end + if (IsEHa) { + if (Personality.isMSVCXXPersonality()) + EmitSehCppScopeEnd(); + else + EmitSehTryScopeEnd(); + } + // III. Figure out where we're going and build the cleanup // epilogue. @@ -1248,11 +1278,18 @@ void CodeGenFunction::DeactivateCleanupBlock(EHScopeStack::stable_iterator C, // to the current RunCleanupsScope. if (C == EHStack.stable_begin() && CurrentCleanupScopeDepth.strictlyEncloses(C)) { - // If it's a normal cleanup, we need to pretend that the - // fallthrough is unreachable. - CGBuilderTy::InsertPoint SavedIP = Builder.saveAndClearIP(); - PopCleanupBlock(); - Builder.restoreIP(SavedIP); + // Per comment below, checking EHAsynch is not really necessary + // it's there to assure zero-impact w/o EHAsynch option + if (!Scope.isNormalCleanup() && getLangOpts().EHAsynch) + PopCleanupBlock(); + else + { + // If it's a normal cleanup, we need to pretend that the + // fallthrough is unreachable. + CGBuilderTy::InsertPoint SavedIP = Builder.saveAndClearIP(); + PopCleanupBlock(); + Builder.restoreIP(SavedIP); + } return; } @@ -1276,3 +1313,60 @@ void CodeGenFunction::EmitCXXTemporary(const CXXTemporary *Temporary, pushDestroy(NormalAndEHCleanup, Ptr, TempType, destroyCXXObject, /*useEHCleanup*/ true); } + +// Need to set "funclet" in OperandBundle properly for noThrow +// intrinsic (see CGCall.cpp) +static void EmitSehScope(CodeGenFunction &CGF, + llvm::FunctionCallee &SehCppScope) { + llvm::BasicBlock *InvokeDest = CGF.getInvokeDest(); + llvm::BasicBlock *BB = CGF.Builder.GetInsertBlock(); + assert(BB && InvokeDest); + llvm::BasicBlock *Cont = CGF.createBasicBlock("invoke.cont"); + SmallVector BundleList = + CGF.getBundlesForFunclet(SehCppScope.getCallee()); + if (CGF.CurrentFuncletPad) + BundleList.emplace_back("funclet", CGF.CurrentFuncletPad); + CGF.Builder.CreateInvoke(SehCppScope, Cont, InvokeDest, None, BundleList); + CGF.EmitBlock(Cont); +} + +// Invoke a llvm.seh.scope.begin at the beginning of a CPP scope for -EHa +void CodeGenFunction::EmitSehCppScopeBegin() { + assert(getLangOpts().EHAsynch); + llvm::FunctionType *FTy = + llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + llvm::FunctionCallee SehCppScope = + CGM.CreateRuntimeFunction(FTy, "llvm.seh.scope.begin"); + EmitSehScope(*this, SehCppScope); +} + +// Invoke a llvm.seh.scope.end at the end of a CPP scope for -EHa +// llvm.seh.scope.end is emitted before popCleanup, so it's "invoked" +void CodeGenFunction::EmitSehCppScopeEnd() { + assert(getLangOpts().EHAsynch); + llvm::FunctionType *FTy = + llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + llvm::FunctionCallee SehCppScope = + CGM.CreateRuntimeFunction(FTy, "llvm.seh.scope.end"); + EmitSehScope(*this, SehCppScope); +} + +// Invoke a llvm.seh.try.begin at the beginning of a SEH scope for -EHa +void CodeGenFunction::EmitSehTryScopeBegin() { + assert(getLangOpts().EHAsynch); + llvm::FunctionType* FTy = + llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + llvm::FunctionCallee SehCppScope = + CGM.CreateRuntimeFunction(FTy, "llvm.seh.try.begin"); + EmitSehScope(*this, SehCppScope); +} + +// Invoke a llvm.seh.try.end at the end of a SEH scope for -EHa +void CodeGenFunction::EmitSehTryScopeEnd() { + assert(getLangOpts().EHAsynch); + llvm::FunctionType* FTy = + llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + llvm::FunctionCallee SehCppScope = + CGM.CreateRuntimeFunction(FTy, "llvm.seh.try.end"); + EmitSehScope(*this, SehCppScope); +} \ No newline at end of file diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index bdf70252b5a..a4e4d7d8120 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -39,6 +39,18 @@ static llvm::FunctionCallee getFreeExceptionFn(CodeGenModule &CGM) { return CGM.CreateRuntimeFunction(FTy, "__cxa_free_exception"); } +static llvm::FunctionCallee getSehTryBeginFn(CodeGenModule &CGM) { + llvm::FunctionType *FTy = + llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + return CGM.CreateRuntimeFunction(FTy, "llvm.seh.try.begin"); +} + +static llvm::FunctionCallee getSehTryEndFn(CodeGenModule &CGM) { + llvm::FunctionType *FTy = + llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + return CGM.CreateRuntimeFunction(FTy, "llvm.seh.try.end"); +} + static llvm::FunctionCallee getUnexpectedFn(CodeGenModule &CGM) { // void __cxa_call_unexpected(void *thrown_exception); @@ -451,7 +463,7 @@ void CodeGenFunction::EmitStartEHSpec(const Decl *D) { if (!FD) { // Check if CapturedDecl is nothrow and create terminate scope for it. if (const CapturedDecl* CD = dyn_cast_or_null(D)) { - if (CD->isNothrow()) + if (CD->isNothrow() && !getLangOpts().EHAsynch /* !IsEHa */) EHStack.pushTerminate(); } return; @@ -463,7 +475,8 @@ void CodeGenFunction::EmitStartEHSpec(const Decl *D) { ExceptionSpecificationType EST = Proto->getExceptionSpecType(); if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot) { // noexcept functions are simple terminate scopes. - EHStack.pushTerminate(); + if (!getLangOpts().EHAsynch) // -EHa: HW exception still can occur + EHStack.pushTerminate(); } else if (EST == EST_Dynamic || EST == EST_DynamicNone) { // TODO: Revisit exception specifications for the MS ABI. There is a way to // encode these in an object file but MSVC doesn't do anything with it. @@ -540,7 +553,7 @@ void CodeGenFunction::EmitEndEHSpec(const Decl *D) { if (!FD) { // Check if CapturedDecl is nothrow and pop terminate scope for it. if (const CapturedDecl* CD = dyn_cast_or_null(D)) { - if (CD->isNothrow()) + if (CD->isNothrow() && !EHStack.empty()) EHStack.popTerminate(); } return; @@ -550,7 +563,8 @@ void CodeGenFunction::EmitEndEHSpec(const Decl *D) { return; ExceptionSpecificationType EST = Proto->getExceptionSpecType(); - if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot) { + if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot && + !EHStack.empty() /* possible empty when -EHa */) { EHStack.popTerminate(); } else if (EST == EST_Dynamic || EST == EST_DynamicNone) { // TODO: Revisit exception specifications for the MS ABI. There is a way to @@ -606,6 +620,9 @@ void CodeGenFunction::EnterCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) { } else { // No exception decl indicates '...', a catch-all. CatchScope->setHandler(I, CGM.getCXXABI().getCatchAllTypeInfo(), Handler); + // Mark scope with SehTryBegin + if (getLangOpts().EHAsynch) + EmitRuntimeCallOrInvoke(getSehTryBeginFn(CGM)); } } } @@ -720,7 +737,7 @@ llvm::BasicBlock *CodeGenFunction::getInvokeDestImpl() { // If exceptions are disabled/ignored and SEH is not in use, then there is no // invoke destination. SEH "works" even if exceptions are off. In practice, // this means that C++ destructors and other EH cleanups don't run, which is - // consistent with MSVC's behavior. + // consistent with MSVC's behavior, except in the presence of -EHa const LangOptions &LO = CGM.getLangOpts(); if (!LO.Exceptions || LO.IgnoreExceptions) { if (!LO.Borland && !LO.MicrosoftExt) @@ -1610,7 +1627,23 @@ void CodeGenFunction::EmitSEHTryStmt(const SEHTryStmt &S) { JumpDest TryExit = getJumpDestInCurrentScope("__try.__leave"); SEHTryEpilogueStack.push_back(&TryExit); + + llvm::BasicBlock *TryBB = nullptr; + // IsEHa: emit an invoke to _seh_try_begin() runtime for -EHa + if (getLangOpts().EHAsynch) { + EmitRuntimeCallOrInvoke(getSehTryBeginFn(CGM)); + if (SEHTryEpilogueStack.size() == 1) // outermost only + TryBB = Builder.GetInsertBlock(); + } + EmitStmt(S.getTryBlock()); + + // Volatilize all blocks in Try, till current insert point + if (TryBB) { + llvm::SmallPtrSet Visited; + VolatilizeTryBlocks(TryBB, Visited); + } + SEHTryEpilogueStack.pop_back(); if (!TryExit.getBlock()->use_empty()) @@ -1621,6 +1654,38 @@ void CodeGenFunction::EmitSEHTryStmt(const SEHTryStmt &S) { ExitSEHTryStmt(S); } +// Recursively walk through blocks in a _try +// and make all memory instructions volatile +void CodeGenFunction::VolatilizeTryBlocks( + llvm::BasicBlock *BB, llvm::SmallPtrSet &V) { + if (BB == SEHTryEpilogueStack.back()->getBlock() /* end of Try */ || + !V.insert(BB).second /* already visited */ || + !BB->getParent() /* not emitted */ || BB->empty()) + return; + + if (!BB->isEHPad()) { + for (llvm::BasicBlock::iterator J = BB->begin(), JE = BB->end(); J != JE; + ++J) { + if (isa(J)) { + auto LI = cast(J); + LI->setVolatile(true); + } else if (isa(J)) { + auto SI = cast(J); + SI->setVolatile(true); + } else if (isa(J)) { + auto *MCI = cast(J); + MCI->setVolatile(llvm::ConstantInt::get(Builder.getInt1Ty(), 1)); + } + } + } + const llvm::Instruction *TI = BB->getTerminator(); + if (TI) { + unsigned N = TI->getNumSuccessors(); + for (unsigned I = 0; I < N; I++) + VolatilizeTryBlocks(TI->getSuccessor(I), V); + } +} + namespace { struct PerformSEHFinally final : EHScopeStack::Cleanup { llvm::Function *OutlinedFinally; @@ -2101,6 +2166,12 @@ void CodeGenFunction::ExitSEHTryStmt(const SEHTryStmt &S) { return; } + // IsEHa: emit an invoke _seh_try_end() to mark end of FT flow + if (getLangOpts().EHAsynch && Builder.GetInsertBlock()) { + llvm::FunctionCallee SehTryEnd = getSehTryEndFn(CGM); + EmitRuntimeCallOrInvoke(SehTryEnd); + } + // Otherwise, we must have an __except block. const SEHExceptStmt *Except = S.getExceptHandler(); assert(Except && "__try must have __finally xor __except"); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 672909849bb..32dca08b8cf 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -606,6 +606,11 @@ void CodeGenFunction::LexicalScope::rescopeLabels() { void CodeGenFunction::EmitLabelStmt(const LabelStmt &S) { EmitLabel(S.getDecl()); + + // IsEHa - emit eha.scope.begin if it's a side entry of a scope + if (getLangOpts().EHAsynch && S.isSideEntry()) + EmitSehCppScopeBegin(); + EmitStmt(S.getSubStmt()); } diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 8ce488f35dd..4cc8304c455 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -71,6 +71,7 @@ CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext) shouldEmitLifetimeMarkers(CGM.getCodeGenOpts(), CGM.getLangOpts())) { if (!suppressNewContext) CGM.getCXXABI().getMangleContext().startNewFunction(); + EHStack.setCGF(this); SetFastMathFlags(CurFPFeatures); SetFPModel(); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index d794f4f0fa8..1b5892c7459 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -2784,6 +2784,11 @@ public: void EmitCXXTemporary(const CXXTemporary *Temporary, QualType TempType, Address Ptr); + void EmitSehCppScopeBegin(); + void EmitSehCppScopeEnd(); + void EmitSehTryScopeBegin(); + void EmitSehTryScopeEnd(); + llvm::Value *EmitLifetimeStart(uint64_t Size, llvm::Value *Addr); void EmitLifetimeEnd(llvm::Value *Size, llvm::Value *Addr); @@ -3134,6 +3139,8 @@ public: void EmitSEHLeaveStmt(const SEHLeaveStmt &S); void EnterSEHTryStmt(const SEHTryStmt &S); void ExitSEHTryStmt(const SEHTryStmt &S); + void VolatilizeTryBlocks(llvm::BasicBlock *BB, + llvm::SmallPtrSet &V); void pushSEHCleanup(CleanupKind kind, llvm::Function *FinallyFunc); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 4ae8ce7e5cc..5512c8fe88a 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -593,6 +593,9 @@ void CodeGenModule::Release() { llvm::DenormalMode::IEEE); } + if (LangOpts.EHAsynch) + getModule().addModuleFlag(llvm::Module::Warning, "eh-asynch", 1); + // Emit OpenCL specific module metadata: OpenCL/SPIR version. if (LangOpts.OpenCL) { EmitOpenCLMetadata(); diff --git a/clang/lib/CodeGen/EHScopeStack.h b/clang/lib/CodeGen/EHScopeStack.h index 3a640d6117d..60fe9b3c9dd 100644 --- a/clang/lib/CodeGen/EHScopeStack.h +++ b/clang/lib/CodeGen/EHScopeStack.h @@ -236,6 +236,9 @@ private: /// The innermost EH scope on the stack. stable_iterator InnermostEHScope; + /// The CGF this Stack belong to + CodeGenFunction* CGF; + /// The current set of branch fixups. A branch fixup is a jump to /// an as-yet unemitted label, i.e. a label for which we don't yet /// know the EH stack depth. Whenever we pop a cleanup, we have @@ -263,7 +266,7 @@ private: public: EHScopeStack() : StartOfBuffer(nullptr), EndOfBuffer(nullptr), StartOfData(nullptr), InnermostNormalCleanup(stable_end()), - InnermostEHScope(stable_end()) {} + InnermostEHScope(stable_end()), CGF(nullptr) {} ~EHScopeStack() { delete[] StartOfBuffer; } /// Push a lazily-created cleanup on the stack. @@ -311,6 +314,8 @@ public: std::memcpy(Buffer, Cleanup, Size); } + void setCGF(CodeGenFunction* inCGF) { CGF = inCGF; } + /// Pops a cleanup scope off the stack. This is private to CGCleanup.cpp. void popCleanup(); diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 45c6cb6b2e0..a080c93356a 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -131,7 +131,12 @@ public: /// MSVC needs an extra flag to indicate a catchall. CatchTypeInfo getCatchAllTypeInfo() override { - return CatchTypeInfo{nullptr, 0x40}; + // For -EHa catch(...) must handle HW exception + // Adjective = HT_IsStdDotDot (0x40), only catch C++ exceptions + if (getContext().getLangOpts().EHAsynch) + return CatchTypeInfo{nullptr, 0}; + else + return CatchTypeInfo{nullptr, 0x40}; } bool shouldTypeidBeNullChecked(bool IsDeref, QualType SrcRecordTy) override; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index af4bcf951e6..71112783ac0 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -415,6 +415,7 @@ static void addExceptionArgs(const ArgList &Args, types::ID InputType, Args.ClaimAllArgs(options::OPT_fobjc_exceptions); Args.ClaimAllArgs(options::OPT_fno_objc_exceptions); Args.ClaimAllArgs(options::OPT_fcxx_exceptions); + Args.ClaimAllArgs(options::OPT_feh_asynch); Args.ClaimAllArgs(options::OPT_fno_cxx_exceptions); return; } @@ -423,6 +424,13 @@ static void addExceptionArgs(const ArgList &Args, types::ID InputType, bool EH = Args.hasFlag(options::OPT_fexceptions, options::OPT_fno_exceptions, false); + bool EHa = + Args.hasFlag(options::OPT_feh_asynch, options::OPT_fno_eh_asynch, false); + if (EHa) { + CmdArgs.push_back("-feh-asynch"); + EH = true; + } + // Obj-C exceptions are enabled by default, regardless of -fexceptions. This // is not necessarily sensible, but follows GCC. if (types::isObjC(InputType) && @@ -6581,7 +6589,10 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType, if (types::isCXX(InputType)) CmdArgs.push_back("-fcxx-exceptions"); CmdArgs.push_back("-fexceptions"); + if (EH.Asynch) + CmdArgs.push_back("-feh-asynch"); } + if (types::isCXX(InputType) && EH.Synch && EH.NoUnwindC) CmdArgs.push_back("-fexternc-nounwind"); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 73114c6d76c..b58af0b68e7 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2819,6 +2819,7 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK, Opts.IgnoreExceptions = Args.hasArg(OPT_fignore_exceptions); Opts.ObjCExceptions = Args.hasArg(OPT_fobjc_exceptions); Opts.CXXExceptions = Args.hasArg(OPT_fcxx_exceptions); + Opts.EHAsynch = Args.hasArg(OPT_feh_asynch); // -ffixed-point Opts.FixedPoint = diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index b34243edea3..304f059a032 100644 --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -930,6 +930,9 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc, if (!ToScopesWarning.empty()) { S.Diag(DiagLoc, JumpDiagWarning); NoteJumpIntoScopes(ToScopesWarning); + assert(isa(To)); + LabelStmt *Label = cast(To); + Label->setSideEntry(true); } // Handle errors. diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index a40c5499a6d..bfed22a1252 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -185,11 +185,13 @@ void ASTStmtReader::VisitDefaultStmt(DefaultStmt *S) { void ASTStmtReader::VisitLabelStmt(LabelStmt *S) { VisitStmt(S); + bool IsSideEntry = Record.readInt(); auto *LD = readDeclAs(); LD->setStmt(S); S->setDecl(LD); S->setSubStmt(Record.readSubStmt()); S->setIdentLoc(readSourceLocation()); + S->setSideEntry(IsSideEntry); } void ASTStmtReader::VisitAttributedStmt(AttributedStmt *S) { diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 0767b3a24bf..614fd45d193 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -115,6 +115,7 @@ void ASTStmtWriter::VisitDefaultStmt(DefaultStmt *S) { void ASTStmtWriter::VisitLabelStmt(LabelStmt *S) { VisitStmt(S); + Record.push_back(S->isSideEntry()); Record.AddDeclRef(S->getDecl()); Record.AddStmt(S->getSubStmt()); Record.AddSourceLocation(S->getIdentLoc()); diff --git a/clang/test/CodeGen/windows-seh-EHa-CppCatchDotDotDot.cpp b/clang/test/CodeGen/windows-seh-EHa-CppCatchDotDotDot.cpp new file mode 100644 index 00000000000..2928a610d3c --- /dev/null +++ b/clang/test/CodeGen/windows-seh-EHa-CppCatchDotDotDot.cpp @@ -0,0 +1,58 @@ +// RUN: %clang_cc1 -triple x86_64-windows -feh-asynch -fcxx-exceptions -fexceptions -fms-extensions -x c++ -Wno-implicit-function-declaration -S -emit-llvm %s -o - | FileCheck %s + +// CHECK: define dso_local void @"?crash@@YAXH@Z +// CHECK: invoke void @llvm.seh.try.begin() +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() + +// CHECK: %[[dst:[0-9-]+]] = catchswitch within none [label %catch] unwind to caller +// CHECK: %[[dst1:[0-9-]+]] = catchpad within %[[dst]] [i8* null, i32 0, i8* null] +// CHECK: "funclet"(token %[[dst1]]) + +// CHECK: invoke void @llvm.seh.try.begin() +// CHECK: %[[src:[0-9-]+]] = load volatile i32, i32* %i +// CHECK-NEXT: invoke void @"?crash@@YAXH@Z"(i32 %[[src]]) +// CHECK: invoke void @llvm.seh.try.end() + +// ***************************************************************************** +// Abstract: Test CPP catch(...) under SEH -EHa option + +void printf(...); +int volatile *NullPtr = 0; +void foo() { + *NullPtr = 0; +} +int *pt1, *pt2, *pt3; +int g; +void crash(int i) { + g = i; + try { + struct A { + A() { + printf(" in A ctor \n"); + if (g == 0) + *NullPtr = 0; + } + ~A() { + printf(" in A dtor \n"); + } + } ObjA; + if (i == 1) + *NullPtr = 0; + } catch (...) { + printf(" in catch(...) funclet \n"); + if (i == 1) + throw(i); + } +} + +int main() { + for (int i = 0; i < 2; i++) { + __try { + crash(i); + } __except (1) { + printf(" Test CPP unwind: in except handler i = %d \n", i); + } + } + return 0; +} diff --git a/clang/test/CodeGen/windows-seh-EHa-CppCondiTemps.cpp b/clang/test/CodeGen/windows-seh-EHa-CppCondiTemps.cpp new file mode 100644 index 00000000000..d588d6e2292 --- /dev/null +++ b/clang/test/CodeGen/windows-seh-EHa-CppCondiTemps.cpp @@ -0,0 +1,129 @@ +// RUN: %clang_cc1 -triple x86_64-windows -feh-asynch -fcxx-exceptions -fexceptions -fms-extensions -x c++ -Wno-implicit-function-declaration -S -emit-llvm %s -o - | FileCheck %s + +// CHECK: define dso_local i32 @"?bar@@YAHHVB1@@VB2@@@Z" +// CHECK: %coerce.dive1 = getelementptr inbounds %class.B2 +// CHECK: %coerce.dive2 = getelementptr inbounds %class.B1 +// ----- scope begin of two passed-by-value temps +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: call void @"??1B1@@QEAA@XZ" +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: call void @"??1B2@@QEAA@XZ" + +// CHECK: define linkonce_odr dso_local void @"??1B2@@QEAA@XZ" +// CHECK: %this.addr = alloca %class.B2* +// ----- B1 scope begin without base ctor +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: call void @"??1B1@@QEAA@XZ" + +// CHECK: define dso_local void @"?goo@@YA?AVB1@@H@Z" +// CHECK: call %class.B2* @"??0B2@@QEAA@XZ"(%class.B2* %b2ingoo) +// CHECK: invoke void @llvm.seh.scope.begin() +// check: call void @llvm.memcpy +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: call void @"??1B2@@QEAA@XZ"(%class.B2* %b2ingoo) + +// CHECK: define linkonce_odr dso_local %class.B2* @"??0B2@@QEAA@XZ" +// CHECK: call %class.B1* @"??0B1@@QEAA@XZ"(%class.B1* +// ----- scope begin of base ctor +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() +// ----- B1 scope end without base dtor + +// **************************************************************************** +// Abstract: Test CPP Conditional-Expr & ABI Temps under SEH -EHa option + +void printf(...); + +int xxxx = 0; +int* ptr; + +int foo(int a) +{ + return xxxx + a; +} + +class B1 { +public: + int data = 90; + B1() { foo(data + 111); } + ~B1() { printf("in B1 Dtor \n"); } +}; +class B2 : public B1 { +public: + B2() { foo(data + 222); } + ~B2() { printf("in B2 Dtor \n");; } +}; +class B3 : public B2 { +public: + B3() { foo(data + 333); } + ~B3() { printf("in B3 Dtor \n");; } +}; + +int bar(int j, class B1 b1Bar, class B2 b2Bar) +{ + int ww; + if ( j > 0) + ww = b1Bar.data; + else + ww = b2Bar.data; + return ww + *ptr; +} + +class B1 goo(int w) +{ + class B2 b2ingoo; + b2ingoo.data += w; + return b2ingoo; +} + +// CHECK: define dso_local i32 @main() +// CHECK: invoke void @llvm.seh.scope.begin() +// --- beginning of conditional temp test +// CHECK: invoke %class.B2* @"??0B2@@QEAA@XZ" +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke %class.B3* @"??0B3@@QEAA@XZ" +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: call void @"??1B3@@QEAA@XZ" +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: call void @"??1B2@@QEAA@XZ" +// ----- end of conditional temp test + +// ----- testing caller's passed-by-value temps +// setting scope in case exception occurs before the call +// check: invoke %class.B2* @"??0B2@@QEAA@XZ" +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke %class.B1* @"??0B1@@QEAA@XZ" +// CHECK: invoke void @llvm.seh.scope.begin() +// ----- end of temps' scope right before callee +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: invoke i32 @"?bar@@YAHHVB1@@VB2@@@Z" + +// ----- testing caller's return-by-value temp +// scope begins right after callee which is the ctor of return temp +// CHECK: void @"?goo@@YA?AVB1@@H@Z" +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() + +int main() { + class B3 b3inmain; + + // Test conditional ctor and dtor + int m = (xxxx > 1) ? B2().data + foo(99) : + B3().data + foo(88); + + // Test: passed-in by value + // Per Windows ABI, ctored by caller, dtored by callee + int i = bar(foo(0), B1(), B2()); + + // Test: returned by value + // Per Windows ABI, caller allocate a temp in stack, then ctored by callee, + // finally dtored in caller after consumed + class B1 b1fromgoo = goo(i); + + return m + b1fromgoo.data + b3inmain.data; +} \ No newline at end of file diff --git a/clang/test/CodeGen/windows-seh-EHa-CppDtors01.cpp b/clang/test/CodeGen/windows-seh-EHa-CppDtors01.cpp new file mode 100644 index 00000000000..8fdb1e889c4 --- /dev/null +++ b/clang/test/CodeGen/windows-seh-EHa-CppDtors01.cpp @@ -0,0 +1,60 @@ +// RUN: %clang_cc1 -triple x86_64-windows -feh-asynch -fcxx-exceptions -fexceptions -fms-extensions -x c++ -Wno-implicit-function-declaration -S -emit-llvm %s -o - | FileCheck %s + +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.begin() +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: invoke void @llvm.seh.scope.end() +// CHECK: invoke void @llvm.seh.scope.end() + +// CHECK: invoke void @llvm.seh.try.begin() +// CHECK: %[[src:[0-9-]+]] = load volatile i32, i32* %i +// CHECK-NEXT: invoke void @"?crash@@YAXH@Z"(i32 %[[src]]) +// CHECK: invoke void @llvm.seh.try.end() + +// **************************************************************************** +// Abstract: Test CPP unwind Dtoring under SEH -EHa option + +void printf(...); +int volatile *NullPtr = 0; +void crash(int i) { + struct A { + ~A() { + printf(" in A dtor \n"); + } + } ObjA; + if (i == 0) + *NullPtr = 0; + + struct B { + ~B() { + printf(" in B dtor \n"); + } + } ObjB; + if (i == 1) + *NullPtr = 0; + + struct C { + ~C() { + printf(" in C dtor \n"); + } + } ObjC; + if (i == 2) + *NullPtr = 0; +} + +#define TRY __try +#define CATCH_ALL __except (1) + +int g; +int main() { + for (int i = 0; i < 3; i++) { + TRY { + crash(i); + } + CATCH_ALL { + printf(" Test CPP unwind: in catch handler i = %d \n", i); + } + } + return 0; +} diff --git a/clang/test/CodeGen/windows-seh-EHa-TryInFinally.cpp b/clang/test/CodeGen/windows-seh-EHa-TryInFinally.cpp new file mode 100644 index 00000000000..3efd7d6505f --- /dev/null +++ b/clang/test/CodeGen/windows-seh-EHa-TryInFinally.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -triple x86_64-windows -feh-asynch -fcxx-exceptions -fexceptions -fms-extensions -x c++ -Wno-implicit-function-declaration -S -emit-llvm %s -o - | FileCheck %s + +// CHECK: invoke void @llvm.seh.try.begin() +// CHECK: invoke void @llvm.seh.try.begin() +// CHECK: %[[src:[0-9-]+]] = load volatile i32, i32* %i +// CHECK-NEXT: i32 %[[src]] +// CHECK: invoke void @llvm.seh.try.end() +// CHECK: invoke void @llvm.seh.try.end() + +// CHECK: define internal void @"?fin$0@0@main@@"(i8 %abnormal_termination +// CHECK: invoke void @llvm.seh.try.begin() +// CHECK: invoke void @llvm.seh.try.end() + +// ***************************************************************************** +// Abstract: Test __Try in __finally under SEH -EHa option +void printf(...); +int volatile *NullPtr = 0; +int main() { + for (int i = 0; i < 3; i++) { + printf(" --- Test _Try in _finally --- i = %d \n", i); + __try { + __try { + printf(" In outer _try i = %d \n", i); + if (i == 0) + *NullPtr = 0; + } __finally { + __try { + printf(" In outer _finally i = %d \n", i); + if (i == 1) + *NullPtr = 0; + } __finally { + printf(" In Inner _finally i = %d \n", i); + if (i == 2) + *NullPtr = 0; + } + } + } __except (1) { + printf(" --- In outer except handler i = %d \n", i); + } + } + return 0; +} diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 9b58b9dfb17..7ba418ab7d0 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -11642,6 +11642,66 @@ The '``llvm.localescape``' intrinsic blocks inlining, as inlining changes where the escaped allocas are allocated, which would break attempts to use '``llvm.localrecover``'. +'``llvm.seh.try.begin``' and '``llvm.seh.try.end``' Intrinsics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.seh.try.begin() + declare void @llvm.seh.try.end() + +Overview: +""""""""" + +The '``llvm.seh.try.begin``' and '``llvm.seh.try.end``' intrinsics mark +the boundary of a _try region for Windows SEH Asynchrous Exception Handling. + +Semantics: +"""""""""" + +When a C-function is compiled with Windows SEH Asynchrous Exception option, +-feh_asynch (aka MSVC -EHa), these two intrinsics are injected to mark _try +boundary and to prevent from potential exceptions being moved across boundary. + +'``llvm.seh.scope.begin``' and '``llvm.seh.scope.end``' Intrinsics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.seh.scope.begin() + declare void @llvm.seh.scope.end() + +Overview: +""""""""" + +The '``llvm.seh.scope.begin``' and '``llvm.seh.scope.end``' intrinsics mark +the boundary of a CPP object lifetime for Windows SEH Asynchrous Exception +Handling (MSVC option -EHa). + +Semantics: +"""""""""" + +LLVM's ordinary exception-handling representation associates EH cleanups and +handlers only with ``invoke``s, which normally correspond only to call sites. To +support arbitrary faulting instructions, it must be possible to recover the current +EH scope for any instruction. Turning every operation in LLVM that could fault +into an ``invoke`` of a new, potentially-throwing intrinsic would require adding a +large number of intrinsics, impede optimization of those operations, and make +compilation slower by introducing many extra basic blocks. These intrinsics can +be used instead to mark the region protected by a cleanup, such as for a local +C++ object with a non-trivial destructor. ``llvm.seh.scope.begin`` is used to mark +the start of the region; it is aways called with ``invoke``, with the unwind block +being the desired unwind destination for any potentially-throwing instructions +within the region. `llvm.seh.scope.end` is used to mark when the scope ends +and the EH cleanup is no longer required (e.g. because the destructor is being +called). + .. _int_read_register: .. _int_read_volatile_register: .. _int_write_register: diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 4918ea876df..0d33b64998d 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -481,6 +481,16 @@ def int_eh_recoverfp : Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty, llvm_ptr_ty], [IntrNoMem]>; +// To mark the beginning/end of a try-scope for Windows SEH -EHa +// calls/invokes to these intrinsics are placed to model control flows +// caused by HW exceptions under option -EHa. +// calls/invokes to these intrinsics will be discarded during a codegen pass +// after EH tables are generated +def int_seh_try_begin : Intrinsic<[], [], [IntrReadMem, IntrWriteMem, IntrWillReturn]>; +def int_seh_try_end : Intrinsic<[], [], [IntrReadMem, IntrWriteMem, IntrWillReturn]>; +def int_seh_scope_begin : Intrinsic<[], [], [IntrNoMem]>; +def int_seh_scope_end : Intrinsic<[], [], [IntrNoMem]>; + // Note: we treat stacksave/stackrestore as writemem because we don't otherwise // model their dependencies on allocas. def int_stacksave : Intrinsic<[llvm_ptr_ty]>, diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index d2930391f87..0e5a78f314b 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -2801,6 +2801,10 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) { llvm_unreachable("Cannot invoke this intrinsic"); case Intrinsic::donothing: // Ignore invokes to @llvm.donothing: jump directly to the next BB. + case Intrinsic::seh_try_begin: + case Intrinsic::seh_scope_begin: + case Intrinsic::seh_try_end: + case Intrinsic::seh_scope_end: break; case Intrinsic::experimental_patchpoint_void: case Intrinsic::experimental_patchpoint_i64: @@ -6635,6 +6639,10 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, lowerCallToExternalSymbol(I, FunctionName); return; case Intrinsic::donothing: + case Intrinsic::seh_try_begin: + case Intrinsic::seh_scope_begin: + case Intrinsic::seh_try_end: + case Intrinsic::seh_scope_end: // ignore return; case Intrinsic::experimental_stackmap: diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index c518ae87ea9..6fb5e60c34a 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -4269,6 +4269,10 @@ void Verifier::visitInstruction(Instruction &I) { Assert( !F->isIntrinsic() || isa(I) || F->getIntrinsicID() == Intrinsic::donothing || + F->getIntrinsicID() == Intrinsic::seh_try_begin || + F->getIntrinsicID() == Intrinsic::seh_try_end || + F->getIntrinsicID() == Intrinsic::seh_scope_begin || + F->getIntrinsicID() == Intrinsic::seh_scope_end || F->getIntrinsicID() == Intrinsic::coro_resume || F->getIntrinsicID() == Intrinsic::coro_destroy || F->getIntrinsicID() == Intrinsic::experimental_patchpoint_void ||