[llvm] [Inline][WinEH] Fix try scopes leaking to caller on inline (PR #164170)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Oct 19 10:37:27 PDT 2025
https://github.com/MuellerMP created https://github.com/llvm/llvm-project/pull/164170
This fixes issue #164169
When inlining functions compiled with -EHa, try scope terminators might need to be inserted before inlined returns.
This prevents leaking try scopes over to the caller.
>From 8ac73b70bea2e719135a51876d07b811bd87fc05 Mon Sep 17 00:00:00 2001
From: MuellerMP <mirkomueller97 at live.de>
Date: Sun, 19 Oct 2025 19:36:18 +0200
Subject: [PATCH] [Inline][WinEH] Fix try scopes leaking to caller on inline
This fixes issue #164169
When inlining functions compiled with -EHa, try scope terminators might need to be inserted before inlined returns.
This prevents leaking try scopes over to the caller.
---
llvm/lib/Transforms/Utils/InlineFunction.cpp | 141 ++++++++++++
.../Inline/eha-try-fixup-multiple-scopes.ll | 207 ++++++++++++++++++
.../Transforms/Inline/eha-try-fixup-phis.ll | 119 ++++++++++
llvm/test/Transforms/Inline/eha-try-fixup.ll | 107 +++++++++
4 files changed, 574 insertions(+)
create mode 100644 llvm/test/Transforms/Inline/eha-try-fixup-multiple-scopes.ll
create mode 100644 llvm/test/Transforms/Inline/eha-try-fixup-phis.ll
create mode 100644 llvm/test/Transforms/Inline/eha-try-fixup.ll
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index f49fbf8807bac..08a66530b67ad 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,138 @@ inlineRetainOrClaimRVCalls(CallBase &CB, objcarc::ARCInstKind RVCallKind,
}
}
+struct SehTryScope {
+ llvm::BasicBlock *Start;
+ llvm::InvokeInst *Invoke;
+};
+
+// Determine SEH try scopes and their terminators and order them by dominance.
+static SmallVector<SehTryScope, 1> GetOrderedSehTryScopes(Function *Func,
+ DominatorTree &DT) {
+ SmallVector<SehTryScope, 1> Scopes{};
+ bool DTCalculated = false;
+
+ for (auto &BB : *Func) {
+ auto *TI = BB.getTerminator();
+ if (auto *II = dyn_cast<InvokeInst>(TI)) {
+ auto *Call = cast<CallBase>(II);
+ const auto *Fn = Call->getCalledFunction();
+ if (!Fn || !Fn->isIntrinsic())
+ continue;
+
+ if (Fn->getIntrinsicID() != Intrinsic::seh_try_begin)
+ continue;
+
+ if (!DTCalculated) {
+ DT.recalculate(*Func);
+ DTCalculated = true;
+ }
+
+ auto InsertIt = Scopes.end();
+ if (!Scopes.empty())
+ for (auto ScopeIt = Scopes.begin(); ScopeIt != Scopes.end(); ++ScopeIt)
+ if (DT.dominates(ScopeIt->Start, &BB))
+ InsertIt = std::next(ScopeIt);
+
+ Scopes.insert(InsertIt, {&BB, II});
+ }
+ }
+
+ return Scopes;
+}
+
+// Find, if present, the outermost unterminated try scope for the input block.
+static SehTryScope *
+GetOutermostUnterminatedTryScope(llvm::BasicBlock *Block,
+ SmallVector<SehTryScope, 1> &Scopes,
+ DominatorTree &DT) {
+ SehTryScope *UnterminatedScope{nullptr};
+
+ for (auto ScopeIt = Scopes.rbegin(); ScopeIt != Scopes.rend(); ++ScopeIt) {
+ // Return might not be in a try scope.
+ if (!DT.dominates(ScopeIt->Start, Block))
+ continue;
+
+ // If there is a catch which connects to the return, the try scope is
+ // considered to be terminated.
+ if (isPotentiallyReachable(ScopeIt->Invoke->getUnwindDest(), Block, nullptr,
+ &DT))
+ continue;
+
+ UnterminatedScope = &*ScopeIt;
+ }
+
+ return UnterminatedScope;
+}
+
+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<UnterminatedTryScope, 1>
+GetUnterminatedTryScopes(Function *CalledFunc) {
+ SmallVector<UnterminatedTryScope, 1> UnterminatedScopes;
+ DominatorTree DT;
+ auto Scopes = GetOrderedSehTryScopes(CalledFunc, DT);
+ if (Scopes.empty())
+ return UnterminatedScopes;
+
+ SmallVector<ReturnInst *, 8> Returns;
+ for (auto &BB : *CalledFunc)
+ if (ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator()))
+ Returns.push_back(RI);
+
+ for (auto *RI : Returns) {
+ auto *Block = RI->getParent();
+ auto *OutermostScope = GetOutermostUnterminatedTryScope(Block, Scopes, DT);
+ if (!OutermostScope)
+ continue;
+
+ UnterminatedScopes.push_back({Block, OutermostScope->Invoke->getParent(),
+ OutermostScope->Invoke->getUnwindDest()});
+ }
+
+ return UnterminatedScopes;
+}
+
+// Insert terminator for unterminated try scopes
+static void HandleUnterminatedTryScopes(
+ Function *Caller,
+ SmallVector<UnterminatedTryScope, 1> &UnterminatedTryScopes,
+ ValueToValueMapTy &VMap) {
+ for (auto &Scope : UnterminatedTryScopes) {
+ auto *ReturnBlock = cast<BasicBlock>(VMap[Scope.ReturnBlock]);
+ auto *TryStart = cast<BasicBlock>(VMap[Scope.TryStart]);
+ auto *UnwindDestination = cast<BasicBlock>(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<PHINode>(&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 +2758,7 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
SmallVector<ReturnInst*, 8> Returns;
ClonedCodeInfo InlinedFunctionInfo;
Function::iterator FirstNewBlock;
+ SmallVector<UnterminatedTryScope, 1> 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 +2782,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 +2976,8 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
for (Instruction &I : NewBlock)
if (auto *II = dyn_cast<AssumeInst>(&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.ll b/llvm/test/Transforms/Inline/eha-try-fixup.ll
new file mode 100644
index 0000000000000..7c9680561ca0e
--- /dev/null
+++ b/llvm/test/Transforms/Inline/eha-try-fixup.ll
@@ -0,0 +1,107 @@
+; 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)
+declare void @DoSth(i32 %val)
+
+attributes #0 = { noreturn }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 2, !"eh-asynch", i32 1}
More information about the llvm-commits
mailing list