From a9ee0b6ba0d0d2c67cc6fcc5999e10616777ab40 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 3 Nov 2025 13:43:34 -0800 Subject: [PATCH 1/2] Add Iterable BorrowIteratorProtocol --- .../cmake/modules/ExperimentalFeatures.cmake | 3 +- Runtimes/Core/core/CMakeLists.txt | 2 + stdlib/cmake/modules/SwiftSource.cmake | 9 +- stdlib/public/core/Array.swift | 36 +++- .../public/core/BorrowIteratorProtocol.swift | 185 ++++++++++++++++++ stdlib/public/core/CMakeLists.txt | 6 +- stdlib/public/core/GroupInfo.json | 6 +- stdlib/public/core/Iterable.swift | 58 ++++++ 8 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 stdlib/public/core/BorrowIteratorProtocol.swift create mode 100644 stdlib/public/core/Iterable.swift diff --git a/Runtimes/Core/cmake/modules/ExperimentalFeatures.cmake b/Runtimes/Core/cmake/modules/ExperimentalFeatures.cmake index d11fb6e8fcab3..e6c1d3c3523c9 100644 --- a/Runtimes/Core/cmake/modules/ExperimentalFeatures.cmake +++ b/Runtimes/Core/cmake/modules/ExperimentalFeatures.cmake @@ -13,4 +13,5 @@ add_compile_options( "$<$:SHELL:-enable-experimental-feature BitwiseCopyable>" "$<$:SHELL:-enable-experimental-feature Extern>" "$<$:SHELL:-enable-experimental-feature AllowUnsafeAttribute>" - "$<$:SHELL:-enable-experimental-feature ValueGenerics>") + "$<$:SHELL:-enable-experimental-feature ValueGenerics>" + "$<$:SHELL:-enable-experimental-feature Lifetimes>") diff --git a/Runtimes/Core/core/CMakeLists.txt b/Runtimes/Core/core/CMakeLists.txt index 023c23fe9e433..7b99164d5c05a 100644 --- a/Runtimes/Core/core/CMakeLists.txt +++ b/Runtimes/Core/core/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(swiftCore BidirectionalCollection.swift Bitset.swift Bool.swift + BorrowIteratorProtocol.swift BridgeObjectiveC.swift BridgeStorage.swift BridgingBuffer.swift @@ -93,6 +94,7 @@ add_library(swiftCore InputStream.swift IntegerParsing.swift Integers.swift + Iterable.swift Join.swift KeyPath.swift KeyValuePairs.swift diff --git a/stdlib/cmake/modules/SwiftSource.cmake b/stdlib/cmake/modules/SwiftSource.cmake index 98ead31c28e32..6e81f20cadde2 100644 --- a/stdlib/cmake/modules/SwiftSource.cmake +++ b/stdlib/cmake/modules/SwiftSource.cmake @@ -342,15 +342,15 @@ function(_add_target_variant_swift_compile_flags if(SWIFT_STDLIB_OS_VERSIONING) list(APPEND result "-D" "SWIFT_RUNTIME_OS_VERSIONING") endif() - + if(SWIFT_STDLIB_STATIC_PRINT) list(APPEND result "-D" "SWIFT_STDLIB_STATIC_PRINT") endif() - + if(SWIFT_STDLIB_ENABLE_UNICODE_DATA) list(APPEND result "-D" "SWIFT_STDLIB_ENABLE_UNICODE_DATA") endif() - + if(SWIFT_STDLIB_ENABLE_VECTOR_TYPES) list(APPEND result "-D" "SWIFT_STDLIB_ENABLE_VECTOR_TYPES") endif() @@ -612,7 +612,7 @@ function(_compile_swift_files list(APPEND swift_flags "-autolink-force-load") endif() - # Don't need to link runtime compatibility libraries for older runtimes + # Don't need to link runtime compatibility libraries for older runtimes # into the new runtime. if (SWIFTFILE_IS_STDLIB OR SWIFTFILE_IS_SDK_OVERLAY) list(APPEND swift_flags "-runtime-compatibility-version" "none") @@ -640,6 +640,7 @@ function(_compile_swift_files list(APPEND swift_flags "-enable-experimental-feature" "LifetimeDependence") list(APPEND swift_flags "-enable-experimental-feature" "InoutLifetimeDependence") list(APPEND swift_flags "-enable-experimental-feature" "LifetimeDependenceMutableAccessors") + list(APPEND swift_flags "-enable-experimental-feature" "Lifetimes") list(APPEND swift_flags "-enable-upcoming-feature" "MemberImportVisibility") diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index a590a5ba2782c..6346a7658f712 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -498,7 +498,7 @@ extension Array: _ArrayProtocol { @inlinable // FIXME(inline-always) @inline(__always) get { - return _buffer.owner + return _buffer.owner } } @@ -814,7 +814,7 @@ extension Array: RandomAccessCollection, MutableCollection { } } } - + /// The number of elements in the array. @inlinable @_semantics("array.get_count") @@ -1249,14 +1249,14 @@ extension Array: RangeReplaceableCollection { let oldCount = _buffer.mutableCount let startNewElements = unsafe _buffer.mutableFirstElementAddress + oldCount let buf = unsafe UnsafeMutableBufferPointer( - start: startNewElements, + start: startNewElements, count: _buffer.mutableCapacity - oldCount) var (remainder,writtenUpTo) = unsafe buf.initialize(from: newElements) - + // trap on underflow from the sequence's underestimate: let writtenCount = unsafe buf.distance(from: buf.startIndex, to: writtenUpTo) - _precondition(newElementsCount <= writtenCount, + _precondition(newElementsCount <= writtenCount, "newElements.underestimatedCount was an overestimate") // can't check for overflow as sequences can underestimate @@ -1440,7 +1440,7 @@ extension Array: RangeReplaceableCollection { return try unsafe body(bufferPointer) } } - + @inlinable public __consuming func _copyToContiguousArray() -> ContiguousArray { if let n = _buffer.requestNativeBuffer() { @@ -1799,7 +1799,7 @@ extension Array { // a precondition and Array never lies about its count. guard var p = buffer.baseAddress else { _preconditionFailure("Attempt to copy contents into nil buffer pointer") } - _precondition(self.count <= buffer.count, + _precondition(self.count <= buffer.count, "Insufficient space allocated to copy array contents") if let s = unsafe _baseAddressIfContiguous { @@ -2160,3 +2160,25 @@ internal struct _ArrayAnyHashableBox } extension Array: @unchecked Sendable where Element: Sendable { } + +// FIXME: This should be accepted, but we get the following: +// error: type 'Array' does not conform to protocol 'Iterable' +// note: multiple matching functions named '_customContainsEquatableElement' with type '(borrowing Array.Element) -> Bool?' (aka '(borrowing Element) -> Optional') + +// @available(SwiftStdlib 6.3, *) +// extension Array: Iterable { +// public typealias BorrowIterator = Span.BorrowIterator + +// @_alwaysEmitIntoClient +// @_transparent +// public var estimatedCount: EstimatedCount { +// .exactly(count) +// } + +// @_alwaysEmitIntoClient +// @_lifetime(borrow self) +// @_transparent +// public func startBorrowIteration() -> Span { +// self.span +// } +// } diff --git a/stdlib/public/core/BorrowIteratorProtocol.swift b/stdlib/public/core/BorrowIteratorProtocol.swift new file mode 100644 index 0000000000000..8a585b98abc21 --- /dev/null +++ b/stdlib/public/core/BorrowIteratorProtocol.swift @@ -0,0 +1,185 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.3, *) +public protocol BorrowIteratorProtocol: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + // FIXME: This ought to be a core requirement, but `Ref` is not a thing yet. +// @_lifetime(&self) +// @_lifetime(self: copy self) +// mutating func next() -> Ref? + + /// Advance the iterator, returning an ephemeral span over the elements + /// that are ready to be visited. + /// + /// If the underlying iterable is a container type, then the returned span + /// typically directly addresses one of its storage buffers. On the other + /// hand, if the underlying iterable materializes its elements on demand, + /// then the returned span addresses some temporary buffer associated with + /// the iterator itself. Consequently, the returned span is tied to this + /// particular invocation of `nextSpan`, and it cannot survive until the next + /// invocation of it. + /// + /// If the iterator has not yet reached the end of the underlying iterable, + /// then this method returns a non-empty span of at most `maximumCount` + /// elements, and updates the iterator's current position to the element + /// following the last item in the returned span (or the end, if there is + /// none). The `maximumCount` argument allows callers to avoid getting more + /// items that they are able to process in one go, simplifying usage, and + /// avoiding materializing more elements than needed. + /// + /// If the iterator's current position is at the end of the container, then + /// this method returns an empty span without updating the position. + /// + /// This method can be used to efficiently process the items of a container + /// in bulk, by directly iterating over its piecewise contiguous pieces of + /// storage: + /// + /// var it = items.startBorrowIteration() + /// while true { + /// let span = it.nextSpan(after: &index) + /// if span.isEmpty { break } + /// // Process items in `span` + /// } + /// + /// Note: The spans returned by this method are not guaranteed to be disjunct. + /// Iterators that materialize elements on demand typically reuse the same + /// buffer over and over again; and even some proper containers may link to a + /// single storage chunk (or parts of a storage chunk) multiple times, for + /// example to repeat their contents. + /// + /// Note: Repeatedly iterating over the same container is expected to return + /// the same items (collected in similarly sized span instances), but the + /// returned spans are not guaranteed to be identical. For example, this is + /// the case with containers that store some of their contents within their + /// direct representation. Such containers may not always have a unique + /// address in memory, and so the locations of the spans exposed by this + /// method may vary between different borrows of the same container.) + @_lifetime(&self) + @_lifetime(self: copy self) + mutating func nextSpan(maximumCount: Int) -> Span + + /// Advance the position of this iterator by the specified offset, or until + /// the end of the underlying iterable. + /// + /// Returns the number of items that were skipped. If this is less than + /// `maximumOffset`, then the iterable did not have enough elements left to + /// skip the requested number of items. In this case, the iterator's current + /// position is set to the end of the iterable. + /// + /// `maximumOffset` must be nonnegative, unless this is a bidirectional + /// or random-access iterator. + @_lifetime(self: copy self) + mutating func skip(by maximumOffset: Int) -> Int + + // FIXME: Add BidirectionalBorrowIteratorProtocol and RandomAccessBorrowIteratorProtocol. + // BidirectionalBorrowIteratorProtocol would need to have a `previousSpan` + // method, which considerably complicates implementation. + // Perhaps these would be better left to as variants of protocol Container, + // which do not need a separate iterator concept. +} + +@available(SwiftStdlib 6.3, *) +extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable { + @_alwaysEmitIntoClient + @_lifetime(&self) + @_lifetime(self: copy self) + @_transparent + public mutating func nextSpan() -> Span { + nextSpan(maximumCount: Int.max) + } +} + +@available(SwiftStdlib 6.3, *) +extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable { + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func skip(by offset: Int) -> Int { + var remainder = offset + while remainder > 0 { + let span = nextSpan(maximumCount: remainder) + if span.isEmpty { break } + remainder &-= span.count + } + return offset &- remainder + } +} + +@available(SwiftStdlib 6.3, *) +extension Span: Iterable where Element: ~Copyable { + // FIXME: This simple definition cannot also be a backward (or bidirectional) + // iterator, nor a random-access iterator. If we want to go in that direction, + // we'll need to rather introduce a type more like `RigidArray.BorrowIterator`. + public typealias BorrowIterator = Self + + @_alwaysEmitIntoClient + @_transparent + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_alwaysEmitIntoClient + @_lifetime(copy self) + @_transparent + public func startBorrowIteration() -> Span { + self + } +} + +@available(SwiftStdlib 6.3, *) +extension Span: BorrowIteratorProtocol where Element: ~Copyable { + @_alwaysEmitIntoClient + @_lifetime(&self) + @_lifetime(self: copy self) + public mutating func nextSpan(maximumCount: Int) -> Span { + let result = extracting(first: maximumCount) + self = extracting(droppingFirst: maximumCount) + return result + } +} + +@available(SwiftStdlib 6.3, *) +extension MutableSpan: Iterable where Element: ~Copyable { + public typealias BorrowIterator = Span.BorrowIterator + + @_alwaysEmitIntoClient + @_transparent + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_alwaysEmitIntoClient + @_lifetime(borrow self) + @_transparent + public func startBorrowIteration() -> Span { + span + } +} + +@available(SwiftStdlib 6.3, *) +extension OutputSpan: Iterable where Element: ~Copyable { + public typealias BorrowIterator = Span.BorrowIterator + + @_alwaysEmitIntoClient + @_transparent + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_alwaysEmitIntoClient + @_lifetime(borrow self) + @_transparent + public func startBorrowIteration() -> Span { + self.span + } +} diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 935c86843bedc..fc574b36436ec 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -45,6 +45,7 @@ split_embedded_sources( EMBEDDED BidirectionalCollection.swift EMBEDDED Bitset.swift EMBEDDED Bool.swift + EMBEDDED BorrowIteratorProtocol.swift NORMAL BridgeObjectiveC.swift EMBEDDED BridgeStorage.swift NORMAL BridgingBuffer.swift @@ -98,6 +99,7 @@ split_embedded_sources( EMBEDDED InputStream.swift EMBEDDED IntegerParsing.swift EMBEDDED Integers.swift + EMBEDDED Iterable.swift EMBEDDED Join.swift EMBEDDED KeyPath.swift EMBEDDED KeyValuePairs.swift @@ -275,7 +277,7 @@ if(SWIFT_STDLIB_ENABLE_VECTOR_TYPES) split_embedded_sources( OUT_LIST_EMBEDDED SWIFTLIB_EMBEDDED_VECTOR_GYB_SOURCES OUT_LIST_NORMAL SWIFTLIB_VECTOR_GYB_SOURCES - + EMBEDDED SIMDIntegerConcreteOperations.swift.gyb EMBEDDED SIMDFloatConcreteOperations.swift.gyb EMBEDDED SIMDMaskConcreteOperations.swift.gyb @@ -530,7 +532,7 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB) list(GET list 0 arch) list(GET list 1 mod) list(GET list 2 triple) - + set(SWIFT_SDK_embedded_ARCH_${arch}_MODULE "${mod}") set(SWIFT_SDK_embedded_LIB_SUBDIR "embedded") set(SWIFT_SDK_embedded_ARCH_${arch}_TRIPLE "${triple}") diff --git a/stdlib/public/core/GroupInfo.json b/stdlib/public/core/GroupInfo.json index 0c85960838b5b..2e021cc6637f4 100644 --- a/stdlib/public/core/GroupInfo.json +++ b/stdlib/public/core/GroupInfo.json @@ -211,7 +211,7 @@ "RawSpan.swift", "Span.swift" ], - "UTF8Span": [ + "UTF8Span": [ "UTF8EncodingError.swift", "UTF8Span.swift", "UTF8SpanBits.swift", @@ -276,7 +276,9 @@ "EmbeddedStubs.swift", "EmbeddedPrint.swift", "InlineArray.swift", - "_InlineArray.swift" + "_InlineArray.swift", + "Iterable.swift", + "BorrowIteratorProtocol.swift" ], "Result": [ "Result.swift" diff --git a/stdlib/public/core/Iterable.swift b/stdlib/public/core/Iterable.swift new file mode 100644 index 0000000000000..0b020293dc3f8 --- /dev/null +++ b/stdlib/public/core/Iterable.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.3, *) +@frozen +public enum EstimatedCount { + case infinite + case exactly(Int) + case unknown +} + +@available(SwiftStdlib 6.3, *) +public protocol Iterable: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + associatedtype BorrowIterator: BorrowIteratorProtocol & ~Copyable & ~Escapable + + var isEmpty: Bool { get } + + var estimatedCount: EstimatedCount { get } + + @_lifetime(borrow self) + borrowing func startBorrowIteration() -> BorrowIterator + + func _customContainsEquatableElement( + _ element: borrowing Element + ) -> Bool? +} + +@available(SwiftStdlib 6.3, *) +extension Iterable where Self: ~Copyable & ~Escapable { + @_alwaysEmitIntoClient + @_transparent + public var underestimatedCount: Int { + switch estimatedCount { + case .infinite: + .max + case .exactly(let c): + c + case .unknown: + 0 + } + } + + @_alwaysEmitIntoClient + @_transparent + public func _customContainsEquatableElement(_ element: borrowing Element) -> Bool? { + nil + } +} From ff2e38733c1bd0016cf9f5d21bff86255deefb6e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 3 Nov 2025 14:26:37 -0800 Subject: [PATCH 2/2] Add InlineArray Iterable conformance --- stdlib/public/core/InlineArray.swift | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/stdlib/public/core/InlineArray.swift b/stdlib/public/core/InlineArray.swift index af4b93e337590..833d62ccd0240 100644 --- a/stdlib/public/core/InlineArray.swift +++ b/stdlib/public/core/InlineArray.swift @@ -30,7 +30,7 @@ /// let b: InlineArray<_, Int> = [1, 2, 3] /// let c: InlineArray<3, _> = [1, 2, 3] /// let d: InlineArray = [1, 2, 3] -/// +/// /// let e: [3 of Int] = [1, 2, 3] /// let f: [_ of Int] = [1, 2, 3] /// let g: [3 of _] = [1, 2, 3] @@ -606,3 +606,21 @@ extension InlineArray where Element: ~Copyable { } } } + +@available(SwiftStdlib 6.3, *) +extension InlineArray: Iterable where Element: ~Copyable { + public typealias BorrowIterator = Span.BorrowIterator + + @_alwaysEmitIntoClient + @_transparent + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_alwaysEmitIntoClient + @_lifetime(borrow self) + @_transparent + public func startBorrowIteration() -> Span { + span + } +}