Skip to content

Commit 5393090

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 5393090

File tree

4 files changed

+571
-0
lines changed

4 files changed

+571
-0
lines changed

llvm/lib/Transforms/Utils/InlineFunction.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "llvm/Analysis/AliasAnalysis.h"
2222
#include "llvm/Analysis/AssumptionCache.h"
2323
#include "llvm/Analysis/BlockFrequencyInfo.h"
24+
#include "llvm/Analysis/CFG.h"
2425
#include "llvm/Analysis/CallGraph.h"
2526
#include "llvm/Analysis/CaptureTracking.h"
2627
#include "llvm/Analysis/CtxProfAnalysis.h"
@@ -2210,6 +2211,135 @@ inlineRetainOrClaimRVCalls(CallBase &CB, objcarc::ARCInstKind RVCallKind,
22102211
}
22112212
}
22122213

2214+
// Determine SEH try scopes and order them by dominance.
2215+
static SmallVector<llvm::InvokeInst *, 1>
2216+
GetOrderedSehTryScopes(Function *Func, DominatorTree &DT) {
2217+
SmallVector<llvm::InvokeInst *, 1> Scopes{};
2218+
bool DTCalculated = false;
2219+
2220+
for (auto &BB : *Func) {
2221+
auto *TI = BB.getTerminator();
2222+
if (auto *II = dyn_cast<InvokeInst>(TI)) {
2223+
auto *Call = cast<CallBase>(II);
2224+
const auto *Fn = Call->getCalledFunction();
2225+
if (!Fn || !Fn->isIntrinsic())
2226+
continue;
2227+
2228+
if (Fn->getIntrinsicID() != Intrinsic::seh_try_begin)
2229+
continue;
2230+
2231+
if (!DTCalculated) {
2232+
DT.recalculate(*Func);
2233+
DTCalculated = true;
2234+
}
2235+
2236+
auto InsertIt = Scopes.end();
2237+
if (!Scopes.empty())
2238+
for (auto ScopeIt = Scopes.begin(); ScopeIt != Scopes.end(); ++ScopeIt)
2239+
if (DT.dominates((*ScopeIt)->getParent(), &BB))
2240+
InsertIt = std::next(ScopeIt);
2241+
2242+
Scopes.insert(InsertIt, II);
2243+
}
2244+
}
2245+
2246+
return Scopes;
2247+
}
2248+
2249+
// Find, if present, the outermost unterminated try scope for the input block.
2250+
static llvm::InvokeInst *GetOutermostUnterminatedTryScopeBegin(
2251+
llvm::BasicBlock *ReturnBlock, SmallVector<llvm::InvokeInst *, 1> &Scopes,
2252+
DominatorTree &DT) {
2253+
llvm::InvokeInst *UnterminatedScope{nullptr};
2254+
2255+
for (auto ScopeIt = Scopes.rbegin(); ScopeIt != Scopes.rend(); ++ScopeIt) {
2256+
auto *Invoke = *ScopeIt;
2257+
2258+
// Return might not be in a try scope.
2259+
if (!DT.dominates(Invoke->getParent(), ReturnBlock))
2260+
continue;
2261+
2262+
// If there is a catch which connects to the return, the try scope is
2263+
// considered to be terminated.
2264+
if (isPotentiallyReachable(Invoke->getUnwindDest(), ReturnBlock, nullptr,
2265+
&DT))
2266+
continue;
2267+
2268+
UnterminatedScope = Invoke;
2269+
}
2270+
2271+
return UnterminatedScope;
2272+
}
2273+
2274+
struct UnterminatedTryScope {
2275+
llvm::BasicBlock *ReturnBlock;
2276+
llvm::BasicBlock *TryStart;
2277+
llvm::BasicBlock *UnwindDestination;
2278+
};
2279+
2280+
// For Windows -EHa there may be the case of try scopes spanning over a return
2281+
// instruction. If no other return out of the function is given (e.g. by letting
2282+
// all other blocks terminate with unreachable) the try scope is considered
2283+
// unterminated upon inlining. To avoid leaking the try scopes over to a caller
2284+
// we need to add a terminator for the outermost unterminated try scope.
2285+
static SmallVector<UnterminatedTryScope, 1>
2286+
GetUnterminatedTryScopes(Function *CalledFunc) {
2287+
SmallVector<UnterminatedTryScope, 1> UnterminatedScopes;
2288+
DominatorTree DT;
2289+
auto Scopes = GetOrderedSehTryScopes(CalledFunc, DT);
2290+
if (Scopes.empty())
2291+
return UnterminatedScopes;
2292+
2293+
SmallVector<ReturnInst *, 8> Returns;
2294+
for (auto &BB : *CalledFunc)
2295+
if (ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator()))
2296+
Returns.push_back(RI);
2297+
2298+
for (auto *RI : Returns) {
2299+
auto *Block = RI->getParent();
2300+
auto *OutermostScope =
2301+
GetOutermostUnterminatedTryScopeBegin(Block, Scopes, DT);
2302+
if (!OutermostScope)
2303+
continue;
2304+
2305+
UnterminatedScopes.push_back(
2306+
{Block, OutermostScope->getParent(), OutermostScope->getUnwindDest()});
2307+
}
2308+
2309+
return UnterminatedScopes;
2310+
}
2311+
2312+
// Insert terminator for unterminated try scopes.
2313+
static void HandleUnterminatedTryScopes(
2314+
Function *Caller,
2315+
SmallVector<UnterminatedTryScope, 1> &UnterminatedTryScopes,
2316+
ValueToValueMapTy &VMap) {
2317+
for (auto &Scope : UnterminatedTryScopes) {
2318+
auto *ReturnBlock = cast<BasicBlock>(VMap[Scope.ReturnBlock]);
2319+
auto *TryStart = cast<BasicBlock>(VMap[Scope.TryStart]);
2320+
auto *UnwindDestination = cast<BasicBlock>(VMap[Scope.UnwindDestination]);
2321+
// did not survive (partial) inlining - ignore
2322+
if (!ReturnBlock || !TryStart || !UnwindDestination)
2323+
continue;
2324+
2325+
auto *Mod = (llvm::Module *)Caller->getParent();
2326+
auto *SehTryEndFn =
2327+
Intrinsic::getOrInsertDeclaration(Mod, Intrinsic::seh_try_end);
2328+
auto *BB = ReturnBlock->splitBasicBlockBefore(ReturnBlock->getTerminator(),
2329+
"try.end");
2330+
BB->getTerminator()->eraseFromParent();
2331+
IRBuilder<>(BB).CreateInvoke(SehTryEndFn, ReturnBlock, UnwindDestination);
2332+
2333+
for (auto &Insn : *UnwindDestination) {
2334+
if (auto *Phi = dyn_cast<PHINode>(&Insn)) {
2335+
auto *ValType = Phi->getIncomingValueForBlock(TryStart)->getType();
2336+
auto *Val = PoisonValue::get(ValType);
2337+
Phi->addIncoming(Val, BB);
2338+
}
2339+
}
2340+
}
2341+
}
2342+
22132343
// In contextual profiling, when an inline succeeds, we want to remap the
22142344
// indices of the callee into the index space of the caller. We can't just leave
22152345
// them as-is because the same callee may appear in other places in this caller
@@ -2625,6 +2755,7 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
26252755
SmallVector<ReturnInst*, 8> Returns;
26262756
ClonedCodeInfo InlinedFunctionInfo;
26272757
Function::iterator FirstNewBlock;
2758+
SmallVector<UnterminatedTryScope, 1> UnterminatedTryScopes;
26282759

26292760
// GC poses two hazards to inlining, which only occur when the callee has GC:
26302761
// 1. If the caller has no GC, then the callee's GC must be propagated to the
@@ -2648,6 +2779,11 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
26482779
assert(Caller->getPersonalityFn()->stripPointerCasts() ==
26492780
CalledPersonality &&
26502781
"CanInlineCallSite should have verified compatible personality");
2782+
2783+
const llvm::Module *M = CalledFunc->getParent();
2784+
if (M->getModuleFlag("eh-asynch")) {
2785+
UnterminatedTryScopes = GetUnterminatedTryScopes(CalledFunc);
2786+
}
26512787
}
26522788

26532789
{ // Scope to destroy VMap after cloning.
@@ -2837,6 +2973,8 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
28372973
for (Instruction &I : NewBlock)
28382974
if (auto *II = dyn_cast<AssumeInst>(&I))
28392975
IFI.GetAssumptionCache(*Caller).registerAssumption(II);
2976+
2977+
HandleUnterminatedTryScopes(Caller, UnterminatedTryScopes, VMap);
28402978
}
28412979

28422980
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)