Skip to content

Commit

Permalink
Merge pull request #73025 from atrick/60-fix-enum-local
Browse files Browse the repository at this point in the history
[6.0] LocalVariableUtils: add support for temporary enum initialization.
  • Loading branch information
atrick committed Apr 15, 2024
2 parents 6d5fa1e + 1d5ef2b commit 59748d9
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 3 deletions.
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
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
@@ -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()
}
}

0 comments on commit 59748d9

Please sign in to comment.