Skip to content

Commit e6f3e8f

Browse files
committed
OwnershipUtils review feedback: Comments and examples.
1 parent eb3f734 commit e6f3e8f

File tree

2 files changed

+203
-45
lines changed

2 files changed

+203
-45
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,126 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
//
13+
// Utilities that model Ownership SSA (OSSA) borrow scopes.
14+
//
15+
// A borrowing instruction borrows one or more operands over a new
16+
// borrow scope, up to its scope-ending uses. This is typically
17+
// checked during a def-use walk.
18+
//
19+
// %val = some owned value
20+
// %store = store_borrow %val to %addr // borrowing instruction
21+
// ... // borrow scope
22+
// end_borrow %store // scope-ending use
23+
//
24+
// A begin-borrow value introduces a guaranteed OSSA lifetime. It
25+
// begins a new borrow scope that ends at its scope-ending uses. A
26+
// begin-borrow value may be defined by a borrowing instruction:
27+
//
28+
// %begin = begin_borrow %val // %begin borrows %val
29+
// ... // borrow scope
30+
// end_borrow %begin // scope-ending use
31+
//
32+
// Other begin-borrow cases, however, like block arguments and
33+
// `load_borrow`, are not borrowing instructions. Begin-borrow values
34+
// are typically checked during a use-def walk. Here, walking up from
35+
// `%forward` finds `%begin` as the introducer of its guaranteed
36+
// lifetime:
37+
//
38+
// %begin = load_borrow %addr // begin-borrow value
39+
// %forward = struct (%begin) // forwards a guaranteed value
40+
// ...
41+
// end_borrow %begin // scope-ending use
42+
//
43+
// Every guaranteed OSSA value has a set of borrow introducers, each
44+
// of which dominates the value and introduces a borrow scope that
45+
// encloses all forwarded uses of the guaranteed value.
46+
//
47+
// %1 = begin_borrow %0 // borrow introducer for %3
48+
// %2 = begin_borrow %1 // borrow introducer for %3
49+
// %3 = struct (%1, %2) // forwards two guaranteed values
50+
// ... all forwarded uses of %3
51+
// end_borrow %1 // scope-ending use
52+
// end_borrow %2 // scope-ending use
53+
//
54+
// Inner borrow scopes may be nested in outer borrow scopes:
55+
//
56+
// %1 = begin_borrow %0 // borrow introducer for %2
57+
// %2 = begin_borrow %1 // borrow introducer for %3
58+
// %3 = struct (%2)
59+
// ... all forwarded uses of %3
60+
// end_borrow %2 // scope-ending use of %2
61+
// end_borrow %1 // scope-ending use of %1
62+
//
63+
// Walking up the nested OSSA lifetimes requires iteratively querying
64+
// "enclosing values" until either a guaranteed function argument or
65+
// owned value is reached. Like a borrow introducer, an enclosing
66+
// value dominates all values that it encloses.
67+
//
68+
// Borrow Introducer Enclosing Value
69+
// ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
70+
// %0 = some owned value invalid none
71+
// %1 = begin_borrow %0 %1 %0
72+
// %2 = begin_borrow %1 %2 %1
73+
// %3 = struct (%2) %2 %2
74+
//
75+
// The borrow introducer of a guaranteed phi is not directly
76+
// determined by a use-def walk because an introducer must dominate
77+
// all uses in its scope:
78+
//
79+
// Borrow Introducer Enclosing Value
80+
// ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
81+
//
82+
// cond_br ..., bb1, bb2
83+
// bb1:
84+
// %2 = begin_borrow %0 %2 %0
85+
// %3 = struct (%2) %2 %2
86+
// br bb3(%2, %3)
87+
// bb2:
88+
// %6 = begin_borrow %0 %6 %0
89+
// %7 = struct (%6) %6 %6
90+
// br bb3(%6, %7)
91+
// bb3(%reborrow: @guaranteed, %reborrow %0
92+
// %phi: @guaranteed): %phi %reborrow
93+
//
94+
// `%reborrow` is an outer-adjacent phi to `%phi` because it encloses
95+
// `%phi`. `%phi` is an inner-adjacent phi to `%reborrow` because its
96+
// uses keep `%reborrow` alive. An outer-adjacent phi is either an
97+
// owned value or a reborrow. An inner-adjacent phi is either a
98+
// reborrow or a guaranteed forwarding phi. Here is an example of an
99+
// owned outer-adjacent phi with an inner-adjacent reborrow:
100+
//
101+
// Borrow Introducer Enclosing Value
102+
// ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
103+
//
104+
// cond_br ..., bb1, bb2
105+
// bb1:
106+
// %1 = owned value
107+
// %2 = begin_borrow %0 %2 %1
108+
// br bb3(%1, %2)
109+
// bb2:
110+
// %5 = owned value
111+
// %6 = begin_borrow %5 %6 %5
112+
// br bb3(%5, %6)
113+
// bb3(%phi: @owned, invalid %0
114+
// %reborrow: @guaranteed): %reborrow %phi
115+
//
116+
// In OSSA, each owned value defines a separate lifetime. It is
117+
// consumed on all paths by a direct use. Owned lifetimes can,
118+
// however, be nested within a borrow scope. In this case, finding the
119+
// scope-ending uses requires traversing owned forwarding
120+
// instructions:
121+
//
122+
// %1 = partial_apply %f(%0) // borrowing instruction borrows %0 and produces
123+
// // an owned closure value.
124+
// %2 = struct (%1) // end owned lifetime %1, begin owned lifetime %2
125+
// destroy_value %2 // end owned lifetime %2, scope-ending use of %1
126+
//
127+
//
128+
// TODO: These utilities should be integrated with OSSA SIL verification and
129+
// guaranteed to be compelete (produce known results for all legal SIL
130+
// patterns).
131+
// ===----------------------------------------------------------------------===//
12132

13133
import SIL
14134

SwiftCompilerSources/Sources/Optimizer/Utilities/OwnershipLiveness.swift

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
//
13+
// Utilities that specify ownership SSA (OSSA) lifetimes.
14+
//
1315
// TODO: Implement ExtendedLinearLiveness. This requires
1416
// MultiDefPrunedLiveness, which is not supported by InstructionRange.
1517
//
@@ -60,28 +62,29 @@ func computeLinearLiveness(for definingValue: Value, _ context: Context)
6062
return range
6163
}
6264

63-
/// Indicate whether OwnershipUseVisitor is visiting a use of the
64-
/// outer OSSA lifetime or within an inner borrow scope (reborrow).
65-
enum IsInnerLifetime {
66-
case outerLifetime
67-
case innerLifetime
68-
}
69-
7065
typealias InnerScopeHandler = (Value) -> WalkResult
7166

7267
/// Compute liveness and return a range, which the caller must deinitialize.
7368
///
74-
/// An OSSA lifetime begins with a single "defining" value, which
75-
/// must be owned, or must begin a borrow scope.
69+
/// An OSSA lifetime begins with a single "defining" value, which must
70+
/// be owned, or must begin a borrow scope. A complete OSSA lifetime
71+
/// has a linear lifetime, meaning that it has a lifetime-ending use
72+
/// on all paths. Interior liveness computes liveness without assuming
73+
/// the lifetime is complete. To do this, it must find all "use
74+
/// points" and prove that the defining value is never propagated
75+
/// beyond those points. This is used to initially complete OSSA
76+
/// lifetimes and fix them after transformations that's don't preserve
77+
/// OSSA.
7678
///
77-
/// - Liveness does not extend beyond lifetime-ending operations
78-
/// (a.k.a. affine lifetimes).
79+
/// Invariants:
7980
///
8081
/// - The definition dominates all use points.
8182
///
82-
/// - Does not assume the current lifetime is complete, but does
83-
/// assume any inner scopes are complete. Use `innerScopeHandler` to
84-
/// complete them or bail-out.
83+
/// - Liveness does not extend beyond lifetime-ending operations
84+
/// (a.k.a. affine lifetimes).
85+
///
86+
/// - All inner scopes are complete. (Use `innerScopeHandler` to
87+
/// complete them or bail-out).
8588
func computeInteriorLiveness(for definingValue: Value,
8689
_ context: FunctionPassContext,
8790
innerScopeHandler: InnerScopeHandler? = nil) -> InstructionRange {
@@ -115,7 +118,7 @@ func computeInteriorLiveness(for definingValue: Value,
115118
return range
116119
}
117120

118-
/// Visit all interior uses of on OSSA lifetime.
121+
/// Visit all interior uses of an OSSA lifetime.
119122
///
120123
/// - `definingValue` dominates all uses. Only dominated phis extend
121124
/// the lifetime. All other phis must have a lifetime-ending outer
@@ -132,14 +135,14 @@ func computeInteriorLiveness(for definingValue: Value,
132135
/// begin_access) A `innerScopeHandler` callback may be used to
133136
/// complete inner scopes before updating liveness.
134137
///
135-
/// InteriorUseVisitor can be used to complete (linearize) an OSSA
138+
/// InteriorUseWalker can be used to complete (linearize) an OSSA
136139
/// lifetime after transformation that invalidates OSSA.
137140
///
138141
/// Example:
139142
///
140-
/// %struct = struct ...
143+
/// %s = struct ...
141144
/// %f = struct_extract %s // defines a guaranteed value (%f)
142-
/// %b = begin_borrow %field
145+
/// %b = begin_borrow %f
143146
/// %a = ref_element_addr %b
144147
/// _ = address_to_pointer %a
145148
/// end_borrow %b // the only interior use of %f
@@ -631,17 +634,46 @@ extension AddressUseVisitor {
631634
}
632635
}
633636

634-
/// Enumerate all special cases of ownership uses in a visitor
635-
/// API. This encourages anyone who needs to analyze ownership uses to
636-
/// think about all of the special cases, many of which result
637-
/// from phis of borrowed values. Relying on linear lifetimes, which
638-
/// results from running "lifetime completion", allows you to ignore
639-
/// those cases.
637+
/// For OwnershipUseVisitor API entry points that operate on a
638+
/// use, Isinnerlifetime indicates whether the value being used is
639+
/// defined by the "outer" OSSA lifetime or an inner borrow scope.
640+
///
641+
/// When the OwnershipUseVisitor is invoked on an outer value
642+
/// (visitUsesOfOuter(value:)), it visits all the uses of that value
643+
/// and also visits the lifetime-ending uses of any inner borrow
644+
/// scopes. This provides a complete set of liveness "use points":
645+
///
646+
/// %0 = begin_borrow %outerValue
647+
/// %1 = begin_borrow %0
648+
/// end_borrow %1 // inner "use point" of %0
649+
/// end_borrow %0 // outer use of %1
650+
///
651+
/// This becomes more complicated with reborrows and closures. The
652+
/// implementation can simply rely on IsInnerLifetime to know whether
653+
/// the value being used is part of the outer lifetimes vs. its inner
654+
/// lifetimes. This is important, for example, if the implementation
655+
/// wants to know if the use ends the lifetime of the outer value.
656+
///
657+
/// Visitor implementations treat inner and outer uses differently. It
658+
/// may, for example, assume that inner lifetimes are complete
659+
/// and therefore only care about the lifetime-ending uses.
660+
enum IsInnerLifetime {
661+
case outerLifetime
662+
case innerLifetime
663+
}
664+
665+
/// Package operand ownership into a visitor API.
666+
///
667+
/// Code that relies on effect of a use on ownership of its value
668+
/// should conform to this visitor. This ensures correct handling for
669+
/// special cases involving borrow scopes and interior pointers.
640670
///
641-
/// To visit a value's uses:
671+
/// `visitUsesOfOuter(value:)` is the main entry point.
642672
///
643-
/// - visitUsesOfOuter(value:)
644-
/// - visitUsesOfInner(value:)
673+
/// `visitUsesOfInner(value:)` is called back for each inner borrow
674+
/// scope. The implementation may or may not decide to recurse through
675+
/// reborrows and nested borrow scopes, calling back to
676+
/// `visitUsesOfInner(value:)` as needed.
645677
///
646678
/// Visitors need to implement:
647679
///
@@ -652,14 +684,14 @@ extension AddressUseVisitor {
652684
///
653685
/// This only visits the first level of uses. The implementation may
654686
/// transitively visit forwarding operations in its implementation of
655-
/// `visitForwarding(operand:_)` and `visitReborrow(operand:_)`,
656-
/// calling back to `visitUsesOfOuter(value:)` or
687+
/// `visitForwarding(operand:_)` and `visitReborrow(operand:_)`, which
688+
/// can recursively call back to `visitUsesOfOuter(value:)` or
657689
/// `visitUsesOfInner(value:)`.
658690
///
659-
/// For uses that begin a borrow or access scope, this skips ahead to
660-
/// the end of the scope. To record incomplete or dead inner scopes
661-
/// (no scope-ending use on some path), the implementation must
662-
/// override handleInner(borrow:).
691+
/// For uses that begin a borrow or access scope, this correctly
692+
/// "skips ahead" to the end of the scope. To record incomplete or
693+
/// dead inner scopes (no scope-ending use on some path), the
694+
/// implementation must override `handleInner(borrow:)`.
663695
protocol OwnershipUseVisitor {
664696
var _context: Context { get }
665697

@@ -698,30 +730,36 @@ protocol OwnershipUseVisitor {
698730
/// result and implement this as a fatalError.
699731
mutating func visitPointerEscape(use: Operand) -> WalkResult
700732

701-
/// Handle begin_borrow, load_borrow, store_borrow, begin_apply.
733+
/// Handles any of:
702734
///
703-
/// Handle an inner adjacent phi where the original OSSA def is a
704-
/// phi in the same block
735+
/// - begin_borrow, load_borrow, store_borrow, begin_apply.
705736
///
706-
/// Handle a reborrow of an inner borrow scope or inner adjacent phi
737+
/// - an inner adjacent phi (where the value currently being visited is an
738+
/// outer adjacent phi in the same block).
739+
///
740+
/// - a reborrow of an inner borrow scope or a reborrow of an inner
741+
/// adjacent phi.
707742
///
708743
/// If this returns .continueWalk, then visit(use:) will be called
709-
/// on the scope ending operands.
744+
/// on the scope-ending operands.
710745
///
711-
/// Allows the implementation to complete inner scopes before considering
712-
/// their scope ending operations as uses of the outer scope.
746+
/// Allows the implementation to complete an inner scope before
747+
/// visiting its scope-ending operations as uses of the outer
748+
/// scope. The implementation may add uses to the inner scope, but
749+
/// it may not modify the use-list containing \p address or in any
750+
/// outer scopes.
713751
mutating func handleInner(borrow: BeginBorrowValue) -> WalkResult
714752

715753
/// Handle begin_access.
716754
///
717755
/// If this returns .continueWalk, then visit(use:) will be called
718756
/// on the scope ending operands.
719757
///
720-
/// Allows the implementation to complete inner scopes before considering
721-
/// their scope ending operations as uses of the outer scope.
722-
///
723-
/// This may add uses to the inner scope, but it may not modify the use-list
724-
/// containing \p address or in any outer scopes.
758+
/// Allows the implementation to complete an inner scope before
759+
/// visiting its scope-ending operations as uses of the outer
760+
/// scope. The implementation may add uses to the inner scope, but
761+
/// it may not modify the use-list containing \p address or in any
762+
/// outer scopes.
725763
mutating func handleAccess(address: BeginAccessInst) -> WalkResult
726764
}
727765

0 commit comments

Comments
 (0)