Skip to content

Commit fdcb46e

Browse files
committed
SIL: Utilities for calculating and verifying memory lifetimes.
In analogy to the ownership verifier, the MemoryLifetimeVerifier checks the lifetime of memory locations. It's based on MemoryLocations and MemoryDataflow, which are general utilities for analysing memory locations and calculating the lifetime of locations. Memory locations are limited to addresses which are guaranteed to be not aliased, like @in/inout parameters or alloc_stack.
1 parent f26a9b9 commit fdcb46e

File tree

8 files changed

+1555
-5
lines changed

8 files changed

+1555
-5
lines changed

include/swift/SIL/MemoryLifetime.h

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
//===--- MemoryLifetime.h ---------------------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// \file Contains utilities for calculating and verifying memory lifetime.
14+
///
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_SIL_MEMORY_LIFETIME_H
18+
#define SWIFT_SIL_MEMORY_LIFETIME_H
19+
20+
#include "swift/SIL/SILBasicBlock.h"
21+
#include "swift/SIL/SILFunction.h"
22+
23+
namespace swift {
24+
25+
/// The MemoryLocations utility provides functions to analyze memory locations.
26+
///
27+
/// Memory locations are limited to addresses which are guaranteed to
28+
/// be not aliased, like @in/inout parameters and alloc_stack.
29+
/// Currently only a certain set of address instructions are supported:
30+
/// Specifically those instructions which are going to be included when SIL
31+
/// supports opaque values.
32+
/// TODO: Support more address instructions, like cast instructions.
33+
///
34+
/// The MemoryLocations works well together with MemoryDataflow, which can be
35+
/// used to calculate global dataflow of location information.
36+
class MemoryLocations {
37+
public:
38+
39+
using Bits = llvm::SmallBitVector;
40+
41+
/// Represents a not-aliased memory location: either an indirect function
42+
/// parameter or an alloc_stack.
43+
///
44+
/// Each location has a unique number which is index in the
45+
/// MemoryLifetime::locations array and the bit number in the bit sets.
46+
///
47+
/// Locations can have sub-locations in case the parent location is a struct
48+
/// or tuple with fields/elements. So, each top-level location forms a
49+
/// tree-like data structure. Sub-locations are only created lazily, i.e. if
50+
/// struct/tuple elements are really accessed with struct/tuple_element_addr.
51+
///
52+
/// As most alloc_stack locations only live within a single block, such
53+
/// single-block locations are not included in the "regular" data flow
54+
/// analysis (to not blow up the bit vectors). They are handled separately
55+
/// with a simple single-block data flow analysis, which runs independently
56+
/// for each block.
57+
struct Location {
58+
59+
/// The SIL value of the memory location.
60+
///
61+
/// For top-level locations this is either a function argument or an
62+
/// alloc_stack. For sub-locations it's the struct/tuple_element_addr.
63+
/// In case there are multiple struct/tuple_element_addr for a single
64+
/// field, this is only one representative instruction out of the set.
65+
SILValue representativeValue;
66+
67+
/// All tracked sub-locations.
68+
///
69+
/// If all tracked sub-locations cover the whole memory location, the "self"
70+
/// bit is not set. In other words: the "self" bit represents all
71+
/// sublocations, which are not explicitly tracked as locations.
72+
/// For example:
73+
/// \code
74+
/// struct Inner {
75+
/// var a: T
76+
/// var b: T
77+
/// }
78+
/// struct Outer {
79+
/// var x: T
80+
/// var y: Inner
81+
/// var z: T // not accessed in the analyzed function
82+
/// }
83+
/// \endcode
84+
///
85+
/// If the analyzed function contains:
86+
/// \code
87+
/// %a = alloc_stack $Outer // = location 0
88+
/// %ox = struct_element_adr %a, #Outer.x // = location 1
89+
/// %oy = struct_element_adr %a, #Outer.y // = location 2
90+
/// %ia = struct_element_adr %oy, #Inner.a // = location 3
91+
/// %ib = struct_element_adr %oy, #Inner.b // = location 4
92+
/// \endcode
93+
///
94+
/// the ``subLocations`` bits are:
95+
/// \code
96+
/// location 0 (alloc_stack): [0, 1, 3, 4]
97+
/// location 1 (Outer.x): [ 1 ]
98+
/// location 2 (Outer.y): [ 3, 4]
99+
/// location 3 (Inner.a): [ 3 ]
100+
/// location 4 (Inner.b): [ 4]
101+
/// \endcode
102+
///
103+
/// Bit 2 is never set because Inner is completly represented by its
104+
/// sub-locations 3 and 4. But bit 0 is set in location 0 (the "self" bit),
105+
/// because it represents the untracked field ``Outer.z``.
106+
Bits subLocations;
107+
108+
/// The accumulated parent bits, including the "self" bit.
109+
///
110+
/// For the example given for ``subLocations``, the ``selfAndParents`` bits
111+
/// are:
112+
/// \code
113+
/// location 0 (alloc_stack): [0 ]
114+
/// location 1 (Outer.x): [0, 1 ]
115+
/// location 2 (Outer.y): [0, 2 ]
116+
/// location 3 (Inner.a): [0, 2, 3 ]
117+
/// location 4 (Inner.b): [0, 2, 4]
118+
/// \endcode
119+
Bits selfAndParents;
120+
121+
/// The location index of the parent, or -1 if it's a top-level location.
122+
///
123+
/// For the example given for ``subLocations``, the ``parentIdx`` indices
124+
/// are:
125+
/// \code
126+
/// location 0 (alloc_stack): -1
127+
/// location 1 (Outer.x): 0
128+
/// location 2 (Outer.y): 0
129+
/// location 3 (Inner.a): 2
130+
/// location 4 (Inner.b): 2
131+
/// \endcode
132+
int parentIdx;
133+
134+
/// Used to decide if a location is completely covered by its sub-locations.
135+
///
136+
/// -1 means: not yet initialized.
137+
int numFieldsNotCoveredBySubfields = -1;
138+
139+
Location(SILValue val, unsigned index, int parentIdx = -1);
140+
};
141+
142+
private:
143+
/// The array of locations.
144+
llvm::SmallVector<Location, 64> locations;
145+
146+
/// Mapping from SIL values (function arguments and alloc_stack) to location
147+
/// indices.
148+
///
149+
/// In case there are multiple struct/tuple_element_addr for a single
150+
/// field, this map contains multiple entries mapping to the same location.
151+
llvm::DenseMap<SILValue, unsigned> addr2LocIdx;
152+
153+
/// Memory locations (e.g. alloc_stack) which live in a single basic block.
154+
///
155+
/// Those locations are excluded from the locations to keep the bit sets
156+
/// small. They can be handled separately with handleSingleBlockLocations().
157+
llvm::SmallVector<SingleValueInstruction *, 16> singleBlockLocations;
158+
159+
public:
160+
MemoryLocations() {}
161+
162+
MemoryLocations(const MemoryLocations &) = delete;
163+
MemoryLocations &operator=(const MemoryLocations &) = delete;
164+
165+
/// Returns the number of collected locations, except single-block locations.
166+
unsigned getNumLocations() const { return locations.size(); }
167+
168+
/// Returns the location index corresponding to a memory address or -1, if
169+
/// \p addr is not associated with a location.
170+
int getLocationIdx(SILValue addr) const;
171+
172+
/// Returns the location corresponding to a memory address or null, if
173+
/// \p addr is not associated with a location.
174+
const Location *getLocation(SILValue addr) const {
175+
int locIdx = getLocationIdx(addr);
176+
if (locIdx >= 0)
177+
return &locations[locIdx];
178+
return nullptr;
179+
}
180+
181+
/// Returns the location with a given \p index.
182+
const Location *getLocation(unsigned index) const {
183+
return &locations[index];
184+
}
185+
186+
/// Sets the location bits os \p addr in \p bits, if \p addr is associated
187+
/// with a location.
188+
void setBits(Bits &bits, SILValue addr) {
189+
if (auto *loc = getLocation(addr))
190+
bits |= loc->subLocations;
191+
}
192+
193+
/// Clears the location bits os \p addr in \p bits, if \p addr is associated
194+
/// with a location.
195+
void clearBits(Bits &bits, SILValue addr) {
196+
if (auto *loc = getLocation(addr))
197+
bits.reset(loc->subLocations);
198+
}
199+
200+
/// Analyzes all locations in a function.
201+
///
202+
/// Single-block locations are not analyzed, but added to singleBlockLocations.
203+
void analyzeLocations(SILFunction *function);
204+
205+
/// Analyze a single top-level location.
206+
///
207+
/// If all uses of \p loc are okay, the location and its sub-locations are
208+
/// added to the data structures.
209+
void analyzeLocation(SILValue loc);
210+
211+
/// Do a block-local processing for all locations in singleBlockLocations.
212+
///
213+
/// First, initializes all locations which are alive in a block and then
214+
/// calls \p handlerFunc for the block.
215+
void handleSingleBlockLocations(
216+
std::function<void (SILBasicBlock *block)> handlerFunc);
217+
218+
/// Debug dump the MemoryLifetime internals.
219+
void dump() const;
220+
221+
/// Debug dump a bit set .
222+
static void dumpBits(const Bits &bits);
223+
224+
private:
225+
/// Clears all datastructures, except singleBlockLocations;
226+
void clear();
227+
228+
// (locationIdx, fieldNr) -> subLocationIdx
229+
using SubLocationMap = llvm::DenseMap<std::pair<unsigned, unsigned>, unsigned>;
230+
231+
/// Helper function called by analyzeLocation to check all uses of the
232+
/// location recursively.
233+
///
234+
/// The \p subLocationMap is a temporary cache to speed up sub-location lookup.
235+
bool analyzeLocationUsesRecursively(SILValue V, unsigned locIdx,
236+
SmallVectorImpl<SILValue> &collectedVals,
237+
SubLocationMap &subLocationMap);
238+
239+
/// Helper function called by analyzeLocation to create a sub-location for
240+
/// and address projection and check all of its uses.
241+
bool analyzeAddrProjection(
242+
SingleValueInstruction *projection, unsigned parentLocIdx,unsigned fieldNr,
243+
SmallVectorImpl<SILValue> &collectedVals, SubLocationMap &subLocationMap);
244+
245+
/// Calculates Location::numFieldsNotCoveredBySubfields
246+
void initFieldsCounter(Location &loc);
247+
248+
/// Only memory locations which store a non-trivial type are considered.
249+
bool shouldTrackLocation(SILType type, SILFunction *inFunction) {
250+
return !type.isTrivial(*inFunction);
251+
}
252+
};
253+
254+
/// The MemoryDataflow utility calculates global dataflow of memory locations.
255+
///
256+
/// The MemoryDataflow works well together with MemoryLocations, which can be
257+
/// used to analyze locations as input to the dataflow.
258+
/// TODO: Actuall this utility can be used for any kind of dataflow, not just
259+
/// for memory locations. Consider renaming it.
260+
class MemoryDataflow {
261+
262+
public:
263+
using Bits = MemoryLocations::Bits;
264+
265+
/// Basic-block specific information used for dataflow analysis.
266+
struct BlockState {
267+
/// The backlink to the SILBasicBlock.
268+
SILBasicBlock *block;
269+
270+
/// The bits valid at the entry (i.e. the first instruction) of the block.
271+
Bits entrySet;
272+
273+
/// The bits valid at the exit (i.e. after the terminator) of the block.
274+
Bits exitSet;
275+
276+
/// Generated bits of the block.
277+
Bits genSet;
278+
279+
/// Killed bits of the block.
280+
Bits killSet;
281+
282+
/// True, if this block is reachable from the entry block, i.e. is not an
283+
/// unreachable block.
284+
///
285+
/// This flag is only computed if entryReachabilityAnalysis is called.
286+
bool reachableFromEntry = false;
287+
288+
/// True, if any function-exit block can be reached from this block, i.e. is
289+
/// not a block which eventually ends in an unreachable instruction.
290+
///
291+
/// This flag is only computed if exitReachableAnalysis is called.
292+
bool exitReachable = false;
293+
294+
BlockState(SILBasicBlock *block = nullptr) : block(block) { }
295+
296+
// Utility functions for setting and clearing gen- and kill-bits.
297+
298+
void genBits(SILValue addr, const MemoryLocations &locs) {
299+
if (auto *loc = locs.getLocation(addr)) {
300+
killSet.reset(loc->subLocations);
301+
genSet |= loc->subLocations;
302+
}
303+
}
304+
305+
void killBits(SILValue addr, const MemoryLocations &locs) {
306+
if (auto *loc = locs.getLocation(addr)) {
307+
genSet.reset(loc->subLocations);
308+
killSet |= loc->subLocations;
309+
}
310+
}
311+
};
312+
313+
private:
314+
/// All block states.
315+
std::vector<BlockState> blockStates;
316+
317+
/// Getting from SILBasicBlock to BlockState.
318+
llvm::DenseMap<SILBasicBlock *, BlockState *> block2State;
319+
320+
public:
321+
/// Sets up the BlockState datastructures and associates all basic blocks with
322+
/// a state.
323+
MemoryDataflow(SILFunction *function, unsigned numLocations);
324+
325+
MemoryDataflow(const MemoryDataflow &) = delete;
326+
MemoryDataflow &operator=(const MemoryDataflow &) = delete;
327+
328+
using iterator = std::vector<BlockState>::iterator;
329+
330+
iterator begin() { return blockStates.begin(); }
331+
iterator end() { return blockStates.end(); }
332+
333+
/// Returns the state of a block.
334+
BlockState *getState(SILBasicBlock *block) {
335+
return block2State[block];
336+
}
337+
338+
/// Calculates the BlockState::reachableFromEntry flags.
339+
void entryReachabilityAnalysis();
340+
341+
/// Calculates the BlockState::exitReachable flags.
342+
void exitReachableAnalysis();
343+
344+
/// Derives the block exit sets from the entry sets by applying the gen and
345+
/// kill sets.
346+
void solveDataflowForward();
347+
348+
/// Derives the block entry sets from the exit sets by applying the gen and
349+
/// kill sets.
350+
void solveDataflowBackward();
351+
352+
/// Debug dump the MemoryLifetime internals.
353+
void dump() const;
354+
};
355+
356+
/// Verifies the lifetime of memory locations in a function.
357+
void verifyMemoryLifetime(SILFunction *function);
358+
359+
} // end swift namespace
360+
361+
#endif

include/swift/SIL/SILInstruction.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3401,8 +3401,7 @@ class EndBorrowInst
34013401
return bbi->getOperand();
34023402
if (auto *lbi = dyn_cast<LoadBorrowInst>(v))
34033403
return lbi->getOperand();
3404-
llvm::errs() << "Can not end borrow for value: " << v;
3405-
llvm_unreachable("standard error assertion");
3404+
return SILValue();
34063405
}
34073406

34083407
/// Return the set of guaranteed values that have scopes ended by this

lib/SIL/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_swift_host_library(swiftSIL STATIC
1010
Linker.cpp
1111
LinearLifetimeChecker.cpp
1212
LoopInfo.cpp
13+
MemoryLifetime.cpp
1314
Notifications.cpp
1415
OperandOwnership.cpp
1516
OptimizationRemark.cpp

0 commit comments

Comments
 (0)