Skip to content

Commit 8d231d2

Browse files
committed
Rewrite StackNesting to be a non-iterative single-pass algorithm.
The previous algorithm was doing an iterative forward data flow analysis followed by a reverse data flow analysis. I suspect the history here is that it was a reverse analysis, and that didn't really work for infinite loops, and so complexity accumulated. The new algorithm is quite straightforward and relies on the allocations being properly jointly post-dominated, just not nested. We simply walk forward through the blocks in consistent-with-dominance order, maintaining the stack of active allocations and deferring deallocations that are improperly nested until we deallocate the allocations above it. The only real subtlety is that we have to delay walking into dead-end regions until we've seen all of the edges into them, so that we can know whether we have a coherent stack state in them. If the state is incoherent, we need to remove any deallocations of previous allocations because we cannot talk correctly about what's on top of the stack. The reason I'm doing this, besides it just being a simpler and hopefully faster algorithm, is that modeling some of the uses of the async stack allocator properly requires builtins that cannot just be semantically reordered. That should be somewhat easier to handle with the new approach, although really (1) we should not have runtime functions that need this and (2) we're going to need a conservatively-correct solution that's different from this anyway because hoisting allocations is *also* limited in its own way. I've attached a rather pedantic proof of the correctness of the algorithm. The thing that concerns me most about the rewritten pass is that it isn't actually validating joint post-dominance on input, so if you give it bad input, it might be a little mystifying to debug the verifier failures.
1 parent 164d3ae commit 8d231d2

File tree

7 files changed

+5496
-485
lines changed

7 files changed

+5496
-485
lines changed

docs/SIL/SIL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,8 @@ scope-ending instructions if:
866866
through a scope-ending instruction of `I` in between.
867867

868868
- All initial paths that pass through a scope-ending instruction
869-
of `I` twice must also pass through `I` in between.
869+
of `I` twice (not necessarily the same instruction) must also
870+
pass through `I` in between.
870871

871872
- All terminating initial paths that pass through `I` must also
872873
pass through a scope-ending instruction of `I`.

docs/StackNestingProof.txt

Lines changed: 4586 additions & 0 deletions
Large diffs are not rendered by default.

include/swift/SILOptimizer/Utils/StackNesting.h

Lines changed: 4 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ namespace swift {
4545
/// dealloc_stack %1
4646
/// \endcode
4747
///
48+
/// Each allocation must still be properly jointly post-dominated by
49+
/// its deallocations. StackNesting only fixes the nesting of allocations
50+
/// deallocations; it does not insert required deallocations that are
51+
/// missing entirely.
4852
class StackNesting {
49-
5053
public:
5154

5255
/// The possible return values of fixNesting().
@@ -61,122 +64,6 @@ class StackNesting {
6164
CFG
6265
};
6366

64-
private:
65-
typedef SmallBitVector BitVector;
66-
67-
/// Data stored for each block (actually for each block which is not dead).
68-
struct BlockInfo {
69-
/// The list of stack allocating/deallocating instructions in the block.
70-
llvm::SmallVector<SILInstruction *, 8> StackInsts;
71-
72-
/// The bit-set of alive stack locations at the block entry.
73-
BitVector AliveStackLocsAtEntry;
74-
75-
/// The bit-set of alive stack locations at the block exit.
76-
BitVector AliveStackLocsAtExit;
77-
78-
/// Used in the setup function to walk over the CFG.
79-
bool visited = false;
80-
81-
/// True for dead-end blocks, i.e. blocks from which there is no path to
82-
/// a function exit, e.g. blocks which end with `unreachable` or an
83-
/// infinite loop.
84-
bool isDeadEnd = false;
85-
};
86-
87-
/// Data stored for each stack location (= allocation).
88-
///
89-
/// Each stack location is allocated by a single allocation instruction.
90-
struct StackLoc {
91-
StackLoc(SILInstruction *Alloc) : Alloc(Alloc) {}
92-
93-
/// Back-link to the allocation instruction.
94-
SILInstruction *Alloc;
95-
96-
/// Bit-set which represents all alive locations at this allocation.
97-
/// It obviously includes this location itself. And it includes all "outer"
98-
/// locations which surround this location.
99-
BitVector AliveLocs;
100-
};
101-
102-
/// Mapping from stack allocations (= locations) to bit numbers.
103-
llvm::DenseMap<SILInstruction *, unsigned> StackLoc2BitNumbers;
104-
105-
/// The list of stack locations. The index into this array is also the bit
106-
/// number in the bit-sets.
107-
llvm::SmallVector<StackLoc, 8> StackLocs;
108-
109-
BasicBlockData<BlockInfo> BlockInfos;
110-
111-
StackNesting(SILFunction *F) : BlockInfos(F) { }
112-
113-
/// Performs correction of stack nesting by moving stack-deallocation
114-
/// instructions down the control flow.
115-
///
116-
/// Returns the status of what changes were made.
117-
Changes run();
118-
119-
/// For debug dumping.
120-
void dump() const;
121-
122-
static void dumpBits(const BitVector &Bits);
123-
124-
/// Initializes the data structures.
125-
void setup();
126-
127-
/// Solves the dataflow problem.
128-
///
129-
/// Returns true if there is a nesting of locations in any way, which can
130-
/// potentially in the wrong order.
131-
bool solve();
132-
133-
bool analyze() {
134-
setup();
135-
return solve();
136-
}
137-
138-
/// Insert deallocation instructions for all locations which are alive before
139-
/// the InsertionPoint (AliveBefore) but not alive after the InsertionPoint
140-
/// (AliveAfter).
141-
///
142-
/// Returns true if any deallocations were inserted.
143-
bool insertDeallocs(const BitVector &AliveBefore, const BitVector &AliveAfter,
144-
SILInstruction *InsertionPoint,
145-
std::optional<SILLocation> Location);
146-
147-
/// Returns the location bit number for a stack allocation instruction.
148-
int bitNumberForAlloc(SILInstruction *AllocInst) {
149-
assert(AllocInst->isAllocatingStack());
150-
return StackLoc2BitNumbers[AllocInst];
151-
}
152-
153-
/// Returns the location bit number for a stack deallocation instruction.
154-
int bitNumberForDealloc(SILInstruction *DeallocInst) {
155-
assert(DeallocInst->isDeallocatingStack());
156-
auto *AllocInst = getAllocForDealloc(DeallocInst);
157-
return bitNumberForAlloc(AllocInst);
158-
}
159-
160-
/// Returns the stack allocation instruction for a stack deallocation
161-
/// instruction.
162-
SILInstruction *getAllocForDealloc(SILInstruction *Dealloc) const {
163-
SILValue op = Dealloc->getOperand(0);
164-
while (auto *mvi = dyn_cast<MoveValueInst>(op)) {
165-
op = mvi->getOperand();
166-
}
167-
return op->getDefiningInstruction();
168-
}
169-
170-
/// Insert deallocations at block boundaries.
171-
Changes insertDeallocsAtBlockBoundaries();
172-
173-
/// Modifies the SIL to end up with a correct stack nesting.
174-
///
175-
/// Returns the status of what changes were made.
176-
Changes adaptDeallocs();
177-
178-
public:
179-
18067
/// Performs correction of stack nesting by moving stack-deallocation
18168
/// instructions down the control flow.
18269
///

0 commit comments

Comments
 (0)