Skip to content

Commit 5c08a3f

Browse files
committed
Add OwnershipLiveness utilities
Encapsulate all the complexity of reborrows and guaranteed phi in 3 ownership liveness interfaces: LinerLiveness, InteriorLiveness, and ExtendedLiveness.
1 parent 2d91316 commit 5c08a3f

File tree

8 files changed

+1945
-3
lines changed

8 files changed

+1945
-3
lines changed

include/swift/SIL/OwnershipLiveness.h

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
//===--- OwnershipLiveness.h ---------------------------------*- C++ -*----===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 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+
/// Terminology:
14+
///
15+
/// Linear lifetime: All paths through the value's definition to the
16+
/// function exit pass through exactly one lifetime-ending operation.
17+
///
18+
/// Complete OSSA: All owned values and borrow introducers have linear lifetime.
19+
///
20+
/// Borrow introducer: defines a guaranteed SILValue and an associated explicit
21+
/// borrow scope: begin_borrow, load_borrow, store_borrow, and phi.
22+
/// A phi only introduces a scope when `isGuaranteedForwarding` returns
23+
/// false. We refer to these as reborrows. Eventually, the phi will know whether
24+
/// it is a reborrow based on a flag or subclass. A guaranteed function argument
25+
/// conceptually introduces a borrow scope, but the scope is implicit in the
26+
/// function entry and return points.
27+
///
28+
/// Linear SSA liveness:
29+
///
30+
/// - Liveness for a single "defining" owned value or borrow introducing value
31+
/// assuming the value's lifetime is already linear.
32+
///
33+
/// - The definition dominates all use points (phis do not extend the lifetime)
34+
///
35+
/// - Only lifetime-ending operations generate liveness
36+
///
37+
/// - Oblivious to pointer escapes within the lifetime
38+
///
39+
///
40+
/// Interior SSA liveness:
41+
///
42+
/// - Liveness for a single "defining" value with any ownership.
43+
///
44+
/// - The definition dominates all use points (phis do not extend the lifetime)
45+
///
46+
/// - Does not assume the current lifetime is linear. Transitively follows
47+
/// guaranteed forwarding and address uses within the current scope.
48+
///
49+
/// - Liveness cannot extend beyond lifetime-ending operations
50+
/// (a.k.a. affine lifetimes).
51+
///
52+
/// - Assumes inner scopes *are* linear, including borrow and address scopes
53+
/// (e.g. begin_borrow, load_borrow, begin_apply, store_borrow, begin_access)
54+
/// A callback may be used to complete inner scopes before updating liveness.
55+
/// Rarely returns AddressUseKind::PointerEscape, because inner scopes hide
56+
/// pointer escapes.
57+
///
58+
/// - Insulates outer scopes from inner scope details. Maintains the
59+
/// invariant that inlining cannot pessimize optimization.
60+
///
61+
/// - Interior SSA liveness is used to complete (linearize) an OSSA lifetime
62+
///
63+
/// Interior liveness example:
64+
///
65+
/// %struct = struct ...
66+
/// %f = struct_extract %s // defines a guaranteed value
67+
/// %b = begin_borrow %field
68+
/// %a = ref_element_addr %b
69+
/// _ = address_to_pointer %a
70+
/// end_borrow %b // the only interior use of %f
71+
///
72+
/// When computing interior liveness for %f, %b is an inner scope. Because inner
73+
/// scopes are complete, the only relevant use is end_borrow %b. Despite the
74+
/// address_to_pointer instruction, %f does not escape any dependent address.
75+
///
76+
/// Transitive SSA liveness
77+
///
78+
/// - Liveness for a single "defining" value with any ownership.
79+
///
80+
/// - The definition dominates all use points (phis do not extend the lifetime)
81+
///
82+
/// - Does not assume that any lifetimes are linear. Transitively follows uses
83+
/// within inner scopes, recursively through nested scopes, including
84+
/// forwarding operations and address uses.
85+
///
86+
/// - Much more likely to return AddressUseKind::PointerEscape
87+
///
88+
/// - ** Transitive liveness is no longer needed with complete OSSA lifetimes **
89+
///
90+
/// Transitive liveness example:
91+
///
92+
/// %struct = struct ...
93+
/// %f = struct_extract %s // defines a guaranteed value
94+
/// %b = begin_borrow %field
95+
/// %a = ref_element_addr %b
96+
/// _ = address_to_pointer %a // a transitive use of %f escapes
97+
///
98+
/// When computing transitive liveness for %f, %b is an inner scope. Liveness
99+
/// does not assume that an end_borrow exists. Instead it transitively considers
100+
/// all uses of %b. As a result, %f escapes.
101+
///
102+
/// Extended liveness
103+
///
104+
/// - Liveness of a single "defining" value extended beyond its lifetime-ending
105+
/// operations.
106+
///
107+
/// - May refer to copy-extension, reborrow-extension, or extension over
108+
/// barriers such as access markers.
109+
///
110+
/// - For reborrow-extension, the definition no longer dominates the use
111+
/// points. MultiDefPrunedLiveness must be used. Each extended reborrow is
112+
/// added as a new definition.
113+
///
114+
///
115+
/// Extended linear liveness
116+
///
117+
/// - Only considers lifetime-ending operations. Assumes the definition already
118+
/// has a linear lifetime and all copy-extended or reborrow-extended lifetimes
119+
/// are also linear.
120+
///
121+
///
122+
/// Extended interior liveness
123+
///
124+
/// - Like interior SSA liveness, does not assume the current lifetime is
125+
/// linear. Transitively follows guaranteed forwarding and address uses within
126+
/// the current scope. Assumes inner scopes *are* linear.
127+
///
128+
/// - Interior copy-extension is used to canonicalize an OSSA lifetime
129+
///
130+
/// - Interior reborrow-extension is used to check borrow scopes relative to
131+
/// their inner uses and outer lifetime.
132+
///
133+
//===----------------------------------------------------------------------===//
134+
///
135+
/// Completing an OSSA lifetime may require phi creation whenever a reborrow is
136+
/// not dominated by the OSSA definition. The new phi's lifetime will then be
137+
/// extended over any of its inner adjacent phis.
138+
///
139+
//===----------------------------------------------------------------------===//
140+
141+
#ifndef SWIFT_SIL_OWNERSHIPLIVENESS_H
142+
#define SWIFT_SIL_OWNERSHIPLIVENESS_H
143+
144+
#include "swift/Basic/Debug.h"
145+
#include "swift/Basic/LLVM.h"
146+
#include "swift/SIL/PrunedLiveness.h"
147+
#include "swift/SIL/OwnershipUseVisitor.h"
148+
#include "swift/SIL/SILArgument.h"
149+
#include "swift/SIL/SILBasicBlock.h"
150+
#include "swift/SIL/SILInstruction.h"
151+
#include "swift/SIL/SILValue.h"
152+
#include "llvm/ADT/SmallVector.h"
153+
154+
namespace swift {
155+
156+
/// Analyze SSA liveness of values that introduce an OSSA live range:
157+
///
158+
/// 1. Owned non-phi values
159+
/// 2. Owned phi values
160+
/// 3. Borrow scope introducers: begin_borrow/load_borrow
161+
/// 4. Reborrows: guaranteed phis transitively defined by at least one borrow
162+
/// scope introducer.
163+
///
164+
/// Used for OSSA lifetime completion.
165+
class OSSALiveness {
166+
protected:
167+
SILValue ownershipDef;
168+
169+
// MARK: Results.
170+
171+
SmallVector<SILBasicBlock *, 8> discoveredBlocks;
172+
SSAPrunedLiveness liveness;
173+
174+
OSSALiveness(const OSSALiveness &) = delete;
175+
OSSALiveness &operator=(const OSSALiveness &) = delete;
176+
177+
public:
178+
OSSALiveness(SILValue def): ownershipDef(def), liveness(&discoveredBlocks) {}
179+
180+
const SSAPrunedLiveness &getLiveness() const { return liveness; }
181+
182+
ArrayRef<SILBasicBlock *> getDiscoveredBlocks() const {
183+
return discoveredBlocks;
184+
}
185+
186+
void print(llvm::raw_ostream &OS) const;
187+
void dump() const;
188+
};
189+
190+
// Internal implementation
191+
struct LinearLivenessVisitor;
192+
193+
/// Compute ownershipDef's lifetime based on it's lifetime-ending uses, assuming
194+
/// it is already complete/linear. ownershipDef must be either an owned value or
195+
/// a local borrow scope introduced (begin_borrow, load_borrow, or
196+
/// store_borrow).
197+
///
198+
/// This is the simplest OSSA liveness analysis, but is not appropriate for
199+
/// fixing OSSA lifetimes after transformation and cannot tell you whether
200+
/// pointer escapes occur.
201+
class LinearLiveness : public OSSALiveness {
202+
friend LinearLivenessVisitor;
203+
204+
public:
205+
LinearLiveness(SILValue def);
206+
207+
void compute();
208+
};
209+
210+
// Internal implementation
211+
struct InteriorLivenessVisitor;
212+
213+
/// Compute ownershipDef's lifetime based on all its uses (except those
214+
/// already enclosed by inner scopes). Returns AddressUseKind::PointerEscape
215+
/// if a pointer to ownershipDef escapes (and is not already enclosed by an
216+
/// inner scope).
217+
class InteriorLiveness : public OSSALiveness {
218+
friend InteriorLivenessVisitor;
219+
220+
// Handle inner scopes. Called for inner reborrows, inner adjacent reborrows,
221+
// and address scopes.
222+
//
223+
// This may add uses to the inner scope, but it may not modify a use-list
224+
// in any outer scopes.
225+
using InnerScopeHandlerRef = llvm::function_ref<void(SILValue)>;
226+
227+
public:
228+
// Summarize address uses
229+
AddressUseKind addressUseKind = AddressUseKind::Unknown;
230+
231+
// Record any guaranteed phi uses that are not already enclosed by an outer
232+
// adjacent phi.
233+
SmallVector<SILValue, 8> unenclosedPhis;
234+
235+
public:
236+
InteriorLiveness(SILValue def): OSSALiveness(def) {}
237+
238+
void compute(const DominanceInfo *domInfo,
239+
InnerScopeHandlerRef handleInnerScope = InnerScopeHandlerRef());
240+
241+
AddressUseKind getAddressUseKind() const { return addressUseKind; }
242+
243+
ArrayRef<SILValue> getUnenclosedPhis() const { return unenclosedPhis; }
244+
245+
void print(llvm::raw_ostream &OS) const;
246+
void dump() const;
247+
};
248+
249+
// Internal implementation
250+
struct ExtendedLivenessVisitor;
251+
252+
/// Analyze liveness of values that introduce an OSSA live range. This computes
253+
/// the phi-extended live range for these four categories of live range
254+
/// introducing values:
255+
///
256+
/// 1. Owned non-phi values
257+
/// 2. Owned phi values
258+
/// 3. Borrow scope introducers: begin_borrow/load_borrow
259+
/// 4. Reborrows: guaranteed phis that end their incoming borrow scopes and
260+
/// begin a new borrow scope
261+
class ExtendedLiveness {
262+
friend ExtendedLivenessVisitor;
263+
264+
SILValue ownershipDef;
265+
266+
// MARK: Results.
267+
268+
// Because of reborrows, the ssa def may not dominate all
269+
// uses. Consider the reborrows to be separate defs.
270+
SmallVector<SILBasicBlock *, 8> discoveredBlocks;
271+
MultiDefPrunedLiveness liveness;
272+
273+
ExtendedLiveness(const ExtendedLiveness &) = delete;
274+
ExtendedLiveness &operator=(const ExtendedLiveness &) = delete;
275+
276+
public:
277+
ExtendedLiveness(SILValue def);
278+
279+
void compute();
280+
281+
/// The array of defs. The first element is ownershipDef. The remaining
282+
/// elements are outer reborrows discovered during computation.
283+
///
284+
/// TODO: These are always SILValues. Convert the iterator.
285+
NodeSetVector::iterator defBegin() const { return liveness.defBegin(); }
286+
NodeSetVector::iterator defEnd() const { return liveness.defBegin(); }
287+
288+
const MultiDefPrunedLiveness &getLiveness() const { return liveness; }
289+
290+
void print(llvm::raw_ostream &OS) const;
291+
void dump() const;
292+
};
293+
294+
} // namespace swift
295+
296+
#endif

0 commit comments

Comments
 (0)