diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift index 49fa57f3c313e..684052f28d984 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift @@ -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) } @@ -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 diff --git a/test/SILOptimizer/lifetime_dependence_diagnostics.swift b/test/SILOptimizer/lifetime_dependence_diagnostics.swift index 6e1e3f18e70c8..eaec501e8494e 100644 --- a/test/SILOptimizer/lifetime_dependence_diagnostics.swift +++ b/test/SILOptimizer/lifetime_dependence_diagnostics.swift @@ -48,6 +48,16 @@ public struct NEInt: ~Escapable { } } +public enum NEOptional: ~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. diff --git a/test/SILOptimizer/lifetime_dependence_optional.swift b/test/SILOptimizer/lifetime_dependence_optional.swift new file mode 100644 index 0000000000000..f0a37c88da181 --- /dev/null +++ b/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: ~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( + _ 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( + _ 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( + _ 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( + _ 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 ?? ( + 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 ?? ( + optional: consuming T?, + defaultValue: @autoclosure () throws -> T? +) rethrows -> T? { + switch consume optional { + case .some(let value): + return value + case .none: + return try defaultValue() + } +}