Skip to content

Commit 9718e5a

Browse files
authored
Merge pull request #22285 from eeckstein/fix-inlining-stack-nesting
2 parents d3dfbdb + 767ad5e commit 9718e5a

File tree

7 files changed

+70
-157
lines changed

7 files changed

+70
-157
lines changed

include/swift/SILOptimizer/Utils/SILInliner.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ class SILInliner {
6767
/// attempts to inline \arg AI and this returns false, an assert will fire.
6868
static bool canInlineApplySite(FullApplySite apply);
6969

70+
/// Returns true if inlining \arg apply can result in improperly nested stack
71+
/// allocations.
72+
///
73+
/// In this case stack nesting must be corrected after inlining with the
74+
/// StackNesting utility.
75+
static bool needsUpdateStackNesting(FullApplySite apply) {
76+
// Inlining of coroutines can result in improperly nested stack
77+
// allocations.
78+
return isa<BeginApplyInst>(apply);
79+
}
80+
7081
/// Allow the client to track instructions before they are deleted. The
7182
/// registered callback is called from
7283
/// recursivelyDeleteTriviallyDeadInstructions.
@@ -99,6 +110,9 @@ class SILInliner {
99110
/// to be inlined using SILInliner::canInlineApplySite before calling this
100111
/// function.
101112
///
113+
/// *NOTE*: Inlining can result in improperly nested stack allocations, which
114+
/// must be corrected after inlining. See needsUpdateStackNesting().
115+
///
102116
/// Returns an iterator to the first inlined instruction (or the end of the
103117
/// caller block for empty functions) and the last block in function order
104118
/// containing inlined instructions (the original caller block for

include/swift/SILOptimizer/Utils/StackNesting.h

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,6 @@
2020

2121
namespace swift {
2222

23-
struct InstructionMatchResult {
24-
/// The instruction matches.
25-
bool matches;
26-
27-
/// The search should be halted.
28-
bool halt;
29-
};
30-
31-
using InstructionMatcher =
32-
llvm::function_ref<InstructionMatchResult(SILInstruction *i)>;
33-
34-
/// Given a predicate defining interesting instructions, check whether
35-
/// the stack is different at any of them from how it stood at the start
36-
/// of the search.
37-
///
38-
/// The matcher function must halt the search before any edges which would
39-
/// lead back to before the start point.
40-
bool hasStackDifferencesAt(SILInstruction *start, InstructionMatcher matcher);
41-
4223
/// A utility to correct the nesting of stack allocating/deallocating
4324
/// instructions.
4425
///

lib/SILOptimizer/Mandatory/MandatoryInlining.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/SILOptimizer/Utils/Local.h"
2626
#include "swift/SILOptimizer/Utils/SILInliner.h"
2727
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
28+
#include "swift/SILOptimizer/Utils/StackNesting.h"
2829
#include "llvm/ADT/DenseSet.h"
2930
#include "llvm/ADT/ImmutableSet.h"
3031
#include "llvm/ADT/Statistic.h"
@@ -768,6 +769,7 @@ runOnFunctionRecursively(SILOptFunctionBuilder &FuncBuilder,
768769

769770
SmallVector<ParameterConvention, 16> CapturedArgConventions;
770771
SmallVector<SILValue, 32> FullArgs;
772+
bool needUpdateStackNesting = false;
771773

772774
// Visiting blocks in reverse order avoids revisiting instructions after block
773775
// splitting, which would be quadratic.
@@ -886,6 +888,8 @@ runOnFunctionRecursively(SILOptFunctionBuilder &FuncBuilder,
886888
closureCleanup.recordDeadFunction(I);
887889
});
888890

891+
needUpdateStackNesting |= Inliner.needsUpdateStackNesting(InnerAI);
892+
889893
// Inlining deletes the apply, and can introduce multiple new basic
890894
// blocks. After this, CalleeValue and other instructions may be invalid.
891895
// nextBB will point to the last inlined block
@@ -905,6 +909,11 @@ runOnFunctionRecursively(SILOptFunctionBuilder &FuncBuilder,
905909
break;
906910
}
907911
}
912+
913+
if (needUpdateStackNesting) {
914+
StackNesting().correctStackNesting(F);
915+
}
916+
908917
// Keep track of full inlined functions so we don't waste time recursively
909918
// reprocessing them.
910919
FullyInlinedSet.insert(F);

lib/SILOptimizer/Transforms/PerformanceInliner.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/SILOptimizer/Utils/Generics.h"
2323
#include "swift/SILOptimizer/Utils/PerformanceInlinerUtils.h"
2424
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
25+
#include "swift/SILOptimizer/Utils/StackNesting.h"
2526
#include "llvm/ADT/Statistic.h"
2627
#include "llvm/Support/CommandLine.h"
2728
#include "llvm/Support/Debug.h"
@@ -877,6 +878,7 @@ bool SILPerformanceInliner::inlineCallsIntoFunction(SILFunction *Caller) {
877878
// remains valid.
878879
SmallVector<FullApplySite, 8> AppliesToInline;
879880
collectAppliesToInline(Caller, AppliesToInline);
881+
bool needUpdateStackNesting = false;
880882

881883
if (AppliesToInline.empty())
882884
return false;
@@ -909,6 +911,8 @@ bool SILPerformanceInliner::inlineCallsIntoFunction(SILFunction *Caller) {
909911
SILInliner Inliner(FuncBuilder, SILInliner::InlineKind::PerformanceInline,
910912
AI.getSubstitutionMap(), OpenedArchetypesTracker);
911913

914+
needUpdateStackNesting |= Inliner.needsUpdateStackNesting(AI);
915+
912916
// We've already determined we should be able to inline this, so
913917
// unconditionally inline the function.
914918
//
@@ -920,6 +924,11 @@ bool SILPerformanceInliner::inlineCallsIntoFunction(SILFunction *Caller) {
920924
// The inliner splits blocks at call sites. Re-merge trivial branches to
921925
// reestablish a canonical CFG.
922926
mergeBasicBlocks(Caller);
927+
928+
if (needUpdateStackNesting) {
929+
StackNesting().correctStackNesting(Caller);
930+
}
931+
923932
return true;
924933
}
925934

lib/SILOptimizer/Utils/SILInliner.cpp

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,54 +17,10 @@
1717
#include "swift/SIL/TypeSubstCloner.h"
1818
#include "swift/SILOptimizer/Utils/CFG.h"
1919
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
20-
#include "swift/SILOptimizer/Utils/StackNesting.h"
2120
#include "llvm/ADT/STLExtras.h"
2221
#include "llvm/Support/Debug.h"
2322
using namespace swift;
2423

25-
/// Does the given coroutine make any stack allocations that are live across
26-
/// its yields?
27-
static bool allocatesStackAcrossYields(SILFunction *F) {
28-
assert(F->getLoweredFunctionType()->isCoroutine());
29-
30-
return hasStackDifferencesAt(&F->getEntryBlock()->front(),
31-
[](SILInstruction *i) -> InstructionMatchResult {
32-
if (isa<YieldInst>(i)) {
33-
return {
34-
/*matches*/ true,
35-
/*halt*/ i->getFunction()->getLoweredFunctionType()->getCoroutineKind()
36-
== SILCoroutineKind::YieldOnce
37-
};
38-
}
39-
40-
// Otherwise, search until the end of the function.
41-
return { false, false };
42-
});
43-
}
44-
45-
static bool isEndOfApply(SILInstruction *i, BeginApplyInst *beginApply) {
46-
if (auto endApply = dyn_cast<EndApplyInst>(i)) {
47-
return endApply->getBeginApply() == beginApply;
48-
} else if (auto abortApply = dyn_cast<AbortApplyInst>(i)) {
49-
return abortApply->getBeginApply() == beginApply;
50-
} else {
51-
return false;
52-
}
53-
}
54-
55-
/// Are there any stack differences from the given begin_apply to any
56-
/// corresponding end_apply/abort_apply?
57-
static bool hasStackDifferencesAtEnds(BeginApplyInst *apply) {
58-
return hasStackDifferencesAt(apply, [apply](SILInstruction *i)
59-
-> InstructionMatchResult {
60-
// Search for ends of the original apply. We can stop searching
61-
// at these points.
62-
if (isEndOfApply(i, apply))
63-
return { true, true };
64-
return { false, false };
65-
});
66-
}
67-
6824
static bool canInlineBeginApply(BeginApplyInst *BA) {
6925
// Don't inline if we have multiple resumption sites (i.e. end_apply or
7026
// abort_apply instructions). The current implementation clones a single
@@ -122,7 +78,6 @@ class BeginApplySite {
12278
SILBuilder *Builder;
12379
BeginApplyInst *BeginApply;
12480
bool HasYield = false;
125-
bool NeedsStackCorrection;
12681

12782
EndApplyInst *EndApply = nullptr;
12883
SILBasicBlock *EndApplyBB = nullptr;
@@ -134,31 +89,15 @@ class BeginApplySite {
13489

13590
public:
13691
BeginApplySite(BeginApplyInst *BeginApply, SILLocation Loc,
137-
SILBuilder *Builder, bool NeedsStackCorrection)
138-
: Loc(Loc), Builder(Builder), BeginApply(BeginApply),
139-
NeedsStackCorrection(NeedsStackCorrection) {}
92+
SILBuilder *Builder)
93+
: Loc(Loc), Builder(Builder), BeginApply(BeginApply) {}
14094

14195
static Optional<BeginApplySite> get(FullApplySite AI, SILLocation Loc,
14296
SILBuilder *Builder) {
14397
auto *BeginApply = dyn_cast<BeginApplyInst>(AI);
14498
if (!BeginApply)
14599
return None;
146-
147-
// We need stack correction if there are both:
148-
// - stack allocations in the callee that are live across the yield and
149-
// - stack differences in the caller from the begin_apply to any
150-
// end_apply or abort_apply.
151-
// In these cases, naive cloning will cause the allocations to become
152-
// improperly nested.
153-
//
154-
// We need to compute this here before we do any splitting in the parent
155-
// function.
156-
bool NeedsStackCorrection = false;
157-
if (allocatesStackAcrossYields(BeginApply->getReferencedFunction()) &&
158-
hasStackDifferencesAtEnds(BeginApply))
159-
NeedsStackCorrection = true;
160-
161-
return BeginApplySite(BeginApply, Loc, Builder, NeedsStackCorrection);
100+
return BeginApplySite(BeginApply, Loc, Builder);
162101
}
163102

164103
void preprocess(SILBasicBlock *returnToBB) {
@@ -286,11 +225,6 @@ class BeginApplySite {
286225
AbortApply->eraseFromParent();
287226

288227
assert(!BeginApply->hasUsesOfAnyResult());
289-
290-
// Correct the stack if necessary.
291-
if (NeedsStackCorrection) {
292-
StackNesting().correctStackNesting(BeginApply->getFunction());
293-
}
294228
}
295229
};
296230
} // namespace swift

lib/SILOptimizer/Utils/StackNesting.cpp

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -18,75 +18,6 @@
1818

1919
using namespace swift;
2020

21-
bool swift::hasStackDifferencesAt(SILInstruction *start,
22-
InstructionMatcher matcher) {
23-
struct StackStatus {
24-
/// The number of currently-active stack allocations on this path.
25-
/// Because of the stack discipline, we only have to maintain a count.
26-
unsigned depth;
27-
28-
/// Whether we've popped anything off of the stack that was active
29-
/// at the start of the search.
30-
bool hasPops;
31-
32-
bool hasDifferences() const { return hasPops || depth != 0; }
33-
};
34-
35-
SmallPtrSet<SILBasicBlock*, 8> visited;
36-
SmallVector<std::pair<SILInstruction *, StackStatus>, 8> worklist;
37-
worklist.push_back({start, {0, false}});
38-
39-
while (!worklist.empty()) {
40-
// Pull a block and depth off the worklist.
41-
// Visitation order doesn't matter.
42-
auto pair = worklist.pop_back_val();
43-
auto status = pair.second;
44-
auto firstInst = pair.first;
45-
auto block = firstInst->getParent();
46-
47-
for (SILBasicBlock::iterator i(firstInst), e = block->end(); i != e; ++i) {
48-
auto match = matcher(&*i);
49-
50-
// If this is an interesting instruction, check for stack
51-
// differences here.
52-
if (match.matches && status.hasDifferences())
53-
return true;
54-
55-
// If we should halt, just go to the next work-list item, bypassing
56-
// visiting the successors.
57-
if (match.halt)
58-
goto nextWorkListItem; // a labelled continue would be nice
59-
60-
// Otherwise, do stack-depth bookkeeping.
61-
if (i->isAllocatingStack()) {
62-
status.depth++;
63-
} else if (i->isDeallocatingStack()) {
64-
// If the stack depth is already zero, we're deallocating a stack
65-
// allocation that was live into the search.
66-
if (status.depth > 0) {
67-
status.depth--;
68-
} else {
69-
status.hasPops = true;
70-
}
71-
}
72-
}
73-
74-
// Add the successors to the worklist.
75-
for (auto &succ : block->getSuccessors()) {
76-
auto succBlock = succ.getBB();
77-
auto insertResult = visited.insert(succBlock);
78-
if (insertResult.second) {
79-
worklist.push_back({&succBlock->front(), status});
80-
}
81-
}
82-
83-
nextWorkListItem: ;
84-
}
85-
86-
return false;
87-
}
88-
89-
9021
void StackNesting::setup(SILFunction *F) {
9122
SmallVector<BlockInfo *, 8> WorkList;
9223
llvm::DenseMap<SILBasicBlock *, BlockInfo *> BlockMapping;

test/SILOptimizer/inline_begin_apply.sil

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,38 @@ bb0:
485485
%ret = tuple ()
486486
return %ret : $()
487487
}
488+
489+
// CHECK-LABEL: sil [ossa] @test_stack_overlap_unreachable
490+
// CHECK: bb0:
491+
// CHECK: [[A16:%.*]] = alloc_stack $Builtin.Int16
492+
// CHECK: [[A32:%.*]] = alloc_stack $Builtin.Int32
493+
// CHECK: bb1:
494+
// CHECK: dealloc_stack [[A32]] : $*Builtin.Int32
495+
// CHECK: unreachable
496+
// CHECK: bb2:
497+
// CHECK: dealloc_stack [[A32]] : $*Builtin.Int32
498+
// CHECK: dealloc_stack [[A16]] : $*Builtin.Int16
499+
// CHECK: unreachable
500+
// CHECK: bb3:
501+
// CHECK: dealloc_stack [[A32]] : $*Builtin.Int32
502+
// CHECK: dealloc_stack [[A16]] : $*Builtin.Int16
503+
// CHECK: return
504+
// CHECK-LABEL: // end sil function 'test_stack_overlap_unreachable'
505+
sil [ossa] @test_stack_overlap_unreachable : $() -> () {
506+
bb0:
507+
%stack = alloc_stack $Builtin.Int16
508+
%0 = function_ref @stack_overlap : $@convention(thin) @yield_once () -> (@yields @inout Builtin.Int32)
509+
(%value, %token) = begin_apply %0() : $@convention(thin) @yield_once () -> (@yields @inout Builtin.Int32)
510+
cond_br undef, bb1, bb2
511+
512+
bb1:
513+
dealloc_stack %stack : $*Builtin.Int16
514+
unreachable
515+
516+
bb2:
517+
end_apply %token
518+
dealloc_stack %stack : $*Builtin.Int16
519+
%ret = tuple ()
520+
return %ret : $()
521+
}
522+

0 commit comments

Comments
 (0)