Skip to content

Commit 39f456e

Browse files
committed
[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.
1 parent 4a3e000 commit 39f456e

File tree

5 files changed

+736
-0
lines changed

5 files changed

+736
-0
lines changed

llvm/lib/Transforms/Utils/InlineFunction.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "llvm/Analysis/BlockFrequencyInfo.h"
2424
#include "llvm/Analysis/CallGraph.h"
2525
#include "llvm/Analysis/CaptureTracking.h"
26+
#include "llvm/Analysis/CFG.h"
2627
#include "llvm/Analysis/CtxProfAnalysis.h"
2728
#include "llvm/Analysis/IndirectCallVisitor.h"
2829
#include "llvm/Analysis/InstructionSimplify.h"
@@ -2210,6 +2211,147 @@ inlineRetainOrClaimRVCalls(CallBase &CB, objcarc::ARCInstKind RVCallKind,
22102211
}
22112212
}
22122213

2214+
struct SehScope {
2215+
llvm::InvokeInst *Begin;
2216+
llvm::InvokeInst *End;
2217+
};
2218+
2219+
// Determine SEH try scope begins and ends.
2220+
static SmallVector<SehScope, 1> GetSehTryScopes(Function *Func) {
2221+
SmallVector<SehScope, 1> Scopes{};
2222+
DenseMap<llvm::BasicBlock *, llvm::InvokeInst *> ScopeEnds{};
2223+
2224+
for (auto &BB : *Func) {
2225+
auto *TI = BB.getTerminator();
2226+
if (auto *II = dyn_cast<InvokeInst>(TI)) {
2227+
auto *Call = cast<CallBase>(II);
2228+
const auto *Fn = Call->getCalledFunction();
2229+
if (!Fn || !Fn->isIntrinsic())
2230+
continue;
2231+
2232+
if (Fn->getIntrinsicID() == Intrinsic::seh_try_end) {
2233+
ScopeEnds[II->getUnwindDest()] = II;
2234+
} else if (Fn->getIntrinsicID() == Intrinsic::seh_try_begin) {
2235+
Scopes.push_back({II, nullptr});
2236+
}
2237+
}
2238+
}
2239+
2240+
// Assign scope end to begin if the unwind destination matches.
2241+
for (auto &Scope : Scopes) {
2242+
auto ScopeEndIt = ScopeEnds.find(Scope.Begin->getUnwindDest());
2243+
if (ScopeEndIt != ScopeEnds.end())
2244+
Scope.End = ScopeEndIt->second;
2245+
}
2246+
2247+
return Scopes;
2248+
}
2249+
2250+
// Find, if present, the outermost unterminated try scope for the input block.
2251+
static llvm::InvokeInst *
2252+
GetOutermostUnterminatedTryScopeBegin(llvm::BasicBlock *ReturnBlock,
2253+
SmallVector<SehScope, 1> &Scopes) {
2254+
llvm::InvokeInst *OutermostScope{nullptr};
2255+
DominatorTree DT;
2256+
DT.recalculate(*ReturnBlock->getParent());
2257+
2258+
for (auto &Scope : Scopes) {
2259+
auto *Invoke = Scope.Begin;
2260+
2261+
// Return might not be in a try scope.
2262+
if (!DT.dominates(Invoke->getParent(), ReturnBlock))
2263+
continue;
2264+
2265+
// If there is a catch which connects to the return, the try scope is
2266+
// considered to be terminated.
2267+
if (isPotentiallyReachable(Invoke->getUnwindDest(), ReturnBlock, nullptr,
2268+
&DT))
2269+
continue;
2270+
2271+
// For SEH there can be try scope ends on invokes and finally statements.
2272+
if (Scope.End && isPotentiallyReachable(Scope.End->getParent(), ReturnBlock,
2273+
nullptr, &DT))
2274+
continue;
2275+
2276+
// Keep the outermost scope if we match multiple ones.
2277+
if (OutermostScope) {
2278+
if (!DT.dominates(Invoke->getParent(), OutermostScope->getParent()))
2279+
continue;
2280+
}
2281+
2282+
OutermostScope = Invoke;
2283+
}
2284+
2285+
return OutermostScope;
2286+
}
2287+
2288+
struct UnterminatedTryScope {
2289+
llvm::BasicBlock *ReturnBlock;
2290+
llvm::BasicBlock *TryStart;
2291+
llvm::BasicBlock *UnwindDestination;
2292+
};
2293+
2294+
// For Windows -EHa there may be the case of try scopes spanning over a return
2295+
// instruction. If no other return out of the function is given (e.g. by letting
2296+
// all other blocks terminate with unreachable) the try scope is considered
2297+
// unterminated upon inlining. To avoid leaking the try scopes over to a caller
2298+
// we need to add a terminator for the outermost unterminated try scope.
2299+
static SmallVector<UnterminatedTryScope, 1>
2300+
GetUnterminatedTryScopes(Function *CalledFunc) {
2301+
SmallVector<UnterminatedTryScope, 1> UnterminatedScopes;
2302+
auto Scopes = GetSehTryScopes(CalledFunc);
2303+
if (Scopes.empty())
2304+
return UnterminatedScopes;
2305+
2306+
SmallVector<ReturnInst *, 8> Returns;
2307+
for (auto &BB : *CalledFunc)
2308+
if (ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator()))
2309+
Returns.push_back(RI);
2310+
2311+
for (auto *RI : Returns) {
2312+
auto *Block = RI->getParent();
2313+
auto *OutermostScope = GetOutermostUnterminatedTryScopeBegin(Block, Scopes);
2314+
if (!OutermostScope)
2315+
continue;
2316+
2317+
UnterminatedScopes.push_back(
2318+
{Block, OutermostScope->getParent(), OutermostScope->getUnwindDest()});
2319+
}
2320+
2321+
return UnterminatedScopes;
2322+
}
2323+
2324+
// Insert terminator for unterminated try scopes.
2325+
static void HandleUnterminatedTryScopes(
2326+
Function *Caller,
2327+
SmallVector<UnterminatedTryScope, 1> &UnterminatedTryScopes,
2328+
ValueToValueMapTy &VMap) {
2329+
for (auto &Scope : UnterminatedTryScopes) {
2330+
auto *ReturnBlock = cast<BasicBlock>(VMap[Scope.ReturnBlock]);
2331+
auto *TryStart = cast<BasicBlock>(VMap[Scope.TryStart]);
2332+
auto *UnwindDestination = cast<BasicBlock>(VMap[Scope.UnwindDestination]);
2333+
// did not survive (partial) inlining - ignore
2334+
if (!ReturnBlock || !TryStart || !UnwindDestination)
2335+
continue;
2336+
2337+
auto *Mod = (llvm::Module *)Caller->getParent();
2338+
auto *SehTryEndFn =
2339+
Intrinsic::getOrInsertDeclaration(Mod, Intrinsic::seh_try_end);
2340+
auto *BB = ReturnBlock->splitBasicBlockBefore(ReturnBlock->getTerminator(),
2341+
"try.end");
2342+
BB->getTerminator()->eraseFromParent();
2343+
IRBuilder<>(BB).CreateInvoke(SehTryEndFn, ReturnBlock, UnwindDestination);
2344+
2345+
for (auto &Insn : *UnwindDestination) {
2346+
if (auto *Phi = dyn_cast<PHINode>(&Insn)) {
2347+
auto *ValType = Phi->getIncomingValueForBlock(TryStart)->getType();
2348+
auto *Val = PoisonValue::get(ValType);
2349+
Phi->addIncoming(Val, BB);
2350+
}
2351+
}
2352+
}
2353+
}
2354+
22132355
// In contextual profiling, when an inline succeeds, we want to remap the
22142356
// indices of the callee into the index space of the caller. We can't just leave
22152357
// 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,
26252767
SmallVector<ReturnInst*, 8> Returns;
26262768
ClonedCodeInfo InlinedFunctionInfo;
26272769
Function::iterator FirstNewBlock;
2770+
SmallVector<UnterminatedTryScope, 1> UnterminatedTryScopes;
26282771

26292772
// GC poses two hazards to inlining, which only occur when the callee has GC:
26302773
// 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,
26482791
assert(Caller->getPersonalityFn()->stripPointerCasts() ==
26492792
CalledPersonality &&
26502793
"CanInlineCallSite should have verified compatible personality");
2794+
2795+
const llvm::Module *M = CalledFunc->getParent();
2796+
if (M->getModuleFlag("eh-asynch")) {
2797+
UnterminatedTryScopes = GetUnterminatedTryScopes(CalledFunc);
2798+
}
26512799
}
26522800

26532801
{ // Scope to destroy VMap after cloning.
@@ -2837,6 +2985,8 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
28372985
for (Instruction &I : NewBlock)
28382986
if (auto *II = dyn_cast<AssumeInst>(&I))
28392987
IFI.GetAssumptionCache(*Caller).registerAssumption(II);
2988+
2989+
HandleUnterminatedTryScopes(Caller, UnterminatedTryScopes, VMap);
28402990
}
28412991

28422992
if (IFI.ConvergenceControlToken) {
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
2+
; RUN: opt < %s -passes=inline -S | FileCheck %s
3+
; Check that both unterminated try scopes of ExitOnThrow are terminated upon inlining into main.
4+
; The try scope of main should not get an additional terminator.
5+
6+
define i32 @ExitOnThrow(i32 %argc) #0 personality ptr @__CxxFrameHandler3 {
7+
; CHECK-LABEL: define i32 @ExitOnThrow(
8+
; CHECK-SAME: i32 [[ARGC:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__CxxFrameHandler3 {
9+
; CHECK-NEXT: [[ENTRY:.*:]]
10+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
11+
; CHECK-NEXT: to label %[[INVOKE_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]]
12+
; CHECK: [[INVOKE_CONT_I]]:
13+
; CHECK-NEXT: [[CALL_I:%.*]] = invoke i32 @ThrowException()
14+
; CHECK-NEXT: to label %[[TRY_CONT_I:.*]] unwind label %[[CATCH_DISPATCH_I]]
15+
; CHECK: [[CATCH_DISPATCH_I]]:
16+
; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch.i] unwind to caller
17+
; CHECK: [[CATCH_I:.*:]]
18+
; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null]
19+
; CHECK-NEXT: catchret from [[TMP1]] to label %[[TRY_CONT_I]]
20+
; CHECK: [[TRY_CONT_I]]:
21+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
22+
; CHECK-NEXT: to label %[[INVOKE_CONT2_I:.*]] unwind label %[[CATCH_DISPATCH3:.*]]
23+
; CHECK: [[INVOKE_CONT2_I]]:
24+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
25+
; CHECK-NEXT: to label %[[INVOKE_CONT1:.*]] unwind label %[[CATCH_DISPATCH:.*]]
26+
; CHECK: [[INVOKE_CONT1]]:
27+
; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[ARGC]], 0
28+
; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[IF_THEN_I:.*]], label %[[IF_END_I:.*]]
29+
; CHECK: [[IF_THEN_I]]:
30+
; CHECK-NEXT: invoke void @ThrowException()
31+
; CHECK-NEXT: to label %[[UNREACHABLE:.*]] unwind label %[[CATCH_DISPATCH]]
32+
; CHECK: [[CATCH_DISPATCH]]:
33+
; CHECK-NEXT: [[TMP2:%.*]] = catchswitch within none [label %catch] unwind label %[[CATCH_DISPATCH3]]
34+
; CHECK: [[CATCH:.*:]]
35+
; CHECK-NEXT: [[TMP3:%.*]] = catchpad within [[TMP2]] [ptr null, i32 0, ptr null]
36+
; CHECK-NEXT: invoke void @Exit() #[[ATTR0]] [ "funclet"(token [[TMP3]]) ]
37+
; CHECK-NEXT: to label %[[INVOKE_CONT2:.*]] unwind label %[[CATCH_DISPATCH3]]
38+
; CHECK: [[CATCH_DISPATCH3]]:
39+
; CHECK-NEXT: [[TMP4:%.*]] = catchswitch within none [label %catch4] unwind to caller
40+
; CHECK: [[CATCH4:.*:]]
41+
; CHECK-NEXT: [[TMP5:%.*]] = catchpad within [[TMP4]] [ptr null, i32 0, ptr null]
42+
; CHECK-NEXT: catchret from [[TMP5]] to label %[[TRY_CONT5:.*]]
43+
; CHECK: [[TRY_CONT5]]:
44+
; CHECK-NEXT: call void @Exit() #[[ATTR0]]
45+
; CHECK-NEXT: unreachable
46+
; CHECK: [[INVOKE_CONT2]]:
47+
; CHECK-NEXT: unreachable
48+
; CHECK: [[IF_END_I]]:
49+
; CHECK-NEXT: ret i32 [[ARGC]]
50+
; CHECK: [[UNREACHABLE]]:
51+
; CHECK-NEXT: unreachable
52+
;
53+
entry:
54+
invoke void @llvm.seh.try.begin()
55+
to label %invoke.cont.i unwind label %catch.dispatch.i
56+
57+
invoke.cont.i: ; preds = %entry
58+
%call.i = invoke i32 @ThrowException()
59+
to label %try.cont.i unwind label %catch.dispatch.i
60+
61+
catch.dispatch.i: ; preds = %try.cont9.i, %invoke.cont.i, %entry
62+
%0 = catchswitch within none [label %catch.i] unwind to caller
63+
64+
catch.i: ; preds = %catch.dispatch.i
65+
%1 = catchpad within %0 [ptr null, i32 0, ptr null]
66+
catchret from %1 to label %try.cont.i
67+
68+
try.cont.i: ; preds = %catch.i, invoke.cont.i
69+
invoke void @llvm.seh.try.begin()
70+
to label %invoke.cont2.i unwind label %catch.dispatch3
71+
72+
invoke.cont2.i: ; preds = %try.cont.i
73+
invoke void @llvm.seh.try.begin()
74+
to label %invoke.cont1 unwind label %catch.dispatch
75+
76+
invoke.cont1: ; preds = %invoke.cont2.i
77+
%tobool.not = icmp eq i32 %argc, 0
78+
br i1 %tobool.not, label %if.then.i, label %if.end.i
79+
80+
if.then.i: ; preds = %invoke.cont1
81+
invoke void @ThrowException()
82+
to label %unreachable unwind label %catch.dispatch
83+
84+
catch.dispatch: ; preds = %if.then.i, %invoke.cont2.i
85+
%2 = catchswitch within none [label %catch] unwind label %catch.dispatch3
86+
87+
catch: ; preds = %catch.dispatch
88+
%3 = catchpad within %2 [ptr null, i32 0, ptr null]
89+
invoke void @Exit() #0 [ "funclet"(token %3) ]
90+
to label %invoke.cont2 unwind label %catch.dispatch3
91+
92+
catch.dispatch3: ; preds = %catch, %catch.dispatch, %entry
93+
%4 = catchswitch within none [label %catch4] unwind to caller
94+
95+
catch4: ; preds = %catch.dispatch3
96+
%5 = catchpad within %4 [ptr null, i32 0, ptr null]
97+
catchret from %5 to label %try.cont5
98+
99+
try.cont5: ; preds = %catch4
100+
call void @Exit() #0
101+
unreachable
102+
103+
invoke.cont2: ; preds = %catch
104+
unreachable
105+
106+
if.end.i: ; preds = %invoke.cont1
107+
ret i32 %argc
108+
109+
unreachable: ; preds = %if.then
110+
unreachable
111+
}
112+
113+
define i32 @main(i32 noundef %argc, ptr noundef %argv) personality ptr @__CxxFrameHandler3 {
114+
; CHECK-LABEL: define i32 @main(
115+
; CHECK-SAME: i32 noundef [[ARGC:%.*]], ptr noundef [[ARGV:%.*]]) personality ptr @__CxxFrameHandler3 {
116+
; CHECK-NEXT: [[ENTRY:.*:]]
117+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
118+
; CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
119+
; CHECK: [[CATCH_DISPATCH]]:
120+
; CHECK-NEXT: [[TMP0:%.*]] = catchswitch within none [label %catch] unwind to caller
121+
; CHECK: [[CATCH:.*]]:
122+
; CHECK-NEXT: [[TMP1:%.*]] = catchpad within [[TMP0]] [ptr null, i32 0, ptr null]
123+
; CHECK-NEXT: catchret from [[TMP1]] to label %[[CLEANUP:.*]]
124+
; CHECK: [[INVOKE_CONT]]:
125+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
126+
; CHECK-NEXT: to label %[[INVOKE_CONT_I_I:.*]] unwind label %[[CATCH_DISPATCH_I_I:.*]]
127+
; CHECK: [[INVOKE_CONT_I_I]]:
128+
; CHECK-NEXT: [[CALL_I_I:%.*]] = invoke i32 @ThrowException()
129+
; CHECK-NEXT: to label %[[TRY_CONT_I_I:.*]] unwind label %[[CATCH_DISPATCH_I_I]]
130+
; CHECK: [[CATCH_DISPATCH_I_I]]:
131+
; CHECK-NEXT: [[TMP2:%.*]] = catchswitch within none [label %catch.i.i] unwind to caller
132+
; CHECK: [[CATCH_I_I:.*:]]
133+
; CHECK-NEXT: [[TMP3:%.*]] = catchpad within [[TMP2]] [ptr null, i32 0, ptr null]
134+
; CHECK-NEXT: catchret from [[TMP3]] to label %[[TRY_CONT_I_I]]
135+
; CHECK: [[TRY_CONT_I_I]]:
136+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
137+
; CHECK-NEXT: to label %[[INVOKE_CONT2_I_I:.*]] unwind label %[[CATCH_DISPATCH3_I:.*]]
138+
; CHECK: [[INVOKE_CONT2_I_I]]:
139+
; CHECK-NEXT: invoke void @llvm.seh.try.begin()
140+
; CHECK-NEXT: to label %[[INVOKE_CONT1_I:.*]] unwind label %[[CATCH_DISPATCH_I:.*]]
141+
; CHECK: [[INVOKE_CONT1_I]]:
142+
; CHECK-NEXT: [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[ARGC]], 0
143+
; CHECK-NEXT: br i1 [[TOBOOL_NOT_I]], label %[[IF_THEN_I_I:.*]], label %[[TRY_END:.*]]
144+
; CHECK: [[IF_THEN_I_I]]:
145+
; CHECK-NEXT: invoke void @ThrowException()
146+
; CHECK-NEXT: to label %[[UNREACHABLE_I:.*]] unwind label %[[CATCH_DISPATCH_I]]
147+
; CHECK: [[CATCH_DISPATCH_I]]:
148+
; CHECK-NEXT: [[TMP4:%.*]] = catchswitch within none [label %catch.i] unwind label %[[CATCH_DISPATCH3_I]]
149+
; CHECK: [[CATCH_I:.*:]]
150+
; CHECK-NEXT: [[TMP5:%.*]] = catchpad within [[TMP4]] [ptr null, i32 0, ptr null]
151+
; CHECK-NEXT: invoke void @Exit() #[[ATTR0]] [ "funclet"(token [[TMP5]]) ]
152+
; CHECK-NEXT: to label %[[INVOKE_CONT2_I:.*]] unwind label %[[CATCH_DISPATCH3_I]]
153+
; CHECK: [[CATCH_DISPATCH3_I]]:
154+
; CHECK-NEXT: [[TMP6:%.*]] = catchswitch within none [label %catch4.i] unwind to caller
155+
; CHECK: [[CATCH4_I:.*:]]
156+
; CHECK-NEXT: [[TMP7:%.*]] = catchpad within [[TMP6]] [ptr null, i32 0, ptr null]
157+
; CHECK-NEXT: catchret from [[TMP7]] to label %[[TRY_CONT5_I:.*]]
158+
; CHECK: [[TRY_CONT5_I]]:
159+
; CHECK-NEXT: call void @Exit() #[[ATTR0]]
160+
; CHECK-NEXT: unreachable
161+
; CHECK: [[INVOKE_CONT2_I]]:
162+
; CHECK-NEXT: unreachable
163+
; CHECK: [[TRY_END]]:
164+
; CHECK-NEXT: invoke void @llvm.seh.try.end()
165+
; CHECK-NEXT: to label %[[EXITONTHROW_EXIT:.*]] unwind label %[[CATCH_DISPATCH3_I]]
166+
; CHECK: [[UNREACHABLE_I]]:
167+
; CHECK-NEXT: unreachable
168+
; CHECK: [[EXITONTHROW_EXIT]]:
169+
; CHECK-NEXT: [[CALL1:%.*]] = call noundef i32 @AlwaysThrows(i32 noundef [[ARGC]])
170+
; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[ARGC]], [[CALL1]]
171+
; CHECK-NEXT: br label %[[CLEANUP]]
172+
; CHECK: [[CLEANUP]]:
173+
; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i32 [ [[ADD]], %[[EXITONTHROW_EXIT]] ], [ 123, %[[CATCH]] ]
174+
; CHECK-NEXT: ret i32 [[RETVAL_0]]
175+
;
176+
entry:
177+
invoke void @llvm.seh.try.begin()
178+
to label %invoke.cont unwind label %catch.dispatch
179+
180+
catch.dispatch: ; preds = %entry
181+
%0 = catchswitch within none [label %catch] unwind to caller
182+
183+
catch: ; preds = %catch.dispatch
184+
%1 = catchpad within %0 [ptr null, i32 0, ptr null]
185+
catchret from %1 to label %cleanup
186+
187+
invoke.cont: ; preds = %entry
188+
%call = call noundef i32 @ExitOnThrow(i32 noundef %argc)
189+
%call1 = call noundef i32 @AlwaysThrows(i32 noundef %call)
190+
%add = add nsw i32 %call, %call1
191+
br label %cleanup
192+
193+
cleanup: ; preds = %catch, %invoke.cont
194+
%retval.0 = phi i32 [ %add, %invoke.cont ], [ 123, %catch ]
195+
ret i32 %retval.0
196+
}
197+
198+
declare void @llvm.seh.try.begin()
199+
declare i32 @__CxxFrameHandler3(...)
200+
declare void @ThrowException()
201+
declare void @Exit() #0
202+
declare i32 @AlwaysThrows(i32 %id)
203+
204+
attributes #0 = { noreturn }
205+
206+
!llvm.module.flags = !{!0}
207+
!0 = !{i32 2, !"eh-asynch", i32 1}

0 commit comments

Comments
 (0)