Skip to content

Commit 246da83

Browse files
authored
Merge pull request #71055 from atrick/lifetime-dependence
Lifetime dependence utilities
2 parents 5e803c6 + 2ef870a commit 246da83

24 files changed

+1793
-530
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//===--- AddressUtils.swift - Utilities for handling SIL addresses -------===//
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+
import SIL
14+
15+
/// Classify address uses. This can be used by def-use walkers to
16+
/// ensure complete handling of all legal SIL patterns.
17+
///
18+
/// TODO: Integrate this with SIL verification to ensure completeness.
19+
///
20+
/// TODO: Convert AddressDefUseWalker to conform to AddressUtils after
21+
/// checking that the additional instructions are handled correctly by
22+
/// escape analysis.
23+
///
24+
/// TODO: Verify that pointerEscape is only called for live ranges in which
25+
/// `findPointerEscape()` returns true.
26+
protocol AddressUseVisitor {
27+
var context: Context { get }
28+
29+
/// An address projection produces a single address result and does not
30+
/// escape its address operand in any other way.
31+
mutating func projectedAddressUse(of operand: Operand, into value: Value)
32+
-> WalkResult
33+
34+
/// An access scope: begin_access, begin_apply, load_borrow.
35+
mutating func scopedAddressUse(of operand: Operand) -> WalkResult
36+
37+
/// A address leaf use cannot propagate the address bits beyond the
38+
/// instruction.
39+
///
40+
/// An apply or builtin propagates an address into the callee, but
41+
/// it is considered a leaf use as long as the argument does not escape.
42+
mutating func leafAddressUse(of operand: Operand) -> WalkResult
43+
44+
/// A loaded address use propagates the value at the address.
45+
mutating func loadedAddressUse(of operand: Operand, into value: Value)
46+
-> WalkResult
47+
48+
/// A loaded address use propagates the value at the address to the
49+
/// destination address operand.
50+
mutating func loadedAddressUse(of operand: Operand, into address: Operand)
51+
-> WalkResult
52+
53+
/// A non-address owned `value` whose ownership depends on the in-memory
54+
/// value at `address`, such as `mark_dependence %value on %address`.
55+
mutating func dependentAddressUse(of operand: Operand, into value: Value)
56+
-> WalkResult
57+
58+
/// A pointer escape may propagate the address beyond the current instruction.
59+
mutating func escapingAddressUse(of operand: Operand) -> WalkResult
60+
61+
/// A unknown address use. This should never be called in valid SIL.
62+
mutating func unknownAddressUse(of operand: Operand) -> WalkResult
63+
}
64+
65+
extension AddressUseVisitor {
66+
/// Classify an address-type operand, dispatching to one of the
67+
/// protocol methods above.
68+
mutating func classifyAddress(operand: Operand) -> WalkResult {
69+
switch operand.instruction {
70+
case is BeginAccessInst, is BeginApplyInst, is LoadBorrowInst,
71+
is StoreBorrowInst:
72+
return scopedAddressUse(of: operand)
73+
74+
case let markDep as MarkDependenceInst:
75+
if markDep.valueOperand == operand {
76+
return projectedAddressUse(of: operand, into: markDep)
77+
}
78+
assert(markDep.baseOperand == operand)
79+
// If another address depends on the current address,
80+
// handle it like a projection.
81+
if markDep.type.isAddress {
82+
return projectedAddressUse(of: operand, into: markDep)
83+
}
84+
if LifetimeDependence(markDependence: markDep, context) != nil {
85+
// This is unreachable from InteriorUseVisitor because the
86+
// base address of a `mark_dependence [nonescaping]` must be a
87+
// `begin_access`, and interior liveness does not check uses of
88+
// the accessed address.
89+
return dependentAddressUse(of: operand, into: markDep)
90+
}
91+
// A potentially escaping value depends on this address.
92+
return escapingAddressUse(of: operand)
93+
94+
case let pai as PartialApplyInst where pai.isOnStack:
95+
return dependentAddressUse(of: operand, into: pai)
96+
97+
case let pai as PartialApplyInst where !pai.isOnStack:
98+
return escapingAddressUse(of: operand)
99+
100+
case is AddressToPointerInst:
101+
return escapingAddressUse(of: operand)
102+
103+
case is StructElementAddrInst, is TupleElementAddrInst,
104+
is IndexAddrInst, is TailAddrInst, is TuplePackElementAddrInst,
105+
is InitEnumDataAddrInst, is UncheckedTakeEnumDataAddrInst,
106+
is InitExistentialAddrInst, is OpenExistentialAddrInst,
107+
is ProjectBlockStorageInst, is UncheckedAddrCastInst,
108+
is UnconditionalCheckedCastAddrInst,
109+
is MarkUninitializedInst, is DropDeinitInst,
110+
is CopyableToMoveOnlyWrapperAddrInst,
111+
is MoveOnlyWrapperToCopyableAddrInst,
112+
is MarkUnresolvedNonCopyableValueInst:
113+
let svi = operand.instruction as! SingleValueInstruction
114+
return projectedAddressUse(of: operand, into: svi)
115+
116+
case is ReturnInst, is ThrowInst, is YieldInst, is TryApplyInst,
117+
is SwitchEnumAddrInst, is CheckedCastAddrBranchInst,
118+
is SelectEnumAddrInst, is InjectEnumAddrInst,
119+
is StoreInst, is StoreUnownedInst, is StoreWeakInst,
120+
is AssignInst, is AssignByWrapperInst, is AssignOrInitInst,
121+
is TupleAddrConstructorInst, is InitBlockStorageHeaderInst,
122+
is RetainValueAddrInst, is ReleaseValueAddrInst,
123+
is DestroyAddrInst, is DeallocStackInst,
124+
is DeinitExistentialAddrInst,
125+
is EndApplyInst, is IsUniqueInst, is MarkFunctionEscapeInst,
126+
is PackElementSetInst:
127+
return leafAddressUse(of: operand)
128+
129+
case is LoadInst, is LoadUnownedInst, is LoadWeakInst,
130+
is ValueMetatypeInst, is ExistentialMetatypeInst,
131+
is PackElementGetInst:
132+
let svi = operand.instruction as! SingleValueInstruction
133+
return loadedAddressUse(of: operand, into: svi)
134+
135+
case let sdai as SourceDestAddrInstruction
136+
where sdai.sourceOperand == operand:
137+
return loadedAddressUse(of: operand, into: sdai.destinationOperand)
138+
139+
case let sdai as SourceDestAddrInstruction
140+
where sdai.destinationOperand == operand:
141+
return leafAddressUse(of: operand)
142+
143+
case let builtin as BuiltinInst:
144+
switch builtin.id {
145+
case .Copy where builtin.operands[1] == operand: // source
146+
return loadedAddressUse(of: operand, into: builtin.operands[0])
147+
148+
case .Copy where builtin.operands[0] == operand: // dest
149+
return leafAddressUse(of: operand)
150+
151+
// Builtins that cannot load a nontrivial value.
152+
case .TSanInoutAccess, .ResumeThrowingContinuationReturning,
153+
.ResumeNonThrowingContinuationReturning, .GenericAdd,
154+
.GenericFAdd, .GenericAnd, .GenericAShr, .GenericLShr, .GenericOr,
155+
.GenericFDiv, .GenericMul, .GenericFMul, .GenericSDiv,
156+
.GenericExactSDiv, .GenericShl, .GenericSRem, .GenericSub,
157+
.GenericFSub, .GenericUDiv, .GenericExactUDiv, .GenericURem,
158+
.GenericFRem, .GenericXor, .TaskRunInline, .ZeroInitializer,
159+
.GetEnumTag, .InjectEnumTag:
160+
return leafAddressUse(of: operand)
161+
default:
162+
// TODO: SIL verification should check that this exhaustively
163+
// recognizes all builtin address uses.
164+
return .abortWalk
165+
}
166+
167+
case is BranchInst, is CondBranchInst:
168+
fatalError("address phi is not allowed")
169+
170+
default:
171+
if operand.instruction.isIncidentalUse {
172+
return leafAddressUse(of: operand)
173+
}
174+
// Unkown instruction.
175+
return unknownAddressUse(of: operand)
176+
}
177+
}
178+
}

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ import SIL
145145
///
146146
/// Note: This must handle all instructions with a .borrow operand ownership.
147147
///
148+
/// Note: mark_dependence is a BorrowingInstruction because it creates
149+
/// a borrow scope for its base operand. Its result, however, is not a
150+
/// BeginBorrowValue. It is instead a ForwardingInstruction relative
151+
/// to its value operand.
152+
///
148153
/// TODO: replace BorrowIntroducingInstruction
149154
///
150155
/// TODO: Add non-escaping MarkDependence.
@@ -153,15 +158,21 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
153158
case storeBorrow(StoreBorrowInst)
154159
case beginApply(BeginApplyInst)
155160
case partialApply(PartialApplyInst)
161+
case markDependence(MarkDependenceInst)
156162
case startAsyncLet(BuiltinInst)
157163

158164
init?(_ inst: Instruction) {
159165
switch inst {
160-
case let bbi as BeginBorrowInst: self = .beginBorrow(bbi)
161-
case let sbi as StoreBorrowInst: self = .storeBorrow(sbi)
162-
case let bai as BeginApplyInst: self = .beginApply(bai)
166+
case let bbi as BeginBorrowInst:
167+
self = .beginBorrow(bbi)
168+
case let sbi as StoreBorrowInst:
169+
self = .storeBorrow(sbi)
170+
case let bai as BeginApplyInst:
171+
self = .beginApply(bai)
163172
case let pai as PartialApplyInst where pai.isOnStack:
164173
self = .partialApply(pai)
174+
case let mdi as MarkDependenceInst:
175+
self = .markDependence(mdi)
165176
case let bi as BuiltinInst
166177
where bi.id == .StartAsyncLetWithLocalBuffer:
167178
self = .startAsyncLet(bi)
@@ -172,11 +183,18 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
172183

173184
var instruction: Instruction {
174185
switch self {
175-
case .beginBorrow(let bbi): return bbi
176-
case .storeBorrow(let sbi): return sbi
177-
case .beginApply(let bai): return bai
178-
case .partialApply(let pai): return pai
179-
case .startAsyncLet(let bi): return bi
186+
case .beginBorrow(let bbi):
187+
return bbi
188+
case .storeBorrow(let sbi):
189+
return sbi
190+
case .beginApply(let bai):
191+
return bai
192+
case .partialApply(let pai):
193+
return pai
194+
case .markDependence(let mdi):
195+
return mdi
196+
case .startAsyncLet(let bi):
197+
return bi
180198
}
181199
}
182200

@@ -188,9 +206,17 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
188206
/// incoming value dominates or is consumed by an outer adjacent
189207
/// phi. See InteriorLiveness.
190208
///
191-
/// TODO: to hande reborrow-extended uses migrate ExtendedLiveness
209+
/// TODO: to hande reborrow-extended uses, migrate ExtendedLiveness
192210
/// to SwiftCompilerSources.
193211
///
212+
/// TODO: Handle .partialApply and .markDependence forwarded uses
213+
/// that are phi operands. Currently, partial_apply [on_stack]
214+
/// and mark_dependence [nonescaping] cannot be cloned, so walking
215+
/// through the phi safely returns dominated scope-ending operands.
216+
/// Instead, this could report the phi as a scope-ending use, and
217+
/// the client could decide whether to walk through them or to
218+
/// construct reborrow-extended liveness.
219+
///
194220
/// TODO: For instructions that are not a BeginBorrowValue, verify
195221
/// that scope ending instructions exist on all paths. These
196222
/// instructions should be complete after SILGen and never cloned to
@@ -206,8 +232,10 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
206232
}
207233
case .beginApply(let bai):
208234
return bai.token.uses.walk { return visitor($0) }
209-
case .partialApply(let pai):
210-
return visitForwardedUses(introducer: pai, context) {
235+
case .partialApply, .markDependence:
236+
let svi = instruction as! SingleValueInstruction
237+
assert(svi.ownership == .owned)
238+
return visitForwardedUses(introducer: svi, context) {
211239
switch $0 {
212240
case let .operand(operand):
213241
if operand.endsLifetime {
@@ -306,7 +334,7 @@ enum BeginBorrowValue {
306334
self = BeginBorrowValue(beginBorrow)!
307335
case let .beginApply(beginApply):
308336
self = BeginBorrowValue(beginApply.token)!
309-
case .storeBorrow, .partialApply, .startAsyncLet:
337+
case .storeBorrow, .partialApply, .markDependence, .startAsyncLet:
310338
return nil
311339
}
312340
}

SwiftCompilerSources/Sources/Optimizer/Utilities/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
swift_compiler_sources(Optimizer
10+
AddressUtils.swift
1011
BorrowUtils.swift
1112
DiagnosticEngine.swift
1213
Devirtualization.swift
1314
EscapeUtils.swift
1415
ForwardingUtils.swift
16+
LifetimeDependenceUtils.swift
1517
OptUtils.swift
1618
OwnershipLiveness.swift
1719
SSAUpdater.swift

SwiftCompilerSources/Sources/Optimizer/Utilities/ForwardingUtils.swift

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -197,32 +197,39 @@ enum ForwardingUseResult: CustomStringConvertible {
197197
}
198198
}
199199

200-
/// Visit all the uses in a forward-extended lifetime
200+
/// Visit all the non-forwarding uses in a forward-extended lifetime
201201
/// (LifetimeIntroducer -> Operand).
202202
///
203203
/// Minimal requirements:
204204
/// needWalk(for value: Value) -> Bool
205-
/// leafUse(_ operand: Operand) -> WalkResult
205+
/// nonForwardingUse(_ operand: Operand) -> WalkResult
206206
/// deadValue(_ value: Value, using operand: Operand?) -> WalkResult
207207
///
208208
/// Start walking:
209209
/// walkDown(root: Value)
210+
///
210211
protocol ForwardingDefUseWalker {
211-
// Minimally, check a ValueSet. This walker may traverse chains of
212-
// aggregation and destructuring by default. Implementations may
213-
// handle phis.
212+
/// Minimally, check a ValueSet. This walker may traverse chains of
213+
/// aggregation and destructuring by default. Implementations may
214+
/// handle phis.
214215
mutating func needWalk(for value: Value) -> Bool
215216

216-
mutating func leafUse(_ operand: Operand) -> WalkResult
217+
/// A nonForwarding use does not forward ownership, but may
218+
/// propagate the lifetime in other ways, such as an interior
219+
/// pointer.
220+
mutating func nonForwardingUse(_ operand: Operand) -> WalkResult
217221

218-
// Report any initial or forwarded value with no uses. Only relevant for
219-
// guaranteed values or incomplete OSSA. This could be a dead
220-
// instruction, a terminator in which the result is dead on one
221-
// path, or a dead phi.
222-
//
223-
// \p operand is nil if \p value is the root.
222+
/// Report any initial or forwarded value with no uses. Only relevant for
223+
/// guaranteed values or incomplete OSSA. This could be a dead
224+
/// instruction, a terminator in which the result is dead on one
225+
/// path, or a dead phi.
226+
///
227+
/// \p operand is nil if \p value is the root.
224228
mutating func deadValue(_ value: Value, using operand: Operand?) -> WalkResult
225229

230+
/// This is called for every forwarded value. If the root was an
231+
/// owned value, then this identifies all OSSA lifetimes in the
232+
/// forward-extendd lifetime.
226233
mutating func walkDownUses(of: Value, using: Operand?) -> WalkResult
227234

228235
mutating func walkDown(operand: Operand) -> WalkResult
@@ -235,7 +242,7 @@ extension ForwardingDefUseWalker {
235242
}
236243

237244
mutating func walkDownUses(of value: Value, using operand: Operand?)
238-
-> WalkResult {
245+
-> WalkResult {
239246
return walkDownUsesDefault(forwarding: value, using: operand)
240247
}
241248

@@ -245,8 +252,8 @@ extension ForwardingDefUseWalker {
245252
if !needWalk(for: value) { return .continueWalk }
246253

247254
var hasUse = false
248-
for operand in value.uses where !operand.isTypeDependent {
249-
if walkDown(operand: operand) == .abortWalk {
255+
for use in value.uses where !use.isTypeDependent {
256+
if walkDown(operand: use) == .abortWalk {
250257
return .abortWalk
251258
}
252259
hasUse = true
@@ -263,12 +270,17 @@ extension ForwardingDefUseWalker {
263270

264271
mutating func walkDownDefault(forwarding operand: Operand) -> WalkResult {
265272
if let inst = operand.instruction as? ForwardingInstruction {
266-
return inst.forwardedResults.walk { walkDownUses(of: $0, using: operand) }
273+
let singleOper = inst.singleForwardedOperand
274+
if singleOper == nil || singleOper! == operand {
275+
return inst.forwardedResults.walk {
276+
walkDownUses(of: $0, using: operand)
277+
}
278+
}
267279
}
268280
if let phi = Phi(using: operand) {
269281
return walkDownUses(of: phi.value, using: operand)
270282
}
271-
return leafUse(operand)
283+
return nonForwardingUse(operand)
272284
}
273285
}
274286

@@ -301,7 +313,7 @@ private struct VisitForwardedUses : ForwardingDefUseWalker {
301313
visitedValues.insert(value)
302314
}
303315

304-
mutating func leafUse(_ operand: Operand) -> WalkResult {
316+
mutating func nonForwardingUse(_ operand: Operand) -> WalkResult {
305317
return visitor(.operand(operand))
306318
}
307319

0 commit comments

Comments
 (0)