Skip to content

Commit 4e7623e

Browse files
authored
Merge pull request #79236 from atrick/fix-lifedep-access
LifetimeDependence: simplify and fix multiple bugs
2 parents bda17b0 + c2842e8 commit 4e7623e

24 files changed

+997
-724
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 10 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
}
@@ -199,6 +200,10 @@ private struct DiagnoseDependence {
199200
if function.hasUnsafeNonEscapableResult {
200201
return .continueWalk
201202
}
203+
// If the dependence scope is global, then it has immortal lifetime.
204+
if case .global = dependence.scope {
205+
return .continueWalk
206+
}
202207
// Check that the parameter dependence for this result is the same
203208
// as the current dependence scope.
204209
if let arg = dependence.scope.parentValue as? FunctionArgument,
@@ -287,7 +292,7 @@ private struct LifetimeVariable {
287292

288293
private func getFirstVariableIntroducer(of value: Value, _ context: some Context) -> Value? {
289294
var introducer: Value?
290-
var useDefVisitor = VariableIntroducerUseDefWalker(context) {
295+
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value) {
291296
introducer = $0
292297
return .abortWalk
293298
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 255 additions & 12 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
@@ -235,7 +235,7 @@ private func insertResultDependencies(for apply: LifetimeDependentApply, _ conte
235235
guard var sources = apply.getResultDependenceSources() else {
236236
return
237237
}
238-
log("Creating dependencies for \(apply.applySite)")
238+
log("Creating result dependencies for \(apply.applySite)")
239239

240240
// Find the dependence base for each source.
241241
sources.initializeBases(context)
@@ -246,13 +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 {
250-
continue
251-
}
252-
// TODO: This might bail-out on SIL that should be diagnosed. We should handle/cleanup projections and casts that
253-
// occur before the initializingStore. Or check in the SIL verifier that all stores without an access scope follow
254-
// this form. Then convert this bail-out to an assert.
255-
guard initialAddress.usesOccurOnOrAfter(instruction: initializingStore, context) else {
249+
guard case let .store(initializingStore, initialAddress) = accessBase.findSingleInitializer(context) else {
256250
continue
257251
}
258252
assert(initializingStore == resultOper.instruction, "an indirect result is a store")
@@ -268,7 +262,7 @@ private func insertParameterDependencies(apply: LifetimeDependentApply, target:
268262
guard var sources = apply.getParameterDependenceSources(target: target) else {
269263
return
270264
}
271-
log("Creating dependencies for \(apply.applySite)")
265+
log("Creating parameter dependencies for \(apply.applySite)")
272266

273267
sources.initializeBases(context)
274268

@@ -285,8 +279,13 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
285279
let markDep = builder.createMarkDependence(
286280
value: currentValue, base: base, kind: .Unresolved)
287281

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

0 commit comments

Comments
 (0)