Skip to content

Commit 3897929

Browse files
committed
LifetimeDependence: handle dependence on trivial values.
1 parent 5c89708 commit 3897929

File tree

6 files changed

+98
-80
lines changed

6 files changed

+98
-80
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
6565
// but the change won't appear in -sil-print-function. Ideally, we could notify context of a flag change
6666
// without invalidating analyses.
6767
lifetimeDep.resolve(context)
68+
continue
6869
}
69-
} else {
70-
// For now, if the mark_dependence wasn't recognized as a lifetime dependence, conservatively settle it as
71-
// escaping. In the future, we should not need this because, for escapable types, mark_dependence [unresolved]
72-
// will all be settled during an early LifetimeNormalization pass.
73-
markDep.settleToEscaping()
7470
}
71+
// For now, if the mark_dependence wasn't recognized as a lifetime dependency, or if the dependencies uses are not
72+
// in scope, conservatively settle it as escaping. For example, it is not uncommon for the pointer value returned
73+
// by `unsafeAddress` to outlive its `self` argument. This will not be diagnosed as an error, but the
74+
// mark_dependence will hanceforth be treated as an unknown use by the optimizer. In the future, we should not
75+
// need to set this flag during diagnostics because, for escapable types, mark_dependence [unresolved] will all be
76+
// settled during an early LifetimeNormalization pass.
77+
markDep.settleToEscaping()
7578
continue
7679
}
7780
if let apply = instruction as? FullApplySite {
@@ -97,7 +100,15 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
97100
/// Return true on success.
98101
private func analyze(dependence: LifetimeDependence, _ context: FunctionPassContext) -> Bool {
99102
log("Dependence scope:\n\(dependence)")
100-
103+
104+
// Early versions of Span in the standard library violate trivial lifetimes. Contemporary versions of the compiler
105+
// simply ignored dependencies on trivial values.
106+
if !context.options.hasFeature(.LifetimeDependenceDiagnoseTrivial) {
107+
if dependence.parentValue.type.objectType.isTrivial(in: dependence.function) {
108+
return true
109+
}
110+
}
111+
101112
// Compute this dependence scope.
102113
var range = dependence.computeRange(context)
103114
defer { range?.deinitialize() }
@@ -110,8 +121,10 @@ private func analyze(dependence: LifetimeDependence, _ context: FunctionPassCont
110121
// Check each lifetime-dependent use via a def-use visitor
111122
var walker = DiagnoseDependenceWalker(diagnostics, context)
112123
defer { walker.deinitialize() }
113-
_ = walker.walkDown(root: dependence.dependentValue)
114-
return !error
124+
let result = walker.walkDown(root: dependence.dependentValue)
125+
// The walk may abort without a diagnostic error.
126+
assert(!error || result == .abortWalk)
127+
return result == .continueWalk
115128
}
116129

117130
/// Analyze and diagnose a single LifetimeDependence.
@@ -214,6 +227,10 @@ private struct DiagnoseDependence {
214227
}
215228

216229
func reportError(operand: Operand, diagID: DiagID) {
230+
// If the dependent value is Escapable, then mark_dependence resolution fails, but this is not a diagnostic error.
231+
if dependence.dependentValue.isEscapable {
232+
return
233+
}
217234
onError()
218235

219236
// Identify the escaping variable.

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,10 @@ private extension LifetimeDependentApply.LifetimeSourceInfo {
218218
case .result, .inParameter, .inoutParameter:
219219
// Create a new dependence on the apply's access to the argument.
220220
for varIntoducer in gatherVariableIntroducers(for: source.value, context) {
221-
if let scope = LifetimeDependence.Scope(base: varIntoducer, context) {
222-
log("Scoped lifetime from \(source.value)")
223-
log(" scope: \(scope)")
224-
bases.append(scope.parentValue)
225-
}
221+
let scope = LifetimeDependence.Scope(base: varIntoducer, context)
222+
log("Scoped lifetime from \(source.value)")
223+
log(" scope: \(scope)")
224+
bases.append(scope.parentValue)
226225
}
227226
}
228227
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,7 @@ private extension LifetimeDependence.Scope {
299299
dependsOnArg: dependsOnArg))
300300
case let .borrowed(beginBorrow):
301301
let borrowedValue = beginBorrow.baseOperand!.value
302-
guard let enclosingScope = LifetimeDependence.Scope(base: borrowedValue, context) else {
303-
return nil
304-
}
302+
let enclosingScope = LifetimeDependence.Scope(base: borrowedValue, context)
305303
innerScopes.push(self)
306304
var innerBorrowScopes = innerScopes
307305
innerBorrowScopes.push(enclosingScope)
@@ -323,9 +321,7 @@ private extension LifetimeDependence.Scope {
323321
guard let dep = applySite.resultDependence(on: operand), dep == .scope else {
324322
continue
325323
}
326-
guard let enclosingScope = LifetimeDependence.Scope(base: operand.value, context) else {
327-
continue
328-
}
324+
let enclosingScope = LifetimeDependence.Scope(base: operand.value, context)
329325
if let operandExtensions = enclosingScope.gatherExtensions(innerScopes: innerScopes, context) {
330326
extensions.append(contentsOf: operandExtensions)
331327
} else {

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 63 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ private func log(prefix: Bool = true, _ message: @autoclosure () -> String) {
6666
/// Walk up the value dependence chain to find the best-effort
6767
/// variable declaration. Typically called while diagnosing an error.
6868
///
69+
/// Returns an array with at least one introducer value.
70+
///
6971
/// The walk stops at:
7072
/// - an address
7173
/// - a variable declaration (begin_borrow [var_decl], move_value [var_decl])
@@ -85,6 +87,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
8587
}
8688
defer { useDefVisitor.deinitialize() }
8789
_ = useDefVisitor.walkUp(valueOrAddress: value)
90+
assert(!introducers.isEmpty, "missing variable introducer")
8891
return introducers
8992
}
9093

@@ -104,7 +107,6 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
104107
/// A lifetime dependence identifies its parent value, the kind of
105108
/// scope that the parent value represents, and a dependent value.
106109
struct LifetimeDependence : CustomStringConvertible {
107-
// TODO: handle trivial values based on variable binding
108110
enum Scope : CustomStringConvertible {
109111
/// A guaranteed or inout argument whose scope is provided by the caller
110112
/// and covers the entire function and any dependent results or yields.
@@ -113,9 +115,10 @@ struct LifetimeDependence : CustomStringConvertible {
113115
case access(BeginAccessInst)
114116
/// A coroutine.
115117
case yield(Value)
116-
/// An owned value whose OSSA lifetime encloses nonescapable values
118+
/// An owned value whose OSSA lifetime encloses nonescapable values, or a trivial variable introduced by move_value.
117119
case owned(Value)
118-
/// An borrowed value whose OSSA lifetime encloses nonescapable values
120+
/// An borrowed value whose OSSA lifetime encloses nonescapable values, or a trivial variable introduced by
121+
/// begin_borrow.
119122
case borrowed(BeginBorrowValue)
120123
/// Singly-initialized addressable storage (likely for an
121124
/// immutable address-only value). The lifetime extends until the
@@ -128,8 +131,7 @@ struct LifetimeDependence : CustomStringConvertible {
128131
/// If `initializingStore` is nil, then the `initialAddress` is
129132
/// initialized on function entry.
130133
case initialized(initialAddress: Value, initializingStore: Instruction?)
131-
// TODO: Add SIL verification that no mark_depedence [unresolved] has an unknown LifetimeDependence.
132-
// This currently requires stack allocations to be singly initialized.
134+
// Unknown includes: escapable values with local var_decl, stack allocations that are not singly initialized.
133135
case unknown(Value)
134136

135137
var parentValue: Value {
@@ -202,7 +204,7 @@ extension LifetimeDependence {
202204
if arg.isIndirectResult {
203205
return nil
204206
}
205-
self.scope = Scope(base: arg, context)!
207+
self.scope = Scope(base: arg, context)
206208
self.dependentValue = arg
207209
}
208210

@@ -219,7 +221,7 @@ extension LifetimeDependence {
219221
return nil
220222
}
221223
assert(value.ownership == .owned, "unsafe apply result must be owned")
222-
self.scope = Scope(base: value, context)!
224+
self.scope = Scope(base: value, context)
223225
self.dependentValue = value
224226
}
225227

@@ -238,14 +240,11 @@ extension LifetimeDependence {
238240
/// For any LifetimeDependence constructed from a mark_dependence, its `dependentValue` will be the result of the
239241
/// mark_dependence.
240242
///
241-
/// Returns 'nil' for dependence on a trivial value.
243+
/// Returns 'nil' for unknown dependence.
242244
init?(_ markDep: MarkDependenceInst, _ context: some Context) {
243245
switch markDep.dependenceKind {
244246
case .Unresolved, .NonEscaping:
245-
guard let scope = Scope(base: markDep.base, context) else {
246-
return nil
247-
}
248-
self.scope = scope
247+
self.scope = Scope(base: markDep.base, context)
249248
self.dependentValue = markDep
250249
case .Escaping:
251250
return nil
@@ -294,65 +293,45 @@ extension LifetimeDependence.Scope {
294293
/// directly defines the parent lifetime. If `base` is guaranteed, then it must have a single borrow introducer, which
295294
/// defines the parent lifetime. `base` must not be derived from a guaranteed phi or forwarded (via struct/tuple) from
296295
/// multiple guaranteed values.
297-
///
298-
/// Returns 'nil' for dependence on a trivial value.
299-
init?(base: Value, _ context: some Context) {
296+
init(base: Value, _ context: some Context) {
300297
if base.type.isAddress {
301-
guard let scope = Self(address: base, context) else {
302-
return nil
303-
}
304-
self = scope
298+
self = Self(address: base, context)
305299
return
306300
}
307301
switch base.ownership {
308302
case .owned:
309303
self = .owned(base)
310304
return
311305
case .guaranteed:
312-
guard let scope = Self(guaranteed: base, context) else {
313-
return nil
314-
}
315-
self = scope
306+
self = Self(guaranteed: base, context)
316307
case .none:
317-
// lifetime dependence requires a nontrivial value
318-
return nil
308+
self = Self(variable: base, context)
319309
case .unowned:
320310
self = .unknown(base)
321311
}
322312
}
323313

324-
/// Returns 'nil' for dependence on a trivial value.
325-
private init?(address: Value, _ context: some Context) {
314+
private init(address: Value, _ context: some Context) {
326315
switch address.enclosingAccessScope {
327316
case let .scope(access):
328317
self = .access(access)
329318
case let .base(accessBase):
330-
guard let scope = Self(accessBase: accessBase, address: address, context) else {
331-
return nil
332-
}
333-
self = scope
319+
self = Self(accessBase: accessBase, address: address, context)
334320
}
335321
}
336322

337-
/// Returns 'nil' for dependence on a trivial value.
338-
init?(accessBase: AccessBase, address: Value, _ context: some Context) {
323+
init(accessBase: AccessBase, address: Value, _ context: some Context) {
339324
switch accessBase {
340325
case let .box(projectBox):
341326
// Note: the box may be in a borrow scope.
342-
guard let scope = Self(base: projectBox.operand.value, context) else {
343-
return nil
344-
}
345-
self = scope
327+
self = Self(base: projectBox.operand.value, context)
346328
case let .stack(allocStack):
347329
self = Self(allocation: allocStack, context)
348330
case .global:
349331
self = .unknown(address)
350332
case .class, .tail:
351333
let refElt = address as! UnaryInstruction
352-
guard let scope = Self(guaranteed: refElt.operand.value, context) else {
353-
return nil
354-
}
355-
self = scope
334+
self = Self(guaranteed: refElt.operand.value, context)
356335
case let .argument(arg):
357336
if arg.convention.isIndirectIn {
358337
self = .initialized(initialAddress: arg, initializingStore: nil)
@@ -368,24 +347,23 @@ extension LifetimeDependence.Scope {
368347
case let .yield(result):
369348
self = Self(yield: result)
370349
case .storeBorrow(let sb):
371-
guard let scope = Self(base: sb.source, context) else {
372-
return nil
373-
}
374-
self = scope
350+
self = Self(base: sb.source, context)
375351
case .pointer, .index, .unidentified:
376352
self = .unknown(address)
377353
}
378354
}
379355

380-
/// Returns 'nil' for dependence on a trivial value.
381-
private init?(guaranteed base: Value, _ context: some Context) {
382-
// If introducers is empty, then the dependence is on a trivial value, so
383-
// there is no dependence scope.
384-
//
356+
private init(guaranteed base: Value, _ context: some Context) {
385357
// TODO: Add a SIL verifier check that a mark_dependence [nonescaping]
386358
// base is never a guaranteed phi.
387359
var iter = base.getBorrowIntroducers(context).makeIterator()
388-
guard let beginBorrow = iter.next() else { return nil }
360+
// If no borrow introducer was found, then this is a borrow of a trivial value. Since we can assume a single
361+
// introducer here, then this is the only condition under which we have a trivial introducer.
362+
guard let beginBorrow = iter.next() else {
363+
self = Self(variable: base, context)
364+
return
365+
}
366+
// TODO: will we need to handle tuple/struct with multiple scopes?
389367
assert(iter.next() == nil,
390368
"guaranteed phis not allowed when diagnosing lifetime dependence")
391369
switch beginBorrow {
@@ -400,6 +378,24 @@ extension LifetimeDependence.Scope {
400378
}
401379
}
402380

381+
private init(variable base: Value, _ context: some Context) {
382+
guard let introducer = gatherVariableIntroducers(for: base, context).singleElement else {
383+
// TODO: do we need to handle multiple introducers in case of a tuple/struct?
384+
self = .unknown(base)
385+
return
386+
}
387+
switch introducer {
388+
case let arg as FunctionArgument:
389+
self = .caller(arg)
390+
case let bbi as BeginBorrowInst:
391+
self = .borrowed(BeginBorrowValue(bbi)!)
392+
case is MoveValueInst:
393+
self = .owned(introducer)
394+
default:
395+
self = .unknown(introducer)
396+
}
397+
}
398+
403399
private init(yield result: MultipleValueInstructionResult) {
404400
// Consider an @in yield an .initialized scope. We must find the destroys.
405401
let apply = result.parentInstruction as! FullApplySite
@@ -431,7 +427,7 @@ extension LifetimeDependence.Scope {
431427
if bb.isFromVarDecl {
432428
return self
433429
}
434-
return LifetimeDependence.Scope(base: bb.borrowedValue, context)?.ignoreBorrowScope(context)
430+
return LifetimeDependence.Scope(base: bb.borrowedValue, context).ignoreBorrowScope(context)
435431
case let .loadBorrow(lb):
436432
return LifetimeDependence.Scope(base: lb.address, context)
437433
default:
@@ -443,7 +439,7 @@ extension LifetimeDependence.Scope {
443439
extension LifetimeDependence.Scope {
444440
/// Compute the range of the dependence scope.
445441
///
446-
/// Returns nil if the dependence scope covers the entire function.
442+
/// Returns nil if the dependence scope covers the entire function. Returns an empty range for an unknown scope.
447443
///
448444
/// Note: The caller must deinitialize the returned range.
449445
func computeRange(_ context: Context) -> InstructionRange? {
@@ -480,11 +476,12 @@ extension LifetimeDependence.Scope {
480476
return InstructionRange(for: value, context)
481477
}
482478
}
483-
484-
private static func computeInitializedRange(initialAddress: Value,
485-
initializingStore: Instruction?,
479+
480+
// !!! - handle allocations of trivial values: no destroy. Use the dealloc in that case?
481+
private static func computeInitializedRange(initialAddress: Value, initializingStore: Instruction?,
486482
_ context: Context)
487483
-> InstructionRange {
484+
488485
assert(initialAddress.type.isAddress)
489486

490487
var range: InstructionRange
@@ -619,7 +616,12 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
619616

620617
mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult {
621618
if let inst = value.definingInstruction, VariableScopeInstruction(inst) != nil {
622-
return introducer(value, owner)
619+
return visitorClosure(value)
620+
}
621+
// Finding a variable introducer requires following the mark_dependence forwarded value, not the base value like the
622+
// default LifetimeDependenceUseDefWalker.
623+
if value is MarkDependenceInst {
624+
return walkUpDefault(forwarded: value, owner)
623625
}
624626
return walkUpDefault(dependent: value, owner: owner)
625627
}
@@ -685,6 +687,9 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
685687
protocol LifetimeDependenceUseDefWalker : ForwardingUseDefWalker where PathContext == Value? {
686688
var context: Context { get }
687689

690+
/// 'owner' is the most recently visited suitable base. Generally, this is the most recent owned value. When a
691+
/// mark_dependence value operand is forwarded from its base operand, however, the owner is not updated because that
692+
/// would could lead to introducing an illegal mark_dependence with the same value for both operands.
688693
mutating func introducer(_ value: Value, _ owner: Value?) -> WalkResult
689694

690695
// Minimally, check a ValueSet. This walker may traverse chains of
@@ -1239,7 +1244,7 @@ let lifetimeDependenceScopeTest = FunctionTest("lifetime_dependence_scope") {
12391244
function, arguments, context in
12401245
let markDep = arguments.takeValue() as! MarkDependenceInst
12411246
guard let dependence = LifetimeDependence(markDep, context) else {
1242-
print("Trivial Dependence")
1247+
print("Invalid Dependence")
12431248
return
12441249
}
12451250
print(dependence)

SwiftCompilerSources/Sources/Optimizer/Utilities/OwnershipLiveness.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ func computeLinearLiveness(for definingValue: Value, _ context: Context)
5656
-> InstructionRange {
5757

5858
assert(definingValue.ownership == .owned
59-
|| BeginBorrowValue(definingValue) != nil,
60-
"value must define an OSSA lifetime")
59+
|| BeginBorrowValue(definingValue) != nil
60+
|| VariableScopeInstruction(definingValue.definingInstruction) != nil,
61+
"value must define an OSSA lifetime or variable scope")
6162

6263
// InstructionRange cannot directly represent the beginning of the block
6364
// so we fake it with getRepresentativeInstruction().

0 commit comments

Comments
 (0)