Skip to content

LifetimeDependenceScopeFixup: handle unsafe addressors #78587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ private enum ImmutableScope {

init?(for basedAddress: Value, _ context: FunctionPassContext) {
switch basedAddress.enclosingAccessScope {
case .scope(let beginAccess):
case .access(let beginAccess):
if beginAccess.isUnsafe {
return nil
}
Expand Down Expand Up @@ -652,6 +652,9 @@ private enum ImmutableScope {
return nil
}
}
case .dependence(let markDep):
// ignore mark_dependence for the purpose of alias analysis.
self.init(for: markDep.value, context)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ private extension LifetimeDependence.Scope {
case let .access(beginAccess):
// Finding the access base also finds all intermediate nested scopes; there is no need to recursively call
// gatherExtensions().
let (accessBase, nestedAccesses) = beginAccess.accessBaseWithScopes
let accessBaseAndScopes = beginAccess.accessBaseWithScopes
let accessBase = accessBaseAndScopes.base
let ownerAddress: Value
var dependsOnArg: FunctionArgument? = nil
switch accessBase {
Expand All @@ -287,13 +288,18 @@ private extension LifetimeDependence.Scope {
.pointer, .index:
ownerAddress = accessBase.address!
}
assert(!nestedAccesses.isEmpty)
for nestedAccess in nestedAccesses {
innerScopes.push(.access(nestedAccess))
assert(!accessBaseAndScopes.scopes.isEmpty)
for nestedScope in accessBaseAndScopes.scopes {
switch nestedScope {
case let .access(beginAccess):
innerScopes.push(.access(beginAccess))
case .dependence, .base:
// ignore recursive mark_dependence base for the purpose of extending scopes. This pass will extend the base
// of that mark_dependence (if it is unresolved) later as a separate LifetimeDependence.Scope.
break
}
}
// This is the outermost scope. We only see nested access scopes after inlining.
//
// TODO: could we have a nested access within an yielded inout address?
// TODO: could we have a nested access within an yielded inout address prior to inlining?
return SingleInlineArray(element: ScopeExtension(owner: ownerAddress, nestedScopes: innerScopes,
dependsOnArg: dependsOnArg))
case let .borrowed(beginBorrow):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ struct LifetimeDependence : CustomStringConvertible {
case yield(Value)
/// An owned value whose OSSA lifetime encloses nonescapable values, or a trivial variable introduced by move_value.
case owned(Value)
/// An borrowed value whose OSSA lifetime encloses nonescapable values, or a trivial variable introduced by
/// A borrowed value whose OSSA lifetime encloses nonescapable values, or a trivial variable introduced by
/// begin_borrow.
case borrowed(BeginBorrowValue)
/// Singly-initialized addressable storage (likely for an
Expand Down Expand Up @@ -313,10 +313,15 @@ extension LifetimeDependence.Scope {

private init(address: Value, _ context: some Context) {
switch address.enclosingAccessScope {
case let .scope(access):
case let .access(access):
self = .access(access)
case let .base(accessBase):
self = Self(accessBase: accessBase, address: address, context)
case let .dependence(markDep):
// The current dependence only represents the forwarded address. If the mark_dependence instruction encoutered
// here is [unresolved], then a separate LifetimeDependence.Scope will be created for it, and if it is [escaping],
// then it is ignored for the purpose of lifetime dependence.
self.init(address: markDep.value, context)
}
}

Expand Down Expand Up @@ -633,12 +638,34 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {

mutating func walkUp(address: Value) -> WalkResult {
if let beginAccess = address.definingInstruction as? BeginAccessInst {
// Treat calls to unsafe[Mutable]Address like a projection of 'self' rather than a separate variable access.
if let addressorSelf = beginAccess.unsafeAddressorSelf {
return walkUp(valueOrAddress: addressorSelf)
}
return introducer(beginAccess, nil)
}
return walkUpDefault(address: address)
}
}

private extension BeginAccessInst {
// Recognize an access scope for a unsafe addressor:
// %adr = pointer_to_address
// %md = mark_dependence %adr
// begin_access [unsafe] %md
var unsafeAddressorSelf: Value? {
guard isUnsafe else {
return nil
}
let accessBaseAndScopes = address.accessBaseWithScopes
guard case .pointer = accessBaseAndScopes.base,
case let .dependence(markDep) = accessBaseAndScopes.scopes.first else {
return nil
}
return markDep.base
}
}

/// Walk up the lifetime dependence chain.
///
/// This finds the introducers of a dependence chain. which represent the value's "inherited" dependencies. This stops
Expand Down Expand Up @@ -1151,7 +1178,7 @@ extension LifetimeDependenceDefUseWalker {

// Get the local variable access that encloses this store.
var storeAccess = storedOperand.instruction
if case let .scope(beginAccess) = storeAddress.enclosingAccessScope {
if case let .access(beginAccess) = storeAddress.enclosingAccessScope {
storeAccess = beginAccess
}
if !localReachability.gatherAllReachableUses(of: storeAccess, in: &accessStack) {
Expand Down
52 changes: 38 additions & 14 deletions SwiftCompilerSources/Sources/SIL/Utilities/AccessUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ private extension PointerToAddressInst {
}
}

/// The `EnclosingScope` of an access is the innermost `begin_access`
/// The `EnclosingAccessScope` of an access is the innermost `begin_access`
/// instruction that checks for exclusivity of the access.
/// If there is no `begin_access` instruction found, then the scope is
/// the base itself.
Expand All @@ -497,13 +497,29 @@ private extension PointerToAddressInst {
/// %l3 = load %a3 : $*Int64
/// end_access %a3 : $*Int64
/// ```
public enum EnclosingScope {
case scope(BeginAccessInst)
public enum EnclosingAccessScope {
case access(BeginAccessInst)
case base(AccessBase)
case dependence(MarkDependenceInst)
}

// An AccessBase with the nested enclosing scopes that contain the original address in bottom-up order.
public struct AccessBaseAndScopes {
public let base: AccessBase
public let scopes: SingleInlineArray<EnclosingAccessScope>

public var innermostAccess: BeginAccessInst? {
for scope in scopes {
if case let .access(beginAccess) = scope {
return beginAccess
}
}
return nil
}
}

private struct EnclosingAccessWalker : AddressUseDefWalker {
var enclosingScope: EnclosingScope?
var enclosingScope: EnclosingAccessScope?

mutating func walk(startAt address: Value, initialPath: UnusedWalkingPath = UnusedWalkingPath()) {
if walkUp(address: address, path: UnusedWalkingPath()) == .abortWalk {
Expand All @@ -522,19 +538,24 @@ private struct EnclosingAccessWalker : AddressUseDefWalker {
}

mutating func walkUp(address: Value, path: UnusedWalkingPath) -> WalkResult {
if let ba = address as? BeginAccessInst {
enclosingScope = .scope(ba)
switch address {
case let ba as BeginAccessInst:
enclosingScope = .access(ba)
return .continueWalk
case let md as MarkDependenceInst:
enclosingScope = .dependence(md)
return .continueWalk
default:
return walkUpDefault(address: address, path: path)
}
return walkUpDefault(address: address, path: path)
}
}

private struct AccessPathWalker : AddressUseDefWalker {
var result = AccessPath.unidentified()

// List of nested BeginAccessInst: inside-out order.
var foundBeginAccesses = SingleInlineArray<BeginAccessInst>()
// List of nested BeginAccessInst & MarkDependenceInst: inside-out order.
var foundEnclosingScopes = SingleInlineArray<EnclosingAccessScope>()

let enforceConstantProjectionPath: Bool

Expand Down Expand Up @@ -602,7 +623,9 @@ private struct AccessPathWalker : AddressUseDefWalker {
// projection. Bail out
return .abortWalk
} else if let ba = address as? BeginAccessInst {
foundBeginAccesses.push(ba)
foundEnclosingScopes.push(.access(ba))
} else if let md = address as? MarkDependenceInst {
foundEnclosingScopes.push(.dependence(md))
}
return walkUpDefault(address: address, path: path.with(indexAddr: false))
}
Expand Down Expand Up @@ -655,20 +678,21 @@ extension Value {
public var accessPathWithScope: (AccessPath, scope: BeginAccessInst?) {
var walker = AccessPathWalker()
walker.walk(startAt: self)
return (walker.result, walker.foundBeginAccesses.first)
let baseAndScopes = AccessBaseAndScopes(base: walker.result.base, scopes: walker.foundEnclosingScopes)
return (walker.result, baseAndScopes.innermostAccess)
}

/// Computes the enclosing access scope of this address value.
public var enclosingAccessScope: EnclosingScope {
public var enclosingAccessScope: EnclosingAccessScope {
var walker = EnclosingAccessWalker()
walker.walk(startAt: self)
return walker.enclosingScope ?? .base(.unidentified)
}

public var accessBaseWithScopes: (AccessBase, SingleInlineArray<BeginAccessInst>) {
public var accessBaseWithScopes: AccessBaseAndScopes {
var walker = AccessPathWalker()
walker.walk(startAt: self)
return (walker.result.base, walker.foundBeginAccesses)
return AccessBaseAndScopes(base: walker.result.base, scopes: walker.foundEnclosingScopes)
}

/// The root definition of a reference, obtained by skipping ownership forwarding and ownership transition.
Expand Down
42 changes: 42 additions & 0 deletions test/SILOptimizer/lifetime_dependence/semantics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,34 @@ extension Array {
}
}

struct Inner {
var p: UnsafePointer<Int>

@lifetime(borrow self)
borrowing func span() -> Span<Int> {
Span(base: p, count: 1)
}
}

struct Outer {
let inner: Inner
let fakePointer: UnsafePointer<Inner>

var innerAddress: Inner {
unsafeAddress {
fakePointer
}
}
/* TODO: rdar://137608270 Add Builtin.addressof() support for @addressable arguments
@addressableSelf
var innerAddress: Inner {
unsafeAddress {
Builtin.addressof(inner)
}
}
*/
}

func parse(_ span: Span<Int>) {}

// =============================================================================
Expand Down Expand Up @@ -172,3 +200,17 @@ func testTrivialScope<T>(a: Array<T>) -> Span<T> {
// expected-note @-3{{it depends on the lifetime of variable 'p'}}
// expected-note @-3{{this use causes the lifetime-dependent value to escape}}
}

// =============================================================================
// Scoped dependence on property access
// =============================================================================

@lifetime(borrow outer)
func testBorrowComponent(outer: Outer) -> Span<Int> {
outer.inner.span()
}

@lifetime(borrow outer)
func testBorrowAddressComponent(outer: Outer) -> Span<Int> {
outer.innerAddress.span()
}