diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp index f49fbf8807bac..a4ebd992f621a 100644 --- a/llvm/lib/Transforms/Utils/InlineFunction.cpp +++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp @@ -21,6 +21,7 @@ #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/AssumptionCache.h" #include "llvm/Analysis/BlockFrequencyInfo.h" +#include "llvm/Analysis/CFG.h" #include "llvm/Analysis/CallGraph.h" #include "llvm/Analysis/CaptureTracking.h" #include "llvm/Analysis/CtxProfAnalysis.h" @@ -2210,6 +2211,147 @@ inlineRetainOrClaimRVCalls(CallBase &CB, objcarc::ARCInstKind RVCallKind, } } +struct SehScope { + llvm::InvokeInst *Begin; + llvm::InvokeInst *End; +}; + +// Determine SEH try scope begins and ends. +static SmallVector GetSehTryScopes(Function *Func) { + SmallVector Scopes{}; + DenseMap ScopeEnds{}; + + for (auto &BB : *Func) { + auto *TI = BB.getTerminator(); + if (auto *II = dyn_cast(TI)) { + auto *Call = cast(II); + const auto *Fn = Call->getCalledFunction(); + if (!Fn || !Fn->isIntrinsic()) + continue; + + if (Fn->getIntrinsicID() == Intrinsic::seh_try_end) { + ScopeEnds[II->getUnwindDest()] = II; + } else if (Fn->getIntrinsicID() == Intrinsic::seh_try_begin) { + Scopes.push_back({II, nullptr}); + } + } + } + + // Assign scope end to begin if the unwind destination matches. + for (auto &Scope : Scopes) { + auto ScopeEndIt = ScopeEnds.find(Scope.Begin->getUnwindDest()); + if (ScopeEndIt != ScopeEnds.end()) + Scope.End = ScopeEndIt->second; + } + + return Scopes; +} + +// Find, if present, the outermost unterminated try scope for the input block. +static llvm::InvokeInst * +GetOutermostUnterminatedTryScopeBegin(llvm::BasicBlock *ReturnBlock, + SmallVector &Scopes) { + llvm::InvokeInst *OutermostScope{nullptr}; + DominatorTree DT; + DT.recalculate(*ReturnBlock->getParent()); + + for (auto &Scope : Scopes) { + auto *Invoke = Scope.Begin; + + // Return might not be in a try scope. + if (!DT.dominates(Invoke->getParent(), ReturnBlock)) + continue; + + // If there is a catch which connects to the return, the try scope is + // considered to be terminated. + if (isPotentiallyReachable(Invoke->getUnwindDest(), ReturnBlock, nullptr, + &DT)) + continue; + + // For SEH there can be try scope ends on invokes and finally statements. + if (Scope.End && isPotentiallyReachable(Scope.End->getParent(), ReturnBlock, + nullptr, &DT)) + continue; + + // Keep the outermost scope if we match multiple ones. + if (OutermostScope) { + if (!DT.dominates(Invoke->getParent(), OutermostScope->getParent())) + continue; + } + + OutermostScope = Invoke; + } + + return OutermostScope; +} + +struct UnterminatedTryScope { + llvm::BasicBlock *ReturnBlock; + llvm::BasicBlock *TryStart; + llvm::BasicBlock *UnwindDestination; +}; + +// For Windows -EHa there may be the case of try scopes spanning over a return +// instruction. If no other return out of the function is given (e.g. by letting +// all other blocks terminate with unreachable) the try scope is considered +// unterminated upon inlining. To avoid leaking the try scopes over to a caller +// we need to add a terminator for the outermost unterminated try scope. +static SmallVector +GetUnterminatedTryScopes(Function *CalledFunc) { + SmallVector UnterminatedScopes; + auto Scopes = GetSehTryScopes(CalledFunc); + if (Scopes.empty()) + return UnterminatedScopes; + + SmallVector Returns; + for (auto &BB : *CalledFunc) + if (ReturnInst *RI = dyn_cast(BB.getTerminator())) + Returns.push_back(RI); + + for (auto *RI : Returns) { + auto *Block = RI->getParent(); + auto *OutermostScope = GetOutermostUnterminatedTryScopeBegin(Block, Scopes); + if (!OutermostScope) + continue; + + UnterminatedScopes.push_back( + {Block, OutermostScope->getParent(), OutermostScope->getUnwindDest()}); + } + + return UnterminatedScopes; +} + +// Insert terminator for unterminated try scopes. +static void HandleUnterminatedTryScopes( + Function *Caller, + SmallVector &UnterminatedTryScopes, + ValueToValueMapTy &VMap) { + for (auto &Scope : UnterminatedTryScopes) { + auto *ReturnBlock = cast(VMap[Scope.ReturnBlock]); + auto *TryStart = cast(VMap[Scope.TryStart]); + auto *UnwindDestination = cast(VMap[Scope.UnwindDestination]); + // did not survive (partial) inlining - ignore + if (!ReturnBlock || !TryStart || !UnwindDestination) + continue; + + auto *Mod = (llvm::Module *)Caller->getParent(); + auto *SehTryEndFn = + Intrinsic::getOrInsertDeclaration(Mod, Intrinsic::seh_try_end); + auto *BB = ReturnBlock->splitBasicBlockBefore(ReturnBlock->getTerminator(), + "try.end"); + BB->getTerminator()->eraseFromParent(); + IRBuilder<>(BB).CreateInvoke(SehTryEndFn, ReturnBlock, UnwindDestination); + + for (auto &Insn : *UnwindDestination) { + if (auto *Phi = dyn_cast(&Insn)) { + auto *ValType = Phi->getIncomingValueForBlock(TryStart)->getType(); + auto *Val = PoisonValue::get(ValType); + Phi->addIncoming(Val, BB); + } + } + } +} + // In contextual profiling, when an inline succeeds, we want to remap the // indices of the callee into the index space of the caller. We can't just leave // them as-is because the same callee may appear in other places in this caller @@ -2625,6 +2767,7 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI, SmallVector Returns; ClonedCodeInfo InlinedFunctionInfo; Function::iterator FirstNewBlock; + SmallVector UnterminatedTryScopes; // GC poses two hazards to inlining, which only occur when the callee has GC: // 1. If the caller has no GC, then the callee's GC must be propagated to the @@ -2648,6 +2791,11 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI, assert(Caller->getPersonalityFn()->stripPointerCasts() == CalledPersonality && "CanInlineCallSite should have verified compatible personality"); + + const llvm::Module *M = CalledFunc->getParent(); + if (M->getModuleFlag("eh-asynch")) { + UnterminatedTryScopes = GetUnterminatedTryScopes(CalledFunc); + } } { // Scope to destroy VMap after cloning. @@ -2837,6 +2985,8 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI, for (Instruction &I : NewBlock) if (auto *II = dyn_cast(&I)) IFI.GetAssumptionCache(*Caller).registerAssumption(II); + + HandleUnterminatedTryScopes(Caller, UnterminatedTryScopes, VMap); } if (IFI.ConvergenceControlToken) { diff --git a/llvm/test/Transforms/Inline/eha-try-fixup-multiple-scopes.ll b/llvm/test/Transforms/Inline/eha-try-fixup-multiple-scopes.ll new file mode 100644 index 0000000000000..21e88308a848f --- /dev/null +++ b/llvm/test/Transforms/Inline/eha-try-fixup-multiple-scopes.ll @@ -0,0 +1,207 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -passes=inline -S | FileCheck %s +; Check that both unterminated try scopes of ExitOnThrow are terminated upon inlining into main. +; The try scope of main should not get an additional terminator. + +define i32 @ExitOnThrow(i32 %argc) #0 personality ptr @__CxxFrameHandler3 { +; CHECK-LABEL: define i32 @ExitOnThrow( +; CHECK-SAME: i32 [[ARGC:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__CxxFrameHandler3 { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]] +; CHECK: [[INVOKE_CONT_I]]: +; CHECK-NEXT: [[CALL_I:%.*]] = invoke i32 @ThrowException() +; CHECK-NEXT: to label %[[TRY_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[CATCH_DISPATCH_I]]: +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch.i] unwind to caller +; CHECK: [[CATCH_I:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[TRY_CONT_I]] +; CHECK: [[TRY_CONT_I]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT2_I:.*]] unwind label %[[CATCH_DISPATCH3:.*]] +; CHECK: [[INVOKE_CONT2_I]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT1:.*]] unwind label %[[CATCH_DISPATCH:.*]] +; CHECK: [[INVOKE_CONT1]]: +; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[ARGC]], 0 +; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[IF_THEN_I:.*]], label %[[IF_END_I:.*]] +; CHECK: [[IF_THEN_I]]: +; CHECK-NEXT: invoke void @ThrowException() +; CHECK-NEXT: to label %[[UNREACHABLE:.*]] unwind label %[[CATCH_DISPATCH]] +; CHECK: [[CATCH_DISPATCH]]: +; CHECK-NEXT: [[TMP2:%.*]] = catchswitch within none [label %catch] unwind label %[[CATCH_DISPATCH3]] +; CHECK: [[CATCH:.*:]] +; CHECK-NEXT: [[TMP3:%.*]] = catchpad within [[TMP2]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: invoke void @Exit() #[[ATTR0]] [ "funclet"(token [[TMP3]]) ] +; CHECK-NEXT: to label %[[INVOKE_CONT2:.*]] unwind label %[[CATCH_DISPATCH3]] +; CHECK: [[CATCH_DISPATCH3]]: +; CHECK-NEXT: [[TMP4:%.*]] = catchswitch within none [label %catch4] unwind to caller +; CHECK: [[CATCH4:.*:]] +; CHECK-NEXT: [[TMP5:%.*]] = catchpad within [[TMP4]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP5]] to label %[[TRY_CONT5:.*]] +; CHECK: [[TRY_CONT5]]: +; CHECK-NEXT: call void @Exit() #[[ATTR0]] +; CHECK-NEXT: unreachable +; CHECK: [[INVOKE_CONT2]]: +; CHECK-NEXT: unreachable +; CHECK: [[IF_END_I]]: +; CHECK-NEXT: ret i32 [[ARGC]] +; CHECK: [[UNREACHABLE]]: +; CHECK-NEXT: unreachable +; +entry: + invoke void @llvm.seh.try.begin() + to label %invoke.cont.i unwind label %catch.dispatch.i + +invoke.cont.i: ; preds = %entry + %call.i = invoke i32 @ThrowException() + to label %try.cont.i unwind label %catch.dispatch.i + +catch.dispatch.i: ; preds = %try.cont9.i, %invoke.cont.i, %entry + %0 = catchswitch within none [label %catch.i] unwind to caller + +catch.i: ; preds = %catch.dispatch.i + %1 = catchpad within %0 [ptr null, i32 0, ptr null] + catchret from %1 to label %try.cont.i + +try.cont.i: ; preds = %catch.i, invoke.cont.i + invoke void @llvm.seh.try.begin() + to label %invoke.cont2.i unwind label %catch.dispatch3 + +invoke.cont2.i: ; preds = %try.cont.i + invoke void @llvm.seh.try.begin() + to label %invoke.cont1 unwind label %catch.dispatch + +invoke.cont1: ; preds = %invoke.cont2.i + %tobool.not = icmp eq i32 %argc, 0 + br i1 %tobool.not, label %if.then.i, label %if.end.i + +if.then.i: ; preds = %invoke.cont1 + invoke void @ThrowException() + to label %unreachable unwind label %catch.dispatch + +catch.dispatch: ; preds = %if.then.i, %invoke.cont2.i + %2 = catchswitch within none [label %catch] unwind label %catch.dispatch3 + +catch: ; preds = %catch.dispatch + %3 = catchpad within %2 [ptr null, i32 0, ptr null] + invoke void @Exit() #0 [ "funclet"(token %3) ] + to label %invoke.cont2 unwind label %catch.dispatch3 + +catch.dispatch3: ; preds = %catch, %catch.dispatch, %entry + %4 = catchswitch within none [label %catch4] unwind to caller + +catch4: ; preds = %catch.dispatch3 + %5 = catchpad within %4 [ptr null, i32 0, ptr null] + catchret from %5 to label %try.cont5 + +try.cont5: ; preds = %catch4 + call void @Exit() #0 + unreachable + +invoke.cont2: ; preds = %catch + unreachable + +if.end.i: ; preds = %invoke.cont1 + ret i32 %argc + +unreachable: ; preds = %if.then + unreachable +} + +define i32 @main(i32 noundef %argc, ptr noundef %argv) personality ptr @__CxxFrameHandler3 { +; CHECK-LABEL: define i32 @main( +; CHECK-SAME: i32 noundef [[ARGC:%.*]], ptr noundef [[ARGV:%.*]]) personality ptr @__CxxFrameHandler3 { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]] +; CHECK: [[CATCH_DISPATCH]]: +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch] unwind to caller +; CHECK: [[CATCH:.*]]: +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[CLEANUP:.*]] +; CHECK: [[INVOKE_CONT]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT_I_I:.*]] unwind label %[[CATCH_DISPATCH_I_I:.*]] +; CHECK: [[INVOKE_CONT_I_I]]: +; CHECK-NEXT: [[CALL_I_I:%.*]] = invoke i32 @ThrowException() +; CHECK-NEXT: to label %[[TRY_CONT_I_I:.*]] unwind label %[[CATCH_DISPATCH_I_I]] +; CHECK: [[CATCH_DISPATCH_I_I]]: +; CHECK-NEXT: [[TMP2:%.*]] = catchswitch within none [label %catch.i.i] unwind to caller +; CHECK: [[CATCH_I_I:.*:]] +; CHECK-NEXT: [[TMP3:%.*]] = catchpad within [[TMP2]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP3]] to label %[[TRY_CONT_I_I]] +; CHECK: [[TRY_CONT_I_I]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT2_I_I:.*]] unwind label %[[CATCH_DISPATCH3_I:.*]] +; CHECK: [[INVOKE_CONT2_I_I]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT1_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]] +; CHECK: [[INVOKE_CONT1_I]]: +; CHECK-NEXT: [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[ARGC]], 0 +; CHECK-NEXT: br i1 [[TOBOOL_NOT_I]], label %[[IF_THEN_I_I:.*]], label %[[TRY_END:.*]] +; CHECK: [[IF_THEN_I_I]]: +; CHECK-NEXT: invoke void @ThrowException() +; CHECK-NEXT: to label %[[UNREACHABLE_I:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[CATCH_DISPATCH_I]]: +; CHECK-NEXT: [[TMP4:%.*]] = catchswitch within none [label %catch.i] unwind label %[[CATCH_DISPATCH3_I]] +; CHECK: [[CATCH_I:.*:]] +; CHECK-NEXT: [[TMP5:%.*]] = catchpad within [[TMP4]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: invoke void @Exit() #[[ATTR0]] [ "funclet"(token [[TMP5]]) ] +; CHECK-NEXT: to label %[[INVOKE_CONT2_I:.*]] unwind label %[[CATCH_DISPATCH3_I]] +; CHECK: [[CATCH_DISPATCH3_I]]: +; CHECK-NEXT: [[TMP6:%.*]] = catchswitch within none [label %catch4.i] unwind to caller +; CHECK: [[CATCH4_I:.*:]] +; CHECK-NEXT: [[TMP7:%.*]] = catchpad within [[TMP6]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP7]] to label %[[TRY_CONT5_I:.*]] +; CHECK: [[TRY_CONT5_I]]: +; CHECK-NEXT: call void @Exit() #[[ATTR0]] +; CHECK-NEXT: unreachable +; CHECK: [[INVOKE_CONT2_I]]: +; CHECK-NEXT: unreachable +; CHECK: [[TRY_END]]: +; CHECK-NEXT: invoke void @llvm.seh.try.end() +; CHECK-NEXT: to label %[[EXITONTHROW_EXIT:.*]] unwind label %[[CATCH_DISPATCH3_I]] +; CHECK: [[UNREACHABLE_I]]: +; CHECK-NEXT: unreachable +; CHECK: [[EXITONTHROW_EXIT]]: +; CHECK-NEXT: [[CALL1:%.*]] = call noundef i32 @AlwaysThrows(i32 noundef [[ARGC]]) +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[ARGC]], [[CALL1]] +; CHECK-NEXT: br label %[[CLEANUP]] +; CHECK: [[CLEANUP]]: +; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i32 [ [[ADD]], %[[EXITONTHROW_EXIT]] ], [ 123, %[[CATCH]] ] +; CHECK-NEXT: ret i32 [[RETVAL_0]] +; +entry: + invoke void @llvm.seh.try.begin() + to label %invoke.cont unwind label %catch.dispatch + +catch.dispatch: ; preds = %entry + %0 = catchswitch within none [label %catch] unwind to caller + +catch: ; preds = %catch.dispatch + %1 = catchpad within %0 [ptr null, i32 0, ptr null] + catchret from %1 to label %cleanup + +invoke.cont: ; preds = %entry + %call = call noundef i32 @ExitOnThrow(i32 noundef %argc) + %call1 = call noundef i32 @AlwaysThrows(i32 noundef %call) + %add = add nsw i32 %call, %call1 + br label %cleanup + +cleanup: ; preds = %catch, %invoke.cont + %retval.0 = phi i32 [ %add, %invoke.cont ], [ 123, %catch ] + ret i32 %retval.0 +} + +declare void @llvm.seh.try.begin() +declare i32 @__CxxFrameHandler3(...) +declare void @ThrowException() +declare void @Exit() #0 +declare i32 @AlwaysThrows(i32 %id) + +attributes #0 = { noreturn } + +!llvm.module.flags = !{!0} +!0 = !{i32 2, !"eh-asynch", i32 1} diff --git a/llvm/test/Transforms/Inline/eha-try-fixup-phis.ll b/llvm/test/Transforms/Inline/eha-try-fixup-phis.ll new file mode 100644 index 0000000000000..2fcf787239579 --- /dev/null +++ b/llvm/test/Transforms/Inline/eha-try-fixup-phis.ll @@ -0,0 +1,119 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -passes=inline -S | FileCheck %s +; Check that the phi in catch.dispatch is adjusted for the try scope end upon inlining. + +define i32 @ExitOnThrow(i32 %argc) #3 personality ptr @__CxxFrameHandler3 { +; CHECK-LABEL: define i32 @ExitOnThrow( +; CHECK-SAME: i32 [[ARGC:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__CxxFrameHandler3 { +; CHECK-NEXT: [[ENTRY:.*]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]] +; CHECK: [[INVOKE_CONT]]: +; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[ARGC]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[IF_END:.*]] +; CHECK: [[IF_THEN]]: +; CHECK-NEXT: invoke void @ThrowException() #[[ATTR2:[0-9]+]] +; CHECK-NEXT: to label %[[UNREACHABLE:.*]] unwind label %[[CATCH_DISPATCH]] +; CHECK: [[CATCH_DISPATCH]]: +; CHECK-NEXT: [[PHI:%.*]] = phi i32 [ 1, %[[IF_THEN]] ], [ 0, %[[ENTRY]] ] +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch] unwind to caller +; CHECK: [[CATCH:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: call void @DoSth(i32 noundef [[PHI]]) #[[ATTR3:[0-9]+]] [ "funclet"(token [[TMP1]]) ] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[TRY_CONT:.*]] +; CHECK: [[TRY_CONT]]: +; CHECK-NEXT: call void @Exit() #[[ATTR4:[0-9]+]] +; CHECK-NEXT: unreachable +; CHECK: [[IF_END]]: +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[ARGC]], 5 +; CHECK-NEXT: ret i32 [[ADD]] +; CHECK: [[UNREACHABLE]]: +; CHECK-NEXT: unreachable +; +entry: + invoke void @llvm.seh.try.begin() + to label %invoke.cont unwind label %catch.dispatch + +invoke.cont: ; preds = %entry + %cmp = icmp sgt i32 %argc, 0 + br i1 %cmp, label %if.then, label %if.end + +if.then: ; preds = %invoke.cont + invoke void @ThrowException() #0 + to label %unreachable unwind label %catch.dispatch + +catch.dispatch: ; preds = %if.then, %entry + %phi = phi i32 [ 1, %if.then ], [ 0, %entry ] + %0 = catchswitch within none [label %catch] unwind to caller + +catch: ; preds = %catch.dispatch + %1 = catchpad within %0 [ptr null, i32 0, ptr null] + call void @DoSth(i32 noundef %phi) #1 [ "funclet"(token %1) ] + catchret from %1 to label %try.cont + +try.cont: ; preds = %catch, %invoke.cont + call void @Exit() #2 + unreachable + +if.end: ; preds = %invoke.cont + %add = add nsw i32 %argc, 5 + ret i32 %add + +unreachable: ; preds = %if.then + unreachable +} + +define i32 @main(i32 noundef %argc, ptr noundef %argv) { +; CHECK-LABEL: define i32 @main( +; CHECK-SAME: i32 noundef [[ARGC:%.*]], ptr noundef [[ARGV:%.*]]) personality ptr @__CxxFrameHandler3 { +; CHECK-NEXT: [[ENTRY:.*]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]] +; CHECK: [[INVOKE_CONT_I]]: +; CHECK-NEXT: [[CMP_I:%.*]] = icmp sgt i32 [[ARGC]], 0 +; CHECK-NEXT: br i1 [[CMP_I]], label %[[IF_THEN_I:.*]], label %[[TRY_END:.*]] +; CHECK: [[IF_THEN_I]]: +; CHECK-NEXT: invoke void @ThrowException() #[[ATTR2]] +; CHECK-NEXT: to label %[[UNREACHABLE_I:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[CATCH_DISPATCH_I]]: +; CHECK-NEXT: [[PHI_I:%.*]] = phi i32 [ 1, %[[IF_THEN_I]] ], [ 0, %[[ENTRY]] ], [ poison, %[[TRY_END]] ] +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch.i] unwind to caller +; CHECK: [[CATCH_I:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: call void @DoSth(i32 noundef [[PHI_I]]) #[[ATTR3]] [ "funclet"(token [[TMP1]]) ] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[TRY_CONT_I:.*]] +; CHECK: [[TRY_CONT_I]]: +; CHECK-NEXT: call void @Exit() #[[ATTR4]] +; CHECK-NEXT: unreachable +; CHECK: [[TRY_END]]: +; CHECK-NEXT: [[ADD_I:%.*]] = add nsw i32 [[ARGC]], 5 +; CHECK-NEXT: invoke void @llvm.seh.try.end() +; CHECK-NEXT: to label %[[EXITONTHROW_EXIT:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[UNREACHABLE_I]]: +; CHECK-NEXT: unreachable +; CHECK: [[EXITONTHROW_EXIT]]: +; CHECK-NEXT: [[CALL1:%.*]] = call i32 @AlwaysThrows(i32 noundef [[ADD_I]]) +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[ADD_I]], [[CALL1]] +; CHECK-NEXT: ret i32 [[ADD]] +; +entry: + %call = call i32 @ExitOnThrow(i32 noundef %argc) + %call1 = call i32 @AlwaysThrows(i32 noundef %call) + %add = add nsw i32 %call, %call1 + ret i32 %add +} + +declare void @llvm.seh.try.begin() +declare i32 @__CxxFrameHandler3(...) +declare void @ThrowException() +declare void @Exit() #0 +declare i32 @AlwaysThrows(i32 %id) +declare void @DoSth(i32 %val) + +attributes #0 = { noreturn } +attributes #1 = { nounwind } +attributes #2 = { noreturn nounwind } +attributes #3 = { mustprogress noreturn nounwind uwtable } + +!llvm.module.flags = !{!0} +!0 = !{i32 2, !"eh-asynch", i32 1} diff --git a/llvm/test/Transforms/Inline/eha-try-fixup-seh.ll b/llvm/test/Transforms/Inline/eha-try-fixup-seh.ll new file mode 100644 index 0000000000000..e44ea52cc6958 --- /dev/null +++ b/llvm/test/Transforms/Inline/eha-try-fixup-seh.ll @@ -0,0 +1,154 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -passes=inline -S | FileCheck %s +; Check that only the unterminated try scope for unwind destination catch.dispatch6 +; is terminated upon inlining into main. + +define i32 @ExitOnThrow(i32 %argc) personality ptr @__C_specific_handler { +; CHECK-LABEL: define i32 @ExitOnThrow( +; CHECK-SAME: i32 [[ARGC:%.*]]) personality ptr @__C_specific_handler { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[ARGC_ADDR:%.*]] = alloca i32, align 4 +; CHECK-NEXT: store i32 [[ARGC]], ptr [[ARGC_ADDR]], align 4 +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]] +; CHECK: [[INVOKE_CONT]]: +; CHECK-NEXT: invoke void @DoSth() +; CHECK-NEXT: to label %[[INVOKE_CONT1:.*]] unwind label %[[CATCH_DISPATCH]] +; CHECK: [[INVOKE_CONT1]]: +; CHECK-NEXT: invoke void @llvm.seh.try.end() +; CHECK-NEXT: to label %[[__TRY_CONT:.*]] unwind label %[[CATCH_DISPATCH]] +; CHECK: [[CATCH_DISPATCH]]: +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %__except] unwind to caller +; CHECK: [[__EXCEPT:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[__EXCEPT3:.*]] +; CHECK: [[__EXCEPT3]]: +; CHECK-NEXT: [[TMP2:%.*]] = tail call i32 @llvm.eh.exceptioncode(token [[TMP1]]) +; CHECK-NEXT: br label %[[__TRY_CONT]] +; CHECK: [[__TRY_CONT]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT5:.*]] unwind label %[[CATCH_DISPATCH6:.*]] +; CHECK: [[CATCH_DISPATCH6]]: +; CHECK-NEXT: [[TMP3:%.*]] = catchswitch within none [label %__except7] unwind to caller +; CHECK: [[__EXCEPT7:.*:]] +; CHECK-NEXT: [[TMP4:%.*]] = catchpad within [[TMP3]] [ptr null] +; CHECK-NEXT: catchret from [[TMP4]] to label %[[__EXCEPT8:.*]] +; CHECK: [[__EXCEPT8]]: +; CHECK-NEXT: [[TMP5:%.*]] = tail call i32 @llvm.eh.exceptioncode(token [[TMP4]]) +; CHECK-NEXT: tail call void @Exit() #[[ATTR2:[0-9]+]] +; CHECK-NEXT: unreachable +; CHECK: [[INVOKE_CONT5]]: +; CHECK-NEXT: [[ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_:%.*]] = load volatile i32, ptr [[ARGC_ADDR]], align 4 +; CHECK-NEXT: ret i32 [[ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_]] +; +entry: + %argc.addr = alloca i32, align 4 + store i32 %argc, ptr %argc.addr, align 4 + invoke void @llvm.seh.try.begin() + to label %invoke.cont unwind label %catch.dispatch + +invoke.cont: ; preds = %entry + invoke void @DoSth() + to label %invoke.cont1 unwind label %catch.dispatch + +invoke.cont1: ; preds = %invoke.cont + invoke void @llvm.seh.try.end() + to label %__try.cont unwind label %catch.dispatch + +catch.dispatch: ; preds = %invoke.cont1, %invoke.cont, %entry + %0 = catchswitch within none [label %__except] unwind to caller + +__except: ; preds = %catch.dispatch + %1 = catchpad within %0 [ptr null] + catchret from %1 to label %__except3 + +__except3: ; preds = %__except + %2 = tail call i32 @llvm.eh.exceptioncode(token %1) + br label %__try.cont + +__try.cont: ; preds = %invoke.cont1, %__except3 + invoke void @llvm.seh.try.begin() + to label %invoke.cont5 unwind label %catch.dispatch6 + +catch.dispatch6: ; preds = %__try.cont + %3 = catchswitch within none [label %__except7] unwind to caller + +__except7: ; preds = %catch.dispatch6 + %4 = catchpad within %3 [ptr null] + catchret from %4 to label %__except8 + +__except8: ; preds = %__except7 + %5 = tail call i32 @llvm.eh.exceptioncode(token %4) + tail call void @Exit() #0 + unreachable + +invoke.cont5: ; preds = %__try.cont + %argc.addr.0.argc.addr.0.argc.addr.0.argc.addr.0. = load volatile i32, ptr %argc.addr, align 4 + ret i32 %argc.addr.0.argc.addr.0.argc.addr.0.argc.addr.0. +} + +define i32 @main(i32 noundef %argc, ptr noundef %argv) { +; CHECK-LABEL: define i32 @main( +; CHECK-SAME: i32 noundef [[ARGC:%.*]], ptr noundef [[ARGV:%.*]]) personality ptr @__C_specific_handler { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[ARGC_ADDR_I:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ARGC_ADDR_I]]) +; CHECK-NEXT: store i32 [[ARGC]], ptr [[ARGC_ADDR_I]], align 4 +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]] +; CHECK: [[INVOKE_CONT_I]]: +; CHECK-NEXT: invoke void @DoSth() +; CHECK-NEXT: to label %[[INVOKE_CONT1_I:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[INVOKE_CONT1_I]]: +; CHECK-NEXT: invoke void @llvm.seh.try.end() +; CHECK-NEXT: to label %[[__TRY_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[CATCH_DISPATCH_I]]: +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %__except.i] unwind to caller +; CHECK: [[__EXCEPT_I:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[__EXCEPT3_I:.*]] +; CHECK: [[__EXCEPT3_I]]: +; CHECK-NEXT: [[TMP2:%.*]] = call i32 @llvm.eh.exceptioncode(token [[TMP1]]) +; CHECK-NEXT: br label %[[__TRY_CONT_I]] +; CHECK: [[__TRY_CONT_I]]: +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[TRY_END:.*]] unwind label %[[CATCH_DISPATCH6_I:.*]] +; CHECK: [[CATCH_DISPATCH6_I]]: +; CHECK-NEXT: [[TMP3:%.*]] = catchswitch within none [label %__except7.i] unwind to caller +; CHECK: [[__EXCEPT7_I:.*:]] +; CHECK-NEXT: [[TMP4:%.*]] = catchpad within [[TMP3]] [ptr null] +; CHECK-NEXT: catchret from [[TMP4]] to label %[[__EXCEPT8_I:.*]] +; CHECK: [[__EXCEPT8_I]]: +; CHECK-NEXT: [[TMP5:%.*]] = call i32 @llvm.eh.exceptioncode(token [[TMP4]]) +; CHECK-NEXT: call void @Exit() #[[ATTR2]] +; CHECK-NEXT: unreachable +; CHECK: [[TRY_END]]: +; CHECK-NEXT: [[ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0__I:%.*]] = load volatile i32, ptr [[ARGC_ADDR_I]], align 4 +; CHECK-NEXT: invoke void @llvm.seh.try.end() +; CHECK-NEXT: to label %[[EXITONTHROW_EXIT:.*]] unwind label %[[CATCH_DISPATCH6_I]] +; CHECK: [[EXITONTHROW_EXIT]]: +; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ARGC_ADDR_I]]) +; CHECK-NEXT: [[CALL1:%.*]] = call i32 @AlwaysThrows(i32 noundef [[ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0__I]]) +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0_ARGC_ADDR_0__I]], [[CALL1]] +; CHECK-NEXT: ret i32 [[ADD]] +; +entry: + %call = call i32 @ExitOnThrow(i32 noundef %argc) + %call1 = call i32 @AlwaysThrows(i32 noundef %call) + %add = add nsw i32 %call, %call1 + ret i32 %add +} + +declare void @llvm.seh.try.begin() +declare void @llvm.seh.try.end() +declare i32 @llvm.eh.exceptioncode(token) +declare i32 @__C_specific_handler(...) +declare void @Fault() +declare void @Exit() #0 +declare i32 @AlwaysThrows(i32 %id) +declare void @DoSth(i32 %val) + +attributes #0 = { noreturn nounwind } + +!llvm.module.flags = !{!0} +!0 = !{i32 2, !"eh-asynch", i32 1} diff --git a/llvm/test/Transforms/Inline/eha-try-fixup.ll b/llvm/test/Transforms/Inline/eha-try-fixup.ll new file mode 100644 index 0000000000000..8e16faee450a9 --- /dev/null +++ b/llvm/test/Transforms/Inline/eha-try-fixup.ll @@ -0,0 +1,106 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -passes=inline -S | FileCheck %s +; Check that the try scope of ExitOnThrow is terminated upon inlining into main. + +define i32 @ExitOnThrow(i32 %argc) personality ptr @__CxxFrameHandler3 { +; CHECK-LABEL: define i32 @ExitOnThrow( +; CHECK-SAME: i32 [[ARGC:%.*]]) personality ptr @__CxxFrameHandler3 { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]] +; CHECK: [[INVOKE_CONT]]: +; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[ARGC]], 0 +; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[IF_THEN:.*]], label %[[IF_END:.*]] +; CHECK: [[IF_THEN]]: +; CHECK-NEXT: invoke void @ThrowException() +; CHECK-NEXT: to label %[[UNREACHABLE:.*]] unwind label %[[CATCH_DISPATCH]] +; CHECK: [[CATCH_DISPATCH]]: +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch] unwind to caller +; CHECK: [[CATCH:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[TRY_CONT:.*]] +; CHECK: [[TRY_CONT]]: +; CHECK-NEXT: call void @Exit() +; CHECK-NEXT: unreachable +; CHECK: [[IF_END]]: +; CHECK-NEXT: ret i32 [[ARGC]] +; CHECK: [[UNREACHABLE]]: +; CHECK-NEXT: unreachable +; +entry: + invoke void @llvm.seh.try.begin() + to label %invoke.cont unwind label %catch.dispatch + +invoke.cont: ; preds = %entry + %tobool.not = icmp eq i32 %argc, 0 + br i1 %tobool.not, label %if.then, label %if.end + +if.then: ; preds = %invoke.cont + invoke void @ThrowException() + to label %unreachable unwind label %catch.dispatch + +catch.dispatch: ; preds = %if.then, %entry + %0 = catchswitch within none [label %catch] unwind to caller + +catch: ; preds = %catch.dispatch + %1 = catchpad within %0 [ptr null, i32 0, ptr null] + catchret from %1 to label %try.cont + +try.cont: ; preds = %catch + call void @Exit() + unreachable + +if.end: ; preds = %invoke.cont + ret i32 %argc + +unreachable: ; preds = %if.then + unreachable +} + +define i32 @main(i32 noundef %argc, ptr noundef %argv) { +; CHECK-LABEL: define i32 @main( +; CHECK-SAME: i32 noundef [[ARGC:%.*]], ptr noundef [[ARGV:%.*]]) personality ptr @__CxxFrameHandler3 { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: invoke void @llvm.seh.try.begin() +; CHECK-NEXT: to label %[[INVOKE_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]] +; CHECK: [[INVOKE_CONT_I]]: +; CHECK-NEXT: [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[ARGC]], 0 +; CHECK-NEXT: br i1 [[TOBOOL_NOT_I]], label %[[IF_THEN_I:.*]], label %[[TRY_END:.*]] +; CHECK: [[IF_THEN_I]]: +; CHECK-NEXT: invoke void @ThrowException() +; CHECK-NEXT: to label %[[UNREACHABLE_I:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[CATCH_DISPATCH_I]]: +; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch.i] unwind to caller +; CHECK: [[CATCH_I:.*:]] +; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null] +; CHECK-NEXT: catchret from [[TMP1]] to label %[[TRY_CONT_I:.*]] +; CHECK: [[TRY_CONT_I]]: +; CHECK-NEXT: call void @Exit() +; CHECK-NEXT: unreachable +; CHECK: [[TRY_END]]: +; CHECK-NEXT: invoke void @llvm.seh.try.end() +; CHECK-NEXT: to label %[[EXITONTHROW_EXIT:.*]] unwind label %[[CATCH_DISPATCH_I]] +; CHECK: [[UNREACHABLE_I]]: +; CHECK-NEXT: unreachable +; CHECK: [[EXITONTHROW_EXIT]]: +; CHECK-NEXT: [[CALL1:%.*]] = call i32 @AlwaysThrows(i32 noundef [[ARGC]]) +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[ARGC]], [[CALL1]] +; CHECK-NEXT: ret i32 [[ADD]] +; +entry: + %call = call i32 @ExitOnThrow(i32 noundef %argc) + %call1 = call i32 @AlwaysThrows(i32 noundef %call) + %add = add nsw i32 %call, %call1 + ret i32 %add +} + +declare void @llvm.seh.try.begin() +declare i32 @__CxxFrameHandler3(...) +declare void @ThrowException() +declare void @Exit() #0 +declare i32 @AlwaysThrows(i32 %id) + +attributes #0 = { noreturn } + +!llvm.module.flags = !{!0} +!0 = !{i32 2, !"eh-asynch", i32 1}