[llvm] [Inline][WinEH] Fix try scopes leaking to caller on inline (PR #164170)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Oct 22 01:58:59 PDT 2025
https://github.com/MuellerMP updated https://github.com/llvm/llvm-project/pull/164170
>From 3d05e772bd28f7ebf8cba2c429a9a2dd84ee21a7 Mon Sep 17 00:00:00 2001
From: MuellerMP <mirkomueller97 at live.de>
Date: Wed, 22 Oct 2025 10:58:36 +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 | 150 +++++++++++++
.../Inline/eha-try-fixup-multiple-scopes.ll | 207 ++++++++++++++++++
.../Transforms/Inline/eha-try-fixup-phis.ll | 119 ++++++++++
.../Transforms/Inline/eha-try-fixup-seh.ll | 154 +++++++++++++
llvm/test/Transforms/Inline/eha-try-fixup.ll | 106 +++++++++
5 files changed, 736 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-seh.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..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<SehScope, 1> GetSehTryScopes(Function *Func) {
+ SmallVector<SehScope, 1> Scopes{};
+ DenseMap<llvm::BasicBlock *, llvm::InvokeInst *> ScopeEnds{};
+
+ 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_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<SehScope, 1> &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<UnterminatedTryScope, 1>
+GetUnterminatedTryScopes(Function *CalledFunc) {
+ SmallVector<UnterminatedTryScope, 1> UnterminatedScopes;
+ auto Scopes = GetSehTryScopes(CalledFunc);
+ 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 = 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<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 +2767,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 +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<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-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}
More information about the llvm-commits
mailing list