Skip to content

Commit 02293fd

Browse files
committed
LifetimeDependence: simplify and fix multiple bugs.
Functional changes: Improved modeling of dependence on local variable scopes. For nested modify->read accesses, only extend the read accesses. Avoid making a read access dependent on an inout argument. The following needs to be an error to prevent span storage from being modified: @Lifetime(owner) foo(owner: inout Owner) -> Span { owner.span } Improve usability of borrowing trivial values (UnsafePointer). Allow: let span = Span(buffer.baseAddress) Ignore access scopes for trivial values. Structural changes: Delete the LifetimeDependenceUseDefWalker. Encapsulate all logic for variable introducers within the LifetimeDependenceInsertion pass. Once mark_dependence instructions are inserted, no subsequent pass needs to think about the "root" of a dependence. Fixes: rdar://142451725 (Escape analysis fails with mutations)
1 parent ca31440 commit 02293fd

File tree

7 files changed

+434
-557
lines changed

7 files changed

+434
-557
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -101,10 +101,11 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
101101
private func analyze(dependence: LifetimeDependence, _ context: FunctionPassContext) -> Bool {
102102
log("Dependence scope:\n\(dependence)")
103103

104-
// Briefly, some versions of Span in the standard library violated trivial lifetimes; versions of the compiler built
105-
// at that time simply ignored dependencies on trivial values. For now, disable trivial dependencies to allow newer
106-
// compilers to build against those older standard libraries. This check is only relevant for ~6 mo (until July 2025).
107104
if dependence.parentValue.type.objectType.isTrivial(in: dependence.function) {
105+
// Briefly, some versions of Span in the standard library violated trivial lifetimes; versions of the compiler built
106+
// at that time simply ignored dependencies on trivial values. For now, disable trivial dependencies to allow newer
107+
// compilers to build against those older standard libraries. This check is only relevant for ~6 mo (until July
108+
// 2025).
108109
if let sourceFileKind = dependence.function.sourceFileKind, sourceFileKind == .interface {
109110
return true
110111
}
@@ -287,7 +288,7 @@ private struct LifetimeVariable {
287288

288289
private func getFirstVariableIntroducer(of value: Value, _ context: some Context) -> Value? {
289290
var introducer: Value?
290-
var useDefVisitor = VariableIntroducerUseDefWalker(context) {
291+
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value) {
291292
introducer = $0
292293
return .abortWalk
293294
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 246 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -246,7 +246,7 @@ private func insertResultDependencies(for apply: LifetimeDependentApply, _ conte
246246
}
247247
for resultOper in apply.applySite.indirectResultOperands {
248248
let accessBase = resultOper.value.accessBase
249-
guard let (initialAddress, initializingStore) = accessBase.findSingleInitializer(context) else {
249+
guard case let .store(initializingStore, initialAddress) = accessBase.findSingleInitializer(context) else {
250250
continue
251251
}
252252
// TODO: This might bail-out on SIL that should be diagnosed. We should handle/cleanup projections and casts that
@@ -300,3 +300,247 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
300300
currentValue = markDep
301301
}
302302
}
303+
304+
/// Walk up the value dependence chain to find the best-effort variable declaration. Typically called while diagnosing
305+
/// an error.
306+
///
307+
/// Returns an array with at least one introducer value.
308+
///
309+
/// The walk stops at:
310+
/// - a variable declaration (begin_borrow [var_decl], move_value [var_decl])
311+
/// - a begin_access for a mutable variable access
312+
/// - the value or address "root" of the dependence chain
313+
func gatherVariableIntroducers(for value: Value, _ context: Context)
314+
-> SingleInlineArray<Value>
315+
{
316+
var introducers = SingleInlineArray<Value>()
317+
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value) {
318+
introducers.push($0)
319+
return .continueWalk
320+
}
321+
defer { useDefVisitor.deinitialize() }
322+
_ = useDefVisitor.walkUp(valueOrAddress: value)
323+
assert(!introducers.isEmpty, "missing variable introducer")
324+
return introducers
325+
}
326+
327+
// =============================================================================
328+
// VariableIntroducerUseDefWalker - upward walk
329+
// =============================================================================
330+
331+
/// Walk up lifetime dependencies to the first value associated with a variable declaration.
332+
///
333+
/// To start walking:
334+
/// walkUp(valueOrAddress: Value) -> WalkResult
335+
///
336+
/// This utility finds the value or address associated with the lvalue (variable declaration) that is passed as the
337+
/// source of a lifetime dependent argument. If no lvalue is found, then it finds the "root" of the chain of temporary
338+
/// rvalues.
339+
///
340+
/// This "looks through" projections: a property that is either visible as a stored property or access via
341+
/// unsafe[Mutable]Address.
342+
///
343+
/// dependsOn(lvalue.field) // finds 'lvalue' when 'field' is a stored property
344+
///
345+
/// dependsOn(lvalue.computed) // finds the temporary value directly returned by a getter.
346+
///
347+
/// SILGen emits temporary copies that violate lifetime dependence semantcs. This utility looks through such temporary
348+
/// copies, stopping at a value that introduces an immutable variable: move_value [var_decl] or begin_borrow [var_decl],
349+
/// or at an access of a mutable variable: begin_access [read] or begin_access [modify].
350+
///
351+
/// In this example, the dependence "root" is copied, borrowed, and forwarded before being used as the base operand of
352+
/// `mark_dependence`. The dependence "root" is the parent of the outer-most dependence scope.
353+
///
354+
/// %root = apply // lifetime dependence root
355+
/// %copy = copy_value %root
356+
/// %parent = begin_borrow %copy // lifetime dependence parent value
357+
/// %base = struct_extract %parent // lifetime dependence base value
358+
/// %dependent = mark_dependence [nonescaping] %value on %base
359+
///
360+
/// VariableIntroducerUseDefWalker extends the ForwardingUseDefWalker to follow copies, moves, and
361+
/// borrows. ForwardingUseDefWalker treats these as forward-extended lifetime introducers. But they inherit a lifetime
362+
/// dependency from their operand because non-escapable values can be copied, moved, and borrowed. Nonetheless, all of
363+
/// their uses must remain within original dependence scope.
364+
///
365+
/// # owned lifetime dependence
366+
/// %parent = apply // begin dependence scope -+
367+
/// ... |
368+
/// %1 = mark_dependence [nonescaping] %value on %parent |
369+
/// ... |
370+
/// %2 = copy_value %1 -+ |
371+
/// # forwarding instruction | |
372+
/// %3 = struct $S (%2) | forward-extended lifetime |
373+
/// | | OSSA Lifetime
374+
/// %4 = move_value %3 -+ |
375+
/// ... | forward-extended lifetime |
376+
/// %5 = begin_borrow %4 | -+ |
377+
/// # dependent use of %1 | | forward-extended lifetime|
378+
/// end_borrow %5 | -+ |
379+
/// destroy_value %4 -+ |
380+
/// ... |
381+
/// destroy_value %parent // end dependence scope -+
382+
///
383+
/// All of the dependent uses including `end_borrow %5` and `destroy_value %4` must be before the end of the dependence
384+
/// scope: `destroy_value %parent`. In this case, the dependence parent is an owned value, so the scope is simply the
385+
/// value's OSSA lifetime.
386+
struct VariableIntroducerUseDefWalker : ForwardingUseDefWalker {
387+
// The ForwardingUseDefWalker's context is the most recent lifetime owner.
388+
typealias PathContext = Value?
389+
390+
let context: Context
391+
392+
// If the scoped value is trivial, then only the variable's lexical scope is relevant, and access scopes can be
393+
// ignored.
394+
let isTrivialScope: Bool
395+
396+
// This visited set is only really needed for instructions with
397+
// multiple results, including phis.
398+
private var visitedValues: ValueSet
399+
400+
// Call \p visit rather than calling this directly.
401+
private let visitorClosure: (Value) -> WalkResult
402+
403+
init(_ context: Context, scopedValue: Value, _ visitor: @escaping (Value) -> WalkResult) {
404+
self.context = context
405+
self.isTrivialScope = scopedValue.type.isAddress
406+
? scopedValue.type.objectType.isTrivial(in: scopedValue.parentFunction)
407+
: scopedValue.isTrivial(context)
408+
self.visitedValues = ValueSet(context)
409+
self.visitorClosure = visitor
410+
}
411+
412+
mutating func deinitialize() {
413+
visitedValues.deinitialize()
414+
}
415+
416+
mutating func needWalk(for value: Value, _ owner: Value?) -> Bool {
417+
visitedValues.insert(value)
418+
}
419+
420+
mutating func introducer(_ value: Value, _ owner: Value?) -> WalkResult {
421+
return visitorClosure(value)
422+
}
423+
424+
mutating func walkUp(valueOrAddress: Value) -> WalkResult {
425+
if valueOrAddress.type.isAddress {
426+
return walkUp(address: valueOrAddress)
427+
}
428+
return walkUp(newLifetime: valueOrAddress)
429+
}
430+
}
431+
432+
// Helpers
433+
extension VariableIntroducerUseDefWalker {
434+
mutating func walkUp(newLifetime: Value) -> WalkResult {
435+
let newOwner = newLifetime.ownership == .owned ? newLifetime : nil
436+
return walkUp(value: newLifetime, newOwner)
437+
}
438+
439+
mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult {
440+
// Check for variable introducers: move_value, begin_value, before following OwnershipTransitionInstruction.
441+
if let inst = value.definingInstruction, VariableScopeInstruction(inst) != nil {
442+
return visitorClosure(value)
443+
}
444+
switch value.definingInstruction {
445+
case let transition as OwnershipTransitionInstruction:
446+
return walkUp(newLifetime: transition.operand.value)
447+
case let load as LoadInstruction:
448+
return walkUp(address: load.address)
449+
default:
450+
break
451+
}
452+
// If the dependence chain has a phi, consider it a root. Dependence roots dominate all dependent values.
453+
if Phi(value) != nil {
454+
return introducer(value, owner)
455+
}
456+
// ForwardingUseDefWalker will callback to introducer() when it finds no forwarding instruction.
457+
return walkUpDefault(forwarded: value, owner)
458+
}
459+
460+
// Handle temporary allocations and access scopes.
461+
mutating func walkUp(address: Value) -> WalkResult {
462+
let accessBaseAndScopes = address.accessBaseWithScopes
463+
// Continue walking for some kinds of access base.
464+
switch accessBaseAndScopes.base {
465+
case .box, .global, .class, .tail, .pointer, .index, .unidentified:
466+
break
467+
case let .stack(allocStack):
468+
if allocStack.varDecl == nil {
469+
// Ignore temporary stack locations. Their access scopes do not affect lifetime dependence.
470+
return walkUp(stackInitializer: allocStack, at: address)
471+
}
472+
case let .argument(arg):
473+
// Ignore access scopes for @in or @in_guaranteed arguments when all scopes are reads. Do not ignore a [read]
474+
// access of an inout argument or outer [modify]. Mutation later with the outer scope could invalidate the
475+
// borrowed state in this narrow scope. Do not ignore any mark_depedence on the address.
476+
if arg.convention.isIndirectIn && accessBaseAndScopes.isOnlyReadAccess {
477+
return introducer(arg, nil)
478+
}
479+
// @inout arguments may be singly initialized (when no modification exists in this function), but this is not
480+
// relevant here because they require nested access scopes which can never be ignored.
481+
case let .yield(yieldedAddress):
482+
// Ignore access scopes for @in or @in_guaranteed yields when all scopes are reads.
483+
let apply = yieldedAddress.definingInstruction as! FullApplySite
484+
if apply.convention(of: yieldedAddress).isIndirectIn && accessBaseAndScopes.isOnlyReadAccess {
485+
return introducer(yieldedAddress, nil)
486+
}
487+
case .storeBorrow(let sb):
488+
// Walk up through a store into a temporary.
489+
if accessBaseAndScopes.scopes.isEmpty,
490+
case .stack = sb.destinationOperand.value.accessBase {
491+
return walkUp(newLifetime: sb.source)
492+
}
493+
}
494+
// Skip the access scope for unsafe[Mutable]Address. Treat it like a projection of 'self' rather than a separate
495+
// variable access.
496+
if case let .access(innerAccess) = accessBaseAndScopes.scopes.first,
497+
let addressorSelf = innerAccess.unsafeAddressorSelf {
498+
return walkUp(valueOrAddress: addressorSelf)
499+
}
500+
// Ignore the acces scope for trivial values regardless of whether it is singly-initialized. Trivial values do not
501+
// need to be kept alive in memory and can be safely be overwritten in the same scope. Lifetime dependence only
502+
// cares that the loaded value is within the lexical scope of the trivial value's variable declaration. Rather than
503+
// skipping all access scopes, call 'walkUp' on each nested access in case one of them needs to redirect the walk,
504+
// as required for 'access.unsafeAddressorSelf'.
505+
if isTrivialScope {
506+
switch accessBaseAndScopes.scopes.first {
507+
case .none, .base:
508+
break
509+
case let .access(beginAccess):
510+
return walkUp(address: beginAccess.address)
511+
case let .dependence(markDep):
512+
return walkUp(address: markDep.value)
513+
}
514+
}
515+
return introducer(accessBaseAndScopes.enclosingAccess.address ?? address, nil)
516+
}
517+
518+
// Handle singly-initialized temporary stack locations.
519+
mutating func walkUp(stackInitializer allocStack: AllocStackInst, at address: Value) -> WalkResult {
520+
guard let initializer = allocStack.accessBase.findSingleInitializer(context) else {
521+
return introducer(address, nil)
522+
}
523+
if case let .store(store, _) = initializer {
524+
switch store {
525+
case let store as StoringInstruction:
526+
return walkUp(newLifetime: store.source)
527+
case let srcDestInst as SourceDestAddrInstruction:
528+
return walkUp(address: srcDestInst.destination)
529+
case let apply as FullApplySite:
530+
if let f = apply.referencedFunction, f.isConvertPointerToPointerArgument {
531+
return walkUp(address: apply.parameterOperands[0].value)
532+
}
533+
default:
534+
break
535+
}
536+
}
537+
return introducer(address, nil)
538+
}
539+
}
540+
541+
let variableIntroducerTest = FunctionTest("variable_introducer") {
542+
function, arguments, context in
543+
let value = arguments.takeValue()
544+
print("Variable introducers of: \(value)")
545+
print(gatherVariableIntroducers(for: value, context))
546+
}

0 commit comments

Comments
 (0)