Skip to content

[6.0] LocalVariableUtils: add support for temporary enum initialization. #73025

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
Apr 15, 2024
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 @@ -365,12 +365,19 @@ extension LocalVariableAccessWalker: AddressUseVisitor {

// Handle storage type projections, like MarkUninitializedInst. Path projections should not be visited. They only
// occur inside the access.
//
// Exception: stack-allocated temporaries may be treated like local variables for the purpose of finding all
// uses. Such temporaries do not have access scopes, so we need to walk down any projection that may be used to
// initialize the temporary.
mutating func projectedAddressUse(of operand: Operand, into value: Value) -> WalkResult {
// TODO: we need an abstraction for path projections. For local variables, these cannot occur outside of an access.
switch operand.instruction {
case is StructExtractInst, is TupleElementAddrInst, is IndexAddrInst, is TailAddrInst, is InitEnumDataAddrInst,
is UncheckedTakeEnumDataAddrInst, is InitExistentialAddrInst, is OpenExistentialAddrInst:
case is StructElementAddrInst, is TupleElementAddrInst, is IndexAddrInst, is TailAddrInst,
is UncheckedTakeEnumDataAddrInst, is OpenExistentialAddrInst:
return .abortWalk
// Projections used to initialize a temporary
case is InitEnumDataAddrInst, is InitExistentialAddrInst:
fallthrough
default:
return walkDownAddressUses(address: value)
}
Expand Down Expand Up @@ -401,7 +408,9 @@ extension LocalVariableAccessWalker: AddressUseVisitor {

mutating func leafAddressUse(of operand: Operand) -> WalkResult {
switch operand.instruction {
case is StoringInstruction, is SourceDestAddrInstruction, is DestroyAddrInst:
case is StoringInstruction, is SourceDestAddrInstruction, is DestroyAddrInst, is DeinitExistentialAddrInst,
is InjectEnumAddrInst, is TupleAddrConstructorInst, is InitBlockStorageHeaderInst, is PackElementSetInst:
// Handle instructions that initialize both temporaries and local variables.
visit(LocalVariableAccess(.store, operand))
case is DeallocStackInst:
break
Expand Down
10 changes: 10 additions & 0 deletions test/SILOptimizer/lifetime_dependence_diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ public struct NEInt: ~Escapable {
}
}

public enum NEOptional<Wrapped: ~Escapable>: ~Escapable {
case none
case some(Wrapped)
}

extension NEOptional where Wrapped: ~Escapable {
// Test that enum initialization passes diagnostics.
public init(_ some: consuming Wrapped) { self = .some(some) }
}

func takeClosure(_: () -> ()) {}

// No mark_dependence is needed for a inherited scope.
Expand Down
206 changes: 206 additions & 0 deletions test/SILOptimizer/lifetime_dependence_optional.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// RUN: %target-swift-frontend %s -emit-sil \
// RUN: -verify \
// RUN: -sil-verify-all \
// RUN: -module-name test \
// RUN: -enable-experimental-feature NoncopyableGenerics \
// RUN: -enable-experimental-feature NonescapableTypes \
// RUN: -enable-experimental-feature BorrowingSwitch

// REQUIRES: asserts
// REQUIRES: swift_in_compiler

// Simply test that it is possible for a module to define a pseudo-Optional type without triggering any compiler errors.

public protocol ExpressibleByNilLiteral: ~Copyable & ~Escapable {
@_unsafeNonescapableResult
init(nilLiteral: ())
}

@frozen
public enum Nillable<Wrapped: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {
case none
case some(Wrapped)
}

extension Nillable: Copyable where Wrapped: ~Escapable /* & Copyable */ {}

extension Nillable: Escapable where Wrapped: ~Copyable /* & Escapable */ {}

extension Nillable: Sendable where Wrapped: ~Copyable & ~Escapable & Sendable { }

extension Nillable: _BitwiseCopyable where Wrapped: _BitwiseCopyable { }

extension Nillable: ExpressibleByNilLiteral where Wrapped: ~Copyable & ~Escapable {
@_transparent
@_unsafeNonescapableResult
public init(nilLiteral: ()) {
self = .none
}
}

extension Nillable where Wrapped: ~Copyable & ~Escapable {
@_transparent
public init(_ some: consuming Wrapped) { self = .some(some) }
}

extension Nillable where Wrapped: ~Copyable {
public consuming func _consumingMap<U: ~Copyable, E: Error>(
_ transform: (consuming Wrapped) throws(E) -> U
) throws(E) -> U? {
switch consume self {
case .some(let y):
return .some(try transform(y))
case .none:
return .none
}
}

public borrowing func _borrowingMap<U: ~Copyable, E: Error>(
_ transform: (borrowing Wrapped) throws(E) -> U
) throws(E) -> U? {
switch self {
case .some(borrowing y):
return .some(try transform(y))
case .none:
return .none
}
}
}

extension Nillable where Wrapped: ~Copyable {
public consuming func _consumingFlatMap<U: ~Copyable, E: Error>(
_ transform: (consuming Wrapped) throws(E) -> U?
) throws(E) -> U? {
switch consume self {
case .some(let y):
return try transform(consume y)
case .none:
return .none
}
}

public func _borrowingFlatMap<U: ~Copyable, E: Error>(
_ transform: (borrowing Wrapped) throws(E) -> U?
) throws(E) -> U? {
switch self {
case .some(borrowing y):
return try transform(y)
case .none:
return .none
}
}
}

extension Nillable where Wrapped: ~Copyable {
public consuming func _consumingUnsafelyUnwrap() -> Wrapped {
switch consume self {
case .some(let x):
return x
case .none:
fatalError("consumingUsafelyUnwrap of nil optional")
}
}
}

extension Optional where Wrapped: ~Copyable {
internal consuming func _consumingUncheckedUnwrapped() -> Wrapped {
if let x = self {
return x
}
fatalError("_uncheckedUnwrapped of nil optional")
}
}

extension Optional where Wrapped: ~Copyable {
public mutating func _take() -> Self {
let result = consume self
self = nil
return result
}
}

extension Optional where Wrapped: ~Copyable {
public static func ~=(
lhs: _OptionalNilComparisonType,
rhs: borrowing Wrapped?
) -> Bool {
switch rhs {
case .some:
return false
case .none:
return true
}
}

public static func ==(
lhs: borrowing Wrapped?,
rhs: _OptionalNilComparisonType
) -> Bool {
switch lhs {
case .some:
return false
case .none:
return true
}
}

public static func !=(
lhs: borrowing Wrapped?,
rhs: _OptionalNilComparisonType
) -> Bool {
switch lhs {
case .some:
return true
case .none:
return false
}
}

public static func ==(
lhs: _OptionalNilComparisonType,
rhs: borrowing Wrapped?
) -> Bool {
switch rhs {
case .some:
return false
case .none:
return true
}
}

public static func !=(
lhs: _OptionalNilComparisonType,
rhs: borrowing Wrapped?
) -> Bool {
switch rhs {
case .some:
return true
case .none:
return false
}
}
}

public func ?? <T: ~Copyable>(
optional: consuming T?,
defaultValue: @autoclosure () throws -> T
) rethrows -> T {
switch consume optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}

public func ?? <T: ~Copyable>(
optional: consuming T?,
defaultValue: @autoclosure () throws -> T?
) rethrows -> T? {
switch consume optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}