From 5316942c3099a2423fcbffa792afb69f87fbeafe Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 29 May 2024 13:36:34 -0700 Subject: [PATCH 001/195] Add a dummy Future module This is a temporary staging area for prototyping potential future stdlib additions. (We do not expect to ship it in any tagged release of swift-collections.) --- Package.swift | 6 ++++++ Sources/CMakeLists.txt | 1 + Sources/Future/CMakeLists.txt | 20 ++++++++++++++++++++ Sources/Future/Dummy.swift | 3 +++ 4 files changed, 30 insertions(+) create mode 100644 Sources/Future/CMakeLists.txt create mode 100644 Sources/Future/Dummy.swift diff --git a/Package.swift b/Package.swift index 9abb4c8fe..5299d965b 100644 --- a/Package.swift +++ b/Package.swift @@ -206,6 +206,12 @@ let targets: [CustomTarget] = [ "UnsafeMutableBufferPointer+Extras.swift.gyb", ]), + .target( + kind: .exported, + name: "Future", + dependencies: ["_CollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( kind: .exported, name: "BitCollections", diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 2ab9a5942..150d850b2 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -7,6 +7,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] +add_subdirectory(Future) add_subdirectory(BitCollections) add_subdirectory(Collections) add_subdirectory(DequeModule) diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt new file mode 100644 index 000000000..015dc671c --- /dev/null +++ b/Sources/Future/CMakeLists.txt @@ -0,0 +1,20 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2024 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 +#]] + +add_library(Future + "Dummy.swift" +) + +target_link_libraries(Future PRIVATE + _CollectionsUtilities) +set_target_properties(Future PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +_install_target(Future) +set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS Future) diff --git a/Sources/Future/Dummy.swift b/Sources/Future/Dummy.swift new file mode 100644 index 000000000..ec94910d1 --- /dev/null +++ b/Sources/Future/Dummy.swift @@ -0,0 +1,3 @@ +public func greet() { + print("Hello from the future!") +} From 908dafa21b3aed5c54a1194afefcd8e7554a9bba Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 12 Jun 2024 13:52:12 -0700 Subject: [PATCH 002/195] Introduce the Inout type --- Package.swift | 13 +++++-- Sources/Future/Inout.swift | 67 +++++++++++++++++++++++++++++++++++ Tests/FutureTests/Inout.swift | 27 ++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 Sources/Future/Inout.swift create mode 100644 Tests/FutureTests/Inout.swift diff --git a/Package.swift b/Package.swift index 5299d965b..b2c8814a8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:6.0 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project @@ -52,7 +52,12 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -var _settings: [SwiftSetting] = defines.map { .define($0) } +nonisolated(unsafe) +var _settings: [SwiftSetting] = defines.map { .define($0) } + [ + .enableExperimentalFeature("BuiltinModule"), + .enableExperimentalFeature("NonescapableTypes"), + .swiftLanguageVersion(.v5) +] struct CustomTarget { enum Kind { @@ -211,6 +216,10 @@ let targets: [CustomTarget] = [ name: "Future", dependencies: ["_CollectionsUtilities"], exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "FutureTests", + dependencies: ["Future"]), .target( kind: .exported, diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift new file mode 100644 index 000000000..344a7468e --- /dev/null +++ b/Sources/Future/Inout.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +// FIXME: A better name for the generic argument. + +/// A safe mutable reference allowing in-place mutation to an exclusive value. +/// +/// In order to get an instance of an `Inout`, one must have exclusive access +/// to the instance of `T`. This is achieved through the 'inout' operator, '&'. +@frozen +public struct Inout: ~Copyable, ~Escapable { + @usableFromInline + let pointer: UnsafeMutablePointer + + /// Initializes an instance of 'Inout' extending the exclusive access of the + /// passed instance. + /// + /// - Parameter instance: The desired instance to get a mutable reference to. + @_alwaysEmitIntoClient + @_transparent + public init(_ instance: inout T) { + pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + } + + /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' + /// as the mutable reference based on the lifetime of the given 'owner' + /// argument. + /// + /// - Parameter unsafeAddress: The address to use to mutably reference an + /// instance of type 'T'. + /// - Parameter owner: The owning instance that this 'Inout' instance's + /// lifetime is based on. + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafeMutablePointer, + owner: inout Owner + ) { + pointer = unsafeAddress + } +} + +extension Inout where T: ~Copyable { + /// Dereferences the mutable reference allowing for in-place reads and writes + /// to the underlying instance. + public subscript() -> T { + @_transparent + unsafeAddress { + UnsafePointer(pointer) + } + + @_transparent + nonmutating unsafeMutableAddress { + pointer + } + } +} diff --git a/Tests/FutureTests/Inout.swift b/Tests/FutureTests/Inout.swift new file mode 100644 index 000000000..8eaaa7aec --- /dev/null +++ b/Tests/FutureTests/Inout.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class FutureInoutTests: XCTestCase { + func test_basic() { + var x = 0 + let y = Inout(&x) + + XCTAssertEqual(y[], 0) + + y[] += 10 + + XCTAssertEqual(y[], 10) + XCTAssertEqual(x, 10) + } +} From aecceb8d2e2643f3bd1a524d23533482af706055 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 12 Jun 2024 14:36:02 -0700 Subject: [PATCH 003/195] Update Package.swift --- Package.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index b2c8814a8..9a6bad95f 100644 --- a/Package.swift +++ b/Package.swift @@ -52,8 +52,7 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -nonisolated(unsafe) -var _settings: [SwiftSetting] = defines.map { .define($0) } + [ +let _settings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), .swiftLanguageVersion(.v5) From 7b0599c5293354e05eda7d352c7d43ab687c73be Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 13 Jun 2024 18:40:52 -0700 Subject: [PATCH 004/195] fix autoclosure capture issue --- Tests/FutureTests/Inout.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/FutureTests/Inout.swift b/Tests/FutureTests/Inout.swift index 8eaaa7aec..8ef1a1001 100644 --- a/Tests/FutureTests/Inout.swift +++ b/Tests/FutureTests/Inout.swift @@ -17,11 +17,13 @@ final class FutureInoutTests: XCTestCase { var x = 0 let y = Inout(&x) - XCTAssertEqual(y[], 0) + var v = y[] + XCTAssertEqual(v, 0) y[] += 10 - XCTAssertEqual(y[], 10) + v = y[] + XCTAssertEqual(v, 10) XCTAssertEqual(x, 10) } } From 00b3b5f2e30b3d67d51809009ade90354bf09e12 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 13 Jun 2024 10:54:13 -0700 Subject: [PATCH 005/195] Remove a dummy file --- Sources/Future/Dummy.swift | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Sources/Future/Dummy.swift diff --git a/Sources/Future/Dummy.swift b/Sources/Future/Dummy.swift deleted file mode 100644 index ec94910d1..000000000 --- a/Sources/Future/Dummy.swift +++ /dev/null @@ -1,3 +0,0 @@ -public func greet() { - print("Hello from the future!") -} From d3dbe2b292b9ce29cee3c0f6b2b2b6ab28a313fa Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 14 Jun 2024 11:22:46 -0700 Subject: [PATCH 006/195] Update Package.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools-version 6.0 requires Xcode 16 beta, which doesn’t correctly handle the nightly post-6.0 toolchains required to compile this branch. --- Package.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 9a6bad95f..7d73dd742 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.0 +// swift-tools-version:5.10 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project @@ -55,7 +55,8 @@ var defines: [String] = [ let _settings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), - .swiftLanguageVersion(.v5) + .enableExperimentalFeature("BitwiseCopyable"), +// .swiftLanguageVersion(.v5) ] struct CustomTarget { From e94873a99374424e2d105107f23da5f6686e5e7c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 13 Jun 2024 10:25:22 -0700 Subject: [PATCH 007/195] add Span and RawSpan, with tests --- Sources/Future/ContiguousStorage.swift | 114 +++++ Sources/Future/RawSpan.swift | 291 +++++++++++++ Sources/Future/Span.swift | 557 +++++++++++++++++++++++++ Tests/FutureTests/RawSpanTests.swift | 225 ++++++++++ Tests/FutureTests/SpanTests.swift | 340 +++++++++++++++ 5 files changed, 1527 insertions(+) create mode 100644 Sources/Future/ContiguousStorage.swift create mode 100644 Sources/Future/RawSpan.swift create mode 100644 Sources/Future/Span.swift create mode 100644 Tests/FutureTests/RawSpanTests.swift create mode 100644 Tests/FutureTests/SpanTests.swift diff --git a/Sources/Future/ContiguousStorage.swift b/Sources/Future/ContiguousStorage.swift new file mode 100644 index 000000000..b9ffc0b60 --- /dev/null +++ b/Sources/Future/ContiguousStorage.swift @@ -0,0 +1,114 @@ +//===--- ContiguousStorage.swift ------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +public protocol ContiguousStorage: ~Copyable, ~Escapable { + associatedtype StoredElement/*: ~Copyable & ~Escapable*/ + + var storage: Span { borrowing get } +} + +extension Span: ContiguousStorage /*where Element: ~Copyable & ~Escapable*/ { + public typealias StoredElement = Element + public var storage: Self { self } +} + +extension Array: ContiguousStorage { + public typealias StoredElement = Element + public var storage: Span { + _read { + if let a = _baseAddressIfContiguous { + yield Span( + unsafePointer: a, count: count, owner: self + ) + } + else { + let a = ContiguousArray(copy self) + #if true + let s = Span( + unsafePointer: a._baseAddressIfContiguous!, count: a.count, owner: a + ) + #else + let s = a.storage + #endif + yield s + } + } + } +} + +extension ContiguousArray: ContiguousStorage { + public typealias StoredElement = Element + public var storage: Span { + borrowing get { + Span( + unsafePointer: _baseAddressIfContiguous!, count: count, owner: self + ) + } + } +} + +extension CollectionOfOne: ContiguousStorage { + public typealias StoredElement = Element + public var storage: Span { + _read { +/* ideally: (with strawman syntax) + @addressable let value = self._element + yield Span( + unsafePointer: Builtin.addressable(value), count: 1, owner: self + ) +*/ + + let a = ContiguousArray(self) + yield Span( + unsafePointer: a._baseAddressIfContiguous!, count: 1, owner: a + ) + } + } +} + +extension String: ContiguousStorage { + public typealias StoredElement = UTF8.CodeUnit + public var storage: Span { + _read { + if utf8.count < 16 { // Wrong way to know whether the String is smol +// if _guts.isSmall { +// let /*@addressable*/ rawStorage = _guts.asSmall._storage +// let span = RawSpan( +// unsafeRawPointer: UnsafeRawPointer(Builtin.adressable(rawStorage)), +// count: MemoryLayout<_SmallString.RawBitPattern>.size, +// owner: self +// ) +// yield span.view(as: UTF8.CodeUnit.self) + + let a = ContiguousArray(utf8) +// yield a.storage + yield Span( + unsafePointer: a._baseAddressIfContiguous!, count: 1, owner: a + ) + } + else if let buffer = utf8.withContiguousStorageIfAvailable({ $0 }) { + // this is totally wrong, but there is a way with stdlib-internal API + yield Span( + unsafeBufferPointer: buffer, + owner: self + ) + } + else { // copy non-fast code units if we don't have eager bridging + let a = ContiguousArray(utf8) +// yield a.storage + yield Span( + unsafePointer: a._baseAddressIfContiguous!, count: 1, owner: a + ) + } + } + } +} diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift new file mode 100644 index 000000000..0de7bcbde --- /dev/null +++ b/Sources/Future/RawSpan.swift @@ -0,0 +1,291 @@ +//===--- RawSpan.swift ----------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@frozen +public struct RawSpan: Copyable, ~Escapable { + @usableFromInline let _start: UnsafeRawPointer + @usableFromInline let _count: Int + + @inlinable @inline(__always) + internal init( + _unchecked start: UnsafeRawPointer, + count: Int, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + self._start = start + self._count = count + } +} + +@available(*, unavailable) +extension RawSpan: Sendable {} + +extension RawSpan { + + //FIXME: make failable once Optional can be non-escapable + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `RawSpan`. + @inlinable @inline(__always) + public init( + unsafeBytes buffer: UnsafeRawBufferPointer, + owner: borrowing Owner + ) { + guard let baseAddress = buffer.baseAddress else { + fatalError("RawSpan requires a non-nil base address") + } + self.init(_unchecked: baseAddress, count: buffer.count, owner: owner) + } + + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory over `count` bytes starting at + /// `pointer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized element. + /// - count: the number of initialized elements in the view. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + @inlinable @inline(__always) + public init( + unsafeRawPointer pointer: UnsafeRawPointer, + count: Int, + owner: borrowing Owner + ) { + precondition(count >= 0, "Count must not be negative") + self.init(_unchecked: pointer, count: count, owner: owner) + } + + /// Create a `RawSpan` over the memory represented by a `Span` + /// + /// - Parameters: + /// - span: An existing `Span`, which will define both this + /// `RawSpan`'s lifetime and the memory it represents. + @inlinable @inline(__always) + public init( + _ span: borrowing Span + ) { + self.init( + _unchecked: UnsafeRawPointer(span._start), + count: span.count * MemoryLayout.stride, + owner: span + ) + } +} + +//MARK: Bounds Checking +extension RawSpan { + /// Traps if `offset` is not a valid offset into this `RawSpan` + /// + /// - Parameters: + /// - position: an offset to validate + @inlinable @inline(__always) + public func boundsCheckPrecondition(_ offset: Int) { + precondition( + 0 <= offset && offset < count, + "Offset out of bounds" + ) + } + + /// Traps if `bounds` is not a valid range of offsets into this `RawSpan` + /// + /// - Parameters: + /// - offsets: a range of offsets to validate + @inlinable @inline(__always) + public func boundsCheckPrecondition(_ offsets: Range) { + precondition( + 0 <= offsets.lowerBound && offsets.upperBound <= count, + "Range of offsets out of bounds" + ) + } +} + +//MARK: Offset Manipulation +extension RawSpan { + + @inlinable @inline(__always) + public var count: Int { + borrowing get { self._count } + } + + @inlinable @inline(__always) + public var isEmpty: Bool { count == 0 } + + @inlinable @inline(__always) + public var offsets: Range { + .init(uncheckedBounds: (0, count)) + } +} + +//MARK: integer offset subscripts +extension RawSpan { + + @inlinable @inline(__always) + public subscript(offsets offsets: Range) -> Self { + borrowing get { + boundsCheckPrecondition(offsets) + return self[uncheckedOffsets: offsets] + } + } + + @inlinable @inline(__always) + public subscript(uncheckedOffsets offsets: Range) -> Self { + borrowing get { + RawSpan( + _unchecked: _start.advanced(by: offsets.lowerBound), + count: offsets.count, + owner: self + ) + } + } + + @_alwaysEmitIntoClient + public subscript(offsets offsets: some RangeExpression) -> Self { + borrowing get { + self[offsets: offsets.relative(to: 0..) -> Self { + borrowing get { + self[uncheckedOffsets: offsets.relative(to: 0.. Self { + borrowing get { copy self } + } +} + +//MARK: withUnsafeBytes +extension RawSpan { + + //FIXME: mark closure parameter as non-escaping + @_alwaysEmitIntoClient + borrowing public func withUnsafeBytes< + E: Error, Result: ~Copyable & ~Escapable + >( + _ body: (_ buffer: borrowing UnsafeRawBufferPointer) throws(E) -> Result + ) throws(E) -> dependsOn(self) Result { + try body(.init(start: (count==0) ? nil : _start, count: count)) + } + + borrowing public func view( + as: T.Type + ) -> dependsOn(self) Span { + let (c, r) = count.quotientAndRemainder(dividingBy: MemoryLayout.stride) + precondition(r == 0, "Returned span must contain whole number of T") + return Span( + unsafeRawPointer: _start, as: T.self, count: c, owner: self + ) + } +} + +//MARK: load + +extension RawSpan { + + public func load( + fromByteOffset offset: Int = 0, as: T.Type + ) -> T { + boundsCheckPrecondition( + Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) + ) + return load(fromUncheckedByteOffset: offset, as: T.self) + } + + public func load( + fromUncheckedByteOffset offset: Int, as: T.Type + ) -> T { + _start.load(fromByteOffset: offset, as: T.self) + } + + public func loadUnaligned( + fromByteOffset offset: Int = 0, as: T.Type + ) -> T { + boundsCheckPrecondition( + Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) + ) + return loadUnaligned(fromUncheckedByteOffset: offset, as: T.self) + } + + public func loadUnaligned( + fromUncheckedByteOffset offset: Int, as: T.Type + ) -> T { + _start.loadUnaligned(fromByteOffset: offset, as: T.self) + } +} + +//MARK: one-sided slicing operations +extension RawSpan { + + borrowing public func prefix(upTo offset: Int) -> dependsOn(self) Self { + if offset != 0 { + boundsCheckPrecondition(offset &- 1) + } + return Self(_unchecked: _start, count: offset, owner: self) + } + + borrowing public func prefix(through offset: Int) -> dependsOn(self) Self { + boundsCheckPrecondition(offset) + return Self(_unchecked: _start, count: offset &+ 1, owner: self) + } + + borrowing public func prefix(_ maxLength: Int) -> dependsOn(self) Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length.") + let nc = maxLength < count ? maxLength : count + return Self(_unchecked: _start, count: nc, owner: self) + } + + borrowing public func dropLast(_ k: Int = 1) -> dependsOn(self) Self { + precondition(k >= 0, "Can't drop a negative number of elements.") + let nc = k < count ? count&-k : 0 + return Self(_unchecked: _start, count: nc, owner: self) + } + + borrowing public func suffix(from offset: Int) -> dependsOn(self) Self { + if offset != count { + boundsCheckPrecondition(offset) + } + return Self( + _unchecked: _start.advanced(by: offset), + count: count &- offset, + owner: self + ) + } + + borrowing public func suffix(_ maxLength: Int) -> dependsOn(self) Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length.") + let nc = maxLength < count ? maxLength : count + let newStart = _start.advanced(by: count&-nc) + return Self(_unchecked: newStart, count: nc, owner: self) + } + + borrowing public func dropFirst(_ k: Int = 1) -> dependsOn(self) Self { + precondition(k >= 0, "Can't drop a negative number of elements.") + let dc = k < count ? k : count + let newStart = _start.advanced(by: dc) + return Self(_unchecked: newStart, count: count&-dc, owner: self) + } +} diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift new file mode 100644 index 000000000..eb9ce84cf --- /dev/null +++ b/Sources/Future/Span.swift @@ -0,0 +1,557 @@ +//===--- Span.swift -------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +// A Span represents a span of memory which +// contains initialized instances of `Element`. +@frozen +public struct Span: Copyable, ~Escapable { + @usableFromInline let _start: UnsafePointer + @usableFromInline let _count: Int + + @inlinable @inline(__always) + internal init( + _unchecked start: UnsafePointer, + count: Int, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + self._start = start + self._count = count + } +} + +@available(*, unavailable) +extension Span: Sendable {} + +extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { + + @inline(__always) + fileprivate var isAligned: Bool { + (Int(bitPattern: self) & (MemoryLayout.alignment-1)) == 0 + } +} + +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory representing `count` instances starting at + /// `pointer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized element. + /// - count: the number of initialized elements in the view. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafePointer start: UnsafePointer, + count: Int, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + precondition(count >= 0, "Count must not be negative") + precondition( + start.isAligned, + "baseAddress must be properly aligned for accessing \(Element.self)" + ) + self.init(_unchecked: start, count: count, owner: owner) + } + + //FIXME: make failable once Optional can be non-escapable + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafeBufferPointer buffer: UnsafeBufferPointer, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + guard let baseAddress = buffer.baseAddress else { + fatalError("Span requires a non-nil base address") + } + self.init(unsafePointer: baseAddress, count: buffer.count, owner: owner) + } +} + +extension Span where Element: BitwiseCopyable { + + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory representing `count` instances starting at + /// `pointer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized element. + /// - count: the number of initialized elements in the view. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafePointer start: UnsafePointer, + count: Int, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + precondition(count >= 0, "Count must not be negative") + self.init(_unchecked: start, count: count, owner: owner) + } + + //FIXME: make failable once Optional can be non-escapable + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafeBufferPointer buffer: UnsafeBufferPointer, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + guard let baseAddress = buffer.baseAddress else { + fatalError("Span requires a non-nil base address") + } + self.init(unsafePointer: baseAddress, count: buffer.count, owner: owner) + } + + //FIXME: make failable once Optional can be non-escapable + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `unsafeBytes` must be owned by the instance `owner` + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// `unsafeBytes` must be correctly aligned for accessing + /// an element of type `Element`, and must contain a number of bytes + /// that is an exact multiple of `Element`'s stride. + /// + /// - Parameters: + /// - unsafeBytes: a buffer to initialized elements. + /// - type: the type to use when interpreting the bytes in memory. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafeBytes buffer: UnsafeRawBufferPointer, + as type: Element.Type, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + guard let baseAddress = buffer.baseAddress else { + fatalError("Span requires a non-nil base address") + } + let (c, s) = (buffer.count, MemoryLayout.stride) + let (q, r) = c.quotientAndRemainder(dividingBy: s) + precondition(r == 0) + self.init( + unsafeRawPointer: baseAddress, as: Element.self, count: q, owner: owner + ) + } + + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory representing `count` instances starting at + /// `pointer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized element. + /// - count: the number of initialized elements in the view. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafeRawPointer pointer: UnsafeRawPointer, + as type: Element.Type, + count: Int, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + self.init( + unsafePointer: pointer.assumingMemoryBound(to: Element.self), + count: count, + owner: owner + ) + } +} + +extension Span where Element: Equatable { + + public func elementsEqual(_ other: Self) -> Bool { + guard count == other.count else { return false } + if count == 0 { return true } + + //FIXME: This could be short-cut + // with a layout constraint where stride equals size, + // as long as there is at most 1 unused bit pattern. + // if Element is BitwiseEquatable { + // return _swift_stdlib_memcmp(lhs.baseAddress, rhs.baseAddress, count) == 0 + // } + for o in 0..) -> Bool { + guard count == other.count else { return false } + if count == 0 { return true } + + return elementsEqual(AnySequence(other)) + } + + @inlinable + public func elementsEqual(_ other: some Sequence) -> Bool { + var offset = 0 + for otherElement in other { + if offset >= count { return false } + if self[unchecked: offset] != otherElement { return false } + offset += 1 + } + return offset == count + } +} + +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + @inlinable @inline(__always) + public var count: Int { _count } + + @inlinable @inline(__always) + public var isEmpty: Bool { _count == 0 } + + @inlinable @inline(__always) + public var indices: Range { + .init(uncheckedBounds: (0, _count)) + } +} + +//MARK: Bounds Checking +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + /// Traps if `offset` is not a valid offset into this `Span` + /// + /// - Parameters: + /// - position: an Index to validate + @inlinable @inline(__always) + public func boundsCheckPrecondition(_ offset: Int) { + precondition( + 0 <= offset && offset < count, + "Offset out of bounds" + ) + } + + /// Traps if `offsets` is not a valid range of offsets into this `Span` + /// + /// - Parameters: + /// - offsets: a range of indices to validate + @inlinable @inline(__always) + public func boundsCheckPrecondition(_ offsets: Range) { + precondition( + 0 <= offsets.lowerBound && offsets.upperBound <= count, + "Range of offsets out of bounds" + ) + } +} + +extension Span where Element: BitwiseCopyable { + + @inlinable @inline(__always) + public var rawSpan: RawSpan { RawSpan(self) } +} + +//MARK: integer offset subscripts +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + /// Accesses the element at the specified position in the `Span`. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public subscript(_ position: Int) -> Element { + borrowing _read { + boundsCheckPrecondition(position) + yield self[unchecked: position] + } + } + + /// Accesses the element at the specified position in the `Span`. + /// + /// This subscript does not validate `position`; this is an unsafe operation. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public subscript(unchecked position: Int) -> Element { + borrowing _read { + yield _start.advanced(by: position).pointee + } + } +} + +extension Span where Element: BitwiseCopyable { + + /// Accesses the element at the specified position in the `Span`. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public subscript(_ position: Int) -> Element { + get { + boundsCheckPrecondition(position) + return self[unchecked: position] + } + } + + /// Accesses the element at the specified position in the `Span`. + /// + /// This subscript does not validate `position`; this is an unsafe operation. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public subscript(unchecked position: Int) -> Element { + get { + UnsafeRawPointer(_start + position).loadUnaligned(as: Element.self) + } + } +} + +//MARK: extracting sub-spans +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `Span`. + /// + /// - Returns: A `Span` over the items at `bounds` + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public func extracting(_ bounds: Range) -> Self { + boundsCheckPrecondition(bounds) + return extracting(uncheckedBounds: bounds) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `Span`. + /// + /// - Returns: A `Span` over the items at `bounds` + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public func extracting(uncheckedBounds bounds: Range) -> Self { + Span( + _unchecked: _start.advanced(by: bounds.lowerBound), + count: bounds.count, + owner: self + ) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `Span`. + /// + /// - Returns: A `Span` over the items at `bounds` + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public func extracting(_ bounds: some RangeExpression) -> Self { + extracting(bounds.relative(to: indices)) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `Span`. + /// + /// - Returns: A `Span` over the items at `bounds` + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public func extracting( + uncheckedBounds bounds: some RangeExpression + ) -> Self { + extracting(uncheckedBounds: bounds.relative(to: indices)) + } + + /// Constructs a new span over all the items of this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// - Returns: A `Span` over all the items of this span. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public func extracting(_: UnboundedRange) -> Self { + self + } +} + +//MARK: withUnsafePointer, etc. +extension Span where Element: ~Copyable { + + //FIXME: mark closure parameter as non-escaping + /// Calls a closure with a pointer to the viewed contiguous storage. + /// + /// The buffer pointer passed as an argument to `body` is valid only + /// during the execution of `withUnsafeBufferPointer(_:)`. + /// Do not store or return the pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter + /// that points to the viewed contiguous storage. If `body` has + /// a return value, that value is also used as the return value + /// for the `withUnsafeBufferPointer(_:)` method. The closure's + /// parameter is valid only for the duration of its execution. + /// - Returns: The return value of the `body` closure parameter. + borrowing public func withUnsafeBufferPointer< + E: Error, Result: ~Copyable & ~Escapable + >( + _ body: (_ buffer: borrowing UnsafeBufferPointer) throws(E) -> Result + ) throws(E) -> dependsOn(self) Result { + try body(.init(start: (count==0) ? nil : _start, count: count)) + } +} + +extension Span where Element: BitwiseCopyable { + + //FIXME: mark closure parameter as non-escaping + /// Calls the given closure with a pointer to the underlying bytes of + /// the viewed contiguous storage. + /// + /// The buffer pointer passed as an argument to `body` is valid only + /// during the execution of `withUnsafeBytes(_:)`. + /// Do not store or return the pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeRawBufferPointer` + /// parameter that points to the viewed contiguous storage. + /// If `body` has a return value, that value is also + /// used as the return value for the `withUnsafeBytes(_:)` method. + /// The closure's parameter is valid only for the duration of + /// its execution. + /// - Returns: The return value of the `body` closure parameter. + borrowing public func withUnsafeBytes< + E: Error, Result: ~Copyable & ~Escapable + >( + _ body: (_ buffer: borrowing UnsafeRawBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + try RawSpan(self).withUnsafeBytes(body) + } +} + +// `first` and `last` can't exist where Element: ~Copyable +// because we can't construct an Optional of a borrow +extension Span where Element: Copyable { + @inlinable + public var first: Element? { + isEmpty ? nil : self[unchecked: 0] + } + + @inlinable + public var last: Element? { + isEmpty ? nil : self[unchecked: count &- 1] + } +} + +//MARK: one-sided slicing operations +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + borrowing public func prefix(upTo offset: Int) -> dependsOn(self) Self { + if offset != 0 { + boundsCheckPrecondition(offset &- 1) + } + return Self(_unchecked: _start, count: offset, owner: self) + } + + borrowing public func prefix(through offset: Int) -> dependsOn(self) Self { + boundsCheckPrecondition(offset) + return Self(_unchecked: _start, count: offset &+ 1, owner: self) + } + + borrowing public func prefix(_ maxLength: Int) -> dependsOn(self) Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length.") + let nc = maxLength < count ? maxLength : count + return Self(_unchecked: _start, count: nc, owner: self) + } + + borrowing public func dropLast(_ k: Int = 1) -> dependsOn(self) Self { + precondition(k >= 0, "Can't drop a negative number of elements.") + let nc = k < count ? count&-k : 0 + return Self(_unchecked: _start, count: nc, owner: self) + } + + borrowing public func suffix(from offset: Int) -> dependsOn(self) Self { + if offset != count { + boundsCheckPrecondition(offset) + } + return Self( + _unchecked: _start.advanced(by: offset), + count: count &- offset, + owner: self + ) + } + + borrowing public func suffix(_ maxLength: Int) -> dependsOn(self) Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length.") + let nc = maxLength < count ? maxLength : count + let newStart = _start.advanced(by: count&-nc) + return Self(_unchecked: newStart, count: nc, owner: self) + } + + borrowing public func dropFirst(_ k: Int = 1) -> dependsOn(self) Self { + precondition(k >= 0, "Can't drop a negative number of elements.") + let dc = k < count ? k : count + let newStart = _start.advanced(by: dc) + return Self(_unchecked: newStart, count: count&-dc, owner: self) + } +} diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift new file mode 100644 index 000000000..2e931a000 --- /dev/null +++ b/Tests/FutureTests/RawSpanTests.swift @@ -0,0 +1,225 @@ +//===--- RawSpanTests.swift -----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class RawSpanTests: XCTestCase { + + func testOptionalStorage() { +// XCTAssertEqual( +// MemoryLayout.size, MemoryLayout.size +// ) +// XCTAssertEqual( +// MemoryLayout.stride, MemoryLayout.stride +// ) +// XCTAssertEqual( +// MemoryLayout.alignment, MemoryLayout.alignment +// ) + } + + func testInitWithSpanOfIntegers() { + let capacity = 4 + let a = Array(0...stride) + XCTAssertFalse(span.isEmpty) + } + + func testInitWithEmptySpanOfIntegers() { + let a: [Int] = [] + let span = RawSpan(a.storage) + XCTAssertTrue(span.isEmpty) + } + + func testInitWithRawBytes() { + let capacity = 4 + let a = Array(0...stride) + } + } + + func testWithRawPointer() { + let capacity = 4 + let a = Array(0...stride, owner: a + ) + XCTAssertEqual(span.count, capacity*MemoryLayout.stride) + } + } + + func testLoad() { + let capacity = 4 + let s = (0...stride + + let s0 = span.load(as: String.self) + XCTAssertEqual(s0.contains("0"), true) + let s1 = span.load(fromByteOffset: stride, as: String.self) + XCTAssertEqual(s1.contains("1"), true) + let s2 = span.load(fromUncheckedByteOffset: 2*stride, as: String.self) + XCTAssertEqual(s2.contains("2"), true) + } + } + + func testLoadUnaligned() { + let capacity = 64 + let a = Array(0..>.size, MemoryLayout?>.size +// ) +// XCTAssertEqual( +// MemoryLayout>.stride, MemoryLayout?>.stride +// ) +// XCTAssertEqual( +// MemoryLayout>.alignment, MemoryLayout?>.alignment +// ) + } + + func testInitWithOrdinaryElement() { + let capacity = 4 + let s = (0..(unsafeBytes: $0, as: UInt.self, owner: $0) + XCTAssertEqual(b.count, capacity) + + let r = Span(unsafeBytes: $0, as: Int8.self, owner: $0) + XCTAssertEqual(r.count, capacity*MemoryLayout.stride) + } + } + + func testIsEmpty() { + let capacity = 4 + let a = Array(0...stride) + } + } + + func testSpanIndices() { + let capacity = 4 + let a = Array(0..(unsafeUninitializedCapacity: capacity) { + for i in $0.indices { + $0.initializeElement(at: i, to: .random(in: 0..<10)) + } + $1 = $0.count + } + a.withUnsafeBufferPointer { + let span = Span(unsafeBufferPointer: $0, owner: $0) + + XCTAssertEqual(span.elementsEqual(span.prefix(1)), false) + XCTAssertEqual(span.prefix(0).elementsEqual(span.suffix(0)), true) + XCTAssertEqual(span.elementsEqual(span), true) + XCTAssertEqual(span.prefix(3).elementsEqual(span.suffix(3)), false) + + let copy = span.withUnsafeBufferPointer(Array.init) + copy.withUnsafeBufferPointer { + let spanOfCopy = Span(unsafeBufferPointer: $0, owner: $0) + XCTAssertTrue(span.elementsEqual(spanOfCopy)) + } + } + } + + func testElementsEqualCollection() { + let capacity = 4 + let a = Array(0..(unsafeBufferPointer: $0, owner: $0) + XCTAssertEqual(span.count, capacity) + XCTAssertEqual(span.suffix(capacity).first, 0) + XCTAssertEqual(span.suffix(capacity-1).first, 1) + XCTAssertEqual(span.suffix(1).first, capacity-1) + XCTAssertEqual(span.dropFirst(capacity).first, nil) + XCTAssertEqual(span.dropFirst(1).first, 1) + + XCTAssertEqual(span.suffix(from: 0).elementsEqual(a), true) + XCTAssertEqual(span.suffix(from: span.count).isEmpty, true) + } + } + + public func testWithUnsafeBytes() { + let capacity: UInt8 = 64 + let a = Array(0.. + + var startIndex: Int { 0 } + var endIndex: Int { span.count } + func index(after i: Int) -> Int { i+2 } + + init(_ contiguous: borrowing Span) { + span = copy contiguous + } + + subscript(_ p: Int) -> Int { span[p] } + } + + let skipper = Skipper(a.storage) + var i = skipper.startIndex + var s: [Int] = [] + while i < skipper.endIndex { + s.append(skipper[i]) + i = skipper.index(after: i) + } + XCTAssertEqual(s, [0, 2, 4, 6]) + } +} From fc451765e999a3398c129186106d22f245166156 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 14 Jun 2024 12:44:12 -0700 Subject: [PATCH 008/195] rename a property of RawSpan --- Sources/Future/RawSpan.swift | 6 +++--- Tests/FutureTests/RawSpanTests.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 0de7bcbde..217cc49ac 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -131,7 +131,7 @@ extension RawSpan { public var isEmpty: Bool { count == 0 } @inlinable @inline(__always) - public var offsets: Range { + public var indices: Range { .init(uncheckedBounds: (0, count)) } } @@ -161,14 +161,14 @@ extension RawSpan { @_alwaysEmitIntoClient public subscript(offsets offsets: some RangeExpression) -> Self { borrowing get { - self[offsets: offsets.relative(to: 0..) -> Self { borrowing get { - self[uncheckedOffsets: offsets.relative(to: 0.. Date: Fri, 14 Jun 2024 12:44:32 -0700 Subject: [PATCH 009/195] add more doc-comments for `Span` API --- Sources/Future/Span.swift | 129 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index eb9ce84cf..6ec501bae 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -188,6 +188,16 @@ extension Span where Element: BitwiseCopyable { extension Span where Element: Equatable { + /// Returns a Boolean value indicating whether this and another span + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A span to compare to this one. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. public func elementsEqual(_ other: Self) -> Bool { guard count == other.count else { return false } if count == 0 { return true } @@ -204,6 +214,16 @@ extension Span where Element: Equatable { return true } + /// Returns a Boolean value indicating whether this span and a Collection + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A Collection to compare to this span. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. @inlinable public func elementsEqual(_ other: some Collection) -> Bool { guard count == other.count else { return false } @@ -212,6 +232,16 @@ extension Span where Element: Equatable { return elementsEqual(AnySequence(other)) } + /// Returns a Boolean value indicating whether this span and a Sequence + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A Sequence to compare to this span. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. @inlinable public func elementsEqual(_ other: some Sequence) -> Bool { var offset = 0 @@ -226,12 +256,25 @@ extension Span where Element: Equatable { extension Span where Element: ~Copyable /*& ~Escapable*/ { + /// The number of elements in the span. + /// + /// To check whether the span is empty, use its `isEmpty` property + /// instead of comparing `count` to zero. + /// + /// - Complexity: O(1) @inlinable @inline(__always) public var count: Int { _count } + /// A Boolean value indicating whether the span is empty. + /// + /// - Complexity: O(1) @inlinable @inline(__always) public var isEmpty: Bool { _count == 0 } + /// The indices that are valid for subscripting the span, in ascending + /// order. + /// + /// - Complexity: O(1) @inlinable @inline(__always) public var indices: Range { .init(uncheckedBounds: (0, _count)) @@ -268,6 +311,9 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { extension Span where Element: BitwiseCopyable { + /// Construct a RawSpan over the memory represented by this span + /// + /// - Returns: a RawSpan over the memory represented by this span @inlinable @inline(__always) public var rawSpan: RawSpan { RawSpan(self) } } @@ -492,11 +538,22 @@ extension Span where Element: BitwiseCopyable { // `first` and `last` can't exist where Element: ~Copyable // because we can't construct an Optional of a borrow extension Span where Element: Copyable { + + /// The first element in the span. + /// + /// If the span is empty, the value of this property is `nil`. + /// + /// - Returns: The first element in the span, or `nil` if empty @inlinable public var first: Element? { isEmpty ? nil : self[unchecked: 0] } + /// The last element in the span. + /// + /// If the span is empty, the value of this property is `nil`. + /// + /// - Returns: The last element in the span, or `nil` if empty @inlinable public var last: Element? { isEmpty ? nil : self[unchecked: count &- 1] @@ -506,6 +563,16 @@ extension Span where Element: Copyable { //MARK: one-sided slicing operations extension Span where Element: ~Copyable /*& ~Escapable*/ { + /// Returns a span from positions zero up to, but not + /// including, the specified position. + /// + /// The resulting span *does not include* the element at the position + /// `end`. + /// + /// - Parameter end: The "past the end" index of the resulting span. + /// - Returns: A span up to, but not including, the `end` position. + /// + /// - Complexity: O(1) borrowing public func prefix(upTo offset: Int) -> dependsOn(self) Self { if offset != 0 { boundsCheckPrecondition(offset &- 1) @@ -513,23 +580,64 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { return Self(_unchecked: _start, count: offset, owner: self) } + /// Returns a span from positions zero through the specified position. + /// + /// The resulting span includes the element at the position + /// `end`. + /// + /// - Parameter position: The last index of the resulting span. + /// `position` must be a valid index of the collection. + /// - Returns: A span up to, and including, the given position. + /// + /// - Complexity: O(1) borrowing public func prefix(through offset: Int) -> dependsOn(self) Self { boundsCheckPrecondition(offset) return Self(_unchecked: _start, count: offset &+ 1, owner: self) } + /// Returns a span containing the initial elements of this span, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the elements. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` elements. + /// + /// - Complexity: O(1) borrowing public func prefix(_ maxLength: Int) -> dependsOn(self) Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < count ? maxLength : count return Self(_unchecked: _start, count: nc, owner: self) } + /// Returns a span over all but the given number of final elements. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// - Parameter k: The number of elements to drop off the end of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span leaving off the specified number of elements at the end. + /// + /// - Complexity: O(1) borrowing public func dropLast(_ k: Int = 1) -> dependsOn(self) Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < count ? count&-k : 0 return Self(_unchecked: _start, count: nc, owner: self) } + /// Returns a span from the specified position to the end of this span + /// + /// Passing the span's `count` as the `start` parameter results in + /// an empty subsequence. + /// + /// - Parameter start: The position at which to start the resulting span. + /// `start` must be a valid index of the span. + /// - Returns: A span starting at the `start` position. + /// + /// - Complexity: O(1) borrowing public func suffix(from offset: Int) -> dependsOn(self) Self { if offset != count { boundsCheckPrecondition(offset) @@ -541,6 +649,17 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { ) } + /// Returns a span containing the final elements of the span, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the elements. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` elements. + /// + /// - Complexity: O(1) borrowing public func suffix(_ maxLength: Int) -> dependsOn(self) Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < count ? maxLength : count @@ -548,6 +667,16 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { return Self(_unchecked: newStart, count: nc, owner: self) } + /// Returns a span over all but the given number of initial elements. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// - Parameter k: The number of elements to drop from the beginning of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span starting after the specified number of elements. + /// + /// - Complexity: O(1) borrowing public func dropFirst(_ k: Int = 1) -> dependsOn(self) Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < count ? k : count From 7f26f2705ceb71bcb3398cd66877625580d24181 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 14 Jun 2024 12:47:25 -0700 Subject: [PATCH 010/195] Rename elementsEqual with an underscore --- Sources/Future/Span.swift | 8 ++--- Tests/FutureTests/RawSpanTests.swift | 6 ++-- Tests/FutureTests/SpanTests.swift | 50 ++++++++++++++-------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 6ec501bae..a7f6ea9d9 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -198,7 +198,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - public func elementsEqual(_ other: Self) -> Bool { + public func _elementsEqual(_ other: Self) -> Bool { guard count == other.count else { return false } if count == 0 { return true } @@ -225,11 +225,11 @@ extension Span where Element: Equatable { /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. @inlinable - public func elementsEqual(_ other: some Collection) -> Bool { + public func _elementsEqual(_ other: some Collection) -> Bool { guard count == other.count else { return false } if count == 0 { return true } - return elementsEqual(AnySequence(other)) + return _elementsEqual(AnySequence(other)) } /// Returns a Boolean value indicating whether this span and a Sequence @@ -243,7 +243,7 @@ extension Span where Element: Equatable { /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. @inlinable - public func elementsEqual(_ other: some Sequence) -> Bool { + public func _elementsEqual(_ other: some Sequence) -> Bool { var offset = 0 for otherElement in other { if offset >= count { return false } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 0dce26185..7b96a242e 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -104,13 +104,13 @@ final class RawSpanTests: XCTestCase { let sub3 = span[offsets: ...] let sub4 = span[uncheckedOffsets: 2...] XCTAssertTrue( - sub1.view(as: UInt8.self).elementsEqual(sub2.view(as: UInt8.self)) + sub1.view(as: UInt8.self)._elementsEqual(sub2.view(as: UInt8.self)) ) XCTAssertTrue( - sub3.view(as: Int8.self).elementsEqual(span.view(as: Int8.self)) + sub3.view(as: Int8.self)._elementsEqual(span.view(as: Int8.self)) ) XCTAssertFalse( - sub4.view(as: Int8.self).elementsEqual(sub3.view(as: Int8.self)) + sub4.view(as: Int8.self)._elementsEqual(sub3.view(as: Int8.self)) ) } } diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 2dd1af2ae..5274f780e 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -103,15 +103,15 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(unsafeBufferPointer: $0, owner: $0) - XCTAssertEqual(span.elementsEqual(span.prefix(1)), false) - XCTAssertEqual(span.prefix(0).elementsEqual(span.suffix(0)), true) - XCTAssertEqual(span.elementsEqual(span), true) - XCTAssertEqual(span.prefix(3).elementsEqual(span.suffix(3)), false) + XCTAssertEqual(span._elementsEqual(span.prefix(1)), false) + XCTAssertEqual(span.prefix(0)._elementsEqual(span.suffix(0)), true) + XCTAssertEqual(span._elementsEqual(span), true) + XCTAssertEqual(span.prefix(3)._elementsEqual(span.suffix(3)), false) let copy = span.withUnsafeBufferPointer(Array.init) copy.withUnsafeBufferPointer { let spanOfCopy = Span(unsafeBufferPointer: $0, owner: $0) - XCTAssertTrue(span.elementsEqual(spanOfCopy)) + XCTAssertTrue(span._elementsEqual(spanOfCopy)) } } } @@ -122,9 +122,9 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(unsafeBufferPointer: $0, owner: $0) - XCTAssertEqual(span.elementsEqual(a), true) - XCTAssertEqual(span.prefix(0).elementsEqual([]), true) - XCTAssertEqual(span.elementsEqual(a.dropLast()), false) + XCTAssertEqual(span._elementsEqual(a), true) + XCTAssertEqual(span.prefix(0)._elementsEqual([]), true) + XCTAssertEqual(span._elementsEqual(a.dropLast()), false) } } @@ -135,9 +135,9 @@ final class SpanTests: XCTestCase { let span = Span(unsafeBufferPointer: $0, owner: $0) let s = AnySequence(a) - XCTAssertEqual(span.elementsEqual(s), true) - XCTAssertEqual(span.dropLast().elementsEqual(s), false) - XCTAssertEqual(span.elementsEqual(s.dropFirst()), false) + XCTAssertEqual(span._elementsEqual(s), true) + XCTAssertEqual(span.dropLast()._elementsEqual(s), false) + XCTAssertEqual(span._elementsEqual(s.dropFirst()), false) } } @@ -146,10 +146,10 @@ final class SpanTests: XCTestCase { let a = (0.. Date: Mon, 17 Jun 2024 11:09:30 -0700 Subject: [PATCH 011/195] Rename prefix, suffix, dropFirst and dropLast --- Sources/Future/Span.swift | 61 ++----------------------------- Tests/FutureTests/SpanTests.swift | 41 ++++++++------------- 2 files changed, 20 insertions(+), 82 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index a7f6ea9d9..cb7894c75 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -563,38 +563,6 @@ extension Span where Element: Copyable { //MARK: one-sided slicing operations extension Span where Element: ~Copyable /*& ~Escapable*/ { - /// Returns a span from positions zero up to, but not - /// including, the specified position. - /// - /// The resulting span *does not include* the element at the position - /// `end`. - /// - /// - Parameter end: The "past the end" index of the resulting span. - /// - Returns: A span up to, but not including, the `end` position. - /// - /// - Complexity: O(1) - borrowing public func prefix(upTo offset: Int) -> dependsOn(self) Self { - if offset != 0 { - boundsCheckPrecondition(offset &- 1) - } - return Self(_unchecked: _start, count: offset, owner: self) - } - - /// Returns a span from positions zero through the specified position. - /// - /// The resulting span includes the element at the position - /// `end`. - /// - /// - Parameter position: The last index of the resulting span. - /// `position` must be a valid index of the collection. - /// - Returns: A span up to, and including, the given position. - /// - /// - Complexity: O(1) - borrowing public func prefix(through offset: Int) -> dependsOn(self) Self { - boundsCheckPrecondition(offset) - return Self(_unchecked: _start, count: offset &+ 1, owner: self) - } - /// Returns a span containing the initial elements of this span, /// up to the specified maximum length. /// @@ -606,7 +574,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - borrowing public func prefix(_ maxLength: Int) -> dependsOn(self) Self { + borrowing public func extracting(first maxLength: Int) -> dependsOn(self) Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < count ? maxLength : count return Self(_unchecked: _start, count: nc, owner: self) @@ -622,33 +590,12 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) - borrowing public func dropLast(_ k: Int = 1) -> dependsOn(self) Self { + borrowing public func extracting(droppingLast k: Int) -> dependsOn(self) Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < count ? count&-k : 0 return Self(_unchecked: _start, count: nc, owner: self) } - /// Returns a span from the specified position to the end of this span - /// - /// Passing the span's `count` as the `start` parameter results in - /// an empty subsequence. - /// - /// - Parameter start: The position at which to start the resulting span. - /// `start` must be a valid index of the span. - /// - Returns: A span starting at the `start` position. - /// - /// - Complexity: O(1) - borrowing public func suffix(from offset: Int) -> dependsOn(self) Self { - if offset != count { - boundsCheckPrecondition(offset) - } - return Self( - _unchecked: _start.advanced(by: offset), - count: count &- offset, - owner: self - ) - } - /// Returns a span containing the final elements of the span, /// up to the given maximum length. /// @@ -660,7 +607,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - borrowing public func suffix(_ maxLength: Int) -> dependsOn(self) Self { + borrowing public func extracting(last maxLength: Int) -> dependsOn(self) Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < count ? maxLength : count let newStart = _start.advanced(by: count&-nc) @@ -677,7 +624,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) - borrowing public func dropFirst(_ k: Int = 1) -> dependsOn(self) Self { + borrowing public func extracting(droppingFirst k: Int = 1) -> dependsOn(self) Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < count ? k : count let newStart = _start.advanced(by: dc) diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 5274f780e..1dd0889f1 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -103,10 +103,10 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(unsafeBufferPointer: $0, owner: $0) - XCTAssertEqual(span._elementsEqual(span.prefix(1)), false) - XCTAssertEqual(span.prefix(0)._elementsEqual(span.suffix(0)), true) + XCTAssertEqual(span._elementsEqual(span.extracting(first: 1)), false) + XCTAssertEqual(span.extracting(0..<0)._elementsEqual(span.extracting(last: 0)), true) XCTAssertEqual(span._elementsEqual(span), true) - XCTAssertEqual(span.prefix(3)._elementsEqual(span.suffix(3)), false) + XCTAssertEqual(span.extracting(0..<3)._elementsEqual(span.extracting(last: 3)), false) let copy = span.withUnsafeBufferPointer(Array.init) copy.withUnsafeBufferPointer { @@ -123,7 +123,7 @@ final class SpanTests: XCTestCase { let span = Span(unsafeBufferPointer: $0, owner: $0) XCTAssertEqual(span._elementsEqual(a), true) - XCTAssertEqual(span.prefix(0)._elementsEqual([]), true) + XCTAssertEqual(span.extracting(0..<0)._elementsEqual([]), true) XCTAssertEqual(span._elementsEqual(a.dropLast()), false) } } @@ -136,7 +136,7 @@ final class SpanTests: XCTestCase { let s = AnySequence(a) XCTAssertEqual(span._elementsEqual(s), true) - XCTAssertEqual(span.dropLast()._elementsEqual(s), false) + XCTAssertEqual(span.extracting(0..<(capacity-1))._elementsEqual(s), false) XCTAssertEqual(span._elementsEqual(s.dropFirst()), false) } } @@ -214,16 +214,10 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(unsafeBufferPointer: $0, owner: $0) XCTAssertEqual(span.count, capacity) - XCTAssertEqual(span.prefix(1).last, 0) - XCTAssertEqual(span.prefix(capacity).last, capacity-1) - XCTAssertEqual(span.dropLast(capacity).last, nil) - XCTAssertEqual(span.dropLast(1).last, capacity-2) - - XCTAssertTrue(span.prefix(upTo: 0).isEmpty) - XCTAssertTrue(span.prefix(upTo: span.count)._elementsEqual(span)) - - XCTAssertFalse(span.prefix(through: 0).isEmpty) - XCTAssertTrue(span.prefix(through: 2)._elementsEqual(span.prefix(3))) + XCTAssertEqual(span.extracting(first: 1).last, 0) + XCTAssertEqual(span.extracting(first: capacity).last, capacity-1) + XCTAssertEqual(span.extracting(droppingLast: capacity).last, nil) + XCTAssertEqual(span.extracting(droppingLast: 1).last, capacity-2) } } @@ -233,14 +227,11 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(unsafeBufferPointer: $0, owner: $0) XCTAssertEqual(span.count, capacity) - XCTAssertEqual(span.suffix(capacity).first, 0) - XCTAssertEqual(span.suffix(capacity-1).first, 1) - XCTAssertEqual(span.suffix(1).first, capacity-1) - XCTAssertEqual(span.dropFirst(capacity).first, nil) - XCTAssertEqual(span.dropFirst(1).first, 1) - - XCTAssertEqual(span.suffix(from: 0)._elementsEqual(a), true) - XCTAssertEqual(span.suffix(from: span.count).isEmpty, true) + XCTAssertEqual(span.extracting(last: capacity).first, 0) + XCTAssertEqual(span.extracting(last: capacity-1).first, 1) + XCTAssertEqual(span.extracting(last: 1).first, capacity-1) + XCTAssertEqual(span.extracting(droppingFirst: capacity).first, nil) + XCTAssertEqual(span.extracting(droppingFirst: 1).first, 1) } } @@ -294,8 +285,8 @@ final class SpanTests: XCTestCase { let capacity = 8 let a = Array(0.. Date: Mon, 17 Jun 2024 17:08:19 -0700 Subject: [PATCH 012/195] improve one of the Span initializers --- Sources/Future/RawSpan.swift | 10 +++------- Sources/Future/Span.swift | 11 ++++++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 217cc49ac..1f0c6592b 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -178,7 +178,6 @@ extension RawSpan { } } -//MARK: withUnsafeBytes extension RawSpan { //FIXME: mark closure parameter as non-escaping @@ -190,20 +189,17 @@ extension RawSpan { ) throws(E) -> dependsOn(self) Result { try body(.init(start: (count==0) ? nil : _start, count: count)) } +} +extension RawSpan { borrowing public func view( as: T.Type ) -> dependsOn(self) Span { - let (c, r) = count.quotientAndRemainder(dividingBy: MemoryLayout.stride) - precondition(r == 0, "Returned span must contain whole number of T") - return Span( - unsafeRawPointer: _start, as: T.self, count: c, owner: self - ) + Span(unsafeRawPointer: _start, as: T.self, byteCount: count, owner: self) } } //MARK: load - extension RawSpan { public func load( diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index cb7894c75..8991ece67 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -157,7 +157,9 @@ extension Span where Element: BitwiseCopyable { let (q, r) = c.quotientAndRemainder(dividingBy: s) precondition(r == 0) self.init( - unsafeRawPointer: baseAddress, as: Element.self, count: q, owner: owner + unsafePointer: baseAddress.assumingMemoryBound(to: Element.self), + count: q, + owner: owner ) } @@ -175,12 +177,15 @@ extension Span where Element: BitwiseCopyable { public init( unsafeRawPointer pointer: UnsafeRawPointer, as type: Element.Type, - count: Int, + byteCount: Int, owner: borrowing Owner ) -> dependsOn(owner) Self { + let stride = MemoryLayout.stride + let (q, r) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(r == 0) self.init( unsafePointer: pointer.assumingMemoryBound(to: Element.self), - count: count, + count: q, owner: owner ) } From 26d20e4a4e323f99a821bd69aa9081075c8ba3fd Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Jun 2024 17:47:01 -0700 Subject: [PATCH 013/195] fix CMakeLists for the Future target --- Sources/Future/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 015dc671c..df87b0c6a 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -8,7 +8,10 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(Future - "Dummy.swift" + "Inout.swift" + "Span.swift" + "RawSpan.swift" + "ContiguousStorage.swift" ) target_link_libraries(Future PRIVATE From 79ec3505da39d8280e758436f422d5f9bea9cd53 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Jun 2024 17:49:08 -0700 Subject: [PATCH 014/195] reorder initializers, improve labels, 80-column trimming --- Sources/Future/Span.swift | 72 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 8991ece67..f18e757aa 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -43,6 +43,26 @@ extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { extension Span where Element: ~Copyable /*& ~Escapable*/ { + //FIXME: make failable once Optional can be non-escapable + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the returned `Span`. + public init( + unsafeElements buffer: UnsafeBufferPointer, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + guard let baseAddress = buffer.baseAddress else { + fatalError("Span requires a non-nil base address") + } + self.init(unsafeStart: baseAddress, count: buffer.count, owner: owner) + } + /// Unsafely create a `Span` over initialized memory. /// /// The memory representing `count` instances starting at @@ -55,7 +75,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - owner: a binding whose lifetime must exceed that of /// the returned `Span`. public init( - unsafePointer start: UnsafePointer, + unsafeStart start: UnsafePointer, count: Int, owner: borrowing Owner ) -> dependsOn(owner) Self { @@ -66,6 +86,9 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { ) self.init(_unchecked: start, count: count, owner: owner) } +} + +extension Span where Element: BitwiseCopyable { //FIXME: make failable once Optional can be non-escapable /// Unsafely create a `Span` over initialized memory. @@ -78,17 +101,14 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - owner: a binding whose lifetime must exceed that of /// the returned `Span`. public init( - unsafeBufferPointer buffer: UnsafeBufferPointer, + unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner ) -> dependsOn(owner) Self { guard let baseAddress = buffer.baseAddress else { fatalError("Span requires a non-nil base address") } - self.init(unsafePointer: baseAddress, count: buffer.count, owner: owner) + self.init(unsafeStart: baseAddress, count: buffer.count, owner: owner) } -} - -extension Span where Element: BitwiseCopyable { /// Unsafely create a `Span` over initialized memory. /// @@ -102,7 +122,7 @@ extension Span where Element: BitwiseCopyable { /// - owner: a binding whose lifetime must exceed that of /// the returned `Span`. public init( - unsafePointer start: UnsafePointer, + unsafeStart start: UnsafePointer, count: Int, owner: borrowing Owner ) -> dependsOn(owner) Self { @@ -110,26 +130,6 @@ extension Span where Element: BitwiseCopyable { self.init(_unchecked: start, count: count, owner: owner) } - //FIXME: make failable once Optional can be non-escapable - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. - /// - /// - Parameters: - /// - buffer: an `UnsafeBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. - public init( - unsafeBufferPointer buffer: UnsafeBufferPointer, - owner: borrowing Owner - ) -> dependsOn(owner) Self { - guard let baseAddress = buffer.baseAddress else { - fatalError("Span requires a non-nil base address") - } - self.init(unsafePointer: baseAddress, count: buffer.count, owner: owner) - } - //FIXME: make failable once Optional can be non-escapable /// Unsafely create a `Span` over initialized memory. /// @@ -147,7 +147,6 @@ extension Span where Element: BitwiseCopyable { /// the returned `Span`. public init( unsafeBytes buffer: UnsafeRawBufferPointer, - as type: Element.Type, owner: borrowing Owner ) -> dependsOn(owner) Self { guard let baseAddress = buffer.baseAddress else { @@ -157,7 +156,7 @@ extension Span where Element: BitwiseCopyable { let (q, r) = c.quotientAndRemainder(dividingBy: s) precondition(r == 0) self.init( - unsafePointer: baseAddress.assumingMemoryBound(to: Element.self), + unsafeStart: baseAddress.assumingMemoryBound(to: Element.self), count: q, owner: owner ) @@ -175,8 +174,7 @@ extension Span where Element: BitwiseCopyable { /// - owner: a binding whose lifetime must exceed that of /// the returned `Span`. public init( - unsafeRawPointer pointer: UnsafeRawPointer, - as type: Element.Type, + unsafeStart pointer: UnsafeRawPointer, byteCount: Int, owner: borrowing Owner ) -> dependsOn(owner) Self { @@ -184,7 +182,7 @@ extension Span where Element: BitwiseCopyable { let (q, r) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(r == 0) self.init( - unsafePointer: pointer.assumingMemoryBound(to: Element.self), + unsafeStart: pointer.assumingMemoryBound(to: Element.self), count: q, owner: owner ) @@ -579,13 +577,13 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - borrowing public func extracting(first maxLength: Int) -> dependsOn(self) Self { + borrowing public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < count ? maxLength : count return Self(_unchecked: _start, count: nc, owner: self) } - /// Returns a span over all but the given number of final elements. + /// Returns a span over all but the given number of trailing elements. /// /// If the number of elements to drop exceeds the number of elements in /// the span, the result is an empty span. @@ -595,7 +593,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) - borrowing public func extracting(droppingLast k: Int) -> dependsOn(self) Self { + borrowing public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < count ? count&-k : 0 return Self(_unchecked: _start, count: nc, owner: self) @@ -612,7 +610,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - borrowing public func extracting(last maxLength: Int) -> dependsOn(self) Self { + borrowing public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < count ? maxLength : count let newStart = _start.advanced(by: count&-nc) @@ -629,7 +627,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) - borrowing public func extracting(droppingFirst k: Int = 1) -> dependsOn(self) Self { + borrowing public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < count ? k : count let newStart = _start.advanced(by: dc) From 3af133efeca644227ec43b319219c7dfed04ca5f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Jun 2024 18:10:26 -0700 Subject: [PATCH 015/195] renamings and doc-comments in RawSpan --- Sources/Future/RawSpan.swift | 258 +++++++++++++++++++++++++---------- 1 file changed, 183 insertions(+), 75 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 1f0c6592b..387f3ce70 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -12,6 +12,8 @@ import Builtin +// A RawSpan represents a span of initialized memory +// of unspecified type. @frozen public struct RawSpan: Copyable, ~Escapable { @usableFromInline let _start: UnsafeRawPointer @@ -67,12 +69,12 @@ extension RawSpan { /// the returned `Span`. @inlinable @inline(__always) public init( - unsafeRawPointer pointer: UnsafeRawPointer, - count: Int, + unsafeStart pointer: UnsafeRawPointer, + byteCount: Int, owner: borrowing Owner ) { - precondition(count >= 0, "Count must not be negative") - self.init(_unchecked: pointer, count: count, owner: owner) + precondition(byteCount >= 0, "Count must not be negative") + self.init(_unchecked: pointer, count: byteCount, owner: owner) } /// Create a `RawSpan` over the memory represented by a `Span` @@ -92,8 +94,36 @@ extension RawSpan { } } +extension RawSpan { + + /// The number of bytes in the span. + /// + /// To check whether the span is empty, use its `isEmpty` property + /// instead of comparing `count` to zero. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public var count: Int { _count } + + /// A Boolean value indicating whether the span is empty. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public var isEmpty: Bool { count == 0 } + + /// The indices that are valid for subscripting the span, in ascending + /// order. + /// + /// - Complexity: O(1) + @inlinable @inline(__always) + public var indices: Range { + .init(uncheckedBounds: (0, count)) + } +} + //MARK: Bounds Checking extension RawSpan { + /// Traps if `offset` is not a valid offset into this `RawSpan` /// /// - Parameters: @@ -119,69 +149,124 @@ extension RawSpan { } } -//MARK: Offset Manipulation +//MARK: extracting sub-spans extension RawSpan { + /// Constructs a new span over the bytes within the supplied range of + /// positions within this span. + /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `RawSpan`. + /// + /// - Returns: A `Span` over the bytes within `bounds` + /// + /// - Complexity: O(1) @inlinable @inline(__always) - public var count: Int { - borrowing get { self._count } - } - - @inlinable @inline(__always) - public var isEmpty: Bool { count == 0 } - - @inlinable @inline(__always) - public var indices: Range { - .init(uncheckedBounds: (0, count)) - } -} - -//MARK: integer offset subscripts -extension RawSpan { - - @inlinable @inline(__always) - public subscript(offsets offsets: Range) -> Self { - borrowing get { - boundsCheckPrecondition(offsets) - return self[uncheckedOffsets: offsets] - } + public func extracting(_ bounds: Range) -> Self { + boundsCheckPrecondition(bounds) + return extracting(uncheckedBounds: bounds) } + /// Constructs a new span over the bytes within the supplied range of + /// positions within this span. + /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `RawSpan`. + /// + /// - Returns: A `Span` over the bytes within `bounds` + /// + /// - Complexity: O(1) @inlinable @inline(__always) - public subscript(uncheckedOffsets offsets: Range) -> Self { - borrowing get { - RawSpan( - _unchecked: _start.advanced(by: offsets.lowerBound), - count: offsets.count, - owner: self - ) - } + public func extracting(uncheckedBounds bounds: Range) -> Self { + RawSpan( + _unchecked: _start.advanced(by: bounds.lowerBound), + count: bounds.count, + owner: self + ) } + /// Constructs a new span over the bytes within the supplied range of + /// positions within this span. + /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `RawSpan`. + /// + /// - Returns: A `Span` over the bytes within `bounds` + /// + /// - Complexity: O(1) @_alwaysEmitIntoClient - public subscript(offsets offsets: some RangeExpression) -> Self { - borrowing get { - self[offsets: offsets.relative(to: indices)] - } + public func extracting(_ bounds: some RangeExpression) -> Self { + extracting(bounds.relative(to: indices)) } + /// Constructs a new span over the bytes within the supplied range of + /// positions within this span. + /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `RawSpan`. + /// + /// - Returns: A `Span` over the bytes within `bounds` + /// + /// - Complexity: O(1) @_alwaysEmitIntoClient - public subscript(uncheckedOffsets offsets: some RangeExpression) -> Self { - borrowing get { - self[uncheckedOffsets: offsets.relative(to: indices)] - } + public func extracting( + uncheckedBounds bounds: some RangeExpression + ) -> Self { + extracting(uncheckedBounds: bounds.relative(to: indices)) } + /// Constructs a new span over all the bytes of this span. + /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not generally share their indices with the + /// span from which they are extracted. + /// + /// - Returns: A `RawSpan` over all the items of this span. + /// + /// - Complexity: O(1) @_alwaysEmitIntoClient - public subscript(offsets _: UnboundedRange) -> Self { - borrowing get { copy self } + public func extracting(_: UnboundedRange) -> Self { + self } } extension RawSpan { //FIXME: mark closure parameter as non-escaping - @_alwaysEmitIntoClient + /// Calls the given closure with a pointer to the underlying bytes of + /// the viewed contiguous storage. + /// + /// The buffer pointer passed as an argument to `body` is valid only + /// during the execution of `withUnsafeBytes(_:)`. + /// Do not store or return the pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeRawBufferPointer` + /// parameter that points to the viewed contiguous storage. + /// If `body` has a return value, that value is also + /// used as the return value for the `withUnsafeBytes(_:)` method. + /// The closure's parameter is valid only for the duration of + /// its execution. + /// - Returns: The return value of the `body` closure parameter. borrowing public func withUnsafeBytes< E: Error, Result: ~Copyable & ~Escapable >( @@ -192,10 +277,14 @@ extension RawSpan { } extension RawSpan { + /// View the bytes of this span as a given type + /// + /// - Parameter type: The type as which we should view + /// - Returns: A typed span viewing these bytes as T borrowing public func view( - as: T.Type + as type: T.Type ) -> dependsOn(self) Span { - Span(unsafeRawPointer: _start, as: T.self, byteCount: count, owner: self) + Span(unsafeStart: _start, byteCount: count, owner: self) } } @@ -236,49 +325,68 @@ extension RawSpan { //MARK: one-sided slicing operations extension RawSpan { - borrowing public func prefix(upTo offset: Int) -> dependsOn(self) Self { - if offset != 0 { - boundsCheckPrecondition(offset &- 1) - } - return Self(_unchecked: _start, count: offset, owner: self) - } - - borrowing public func prefix(through offset: Int) -> dependsOn(self) Self { - boundsCheckPrecondition(offset) - return Self(_unchecked: _start, count: offset &+ 1, owner: self) - } - - borrowing public func prefix(_ maxLength: Int) -> dependsOn(self) Self { + /// Returns a span containing the initial bytes of this span, + /// up to the specified maximum byte count. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the bytes. + /// + /// - Parameter maxLength: The maximum number of bytes to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` bytes. + /// + /// - Complexity: O(1) + borrowing public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < count ? maxLength : count return Self(_unchecked: _start, count: nc, owner: self) } - borrowing public func dropLast(_ k: Int = 1) -> dependsOn(self) Self { + /// Returns a span over all but the given number of trailing bytes. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// - Parameter k: The number of bytes to drop off the end of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span leaving off the specified number of bytes at the end. + /// + /// - Complexity: O(1) + borrowing public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < count ? count&-k : 0 return Self(_unchecked: _start, count: nc, owner: self) } - borrowing public func suffix(from offset: Int) -> dependsOn(self) Self { - if offset != count { - boundsCheckPrecondition(offset) - } - return Self( - _unchecked: _start.advanced(by: offset), - count: count &- offset, - owner: self - ) - } - - borrowing public func suffix(_ maxLength: Int) -> dependsOn(self) Self { + /// Returns a span containing the trailing bytes of the span, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the bytes. + /// + /// - Parameter maxLength: The maximum number of bytes to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` bytes. + /// + /// - Complexity: O(1) + borrowing public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < count ? maxLength : count let newStart = _start.advanced(by: count&-nc) return Self(_unchecked: newStart, count: nc, owner: self) } - borrowing public func dropFirst(_ k: Int = 1) -> dependsOn(self) Self { + /// Returns a span over all but the given number of initial bytes. + /// + /// If the number of elements to drop exceeds the number of bytes in + /// the span, the result is an empty span. + /// + /// - Parameter k: The number of bytes to drop from the beginning of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span starting after the specified number of bytes. + /// + /// - Complexity: O(1) + borrowing public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < count ? k : count let newStart = _start.advanced(by: dc) From 228f3f3422a38eee11890a5a84c277b1dece43af Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Jun 2024 18:10:42 -0700 Subject: [PATCH 016/195] fix tests and ContiguousStorage --- Sources/Future/ContiguousStorage.swift | 40 +++++++----------- Tests/FutureTests/RawSpanTests.swift | 56 +++++++++++--------------- Tests/FutureTests/SpanTests.swift | 46 ++++++++++----------- 3 files changed, 61 insertions(+), 81 deletions(-) diff --git a/Sources/Future/ContiguousStorage.swift b/Sources/Future/ContiguousStorage.swift index b9ffc0b60..3a747d9d1 100644 --- a/Sources/Future/ContiguousStorage.swift +++ b/Sources/Future/ContiguousStorage.swift @@ -10,31 +10,27 @@ // //===----------------------------------------------------------------------===// -public protocol ContiguousStorage: ~Copyable, ~Escapable { - associatedtype StoredElement/*: ~Copyable & ~Escapable*/ +public protocol ContiguousStorage: ~Copyable, ~Escapable { + associatedtype Element/*: ~Copyable & ~Escapable*/ - var storage: Span { borrowing get } + var storage: Span { borrowing get } } extension Span: ContiguousStorage /*where Element: ~Copyable & ~Escapable*/ { - public typealias StoredElement = Element public var storage: Self { self } } extension Array: ContiguousStorage { - public typealias StoredElement = Element public var storage: Span { _read { if let a = _baseAddressIfContiguous { - yield Span( - unsafePointer: a, count: count, owner: self - ) + yield Span(unsafeStart: a, count: count, owner: self) } else { let a = ContiguousArray(copy self) #if true let s = Span( - unsafePointer: a._baseAddressIfContiguous!, count: a.count, owner: a + unsafeStart: a._baseAddressIfContiguous!, count: a.count, owner: a ) #else let s = a.storage @@ -46,18 +42,16 @@ extension Array: ContiguousStorage { } extension ContiguousArray: ContiguousStorage { - public typealias StoredElement = Element public var storage: Span { borrowing get { Span( - unsafePointer: _baseAddressIfContiguous!, count: count, owner: self + unsafeStart: _baseAddressIfContiguous!, count: count, owner: self ) } } } extension CollectionOfOne: ContiguousStorage { - public typealias StoredElement = Element public var storage: Span { _read { /* ideally: (with strawman syntax) @@ -69,17 +63,16 @@ extension CollectionOfOne: ContiguousStorage { let a = ContiguousArray(self) yield Span( - unsafePointer: a._baseAddressIfContiguous!, count: 1, owner: a + unsafeStart: a._baseAddressIfContiguous!, count: 1, owner: a ) } } } -extension String: ContiguousStorage { - public typealias StoredElement = UTF8.CodeUnit +extension String.UTF8View: ContiguousStorage { public var storage: Span { _read { - if utf8.count < 16 { // Wrong way to know whether the String is smol + if count < 16 { // Wrong way to know whether the String is smol // if _guts.isSmall { // let /*@addressable*/ rawStorage = _guts.asSmall._storage // let span = RawSpan( @@ -89,24 +82,21 @@ extension String: ContiguousStorage { // ) // yield span.view(as: UTF8.CodeUnit.self) - let a = ContiguousArray(utf8) + let a = ContiguousArray(self) // yield a.storage yield Span( - unsafePointer: a._baseAddressIfContiguous!, count: 1, owner: a + unsafeStart: a._baseAddressIfContiguous!, count: 1, owner: a ) } - else if let buffer = utf8.withContiguousStorageIfAvailable({ $0 }) { + else if let buffer = withContiguousStorageIfAvailable({ $0 }) { // this is totally wrong, but there is a way with stdlib-internal API - yield Span( - unsafeBufferPointer: buffer, - owner: self - ) + yield Span(unsafeElements: buffer, owner: self) } else { // copy non-fast code units if we don't have eager bridging - let a = ContiguousArray(utf8) + let a = ContiguousArray(self) // yield a.storage yield Span( - unsafePointer: a._baseAddressIfContiguous!, count: 1, owner: a + unsafeStart: a._baseAddressIfContiguous!, count: 1, owner: a ) } } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 7b96a242e..a857971b1 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -56,7 +56,9 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let pointer = $0.baseAddress! let span = RawSpan( - unsafeRawPointer: pointer, count: capacity*MemoryLayout.stride, owner: a + unsafeStart: pointer, + byteCount: capacity*MemoryLayout.stride, + owner: a ) XCTAssertEqual(span.count, capacity*MemoryLayout.stride) } @@ -84,7 +86,7 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let span = RawSpan(unsafeBytes: $0, owner: $0) - let u0 = span.dropFirst(2).loadUnaligned(as: UInt64.self) + let u0 = span.extracting(droppingFirst: 2).loadUnaligned(as: UInt64.self) XCTAssertEqual(u0 & 0xff, 2) XCTAssertEqual(u0.byteSwapped & 0xff, 9) let u1 = span.loadUnaligned(fromByteOffset: 6, as: UInt64.self) @@ -99,10 +101,10 @@ final class RawSpanTests: XCTestCase { let b = (0..(unsafeBytes: $0, as: UInt.self, owner: $0) + let b = Span(unsafeBytes: $0, owner: $0) XCTAssertEqual(b.count, capacity) - let r = Span(unsafeBytes: $0, as: Int8.self, owner: $0) + let r = Span(unsafeBytes: $0, owner: $0) XCTAssertEqual(r.count, capacity*MemoryLayout.stride) } } @@ -57,11 +57,11 @@ final class SpanTests: XCTestCase { let capacity = 4 let a = Array(0...stride) } @@ -81,7 +81,7 @@ final class SpanTests: XCTestCase { let capacity = 4 let a = Array(0..(unsafeBufferPointer: $0, owner: $0) + let span = Span(unsafeElements: $0, owner: $0) XCTAssertEqual(span.count, capacity) XCTAssertEqual(span.extracting(last: capacity).first, 0) XCTAssertEqual(span.extracting(last: capacity-1).first, 1) @@ -240,7 +240,7 @@ final class SpanTests: XCTestCase { let a = Array(0.. Date: Thu, 20 Jun 2024 01:32:12 -0700 Subject: [PATCH 017/195] add tests for ContiguousStorage --- .../FutureTests/ContiguousStorageTests.swift | 58 +++++++++++++++++++ Tests/FutureTests/SpanTests.swift | 28 --------- 2 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 Tests/FutureTests/ContiguousStorageTests.swift diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift new file mode 100644 index 000000000..b5a0ad33f --- /dev/null +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -0,0 +1,58 @@ +import XCTest +import Future + +final class ContiguousStorageTests: XCTestCase { + + func testBorrowArrayStorage() throws { + + let capacity = 10 + var a: [Int] = [] + a = Array(0.. + + var startIndex: Int { 0 } + var endIndex: Int { span.count } + func index(after i: Int) -> Int { i+2 } + + init(_ contiguous: borrowing Span) { + span = copy contiguous + } + + subscript(_ p: Int) -> Int { span[p] } + } + + @inline(never) + private func skip( + along array: borrowing Array + ) -> dependsOn(array) Skipper { + Skipper(array.storage) + } + + func testSpanWrapper() { + let capacity = 8 + let a = Array(0.. - - var startIndex: Int { 0 } - var endIndex: Int { span.count } - func index(after i: Int) -> Int { i+2 } - - init(_ contiguous: borrowing Span) { - span = copy contiguous - } - - subscript(_ p: Int) -> Int { span[p] } - } - - let skipper = Skipper(a.storage) - var i = skipper.startIndex - var s: [Int] = [] - while i < skipper.endIndex { - s.append(skipper[i]) - i = skipper.index(after: i) - } - XCTAssertEqual(s, [0, 2, 4, 6]) - } } From d0dcbb12c6b4013bff9cd0d0790d62c48bb7e38e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 20 Jun 2024 01:44:08 -0700 Subject: [PATCH 018/195] doc-comment tweak --- Sources/Future/RawSpan.swift | 4 ++-- Sources/Future/Span.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 387f3ce70..e9e71f239 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -44,7 +44,7 @@ extension RawSpan { /// - Parameters: /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. /// - owner: a binding whose lifetime must exceed that of - /// the returned `RawSpan`. + /// the newly created `RawSpan`. @inlinable @inline(__always) public init( unsafeBytes buffer: UnsafeRawBufferPointer, @@ -66,7 +66,7 @@ extension RawSpan { /// - pointer: a pointer to the first initialized element. /// - count: the number of initialized elements in the view. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `RawSpan`. @inlinable @inline(__always) public init( unsafeStart pointer: UnsafeRawPointer, diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index f18e757aa..512777f60 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -52,7 +52,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Parameters: /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `Span`. public init( unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner @@ -73,7 +73,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - pointer: a pointer to the first initialized element. /// - count: the number of initialized elements in the view. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `Span`. public init( unsafeStart start: UnsafePointer, count: Int, @@ -99,7 +99,7 @@ extension Span where Element: BitwiseCopyable { /// - Parameters: /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `Span`. public init( unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner @@ -120,7 +120,7 @@ extension Span where Element: BitwiseCopyable { /// - pointer: a pointer to the first initialized element. /// - count: the number of initialized elements in the view. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `Span`. public init( unsafeStart start: UnsafePointer, count: Int, @@ -144,7 +144,7 @@ extension Span where Element: BitwiseCopyable { /// - unsafeBytes: a buffer to initialized elements. /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `Span`. public init( unsafeBytes buffer: UnsafeRawBufferPointer, owner: borrowing Owner @@ -172,7 +172,7 @@ extension Span where Element: BitwiseCopyable { /// - pointer: a pointer to the first initialized element. /// - count: the number of initialized elements in the view. /// - owner: a binding whose lifetime must exceed that of - /// the returned `Span`. + /// the newly created `Span`. public init( unsafeStart pointer: UnsafeRawPointer, byteCount: Int, From 5334c14ad91de6f527cf6084c52524c691ca19f2 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 20 Jun 2024 02:12:50 -0700 Subject: [PATCH 019/195] doc-comments for `load` and `loadUnaligned` --- Sources/Future/RawSpan.swift | 58 ++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index e9e71f239..8eaf9e55e 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -83,9 +83,7 @@ extension RawSpan { /// - span: An existing `Span`, which will define both this /// `RawSpan`'s lifetime and the memory it represents. @inlinable @inline(__always) - public init( - _ span: borrowing Span - ) { + public init(_ span: borrowing Span) { self.init( _unchecked: UnsafeRawPointer(span._start), count: span.count * MemoryLayout.stride, @@ -291,6 +289,20 @@ extension RawSpan { //MARK: load extension RawSpan { + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// The memory at this pointer plus `offset` must be properly aligned for + /// accessing `T` and initialized to `T` or another type that is layout + /// compatible with `T`. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance is memory-managed and unassociated + /// with the value in the memory referenced by this pointer. public func load( fromByteOffset offset: Int = 0, as: T.Type ) -> T { @@ -300,12 +312,39 @@ extension RawSpan { return load(fromUncheckedByteOffset: offset, as: T.self) } + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// The memory at this pointer plus `offset` must be properly aligned for + /// accessing `T` and initialized to `T` or another type that is layout + /// compatible with `T`. + /// + /// This function does not validate the bounds of the memory access; + /// this is an unsafe operation. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance is memory-managed and unassociated + /// with the value in the memory referenced by this pointer. public func load( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { _start.load(fromByteOffset: offset, as: T.self) } + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance isn't associated + /// with the value in the range of memory referenced by this pointer. public func loadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { @@ -315,6 +354,19 @@ extension RawSpan { return loadUnaligned(fromUncheckedByteOffset: offset, as: T.self) } + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// This function does not validate the bounds of the memory access; + /// this is an unsafe operation. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance isn't associated + /// with the value in the range of memory referenced by this pointer. public func loadUnaligned( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { From 3d819ebabf1630b12f812e63972f3c407978f89c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 20 Jun 2024 02:52:43 -0700 Subject: [PATCH 020/195] add parsing utilities --- Sources/Future/RawSpan.swift | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 8eaf9e55e..2f2db4ff6 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -445,3 +445,112 @@ extension RawSpan { return Self(_unchecked: newStart, count: count&-dc, owner: self) } } + +/// An error indicating that out-of-bounds access was attempted +@frozen +public struct OutOfBoundsError: Error { + /// The number of elements expected + public var expected: Int + + /// The number of elements found + public var has: Int + + @inlinable + public init(expected: Int, has: Int) { + (self.expected, self.has) = (expected, has) + } +} + +extension RawSpan { + /// Parse an instance of `T`, advancing `position`. + @inlinable + public func parse( + _ position: inout Int, as t: T.Type = T.self + ) throws(OutOfBoundsError) -> T { + let length = MemoryLayout.size + guard position >= 0 else { + throw OutOfBoundsError(expected: length, has: 0) + } + let end = position &+ length + guard end <= length else { + throw OutOfBoundsError(expected: length, has: count&-position) + } + return loadUnaligned(fromUncheckedByteOffset: position, as: T.self) + } + + /// Parse `numBytes` of data, advancing `position`. + @inlinable + public func parse( + _ position: inout Int, numBytes: some FixedWidthInteger + ) throws (OutOfBoundsError) -> Self { + let length = Int(numBytes) + guard position >= 0 else { + throw OutOfBoundsError(expected: length, has: 0) + } + let end = position &+ length + guard end <= length else { + throw OutOfBoundsError(expected: length, has: count&-position) + } + return extracting(position.. + + /// The current parsing position + public var position: Int + + @inlinable + public init(_ base: RawSpan, in range: Range) { + base.boundsCheckPrecondition(range) + position = 0 + self.base = base + parseRange = range + } + + @inlinable + public init(_ base: RawSpan) { + position = 0 + self.base = base + parseRange = base.indices + } + + /// Parse an instance of `T` and advance + @inlinable + public mutating func parse( + _ t: T.Type = T.self + ) throws(OutOfBoundsError) -> T { + try base.parse(&position, as: T.self) + } + + /// Parse `numBytes`and advance + @inlinable + public mutating func parse( + numBytes: some FixedWidthInteger + ) throws (OutOfBoundsError) -> RawSpan { + try base.parse(&position, numBytes: numBytes) + } + + /// The bytes that we've parsed so far + @inlinable + public var parsedBytes: RawSpan { base.extracting(.. Cursor { Cursor(self) } + + @inlinable + public func makeCursor(in range: Range) -> Cursor { + Cursor(self, in: range) + } +} From 539d11545e7746b243e6736aca007f98e7d91107 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 20 Jun 2024 15:00:29 -0700 Subject: [PATCH 021/195] de-emphasize indices property on `Span` --- Sources/Future/Span.swift | 6 +++--- Tests/FutureTests/ContiguousStorageTests.swift | 2 +- Tests/FutureTests/SpanTests.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 512777f60..b5efcdb82 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -279,7 +279,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Complexity: O(1) @inlinable @inline(__always) - public var indices: Range { + public var _indices: Range { .init(uncheckedBounds: (0, _count)) } } @@ -447,7 +447,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Complexity: O(1) @_alwaysEmitIntoClient public func extracting(_ bounds: some RangeExpression) -> Self { - extracting(bounds.relative(to: indices)) + extracting(bounds.relative(to: _indices)) } /// Constructs a new span over the items within the supplied range of @@ -469,7 +469,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { - extracting(uncheckedBounds: bounds.relative(to: indices)) + extracting(uncheckedBounds: bounds.relative(to: _indices)) } /// Constructs a new span over all the items of this span. diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift index b5a0ad33f..07762a35f 100644 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -12,7 +12,7 @@ final class ContiguousStorageTests: XCTestCase { let span = a.storage XCTAssertEqual(span.count, capacity) - for i in span.indices { + for i in span._indices { XCTAssertEqual(span[i], a[i]) } diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 44b318373..4fc3e2600 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -82,10 +82,10 @@ final class SpanTests: XCTestCase { let a = Array(0.. Date: Thu, 20 Jun 2024 15:05:33 -0700 Subject: [PATCH 022/195] use `byteCount` instead of `count` for `RawSpan` - use the label `byteOffset` more consistently --- Sources/Future/RawSpan.swift | 58 ++++++++++++++-------------- Tests/FutureTests/RawSpanTests.swift | 14 +++---- Tests/FutureTests/SpanTests.swift | 2 +- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 2f2db4ff6..5dcf499b9 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -22,11 +22,11 @@ public struct RawSpan: Copyable, ~Escapable { @inlinable @inline(__always) internal init( _unchecked start: UnsafeRawPointer, - count: Int, + byteCount: Int, owner: borrowing Owner ) -> dependsOn(owner) Self { self._start = start - self._count = count + self._count = byteCount } } @@ -53,7 +53,7 @@ extension RawSpan { guard let baseAddress = buffer.baseAddress else { fatalError("RawSpan requires a non-nil base address") } - self.init(_unchecked: baseAddress, count: buffer.count, owner: owner) + self.init(_unchecked: baseAddress, byteCount: buffer.count, owner: owner) } /// Unsafely create a `RawSpan` over initialized memory. @@ -74,7 +74,7 @@ extension RawSpan { owner: borrowing Owner ) { precondition(byteCount >= 0, "Count must not be negative") - self.init(_unchecked: pointer, count: byteCount, owner: owner) + self.init(_unchecked: pointer, byteCount: byteCount, owner: owner) } /// Create a `RawSpan` over the memory represented by a `Span` @@ -86,7 +86,7 @@ extension RawSpan { public init(_ span: borrowing Span) { self.init( _unchecked: UnsafeRawPointer(span._start), - count: span.count * MemoryLayout.stride, + byteCount: span.count * MemoryLayout.stride, owner: span ) } @@ -101,21 +101,21 @@ extension RawSpan { /// /// - Complexity: O(1) @inlinable @inline(__always) - public var count: Int { _count } + public var byteCount: Int { _count } /// A Boolean value indicating whether the span is empty. /// /// - Complexity: O(1) @inlinable @inline(__always) - public var isEmpty: Bool { count == 0 } + public var isEmpty: Bool { byteCount == 0 } /// The indices that are valid for subscripting the span, in ascending /// order. /// /// - Complexity: O(1) @inlinable @inline(__always) - public var indices: Range { - .init(uncheckedBounds: (0, count)) + public var _byteOffsets: Range { + .init(uncheckedBounds: (0, byteCount)) } } @@ -129,7 +129,7 @@ extension RawSpan { @inlinable @inline(__always) public func boundsCheckPrecondition(_ offset: Int) { precondition( - 0 <= offset && offset < count, + 0 <= offset && offset < byteCount, "Offset out of bounds" ) } @@ -141,7 +141,7 @@ extension RawSpan { @inlinable @inline(__always) public func boundsCheckPrecondition(_ offsets: Range) { precondition( - 0 <= offsets.lowerBound && offsets.upperBound <= count, + 0 <= offsets.lowerBound && offsets.upperBound <= byteCount, "Range of offsets out of bounds" ) } @@ -188,7 +188,7 @@ extension RawSpan { public func extracting(uncheckedBounds bounds: Range) -> Self { RawSpan( _unchecked: _start.advanced(by: bounds.lowerBound), - count: bounds.count, + byteCount: bounds.count, owner: self ) } @@ -208,7 +208,7 @@ extension RawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient public func extracting(_ bounds: some RangeExpression) -> Self { - extracting(bounds.relative(to: indices)) + extracting(bounds.relative(to: _byteOffsets)) } /// Constructs a new span over the bytes within the supplied range of @@ -230,7 +230,7 @@ extension RawSpan { public func extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { - extracting(uncheckedBounds: bounds.relative(to: indices)) + extracting(uncheckedBounds: bounds.relative(to: _byteOffsets)) } /// Constructs a new span over all the bytes of this span. @@ -270,7 +270,7 @@ extension RawSpan { >( _ body: (_ buffer: borrowing UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> dependsOn(self) Result { - try body(.init(start: (count==0) ? nil : _start, count: count)) + try body(.init(start: (byteCount==0) ? nil : _start, count: byteCount)) } } @@ -282,7 +282,7 @@ extension RawSpan { borrowing public func view( as type: T.Type ) -> dependsOn(self) Span { - Span(unsafeStart: _start, byteCount: count, owner: self) + Span(unsafeStart: _start, byteCount: byteCount, owner: self) } } @@ -390,8 +390,8 @@ extension RawSpan { /// - Complexity: O(1) borrowing public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") - let nc = maxLength < count ? maxLength : count - return Self(_unchecked: _start, count: nc, owner: self) + let nc = maxLength < byteCount ? maxLength : byteCount + return Self(_unchecked: _start, byteCount: nc, owner: self) } /// Returns a span over all but the given number of trailing bytes. @@ -406,8 +406,8 @@ extension RawSpan { /// - Complexity: O(1) borrowing public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let nc = k < count ? count&-k : 0 - return Self(_unchecked: _start, count: nc, owner: self) + let nc = k < byteCount ? byteCount&-k : 0 + return Self(_unchecked: _start, byteCount: nc, owner: self) } /// Returns a span containing the trailing bytes of the span, @@ -423,9 +423,9 @@ extension RawSpan { /// - Complexity: O(1) borrowing public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") - let nc = maxLength < count ? maxLength : count - let newStart = _start.advanced(by: count&-nc) - return Self(_unchecked: newStart, count: nc, owner: self) + let nc = maxLength < byteCount ? maxLength : byteCount + let newStart = _start.advanced(by: byteCount&-nc) + return Self(_unchecked: newStart, byteCount: nc, owner: self) } /// Returns a span over all but the given number of initial bytes. @@ -440,9 +440,9 @@ extension RawSpan { /// - Complexity: O(1) borrowing public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let dc = k < count ? k : count + let dc = k < byteCount ? k : byteCount let newStart = _start.advanced(by: dc) - return Self(_unchecked: newStart, count: count&-dc, owner: self) + return Self(_unchecked: newStart, byteCount: byteCount&-dc, owner: self) } } @@ -473,7 +473,7 @@ extension RawSpan { } let end = position &+ length guard end <= length else { - throw OutOfBoundsError(expected: length, has: count&-position) + throw OutOfBoundsError(expected: length, has: byteCount&-position) } return loadUnaligned(fromUncheckedByteOffset: position, as: T.self) } @@ -489,7 +489,7 @@ extension RawSpan { } let end = position &+ length guard end <= length else { - throw OutOfBoundsError(expected: length, has: count&-position) + throw OutOfBoundsError(expected: length, has: byteCount&-position) } return extracting(position...stride) + XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) XCTAssertFalse(span.isEmpty) } @@ -46,7 +46,7 @@ final class RawSpanTests: XCTestCase { let a = Array(0...stride) + XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) } } @@ -60,7 +60,7 @@ final class RawSpanTests: XCTestCase { byteCount: capacity*MemoryLayout.stride, owner: a ) - XCTAssertEqual(span.count, capacity*MemoryLayout.stride) + XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) } } @@ -124,7 +124,7 @@ final class RawSpanTests: XCTestCase { let span = RawSpan(unsafeBytes: $0, owner: $0) let prefix = span.extracting(0..<8) let beyond = prefix.extracting(uncheckedBounds: 16..<24) - XCTAssertEqual(beyond.count, 8) + XCTAssertEqual(beyond.byteCount, 8) XCTAssertEqual(beyond.load(as: UInt8.self), 16) } } @@ -171,7 +171,7 @@ final class RawSpanTests: XCTestCase { let a = Array(0...stride) + XCTAssertEqual(raw.byteCount, span.count*MemoryLayout.stride) } } From 637518426db7d83f12e592c18cedea7be22572bf Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 20 Jun 2024 18:10:45 -0700 Subject: [PATCH 023/195] a few corrections --- Sources/Future/RawSpan.swift | 12 ++++++------ Sources/Future/Span.swift | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 5dcf499b9..ef5cd4d14 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -35,7 +35,7 @@ extension RawSpan: Sendable {} extension RawSpan { - //FIXME: make failable once Optional can be non-escapable + //FIXME: make properly non-failable /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -160,7 +160,7 @@ extension RawSpan { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `RawSpan`. /// - /// - Returns: A `Span` over the bytes within `bounds` + /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) @inlinable @inline(__always) @@ -181,7 +181,7 @@ extension RawSpan { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `RawSpan`. /// - /// - Returns: A `Span` over the bytes within `bounds` + /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) @inlinable @inline(__always) @@ -203,7 +203,7 @@ extension RawSpan { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `RawSpan`. /// - /// - Returns: A `Span` over the bytes within `bounds` + /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) @_alwaysEmitIntoClient @@ -223,7 +223,7 @@ extension RawSpan { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `RawSpan`. /// - /// - Returns: A `Span` over the bytes within `bounds` + /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) @_alwaysEmitIntoClient @@ -239,7 +239,7 @@ extension RawSpan { /// slices, extracted spans do not generally share their indices with the /// span from which they are extracted. /// - /// - Returns: A `RawSpan` over all the items of this span. + /// - Returns: A span over all the bytes of this span. /// /// - Complexity: O(1) @_alwaysEmitIntoClient diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index b5efcdb82..80b983d3d 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -43,7 +43,7 @@ extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { extension Span where Element: ~Copyable /*& ~Escapable*/ { - //FIXME: make failable once Optional can be non-escapable + //FIXME: make properly non-failable /// Unsafely create a `Span` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -90,7 +90,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { extension Span where Element: BitwiseCopyable { - //FIXME: make failable once Optional can be non-escapable + //FIXME: make properly non-failable /// Unsafely create a `Span` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -130,7 +130,7 @@ extension Span where Element: BitwiseCopyable { self.init(_unchecked: start, count: count, owner: owner) } - //FIXME: make failable once Optional can be non-escapable + //FIXME: make properly non-failable /// Unsafely create a `Span` over initialized memory. /// /// The memory in `unsafeBytes` must be owned by the instance `owner` @@ -399,7 +399,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `Span`. /// - /// - Returns: A `Span` over the items at `bounds` + /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) @inlinable @inline(__always) @@ -420,7 +420,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `Span`. /// - /// - Returns: A `Span` over the items at `bounds` + /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) @inlinable @inline(__always) @@ -442,7 +442,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `Span`. /// - /// - Returns: A `Span` over the items at `bounds` + /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) @_alwaysEmitIntoClient @@ -462,7 +462,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Parameter bounds: A valid range of positions. Every position in /// this range must be within the bounds of this `Span`. /// - /// - Returns: A `Span` over the items at `bounds` + /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) @_alwaysEmitIntoClient From 4a20e6c3c9ba96eb5c79226bdb16e26c68e231a6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Jun 2024 12:50:42 -0700 Subject: [PATCH 024/195] remove some keywords that are inferred by default --- Sources/Future/RawSpan.swift | 22 +++++++++---------- Sources/Future/Span.swift | 42 ++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index ef5cd4d14..fd8be4a78 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -24,7 +24,7 @@ public struct RawSpan: Copyable, ~Escapable { _unchecked start: UnsafeRawPointer, byteCount: Int, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { self._start = start self._count = byteCount } @@ -265,11 +265,9 @@ extension RawSpan { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. - borrowing public func withUnsafeBytes< - E: Error, Result: ~Copyable & ~Escapable - >( - _ body: (_ buffer: borrowing UnsafeRawBufferPointer) throws(E) -> Result - ) throws(E) -> dependsOn(self) Result { + public func withUnsafeBytes( + _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result + ) throws(E) -> Result { try body(.init(start: (byteCount==0) ? nil : _start, count: byteCount)) } } @@ -279,9 +277,9 @@ extension RawSpan { /// /// - Parameter type: The type as which we should view /// - Returns: A typed span viewing these bytes as T - borrowing public func view( + public func view( as type: T.Type - ) -> dependsOn(self) Span { + ) -> Span { Span(unsafeStart: _start, byteCount: byteCount, owner: self) } } @@ -388,7 +386,7 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) - borrowing public func extracting(first maxLength: Int) -> Self { + public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < byteCount ? maxLength : byteCount return Self(_unchecked: _start, byteCount: nc, owner: self) @@ -404,7 +402,7 @@ extension RawSpan { /// - Returns: A span leaving off the specified number of bytes at the end. /// /// - Complexity: O(1) - borrowing public func extracting(droppingLast k: Int) -> Self { + public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < byteCount ? byteCount&-k : 0 return Self(_unchecked: _start, byteCount: nc, owner: self) @@ -421,7 +419,7 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) - borrowing public func extracting(last maxLength: Int) -> Self { + public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < byteCount ? maxLength : byteCount let newStart = _start.advanced(by: byteCount&-nc) @@ -438,7 +436,7 @@ extension RawSpan { /// - Returns: A span starting after the specified number of bytes. /// /// - Complexity: O(1) - borrowing public func extracting(droppingFirst k: Int = 1) -> Self { + public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < byteCount ? k : byteCount let newStart = _start.advanced(by: dc) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 80b983d3d..9df6baa97 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -24,7 +24,7 @@ public struct Span: Copyable, ~Escapable { _unchecked start: UnsafePointer, count: Int, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { self._start = start self._count = count } @@ -56,7 +56,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public init( unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { guard let baseAddress = buffer.baseAddress else { fatalError("Span requires a non-nil base address") } @@ -78,7 +78,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { unsafeStart start: UnsafePointer, count: Int, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { precondition(count >= 0, "Count must not be negative") precondition( start.isAligned, @@ -103,7 +103,7 @@ extension Span where Element: BitwiseCopyable { public init( unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { guard let baseAddress = buffer.baseAddress else { fatalError("Span requires a non-nil base address") } @@ -125,7 +125,7 @@ extension Span where Element: BitwiseCopyable { unsafeStart start: UnsafePointer, count: Int, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { precondition(count >= 0, "Count must not be negative") self.init(_unchecked: start, count: count, owner: owner) } @@ -148,7 +148,7 @@ extension Span where Element: BitwiseCopyable { public init( unsafeBytes buffer: UnsafeRawBufferPointer, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { guard let baseAddress = buffer.baseAddress else { fatalError("Span requires a non-nil base address") } @@ -177,7 +177,7 @@ extension Span where Element: BitwiseCopyable { unsafeStart pointer: UnsafeRawPointer, byteCount: Int, owner: borrowing Owner - ) -> dependsOn(owner) Self { + ) { let stride = MemoryLayout.stride let (q, r) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(r == 0) @@ -332,7 +332,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Complexity: O(1) @inlinable @inline(__always) public subscript(_ position: Int) -> Element { - borrowing _read { + _read { boundsCheckPrecondition(position) yield self[unchecked: position] } @@ -348,7 +348,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Complexity: O(1) @inlinable @inline(__always) public subscript(unchecked position: Int) -> Element { - borrowing _read { + _read { yield _start.advanced(by: position).pointee } } @@ -488,7 +488,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { } //MARK: withUnsafePointer, etc. -extension Span where Element: ~Copyable { +extension Span where Element: ~Copyable /*& ~Escapable*/ { //FIXME: mark closure parameter as non-escaping /// Calls a closure with a pointer to the viewed contiguous storage. @@ -503,11 +503,9 @@ extension Span where Element: ~Copyable { /// for the `withUnsafeBufferPointer(_:)` method. The closure's /// parameter is valid only for the duration of its execution. /// - Returns: The return value of the `body` closure parameter. - borrowing public func withUnsafeBufferPointer< - E: Error, Result: ~Copyable & ~Escapable - >( - _ body: (_ buffer: borrowing UnsafeBufferPointer) throws(E) -> Result - ) throws(E) -> dependsOn(self) Result { + public func withUnsafeBufferPointer( + _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result + ) throws(E) -> Result { try body(.init(start: (count==0) ? nil : _start, count: count)) } } @@ -529,10 +527,8 @@ extension Span where Element: BitwiseCopyable { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. - borrowing public func withUnsafeBytes< - E: Error, Result: ~Copyable & ~Escapable - >( - _ body: (_ buffer: borrowing UnsafeRawBufferPointer) throws(E) -> Result + public func withUnsafeBytes( + _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { try RawSpan(self).withUnsafeBytes(body) } @@ -577,7 +573,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - borrowing public func extracting(first maxLength: Int) -> Self { + public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < count ? maxLength : count return Self(_unchecked: _start, count: nc, owner: self) @@ -593,7 +589,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) - borrowing public func extracting(droppingLast k: Int) -> Self { + public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < count ? count&-k : 0 return Self(_unchecked: _start, count: nc, owner: self) @@ -610,7 +606,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - borrowing public func extracting(last maxLength: Int) -> Self { + public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < count ? maxLength : count let newStart = _start.advanced(by: count&-nc) @@ -627,7 +623,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) - borrowing public func extracting(droppingFirst k: Int = 1) -> Self { + public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < count ? k : count let newStart = _start.advanced(by: dc) From 683e1cfb985afc4dd566bf4ba0a32e9207e4b930 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Jun 2024 12:51:36 -0700 Subject: [PATCH 025/195] make `withUnsafe*` functions be inlined --- Sources/Future/RawSpan.swift | 1 + Sources/Future/Span.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index fd8be4a78..5463b7955 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -265,6 +265,7 @@ extension RawSpan { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. + @_alwaysEmitIntoClient public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 9df6baa97..9e3390190 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -503,6 +503,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// for the `withUnsafeBufferPointer(_:)` method. The closure's /// parameter is valid only for the duration of its execution. /// - Returns: The return value of the `body` closure parameter. + @_alwaysEmitIntoClient public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result ) throws(E) -> Result { @@ -527,6 +528,7 @@ extension Span where Element: BitwiseCopyable { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. + @_alwaysEmitIntoClient public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { From ce574378f856d15eec794157225005947534a6ca Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 25 Jun 2024 10:27:59 -0700 Subject: [PATCH 026/195] improve bounds-checking and bounds-validation api --- Sources/Future/RawSpan.swift | 44 ++++++++++++++++++++-------- Sources/Future/Span.swift | 38 +++++++++++++++++------- Tests/FutureTests/RawSpanTests.swift | 4 +-- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 5463b7955..905a55335 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -122,27 +122,45 @@ extension RawSpan { //MARK: Bounds Checking extension RawSpan { + /// Return true if `offset` is a valid offset into this `RawSpan` + /// + /// - Parameters: + /// - position: an index to validate + /// - Returns: true if `offset` is a valid index + @inlinable @inline(__always) + public func validateBounds(_ offset: Int) -> Bool { + 0 <= offset && offset < byteCount + } + /// Traps if `offset` is not a valid offset into this `RawSpan` /// /// - Parameters: - /// - position: an offset to validate + /// - position: an index to validate @inlinable @inline(__always) - public func boundsCheckPrecondition(_ offset: Int) { + public func assertValidity(_ offset: Int) { precondition( - 0 <= offset && offset < byteCount, - "Offset out of bounds" + validateBounds(offset), "Offset out of bounds" ) } - /// Traps if `bounds` is not a valid range of offsets into this `RawSpan` + /// Return true if `offsets` is a valid range of offsets into this `RawSpan` + /// + /// - Parameters: + /// - offsets: a range of indices to validate + /// - Returns: true if `offsets` is a valid range of indices + @inlinable @inline(__always) + public func validateBounds(_ offsets: Range) -> Bool { + 0 <= offsets.lowerBound && offsets.upperBound <= byteCount + } + + /// Traps if `offsets` is not a valid range of offsets into this `RawSpan` /// /// - Parameters: - /// - offsets: a range of offsets to validate + /// - offsets: a range of indices to validate @inlinable @inline(__always) - public func boundsCheckPrecondition(_ offsets: Range) { + public func assertValidity(_ offsets: Range) { precondition( - 0 <= offsets.lowerBound && offsets.upperBound <= byteCount, - "Range of offsets out of bounds" + validateBounds(offsets), "Range of offsets out of bounds" ) } } @@ -165,7 +183,7 @@ extension RawSpan { /// - Complexity: O(1) @inlinable @inline(__always) public func extracting(_ bounds: Range) -> Self { - boundsCheckPrecondition(bounds) + assertValidity(bounds) return extracting(uncheckedBounds: bounds) } @@ -305,7 +323,7 @@ extension RawSpan { public func load( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - boundsCheckPrecondition( + assertValidity( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) ) return load(fromUncheckedByteOffset: offset, as: T.self) @@ -347,7 +365,7 @@ extension RawSpan { public func loadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - boundsCheckPrecondition( + assertValidity( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) ) return loadUnaligned(fromUncheckedByteOffset: offset, as: T.self) @@ -507,7 +525,7 @@ extension RawSpan { @inlinable public init(_ base: RawSpan, in range: Range) { - base.boundsCheckPrecondition(range) + base.assertValidity(range) position = 0 self.base = base parseRange = range diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 9e3390190..f3dc0326d 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -287,27 +287,45 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { //MARK: Bounds Checking extension Span where Element: ~Copyable /*& ~Escapable*/ { + /// Return true if `offset` is a valid offset into this `Span` + /// + /// - Parameters: + /// - position: an index to validate + /// - Returns: true if `offset` is a valid index + @inlinable @inline(__always) + public func validateBounds(_ offset: Int) -> Bool { + 0 <= offset && offset < count + } + /// Traps if `offset` is not a valid offset into this `Span` /// /// - Parameters: - /// - position: an Index to validate + /// - position: an index to validate @inlinable @inline(__always) - public func boundsCheckPrecondition(_ offset: Int) { + public func assertValidity(_ offset: Int) { precondition( - 0 <= offset && offset < count, - "Offset out of bounds" + validateBounds(offset), "Offset out of bounds" ) } + /// Return true if `offsets` is a valid range of offsets into this `Span` + /// + /// - Parameters: + /// - offsets: a range of indices to validate + /// - Returns: true if `offsets` is a valid range of indices + @inlinable @inline(__always) + public func validateBounds(_ offsets: Range) -> Bool { + 0 <= offsets.lowerBound && offsets.upperBound <= count + } + /// Traps if `offsets` is not a valid range of offsets into this `Span` /// /// - Parameters: /// - offsets: a range of indices to validate @inlinable @inline(__always) - public func boundsCheckPrecondition(_ offsets: Range) { + public func assertValidity(_ offsets: Range) { precondition( - 0 <= offsets.lowerBound && offsets.upperBound <= count, - "Range of offsets out of bounds" + validateBounds(offsets), "Range of offsets out of bounds" ) } } @@ -333,7 +351,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable @inline(__always) public subscript(_ position: Int) -> Element { _read { - boundsCheckPrecondition(position) + assertValidity(position) yield self[unchecked: position] } } @@ -365,7 +383,7 @@ extension Span where Element: BitwiseCopyable { @inlinable @inline(__always) public subscript(_ position: Int) -> Element { get { - boundsCheckPrecondition(position) + assertValidity(position) return self[unchecked: position] } } @@ -404,7 +422,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Complexity: O(1) @inlinable @inline(__always) public func extracting(_ bounds: Range) -> Self { - boundsCheckPrecondition(bounds) + assertValidity(bounds) return extracting(uncheckedBounds: bounds) } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index d8e193a6a..2a4e93a77 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -208,8 +208,8 @@ final class RawSpanTests: XCTestCase { let a = Array(0.. Date: Tue, 25 Jun 2024 12:19:11 -0700 Subject: [PATCH 027/195] remove some useless hedging from doc-comments --- Sources/Future/RawSpan.swift | 10 +++++----- Sources/Future/Span.swift | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 905a55335..1b51dd306 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -172,7 +172,7 @@ extension RawSpan { /// positions within this span. /// /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// - Parameter bounds: A valid range of positions. Every position in @@ -191,7 +191,7 @@ extension RawSpan { /// positions within this span. /// /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// This function does not validate `bounds`; this is an unsafe operation. @@ -215,7 +215,7 @@ extension RawSpan { /// positions within this span. /// /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// - Parameter bounds: A valid range of positions. Every position in @@ -233,7 +233,7 @@ extension RawSpan { /// positions within this span. /// /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// This function does not validate `bounds`; this is an unsafe operation. @@ -254,7 +254,7 @@ extension RawSpan { /// Constructs a new span over all the bytes of this span. /// /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// - Returns: A span over all the bytes of this span. diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index f3dc0326d..3532cb468 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -411,7 +411,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// positions within this span. /// /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// - Parameter bounds: A valid range of positions. Every position in @@ -430,7 +430,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// positions within this span. /// /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// This function does not validate `bounds`; this is an unsafe operation. @@ -454,7 +454,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// positions within this span. /// /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// - Parameter bounds: A valid range of positions. Every position in @@ -472,7 +472,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// positions within this span. /// /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// This function does not validate `bounds`; this is an unsafe operation. @@ -493,7 +493,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// Constructs a new span over all the items of this span. /// /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not generally share their indices with the + /// slices, extracted spans do not share their indices with the /// span from which they are extracted. /// /// - Returns: A `Span` over all the items of this span. From 99b025049e3fd4f1a791349716d7e3a6e6742ea7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 26 Jun 2024 16:26:57 -0700 Subject: [PATCH 028/195] add utilities to determine span containment relationships --- Sources/Future/RawSpan.swift | 17 +++++++++++++ Sources/Future/Span.swift | 17 +++++++++++++ Tests/FutureTests/RawSpanTests.swift | 34 ++++++++++++++++++++++++++ Tests/FutureTests/SpanTests.swift | 36 ++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 1b51dd306..71197e32f 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -391,6 +391,23 @@ extension RawSpan { } } +extension RawSpan { + + @inlinable @inline(__always) + public func contains(_ span: borrowing Self) -> Bool { + _start <= span._start && + span._start.advanced(by: span._count) <= _start.advanced(by: _count) + } + + @inlinable @inline(__always) + public func offsets(of span: borrowing Self) -> Range { + precondition(contains(span)) + let s = _start.distance(to: span._start) + let e = s + span._count + return Range(uncheckedBounds: (s, e)) + } +} + //MARK: one-sided slicing operations extension RawSpan { diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 3532cb468..c6fa8589f 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -579,6 +579,23 @@ extension Span where Element: Copyable { } } +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + @inlinable @inline(__always) + public func contains(_ span: borrowing Self) -> Bool { + _start <= span._start && + span._start.advanced(by: span._count) <= _start.advanced(by: _count) + } + + @inlinable @inline(__always) + public func offsets(of span: borrowing Self) -> Range { + precondition(contains(span)) + let s = _start.distance(to: span._start) + let e = s + span._count + return Range(uncheckedBounds: (s, e)) + } +} + //MARK: one-sided slicing operations extension Span where Element: ~Copyable /*& ~Escapable*/ { diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 2a4e93a77..449cff33e 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -212,4 +212,38 @@ final class RawSpanTests: XCTestCase { } // span.assertValidity(span.count) } + + func testContainment() { + let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) + defer { b.deallocate() } + + let span = RawSpan(unsafeBytes: .init(b), owner: b) + let subSpan = span.extracting(last: 2) + let emptySpan = span.extracting(first: 0) + let fakeSpan = RawSpan( + unsafeStart: b.baseAddress!.advanced(by: 8), byteCount: 8, owner: b + ) + + XCTAssertTrue(span.contains(subSpan)) + XCTAssertFalse(subSpan.contains(span)) + XCTAssertTrue(span.contains(emptySpan)) + XCTAssertFalse(emptySpan.contains(span)) + XCTAssertFalse(span.contains(fakeSpan)) + XCTAssertFalse(fakeSpan.contains(span)) + } + + func testOffsets() { + let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) + defer { b.deallocate() } + + let span = RawSpan(unsafeBytes: .init(b), owner: b) + let subSpan = span.extracting(last: 2) + let emptySpan = span.extracting(first: 0) + + var bounds: Range + bounds = span.offsets(of: subSpan) + XCTAssertEqual(bounds, span._byteOffsets.suffix(2)) + bounds = span.offsets(of: emptySpan) + XCTAssertEqual(bounds, span._byteOffsets.prefix(0)) + } } diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 619b93b0a..f0739465f 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -300,4 +300,40 @@ final class SpanTests: XCTestCase { XCTAssertEqual(span.count, capacity-1) XCTAssertEqual(prefix.count, 2) } + + func testContainment() { + let b = UnsafeMutableBufferPointer.allocate(capacity: 8) + _ = b.initialize(fromContentsOf: 0..<8) + defer { b.deallocate() } + + let span = Span(unsafeElements: .init(b), owner: b) + let subSpan = span.extracting(last: 2) + let emptySpan = span.extracting(first: 0) + let fakeSpan = Span( + unsafeStart: b.baseAddress!.advanced(by: 8), count: 8, owner: b + ) + + XCTAssertTrue(span.contains(subSpan)) + XCTAssertFalse(subSpan.contains(span)) + XCTAssertTrue(span.contains(emptySpan)) + XCTAssertFalse(emptySpan.contains(span)) + XCTAssertFalse(span.contains(fakeSpan)) + XCTAssertFalse(fakeSpan.contains(span)) + } + + func testOffsets() { + let b = UnsafeMutableBufferPointer.allocate(capacity: 8) + _ = b.initialize(fromContentsOf: 0..<8) + defer { b.deallocate() } + + let span = Span(unsafeElements: .init(b), owner: b) + let subSpan = span.extracting(last: 2) + let emptySpan = span.extracting(first: 0) + + var bounds: Range + bounds = span.offsets(of: subSpan) + XCTAssertEqual(bounds, span._indices.suffix(2)) + bounds = span.offsets(of: emptySpan) + XCTAssertEqual(bounds, span._indices.prefix(0)) + } } From f049c7ae66996b3be81be9688aecc313fb62d2bd Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 28 Jun 2024 18:35:37 -0700 Subject: [PATCH 029/195] better document unsafe operations of RawSpan --- Sources/Future/RawSpan.swift | 55 ++++++++++++++++++++-------- Tests/FutureTests/RawSpanTests.swift | 34 ++++++++--------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 71197e32f..72e3a2457 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -292,11 +292,22 @@ extension RawSpan { } extension RawSpan { - /// View the bytes of this span as a given type + + /// View the bytes of this span as type `T` + /// + /// This is the equivalent of `unsafeBitCast(_:to:)`. The + /// underlying bytes must be initialized as type `T`, be + /// initialized to a type that is layout-compatible with `T`, + /// or the function mapping bit patterns of length `8*MemoryLayout.size` + /// to instances of `T` must be surjective. /// - /// - Parameter type: The type as which we should view - /// - Returns: A typed span viewing these bytes as T - public func view( + /// This is an unsafe operation. Failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// + /// - Parameters: + /// - type: The type as which to view the bytes of this span. + /// - Returns: A typed span viewing these bytes as instances of `T`. + public func unsafeView( as type: T.Type ) -> Span { Span(unsafeStart: _start, byteCount: byteCount, owner: self) @@ -313,6 +324,9 @@ extension RawSpan { /// accessing `T` and initialized to `T` or another type that is layout /// compatible with `T`. /// + /// This is an unsafe operation. Failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// /// - Parameters: /// - offset: The offset from this pointer, in bytes. `offset` must be /// nonnegative. The default is zero. @@ -320,13 +334,13 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. - public func load( + public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type ) -> T { assertValidity( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) ) - return load(fromUncheckedByteOffset: offset, as: T.self) + return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) } /// Returns a new instance of the given type, constructed from the raw memory @@ -336,8 +350,9 @@ extension RawSpan { /// accessing `T` and initialized to `T` or another type that is layout /// compatible with `T`. /// - /// This function does not validate the bounds of the memory access; - /// this is an unsafe operation. + /// This is an unsafe operation. This function does not validate the bounds + /// of the memory access, and failure to meet the preconditions + /// above may produce an invalid value of `T`. /// /// - Parameters: /// - offset: The offset from this pointer, in bytes. `offset` must be @@ -346,7 +361,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. - public func load( + public func unsafeLoad( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { _start.load(fromByteOffset: offset, as: T.self) @@ -355,6 +370,12 @@ extension RawSpan { /// Returns a new instance of the given type, constructed from the raw memory /// at the specified offset. /// + /// The memory at this pointer plus `offset` must be initialized to `T` + /// or another type that is layout compatible with `T`. + /// + /// This is an unsafe operation. Failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// /// - Parameters: /// - offset: The offset from this pointer, in bytes. `offset` must be /// nonnegative. The default is zero. @@ -362,20 +383,24 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. - public func loadUnaligned( + public func unsafeLoadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { assertValidity( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) ) - return loadUnaligned(fromUncheckedByteOffset: offset, as: T.self) + return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) } /// Returns a new instance of the given type, constructed from the raw memory /// at the specified offset. /// - /// This function does not validate the bounds of the memory access; - /// this is an unsafe operation. + /// The memory at this pointer plus `offset` must be initialized to `T` + /// or another type that is layout compatible with `T`. + /// + /// This is an unsafe operation. This function does not validate the bounds + /// of the memory access, and failure to meet the preconditions + /// above may produce an invalid value of `T`. /// /// - Parameters: /// - offset: The offset from this pointer, in bytes. `offset` must be @@ -384,7 +409,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. - public func loadUnaligned( + public func unsafeLoadUnaligned( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { _start.loadUnaligned(fromByteOffset: offset, as: T.self) @@ -509,7 +534,7 @@ extension RawSpan { guard end <= length else { throw OutOfBoundsError(expected: length, has: byteCount&-position) } - return loadUnaligned(fromUncheckedByteOffset: position, as: T.self) + return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) } /// Parse `numBytes` of data, advancing `position`. diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 449cff33e..f4c2615b6 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -71,11 +71,11 @@ final class RawSpanTests: XCTestCase { let span = RawSpan(unsafeBytes: $0, owner: $0) let stride = MemoryLayout.stride - let s0 = span.load(as: String.self) + let s0 = span.unsafeLoad(as: String.self) XCTAssertEqual(s0.contains("0"), true) - let s1 = span.load(fromByteOffset: stride, as: String.self) + let s1 = span.unsafeLoad(fromByteOffset: stride, as: String.self) XCTAssertEqual(s1.contains("1"), true) - let s2 = span.load(fromUncheckedByteOffset: 2*stride, as: String.self) + let s2 = span.unsafeLoad(fromUncheckedByteOffset: 2*stride, as: String.self) XCTAssertEqual(s2.contains("2"), true) } } @@ -86,12 +86,12 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let span = RawSpan(unsafeBytes: $0, owner: $0) - let u0 = span.extracting(droppingFirst: 2).loadUnaligned(as: UInt64.self) + let u0 = span.extracting(droppingFirst: 2).unsafeLoadUnaligned(as: UInt64.self) XCTAssertEqual(u0 & 0xff, 2) XCTAssertEqual(u0.byteSwapped & 0xff, 9) - let u1 = span.loadUnaligned(fromByteOffset: 6, as: UInt64.self) + let u1 = span.unsafeLoadUnaligned(fromByteOffset: 6, as: UInt64.self) XCTAssertEqual(u1 & 0xff, 6) - let u3 = span.loadUnaligned(fromUncheckedByteOffset: 7, as: UInt32.self) + let u3 = span.unsafeLoadUnaligned(fromUncheckedByteOffset: 7, as: UInt32.self) XCTAssertEqual(u3 & 0xff, 7) } } @@ -106,13 +106,13 @@ final class RawSpanTests: XCTestCase { let sub3 = span.extracting(...) let sub4 = span.extracting(uncheckedBounds: 2...) XCTAssertTrue( - sub1.view(as: UInt8.self)._elementsEqual(sub2.view(as: UInt8.self)) + sub1.unsafeView(as: UInt8.self)._elementsEqual(sub2.unsafeView(as: UInt8.self)) ) XCTAssertTrue( - sub3.view(as: Int8.self)._elementsEqual(span.view(as: Int8.self)) + sub3.unsafeView(as: Int8.self)._elementsEqual(span.unsafeView(as: Int8.self)) ) XCTAssertFalse( - sub4.view(as: Int8.self)._elementsEqual(sub3.view(as: Int8.self)) + sub4.unsafeView(as: Int8.self)._elementsEqual(sub3.unsafeView(as: Int8.self)) ) } } @@ -125,7 +125,7 @@ final class RawSpanTests: XCTestCase { let prefix = span.extracting(0..<8) let beyond = prefix.extracting(uncheckedBounds: 16..<24) XCTAssertEqual(beyond.byteCount, 8) - XCTAssertEqual(beyond.load(as: UInt8.self), 16) + XCTAssertEqual(beyond.unsafeLoad(as: UInt8.self), 16) } } @@ -172,16 +172,16 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let span = RawSpan(unsafeBytes: $0, owner: $0) XCTAssertEqual(span.byteCount, capacity) - XCTAssertEqual(span.extracting(first: 1).load(as: UInt8.self), 0) + XCTAssertEqual(span.extracting(first: 1).unsafeLoad(as: UInt8.self), 0) XCTAssertEqual( - span.extracting(first: capacity).load( + span.extracting(first: capacity).unsafeLoad( fromByteOffset: capacity-1, as: UInt8.self ), UInt8(capacity-1) ) XCTAssertTrue(span.extracting(droppingLast: capacity).isEmpty) XCTAssertEqual( - span.extracting(droppingLast: 1).load( + span.extracting(droppingLast: 1).unsafeLoad( fromByteOffset: capacity-2, as: UInt8.self ), UInt8(capacity-2) @@ -195,11 +195,11 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let span = RawSpan(unsafeBytes: $0, owner: $0) XCTAssertEqual(span.byteCount, capacity) - XCTAssertEqual(span.extracting(last: capacity).load(as: UInt8.self), 0) - XCTAssertEqual(span.extracting(last: capacity-1).load(as: UInt8.self), 1) - XCTAssertEqual(span.extracting(last: 1).load(as: UInt8.self), UInt8(capacity-1)) + XCTAssertEqual(span.extracting(last: capacity).unsafeLoad(as: UInt8.self), 0) + XCTAssertEqual(span.extracting(last: capacity-1).unsafeLoad(as: UInt8.self), 1) + XCTAssertEqual(span.extracting(last: 1).unsafeLoad(as: UInt8.self), UInt8(capacity-1)) XCTAssertTrue(span.extracting(droppingFirst: capacity).isEmpty) - XCTAssertEqual(span.extracting(droppingFirst: 1).load(as: UInt8.self), 1) + XCTAssertEqual(span.extracting(droppingFirst: 1).unsafeLoad(as: UInt8.self), 1) } } From 17194f3e45a70883bab4c832585178a0f65fbbf2 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 27 Jun 2024 16:40:28 -0700 Subject: [PATCH 030/195] Add box and cell --- Package.swift | 1 + Sources/Future/Box.swift | 66 +++++++++++++++++++++++++++++++++++ Sources/Future/CMakeLists.txt | 6 ++-- Sources/Future/Cell.swift | 57 ++++++++++++++++++++++++++++++ Sources/Future/Inout.swift | 15 ++++++++ 5 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 Sources/Future/Box.swift create mode 100644 Sources/Future/Cell.swift diff --git a/Package.swift b/Package.swift index 7d73dd742..846a3e43a 100644 --- a/Package.swift +++ b/Package.swift @@ -56,6 +56,7 @@ let _settings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), .enableExperimentalFeature("BitwiseCopyable"), + .enableExperimentalFeature("RawLayout") // .swiftLanguageVersion(.v5) ] diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift new file mode 100644 index 000000000..1d642f7ca --- /dev/null +++ b/Sources/Future/Box.swift @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +@frozen +public struct Box: ~Copyable { + @usableFromInline + let pointer: UnsafeMutablePointer + + @_alwaysEmitIntoClient + @_transparent + public init(_ value: consuming T) { + pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: value) + } + + @_alwaysEmitIntoClient + @_transparent + public init(_ fromInout: consuming Inout) { + pointer = fromInout.pointer + } + + @_alwaysEmitIntoClient + @inlinable + deinit { + pointer.deinitialize(count: 1) + pointer.deallocate() + } +} + +extension Box where T: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public consuming func consume() -> T { + let result = pointer.move() + pointer.deallocate() + discard self + return result + } + + @_alwaysEmitIntoClient + @_transparent + public consuming func leak() -> dependsOn(immortal) Inout { + Inout(unsafeImmortalAddress: pointer) + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + UnsafePointer(pointer) + } + + @_transparent + nonmutating unsafeMutableAddress { + pointer + } + } +} diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index df87b0c6a..fdd305f74 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -8,10 +8,12 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(Future + "Box.swift" + "Cell.swift" + "ContiguousStorage.swift" "Inout.swift" - "Span.swift" "RawSpan.swift" - "ContiguousStorage.swift" + "Span.swift" ) target_link_libraries(Future PRIVATE diff --git a/Sources/Future/Cell.swift b/Sources/Future/Cell.swift new file mode 100644 index 000000000..5565245cb --- /dev/null +++ b/Sources/Future/Cell.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@frozen +@_rawLayout(like: T, movesAsLike) +public struct Cell: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public var unsafeAddress: UnsafeMutablePointer { + UnsafeMutablePointer(Builtin.addressOfRawLayout(self)) + } + + @_alwaysEmitIntoClient + @_transparent + public init(_ value: consuming T) { + unsafeAddress.initialize(to: value) + } +} + +extension Cell where T: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public func asInout() -> Inout { + Inout(unsafeAddress: unsafeAddress, owner: self) + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + UnsafePointer(unsafeAddress) + } + + @_transparent + nonmutating unsafeMutableAddress { + unsafeAddress + } + } +} + +extension Cell where T: Copyable { + @_alwaysEmitIntoClient + @_transparent + public borrowing func copy() -> T { + unsafeAddress.pointee + } +} diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift index 344a7468e..1a45e002e 100644 --- a/Sources/Future/Inout.swift +++ b/Sources/Future/Inout.swift @@ -48,11 +48,26 @@ public struct Inout: ~Copyable, ~Escapable { ) { pointer = unsafeAddress } + + /// Unsafely initializes an instance of 'Inout' using the given + /// 'unsafeImmortalAddress' as the mutable reference acting as though its + /// lifetime is immortal. + /// + /// - Parameter unsafeImmortalAddress: The address to use to mutably reference + /// an immortal instance of type 'T'. + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeImmortalAddress: UnsafeMutablePointer + ) -> dependsOn(immortal) Self { + pointer = unsafeImmortalAddress + } } extension Inout where T: ~Copyable { /// Dereferences the mutable reference allowing for in-place reads and writes /// to the underlying instance. + @_alwaysEmitIntoClient public subscript() -> T { @_transparent unsafeAddress { From b8191bedee0fb09d0abb316126fc0ace0e68e853 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 27 Jun 2024 16:45:38 -0700 Subject: [PATCH 031/195] Update Cell.swift --- Sources/Future/Cell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Future/Cell.swift b/Sources/Future/Cell.swift index 5565245cb..538333916 100644 --- a/Sources/Future/Cell.swift +++ b/Sources/Future/Cell.swift @@ -30,7 +30,7 @@ public struct Cell: ~Copyable { extension Cell where T: ~Copyable { @_alwaysEmitIntoClient @_transparent - public func asInout() -> Inout { + public borrowing func asInout() -> Inout { Inout(unsafeAddress: unsafeAddress, owner: self) } From 39a05be6e12f984760062c7d226d0e1016f6fbd9 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 28 Jun 2024 20:39:12 -0700 Subject: [PATCH 032/195] Update Box.swift --- Sources/Future/Box.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index 1d642f7ca..fdd433a56 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -48,7 +48,9 @@ extension Box where T: ~Copyable { @_alwaysEmitIntoClient @_transparent public consuming func leak() -> dependsOn(immortal) Inout { - Inout(unsafeImmortalAddress: pointer) + let result = Inout(unsafeImmortalAddress: pointer) + discard self + return result } @_alwaysEmitIntoClient From 9c4d47328e1ad74d95437ba38afefbe51f527598 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 2 Jul 2024 11:24:52 -0700 Subject: [PATCH 033/195] Add box and cell tests --- Sources/Future/Box.swift | 8 ++++++ Sources/Future/Cell.swift | 4 +-- Tests/FutureTests/BoxTests.swift | 43 +++++++++++++++++++++++++++++++ Tests/FutureTests/CellTests.swift | 37 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 Tests/FutureTests/BoxTests.swift create mode 100644 Tests/FutureTests/CellTests.swift diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index fdd433a56..97670a5d7 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -66,3 +66,11 @@ extension Box where T: ~Copyable { } } } + +extension Box where T: Copyable { + @_alwaysEmitIntoClient + @_transparent + public borrowing func copy() -> T { + pointer.pointee + } +} diff --git a/Sources/Future/Cell.swift b/Sources/Future/Cell.swift index 538333916..f712836dd 100644 --- a/Sources/Future/Cell.swift +++ b/Sources/Future/Cell.swift @@ -30,8 +30,8 @@ public struct Cell: ~Copyable { extension Cell where T: ~Copyable { @_alwaysEmitIntoClient @_transparent - public borrowing func asInout() -> Inout { - Inout(unsafeAddress: unsafeAddress, owner: self) + public mutating func asInout() -> Inout { + Inout(unsafeAddress: unsafeAddress, owner: &self) } @_alwaysEmitIntoClient diff --git a/Tests/FutureTests/BoxTests.swift b/Tests/FutureTests/BoxTests.swift new file mode 100644 index 000000000..9f1075e85 --- /dev/null +++ b/Tests/FutureTests/BoxTests.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class FutureBoxTests: XCTestCase { + func test_basic() { + let intOnHeap = Box(0) + + XCTAssertEqual(intOnHeap[], 0) + + intOnHeap[] = 123 + + XCTAssertEqual(intOnHeap[], 123) + + let inoutToIntOnHeap = intOnHeap.leak() + + XCTAssertEqual(inoutToIntOnHeap[], 123) + + inoutToIntOnHeap[] = 321 + + XCTAssertEqual(inoutToIntOnHeap[], 321) + + let intOnHeapAgain = Box(inoutToIntOnHeap) + + XCTAssertEqual(intOnHeapAgain[], 321) + + XCTAssertEqual(intOnHeapAgain.copy(), 321) + + let intInRegister = intOnHeapAgain.consume() + + XCTAssertEqual(intInRegister, 321) + } +} diff --git a/Tests/FutureTests/CellTests.swift b/Tests/FutureTests/CellTests.swift new file mode 100644 index 000000000..7588e1642 --- /dev/null +++ b/Tests/FutureTests/CellTests.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class FutureCellTests: XCTestCase { + struct IntOnStack: ~Copyable { + var value = Cell(0) + } + + func test_basic() { + var myInt = IntOnStack() + + XCTAssertEqual(myInt.value[], 0) + + myInt.value[] = 123 + + XCTAssertEqual(myInt.value[], 123) + + let inoutToIntOnStack = myInt.value.asInout() + + inoutToIntOnStack[] = 321 + + XCTAssertEqual(myInt.value[], 321) + + XCTAssertEqual(myInt.value.copy(), 321) + } +} From 09f9340dcec6744e7238c0d966cad57e302a7419 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 2 Jul 2024 11:27:31 -0700 Subject: [PATCH 034/195] Update RawSpan.swift --- Sources/Future/RawSpan.swift | 186 +++++++++++++++++------------------ 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 72e3a2457..15ab716e0 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -520,96 +520,96 @@ public struct OutOfBoundsError: Error { } } -extension RawSpan { - /// Parse an instance of `T`, advancing `position`. - @inlinable - public func parse( - _ position: inout Int, as t: T.Type = T.self - ) throws(OutOfBoundsError) -> T { - let length = MemoryLayout.size - guard position >= 0 else { - throw OutOfBoundsError(expected: length, has: 0) - } - let end = position &+ length - guard end <= length else { - throw OutOfBoundsError(expected: length, has: byteCount&-position) - } - return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) - } - - /// Parse `numBytes` of data, advancing `position`. - @inlinable - public func parse( - _ position: inout Int, numBytes: some FixedWidthInteger - ) throws (OutOfBoundsError) -> Self { - let length = Int(numBytes) - guard position >= 0 else { - throw OutOfBoundsError(expected: length, has: 0) - } - let end = position &+ length - guard end <= length else { - throw OutOfBoundsError(expected: length, has: byteCount&-position) - } - return extracting(position.. - - /// The current parsing position - public var position: Int - - @inlinable - public init(_ base: RawSpan, in range: Range) { - base.assertValidity(range) - position = 0 - self.base = base - parseRange = range - } - - @inlinable - public init(_ base: RawSpan) { - position = 0 - self.base = base - parseRange = base._byteOffsets - } - - /// Parse an instance of `T` and advance - @inlinable - public mutating func parse( - _ t: T.Type = T.self - ) throws(OutOfBoundsError) -> T { - try base.parse(&position, as: T.self) - } - - /// Parse `numBytes`and advance - @inlinable - public mutating func parse( - numBytes: some FixedWidthInteger - ) throws (OutOfBoundsError) -> RawSpan { - try base.parse(&position, numBytes: numBytes) - } - - /// The bytes that we've parsed so far - @inlinable - public var parsedBytes: RawSpan { base.extracting(.. Cursor { Cursor(self) } - - @inlinable - public func makeCursor(in range: Range) -> Cursor { - Cursor(self, in: range) - } -} +// extension RawSpan { +// /// Parse an instance of `T`, advancing `position`. +// @inlinable +// public func parse( +// _ position: inout Int, as t: T.Type = T.self +// ) throws(OutOfBoundsError) -> T { +// let length = MemoryLayout.size +// guard position >= 0 else { +// throw OutOfBoundsError(expected: length, has: 0) +// } +// let end = position &+ length +// guard end <= length else { +// throw OutOfBoundsError(expected: length, has: byteCount&-position) +// } +// return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) +// } + +// /// Parse `numBytes` of data, advancing `position`. +// @inlinable +// public func parse( +// _ position: inout Int, numBytes: some FixedWidthInteger +// ) throws (OutOfBoundsError) -> Self { +// let length = Int(numBytes) +// guard position >= 0 else { +// throw OutOfBoundsError(expected: length, has: 0) +// } +// let end = position &+ length +// guard end <= length else { +// throw OutOfBoundsError(expected: length, has: byteCount&-position) +// } +// return extracting(position.. + +// /// The current parsing position +// public var position: Int + +// @inlinable +// public init(_ base: RawSpan, in range: Range) { +// base.assertValidity(range) +// position = 0 +// self.base = base +// parseRange = range +// } + +// @inlinable +// public init(_ base: RawSpan) { +// position = 0 +// self.base = base +// parseRange = base._byteOffsets +// } + +// /// Parse an instance of `T` and advance +// @inlinable +// public mutating func parse( +// _ t: T.Type = T.self +// ) throws(OutOfBoundsError) -> T { +// try base.parse(&position, as: T.self) +// } + +// /// Parse `numBytes`and advance +// @inlinable +// public mutating func parse( +// numBytes: some FixedWidthInteger +// ) throws (OutOfBoundsError) -> RawSpan { +// try base.parse(&position, numBytes: numBytes) +// } + +// /// The bytes that we've parsed so far +// @inlinable +// public var parsedBytes: RawSpan { base.extracting(.. Cursor { Cursor(self) } + +// @inlinable +// public func makeCursor(in range: Range) -> Cursor { +// Cursor(self, in: range) +// } +// } From cb6b7c9a55c85e432433e30af6ec8ee2aaf95964 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 28 Jun 2024 18:59:01 -0700 Subject: [PATCH 035/195] =?UTF-8?q?remove=20some=20outdated=20mentions=20o?= =?UTF-8?q?f=20=E2=80=9Cview=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Future/RawSpan.swift | 2 +- Sources/Future/Span.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 15ab716e0..25d7574ee 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -64,7 +64,7 @@ extension RawSpan { /// /// - Parameters: /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the view. + /// - byteCount: the number of initialized bytes in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. @inlinable @inline(__always) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index c6fa8589f..67ff7ae5f 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -71,7 +71,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Parameters: /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the view. + /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. public init( @@ -118,7 +118,7 @@ extension Span where Element: BitwiseCopyable { /// /// - Parameters: /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the view. + /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. public init( @@ -170,7 +170,7 @@ extension Span where Element: BitwiseCopyable { /// /// - Parameters: /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the view. + /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. public init( From fefc900b92e9ca6530c503156b336770fb2bdd69 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 30 Jun 2024 02:30:00 -0700 Subject: [PATCH 036/195] changed `uncheckedBounds` to just `unchecked` --- Sources/Future/RawSpan.swift | 8 ++++---- Sources/Future/Span.swift | 6 +++--- Tests/FutureTests/RawSpanTests.swift | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 25d7574ee..efeac940f 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -184,7 +184,7 @@ extension RawSpan { @inlinable @inline(__always) public func extracting(_ bounds: Range) -> Self { assertValidity(bounds) - return extracting(uncheckedBounds: bounds) + return extracting(unchecked: bounds) } /// Constructs a new span over the bytes within the supplied range of @@ -203,7 +203,7 @@ extension RawSpan { /// /// - Complexity: O(1) @inlinable @inline(__always) - public func extracting(uncheckedBounds bounds: Range) -> Self { + public func extracting(unchecked bounds: Range) -> Self { RawSpan( _unchecked: _start.advanced(by: bounds.lowerBound), byteCount: bounds.count, @@ -246,9 +246,9 @@ extension RawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient public func extracting( - uncheckedBounds bounds: some RangeExpression + unchecked bounds: some RangeExpression ) -> Self { - extracting(uncheckedBounds: bounds.relative(to: _byteOffsets)) + extracting(unchecked: bounds.relative(to: _byteOffsets)) } /// Constructs a new span over all the bytes of this span. diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 67ff7ae5f..b7859ddda 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -423,7 +423,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable @inline(__always) public func extracting(_ bounds: Range) -> Self { assertValidity(bounds) - return extracting(uncheckedBounds: bounds) + return extracting(unchecked: bounds) } /// Constructs a new span over the items within the supplied range of @@ -442,7 +442,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Complexity: O(1) @inlinable @inline(__always) - public func extracting(uncheckedBounds bounds: Range) -> Self { + public func extracting(unchecked bounds: Range) -> Self { Span( _unchecked: _start.advanced(by: bounds.lowerBound), count: bounds.count, @@ -487,7 +487,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { - extracting(uncheckedBounds: bounds.relative(to: _indices)) + extracting(unchecked: bounds.relative(to: _indices)) } /// Constructs a new span over all the items of this span. diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index f4c2615b6..4d0a17eb4 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -104,7 +104,7 @@ final class RawSpanTests: XCTestCase { let sub1 = span.extracting(0..<2) let sub2 = span.extracting(..<2) let sub3 = span.extracting(...) - let sub4 = span.extracting(uncheckedBounds: 2...) + let sub4 = span.extracting(unchecked: 2...) XCTAssertTrue( sub1.unsafeView(as: UInt8.self)._elementsEqual(sub2.unsafeView(as: UInt8.self)) ) @@ -123,7 +123,7 @@ final class RawSpanTests: XCTestCase { b.withUnsafeBytes { let span = RawSpan(unsafeBytes: $0, owner: $0) let prefix = span.extracting(0..<8) - let beyond = prefix.extracting(uncheckedBounds: 16..<24) + let beyond = prefix.extracting(unchecked: 16..<24) XCTAssertEqual(beyond.byteCount, 8) XCTAssertEqual(beyond.unsafeLoad(as: UInt8.self), 16) } From 5ccce50e287daa8acae9e2518fec5ee3cf110ade Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 30 Jun 2024 02:32:07 -0700 Subject: [PATCH 037/195] repeatedly add a missing doc-comment paragraph --- Sources/Future/RawSpan.swift | 16 ++++++++++++++++ Sources/Future/Span.swift | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index efeac940f..14eb7d966 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -442,6 +442,10 @@ extension RawSpan { /// If the maximum length exceeds the length of this span, /// the result contains all the bytes. /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter maxLength: The maximum number of bytes to return. /// `maxLength` must be greater than or equal to zero. /// - Returns: A span with at most `maxLength` bytes. @@ -458,6 +462,10 @@ extension RawSpan { /// If the number of elements to drop exceeds the number of elements in /// the span, the result is an empty span. /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter k: The number of bytes to drop off the end of /// the span. `k` must be greater than or equal to zero. /// - Returns: A span leaving off the specified number of bytes at the end. @@ -475,6 +483,10 @@ extension RawSpan { /// If the maximum length exceeds the length of this span, /// the result contains all the bytes. /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter maxLength: The maximum number of bytes to return. /// `maxLength` must be greater than or equal to zero. /// - Returns: A span with at most `maxLength` bytes. @@ -492,6 +504,10 @@ extension RawSpan { /// If the number of elements to drop exceeds the number of bytes in /// the span, the result is an empty span. /// + /// The returned span's first byte is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter k: The number of bytes to drop from the beginning of /// the span. `k` must be greater than or equal to zero. /// - Returns: A span starting after the specified number of bytes. diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index b7859ddda..7ea060e0a 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -605,6 +605,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// If the maximum length exceeds the length of this span, /// the result contains all the elements. /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter maxLength: The maximum number of elements to return. /// `maxLength` must be greater than or equal to zero. /// - Returns: A span with at most `maxLength` elements. @@ -621,6 +625,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// If the number of elements to drop exceeds the number of elements in /// the span, the result is an empty span. /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter k: The number of elements to drop off the end of /// the span. `k` must be greater than or equal to zero. /// - Returns: A span leaving off the specified number of elements at the end. @@ -638,6 +646,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// If the maximum length exceeds the length of this span, /// the result contains all the elements. /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter maxLength: The maximum number of elements to return. /// `maxLength` must be greater than or equal to zero. /// - Returns: A span with at most `maxLength` elements. @@ -655,6 +667,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// If the number of elements to drop exceeds the number of elements in /// the span, the result is an empty span. /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// /// - Parameter k: The number of elements to drop from the beginning of /// the span. `k` must be greater than or equal to zero. /// - Returns: A span starting after the specified number of elements. From 105afd86fbc2a40752f4d522f7168c4f5c68f551 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 30 Jun 2024 02:40:25 -0700 Subject: [PATCH 038/195] fix doc-comments --- Sources/Future/RawSpan.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 14eb7d966..34dc23c0c 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -63,7 +63,7 @@ extension RawSpan { /// meaning that as long as `owner` is alive the memory will remain valid. /// /// - Parameters: - /// - pointer: a pointer to the first initialized element. + /// - pointer: a pointer to the first initialized byte. /// - byteCount: the number of initialized bytes in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. @@ -302,7 +302,7 @@ extension RawSpan { /// to instances of `T` must be surjective. /// /// This is an unsafe operation. Failure to meet the preconditions - /// above may produce an invalid value of `T`. + /// above may produce invalid values of `T`. /// /// - Parameters: /// - type: The type as which to view the bytes of this span. From 6b11eeb4dca40243225df5f8004cd2d9c8ae3806 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 9 Jul 2024 17:11:15 -0700 Subject: [PATCH 039/195] un-comment parsing utilities, mark compiler issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These compiled in the development nightly toolchain of 6/13, but didn’t anymore in the development nightly of 7/1. The regression is related to typed throws. --- Sources/Future/RawSpan.swift | 190 ++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 93 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 34dc23c0c..9294450db 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -536,96 +536,100 @@ public struct OutOfBoundsError: Error { } } -// extension RawSpan { -// /// Parse an instance of `T`, advancing `position`. -// @inlinable -// public func parse( -// _ position: inout Int, as t: T.Type = T.self -// ) throws(OutOfBoundsError) -> T { -// let length = MemoryLayout.size -// guard position >= 0 else { -// throw OutOfBoundsError(expected: length, has: 0) -// } -// let end = position &+ length -// guard end <= length else { -// throw OutOfBoundsError(expected: length, has: byteCount&-position) -// } -// return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) -// } - -// /// Parse `numBytes` of data, advancing `position`. -// @inlinable -// public func parse( -// _ position: inout Int, numBytes: some FixedWidthInteger -// ) throws (OutOfBoundsError) -> Self { -// let length = Int(numBytes) -// guard position >= 0 else { -// throw OutOfBoundsError(expected: length, has: 0) -// } -// let end = position &+ length -// guard end <= length else { -// throw OutOfBoundsError(expected: length, has: byteCount&-position) -// } -// return extracting(position.. - -// /// The current parsing position -// public var position: Int - -// @inlinable -// public init(_ base: RawSpan, in range: Range) { -// base.assertValidity(range) -// position = 0 -// self.base = base -// parseRange = range -// } - -// @inlinable -// public init(_ base: RawSpan) { -// position = 0 -// self.base = base -// parseRange = base._byteOffsets -// } - -// /// Parse an instance of `T` and advance -// @inlinable -// public mutating func parse( -// _ t: T.Type = T.self -// ) throws(OutOfBoundsError) -> T { -// try base.parse(&position, as: T.self) -// } - -// /// Parse `numBytes`and advance -// @inlinable -// public mutating func parse( -// numBytes: some FixedWidthInteger -// ) throws (OutOfBoundsError) -> RawSpan { -// try base.parse(&position, numBytes: numBytes) -// } - -// /// The bytes that we've parsed so far -// @inlinable -// public var parsedBytes: RawSpan { base.extracting(.. Cursor { Cursor(self) } - -// @inlinable -// public func makeCursor(in range: Range) -> Cursor { -// Cursor(self, in: range) -// } -// } + extension RawSpan { +#if false // rdar://130971168 + /// Parse an instance of `T`, advancing `position`. + @inlinable + public func parse( + _ position: inout Int, as t: T.Type = T.self + ) throws(OutOfBoundsError) -> T { + let length = MemoryLayout.size + guard position >= 0 else { + throw OutOfBoundsError(expected: length, has: 0) + } + let end = position &+ length + guard end <= length else { + throw OutOfBoundsError(expected: length, has: byteCount&-position) + } + return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) + } +#endif + + /// Parse `numBytes` of data, advancing `position`. + @inlinable + public func parse( + _ position: inout Int, numBytes: some FixedWidthInteger + ) throws (OutOfBoundsError) -> Self { + let length = Int(numBytes) + guard position >= 0 else { + throw OutOfBoundsError(expected: length, has: 0) + } + let end = position &+ length + guard end <= length else { + throw OutOfBoundsError(expected: length, has: byteCount&-position) + } + return extracting(position.. + + /// The current parsing position + public var position: Int + + @inlinable + public init(_ base: RawSpan, in range: Range) { + base.assertValidity(range) + position = 0 + self.base = base + parseRange = range + } + + @inlinable + public init(_ base: RawSpan) { + position = 0 + self.base = base + parseRange = base._byteOffsets + } + +#if false // rdar://130971168 + /// Parse an instance of `T` and advance + @inlinable + public mutating func parse( + _ t: T.Type = T.self + ) throws(OutOfBoundsError) -> T { + try base.parse(&position, as: T.self) + } + + /// Parse `numBytes`and advance + @inlinable + public mutating func parse( + numBytes: some FixedWidthInteger + ) throws (OutOfBoundsError) -> RawSpan { + try base.parse(&position, numBytes: numBytes) + } +#endif + + /// The bytes that we've parsed so far + @inlinable + public var parsedBytes: RawSpan { base.extracting(.. Cursor { Cursor(self) } + + @inlinable + public func makeCursor(in range: Range) -> Cursor { + Cursor(self, in: range) + } + } From f5e16d3f0202bd9dee433f4f7cd7faa7aa2888ca Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 11 Jul 2024 15:16:05 -0700 Subject: [PATCH 040/195] fix indentation --- Sources/Future/RawSpan.swift | 174 +++++++++++++++++------------------ 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 9294450db..2e9eee9f4 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -536,100 +536,100 @@ public struct OutOfBoundsError: Error { } } - extension RawSpan { +extension RawSpan { #if false // rdar://130971168 - /// Parse an instance of `T`, advancing `position`. - @inlinable - public func parse( - _ position: inout Int, as t: T.Type = T.self - ) throws(OutOfBoundsError) -> T { - let length = MemoryLayout.size - guard position >= 0 else { - throw OutOfBoundsError(expected: length, has: 0) - } - let end = position &+ length - guard end <= length else { - throw OutOfBoundsError(expected: length, has: byteCount&-position) - } - return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) - } + /// Parse an instance of `T`, advancing `position`. + @inlinable + public func parse( + _ position: inout Int, as t: T.Type = T.self + ) throws(OutOfBoundsError) -> T { + let length = MemoryLayout.size + guard position >= 0 else { + throw OutOfBoundsError(expected: length, has: 0) + } + let end = position &+ length + guard end <= length else { + throw OutOfBoundsError(expected: length, has: byteCount&-position) + } + return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) + } #endif - /// Parse `numBytes` of data, advancing `position`. - @inlinable - public func parse( - _ position: inout Int, numBytes: some FixedWidthInteger - ) throws (OutOfBoundsError) -> Self { - let length = Int(numBytes) - guard position >= 0 else { - throw OutOfBoundsError(expected: length, has: 0) - } - let end = position &+ length - guard end <= length else { - throw OutOfBoundsError(expected: length, has: byteCount&-position) - } - return extracting(position.. - - /// The current parsing position - public var position: Int - - @inlinable - public init(_ base: RawSpan, in range: Range) { - base.assertValidity(range) - position = 0 - self.base = base - parseRange = range - } - - @inlinable - public init(_ base: RawSpan) { - position = 0 - self.base = base - parseRange = base._byteOffsets - } + /// Parse `numBytes` of data, advancing `position`. + @inlinable + public func parse( + _ position: inout Int, numBytes: some FixedWidthInteger + ) throws (OutOfBoundsError) -> Self { + let length = Int(numBytes) + guard position >= 0 else { + throw OutOfBoundsError(expected: length, has: 0) + } + let end = position &+ length + guard end <= length else { + throw OutOfBoundsError(expected: length, has: byteCount&-position) + } + return extracting(position.. + + /// The current parsing position + public var position: Int + + @inlinable + public init(_ base: RawSpan, in range: Range) { + base.assertValidity(range) + position = 0 + self.base = base + parseRange = range + } + + @inlinable + public init(_ base: RawSpan) { + position = 0 + self.base = base + parseRange = base._byteOffsets + } #if false // rdar://130971168 - /// Parse an instance of `T` and advance - @inlinable - public mutating func parse( - _ t: T.Type = T.self - ) throws(OutOfBoundsError) -> T { - try base.parse(&position, as: T.self) - } - - /// Parse `numBytes`and advance - @inlinable - public mutating func parse( - numBytes: some FixedWidthInteger - ) throws (OutOfBoundsError) -> RawSpan { - try base.parse(&position, numBytes: numBytes) - } + /// Parse an instance of `T` and advance + @inlinable + public mutating func parse( + _ t: T.Type = T.self + ) throws(OutOfBoundsError) -> T { + try base.parse(&position, as: T.self) + } + + /// Parse `numBytes`and advance + @inlinable + public mutating func parse( + numBytes: some FixedWidthInteger + ) throws (OutOfBoundsError) -> RawSpan { + try base.parse(&position, numBytes: numBytes) + } #endif - /// The bytes that we've parsed so far - @inlinable - public var parsedBytes: RawSpan { base.extracting(.. Cursor { Cursor(self) } + @inlinable + public func makeCursor() -> Cursor { Cursor(self) } - @inlinable - public func makeCursor(in range: Range) -> Cursor { - Cursor(self, in: range) - } - } + @inlinable + public func makeCursor(in range: Range) -> Cursor { + Cursor(self, in: range) + } +} From d352247af39781eab012534441b3e3ab18f75b85 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 12 Jul 2024 13:43:54 -0700 Subject: [PATCH 041/195] remove workaround for rdar://130971168 --- Sources/Future/RawSpan.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 2e9eee9f4..1ac021ec7 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -537,7 +537,6 @@ public struct OutOfBoundsError: Error { } extension RawSpan { -#if false // rdar://130971168 /// Parse an instance of `T`, advancing `position`. @inlinable public func parse( @@ -553,7 +552,6 @@ extension RawSpan { } return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) } -#endif /// Parse `numBytes` of data, advancing `position`. @inlinable @@ -598,7 +596,6 @@ extension RawSpan { parseRange = base._byteOffsets } -#if false // rdar://130971168 /// Parse an instance of `T` and advance @inlinable public mutating func parse( @@ -614,7 +611,6 @@ extension RawSpan { ) throws (OutOfBoundsError) -> RawSpan { try base.parse(&position, numBytes: numBytes) } -#endif /// The bytes that we've parsed so far @inlinable From 9c0d9af55aec7492b14b117c9fad13145f32dbd3 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 12 Jul 2024 16:03:47 -0700 Subject: [PATCH 042/195] fix inlinability annotations on Span and RawSpan --- Sources/Future/RawSpan.swift | 9 +++++++++ Sources/Future/Span.swift | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 1ac021ec7..9226fe0a3 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -307,6 +307,7 @@ extension RawSpan { /// - Parameters: /// - type: The type as which to view the bytes of this span. /// - Returns: A typed span viewing these bytes as instances of `T`. + @_alwaysEmitIntoClient public func unsafeView( as type: T.Type ) -> Span { @@ -334,6 +335,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. + @inlinable @inline(__always) public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type ) -> T { @@ -361,6 +363,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. + @inlinable @inline(__always) public func unsafeLoad( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { @@ -383,6 +386,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. + @_alwaysEmitIntoClient public func unsafeLoadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { @@ -409,6 +413,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. + @_alwaysEmitIntoClient public func unsafeLoadUnaligned( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { @@ -451,6 +456,7 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) + @inlinable public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < byteCount ? maxLength : byteCount @@ -471,6 +477,7 @@ extension RawSpan { /// - Returns: A span leaving off the specified number of bytes at the end. /// /// - Complexity: O(1) + @inlinable public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < byteCount ? byteCount&-k : 0 @@ -492,6 +499,7 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) + @inlinable public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < byteCount ? maxLength : byteCount @@ -513,6 +521,7 @@ extension RawSpan { /// - Returns: A span starting after the specified number of bytes. /// /// - Complexity: O(1) + @inlinable public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < byteCount ? k : byteCount diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 7ea060e0a..d5c2fe8a6 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -35,8 +35,8 @@ extension Span: Sendable {} extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { - @inline(__always) - fileprivate var isAligned: Bool { + @usableFromInline @inline(__always) + var isAligned: Bool { (Int(bitPattern: self) & (MemoryLayout.alignment-1)) == 0 } } @@ -53,6 +53,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @inlinable @inline(__always) public init( unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner @@ -74,6 +75,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @inlinable @inline(__always) public init( unsafeStart start: UnsafePointer, count: Int, @@ -100,6 +102,7 @@ extension Span where Element: BitwiseCopyable { /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @inlinable @inline(__always) public init( unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner @@ -121,6 +124,7 @@ extension Span where Element: BitwiseCopyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @inlinable @inline(__always) public init( unsafeStart start: UnsafePointer, count: Int, @@ -145,6 +149,7 @@ extension Span where Element: BitwiseCopyable { /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @inlinable @inline(__always) public init( unsafeBytes buffer: UnsafeRawBufferPointer, owner: borrowing Owner @@ -173,6 +178,7 @@ extension Span where Element: BitwiseCopyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @inlinable @inline(__always) public init( unsafeStart pointer: UnsafeRawPointer, byteCount: Int, @@ -614,6 +620,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) + @inlinable public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let nc = maxLength < count ? maxLength : count @@ -634,6 +641,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) + @inlinable public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let nc = k < count ? count&-k : 0 @@ -655,6 +663,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) + @inlinable public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let nc = maxLength < count ? maxLength : count @@ -676,6 +685,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) + @inlinable public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = k < count ? k : count From b7aab16cce87a31091f3d8ce706fc57bc815feb6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 14 Jul 2024 04:22:12 -0700 Subject: [PATCH 043/195] improve internal representation of Span --- Sources/Future/Span.swift | 103 +++++++++++++++++------------- Tests/FutureTests/SpanTests.swift | 30 ++++++++- 2 files changed, 88 insertions(+), 45 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index d5c2fe8a6..736d36211 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -16,17 +16,32 @@ import Builtin // contains initialized instances of `Element`. @frozen public struct Span: Copyable, ~Escapable { - @usableFromInline let _start: UnsafePointer - @usableFromInline let _count: Int + @usableFromInline let _buffer: UnsafeBufferPointer + + @usableFromInline @inline(__always) + var _pointer: UnsafePointer? { _buffer.baseAddress } + + @usableFromInline @inline(__always) + var _start: UnsafePointer { _pointer.unsafelyUnwrapped } + + @usableFromInline @inline(__always) + var _count: Int { _buffer.count } @inlinable @inline(__always) internal init( - _unchecked start: UnsafePointer, + _unchecked elements: UnsafeBufferPointer, + owner: borrowing Owner + ) { + _buffer = elements + } + + @inlinable @inline(__always) + internal init( + _unchecked start: UnsafePointer?, count: Int, owner: borrowing Owner ) { - self._start = start - self._count = count + self.init(_unchecked: .init(start: start, count: count), owner: owner) } } @@ -43,7 +58,6 @@ extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { extension Span where Element: ~Copyable /*& ~Escapable*/ { - //FIXME: make properly non-failable /// Unsafely create a `Span` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -58,10 +72,11 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner ) { - guard let baseAddress = buffer.baseAddress else { - fatalError("Span requires a non-nil base address") - } - self.init(unsafeStart: baseAddress, count: buffer.count, owner: owner) + precondition( + buffer.count == 0 || buffer.baseAddress.unsafelyUnwrapped.isAligned, + "baseAddress must be properly aligned for accessing \(Element.self)" + ) + self.init(_unchecked: buffer, owner: owner) } /// Unsafely create a `Span` over initialized memory. @@ -92,7 +107,6 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { extension Span where Element: BitwiseCopyable { - //FIXME: make properly non-failable /// Unsafely create a `Span` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -107,10 +121,7 @@ extension Span where Element: BitwiseCopyable { unsafeElements buffer: UnsafeBufferPointer, owner: borrowing Owner ) { - guard let baseAddress = buffer.baseAddress else { - fatalError("Span requires a non-nil base address") - } - self.init(unsafeStart: baseAddress, count: buffer.count, owner: owner) + self.init(_unchecked: buffer, owner: owner) } /// Unsafely create a `Span` over initialized memory. @@ -134,7 +145,6 @@ extension Span where Element: BitwiseCopyable { self.init(_unchecked: start, count: count, owner: owner) } - //FIXME: make properly non-failable /// Unsafely create a `Span` over initialized memory. /// /// The memory in `unsafeBytes` must be owned by the instance `owner` @@ -154,15 +164,13 @@ extension Span where Element: BitwiseCopyable { unsafeBytes buffer: UnsafeRawBufferPointer, owner: borrowing Owner ) { - guard let baseAddress = buffer.baseAddress else { - fatalError("Span requires a non-nil base address") - } - let (c, s) = (buffer.count, MemoryLayout.stride) - let (q, r) = c.quotientAndRemainder(dividingBy: s) - precondition(r == 0) + let (byteCount, stride) = (buffer.count, MemoryLayout.stride) + assert(byteCount >= 0, "Count must not be negative") + let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(remainder == 0) self.init( - unsafeStart: baseAddress.assumingMemoryBound(to: Element.self), - count: q, + _unchecked: buffer.baseAddress?.assumingMemoryBound(to: Element.self), + count: count, owner: owner ) } @@ -184,12 +192,13 @@ extension Span where Element: BitwiseCopyable { byteCount: Int, owner: borrowing Owner ) { + precondition(byteCount >= 0, "Count must not be negative") let stride = MemoryLayout.stride - let (q, r) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(r == 0) + let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(remainder == 0) self.init( - unsafeStart: pointer.assumingMemoryBound(to: Element.self), - count: q, + _unchecked: pointer.assumingMemoryBound(to: Element.self), + count: count, owner: owner ) } @@ -405,7 +414,8 @@ extension Span where Element: BitwiseCopyable { @inlinable @inline(__always) public subscript(unchecked position: Int) -> Element { get { - UnsafeRawPointer(_start + position).loadUnaligned(as: Element.self) + let address = UnsafeRawPointer(_start.advanced(by: position)) + return address.loadUnaligned(as: Element.self) } } } @@ -450,7 +460,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable @inline(__always) public func extracting(unchecked bounds: Range) -> Self { Span( - _unchecked: _start.advanced(by: bounds.lowerBound), + _unchecked: _pointer?.advanced(by: bounds.lowerBound), count: bounds.count, owner: self ) @@ -589,15 +599,20 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable @inline(__always) public func contains(_ span: borrowing Self) -> Bool { - _start <= span._start && - span._start.advanced(by: span._count) <= _start.advanced(by: _count) + if span._count > _count { return false } + if _count == 0 || span._count == 0 { return true } + if _start > span._start { return false } + return span._start.advanced(by: span._count) <= _start.advanced(by: _count) } @inlinable @inline(__always) public func offsets(of span: borrowing Self) -> Range { precondition(contains(span)) - let s = _start.distance(to: span._start) - let e = s + span._count + var (s, e) = (0, 0) + if _pointer != nil && span._pointer != nil { + s = _start.distance(to: span._start) + e = s + span._count + } return Range(uncheckedBounds: (s, e)) } } @@ -623,8 +638,8 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") - let nc = maxLength < count ? maxLength : count - return Self(_unchecked: _start, count: nc, owner: self) + let newCount = min(maxLength, count) + return Self(_unchecked: _pointer, count: newCount, owner: self) } /// Returns a span over all but the given number of trailing elements. @@ -644,8 +659,8 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let nc = k < count ? count&-k : 0 - return Self(_unchecked: _start, count: nc, owner: self) + let droppedCount = min(k, count) + return Self(_unchecked: _pointer, count: count&-droppedCount, owner: self) } /// Returns a span containing the final elements of the span, @@ -666,9 +681,9 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") - let nc = maxLength < count ? maxLength : count - let newStart = _start.advanced(by: count&-nc) - return Self(_unchecked: newStart, count: nc, owner: self) + let newCount = min(maxLength, count) + let newStart = _pointer?.advanced(by: count&-newCount) + return Self(_unchecked: newStart, count: newCount, owner: self) } /// Returns a span over all but the given number of initial elements. @@ -688,8 +703,8 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let dc = k < count ? k : count - let newStart = _start.advanced(by: dc) - return Self(_unchecked: newStart, count: count&-dc, owner: self) + let droppedCount = min(k, count) + let newStart = _pointer?.advanced(by: droppedCount) + return Self(_unchecked: newStart, count: count&-droppedCount, owner: self) } } diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index f0739465f..42b6541f4 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -219,6 +219,14 @@ final class SpanTests: XCTestCase { XCTAssertEqual(span.extracting(droppingLast: capacity).last, nil) XCTAssertEqual(span.extracting(droppingLast: 1).last, capacity-2) } + + do { + let b = UnsafeBufferPointer(start: nil, count: 0) + let span = Span(unsafeElements: b, owner: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span.extracting(first: 1).count, b.count) + XCTAssertEqual(span.extracting(droppingLast: 1).count, b.count) + } } func testSuffix() { @@ -233,7 +241,14 @@ final class SpanTests: XCTestCase { XCTAssertEqual(span.extracting(droppingFirst: capacity).first, nil) XCTAssertEqual(span.extracting(droppingFirst: 1).first, 1) } - } + + do { + let b = UnsafeBufferPointer(start: nil, count: 0) + let span = Span(unsafeElements: b, owner: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span.extracting(last: 1).count, b.count) + XCTAssertEqual(span.extracting(droppingFirst: 1).count, b.count) + } } public func testWithUnsafeBytes() { let capacity: UInt8 = 64 @@ -312,6 +327,9 @@ final class SpanTests: XCTestCase { let fakeSpan = Span( unsafeStart: b.baseAddress!.advanced(by: 8), count: 8, owner: b ) + let nilSpan = Span( + unsafeElements: .init(start: nil, count: 0), owner: b + ) XCTAssertTrue(span.contains(subSpan)) XCTAssertFalse(subSpan.contains(span)) @@ -319,6 +337,9 @@ final class SpanTests: XCTestCase { XCTAssertFalse(emptySpan.contains(span)) XCTAssertFalse(span.contains(fakeSpan)) XCTAssertFalse(fakeSpan.contains(span)) + XCTAssertTrue(span.contains(nilSpan)) + XCTAssertTrue(fakeSpan.contains(nilSpan)) + XCTAssertTrue(nilSpan.contains(emptySpan)) } func testOffsets() { @@ -329,11 +350,18 @@ final class SpanTests: XCTestCase { let span = Span(unsafeElements: .init(b), owner: b) let subSpan = span.extracting(last: 2) let emptySpan = span.extracting(first: 0) + let nilSpan = Span( + unsafeElements: .init(start: nil, count: 0), owner: b + ) var bounds: Range bounds = span.offsets(of: subSpan) XCTAssertEqual(bounds, span._indices.suffix(2)) bounds = span.offsets(of: emptySpan) XCTAssertEqual(bounds, span._indices.prefix(0)) + bounds = span.offsets(of: nilSpan) + XCTAssertEqual(bounds, 0..<0) + bounds = nilSpan.offsets(of: emptySpan) + XCTAssertEqual(bounds, 0..<0) } } From 6f4d9d856088be4b13c96717e45e94f902bb3fbd Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 14 Jul 2024 03:03:29 -0700 Subject: [PATCH 044/195] improve internal representation of RawSpan --- Sources/Future/RawSpan.swift | 53 ++++++++++++++++------------ Tests/FutureTests/RawSpanTests.swift | 25 +++++++++++++ 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 9226fe0a3..8f5220767 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -16,17 +16,21 @@ import Builtin // of unspecified type. @frozen public struct RawSpan: Copyable, ~Escapable { - @usableFromInline let _start: UnsafeRawPointer + @usableFromInline let _pointer: UnsafeRawPointer? + + @usableFromInline @inline(__always) + var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } + @usableFromInline let _count: Int @inlinable @inline(__always) internal init( - _unchecked start: UnsafeRawPointer, + _unchecked start: UnsafeRawPointer?, byteCount: Int, owner: borrowing Owner ) { - self._start = start - self._count = byteCount + _pointer = start + _count = byteCount } } @@ -35,7 +39,6 @@ extension RawSpan: Sendable {} extension RawSpan { - //FIXME: make properly non-failable /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -50,10 +53,9 @@ extension RawSpan { unsafeBytes buffer: UnsafeRawBufferPointer, owner: borrowing Owner ) { - guard let baseAddress = buffer.baseAddress else { - fatalError("RawSpan requires a non-nil base address") - } - self.init(_unchecked: baseAddress, byteCount: buffer.count, owner: owner) + self.init( + _unchecked: buffer.baseAddress, byteCount: buffer.count, owner: owner + ) } /// Unsafely create a `RawSpan` over initialized memory. @@ -205,7 +207,7 @@ extension RawSpan { @inlinable @inline(__always) public func extracting(unchecked bounds: Range) -> Self { RawSpan( - _unchecked: _start.advanced(by: bounds.lowerBound), + _unchecked: _pointer?.advanced(by: bounds.lowerBound), byteCount: bounds.count, owner: self ) @@ -425,15 +427,20 @@ extension RawSpan { @inlinable @inline(__always) public func contains(_ span: borrowing Self) -> Bool { - _start <= span._start && - span._start.advanced(by: span._count) <= _start.advanced(by: _count) + if span._count > _count { return false } + if _count == 0 || span._count == 0 { return true } + if _start > span._start { return false } + return span._start.advanced(by: span._count) <= _start.advanced(by: _count) } @inlinable @inline(__always) public func offsets(of span: borrowing Self) -> Range { precondition(contains(span)) - let s = _start.distance(to: span._start) - let e = s + span._count + var (s, e) = (0, 0) + if _pointer != nil && span._pointer != nil { + s = _start.distance(to: span._start) + e = s + span._count + } return Range(uncheckedBounds: (s, e)) } } @@ -459,8 +466,8 @@ extension RawSpan { @inlinable public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") - let nc = maxLength < byteCount ? maxLength : byteCount - return Self(_unchecked: _start, byteCount: nc, owner: self) + let newCount = min(maxLength, byteCount) + return Self(_unchecked: _pointer, byteCount: newCount, owner: self) } /// Returns a span over all but the given number of trailing bytes. @@ -480,8 +487,8 @@ extension RawSpan { @inlinable public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let nc = k < byteCount ? byteCount&-k : 0 - return Self(_unchecked: _start, byteCount: nc, owner: self) + let dc = min(k, byteCount) + return Self(_unchecked: _pointer, byteCount: byteCount&-dc, owner: self) } /// Returns a span containing the trailing bytes of the span, @@ -502,9 +509,9 @@ extension RawSpan { @inlinable public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") - let nc = maxLength < byteCount ? maxLength : byteCount - let newStart = _start.advanced(by: byteCount&-nc) - return Self(_unchecked: newStart, byteCount: nc, owner: self) + let newCount = min(maxLength, byteCount) + let newStart = _pointer?.advanced(by: byteCount&-newCount) + return Self(_unchecked: newStart, byteCount: newCount, owner: self) } /// Returns a span over all but the given number of initial bytes. @@ -524,8 +531,8 @@ extension RawSpan { @inlinable public func extracting(droppingFirst k: Int = 1) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let dc = k < byteCount ? k : byteCount - let newStart = _start.advanced(by: dc) + let dc = min(k, byteCount) + let newStart = _pointer?.advanced(by: dc) return Self(_unchecked: newStart, byteCount: byteCount&-dc, owner: self) } } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 4d0a17eb4..70cd9b5c1 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -187,6 +187,14 @@ final class RawSpanTests: XCTestCase { UInt8(capacity-2) ) } + + do { + let b = UnsafeRawBufferPointer(start: nil, count: 0) + let span = RawSpan(unsafeBytes: b, owner: b) + XCTAssertEqual(span.byteCount, b.count) + XCTAssertEqual(span.extracting(first: 1).byteCount, b.count) + XCTAssertEqual(span.extracting(droppingLast: 1).byteCount, b.count) + } } func testSuffix() { @@ -201,6 +209,14 @@ final class RawSpanTests: XCTestCase { XCTAssertTrue(span.extracting(droppingFirst: capacity).isEmpty) XCTAssertEqual(span.extracting(droppingFirst: 1).unsafeLoad(as: UInt8.self), 1) } + + do { + let b = UnsafeRawBufferPointer(start: nil, count: 0) + let span = RawSpan(unsafeBytes: b, owner: b) + XCTAssertEqual(span.byteCount, b.count) + XCTAssertEqual(span.extracting(last: 1).byteCount, b.count) + XCTAssertEqual(span.extracting(droppingFirst: 1).byteCount, b.count) + } } func testBoundsChecking() { @@ -223,6 +239,7 @@ final class RawSpanTests: XCTestCase { let fakeSpan = RawSpan( unsafeStart: b.baseAddress!.advanced(by: 8), byteCount: 8, owner: b ) + let nilSpan = RawSpan(unsafeBytes: .init(start: nil, count: 0), owner: b) XCTAssertTrue(span.contains(subSpan)) XCTAssertFalse(subSpan.contains(span)) @@ -230,6 +247,9 @@ final class RawSpanTests: XCTestCase { XCTAssertFalse(emptySpan.contains(span)) XCTAssertFalse(span.contains(fakeSpan)) XCTAssertFalse(fakeSpan.contains(span)) + XCTAssertTrue(span.contains(nilSpan)) + XCTAssertTrue(fakeSpan.contains(nilSpan)) + XCTAssertTrue(nilSpan.contains(emptySpan)) } func testOffsets() { @@ -239,11 +259,16 @@ final class RawSpanTests: XCTestCase { let span = RawSpan(unsafeBytes: .init(b), owner: b) let subSpan = span.extracting(last: 2) let emptySpan = span.extracting(first: 0) + let nilSpan = RawSpan(unsafeBytes: .init(start: nil, count: 0), owner: b) var bounds: Range bounds = span.offsets(of: subSpan) XCTAssertEqual(bounds, span._byteOffsets.suffix(2)) bounds = span.offsets(of: emptySpan) XCTAssertEqual(bounds, span._byteOffsets.prefix(0)) + bounds = span.offsets(of: nilSpan) + XCTAssertEqual(bounds, 0..<0) + bounds = nilSpan.offsets(of: emptySpan) + XCTAssertEqual(bounds, 0..<0) } } From c0d02309058aa4018981a968cc6e3d1927cb1f6f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 16 Jul 2024 08:50:19 -0700 Subject: [PATCH 045/195] add convenience initializers for `RawSpan` --- Sources/Future/RawSpan.swift | 17 ++++++++++++++ Tests/FutureTests/RawSpanTests.swift | 33 ++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 8f5220767..87e1a5371 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -58,6 +58,23 @@ extension RawSpan { ) } + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableRawBufferPointer` to initialized memory. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `RawSpan`. + @_alwaysEmitIntoClient + public init( + unsafeBytes buffer: UnsafeMutableRawBufferPointer, + owner: borrowing Owner + ) { + self.init(unsafeBytes: UnsafeRawBufferPointer(buffer), owner: owner) + } + /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory over `count` bytes starting at diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 70cd9b5c1..bea88a64f 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -43,16 +43,21 @@ final class RawSpanTests: XCTestCase { func testInitWithRawBytes() { let capacity = 4 - let a = Array(0...stride) } + + a.withUnsafeMutableBytes { + let span = RawSpan(unsafeBytes: $0, owner: $0) + XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) + } } func testWithRawPointer() { let capacity = 4 - let a = Array(0...stride, owner: a ) - XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) + XCTAssertEqual(span.byteCount, $0.count) + } + + a.withUnsafeMutableBytes { + let pointer = $0.baseAddress! + let span = RawSpan( + unsafeStart: pointer, + byteCount: capacity*MemoryLayout.stride, + owner: $0 + ) + XCTAssertEqual(span.byteCount, $0.count) } } @@ -233,13 +248,15 @@ final class RawSpanTests: XCTestCase { let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) defer { b.deallocate() } - let span = RawSpan(unsafeBytes: .init(b), owner: b) + let span = RawSpan(unsafeBytes: b, owner: b) let subSpan = span.extracting(last: 2) let emptySpan = span.extracting(first: 0) let fakeSpan = RawSpan( unsafeStart: b.baseAddress!.advanced(by: 8), byteCount: 8, owner: b ) - let nilSpan = RawSpan(unsafeBytes: .init(start: nil, count: 0), owner: b) + let nilSpan = RawSpan( + unsafeBytes: UnsafeRawBufferPointer(start: nil, count: 0), owner: b + ) XCTAssertTrue(span.contains(subSpan)) XCTAssertFalse(subSpan.contains(span)) @@ -256,10 +273,12 @@ final class RawSpanTests: XCTestCase { let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) defer { b.deallocate() } - let span = RawSpan(unsafeBytes: .init(b), owner: b) + let span = RawSpan(unsafeBytes: b, owner: b) let subSpan = span.extracting(last: 2) let emptySpan = span.extracting(first: 0) - let nilSpan = RawSpan(unsafeBytes: .init(start: nil, count: 0), owner: b) + let nilSpan = RawSpan( + unsafeBytes: UnsafeRawBufferPointer(start: nil, count: 0), owner: b + ) var bounds: Range bounds = span.offsets(of: subSpan) From fe649d9a4ca841462d9d50ab6a7f1c15d6a507bc Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 16 Jul 2024 08:32:00 -0700 Subject: [PATCH 046/195] add convenience initializers for `Span` --- Sources/Future/Span.swift | 56 ++++++++++++++++++++++++++++++ Tests/FutureTests/SpanTests.swift | 57 +++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 736d36211..7fb571f40 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -79,6 +79,23 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { self.init(_unchecked: buffer, owner: owner) } + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. + @_alwaysEmitIntoClient + public init( + unsafeElements buffer: UnsafeMutableBufferPointer, + owner: borrowing Owner + ) { + self.init(unsafeElements: UnsafeBufferPointer(buffer), owner: owner) + } + /// Unsafely create a `Span` over initialized memory. /// /// The memory representing `count` instances starting at @@ -124,6 +141,23 @@ extension Span where Element: BitwiseCopyable { self.init(_unchecked: buffer, owner: owner) } + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. + @_alwaysEmitIntoClient + public init( + unsafeElements buffer: UnsafeMutableBufferPointer, + owner: borrowing Owner + ) { + self.init(unsafeElements: UnsafeBufferPointer(buffer), owner: owner) + } + /// Unsafely create a `Span` over initialized memory. /// /// The memory representing `count` instances starting at @@ -175,6 +209,28 @@ extension Span where Element: BitwiseCopyable { ) } + /// Unsafely create a `Span` over initialized memory. + /// + /// The memory in `unsafeBytes` must be owned by the instance `owner` + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// `unsafeBytes` must be correctly aligned for accessing + /// an element of type `Element`, and must contain a number of bytes + /// that is an exact multiple of `Element`'s stride. + /// + /// - Parameters: + /// - unsafeBytes: a buffer to initialized elements. + /// - type: the type to use when interpreting the bytes in memory. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. + @_alwaysEmitIntoClient + public init( + unsafeBytes buffer: UnsafeMutableRawBufferPointer, + owner: borrowing Owner + ) { + self.init(unsafeBytes: UnsafeRawBufferPointer(buffer), owner: owner) + } + /// Unsafely create a `Span` over initialized memory. /// /// The memory representing `count` instances starting at diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 42b6541f4..8c9d32486 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -29,18 +29,42 @@ final class SpanTests: XCTestCase { func testInitWithOrdinaryElement() { let capacity = 4 - let s = (0..(unsafeBytes: $0, owner: $0) XCTAssertEqual(r.count, capacity*MemoryLayout.stride) + + let p = Span( + unsafeStart: $0.baseAddress!, + byteCount: capacity*MemoryLayout.stride, + owner: $0 + ) + XCTAssertEqual(p.count, capacity) + } + + a.withUnsafeMutableBytes { + let b = Span(unsafeBytes: $0, owner: $0) + XCTAssertEqual(b.count, capacity) + + let p = Span( + unsafeStart: $0.baseAddress!, + byteCount: capacity*MemoryLayout.stride, + owner: $0 + ) + XCTAssertEqual(p.count, capacity) } } @@ -328,7 +371,7 @@ final class SpanTests: XCTestCase { unsafeStart: b.baseAddress!.advanced(by: 8), count: 8, owner: b ) let nilSpan = Span( - unsafeElements: .init(start: nil, count: 0), owner: b + unsafeElements: UnsafeBufferPointer(start: nil, count: 0), owner: b ) XCTAssertTrue(span.contains(subSpan)) @@ -351,7 +394,7 @@ final class SpanTests: XCTestCase { let subSpan = span.extracting(last: 2) let emptySpan = span.extracting(first: 0) let nilSpan = Span( - unsafeElements: .init(start: nil, count: 0), owner: b + unsafeElements: UnsafeBufferPointer(start: nil, count: 0), owner: b ) var bounds: Range From c611501b0a6db9591a47af3961b328a498a0abb3 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 16 Jul 2024 09:09:44 -0700 Subject: [PATCH 047/195] mark _indices and _byteOffsets as @_aEIC --- Sources/Future/RawSpan.swift | 2 +- Sources/Future/Span.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 87e1a5371..3028f11f0 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -132,7 +132,7 @@ extension RawSpan { /// order. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public var _byteOffsets: Range { .init(uncheckedBounds: (0, byteCount)) } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 7fb571f40..2d38e2aaa 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -349,7 +349,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// order. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public var _indices: Range { .init(uncheckedBounds: (0, _count)) } From 296f94c2d84ff76311dd05ba5e4fa49a64618e7e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 16 Jul 2024 13:30:29 -0700 Subject: [PATCH 048/195] remove undesirable default values --- Sources/Future/RawSpan.swift | 2 +- Sources/Future/Span.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 3028f11f0..8eac660ad 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -546,7 +546,7 @@ extension RawSpan { /// /// - Complexity: O(1) @inlinable - public func extracting(droppingFirst k: Int = 1) -> Self { + public func extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) let newStart = _pointer?.advanced(by: dc) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 2d38e2aaa..f62580440 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -757,7 +757,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Complexity: O(1) @inlinable - public func extracting(droppingFirst k: Int = 1) -> Self { + public func extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) let newStart = _pointer?.advanced(by: droppedCount) From 93efdb9e4e967a0ad0cb735721b0a67447cd12b2 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 16 Jul 2024 13:30:51 -0700 Subject: [PATCH 049/195] add doc-comments for the containment utilities --- Sources/Future/RawSpan.swift | 14 ++++++++++++++ Sources/Future/Span.swift | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 8eac660ad..4b1058b56 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -442,6 +442,12 @@ extension RawSpan { extension RawSpan { + /// Returns true if the memory represented by `span` is a subrange of + /// the memory represented by `self` + /// + /// Parameters: + /// - span: a span of the same type as `self` + /// Returns: whether `span` is a subrange of `self` @inlinable @inline(__always) public func contains(_ span: borrowing Self) -> Bool { if span._count > _count { return false } @@ -450,6 +456,14 @@ extension RawSpan { return span._start.advanced(by: span._count) <= _start.advanced(by: _count) } + /// Returns the offsets where the memory of `span` is located within + /// the memory represented by `self` + /// + /// Note: `span` must be a subrange of `self` + /// + /// Parameters: + /// - span: a subrange of `self` + /// Returns: A range of offsets within `self` @inlinable @inline(__always) public func offsets(of span: borrowing Self) -> Range { precondition(contains(span)) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index f62580440..7f05fff9f 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -653,6 +653,12 @@ extension Span where Element: Copyable { extension Span where Element: ~Copyable /*& ~Escapable*/ { + /// Returns true if the memory represented by `span` is a subrange of + /// the memory represented by `self` + /// + /// Parameters: + /// - span: a span of the same type as `self` + /// Returns: whether `span` is a subrange of `self` @inlinable @inline(__always) public func contains(_ span: borrowing Self) -> Bool { if span._count > _count { return false } @@ -661,6 +667,14 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { return span._start.advanced(by: span._count) <= _start.advanced(by: _count) } + /// Returns the offsets where the memory of `span` is located within + /// the memory represented by `self` + /// + /// Note: `span` must be a subrange of `self` + /// + /// Parameters: + /// - span: a subrange of `self` + /// Returns: A range of offsets within `self` @inlinable @inline(__always) public func offsets(of span: borrowing Self) -> Range { precondition(contains(span)) From 80c67c2eff941581c8bb48c304c2b9e45d44f3cc Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 24 Jul 2024 17:03:23 -0700 Subject: [PATCH 050/195] add identity operator --- Sources/Future/RawSpan.swift | 9 +++++++++ Sources/Future/Span.swift | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 4b1058b56..e9bdeeafa 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -111,6 +111,15 @@ extension RawSpan { } } +extension RawSpan { + /// Returns a Boolean value indicating whether two `RawSpan` instances + /// refer to the same region in memory. + @inlinable @inline(__always) + public static func ===(_ a: Self, _ b: Self) -> Bool { + (a._pointer == b._pointer) && (a._count == b._count) + } +} + extension RawSpan { /// The number of bytes in the span. diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 7f05fff9f..a8f301a04 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -328,6 +328,15 @@ extension Span where Element: Equatable { } } +extension Span where Element: ~Copyable /*& ~Escapable*/ { + /// Returns a Boolean value indicating whether two `RawSpan` instances + /// refer to the same region in memory. + @inlinable @inline(__always) + public static func ===(_ a: Self, _ b: Self) -> Bool { + (a._pointer == b._pointer) && (a._count == b._count) + } +} + extension Span where Element: ~Copyable /*& ~Escapable*/ { /// The number of elements in the span. From 3847f3ac5337c421cb254be7def0c1ac6b6a4042 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 24 Jul 2024 17:04:14 -0700 Subject: [PATCH 051/195] add a `description` property --- Sources/Future/RawSpan.swift | 11 +++++++++++ Sources/Future/Span.swift | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index e9bdeeafa..447ce3368 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -120,6 +120,17 @@ extension RawSpan { } } +extension RawSpan { + + private var _address: String { + String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) + } + + public var description: String { + "(0x\(_address), \(_count))" + } +} + extension RawSpan { /// The number of bytes in the span. diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index a8f301a04..32cbeec60 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -337,6 +337,17 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { } } +extension Span where Element: ~Copyable /*& ~Escapable*/ { + + private var _address: String { + String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) + } + + public var description: String { + "(0x\(_address), \(_count))" + } +} + extension Span where Element: ~Copyable /*& ~Escapable*/ { /// The number of elements in the span. From 0d4376d5cdd3160e175092f10ce0f7b9d81f3e7a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 29 Jul 2024 13:16:32 -0700 Subject: [PATCH 052/195] [_CollectionsUtilities] Delete obsolete compatibility definitions --- Package.swift | 4 - Sources/_CollectionsUtilities/CMakeLists.txt | 4 - ...safeMutableBufferPointer+SE-0370.swift.gyb | 424 --------- .../UnsafeMutablePointer+SE-0370.swift.gyb | 43 - .../UnsafeRawPointer extensions.swift.gyb | 89 -- .../UnsafeMutableBufferPointer+SE-0370.swift | 832 ------------------ .../UnsafeMutablePointer+SE-0370.swift | 72 -- .../UnsafeRawPointer extensions.swift | 164 ---- .../Specialize.swift.gyb | 34 - .../autogenerated/Specialize.swift | 54 -- 10 files changed, 1720 deletions(-) delete mode 100644 Sources/_CollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb delete mode 100644 Sources/_CollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb delete mode 100644 Sources/_CollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb delete mode 100644 Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift delete mode 100644 Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift delete mode 100644 Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift delete mode 100644 Sources/_CollectionsUtilities/Specialize.swift.gyb delete mode 100644 Sources/_CollectionsUtilities/autogenerated/Specialize.swift diff --git a/Package.swift b/Package.swift index 846a3e43a..639f4b991 100644 --- a/Package.swift +++ b/Package.swift @@ -194,9 +194,6 @@ let targets: [CustomTarget] = [ name: "_CollectionsUtilities", exclude: [ "CMakeLists.txt", - "Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb", - "Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb", - "Compatibility/UnsafeRawPointer extensions.swift.gyb", "Debugging.swift.gyb", "Descriptions.swift.gyb", "IntegerTricks/FixedWidthInteger+roundUpToPowerOfTwo.swift.gyb", @@ -204,7 +201,6 @@ let targets: [CustomTarget] = [ "IntegerTricks/UInt+first and last set bit.swift.gyb", "IntegerTricks/UInt+reversed.swift.gyb", "RandomAccessCollection+Offsets.swift.gyb", - "Specialize.swift.gyb", "UnsafeBitSet/_UnsafeBitSet+Index.swift.gyb", "UnsafeBitSet/_UnsafeBitSet+_Word.swift.gyb", "UnsafeBitSet/_UnsafeBitSet.swift.gyb", diff --git a/Sources/_CollectionsUtilities/CMakeLists.txt b/Sources/_CollectionsUtilities/CMakeLists.txt index 82c992068..5f3927a80 100644 --- a/Sources/_CollectionsUtilities/CMakeLists.txt +++ b/Sources/_CollectionsUtilities/CMakeLists.txt @@ -11,12 +11,8 @@ add_library(_CollectionsUtilities "autogenerated/Debugging.swift" "autogenerated/Descriptions.swift" "autogenerated/RandomAccessCollection+Offsets.swift" - "autogenerated/Specialize.swift" "autogenerated/UnsafeBufferPointer+Extras.swift" "autogenerated/UnsafeMutableBufferPointer+Extras.swift" - "Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift" - "Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift" - "Compatibility/autogenerated/UnsafeRawPointer extensions.swift" "IntegerTricks/autogenerated/FixedWidthInteger+roundUpToPowerOfTwo.swift" "IntegerTricks/autogenerated/Integer rank.swift" "IntegerTricks/autogenerated/UInt+first and last set bit.swift" diff --git a/Sources/_CollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb b/Sources/_CollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb deleted file mode 100644 index 8e4f6debf..000000000 --- a/Sources/_CollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb +++ /dev/null @@ -1,424 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -// Note: These are adapted from SE-0370 in the Swift 5.8 Standard Library. - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Deinitializes every instance in this buffer. - /// - /// The region of memory underlying this buffer must be fully initialized. - /// After calling `deinitialize(count:)`, the memory is uninitialized, - /// but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - ${modifier} func deinitialize() -> UnsafeMutableRawBufferPointer { - guard let start = baseAddress else { return .init(start: nil, count: 0) } - start.deinitialize(count: count) - return .init(start: UnsafeMutableRawPointer(start), - count: count * MemoryLayout.stride) - } -} -#endif - -// Note: this is left unconditionally enabled because we need the SR14663 workaround. :-( -extension UnsafeMutableBufferPointer { - /// Initializes the buffer's memory with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, - /// the memory referenced by the buffer must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer's storage. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - ${modifier} func initialize( - fromContentsOf source: C - ) -> Index - where C.Element == Element { - let count: Int? = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - precondition( - $0.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.initialize(from: sourceAddress, count: $0.count) - return $0.count - } - if let count = count { - return startIndex.advanced(by: count) - } - - var (iterator, copied) = source._copyContents(initializing: self) - precondition( - iterator.next() == nil, - "buffer cannot contain every element from source." - ) - return startIndex.advanced(by: copied) - } -} - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize(fromContentsOf source: Self) -> Index { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return startIndex - } - precondition( - source.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.moveInitialize(from: sourceAddress, count: source.count) - return startIndex.advanced(by: source.count) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize(fromContentsOf source: Slice) -> Index { - return moveInitialize(fromContentsOf: Self(rebasing: source)) - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - ${modifier} func initializeElement(at index: Index, to value: Element) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.initialize(to: value) - } - - /// Retrieves and returns the element at `index`, - /// leaving that element's underlying memory uninitialized. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `moveElement(from:)`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to retrieve and deinitialize. - /// - Returns: The instance referenced by this index in this buffer. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveElement(from index: Index) -> Element { - assert(startIndex <= index && index < endIndex) - return baseAddress.unsafelyUnwrapped.advanced(by: index).move() - } - - /// Deinitializes the memory underlying the element at `index`. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `deinitializeElement()`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to deinitialize. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func deinitializeElement(at index: Index) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.deinitialize(count: 1) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Initializes the buffer slice's memory with with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method - /// on a buffer slice, the memory it references must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the index of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to - /// the buffer slice's `startIndex`. If `source` contains as many elements - /// as the buffer slice can hold, the returned index is equal to - /// to the slice's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer slice's storage. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func initialize( - fromContentsOf source: C - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.initialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer slice into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer slice containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize( - fromContentsOf source: Slice> - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Deinitializes every instance in this buffer slice. - /// - /// The region of memory underlying this buffer slice must be fully - /// initialized. After calling `deinitialize(count:)`, the memory - /// is uninitialized, but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - @_alwaysEmitIntoClient - ${modifier} func deinitialize() -> UnsafeMutableRawBufferPointer - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).deinitialize() - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer slice is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - ${modifier} func initializeElement(at index: Int, to value: Element) - where Base == UnsafeMutableBufferPointer { - assert(startIndex <= index && index < endIndex) - base.baseAddress.unsafelyUnwrapped.advanced(by: index).initialize(to: value) - } -} -#endif - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Updates every element of this buffer's initialized memory. - /// - /// The buffer’s memory must be initialized or its `Element` type - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - ${modifier} func update(repeating repeatedValue: Element) { - guard let dstBase = baseAddress else { return } - dstBase.update(repeating: repeatedValue, count: count) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Updates every element of this buffer slice's initialized memory. - /// - /// The buffer slice’s memory must be initialized or its `Element` - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - ${modifier} func update(repeating repeatedValue: Element) - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).update(repeating: repeatedValue) - } -} -#endif -% end -${visibility_boilerplate("end")} diff --git a/Sources/_CollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb b/Sources/_CollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb deleted file mode 100644 index d95199cf7..000000000 --- a/Sources/_CollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb +++ /dev/null @@ -1,43 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -#if swift(<5.8) -extension UnsafeMutablePointer { - /// Update this pointer's initialized memory with the specified number of - /// consecutive copies of the given value. - /// - /// The region of memory starting at this pointer and covering `count` - /// instances of the pointer's `Pointee` type must be initialized or - /// `Pointee` must be a trivial type. After calling - /// `update(repeating:count:)`, the region is initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - /// - count: The number of consecutive elements to update. - /// `count` must not be negative. - @_alwaysEmitIntoClient - ${modifier} func update(repeating repeatedValue: Pointee, count: Int) { - assert(count >= 0, "UnsafeMutablePointer.update(repeating:count:) with negative count") - for i in 0 ..< count { - self[i] = repeatedValue - } - } -} -#endif -% end -${visibility_boilerplate("end")} diff --git a/Sources/_CollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb b/Sources/_CollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb deleted file mode 100644 index 68c2eedcd..000000000 --- a/Sources/_CollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb +++ /dev/null @@ -1,89 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -#if compiler(<5.7) || (os(macOS) && compiler(<5.8)) // SE-0334 -extension UnsafeRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} - -extension UnsafeMutableRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} -#endif -% end -${visibility_boilerplate("end")} diff --git a/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift b/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift deleted file mode 100644 index 9068f8b91..000000000 --- a/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift +++ /dev/null @@ -1,832 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -// Note: These are adapted from SE-0370 in the Swift 5.8 Standard Library. - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Deinitializes every instance in this buffer. - /// - /// The region of memory underlying this buffer must be fully initialized. - /// After calling `deinitialize(count:)`, the memory is uninitialized, - /// but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - internal func deinitialize() -> UnsafeMutableRawBufferPointer { - guard let start = baseAddress else { return .init(start: nil, count: 0) } - start.deinitialize(count: count) - return .init(start: UnsafeMutableRawPointer(start), - count: count * MemoryLayout.stride) - } -} -#endif - -// Note: this is left unconditionally enabled because we need the SR14663 workaround. :-( -extension UnsafeMutableBufferPointer { - /// Initializes the buffer's memory with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, - /// the memory referenced by the buffer must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer's storage. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - internal func initialize( - fromContentsOf source: C - ) -> Index - where C.Element == Element { - let count: Int? = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - precondition( - $0.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.initialize(from: sourceAddress, count: $0.count) - return $0.count - } - if let count = count { - return startIndex.advanced(by: count) - } - - var (iterator, copied) = source._copyContents(initializing: self) - precondition( - iterator.next() == nil, - "buffer cannot contain every element from source." - ) - return startIndex.advanced(by: copied) - } -} - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize(fromContentsOf source: Self) -> Index { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return startIndex - } - precondition( - source.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.moveInitialize(from: sourceAddress, count: source.count) - return startIndex.advanced(by: source.count) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize(fromContentsOf source: Slice) -> Index { - return moveInitialize(fromContentsOf: Self(rebasing: source)) - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - internal func initializeElement(at index: Index, to value: Element) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.initialize(to: value) - } - - /// Retrieves and returns the element at `index`, - /// leaving that element's underlying memory uninitialized. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `moveElement(from:)`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to retrieve and deinitialize. - /// - Returns: The instance referenced by this index in this buffer. - @inlinable - @_alwaysEmitIntoClient - internal func moveElement(from index: Index) -> Element { - assert(startIndex <= index && index < endIndex) - return baseAddress.unsafelyUnwrapped.advanced(by: index).move() - } - - /// Deinitializes the memory underlying the element at `index`. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `deinitializeElement()`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to deinitialize. - @inlinable - @_alwaysEmitIntoClient - internal func deinitializeElement(at index: Index) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.deinitialize(count: 1) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Initializes the buffer slice's memory with with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method - /// on a buffer slice, the memory it references must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the index of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to - /// the buffer slice's `startIndex`. If `source` contains as many elements - /// as the buffer slice can hold, the returned index is equal to - /// to the slice's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer slice's storage. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - internal func initialize( - fromContentsOf source: C - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.initialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer slice into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer slice containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize( - fromContentsOf source: Slice> - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Deinitializes every instance in this buffer slice. - /// - /// The region of memory underlying this buffer slice must be fully - /// initialized. After calling `deinitialize(count:)`, the memory - /// is uninitialized, but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - @_alwaysEmitIntoClient - internal func deinitialize() -> UnsafeMutableRawBufferPointer - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).deinitialize() - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer slice is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - internal func initializeElement(at index: Int, to value: Element) - where Base == UnsafeMutableBufferPointer { - assert(startIndex <= index && index < endIndex) - base.baseAddress.unsafelyUnwrapped.advanced(by: index).initialize(to: value) - } -} -#endif - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Updates every element of this buffer's initialized memory. - /// - /// The buffer’s memory must be initialized or its `Element` type - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - internal func update(repeating repeatedValue: Element) { - guard let dstBase = baseAddress else { return } - dstBase.update(repeating: repeatedValue, count: count) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Updates every element of this buffer slice's initialized memory. - /// - /// The buffer slice’s memory must be initialized or its `Element` - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - internal func update(repeating repeatedValue: Element) - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).update(repeating: repeatedValue) - } -} -#endif -#else // !COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Deinitializes every instance in this buffer. - /// - /// The region of memory underlying this buffer must be fully initialized. - /// After calling `deinitialize(count:)`, the memory is uninitialized, - /// but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - public func deinitialize() -> UnsafeMutableRawBufferPointer { - guard let start = baseAddress else { return .init(start: nil, count: 0) } - start.deinitialize(count: count) - return .init(start: UnsafeMutableRawPointer(start), - count: count * MemoryLayout.stride) - } -} -#endif - -// Note: this is left unconditionally enabled because we need the SR14663 workaround. :-( -extension UnsafeMutableBufferPointer { - /// Initializes the buffer's memory with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, - /// the memory referenced by the buffer must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer's storage. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - public func initialize( - fromContentsOf source: C - ) -> Index - where C.Element == Element { - let count: Int? = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - precondition( - $0.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.initialize(from: sourceAddress, count: $0.count) - return $0.count - } - if let count = count { - return startIndex.advanced(by: count) - } - - var (iterator, copied) = source._copyContents(initializing: self) - precondition( - iterator.next() == nil, - "buffer cannot contain every element from source." - ) - return startIndex.advanced(by: copied) - } -} - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize(fromContentsOf source: Self) -> Index { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return startIndex - } - precondition( - source.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.moveInitialize(from: sourceAddress, count: source.count) - return startIndex.advanced(by: source.count) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize(fromContentsOf source: Slice) -> Index { - return moveInitialize(fromContentsOf: Self(rebasing: source)) - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - public func initializeElement(at index: Index, to value: Element) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.initialize(to: value) - } - - /// Retrieves and returns the element at `index`, - /// leaving that element's underlying memory uninitialized. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `moveElement(from:)`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to retrieve and deinitialize. - /// - Returns: The instance referenced by this index in this buffer. - @inlinable - @_alwaysEmitIntoClient - public func moveElement(from index: Index) -> Element { - assert(startIndex <= index && index < endIndex) - return baseAddress.unsafelyUnwrapped.advanced(by: index).move() - } - - /// Deinitializes the memory underlying the element at `index`. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `deinitializeElement()`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to deinitialize. - @inlinable - @_alwaysEmitIntoClient - public func deinitializeElement(at index: Index) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.deinitialize(count: 1) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Initializes the buffer slice's memory with with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method - /// on a buffer slice, the memory it references must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the index of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to - /// the buffer slice's `startIndex`. If `source` contains as many elements - /// as the buffer slice can hold, the returned index is equal to - /// to the slice's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer slice's storage. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - public func initialize( - fromContentsOf source: C - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.initialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer slice into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer slice containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize( - fromContentsOf source: Slice> - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Deinitializes every instance in this buffer slice. - /// - /// The region of memory underlying this buffer slice must be fully - /// initialized. After calling `deinitialize(count:)`, the memory - /// is uninitialized, but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - @_alwaysEmitIntoClient - public func deinitialize() -> UnsafeMutableRawBufferPointer - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).deinitialize() - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer slice is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - public func initializeElement(at index: Int, to value: Element) - where Base == UnsafeMutableBufferPointer { - assert(startIndex <= index && index < endIndex) - base.baseAddress.unsafelyUnwrapped.advanced(by: index).initialize(to: value) - } -} -#endif - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Updates every element of this buffer's initialized memory. - /// - /// The buffer’s memory must be initialized or its `Element` type - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - public func update(repeating repeatedValue: Element) { - guard let dstBase = baseAddress else { return } - dstBase.update(repeating: repeatedValue, count: count) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Updates every element of this buffer slice's initialized memory. - /// - /// The buffer slice’s memory must be initialized or its `Element` - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - public func update(repeating repeatedValue: Element) - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).update(repeating: repeatedValue) - } -} -#endif -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift b/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift deleted file mode 100644 index 2e89d5aa0..000000000 --- a/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift +++ /dev/null @@ -1,72 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutablePointer { - /// Update this pointer's initialized memory with the specified number of - /// consecutive copies of the given value. - /// - /// The region of memory starting at this pointer and covering `count` - /// instances of the pointer's `Pointee` type must be initialized or - /// `Pointee` must be a trivial type. After calling - /// `update(repeating:count:)`, the region is initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - /// - count: The number of consecutive elements to update. - /// `count` must not be negative. - @_alwaysEmitIntoClient - internal func update(repeating repeatedValue: Pointee, count: Int) { - assert(count >= 0, "UnsafeMutablePointer.update(repeating:count:) with negative count") - for i in 0 ..< count { - self[i] = repeatedValue - } - } -} -#endif -#else // !COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutablePointer { - /// Update this pointer's initialized memory with the specified number of - /// consecutive copies of the given value. - /// - /// The region of memory starting at this pointer and covering `count` - /// instances of the pointer's `Pointee` type must be initialized or - /// `Pointee` must be a trivial type. After calling - /// `update(repeating:count:)`, the region is initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - /// - count: The number of consecutive elements to update. - /// `count` must not be negative. - @_alwaysEmitIntoClient - public func update(repeating repeatedValue: Pointee, count: Int) { - assert(count >= 0, "UnsafeMutablePointer.update(repeating:count:) with negative count") - for i in 0 ..< count { - self[i] = repeatedValue - } - } -} -#endif -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift b/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift deleted file mode 100644 index c389967eb..000000000 --- a/Sources/_CollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift +++ /dev/null @@ -1,164 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -#if compiler(<5.7) || (os(macOS) && compiler(<5.8)) // SE-0334 -extension UnsafeRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} - -extension UnsafeMutableRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} -#endif -#else // !COLLECTIONS_SINGLE_MODULE -#if compiler(<5.7) || (os(macOS) && compiler(<5.8)) // SE-0334 -extension UnsafeRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} - -extension UnsafeMutableRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} -#endif -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/_CollectionsUtilities/Specialize.swift.gyb b/Sources/_CollectionsUtilities/Specialize.swift.gyb deleted file mode 100644 index c654d78cc..000000000 --- a/Sources/_CollectionsUtilities/Specialize.swift.gyb +++ /dev/null @@ -1,34 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 - 2024 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 -// -//===----------------------------------------------------------------------===// - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -/// Returns `x` as its concrete type `U`, or `nil` if `x` has a different -/// concrete type. -/// -/// This cast can be useful for dispatching to specializations of generic -/// functions. -@_transparent -@inlinable -${modifier} func _specialize(_ x: T, for: U.Type) -> U? { - // Note: this was ported from recent versions of the Swift stdlib. - guard T.self == U.self else { - return nil - } - return _identityCast(x, to: U.self) -} -% end -${visibility_boilerplate("end")} diff --git a/Sources/_CollectionsUtilities/autogenerated/Specialize.swift b/Sources/_CollectionsUtilities/autogenerated/Specialize.swift deleted file mode 100644 index 07087af24..000000000 --- a/Sources/_CollectionsUtilities/autogenerated/Specialize.swift +++ /dev/null @@ -1,54 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 - 2024 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 -// -//===----------------------------------------------------------------------===// - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -/// Returns `x` as its concrete type `U`, or `nil` if `x` has a different -/// concrete type. -/// -/// This cast can be useful for dispatching to specializations of generic -/// functions. -@_transparent -@inlinable -internal func _specialize(_ x: T, for: U.Type) -> U? { - // Note: this was ported from recent versions of the Swift stdlib. - guard T.self == U.self else { - return nil - } - return _identityCast(x, to: U.self) -} -#else // !COLLECTIONS_SINGLE_MODULE -/// Returns `x` as its concrete type `U`, or `nil` if `x` has a different -/// concrete type. -/// -/// This cast can be useful for dispatching to specializations of generic -/// functions. -@_transparent -@inlinable -public func _specialize(_ x: T, for: U.Type) -> U? { - // Note: this was ported from recent versions of the Swift stdlib. - guard T.self == U.self else { - return nil - } - return _identityCast(x, to: U.self) -} -#endif // COLLECTIONS_SINGLE_MODULE From c1dd7622c42ee7eb724dda04d19c4e9615171931 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 29 Jul 2024 13:19:41 -0700 Subject: [PATCH 053/195] [manifest] Bump to v6.0 toolchain and language mode - Enable SuppressedAssociatedTypes to allow experimentation with protocols - Mark _CollectionsTestSupport as a test dependency on FutureTests --- Package.swift | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index 639f4b991..197ac5b38 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10 +// swift-tools-version:6.0 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project @@ -56,8 +56,9 @@ let _settings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), .enableExperimentalFeature("BitwiseCopyable"), - .enableExperimentalFeature("RawLayout") -// .swiftLanguageVersion(.v5) + .enableExperimentalFeature("RawLayout"), + .enableExperimentalFeature("SuppressedAssociatedTypes"), + .swiftLanguageMode(.v6) ] struct CustomTarget { @@ -209,14 +210,14 @@ let targets: [CustomTarget] = [ ]), .target( - kind: .exported, - name: "Future", - dependencies: ["_CollectionsUtilities"], - exclude: ["CMakeLists.txt"]), + kind: .exported, + name: "Future", + dependencies: ["_CollectionsUtilities"], + exclude: ["CMakeLists.txt"]), .target( - kind: .test, - name: "FutureTests", - dependencies: ["Future"]), + kind: .test, + name: "FutureTests", + dependencies: ["Future", "_CollectionsTestSupport"]), .target( kind: .exported, From 2359d9a09cb26561eda2571ea007ba442f32144a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 29 Jul 2024 13:22:44 -0700 Subject: [PATCH 054/195] [_CollectionsUtilities] Fix visibility modifiers in single module mode --- ...nsafeMutableBufferPointer+Extras.swift.gyb | 28 +++++++++---------- .../UnsafeMutableBufferPointer+Extras.swift | 28 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index c7c6ee0fb..4d7cdde7a 100644 --- a/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -18,7 +18,7 @@ ${autogenerated_warning()} ${visibility_boilerplate(modifier)} extension UnsafeMutableBufferPointer { @inlinable - public func initialize(fromContentsOf source: Self) -> Index { + ${modifier} func initialize(fromContentsOf source: Self) -> Index { guard source.count > 0 else { return 0 } precondition( source.count <= self.count, @@ -30,7 +30,7 @@ extension UnsafeMutableBufferPointer { } @inlinable - public func initialize(fromContentsOf source: Slice) -> Index { + ${modifier} func initialize(fromContentsOf source: Slice) -> Index { let sourceCount = source.count guard sourceCount > 0 else { return 0 } precondition( @@ -45,7 +45,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initialize( + ${modifier} func initialize( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index where Base == UnsafeMutableBufferPointer @@ -56,7 +56,7 @@ extension Slice { } @inlinable @inline(__always) - public func initialize( + ${modifier} func initialize( fromContentsOf source: Slice> ) -> Index where Base == UnsafeMutableBufferPointer @@ -69,7 +69,7 @@ extension Slice { extension UnsafeMutableBufferPointer { @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: C ) where C.Element == Element { let i = self.initialize(fromContentsOf: source) @@ -77,25 +77,25 @@ extension UnsafeMutableBufferPointer { } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Self) { + ${modifier} func initializeAll(fromContentsOf source: Self) { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Slice) { + ${modifier} func initializeAll(fromContentsOf source: Slice) { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Self) { + ${modifier} func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Slice) { + ${modifier} func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } @@ -103,7 +103,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: C ) where Base == UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) @@ -111,7 +111,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -119,7 +119,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -127,7 +127,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + ${modifier} func moveInitializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -135,7 +135,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + ${modifier} func moveInitializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) diff --git a/Sources/_CollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/_CollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index 2661d8ca1..be578b238 100644 --- a/Sources/_CollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/_CollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -24,7 +24,7 @@ #if COLLECTIONS_SINGLE_MODULE extension UnsafeMutableBufferPointer { @inlinable - public func initialize(fromContentsOf source: Self) -> Index { + internal func initialize(fromContentsOf source: Self) -> Index { guard source.count > 0 else { return 0 } precondition( source.count <= self.count, @@ -36,7 +36,7 @@ extension UnsafeMutableBufferPointer { } @inlinable - public func initialize(fromContentsOf source: Slice) -> Index { + internal func initialize(fromContentsOf source: Slice) -> Index { let sourceCount = source.count guard sourceCount > 0 else { return 0 } precondition( @@ -51,7 +51,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initialize( + internal func initialize( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index where Base == UnsafeMutableBufferPointer @@ -62,7 +62,7 @@ extension Slice { } @inlinable @inline(__always) - public func initialize( + internal func initialize( fromContentsOf source: Slice> ) -> Index where Base == UnsafeMutableBufferPointer @@ -75,7 +75,7 @@ extension Slice { extension UnsafeMutableBufferPointer { @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: C ) where C.Element == Element { let i = self.initialize(fromContentsOf: source) @@ -83,25 +83,25 @@ extension UnsafeMutableBufferPointer { } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Self) { + internal func initializeAll(fromContentsOf source: Self) { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Slice) { + internal func initializeAll(fromContentsOf source: Slice) { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Self) { + internal func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Slice) { + internal func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } @@ -109,7 +109,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: C ) where Base == UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) @@ -117,7 +117,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -125,7 +125,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -133,7 +133,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + internal func moveInitializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -141,7 +141,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + internal func moveInitializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) From f14a18d7a6b6b832228353f94ab99f86b2f201fc Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 30 Jul 2024 14:02:50 -0700 Subject: [PATCH 055/195] Mark empty singletons as nonisolated(unsafe) --- Sources/DequeModule/_DequeBuffer.swift | 12 ++++++------ .../HashNode/_HashNode+Storage.swift | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/DequeModule/_DequeBuffer.swift b/Sources/DequeModule/_DequeBuffer.swift index c7cd45bf4..3751661ec 100644 --- a/Sources/DequeModule/_DequeBuffer.swift +++ b/Sources/DequeModule/_DequeBuffer.swift @@ -41,9 +41,9 @@ extension _DequeBuffer: CustomStringConvertible { /// The type-punned empty singleton storage instance. @usableFromInline -internal let _emptyDequeStorage = _DequeBuffer.create( - minimumCapacity: 0, - makingHeaderWith: { _ in - _DequeBufferHeader(capacity: 0, count: 0, startSlot: .init(at: 0)) - }) - +nonisolated(unsafe) internal let _emptyDequeStorage + = _DequeBuffer.create( + minimumCapacity: 0, + makingHeaderWith: { _ in + _DequeBufferHeader(capacity: 0, count: 0, startSlot: .init(at: 0)) + }) diff --git a/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift b/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift index c37e07ba5..3f1cabfd2 100644 --- a/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift +++ b/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift @@ -26,6 +26,7 @@ internal typealias _RawHashStorage = ManagedBuffer<_HashNodeHeader, _RawHashNode /// `_HashNode.Storage` subclass is to allow storage instances to properly /// clean up after themselves in their `deinit` method.) @usableFromInline +nonisolated(unsafe) internal let _emptySingleton: _RawHashStorage = _RawHashStorage.create( minimumCapacity: 0, makingHeaderWith: { _ in _HashNodeHeader(byteCapacity: 0) }) From a57ab2acec5621ff6a9ff61c8efe57e42cefc6de Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 5 Aug 2024 18:55:37 -0700 Subject: [PATCH 056/195] [manifest] Revert to Swift 5 for the test suite TestContext is not anywhere near concurrency ready --- Package.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 197ac5b38..e27845aa5 100644 --- a/Package.swift +++ b/Package.swift @@ -52,13 +52,20 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -let _settings: [SwiftSetting] = defines.map { .define($0) } + [ +let _features: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), .enableExperimentalFeature("BitwiseCopyable"), .enableExperimentalFeature("RawLayout"), .enableExperimentalFeature("SuppressedAssociatedTypes"), - .swiftLanguageMode(.v6) +] + +let _settings: [SwiftSetting] = _features + [ + .swiftLanguageVersion(.v6), +] + +let _testSettings: [SwiftSetting] = _features + [ + .swiftLanguageVersion(.v5), ] struct CustomTarget { @@ -121,7 +128,7 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _settings, + swiftSettings: (kind == .testSupport ? _testSettings : _settings), linkerSettings: linkerSettings) case .test: return Target.testTarget( @@ -129,7 +136,7 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _settings, + swiftSettings: _testSettings, linkerSettings: linkerSettings) } } @@ -176,7 +183,7 @@ extension Array where Element == CustomTarget { t.exclude.map { "\(t.name)/\($0)" } }, sources: targets.map { "\($0.name)" }, - swiftSettings: _settings, + swiftSettings: _testSettings, linkerSettings: linkerSettings) } } From e14970493019b12d03046f2fdd01b76fd4f9fa3c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 5 Aug 2024 20:04:38 -0700 Subject: [PATCH 057/195] [RopeModule] Revert to Swift 5 mode --- Package.swift | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index e27845aa5..4c000cc60 100644 --- a/Package.swift +++ b/Package.swift @@ -52,7 +52,7 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -let _features: [SwiftSetting] = defines.map { .define($0) } + [ +let _sharedSettings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), .enableExperimentalFeature("BitwiseCopyable"), @@ -60,11 +60,11 @@ let _features: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("SuppressedAssociatedTypes"), ] -let _settings: [SwiftSetting] = _features + [ +let _settings: [SwiftSetting] = _sharedSettings + [ .swiftLanguageVersion(.v6), ] -let _testSettings: [SwiftSetting] = _features + [ +let _testSettings: [SwiftSetting] = _sharedSettings + [ .swiftLanguageVersion(.v5), ] @@ -81,6 +81,7 @@ struct CustomTarget { var dependencies: [Target.Dependency] var directory: String var exclude: [String] + var settings: [SwiftSetting] } extension CustomTarget.Kind { @@ -105,14 +106,16 @@ extension CustomTarget { name: String, dependencies: [Target.Dependency] = [], directory: String? = nil, - exclude: [String] = [] + exclude: [String] = [], + settings: [SwiftSetting]? = nil ) -> CustomTarget { CustomTarget( kind: kind, name: name, dependencies: dependencies, directory: directory ?? name, - exclude: exclude) + exclude: exclude, + settings: settings ?? (kind.isTest ? _testSettings : _settings)) } func toTarget() -> Target { @@ -128,7 +131,7 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: (kind == .testSupport ? _testSettings : _settings), + swiftSettings: settings, linkerSettings: linkerSettings) case .test: return Target.testTarget( @@ -136,7 +139,7 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _testSettings, + swiftSettings: settings, linkerSettings: linkerSettings) } } @@ -283,7 +286,9 @@ let targets: [CustomTarget] = [ name: "_RopeModule", dependencies: ["_CollectionsUtilities"], directory: "RopeModule", - exclude: ["CMakeLists.txt"]), + exclude: ["CMakeLists.txt"], + // FIXME: _modify accessors in RopeModule seem to be broken in Swift 6 mode + settings: _sharedSettings + [.swiftLanguageVersion(.v5)]), .target( kind: .test, name: "RopeModuleTests", From 14016480166d3c113d3cb39544df4e9acfeb8d27 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 6 Aug 2024 10:39:23 -0700 Subject: [PATCH 058/195] [manifest] Adjust name of utilities module --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index bb185c2fc..21754b2ca 100644 --- a/Package.swift +++ b/Package.swift @@ -222,7 +222,7 @@ let targets: [CustomTarget] = [ .target( kind: .exported, name: "Future", - dependencies: ["_CollectionsUtilities"], + dependencies: ["InternalCollectionsUtilities"], exclude: ["CMakeLists.txt"]), .target( kind: .test, From 0529df9aee02cfed4ec525b5f90595949e89a353 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 29 Jul 2024 13:35:04 -0700 Subject: [PATCH 059/195] [_CollectionsTestUtilities] Add U[M][R]BP._extracting(first:) and ._extracting(last:) Also generalize some members to support ~Copyable elements --- .../UnsafeBufferPointer+Extras.swift.gyb | 17 +++++++- ...nsafeMutableBufferPointer+Extras.swift.gyb | 20 ++++++++++ .../UnsafeBufferPointer+Extras.swift | 34 +++++++++++++++- .../UnsafeMutableBufferPointer+Extras.swift | 40 +++++++++++++++++++ 4 files changed, 108 insertions(+), 3 deletions(-) diff --git a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb index 603e3047e..9bceabee8 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb @@ -16,13 +16,28 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} -extension UnsafeBufferPointer { +extension UnsafeBufferPointer where Element: ~Copyable { @inlinable @inline(__always) ${modifier} func _ptr(at index: Int) -> UnsafePointer { assert(index >= 0 && index < count) return baseAddress.unsafelyUnwrapped + index } + + @inlinable + ${modifier} func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + ${modifier} func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } } + % end ${visibility_boilerplate("end")} diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index 4d7cdde7a..55089fecb 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -16,6 +16,22 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + ${modifier} func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + ${modifier} func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } +} + extension UnsafeMutableBufferPointer { @inlinable ${modifier} func initialize(fromContentsOf source: Self) -> Index { @@ -87,13 +103,17 @@ extension UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer where Element: ~Copyable { @inlinable @inline(__always) ${modifier} func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer { @inlinable @inline(__always) ${modifier} func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift index 749f17bc8..1244d5af8 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift @@ -22,21 +22,51 @@ // but in regular builds we want them to be public. Unfortunately // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE -extension UnsafeBufferPointer { +extension UnsafeBufferPointer where Element: ~Copyable { @inlinable @inline(__always) internal func _ptr(at index: Int) -> UnsafePointer { assert(index >= 0 && index < count) return baseAddress.unsafelyUnwrapped + index } + + @inlinable + internal func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + internal func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } } + #else // !COLLECTIONS_SINGLE_MODULE -extension UnsafeBufferPointer { +extension UnsafeBufferPointer where Element: ~Copyable { @inlinable @inline(__always) public func _ptr(at index: Int) -> UnsafePointer { assert(index >= 0 && index < count) return baseAddress.unsafelyUnwrapped + index } + + @inlinable + public func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + public func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } } + #endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index be578b238..ee44e0cf7 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -22,6 +22,22 @@ // but in regular builds we want them to be public. Unfortunately // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + internal func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + internal func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } +} + extension UnsafeMutableBufferPointer { @inlinable internal func initialize(fromContentsOf source: Self) -> Index { @@ -93,13 +109,17 @@ extension UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer where Element: ~Copyable { @inlinable @inline(__always) internal func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer { @inlinable @inline(__always) internal func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) @@ -149,6 +169,22 @@ extension Slice { } } #else // !COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + public func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + public func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } +} + extension UnsafeMutableBufferPointer { @inlinable public func initialize(fromContentsOf source: Self) -> Index { @@ -220,13 +256,17 @@ extension UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer where Element: ~Copyable { @inlinable @inline(__always) public func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer { @inlinable @inline(__always) public func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) From 6339b9859ea276f9d0c802b9d4605fd2636fc66f Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 30 Jul 2024 14:01:32 -0700 Subject: [PATCH 060/195] [_CollectionsUtilities] Add new buffer pointer operations --- .../UnsafeBufferPointer+Additions.swift | 48 +++++++++++++++++++ .../UnsafeBufferPointer+Extras.swift.gyb | 6 +++ ...nsafeMutableBufferPointer+Extras.swift.gyb | 13 +++++ .../UnsafeBufferPointer+Extras.swift | 12 +++++ .../UnsafeMutableBufferPointer+Extras.swift | 26 ++++++++++ 5 files changed, 105 insertions(+) create mode 100644 Sources/Future/UnsafeBufferPointer+Additions.swift diff --git a/Sources/Future/UnsafeBufferPointer+Additions.swift b/Sources/Future/UnsafeBufferPointer+Additions.swift new file mode 100644 index 000000000..7b7a5e96f --- /dev/null +++ b/Sources/Future/UnsafeBufferPointer+Additions.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two `UnsafeBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + public static func ===(_ a: Self, _ b: Self) -> Bool { + (a.baseAddress == b.baseAddress) && (a.count == b.count) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + public static func ===(_ a: Self, _ b: Self) -> Bool { + (a.baseAddress == b.baseAddress) && (a.count == b.count) + } +} + +extension UnsafeRawBufferPointer { + /// Returns a Boolean value indicating whether two `UnsafeRawBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + public static func ===(_ a: Self, _ b: Self) -> Bool { + (a.baseAddress == b.baseAddress) && (a.count == b.count) + } +} + +extension UnsafeMutableRawBufferPointer { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableRawBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + public static func ===(_ a: Self, _ b: Self) -> Bool { + (a.baseAddress == b.baseAddress) && (a.count == b.count) + } +} diff --git a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb index 9bceabee8..2f959fb7a 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb @@ -17,6 +17,12 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} extension UnsafeBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + ${modifier} static var _empty: Self { + .init(start: nil, count: 0) + } + @inlinable @inline(__always) ${modifier} func _ptr(at index: Int) -> UnsafePointer { diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index 55089fecb..1b2ef6d1a 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -17,6 +17,19 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + ${modifier} static var _empty: Self { + .init(start: nil, count: 0) + } + + @inlinable + @inline(__always) + ${modifier} func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } + @inlinable ${modifier} func _extracting(first n: Int) -> Self { assert(n >= 0) diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift index 1244d5af8..ed4fec34e 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift @@ -23,6 +23,12 @@ // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE extension UnsafeBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + internal static var _empty: Self { + .init(start: nil, count: 0) + } + @inlinable @inline(__always) internal func _ptr(at index: Int) -> UnsafePointer { @@ -47,6 +53,12 @@ extension UnsafeBufferPointer where Element: ~Copyable { #else // !COLLECTIONS_SINGLE_MODULE extension UnsafeBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + public static var _empty: Self { + .init(start: nil, count: 0) + } + @inlinable @inline(__always) public func _ptr(at index: Int) -> UnsafePointer { diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index ee44e0cf7..6adfefd7e 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -23,6 +23,19 @@ // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + internal static var _empty: Self { + .init(start: nil, count: 0) + } + + @inlinable + @inline(__always) + internal func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } + @inlinable internal func _extracting(first n: Int) -> Self { assert(n >= 0) @@ -170,6 +183,19 @@ extension Slice { } #else // !COLLECTIONS_SINGLE_MODULE extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + public static var _empty: Self { + .init(start: nil, count: 0) + } + + @inlinable + @inline(__always) + public func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } + @inlinable public func _extracting(first n: Int) -> Self { assert(n >= 0) From 1a5b0e498151f904a9355b6489d6cecdb8428a8c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 31 Jul 2024 16:20:40 -0700 Subject: [PATCH 061/195] [Future] Add very early prototype for borrowable containers - protocol BorrowingIteratorProtocol - protocol Container - protocol BidirectionalContainer - protocol RandomAccessContainer --- Sources/Future/Containers/Container.swift | 138 ++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Sources/Future/Containers/Container.swift diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift new file mode 100644 index 000000000..bef25aab7 --- /dev/null +++ b/Sources/Future/Containers/Container.swift @@ -0,0 +1,138 @@ +public protocol BorrowingIteratorProtocol: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + mutating func nextChunk(maximumCount: Int) -> dependsOn(self) Span +} + +public protocol Container: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Copyable, ~Escapable + where BorrowingIterator.Element == Element + + borrowing func startBorrowingIteration() -> BorrowingIterator + borrowing func startBorrowingIteration(from start: Index) -> BorrowingIterator + + associatedtype Index: Comparable + + var isEmpty: Bool { get } + var count: Int { get } + + var startIndex: Index { get } + var endIndex: Index { get } + + // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements + @_borrowed subscript(index: Index) -> Element { get } + + func index(after index: Index) -> Index + func formIndex(after i: inout Index) + + func index(at position: borrowing BorrowingIterator) -> Index + + func distance(from start: Index, to end: Index) -> Int + + func index(_ index: Index, offsetBy n: Int) -> Index + + func formIndex( + _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index + ) +} + +public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { + override associatedtype Element: ~Copyable + + func index(before i: Index) -> Index + func formIndex(before i: inout Index) + + @_nonoverride func index(_ i: Index, offsetBy distance: Int) -> Index + @_nonoverride func formIndex( + _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index + ) +} + +public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { + override associatedtype Element: ~Copyable +} + + +extension RandomAccessContainer where Index == Int, Self: ~Copyable { + @inlinable + public func index(after index: Int) -> Int { + // Note: Range checks are deferred until element access. + index + 1 + } + + @inlinable + public func index(before index: Int) -> Int { + // Note: Range checks are deferred until element access. + index - 1 + } + + @inlinable + public func formIndex(after index: inout Int) { + // Note: Range checks are deferred until element access. + index += 1 + } + + @inlinable + public func formIndex(before index: inout Int) { + // Note: Range checks are deferred until element access. + index -= 1 + } + + @inlinable + public func distance(from start: Int, to end: Int) -> Int { + // Note: Range checks are deferred until element access. + end - start + } + + @inlinable + public func index(_ index: Int, offsetBy n: Int) -> Int { + // Note: Range checks are deferred until element access. + index + n + } + + @inlinable + public func formIndex( + _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int + ) { + // Note: Range checks are deferred until element access. + if distance >= 0 { + guard limit >= index else { + index += distance + distance = 0 + return + } + let d = Swift.min(distance, limit - index) + index += d + distance -= d + } else { + guard limit <= index else { + index += distance + distance = 0 + return + } + let d = Swift.max(distance, limit - index) + index += d + distance -= d + } + } +} + +#if false // TODO +public protocol Muterator: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + mutating func nextChunk(maximumCount: Int) -> dependsOn(state) MutableSpan +} + +public protocol MutableContainer: Container, ~Copyable, ~Escapable { + associatedtype MutatingIterationState: ~Copyable, ~Escapable + + mutating func startMutatingIteration() -> MutatingIterationState + + // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements + @_borrowed subscript(index: Index) -> Element { get set } + +} +#endif From 774be1121947baf8108b11f714b01e65877cdb85 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 31 Jul 2024 16:21:00 -0700 Subject: [PATCH 062/195] [Future] Add HypoArray and DynoArray --- Sources/Future/Containers/DynoArray.swift | 181 ++++++++++++++++++ Sources/Future/Containers/HypoArray.swift | 218 ++++++++++++++++++++++ Tests/FutureTests/DynoArrayTests.swift | 180 ++++++++++++++++++ 3 files changed, 579 insertions(+) create mode 100644 Sources/Future/Containers/DynoArray.swift create mode 100644 Sources/Future/Containers/HypoArray.swift create mode 100644 Tests/FutureTests/DynoArrayTests.swift diff --git a/Sources/Future/Containers/DynoArray.swift b/Sources/Future/Containers/DynoArray.swift new file mode 100644 index 000000000..e2516c471 --- /dev/null +++ b/Sources/Future/Containers/DynoArray.swift @@ -0,0 +1,181 @@ +/// A dynamically self-resizing, heap allocated, noncopyable array +/// of potentially noncopyable elements. +@frozen +public struct DynoArray: ~Copyable { + @usableFromInline + internal var _storage: HypoArray + + @inlinable + public init() { + _storage = .init(capacity: 0) + } + + @inlinable + public init(minimumCapacity: Int) { + _storage = .init(capacity: minimumCapacity) + } + + @inlinable + public init(count: Int, initializedBy generator: (Int) -> Element) { + _storage = .init(count: count, initializedBy: generator) + } +} + +extension DynoArray: Sendable where Element: Sendable & ~Copyable {} + +extension DynoArray where Element: ~Copyable { + @inlinable + public var capacity: Int { _storage.capacity } +} + +extension DynoArray: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = HypoArray.BorrowingIterator + public typealias Index = Int + + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(for: _storage, startOffset: 0) + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(for: _storage, startOffset: start) + } + + @inlinable + public var isEmpty: Bool { _storage.isEmpty } + + @inlinable + public var count: Int { _storage.count } + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _storage.count } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + yield _storage[position] + } + @inline(__always) + _modify { + yield &_storage[position] + } + } + + @inlinable + public func index(after i: Int) -> Int { i + 1 } + + @inlinable + public func index(before i: Int) -> Int { i - 1 } + + @inlinable + public func formIndex(after index: inout Int) { + // Note: Range checks are deferred until element access. + index += 1 + } + + @inlinable + public func formIndex(before index: inout Int) { + // Note: Range checks are deferred until element access. + index -= 1 + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + // Note: Range checks are deferred until element access. + position._offset + } + + @inlinable + public func distance(from start: Int, to end: Int) -> Int { + // Note: Range checks are deferred until element access. + end - start + } + + @inlinable + public func index(_ index: Int, offsetBy n: Int) -> Int { + // Note: Range checks are deferred until element access. + index + n + } + + @inlinable + public func formIndex( + _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int + ) { + _storage.formIndex(&index, offsetBy: &distance, limitedBy: limit) + } +} + +extension DynoArray where Element: ~Copyable { + @inlinable + public func borrowElement ( + at index: Int, + by body: (borrowing Element) throws(E) -> R + ) throws(E) -> R { + try _storage.borrowElement(at: index, by: body) + } + + @inlinable + public mutating func updateElement ( + at index: Int, + by body: (inout Element) throws(E) -> R + ) throws(E) -> R { + try _storage.updateElement(at: index, by: body) + } +} + +extension DynoArray where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } +} + +extension DynoArray where Element: ~Copyable { + @inlinable + public mutating func reserveCapacity(_ n: Int) { + _storage.reserveCapacity(n) + } +} + +extension DynoArray where Element: ~Copyable { + @inlinable + internal static func _grow(_ capacity: Int) -> Int { + 2 * capacity + } + + @inlinable + public mutating func _ensureFreeCapacity(_ minimumCapacity: Int) { + guard _storage.freeCapacity < minimumCapacity else { return } + reserveCapacity(max(count + minimumCapacity, Self._grow(capacity))) + } +} + +extension DynoArray where Element: ~Copyable { + @inlinable + public mutating func append(_ item: consuming Element) { + _ensureFreeCapacity(1) + _storage.append(item) + } +} + +extension DynoArray where Element: ~Copyable { + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + _ensureFreeCapacity(1) + _storage.insert(item, at: index) + } +} + +extension DynoArray { + @inlinable + public mutating func append(contentsOf items: some Sequence) { + for item in items { + append(item) + } + } +} diff --git a/Sources/Future/Containers/HypoArray.swift b/Sources/Future/Containers/HypoArray.swift new file mode 100644 index 000000000..a118f40ee --- /dev/null +++ b/Sources/Future/Containers/HypoArray.swift @@ -0,0 +1,218 @@ +/// A manually resizable, heap allocated, noncopyable array of +/// potentially noncopyable elements. +@frozen +public struct HypoArray: ~Copyable { + @usableFromInline + internal var _storage: UnsafeMutableBufferPointer + + @usableFromInline + internal var _count: Int + + @inlinable + public init(capacity: Int) { + precondition(capacity >= 0) + if capacity > 0 { + _storage = .allocate(capacity: capacity) + } else { + _storage = .init(start: nil, count: 0) + } + _count = 0 + } + + @inlinable + public init(count: Int, initializedBy generator: (Int) -> Element) { + _storage = .allocate(capacity: count) + for i in 0 ..< count { + _storage.initializeElement(at: i, to: generator(i)) + } + _count = count + } + + deinit { + _storage.extracting(0 ..< count).deinitialize() + _storage.deallocate() + } +} + +extension HypoArray: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension HypoArray where Element: ~Copyable { + @inlinable + public var capacity: Int { _storage.count } + + @inlinable + public var freeCapacity: Int { capacity - count } + + @inlinable + public var isFull: Bool { freeCapacity == 0 } +} + +extension HypoArray: RandomAccessContainer where Element: ~Copyable { + public struct BorrowingIterator: + BorrowingIteratorProtocol, ~Copyable, ~Escapable + { + @usableFromInline + internal let _items: UnsafeBufferPointer + + @usableFromInline + internal var _offset: Int + + @inlinable + internal init(for array: borrowing HypoArray, startOffset: Int) { + self._items = UnsafeBufferPointer(array._items) + self._offset = startOffset + } + + public mutating func nextChunk( + maximumCount: Int + ) -> dependsOn(self) Span { + let end = _offset + Swift.min(maximumCount, _items.count - _offset) + defer { _offset = end } + let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) + return Span(unsafeElements: chunk, owner: self) + } + } + + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(for: self, startOffset: 0) + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(for: self, startOffset: start) + } + + public typealias Index = Int + + @inlinable + public var isEmpty: Bool { count == 0 } + + @inlinable + public var count: Int { _count } + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { count } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + precondition(position >= 0 && position < _count) + yield _storage[position] + } + @inline(__always) + _modify { + precondition(position >= 0 && position < _count) + yield &_storage[position] + } + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + precondition(position._items === UnsafeBufferPointer(self._items)) + return position._offset + } +} + +extension HypoArray where Element: ~Copyable { + @inlinable + internal var _items: UnsafeMutableBufferPointer { + _storage.extracting(Range(uncheckedBounds: (0, _count))) + } + + @inlinable + internal var _freeSpace: UnsafeMutableBufferPointer { + _storage.extracting(Range(uncheckedBounds: (_count, capacity))) + } +} + +extension HypoArray where Element: ~Copyable { + @inlinable + public mutating func resize(to newCapacity: Int) { + precondition(newCapacity >= count) + guard newCapacity != capacity else { return } + let newStorage: UnsafeMutableBufferPointer = .allocate(capacity: newCapacity) + let i = newStorage.moveInitialize(fromContentsOf: self._items) + assert(i == count) + _storage.deallocate() + _storage = newStorage + } + + @inlinable + public mutating func reserveCapacity(_ n: Int) { + guard capacity < n else { return } + resize(to: n) + } +} + + +extension HypoArray where Element: ~Copyable { + @inlinable + public func borrowElement ( + at index: Int, + by body: (borrowing Element) throws(E) -> R + ) throws(E) -> R { + precondition(index >= 0 && index < _count) + return try body(_storage[index]) + } + + @inlinable + public mutating func updateElement ( + at index: Int, + by body: (inout Element) throws(E) -> R + ) throws(E) -> R { + precondition(index >= 0 && index < _count) + return try body(&_storage[index]) + } +} + +extension HypoArray where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < count) + let old = _storage.moveElement(from: index) + let source = _storage.extracting(index + 1 ..< count) + let target = _storage.extracting(index ..< count - 1) + let i = target.moveInitialize(fromContentsOf: source) + assert(i == target.endIndex) + _count -= 1 + return old + } +} + +extension HypoArray where Element: ~Copyable { + @inlinable + public mutating func append(_ item: consuming Element) { + precondition(!isFull) + _storage.initializeElement(at: _count, to: item) + _count += 1 + } +} + +extension HypoArray where Element: ~Copyable { + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + precondition(!isFull) + if index < count { + let source = _storage.extracting(index ..< count) + let target = _storage.extracting(index + 1 ..< count + 1) + let last = target.moveInitialize(fromContentsOf: source) + assert(last == target.endIndex) + } + _storage.initializeElement(at: index, to: item) + _count += 1 + } +} + +extension HypoArray { + @inlinable + public mutating func append(contentsOf items: some Sequence) { + for item in items { + append(item) + } + } +} diff --git a/Tests/FutureTests/DynoArrayTests.swift b/Tests/FutureTests/DynoArrayTests.swift new file mode 100644 index 000000000..67d285eca --- /dev/null +++ b/Tests/FutureTests/DynoArrayTests.swift @@ -0,0 +1,180 @@ +import XCTest +import _CollectionsTestSupport +import Future + +struct Counted: ~Copyable { + var value: Int + nonisolated(unsafe) static var instances: Int = 0 + + init(_ value: Int) { + self.value = value + Counted.instances += 1 + } + + deinit { + Counted.instances -= 1 + expectGreaterThanOrEqual(Counted.instances, 0) + } +} + +class DynoArrayTests: CollectionTestCase { + + func test_basics() { + var array = DynoArray() + expectTrue(array.isEmpty) + expectEqual(array.count, 0) + expectEqual(array.capacity, 0) + expectEqual(Counted.instances, 0) + + array.append(Counted(42)) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array[0].value, 42) + expectEqual(Counted.instances, 1) + + array.append(Counted(23)) + expectFalse(array.isEmpty) + expectEqual(array.count, 2) + expectEqual(array[0].value, 42) + expectEqual(array[1].value, 23) + expectEqual(Counted.instances, 2) + + let old = array.remove(at: 0) + expectEqual(old.value, 42) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array[0].value, 23) + expectEqual(Counted.instances, 2) + _ = consume old + expectEqual(Counted.instances, 1) + + let old2 = array.remove(at: 0) + expectEqual(old2.value, 23) + expectEqual(array.count, 0) + expectTrue(array.isEmpty) + expectEqual(Counted.instances, 1) + _ = consume old2 + expectEqual(Counted.instances, 0) + } + + func test_read_access() { + let c = 100 + let array = DynoArray(count: c) { Counted($0) } + + for i in 0 ..< c { + expectEqual(array.borrowElement(at: i) { $0.value }, i) + expectEqual(array[i].value, i) + } + } + + func test_update_access() { + let c = 100 + var array = DynoArray(count: c) { Counted($0) } + + for i in 0 ..< c { + array.updateElement(at: i) { $0.value += 100 } + array[i].value += 100 + } + + for i in 0 ..< c { + expectEqual(array[i].value, 200 + i) + } + + expectEqual(Counted.instances, c) + _ = consume array + expectEqual(Counted.instances, 0) + } + + func test_append() { + var array = DynoArray() + let c = 100 + for i in 0 ..< c { + array.append(Counted(100 + i)) + } + expectEqual(Counted.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + // FIXME: unexpected exclusivity violation (rdar://128441125) + //expectEqual(array.borrowElement(at: i) { $0.value }, 100 + i) + expectEqual(array[i].value, 100 + i) + } + + _ = consume array + expectEqual(Counted.instances, 0) + } + + func test_insert() { + var array = DynoArray() + let c = 100 + for i in 0 ..< c { + array.insert(Counted(100 + i), at: 0) + } + expectEqual(Counted.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + // FIXME: unexpected exclusivity violation (rdar://128441125) + //expectEqual(array.borrowElement(at: i) { $0.value }, c + 99 - i) + expectEqual(array[i].value, c + 99 - i) + } + + _ = consume array + expectEqual(Counted.instances, 0) + } + + func test_remove() { + let c = 100 + var array = DynoArray(count: c) { Counted(100 + $0) } + expectEqual(Counted.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + array.remove(at: 0) + expectEqual(array.count, c - 1 - i) + expectEqual(Counted.instances, c - 1 - i) + } + + expectTrue(array.isEmpty) + expectEqual(Counted.instances, 0) + } + + func test_iterate_full() { + let c = 100 + let array = DynoArray(count: c) { Counted(100 + $0) } + + var state = array.startBorrowingIteration() + do { + let span = state.nextChunk(maximumCount: Int.max) + expectEqual(span.count, c) + for i in 0 ..< span.count { + expectEqual(span[i].value, 100 + i) + } + } + do { + let span2 = state.nextChunk(maximumCount: Int.max) + expectEqual(span2.count, 0) + } + } + + func test_iterate_stepped() { + let c = 100 + let array = DynoArray(count: c) { Counted($0) } + + withEvery("stride", in: 1 ... c) { stride in + var state = array.startBorrowingIteration() + var i = 0 + while true { + let span = state.nextChunk(maximumCount: stride) + if span.count == 0 { break } + expectEqual(span.count, i + stride <= c ? stride : c % stride) + for j in 0 ..< span.count { + expectEqual(span[j].value, i) + i += 1 + } + } + expectEqual(i, c) + expectEqual(state.nextChunk(maximumCount: Int.max).count, 0) + } + } +} From 3310a4c21c07c5b4404f391c7707f9d977ce105d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 30 Jul 2024 14:13:16 -0700 Subject: [PATCH 063/195] [Deque] Add HypoDeque and DynoDeque --- Package.swift | 2 +- Sources/DequeModule/Deque+Collection.swift | 12 +- ...Handle.swift => Deque+_UnsafeHandle.swift} | 18 + Sources/DequeModule/Deque._Storage.swift | 8 +- Sources/DequeModule/DynoDeque.swift | 167 ++++ Sources/DequeModule/HypoDeque.swift | 232 +++++ Sources/DequeModule/_UnsafeDequeHandle.swift | 822 ++++++++++++++++++ .../DequeModule/_UnsafeWrappedBuffer.swift | 67 +- 8 files changed, 1287 insertions(+), 41 deletions(-) rename Sources/DequeModule/{Deque._UnsafeHandle.swift => Deque+_UnsafeHandle.swift} (98%) create mode 100644 Sources/DequeModule/DynoDeque.swift create mode 100644 Sources/DequeModule/HypoDeque.swift create mode 100644 Sources/DequeModule/_UnsafeDequeHandle.swift diff --git a/Package.swift b/Package.swift index 21754b2ca..93325e1d2 100644 --- a/Package.swift +++ b/Package.swift @@ -244,7 +244,7 @@ let targets: [CustomTarget] = [ .target( kind: .exported, name: "DequeModule", - dependencies: ["InternalCollectionsUtilities"], + dependencies: ["InternalCollectionsUtilities", "Future"], exclude: ["CMakeLists.txt"]), .target( kind: .test, diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index b56772cb4..e629787e2 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -761,17 +761,7 @@ extension Deque: RangeReplaceableCollection { "Can't insert element at invalid index") _storage.ensureUnique(minimumCapacity: count + 1) _storage.update { target in - if index == 0 { - target.uncheckedPrepend(newElement) - return - } - if index == count { - target.uncheckedAppend(newElement) - return - } - let gap = target.openGap(ofSize: 1, atOffset: index) - assert(gap.first.count == 1) - gap.first.baseAddress!.initialize(to: newElement) + target.uncheckedInsert(newElement, at: index) } } diff --git a/Sources/DequeModule/Deque._UnsafeHandle.swift b/Sources/DequeModule/Deque+_UnsafeHandle.swift similarity index 98% rename from Sources/DequeModule/Deque._UnsafeHandle.swift rename to Sources/DequeModule/Deque+_UnsafeHandle.swift index 8302b635f..ae16f6f05 100644 --- a/Sources/DequeModule/Deque._UnsafeHandle.swift +++ b/Sources/DequeModule/Deque+_UnsafeHandle.swift @@ -469,6 +469,24 @@ extension Deque._UnsafeHandle { // MARK: Insertion extension Deque._UnsafeHandle { + @inlinable + internal mutating func uncheckedInsert( + _ newElement: consuming Element, at offset: Int + ) { + assert(count < capacity) + if offset == 0 { + uncheckedPrepend(newElement) + return + } + if offset == count { + uncheckedAppend(newElement) + return + } + let gap = openGap(ofSize: 1, atOffset: offset) + assert(gap.first.count == 1) + gap.first.baseAddress!.initialize(to: newElement) + } + /// Insert all elements from `newElements` into this deque, starting at /// `offset`. /// diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift index 307801806..165b45a7a 100644 --- a/Sources/DequeModule/Deque._Storage.swift +++ b/Sources/DequeModule/Deque._Storage.swift @@ -119,12 +119,14 @@ extension Deque._Storage { @inlinable @inline(__always) - internal func update(_ body: (_UnsafeHandle) throws -> R) rethrows -> R { + internal func update( + _ body: (inout _UnsafeHandle) throws -> R + ) rethrows -> R { try _buffer.withUnsafeMutablePointers { header, elements in - let handle = _UnsafeHandle(header: header, + var handle = _UnsafeHandle(header: header, elements: elements, isMutable: true) - return try body(handle) + return try body(&handle) } } } diff --git a/Sources/DequeModule/DynoDeque.swift b/Sources/DequeModule/DynoDeque.swift new file mode 100644 index 000000000..4f85a4e3f --- /dev/null +++ b/Sources/DequeModule/DynoDeque.swift @@ -0,0 +1,167 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import _CollectionsUtilities +import Future +#endif + +@frozen +public struct DynoDeque: ~Copyable { + @usableFromInline + internal var _storage: HypoDeque + + @inlinable + public init() { + _storage = .init(capacity: 0) + } + + @inlinable + public init(capacity: Int) { + _storage = .init(capacity: capacity) + } +} + +extension DynoDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension DynoDeque: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = HypoDeque.BorrowingIterator + public typealias Index = Int + + public func startBorrowingIteration() -> BorrowingIterator { + _storage.startBorrowingIteration() + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + _storage.startBorrowingIteration(from: start) + } + + @inlinable + public var isEmpty: Bool { _storage.isEmpty } + + @inlinable + public var count: Int { _storage.count } + + @inlinable + public var startIndex: Int { _storage.startIndex } + + @inlinable + public var endIndex: Int { _storage.endIndex } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + yield _storage[position] + } + @inline(__always) + _modify { + yield &_storage[position] + } + } + + public func index(at position: borrowing BorrowingIterator) -> Int { + _storage.index(at: position) + } +} + +extension DynoDeque where Element: ~Copyable { + @inlinable + internal var _capacity: Int { _storage.capacity } + + @inlinable + internal var _freeCapacity: Int { _storage.freeCapacity } + + @inlinable + internal var _isFull: Bool { _storage.isFull } + + @inlinable + internal mutating func _grow(to minimumCapacity: Int) { + guard minimumCapacity > _capacity else { return } + let c = Swift.max(minimumCapacity, 2 * _capacity) + _storage.resize(to: c) + } + + @inlinable + internal mutating func _ensureFreeCapacity(_ freeCapacity: Int) { + _grow(to: count + freeCapacity) + } +} + +extension DynoDeque where Element: ~Copyable { + @inlinable + public mutating func append(_ newElement: consuming Element) { + _ensureFreeCapacity(1) + _storage.append(newElement) + } + + @inlinable + public mutating func prepend(_ newElement: consuming Element) { + _ensureFreeCapacity(1) + _storage.prepend(newElement) + } + + @inlinable + public mutating func insert(_ newElement: consuming Element, at index: Int) { + _ensureFreeCapacity(1) + _storage.insert(newElement, at: index) + } +} + +extension DynoDeque where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } + + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _storage.removeSubrange(bounds) + } + + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + _storage.removeFirst() + } + + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + _storage.removeLast() + } + + @inlinable + public mutating func removeFirst(_ n: Int) { + _storage.removeFirst(n) + } + + @inlinable + public mutating func removeLast(_ n: Int) { + _storage.removeLast(n) + } + + @inlinable + public mutating func removeAll() { + _storage.removeAll() + } + + @inlinable + public mutating func popFirst() -> Element? { + _storage.popFirst() + } + + @inlinable + public mutating func popLast() -> Element? { + _storage.popLast() + } +} diff --git a/Sources/DequeModule/HypoDeque.swift b/Sources/DequeModule/HypoDeque.swift new file mode 100644 index 000000000..7631a5a57 --- /dev/null +++ b/Sources/DequeModule/HypoDeque.swift @@ -0,0 +1,232 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import _CollectionsUtilities +import Future +#endif + +@frozen +public struct HypoDeque: ~Copyable { + @usableFromInline + internal typealias _Slot = _DequeSlot + + @usableFromInline + internal typealias _Handle = _UnsafeDequeHandle + + @usableFromInline + var _handle: _Handle + + @inlinable + public init(capacity: Int) { + _handle = .allocate(capacity: capacity) + } + + deinit { + _handle.dispose() + } +} + +extension HypoDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension HypoDeque: RandomAccessContainer where Element: ~Copyable { + @frozen + public struct BorrowingIterator: + BorrowingIteratorProtocol, ~Copyable, ~Escapable + { + @usableFromInline + internal let _segments: _UnsafeWrappedBuffer + + @usableFromInline + internal var _offset: Int + + @inlinable + internal init(_for handle: borrowing _Handle, startOffset: Int) { + self._segments = handle.segments() + self._offset = startOffset + } + + @inlinable + public mutating func nextChunk( + maximumCount: Int + ) -> dependsOn(self) Span { + precondition(maximumCount > 0) + if _offset < _segments.first.count { + let d = Swift.min(maximumCount, _segments.first.count - _offset) + let slice = _segments.first.extracting(_offset ..< _offset + d) + _offset += d + return Span(unsafeElements: slice, owner: self) + } + guard let second = _segments.second else { + return Span(unsafeElements: UnsafeBufferPointer._empty, owner: self) + } + let o = _offset - _segments.first.count + let d = Swift.min(maximumCount, second.count - o) + let slice = second.extracting(o ..< o + d) + _offset += d + return Span(unsafeElements: slice, owner: self) + } + } + + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(_for: _handle, startOffset: 0) + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(_for: _handle, startOffset: start) + } + + public typealias Index = Int + + @inlinable + public var isEmpty: Bool { _handle.count == 0 } + + @inlinable + public var count: Int { _handle.count } + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { count } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + precondition(position >= 0 && position < count) + let slot = _handle.slot(forOffset: position) + yield _handle.ptr(at: slot).pointee + } + @inline(__always) + _modify { + precondition(position >= 0 && position < count) + let slot = _handle.slot(forOffset: position) + yield &_handle.mutablePtr(at: slot).pointee + } + } + + public func index(at position: borrowing BorrowingIterator) -> Int { + precondition(_handle.segments().isIdentical(to: position._segments)) + return position._offset + } +} + +extension HypoDeque where Element: ~Copyable { + @inlinable + public var capacity: Int { _handle.capacity } + + @inlinable + public var freeCapacity: Int { capacity - count } + + @inlinable + public var isFull: Bool { count == capacity } + + @inlinable + public mutating func resize(to newCapacity: Int) { + precondition(newCapacity >= count) + guard newCapacity != capacity else { return } + var newHandle = _Handle.allocate(capacity: newCapacity) + newHandle.startSlot = .zero + newHandle.count = _handle.count + let source = _handle.mutableSegments() + let next = newHandle.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + newHandle.moveInitialize(at: next, from: second) + } + self._handle._storage.deallocate() + self._handle = newHandle + } +} + +extension HypoDeque where Element: ~Copyable { + @inlinable + public mutating func append(_ newElement: consuming Element) { + precondition(!isFull, "HypoDeque is full") + _handle.uncheckedAppend(newElement) + } + + @inlinable + public mutating func prepend(_ newElement: consuming Element) { + precondition(!isFull, "HypoDeque is full") + _handle.uncheckedPrepend(newElement) + } + + @inlinable + public mutating func insert(_ newElement: consuming Element, at index: Int) { + precondition(!isFull, "HypoDeque is full") + precondition(index >= 0 && index <= count, + "Can't insert element at invalid index") + _handle.uncheckedInsert(newElement, at: index) + } +} + +extension HypoDeque where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < count, + "Can't remove element at invalid index") + return _handle.uncheckedRemove(at: index) + } + + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + precondition(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index range out of bounds") + _handle.uncheckedRemove(offsets: bounds) + } + + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "Cannot remove first element of an empty HypoDeque") + return _handle.uncheckedRemoveFirst() + } + + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element of an empty HypoDeque") + return _handle.uncheckedRemoveLast() + } + + @inlinable + public mutating func removeFirst(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in a HypoDeque") + _handle.uncheckedRemoveFirst(n) + } + + @inlinable + public mutating func removeLast(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in a HypoDeque") + _handle.uncheckedRemoveLast(n) + } + + @inlinable + public mutating func removeAll() { + _handle.uncheckedRemoveAll() + } + + @inlinable + public mutating func popFirst() -> Element? { + guard !isEmpty else { return nil } + return _handle.uncheckedRemoveFirst() + } + + @inlinable + public mutating func popLast() -> Element? { + guard !isEmpty else { return nil } + return _handle.uncheckedRemoveLast() + } +} diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift new file mode 100644 index 000000000..c2f43f759 --- /dev/null +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -0,0 +1,822 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import _CollectionsUtilities +#endif + +@frozen +@usableFromInline +internal struct _UnsafeDequeHandle: ~Copyable { + @usableFromInline + internal let _storage: UnsafeMutableBufferPointer + + @usableFromInline + internal var count: Int + + @usableFromInline + internal var startSlot: _DequeSlot + + @inlinable + internal static func allocate( + capacity: Int + ) -> Self { + Self( + _storage: capacity > 0 ? .allocate(capacity: capacity) : ._empty, + count: 0, + startSlot: .zero) + } + + @inlinable + internal init( + _storage: UnsafeMutableBufferPointer, + count: Int, + startSlot: _DequeSlot + ) { + self._storage = _storage + self.count = count + self.startSlot = startSlot + } + + @inlinable + internal consuming func dispose() { + _checkInvariants() + if startSlot.position + count <= _storage.count { + _storage._ptr(at: startSlot.position).deinitialize( + count: _storage.count) + } else { + let firstRegion = _storage.count - startSlot.position + _storage._ptr(at: startSlot.position).deinitialize(count: firstRegion) + _storage._ptr(at: 0).deinitialize(count: count - firstRegion) + } + _storage.deallocate() + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable @inline(__always) + internal var _baseAddress: UnsafeMutablePointer { + _storage.baseAddress.unsafelyUnwrapped + } + + @inlinable + internal var capacity: Int { + _storage.count + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { +#if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + precondition(capacity >= 0) + precondition(count >= 0 && count <= capacity) + precondition(startSlot.position >= 0 && startSlot.position <= capacity) + } +#else + @inlinable @inline(__always) + internal func _checkInvariants() {} +#endif // COLLECTIONS_INTERNAL_CHECKS +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @usableFromInline + internal typealias Slot = _DequeSlot + + @inlinable @inline(__always) + internal func ptr(at slot: Slot) -> UnsafePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return UnsafePointer(_baseAddress + slot.position) + } + + @inlinable @inline(__always) + internal mutating func mutablePtr( + at slot: Slot + ) -> UnsafeMutablePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return _baseAddress + slot.position + } + + @inlinable @inline(__always) + internal var mutableBuffer: UnsafeMutableBufferPointer { + mutating get { + _storage + } + } + + @inlinable + internal func buffer(for range: Range) -> UnsafeBufferPointer { + assert(range.upperBound.position <= capacity) + return .init( + start: _baseAddress + range.lowerBound.position, + count: range._count) + } + + @inlinable @inline(__always) + internal mutating func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { + return .init(mutating: buffer(for: range)) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + /// The slot immediately following the last valid one. (`endSlot` refers to + /// the valid slot corresponding to `endIndex`, which is a different thing + /// entirely.) + @inlinable + @inline(__always) + internal var limSlot: Slot { + Slot(at: capacity) + } + + @inlinable + internal func slot(after slot: Slot) -> Slot { + assert(slot.position < capacity) + let position = slot.position + 1 + if position >= capacity { + return Slot(at: 0) + } + return Slot(at: position) + } + + @inlinable + internal func slot(before slot: Slot) -> Slot { + assert(slot.position < capacity) + if slot.position == 0 { return Slot(at: capacity - 1) } + return Slot(at: slot.position - 1) + } + + @inlinable + internal func slot(_ slot: Slot, offsetBy delta: Int) -> Slot { + assert(slot.position <= capacity) + let position = slot.position + delta + if delta >= 0 { + if position >= capacity { return Slot(at: position - capacity) } + } else { + if position < 0 { return Slot(at: position + capacity) } + } + return Slot(at: position) + } + + @inlinable + @inline(__always) + internal var endSlot: Slot { + slot(startSlot, offsetBy: count) + } + + /// Return the storage slot corresponding to the specified offset, which may + /// or may not address an existing element. + @inlinable + internal func slot(forOffset offset: Int) -> Slot { + assert(offset >= 0) + assert(offset <= capacity) // Not `count`! + + // Note: The use of wrapping addition/subscription is justified here by the + // fact that `offset` is guaranteed to fall in the range `0 ..< capacity`. + // Eliminating the overflow checks leads to a measurable speedup for + // random-access subscript operations. (Up to 2x on some microbenchmarks.) + let position = startSlot.position &+ offset + guard position < capacity else { return Slot(at: position &- capacity) } + return Slot(at: position) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal func segments() -> _UnsafeWrappedBuffer { + let wrap = capacity - startSlot.position + if count <= wrap { + return .init(start: ptr(at: startSlot), count: count) + } + return .init(first: ptr(at: startSlot), count: wrap, + second: ptr(at: .zero), count: count - wrap) + } + + @inlinable + internal func segments( + forOffsets offsets: Range + ) -> _UnsafeWrappedBuffer { + assert(offsets.lowerBound >= 0 && offsets.upperBound <= count) + let lower = slot(forOffset: offsets.lowerBound) + let upper = slot(forOffset: offsets.upperBound) + if offsets.count == 0 || lower < upper { + return .init(start: ptr(at: lower), count: offsets.count) + } + return .init(first: ptr(at: lower), count: capacity - lower.position, + second: ptr(at: .zero), count: upper.position) + } + + @inlinable + @inline(__always) + internal mutating func mutableSegments() -> _UnsafeMutableWrappedBuffer { + .init(mutating: segments()) + } + + @inlinable + @inline(__always) + internal mutating func mutableSegments( + forOffsets range: Range + ) -> _UnsafeMutableWrappedBuffer { + .init(mutating: segments(forOffsets: range)) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func availableSegments() -> _UnsafeMutableWrappedBuffer { + let endSlot = self.endSlot + guard count < capacity else { return .init(start: mutablePtr(at: endSlot), count: 0) } + if endSlot < startSlot { return .init(mutableBuffer(for: endSlot ..< startSlot)) } + return .init(mutableBuffer(for: endSlot ..< limSlot), + mutableBuffer(for: .zero ..< startSlot)) + } +} + +extension _UnsafeDequeHandle { + @inlinable + @discardableResult + internal mutating func initialize( + at start: Slot, + from source: UnsafeBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start).initialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + @inline(__always) + @discardableResult + internal mutating func moveInitialize( + at start: Slot, + from source: UnsafeMutableBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start) + .moveInitialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } + + @inlinable + @inline(__always) + @discardableResult + internal mutating func move( + from source: Slot, + to target: Slot, + count: Int + ) -> (source: Slot, target: Slot) { + assert(count >= 0) + assert(source.position + count <= self.capacity) + assert(target.position + count <= self.capacity) + guard count > 0 else { return (source, target) } + mutablePtr(at: target) + .moveInitialize(from: mutablePtr(at: source), count: count) + return (slot(source, offsetBy: count), slot(target, offsetBy: count)) + } +} + + + +extension _UnsafeDequeHandle { + /// Move elements into a new storage instance with the specified minimum + /// capacity. Existing indices in `self` won't necessarily be valid in the + /// result. `self` is left empty. + @inlinable + internal mutating func moveElements(minimumCapacity: Int) -> Deque._Storage { + let count = self.count + assert(minimumCapacity >= count) + let object = _DequeBuffer.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { +#if os(OpenBSD) + let capacity = minimumCapacity +#else + let capacity = $0.capacity +#endif + return _DequeBufferHeader( + capacity: capacity, + count: count, + startSlot: .zero) + }) + let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard count > 0 else { return result } + result.update { target in + let source = self.mutableSegments() + let next = target.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + target.moveInitialize(at: next, from: second) + } + } + self.count = 0 + return result + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal func withUnsafeSegment( + startingAt start: Int, + maximumCount: Int?, + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> (end: Int, result: R) { + assert(start <= count) + guard start < count else { + return try (count, body(UnsafeBufferPointer(start: nil, count: 0))) + } + let endSlot = self.endSlot + + let segmentStart = self.slot(forOffset: start) + let segmentEnd = segmentStart < endSlot ? endSlot : limSlot + let count = Swift.min(maximumCount ?? Int.max, segmentEnd.position - segmentStart.position) + let result = try body(UnsafeBufferPointer(start: ptr(at: segmentStart), count: count)) + return (start + count, result) + } +} + +// MARK: Replacement + +extension _UnsafeDequeHandle where Element: ~Copyable { + /// Replace the elements in `range` with `newElements`. The deque's count must + /// not change as a result of calling this function. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedReplaceInPlace( + inOffsets range: Range, + with newElements: C + ) where C.Element == Element { + assert(range.upperBound <= count) + assert(newElements.count == range.count) + guard !range.isEmpty else { return } + let target = mutableSegments(forOffsets: range) + target.assign(from: newElements) + } +} + +// MARK: Appending + +extension _UnsafeDequeHandle where Element: ~Copyable { + /// Append `element` to this buffer. The buffer must have enough free capacity + /// to insert one new element. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedAppend(_ element: consuming Element) { + assert(count < capacity) + mutablePtr(at: endSlot).initialize(to: element) + count += 1 + } +} + +extension _UnsafeDequeHandle { + /// Append the contents of `source` to this buffer. The buffer must have + /// enough free capacity to insert the new elements. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedAppend(contentsOf source: UnsafeBufferPointer) { + assert(count + source.count <= capacity) + guard source.count > 0 else { return } + let c = self.count + count += source.count + let gap = mutableSegments(forOffsets: c ..< count) + gap.initialize(from: source) + } +} + +// MARK: Prepending + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func uncheckedPrepend(_ element: consuming Element) { + assert(count < capacity) + let slot = self.slot(before: startSlot) + mutablePtr(at: slot).initialize(to: element) + startSlot = slot + count += 1 + } +} + +extension _UnsafeDequeHandle { + /// Prepend the contents of `source` to this buffer. The buffer must have + /// enough free capacity to insert the new elements. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedPrepend(contentsOf source: UnsafeBufferPointer) { + assert(count + source.count <= capacity) + guard source.count > 0 else { return } + let oldStart = startSlot + let newStart = self.slot(startSlot, offsetBy: -source.count) + startSlot = newStart + count += source.count + + let gap = mutableWrappedBuffer(between: newStart, and: oldStart) + gap.initialize(from: source) + } +} + +// MARK: Insertion + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func uncheckedInsert( + _ newElement: consuming Element, at offset: Int + ) { + assert(count < capacity) + if offset == 0 { + uncheckedPrepend(newElement) + return + } + if offset == count { + uncheckedAppend(newElement) + return + } + let gap = openGap(ofSize: 1, atOffset: offset) + assert(gap.first.count == 1) + gap.first.baseAddress!.initialize(to: newElement) + } +} + +extension _UnsafeDequeHandle { + /// Insert all elements from `newElements` into this deque, starting at + /// `offset`. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter newElements: The elements to insert. + /// - Parameter newCount: Must be equal to `newElements.count`. Used to + /// prevent calling `count` more than once. + /// - Parameter offset: The desired offset from the start at which to place + /// the first element. + @inlinable + internal mutating func uncheckedInsert( + contentsOf newElements: __owned C, + count newCount: Int, + atOffset offset: Int + ) where C.Element == Element { + assert(offset <= count) + assert(newElements.count == newCount) + guard newCount > 0 else { return } + let gap = openGap(ofSize: newCount, atOffset: offset) + gap.initialize(from: newElements) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func mutableWrappedBuffer( + between start: Slot, + and end: Slot + ) -> _UnsafeMutableWrappedBuffer { + assert(start.position <= capacity) + assert(end.position <= capacity) + if start < end { + return .init( + start: mutablePtr(at: start), + count: end.position - start.position) + } + return .init( + first: mutablePtr(at: start), count: capacity - start.position, + second: mutablePtr(at: .zero), count: end.position) + } + + /// Slide elements around so that there is a gap of uninitialized slots of + /// size `gapSize` starting at `offset`, and return a (potentially wrapped) + /// buffer holding the newly inserted slots. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter gapSize: The number of uninitialized slots to create. + /// - Parameter offset: The offset from the start at which the uninitialized + /// slots should start. + @inlinable + internal mutating func openGap( + ofSize gapSize: Int, + atOffset offset: Int + ) -> _UnsafeMutableWrappedBuffer { + assert(offset >= 0 && offset <= self.count) + assert(self.count + gapSize <= capacity) + assert(gapSize > 0) + + let headCount = offset + let tailCount = count - offset + if tailCount <= headCount { + // Open the gap by sliding elements to the right. + + let originalEnd = self.slot(startSlot, offsetBy: count) + let newEnd = self.slot(startSlot, offsetBy: count + gapSize) + let gapStart = self.slot(forOffset: offset) + let gapEnd = self.slot(gapStart, offsetBy: gapSize) + + let sourceIsContiguous = gapStart <= originalEnd.orIfZero(capacity) + let targetIsContiguous = gapEnd <= newEnd.orIfZero(capacity) + + if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping; we just need to slide + // elements after the gap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲........ABCD .̲.......ABCDEFGH̲.̲ + // 1) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH......ABCD .̲H......ABCDEFG.̲.̲ + move(from: gapStart, to: gapEnd, count: tailCount) + } else if targetIsContiguous { + // The gap itself will be wrapped. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) E̲FGH.........ABC̲D̲ + // 1) .̲..EFGH......ABC̲D̲ + // 2) .̲CDEFGH......AB.̲.̲ + assert(startSlot > originalEnd.orIfZero(capacity)) + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) + move(from: gapStart, to: gapEnd, count: capacity - gapStart.position) + } else if sourceIsContiguous { + // Opening the gap pushes subsequent elements across the wrap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ........ABC̲D̲E̲FGH. + // 1) GH......ABC̲D̲E̲F... + // 2) GH......AB.̲.̲.̲CDEF + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: newEnd.position) + move(from: gapStart, to: gapEnd, count: tailCount - newEnd.position) + } else { + // The rest of the items are wrapped, and will remain so. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) GH.........AB̲C̲D̲EF + // 1) ...GH......AB̲C̲D̲EF + // 2) DEFGH......AB̲C̲.̲.. + // 3) DEFGH......A.̲.̲.̲BC + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) + move(from: gapStart, to: gapEnd, count: tailCount - gapSize - originalEnd.position) + } + count += gapSize + return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) + } + + // Open the gap by sliding elements to the left. + + let originalStart = self.startSlot + let newStart = self.slot(originalStart, offsetBy: -gapSize) + let gapEnd = self.slot(forOffset: offset) + let gapStart = self.slot(gapEnd, offsetBy: -gapSize) + + let sourceIsContiguous = originalStart <= gapEnd.orIfZero(capacity) + let targetIsContiguous = newStart <= gapStart.orIfZero(capacity) + + if sourceIsContiguous && targetIsContiguous { + // No need to deal with any wrapping. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ....A̲B̲C̲DEFGH... GH.........̲A̲B̲CDEF .̲A̲B̲CDEFGH.......̲.̲ + // 1) .ABC.̲.̲.̲DEFGH... GH......AB.̲.̲.̲CDEF .̲.̲.̲CDEFGH....AB.̲.̲ + move(from: originalStart, to: newStart, count: headCount) + } else if targetIsContiguous { + // The gap itself will be wrapped. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) C̲D̲EFGH.........A̲B̲ + // 1) C̲D̲EFGH.....AB...̲.̲ + // 2) .̲.̲EFGH.....ABCD.̲.̲ + assert(originalStart >= newStart) + move(from: originalStart, to: newStart, count: capacity - originalStart.position) + move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapEnd.position) + } else if sourceIsContiguous { + // Opening the gap pushes preceding elements across the wrap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) .AB̲C̲D̲EFGH......... + // 1) ...̲C̲D̲EFGH.......AB + // 2) CD.̲.̲.̲EFGH.......AB + move(from: originalStart, to: newStart, count: capacity - newStart.position) + move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) + } else { + // The preceding of the items are wrapped, and will remain so. + + // Illustrated steps: (underscores mark eventual gap position) + // 0) CD̲E̲F̲GHIJKL.........AB + // 1) CD̲E̲F̲GHIJKL......AB... + // 2) ..̲.̲F̲GHIJKL......ABCDE + // 3) F.̲.̲.̲GHIJKL......ABCDE + move(from: originalStart, to: newStart, count: capacity - originalStart.position) + move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapSize) + move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) + } + startSlot = newStart + count += gapSize + return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) + } +} + +// MARK: Removal + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func uncheckedRemove(at offset: Int) -> Element { + let slot = self.slot(forOffset: offset) + let result = mutablePtr(at: slot).move() + closeGap(offsets: Range(uncheckedBounds: (offset, offset + 1))) + return result + } + + @inlinable + internal mutating func uncheckedRemoveFirst() -> Element { + assert(count > 0) + let result = mutablePtr(at: startSlot).move() + startSlot = slot(after: startSlot) + count -= 1 + return result + } + + @inlinable + internal mutating func uncheckedRemoveLast() -> Element { + assert(count > 0) + let slot = self.slot(forOffset: count - 1) + let result = mutablePtr(at: slot).move() + count -= 1 + return result + } + + @inlinable + internal mutating func uncheckedRemoveFirst(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: 0 ..< n) + target.deinitialize() + startSlot = slot(startSlot, offsetBy: n) + count -= n + } + + @inlinable + internal mutating func uncheckedRemoveLast(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: count - n ..< count) + target.deinitialize() + count -= n + } + + /// Remove all elements stored in this instance, deinitializing their storage. + /// + /// This method does not ensure that the storage buffer is uniquely + /// referenced. + @inlinable + internal mutating func uncheckedRemoveAll() { + guard count > 0 else { return } + let target = mutableSegments() + target.deinitialize() + count = 0 + startSlot = .zero + } + + /// Remove all elements in `bounds`, deinitializing their storage and sliding + /// remaining elements to close the resulting gap. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedRemove(offsets bounds: Range) { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + + // Deinitialize elements in `bounds`. + mutableSegments(forOffsets: bounds).deinitialize() + closeGap(offsets: bounds) + } + + /// Close the gap of already uninitialized elements in `bounds`, sliding + /// elements outside of the gap to eliminate it, and updating `count` to + /// reflect the removal. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func closeGap(offsets bounds: Range) { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + let gapSize = bounds.count + guard gapSize > 0 else { return } + + let gapStart = self.slot(forOffset: bounds.lowerBound) + let gapEnd = self.slot(forOffset: bounds.upperBound) + + let headCount = bounds.lowerBound + let tailCount = count - bounds.upperBound + + if headCount >= tailCount { + // Close the gap by sliding elements to the left. + let originalEnd = endSlot + let newEnd = self.slot(forOffset: count - gapSize) + + let sourceIsContiguous = gapEnd < originalEnd.orIfZero(capacity) + let targetIsContiguous = gapStart <= newEnd.orIfZero(capacity) + if tailCount == 0 { + // No need to move any elements. + } else if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping. + + // 0) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH........ABCD .̲.̲.̲E..........ABCD.̲.̲ .̲.̲.̲EF........ABCD .̲.̲.̲DE.......ABC + // 1) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲..........ABCD .̲.̲.̲...........ABCDE̲.̲ E̲F̲.̲..........ABCD D̲E̲.̲.........ABC + move(from: gapEnd, to: gapStart, count: tailCount) + } else if sourceIsContiguous { + // The gap lies across the wrap from the subsequent elements. + + // 0) .̲.̲.̲EFGH.......ABCD.̲.̲ EFGH.......ABCD.̲.̲.̲ + // 1) .̲.̲.̲..GH.......ABCDE̲F̲ ..GH.......ABCDE̲F̲G̲ + // 2) G̲H̲.̲...........ABCDE̲F̲ GH.........ABCDE̲F̲G̲ + let c = capacity - gapStart.position + assert(tailCount > c) + let next = move(from: gapEnd, to: gapStart, count: c) + move(from: next.source, to: .zero, count: tailCount - c) + } else if targetIsContiguous { + // We need to move elements across a wrap, but the wrap will + // disappear when we're done. + + // 0) HI....ABCDE.̲.̲.̲FG + // 1) HI....ABCDEF̲G̲.̲.. + // 2) ......ABCDEF̲G̲H̲I. + let next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) + move(from: .zero, to: next.target, count: originalEnd.position) + } else { + // We need to move elements across a wrap that won't go away. + + // 0) HIJKL....ABCDE.̲.̲.̲FG + // 1) HIJKL....ABCDEF̲G̲.̲.. + // 2) ...KL....ABCDEF̲G̲H̲IJ + // 3) KL.......ABCDEF̲G̲H̲IJ + var next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) + next = move(from: .zero, to: next.target, count: gapSize) + move(from: next.source, to: .zero, count: newEnd.position) + } + count -= gapSize + } else { + // Close the gap by sliding elements to the right. + let originalStart = startSlot + let newStart = slot(startSlot, offsetBy: gapSize) + + let sourceIsContiguous = originalStart < gapStart.orIfZero(capacity) + let targetIsContiguous = newStart <= gapEnd.orIfZero(capacity) + + if headCount == 0 { + // No need to move any elements. + } else if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping. + + // 0) ....ABCD.̲.̲.̲EFGH..... EFGH........AB.̲.̲.̲CD .̲.̲.̲CDEFGH.......AB.̲.̲ DEFGH.......ABC.̲.̲ + // 1) .......AB̲C̲D̲EFGH..... EFGH...........̲A̲B̲CD .̲A̲B̲CDEFGH..........̲.̲ DEFGH.........AB̲C̲ ABCDEFGH........̲.̲.̲ + move(from: originalStart, to: newStart, count: headCount) + } else if sourceIsContiguous { + // The gap lies across the wrap from the preceding elements. + + // 0) .̲.̲DEFGH.......ABC.̲.̲ .̲.̲.̲EFGH.......ABCD + // 1) B̲C̲DEFGH.......A...̲.̲ B̲C̲D̲DEFGH......A... + // 2) B̲C̲DEFGH...........̲A̲ B̲C̲D̲DEFGH.........A + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapEnd.position) + move(from: startSlot, to: newStart, count: headCount - gapEnd.position) + } else if targetIsContiguous { + // We need to move elements across a wrap, but the wrap will + // disappear when we're done. + + // 0) CD.̲.̲.̲EFGHI.....AB + // 1) ...̲C̲D̲EFGHI.....AB + // 1) .AB̲C̲D̲EFGHI....... + move(from: .zero, to: gapEnd.advanced(by: -gapStart.position), count: gapStart.position) + move(from: startSlot, to: newStart, count: headCount - gapStart.position) + } else { + // We need to move elements across a wrap that won't go away. + // 0) FG.̲.̲.̲HIJKLMNO....ABCDE + // 1) ...̲F̲G̲HIJKLMNO....ABCDE + // 2) CDE̲F̲G̲HIJKLMNO....AB... + // 3) CDE̲F̲G̲HIJKLMNO.......AB + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: gapStart.position) + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) + move(from: startSlot, to: newStart, count: headCount - gapEnd.position) + } + startSlot = newStart + count -= gapSize + } + } +} diff --git a/Sources/DequeModule/_UnsafeWrappedBuffer.swift b/Sources/DequeModule/_UnsafeWrappedBuffer.swift index 1faefacc0..71c2da763 100644 --- a/Sources/DequeModule/_UnsafeWrappedBuffer.swift +++ b/Sources/DequeModule/_UnsafeWrappedBuffer.swift @@ -11,11 +11,12 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities +import Future #endif @frozen @usableFromInline -internal struct _UnsafeWrappedBuffer { +internal struct _UnsafeWrappedBuffer { @usableFromInline internal let first: UnsafeBufferPointer @@ -54,11 +55,21 @@ internal struct _UnsafeWrappedBuffer { @inlinable internal var count: Int { first.count + (second?.count ?? 0) } + + @inlinable + internal func isIdentical(to other: Self) -> Bool { + guard self.first === other.first else { return false } + switch (self.second, other.second) { + case (nil, nil): return true + case let (a?, b?): return a === b + default: return false + } + } } @frozen @usableFromInline -internal struct _UnsafeMutableWrappedBuffer { +internal struct _UnsafeMutableWrappedBuffer { @usableFromInline internal let first: UnsafeMutableBufferPointer @@ -76,24 +87,6 @@ internal struct _UnsafeMutableWrappedBuffer { assert(first.count > 0 || second == nil) } - @inlinable - @inline(__always) - internal init( - _ first: UnsafeMutableBufferPointer.SubSequence, - _ second: UnsafeMutableBufferPointer? = nil - ) { - self.init(UnsafeMutableBufferPointer(rebasing: first), second) - } - - @inlinable - @inline(__always) - internal init( - _ first: UnsafeMutableBufferPointer, - _ second: UnsafeMutableBufferPointer.SubSequence - ) { - self.init(first, UnsafeMutableBufferPointer(rebasing: second)) - } - @inlinable @inline(__always) internal init( @@ -124,6 +117,26 @@ internal struct _UnsafeMutableWrappedBuffer { } extension _UnsafeMutableWrappedBuffer { + @inlinable + @inline(__always) + internal init( + _ first: UnsafeMutableBufferPointer.SubSequence, + _ second: UnsafeMutableBufferPointer? = nil + ) { + self.init(UnsafeMutableBufferPointer(rebasing: first), second) + } + + @inlinable + @inline(__always) + internal init( + _ first: UnsafeMutableBufferPointer, + _ second: UnsafeMutableBufferPointer.SubSequence + ) { + self.init(first, UnsafeMutableBufferPointer(rebasing: second)) + } +} + +extension _UnsafeMutableWrappedBuffer where Element: ~Copyable { @inlinable @inline(__always) internal var count: Int { first.count + (second?.count ?? 0) } @@ -135,9 +148,9 @@ extension _UnsafeMutableWrappedBuffer { return self } if n <= first.count { - return Self(first.prefix(n)) + return Self(first._extracting(first: n)) } - return Self(first, second!.prefix(n - first.count)) + return Self(first, second!._extracting(first: n - first.count)) } @inlinable @@ -147,22 +160,24 @@ extension _UnsafeMutableWrappedBuffer { return self } guard let second = second else { - return Self(first.suffix(n)) + return Self(first._extracting(last: n)) } if n <= second.count { - return Self(second.suffix(n)) + return Self(second._extracting(last: n)) } - return Self(first.suffix(n - second.count), second) + return Self(first._extracting(last: n - second.count), second) } } -extension _UnsafeMutableWrappedBuffer { +extension _UnsafeMutableWrappedBuffer where Element: ~Copyable { @inlinable internal func deinitialize() { first.deinitialize() second?.deinitialize() } +} +extension _UnsafeMutableWrappedBuffer { @inlinable @discardableResult internal func initialize( From 944f4ab1fe7a78840eb76fced0b85898d43742c9 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 31 Jul 2024 16:16:44 -0700 Subject: [PATCH 064/195] [Deque] Delete old `_UnsafeHandle` type --- Sources/DequeModule/Deque+Collection.swift | 9 +- Sources/DequeModule/Deque+Sendable.swift | 12 - Sources/DequeModule/Deque+_UnsafeHandle.swift | 851 ------------------ Sources/DequeModule/Deque._Storage.swift | 142 ++- Sources/DequeModule/Deque.swift | 2 + 5 files changed, 129 insertions(+), 887 deletions(-) delete mode 100644 Sources/DequeModule/Deque+Sendable.swift delete mode 100644 Sources/DequeModule/Deque+_UnsafeHandle.swift diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index e629787e2..278ec1e6b 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -362,7 +362,7 @@ extension Deque: RandomAccessCollection { _storage.ensureUnique() _storage.update { handle in let slot = handle.slot(forOffset: index) - handle.ptr(at: slot).pointee = newValue + handle.mutablePtr(at: slot).pointee = newValue } } @inline(__always) // https://github.com/apple/swift-collections/issues/164 @@ -384,14 +384,14 @@ extension Deque: RandomAccessCollection { // the corresponding slot temporarily uninitialized. return _storage.update { handle in let slot = handle.slot(forOffset: index) - return (slot, handle.ptr(at: slot).move()) + return (slot, handle.mutablePtr(at: slot).move()) } } @inlinable internal mutating func _finalizeModify(_ slot: _Slot, _ value: Element) { _storage.update { handle in - handle.ptr(at: slot).initialize(to: value) + handle.mutablePtr(at: slot).initialize(to: value) } } @@ -591,7 +591,8 @@ extension Deque: RangeReplaceableCollection { _storage.update { handle in assert(handle.startSlot == .zero) if count > 0 { - handle.ptr(at: .zero).initialize(repeating: repeatedValue, count: count) + handle.mutablePtr(at: .zero).initialize( + repeating: repeatedValue, count: count) } handle.count = count } diff --git a/Sources/DequeModule/Deque+Sendable.swift b/Sources/DequeModule/Deque+Sendable.swift deleted file mode 100644 index 882e921e8..000000000 --- a/Sources/DequeModule/Deque+Sendable.swift +++ /dev/null @@ -1,12 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -extension Deque: @unchecked Sendable where Element: Sendable {} diff --git a/Sources/DequeModule/Deque+_UnsafeHandle.swift b/Sources/DequeModule/Deque+_UnsafeHandle.swift deleted file mode 100644 index ae16f6f05..000000000 --- a/Sources/DequeModule/Deque+_UnsafeHandle.swift +++ /dev/null @@ -1,851 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -extension Deque { - @frozen - @usableFromInline - internal struct _UnsafeHandle { - @usableFromInline - let _header: UnsafeMutablePointer<_DequeBufferHeader> - @usableFromInline - let _elements: UnsafeMutablePointer - #if DEBUG - @usableFromInline - let _isMutable: Bool - #endif - - @inlinable - @inline(__always) - init( - header: UnsafeMutablePointer<_DequeBufferHeader>, - elements: UnsafeMutablePointer, - isMutable: Bool - ) { - self._header = header - self._elements = elements - #if DEBUG - self._isMutable = isMutable - #endif - } - } -} - -extension Deque._UnsafeHandle { - @inlinable - @inline(__always) - func assertMutable() { - #if DEBUG - assert(_isMutable) - #endif - } -} - -extension Deque._UnsafeHandle { - @usableFromInline - internal typealias Slot = _DequeSlot - - @inlinable - @inline(__always) - var header: _DequeBufferHeader { - _header.pointee - } - - @inlinable - @inline(__always) - var capacity: Int { - _header.pointee.capacity - } - - @inlinable - @inline(__always) - var count: Int { - get { _header.pointee.count } - nonmutating set { _header.pointee.count = newValue } - } - - @inlinable - @inline(__always) - var startSlot: Slot { - get { _header.pointee.startSlot } - nonmutating set { _header.pointee.startSlot = newValue } - } - - @inlinable - @inline(__always) - func ptr(at slot: Slot) -> UnsafeMutablePointer { - assert(slot.position >= 0 && slot.position <= capacity) - return _elements + slot.position - } -} - -extension Deque._UnsafeHandle { - @inlinable - @inline(__always) - var mutableBuffer: UnsafeMutableBufferPointer { - assertMutable() - return .init(start: _elements, count: _header.pointee.capacity) - } - - @inlinable - internal func buffer(for range: Range) -> UnsafeBufferPointer { - assert(range.upperBound.position <= capacity) - return .init(start: _elements + range.lowerBound.position, count: range._count) - } - - @inlinable - @inline(__always) - internal func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { - assertMutable() - return .init(mutating: buffer(for: range)) - } -} - -extension Deque._UnsafeHandle { - /// The slot immediately following the last valid one. (`endSlot` refers to - /// the valid slot corresponding to `endIndex`, which is a different thing - /// entirely.) - @inlinable - @inline(__always) - internal var limSlot: Slot { - Slot(at: capacity) - } - - @inlinable - internal func slot(after slot: Slot) -> Slot { - assert(slot.position < capacity) - let position = slot.position + 1 - if position >= capacity { - return Slot(at: 0) - } - return Slot(at: position) - } - - @inlinable - internal func slot(before slot: Slot) -> Slot { - assert(slot.position < capacity) - if slot.position == 0 { return Slot(at: capacity - 1) } - return Slot(at: slot.position - 1) - } - - @inlinable - internal func slot(_ slot: Slot, offsetBy delta: Int) -> Slot { - assert(slot.position <= capacity) - let position = slot.position + delta - if delta >= 0 { - if position >= capacity { return Slot(at: position - capacity) } - } else { - if position < 0 { return Slot(at: position + capacity) } - } - return Slot(at: position) - } - - @inlinable - @inline(__always) - internal var endSlot: Slot { - slot(startSlot, offsetBy: count) - } - - /// Return the storage slot corresponding to the specified offset, which may - /// or may not address an existing element. - @inlinable - internal func slot(forOffset offset: Int) -> Slot { - assert(offset >= 0) - assert(offset <= capacity) // Not `count`! - - // Note: The use of wrapping addition/subscription is justified here by the - // fact that `offset` is guaranteed to fall in the range `0 ..< capacity`. - // Eliminating the overflow checks leads to a measurable speedup for - // random-access subscript operations. (Up to 2x on some microbenchmarks.) - let position = startSlot.position &+ offset - guard position < capacity else { return Slot(at: position &- capacity) } - return Slot(at: position) - } -} - -extension Deque._UnsafeHandle { - @inlinable - internal func segments() -> _UnsafeWrappedBuffer { - let wrap = capacity - startSlot.position - if count <= wrap { - return .init(start: ptr(at: startSlot), count: count) - } - return .init(first: ptr(at: startSlot), count: wrap, - second: ptr(at: .zero), count: count - wrap) - } - - @inlinable - internal func segments( - forOffsets offsets: Range - ) -> _UnsafeWrappedBuffer { - assert(offsets.lowerBound >= 0 && offsets.upperBound <= count) - let lower = slot(forOffset: offsets.lowerBound) - let upper = slot(forOffset: offsets.upperBound) - if offsets.count == 0 || lower < upper { - return .init(start: ptr(at: lower), count: offsets.count) - } - return .init(first: ptr(at: lower), count: capacity - lower.position, - second: ptr(at: .zero), count: upper.position) - } - - @inlinable - @inline(__always) - internal func mutableSegments() -> _UnsafeMutableWrappedBuffer { - assertMutable() - return .init(mutating: segments()) - } - - @inlinable - @inline(__always) - internal func mutableSegments( - forOffsets range: Range - ) -> _UnsafeMutableWrappedBuffer { - assertMutable() - return .init(mutating: segments(forOffsets: range)) - } -} - -extension Deque._UnsafeHandle { - @inlinable - internal func availableSegments() -> _UnsafeMutableWrappedBuffer { - assertMutable() - let endSlot = self.endSlot - guard count < capacity else { return .init(start: ptr(at: endSlot), count: 0) } - if endSlot < startSlot { return .init(mutableBuffer(for: endSlot ..< startSlot)) } - return .init(mutableBuffer(for: endSlot ..< limSlot), - mutableBuffer(for: .zero ..< startSlot)) - } -} - - - -extension Deque._UnsafeHandle { - @inlinable - @discardableResult - func initialize( - at start: Slot, - from source: UnsafeBufferPointer - ) -> Slot { - assert(start.position + source.count <= capacity) - guard source.count > 0 else { return start } - ptr(at: start).initialize(from: source.baseAddress!, count: source.count) - return Slot(at: start.position + source.count) - } - - @inlinable - @inline(__always) - @discardableResult - func moveInitialize( - at start: Slot, - from source: UnsafeMutableBufferPointer - ) -> Slot { - assert(start.position + source.count <= capacity) - guard source.count > 0 else { return start } - ptr(at: start).moveInitialize(from: source.baseAddress!, count: source.count) - return Slot(at: start.position + source.count) - } - - @inlinable - @inline(__always) - @discardableResult - public func move( - from source: Slot, - to target: Slot, - count: Int - ) -> (source: Slot, target: Slot) { - assert(count >= 0) - assert(source.position + count <= self.capacity) - assert(target.position + count <= self.capacity) - guard count > 0 else { return (source, target) } - ptr(at: target).moveInitialize(from: ptr(at: source), count: count) - return (slot(source, offsetBy: count), slot(target, offsetBy: count)) - } -} - - - -extension Deque._UnsafeHandle { - /// Copy elements into a new storage instance without changing capacity or - /// layout. - @inlinable - internal func copyElements() -> Deque._Storage { - let object = _DequeBuffer.create( - minimumCapacity: capacity, - makingHeaderWith: { _ in header }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard self.count > 0 else { return result } - result.update { target in - let source = self.segments() - target.initialize(at: startSlot, from: source.first) - if let second = source.second { - target.initialize(at: .zero, from: second) - } - } - return result - } - - /// Copy elements into a new storage instance with the specified minimum - /// capacity. - @inlinable - internal func copyElements(minimumCapacity: Int) -> Deque._Storage { - assert(minimumCapacity >= count) - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { - #if os(OpenBSD) - let capacity = minimumCapacity - #else - let capacity = $0.capacity - #endif - return _DequeBufferHeader( - capacity: capacity, - count: count, - startSlot: .zero) - }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard count > 0 else { return result } - result.update { target in - assert(target.count == count && target.startSlot.position == 0) - let source = self.segments() - let next = target.initialize(at: .zero, from: source.first) - if let second = source.second { - target.initialize(at: next, from: second) - } - } - return result - } - - /// Move elements into a new storage instance with the specified minimum - /// capacity. Existing indices in `self` won't necessarily be valid in the - /// result. `self` is left empty. - @inlinable - internal func moveElements(minimumCapacity: Int) -> Deque._Storage { - assertMutable() - let count = self.count - assert(minimumCapacity >= count) - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { - #if os(OpenBSD) - let capacity = minimumCapacity - #else - let capacity = $0.capacity - #endif - return _DequeBufferHeader( - capacity: capacity, - count: count, - startSlot: .zero) - }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard count > 0 else { return result } - result.update { target in - let source = self.mutableSegments() - let next = target.moveInitialize(at: .zero, from: source.first) - if let second = source.second { - target.moveInitialize(at: next, from: second) - } - } - self.count = 0 - return result - } -} - -extension Deque._UnsafeHandle { - @inlinable - internal func withUnsafeSegment( - startingAt start: Int, - maximumCount: Int?, - _ body: (UnsafeBufferPointer) throws -> R - ) rethrows -> (end: Int, result: R) { - assert(start <= count) - guard start < count else { - return try (count, body(UnsafeBufferPointer(start: nil, count: 0))) - } - let endSlot = self.endSlot - - let segmentStart = self.slot(forOffset: start) - let segmentEnd = segmentStart < endSlot ? endSlot : limSlot - let count = Swift.min(maximumCount ?? Int.max, segmentEnd.position - segmentStart.position) - let result = try body(UnsafeBufferPointer(start: ptr(at: segmentStart), count: count)) - return (start + count, result) - } -} - -// MARK: Replacement - -extension Deque._UnsafeHandle { - /// Replace the elements in `range` with `newElements`. The deque's count must - /// not change as a result of calling this function. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func uncheckedReplaceInPlace( - inOffsets range: Range, - with newElements: C - ) where C.Element == Element { - assertMutable() - assert(range.upperBound <= count) - assert(newElements.count == range.count) - guard !range.isEmpty else { return } - let target = mutableSegments(forOffsets: range) - target.assign(from: newElements) - } -} - -// MARK: Appending - -extension Deque._UnsafeHandle { - /// Append `element` to this buffer. The buffer must have enough free capacity - /// to insert one new element. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func uncheckedAppend(_ element: Element) { - assertMutable() - assert(count < capacity) - ptr(at: endSlot).initialize(to: element) - count += 1 - } - - /// Append the contents of `source` to this buffer. The buffer must have - /// enough free capacity to insert the new elements. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func uncheckedAppend(contentsOf source: UnsafeBufferPointer) { - assertMutable() - assert(count + source.count <= capacity) - guard source.count > 0 else { return } - let c = self.count - count += source.count - let gap = mutableSegments(forOffsets: c ..< count) - gap.initialize(from: source) - } -} - -// MARK: Prepending - -extension Deque._UnsafeHandle { - @inlinable - internal func uncheckedPrepend(_ element: Element) { - assertMutable() - assert(count < capacity) - let slot = self.slot(before: startSlot) - ptr(at: slot).initialize(to: element) - startSlot = slot - count += 1 - } - - /// Prepend the contents of `source` to this buffer. The buffer must have - /// enough free capacity to insert the new elements. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func uncheckedPrepend(contentsOf source: UnsafeBufferPointer) { - assertMutable() - assert(count + source.count <= capacity) - guard source.count > 0 else { return } - let oldStart = startSlot - let newStart = self.slot(startSlot, offsetBy: -source.count) - startSlot = newStart - count += source.count - - let gap = mutableWrappedBuffer(between: newStart, and: oldStart) - gap.initialize(from: source) - } -} - -// MARK: Insertion - -extension Deque._UnsafeHandle { - @inlinable - internal mutating func uncheckedInsert( - _ newElement: consuming Element, at offset: Int - ) { - assert(count < capacity) - if offset == 0 { - uncheckedPrepend(newElement) - return - } - if offset == count { - uncheckedAppend(newElement) - return - } - let gap = openGap(ofSize: 1, atOffset: offset) - assert(gap.first.count == 1) - gap.first.baseAddress!.initialize(to: newElement) - } - - /// Insert all elements from `newElements` into this deque, starting at - /// `offset`. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - /// - /// - Parameter newElements: The elements to insert. - /// - Parameter newCount: Must be equal to `newElements.count`. Used to - /// prevent calling `count` more than once. - /// - Parameter offset: The desired offset from the start at which to place - /// the first element. - @inlinable - internal func uncheckedInsert( - contentsOf newElements: __owned C, - count newCount: Int, - atOffset offset: Int - ) where C.Element == Element { - assertMutable() - assert(offset <= count) - assert(newElements.count == newCount) - guard newCount > 0 else { return } - let gap = openGap(ofSize: newCount, atOffset: offset) - gap.initialize(from: newElements) - } - - @inlinable - internal func mutableWrappedBuffer( - between start: Slot, - and end: Slot - ) -> _UnsafeMutableWrappedBuffer { - assert(start.position <= capacity) - assert(end.position <= capacity) - if start < end { - return .init(start: ptr(at: start), count: end.position - start.position) - } - return .init( - first: ptr(at: start), count: capacity - start.position, - second: ptr(at: .zero), count: end.position) - } - - /// Slide elements around so that there is a gap of uninitialized slots of - /// size `gapSize` starting at `offset`, and return a (potentially wrapped) - /// buffer holding the newly inserted slots. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - /// - /// - Parameter gapSize: The number of uninitialized slots to create. - /// - Parameter offset: The offset from the start at which the uninitialized - /// slots should start. - @inlinable - internal func openGap( - ofSize gapSize: Int, - atOffset offset: Int - ) -> _UnsafeMutableWrappedBuffer { - assertMutable() - assert(offset >= 0 && offset <= self.count) - assert(self.count + gapSize <= capacity) - assert(gapSize > 0) - - let headCount = offset - let tailCount = count - offset - if tailCount <= headCount { - // Open the gap by sliding elements to the right. - - let originalEnd = self.slot(startSlot, offsetBy: count) - let newEnd = self.slot(startSlot, offsetBy: count + gapSize) - let gapStart = self.slot(forOffset: offset) - let gapEnd = self.slot(gapStart, offsetBy: gapSize) - - let sourceIsContiguous = gapStart <= originalEnd.orIfZero(capacity) - let targetIsContiguous = gapEnd <= newEnd.orIfZero(capacity) - - if sourceIsContiguous && targetIsContiguous { - // No need to deal with wrapping; we just need to slide - // elements after the gap. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲........ABCD .̲.......ABCDEFGH̲.̲ - // 1) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH......ABCD .̲H......ABCDEFG.̲.̲ - move(from: gapStart, to: gapEnd, count: tailCount) - } else if targetIsContiguous { - // The gap itself will be wrapped. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) E̲FGH.........ABC̲D̲ - // 1) .̲..EFGH......ABC̲D̲ - // 2) .̲CDEFGH......AB.̲.̲ - assert(startSlot > originalEnd.orIfZero(capacity)) - move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) - move(from: gapStart, to: gapEnd, count: capacity - gapStart.position) - } else if sourceIsContiguous { - // Opening the gap pushes subsequent elements across the wrap. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) ........ABC̲D̲E̲FGH. - // 1) GH......ABC̲D̲E̲F... - // 2) GH......AB.̲.̲.̲CDEF - move(from: limSlot.advanced(by: -gapSize), to: .zero, count: newEnd.position) - move(from: gapStart, to: gapEnd, count: tailCount - newEnd.position) - } else { - // The rest of the items are wrapped, and will remain so. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) GH.........AB̲C̲D̲EF - // 1) ...GH......AB̲C̲D̲EF - // 2) DEFGH......AB̲C̲.̲.. - // 3) DEFGH......A.̲.̲.̲BC - move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) - move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) - move(from: gapStart, to: gapEnd, count: tailCount - gapSize - originalEnd.position) - } - count += gapSize - return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) - } - - // Open the gap by sliding elements to the left. - - let originalStart = self.startSlot - let newStart = self.slot(originalStart, offsetBy: -gapSize) - let gapEnd = self.slot(forOffset: offset) - let gapStart = self.slot(gapEnd, offsetBy: -gapSize) - - let sourceIsContiguous = originalStart <= gapEnd.orIfZero(capacity) - let targetIsContiguous = newStart <= gapStart.orIfZero(capacity) - - if sourceIsContiguous && targetIsContiguous { - // No need to deal with any wrapping. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) ....A̲B̲C̲DEFGH... GH.........̲A̲B̲CDEF .̲A̲B̲CDEFGH.......̲.̲ - // 1) .ABC.̲.̲.̲DEFGH... GH......AB.̲.̲.̲CDEF .̲.̲.̲CDEFGH....AB.̲.̲ - move(from: originalStart, to: newStart, count: headCount) - } else if targetIsContiguous { - // The gap itself will be wrapped. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) C̲D̲EFGH.........A̲B̲ - // 1) C̲D̲EFGH.....AB...̲.̲ - // 2) .̲.̲EFGH.....ABCD.̲.̲ - assert(originalStart >= newStart) - move(from: originalStart, to: newStart, count: capacity - originalStart.position) - move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapEnd.position) - } else if sourceIsContiguous { - // Opening the gap pushes preceding elements across the wrap. - - // Illustrated steps: (underscores mark eventual gap position) - // - // 0) .AB̲C̲D̲EFGH......... - // 1) ...̲C̲D̲EFGH.......AB - // 2) CD.̲.̲.̲EFGH.......AB - move(from: originalStart, to: newStart, count: capacity - newStart.position) - move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) - } else { - // The preceding of the items are wrapped, and will remain so. - - // Illustrated steps: (underscores mark eventual gap position) - // 0) CD̲E̲F̲GHIJKL.........AB - // 1) CD̲E̲F̲GHIJKL......AB... - // 2) ..̲.̲F̲GHIJKL......ABCDE - // 3) F.̲.̲.̲GHIJKL......ABCDE - move(from: originalStart, to: newStart, count: capacity - originalStart.position) - move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapSize) - move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) - } - startSlot = newStart - count += gapSize - return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) - } -} - -// MARK: Removal - -extension Deque._UnsafeHandle { - @inlinable - internal func uncheckedRemoveFirst() -> Element { - assertMutable() - assert(count > 0) - let result = ptr(at: startSlot).move() - startSlot = slot(after: startSlot) - count -= 1 - return result - } - - @inlinable - internal func uncheckedRemoveLast() -> Element { - assertMutable() - assert(count > 0) - let slot = self.slot(forOffset: count - 1) - let result = ptr(at: slot).move() - count -= 1 - return result - } - - @inlinable - internal func uncheckedRemoveFirst(_ n: Int) { - assertMutable() - assert(count >= n) - guard n > 0 else { return } - let target = mutableSegments(forOffsets: 0 ..< n) - target.deinitialize() - startSlot = slot(startSlot, offsetBy: n) - count -= n - } - - @inlinable - internal func uncheckedRemoveLast(_ n: Int) { - assertMutable() - assert(count >= n) - guard n > 0 else { return } - let target = mutableSegments(forOffsets: count - n ..< count) - target.deinitialize() - count -= n - } - - /// Remove all elements stored in this instance, deinitializing their storage. - /// - /// This method does not ensure that the storage buffer is uniquely - /// referenced. - @inlinable - internal func uncheckedRemoveAll() { - assertMutable() - guard count > 0 else { return } - let target = mutableSegments() - target.deinitialize() - count = 0 - startSlot = .zero - } - - /// Remove all elements in `bounds`, deinitializing their storage and sliding - /// remaining elements to close the resulting gap. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func uncheckedRemove(offsets bounds: Range) { - assertMutable() - assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) - - // Deinitialize elements in `bounds`. - mutableSegments(forOffsets: bounds).deinitialize() - closeGap(offsets: bounds) - } - - /// Close the gap of already uninitialized elements in `bounds`, sliding - /// elements outside of the gap to eliminate it. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func closeGap(offsets bounds: Range) { - assertMutable() - assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) - let gapSize = bounds.count - guard gapSize > 0 else { return } - - let gapStart = self.slot(forOffset: bounds.lowerBound) - let gapEnd = self.slot(forOffset: bounds.upperBound) - - let headCount = bounds.lowerBound - let tailCount = count - bounds.upperBound - - if headCount >= tailCount { - // Close the gap by sliding elements to the left. - let originalEnd = endSlot - let newEnd = self.slot(forOffset: count - gapSize) - - let sourceIsContiguous = gapEnd < originalEnd.orIfZero(capacity) - let targetIsContiguous = gapStart <= newEnd.orIfZero(capacity) - if tailCount == 0 { - // No need to move any elements. - } else if sourceIsContiguous && targetIsContiguous { - // No need to deal with wrapping. - - // 0) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH........ABCD .̲.̲.̲E..........ABCD.̲.̲ .̲.̲.̲EF........ABCD .̲.̲.̲DE.......ABC - // 1) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲..........ABCD .̲.̲.̲...........ABCDE̲.̲ E̲F̲.̲..........ABCD D̲E̲.̲.........ABC - move(from: gapEnd, to: gapStart, count: tailCount) - } else if sourceIsContiguous { - // The gap lies across the wrap from the subsequent elements. - - // 0) .̲.̲.̲EFGH.......ABCD.̲.̲ EFGH.......ABCD.̲.̲.̲ - // 1) .̲.̲.̲..GH.......ABCDE̲F̲ ..GH.......ABCDE̲F̲G̲ - // 2) G̲H̲.̲...........ABCDE̲F̲ GH.........ABCDE̲F̲G̲ - let c = capacity - gapStart.position - assert(tailCount > c) - let next = move(from: gapEnd, to: gapStart, count: c) - move(from: next.source, to: .zero, count: tailCount - c) - } else if targetIsContiguous { - // We need to move elements across a wrap, but the wrap will - // disappear when we're done. - - // 0) HI....ABCDE.̲.̲.̲FG - // 1) HI....ABCDEF̲G̲.̲.. - // 2) ......ABCDEF̲G̲H̲I. - let next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) - move(from: .zero, to: next.target, count: originalEnd.position) - } else { - // We need to move elements across a wrap that won't go away. - - // 0) HIJKL....ABCDE.̲.̲.̲FG - // 1) HIJKL....ABCDEF̲G̲.̲.. - // 2) ...KL....ABCDEF̲G̲H̲IJ - // 3) KL.......ABCDEF̲G̲H̲IJ - var next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) - next = move(from: .zero, to: next.target, count: gapSize) - move(from: next.source, to: .zero, count: newEnd.position) - } - count -= gapSize - } else { - // Close the gap by sliding elements to the right. - let originalStart = startSlot - let newStart = slot(startSlot, offsetBy: gapSize) - - let sourceIsContiguous = originalStart < gapStart.orIfZero(capacity) - let targetIsContiguous = newStart <= gapEnd.orIfZero(capacity) - - if headCount == 0 { - // No need to move any elements. - } else if sourceIsContiguous && targetIsContiguous { - // No need to deal with wrapping. - - // 0) ....ABCD.̲.̲.̲EFGH..... EFGH........AB.̲.̲.̲CD .̲.̲.̲CDEFGH.......AB.̲.̲ DEFGH.......ABC.̲.̲ - // 1) .......AB̲C̲D̲EFGH..... EFGH...........̲A̲B̲CD .̲A̲B̲CDEFGH..........̲.̲ DEFGH.........AB̲C̲ ABCDEFGH........̲.̲.̲ - move(from: originalStart, to: newStart, count: headCount) - } else if sourceIsContiguous { - // The gap lies across the wrap from the preceding elements. - - // 0) .̲.̲DEFGH.......ABC.̲.̲ .̲.̲.̲EFGH.......ABCD - // 1) B̲C̲DEFGH.......A...̲.̲ B̲C̲D̲DEFGH......A... - // 2) B̲C̲DEFGH...........̲A̲ B̲C̲D̲DEFGH.........A - move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapEnd.position) - move(from: startSlot, to: newStart, count: headCount - gapEnd.position) - } else if targetIsContiguous { - // We need to move elements across a wrap, but the wrap will - // disappear when we're done. - - // 0) CD.̲.̲.̲EFGHI.....AB - // 1) ...̲C̲D̲EFGHI.....AB - // 1) .AB̲C̲D̲EFGHI....... - move(from: .zero, to: gapEnd.advanced(by: -gapStart.position), count: gapStart.position) - move(from: startSlot, to: newStart, count: headCount - gapStart.position) - } else { - // We need to move elements across a wrap that won't go away. - // 0) FG.̲.̲.̲HIJKLMNO....ABCDE - // 1) ...̲F̲G̲HIJKLMNO....ABCDE - // 2) CDE̲F̲G̲HIJKLMNO....AB... - // 3) CDE̲F̲G̲HIJKLMNO.......AB - move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: gapStart.position) - move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) - move(from: startSlot, to: newStart, count: headCount - gapEnd.position) - } - startSlot = newStart - count -= gapSize - } - } -} diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift index 165b45a7a..9962168a9 100644 --- a/Sources/DequeModule/Deque._Storage.swift +++ b/Sources/DequeModule/Deque._Storage.swift @@ -104,28 +104,40 @@ extension Deque._Storage { internal typealias Index = Int @usableFromInline - internal typealias _UnsafeHandle = Deque._UnsafeHandle + internal typealias _UnsafeHandle = _UnsafeDequeHandle @inlinable @inline(__always) - internal func read(_ body: (_UnsafeHandle) throws -> R) rethrows -> R { - try _buffer.withUnsafeMutablePointers { header, elements in - let handle = _UnsafeHandle(header: header, - elements: elements, - isMutable: false) + internal func read( + _ body: (borrowing _UnsafeHandle) throws(E) -> R + ) throws(E) -> R { + try _buffer.withUnsafeMutablePointers { (header, elements) throws(E) in + let handle = _UnsafeHandle( + _storage: .init(start: elements, count: header.pointee.capacity), + count: header.pointee.count, + startSlot: header.pointee.startSlot) return try body(handle) } } @inlinable @inline(__always) - internal func update( - _ body: (inout _UnsafeHandle) throws -> R - ) rethrows -> R { - try _buffer.withUnsafeMutablePointers { header, elements in - var handle = _UnsafeHandle(header: header, - elements: elements, - isMutable: true) + internal func update( + _ body: (inout _UnsafeHandle) throws(E) -> R + ) throws(E) -> R { + try _buffer.withUnsafeMutablePointers { (header, elements) throws(E) in + var handle = _UnsafeHandle( + _storage: .init(start: elements, count: header.pointee.capacity), + count: header.pointee.count, + startSlot: header.pointee.startSlot) + defer { + handle._checkInvariants() + assert( + handle.capacity == header.pointee.capacity + && handle._storage.baseAddress == elements) + header.pointee.count = handle.count + header.pointee.startSlot = handle.startSlot + } return try body(&handle) } } @@ -147,13 +159,105 @@ extension Deque._Storage { @inline(__always) internal mutating func ensureUnique() { if isUnique() { return } - self._makeUniqueCopy() + self = makeUniqueCopy() } + /// Copy elements into a new storage instance without changing capacity or + /// layout. @inlinable @inline(never) - internal mutating func _makeUniqueCopy() { - self = self.read { $0.copyElements() } + internal func makeUniqueCopy() -> Self { + self.read { source in + let object = _DequeBuffer.create( + minimumCapacity: capacity, + makingHeaderWith: { _ in + .init( + capacity: source.capacity, + count: source.count, + startSlot: source.startSlot) + }) + let result = Deque._Storage( + _buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard source.count > 0 else { return result } + result.update { target in + let src = source.segments() + target.initialize(at: startSlot, from: src.first) + if let second = src.second { + target.initialize(at: .zero, from: second) + } + } + return result + } + } + + /// Copy elements into a new storage instance with the specified minimum + /// capacity. This operation does not preserve layout. + @inlinable + internal func makeUniqueCopy(minimumCapacity: Int) -> Self { + assert(minimumCapacity >= count) + return self.read { source in + let object = _DequeBuffer.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { +#if os(OpenBSD) + let c = minimumCapacity +#else + let c = $0.capacity +#endif + return _DequeBufferHeader( + capacity: c, + count: source.count, + startSlot: .zero) + }) + let result = Deque._Storage( + _buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard source.count > 0 else { return result } + result.update { target in + assert(target.count == count && target.startSlot.position == 0) + let src = source.segments() + let next = target.initialize(at: .zero, from: src.first) + if let second = src.second { + target.initialize(at: next, from: second) + } + } + return result + } + } + + /// Move elements into a new storage instance with the specified minimum + /// capacity. Existing indices in `self` won't necessarily be valid in the + /// result. The old `self` is left empty. + @inlinable + internal mutating func resize(to minimumCapacity: Int) -> Self { + self.update { source in + let count = source.count + assert(minimumCapacity >= count) + let object = _DequeBuffer.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { +#if os(OpenBSD) + let c = minimumCapacity +#else + let c = $0.capacity +#endif + return _DequeBufferHeader( + capacity: c, + count: count, + startSlot: .zero) + }) + let result = Deque._Storage( + _buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard count > 0 else { return result } + result.update { target in + let src = source.mutableSegments() + let next = target.moveInitialize(at: .zero, from: src.first) + if let second = src.second { + target.moveInitialize(at: next, from: second) + } + } + source.count = 0 + return result + } } /// The growth factor to use to increase storage size to make place for an @@ -202,7 +306,7 @@ extension Deque._Storage { ) { if capacity >= minimumCapacity { assert(!isUnique) - self = self.read { $0.copyElements() } + self = self.makeUniqueCopy() return } @@ -212,9 +316,7 @@ extension Deque._Storage { source.moveElements(minimumCapacity: minimumCapacity) } } else { - self = self.read { source in - source.copyElements(minimumCapacity: minimumCapacity) - } + self = self.makeUniqueCopy(minimumCapacity: minimumCapacity) } } } diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index 9d6667a36..a0576b94f 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -103,3 +103,5 @@ public struct Deque { self._storage = _Storage(minimumCapacity: minimumCapacity) } } + +extension Deque: @unchecked Sendable where Element: Sendable {} From 7a34d55f641bfaf011b423f44a8a63ed63e79e0d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 31 Jul 2024 16:18:00 -0700 Subject: [PATCH 065/195] [Xcode] Update project - Bump to Xcode 16 - Update file lists - Switch to Swift 6 language mode and enable a bunch of experimental features --- Xcode/Collections.xcodeproj/project.pbxproj | 134 ++++++++++-------- .../xcschemes/Collections.xcscheme | 2 +- Xcode/Shared.xcconfig | 9 +- 3 files changed, 85 insertions(+), 60 deletions(-) diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 2eaac6343..9c89e27af 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -12,8 +12,6 @@ 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; 7D9B859A29E4F74400B291CD /* BitArray+RandomBits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859429E4F74400B291CD /* BitArray+RandomBits.swift */; }; 7D9B859B29E4F74400B291CD /* BitArray+ExpressibleByStringLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859529E4F74400B291CD /* BitArray+ExpressibleByStringLiteral.swift */; }; - 7DB0AE762B6E06B300602A20 /* Specialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB0AE752B6E06B300602A20 /* Specialize.swift */; }; - 7DB0AE772B6E06B300602A20 /* Specialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB0AE752B6E06B300602A20 /* Specialize.swift */; }; 7DE91B2F29CA6721004483EB /* Collections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DE91B2629CA6721004483EB /* Collections.framework */; }; 7DE9200D29CA70F3004483EB /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91E7529CA70F3004483EB /* OrderedDictionary+Equatable.swift */; }; 7DE9200E29CA70F3004483EB /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91E7629CA70F3004483EB /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; @@ -87,7 +85,6 @@ 7DE9205429CA70F3004483EB /* Deque+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */; }; 7DE9205529CA70F3004483EB /* Deque+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */; }; 7DE9205629CA70F3004483EB /* Deque+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */; }; - 7DE9205829CA70F3004483EB /* Deque._UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC429CA70F3004483EB /* Deque._UnsafeHandle.swift */; }; 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */; }; 7DE9205A29CA70F3004483EB /* Deque+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */; }; 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */; }; @@ -354,9 +351,6 @@ 7DE9221C29CA8576004483EB /* RangeReplaceableCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE921F729CA8576004483EB /* RangeReplaceableCollectionTests.swift */; }; 7DE9221D29CA8576004483EB /* DequeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE921F829CA8576004483EB /* DequeTests.swift */; }; 7DE9221E29CA8576004483EB /* MutableCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE921F929CA8576004483EB /* MutableCollectionTests.swift */; }; - 7DEBDAF229CBEE5300ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */; }; - 7DEBDAF429CBEE5300ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */; }; - 7DEBDAF529CBEE5300ADC226 /* UnsafeRawPointer extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */; }; 7DEBDAF829CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */; }; 7DEBDAF929CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD529CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift */; }; 7DEBDAFA29CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD629CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift */; }; @@ -373,9 +367,6 @@ 7DEBDB0F29CBF68900ADC226 /* UInt+reversed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAE829CBEE5300ADC226 /* UInt+reversed.swift */; }; 7DEBDB1029CBF68900ADC226 /* UInt+first and last set bit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAEA29CBEE5300ADC226 /* UInt+first and last set bit.swift */; }; 7DEBDB1129CBF68900ADC226 /* FixedWidthInteger+roundUpToPowerOfTwo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAEB29CBEE5300ADC226 /* FixedWidthInteger+roundUpToPowerOfTwo.swift */; }; - 7DEBDB1229CBF68D00ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */; }; - 7DEBDB1429CBF68D00ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */; }; - 7DEBDB1529CBF68D00ADC226 /* UnsafeRawPointer extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */; }; 7DEBDB1629CBF69F00ADC226 /* _UnsafeBitSet+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDADF29CBEE5300ADC226 /* _UnsafeBitSet+Index.swift */; }; 7DEBDB1729CBF69F00ADC226 /* _UnsafeBitSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAE029CBEE5300ADC226 /* _UnsafeBitSet.swift */; }; 7DEBDB1829CBF69F00ADC226 /* _UnsafeBitSet+_Word.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAE129CBEE5300ADC226 /* _UnsafeBitSet+_Word.swift */; }; @@ -422,6 +413,20 @@ 7DEBDB9129CCE44A00ADC226 /* CollectionTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2429CCE43600ADC226 /* CollectionTestCase.swift */; }; 7DEBDB9229CCE44A00ADC226 /* StringConvertibleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */; }; 7DEBDB9D29CCE73D00ADC226 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91F7B29CA70F3004483EB /* Collections.swift */; }; + 7DEBEC152C5AB45600A1BF15 /* DynoDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */; }; + 7DEBEC162C5AB45600A1BF15 /* HypoDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC142C5AB45600A1BF15 /* HypoDeque.swift */; }; + 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */; }; + 7DEBEC252C5AE43C00A1BF15 /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */; }; + 7DEBEC262C5AE43C00A1BF15 /* HypoArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1A2C5AE43C00A1BF15 /* HypoArray.swift */; }; + 7DEBEC272C5AE43C00A1BF15 /* DynoArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC192C5AE43C00A1BF15 /* DynoArray.swift */; }; + 7DEBEC282C5AE43C00A1BF15 /* Inout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */; }; + 7DEBEC292C5AE43C00A1BF15 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC182C5AE43C00A1BF15 /* Container.swift */; }; + 7DEBEC2A2C5AE43C00A1BF15 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */; }; + 7DEBEC2B2C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */; }; + 7DEBEC2C2C5AE43C00A1BF15 /* RawSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */; }; + 7DEBEC2D2C5AE43C00A1BF15 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */; }; + 7DEBEC2E2C5AE43C00A1BF15 /* Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC222C5AE43C00A1BF15 /* Span.swift */; }; + 7DEBEC2F2C5AE43C00A1BF15 /* ContiguousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -448,8 +453,6 @@ 7D9B859429E4F74400B291CD /* BitArray+RandomBits.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BitArray+RandomBits.swift"; sourceTree = ""; }; 7D9B859529E4F74400B291CD /* BitArray+ExpressibleByStringLiteral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BitArray+ExpressibleByStringLiteral.swift"; sourceTree = ""; }; 7D9B859C29E4F77D00B291CD /* Collections.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Collections.xctestplan; sourceTree = ""; }; - 7DB0AE712B6E067A00602A20 /* Specialize.swift.gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = Specialize.swift.gyb; sourceTree = ""; }; - 7DB0AE752B6E06B300602A20 /* Specialize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Specialize.swift; sourceTree = ""; }; 7DE91B2629CA6721004483EB /* Collections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Collections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7DE91B2E29CA6721004483EB /* CollectionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CollectionsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7DE91E7229CA70F3004483EB /* OrderedCollections.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = OrderedCollections.docc; sourceTree = ""; }; @@ -529,7 +532,6 @@ 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Codable.swift"; sourceTree = ""; }; 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Testing.swift"; sourceTree = ""; }; 7DE91EC329CA70F3004483EB /* DequeModule.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DequeModule.docc; sourceTree = ""; }; - 7DE91EC429CA70F3004483EB /* Deque._UnsafeHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deque._UnsafeHandle.swift; sourceTree = ""; }; 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Descriptions.swift"; sourceTree = ""; }; 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Extras.swift"; sourceTree = ""; }; 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; @@ -815,12 +817,6 @@ 7DE921F729CA8576004483EB /* RangeReplaceableCollectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RangeReplaceableCollectionTests.swift; sourceTree = ""; }; 7DE921F829CA8576004483EB /* DequeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DequeTests.swift; sourceTree = ""; }; 7DE921F929CA8576004483EB /* MutableCollectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutableCollectionTests.swift; sourceTree = ""; }; - 7DEBDAC829CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "UnsafeMutablePointer+SE-0370.swift.gyb"; sourceTree = ""; }; - 7DEBDAC929CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "UnsafeMutableBufferPointer+SE-0370.swift.gyb"; sourceTree = ""; }; - 7DEBDACB29CBEE5200ADC226 /* UnsafeRawPointer extensions.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "UnsafeRawPointer extensions.swift.gyb"; sourceTree = ""; }; - 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeMutableBufferPointer+SE-0370.swift"; sourceTree = ""; }; - 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeMutablePointer+SE-0370.swift"; sourceTree = ""; }; - 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeRawPointer extensions.swift"; sourceTree = ""; }; 7DEBDAD129CBEE5200ADC226 /* Descriptions.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Descriptions.swift.gyb; sourceTree = ""; }; 7DEBDAD229CBEE5200ADC226 /* RandomAccessCollection+Offsets.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "RandomAccessCollection+Offsets.swift.gyb"; sourceTree = ""; }; 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeMutableBufferPointer+Extras.swift"; sourceTree = ""; }; @@ -885,6 +881,20 @@ 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = CollectionsTests.xcconfig; sourceTree = ""; }; 7DEBDB9829CCE4A600ADC226 /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; 7DEBDB9929CCE4A600ADC226 /* Collections.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Collections.xcconfig; sourceTree = ""; }; + 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _UnsafeDequeHandle.swift; sourceTree = ""; }; + 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynoDeque.swift; sourceTree = ""; }; + 7DEBEC142C5AB45600A1BF15 /* HypoDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HypoDeque.swift; sourceTree = ""; }; + 7DEBEC182C5AE43C00A1BF15 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + 7DEBEC192C5AE43C00A1BF15 /* DynoArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynoArray.swift; sourceTree = ""; }; + 7DEBEC1A2C5AE43C00A1BF15 /* HypoArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HypoArray.swift; sourceTree = ""; }; + 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; + 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; }; + 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContiguousStorage.swift; sourceTree = ""; }; + 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inout.swift; sourceTree = ""; }; + 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawSpan.swift; sourceTree = ""; }; + 7DEBEC222C5AE43C00A1BF15 /* Span.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Span.swift; sourceTree = ""; }; + 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafeBufferPointer+Additions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -938,13 +948,14 @@ children = ( 7DE91F2229CA70F3004483EB /* InternalCollectionsUtilities */, 7DE91F3929CA70F3004483EB /* BitCollections */, + 7DE91EB529CA70F3004483EB /* CMakeLists.txt */, 7DE91F7929CA70F3004483EB /* Collections */, 7DE91EB629CA70F3004483EB /* DequeModule */, + 7DEBEC242C5AE43C00A1BF15 /* Future */, 7DE91FA729CA70F3004483EB /* HashTreeCollections */, 7DE9200229CA70F3004483EB /* HeapModule */, 7DE91E7129CA70F3004483EB /* OrderedCollections */, 7DE91ECA29CA70F3004483EB /* RopeModule */, - 7DE91EB529CA70F3004483EB /* CMakeLists.txt */, ); name = Sources; path = ../Sources; @@ -1058,10 +1069,11 @@ 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */, 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */, 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */, + 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */, 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */, - 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */, - 7DE91EC429CA70F3004483EB /* Deque._UnsafeHandle.swift */, + 7DE91EB929CA70F3004483EB /* CMakeLists.txt */, 7DE91EBB29CA70F3004483EB /* Deque.swift */, + 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */, 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */, 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */, 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */, @@ -1073,7 +1085,8 @@ 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */, 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */, 7DE91EC329CA70F3004483EB /* DequeModule.docc */, - 7DE91EB929CA70F3004483EB /* CMakeLists.txt */, + 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */, + 7DEBEC142C5AB45600A1BF15 /* HypoDeque.swift */, ); path = DequeModule; sourceTree = ""; @@ -1253,7 +1266,6 @@ isa = PBXGroup; children = ( 7DEBDAD329CBEE5300ADC226 /* autogenerated */, - 7DEBDAC729CBEE5200ADC226 /* Compatibility */, 7DEBDAE229CBEE5300ADC226 /* IntegerTricks */, 7DEBDADA29CBEE5300ADC226 /* UnsafeBitSet */, 7DE91F3029CA70F3004483EB /* _SortedCollection.swift */, @@ -1261,7 +1273,6 @@ 7DEBDAED29CBEE5300ADC226 /* Debugging.swift.gyb */, 7DEBDAD129CBEE5200ADC226 /* Descriptions.swift.gyb */, 7DEBDAD229CBEE5200ADC226 /* RandomAccessCollection+Offsets.swift.gyb */, - 7DB0AE712B6E067A00602A20 /* Specialize.swift.gyb */, 7DEBDAD929CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift.gyb */, 7DEBDAEC29CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift.gyb */, 7DE91F2E29CA70F3004483EB /* CMakeLists.txt */, @@ -1647,34 +1658,12 @@ path = DequeTests; sourceTree = ""; }; - 7DEBDAC729CBEE5200ADC226 /* Compatibility */ = { - isa = PBXGroup; - children = ( - 7DEBDACC29CBEE5200ADC226 /* autogenerated */, - 7DEBDAC929CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift.gyb */, - 7DEBDAC829CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift.gyb */, - 7DEBDACB29CBEE5200ADC226 /* UnsafeRawPointer extensions.swift.gyb */, - ); - path = Compatibility; - sourceTree = ""; - }; - 7DEBDACC29CBEE5200ADC226 /* autogenerated */ = { - isa = PBXGroup; - children = ( - 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */, - 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */, - 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */, - ); - path = autogenerated; - sourceTree = ""; - }; 7DEBDAD329CBEE5300ADC226 /* autogenerated */ = { isa = PBXGroup; children = ( 7DEBDAD829CBEE5300ADC226 /* Debugging.swift */, 7DEBDAD729CBEE5300ADC226 /* Descriptions.swift */, 7DEBDAD629CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift */, - 7DB0AE752B6E06B300602A20 /* Specialize.swift */, 7DEBDAD529CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift */, 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */, ); @@ -1813,6 +1802,32 @@ name = Xcode; sourceTree = ""; }; + 7DEBEC1B2C5AE43C00A1BF15 /* Containers */ = { + isa = PBXGroup; + children = ( + 7DEBEC182C5AE43C00A1BF15 /* Container.swift */, + 7DEBEC192C5AE43C00A1BF15 /* DynoArray.swift */, + 7DEBEC1A2C5AE43C00A1BF15 /* HypoArray.swift */, + ); + path = Containers; + sourceTree = ""; + }; + 7DEBEC242C5AE43C00A1BF15 /* Future */ = { + isa = PBXGroup; + children = ( + 7DEBEC1B2C5AE43C00A1BF15 /* Containers */, + 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */, + 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */, + 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */, + 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */, + 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */, + 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */, + 7DEBEC222C5AE43C00A1BF15 /* Span.swift */, + 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */, + ); + path = Future; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1870,7 +1885,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1520; + LastUpgradeCheck = 1600; TargetAttributes = { 7DE91B2529CA6721004483EB = { CreatedOnToolsVersion = 14.3; @@ -1904,6 +1919,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7DEBEC252C5AE43C00A1BF15 /* CMakeLists.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1925,7 +1941,6 @@ 7DE9214529CA70F4004483EB /* _HashNode+Structural symmetricDifference.swift in Sources */, 7DE9201029CA70F3004483EB /* OrderedDictionary+Deprecations.swift in Sources */, 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */, - 7DEBDAF529CBEE5300ADC226 /* UnsafeRawPointer extensions.swift in Sources */, 7DE9212729CA70F4004483EB /* TreeSet+Equatable.swift in Sources */, 7DE9213429CA70F4004483EB /* TreeSet+ExpressibleByArrayLiteral.swift in Sources */, 7DE9209729CA70F4004483EB /* _RopePath.swift in Sources */, @@ -2005,7 +2020,6 @@ 7DE9217629CA70F4004483EB /* TreeDictionary+Initializers.swift in Sources */, 7DE9207E29CA70F4004483EB /* BigString+UTF16View.swift in Sources */, 7DEBDAFA29CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift in Sources */, - 7DB0AE762B6E06B300602A20 /* Specialize.swift in Sources */, 7DE9216F29CA70F4004483EB /* TreeDictionary+CustomReflectable.swift in Sources */, 7DE920D729CA70F4004483EB /* BitSet+SetAlgebra isSuperset.swift in Sources */, 7DE920F429CA70F4004483EB /* BitArray+Hashable.swift in Sources */, @@ -2028,6 +2042,9 @@ 7DE920E929CA70F4004483EB /* BitArray+Invariants.swift in Sources */, 7DE9215429CA70F4004483EB /* _HashNode+UnsafeHandle.swift in Sources */, 7DE9209829CA70F4004483EB /* Rope+_Node.swift in Sources */, + 7DEBEC152C5AB45600A1BF15 /* DynoDeque.swift in Sources */, + 7DEBEC162C5AB45600A1BF15 /* HypoDeque.swift in Sources */, + 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */, 7DE9204529CA70F3004483EB /* _HashTable+Constants.swift in Sources */, 7DE9214229CA70F4004483EB /* _HashStack.swift in Sources */, 7DE9206F29CA70F4004483EB /* BigString+Chunk+Breaks.swift in Sources */, @@ -2036,7 +2053,6 @@ 7DE920B529CA70F4004483EB /* _SortedCollection.swift in Sources */, 7DE9206929CA70F4004483EB /* BigString+Chunk+Indexing by UTF16.swift in Sources */, 7DE9202A29CA70F3004483EB /* OrderedSet+Partial SetAlgebra isSuperset.swift in Sources */, - 7DEBDAF229CBEE5300ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */, 7DE9207D29CA70F4004483EB /* BigSubstring+UTF16View.swift in Sources */, 7DEBDAF829CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift in Sources */, 7DE9217229CA70F4004483EB /* TreeDictionary+Sendable.swift in Sources */, @@ -2101,12 +2117,21 @@ 7DE9203229CA70F3004483EB /* OrderedSet+Sendable.swift in Sources */, 7DE9206E29CA70F4004483EB /* BigString+Chunk+Splitting.swift in Sources */, 7DE920D829CA70F4004483EB /* BitSet+SetAlgebra subtracting.swift in Sources */, + 7DEBEC262C5AE43C00A1BF15 /* HypoArray.swift in Sources */, + 7DEBEC272C5AE43C00A1BF15 /* DynoArray.swift in Sources */, + 7DEBEC282C5AE43C00A1BF15 /* Inout.swift in Sources */, + 7DEBEC292C5AE43C00A1BF15 /* Container.swift in Sources */, + 7DEBEC2A2C5AE43C00A1BF15 /* Box.swift in Sources */, + 7DEBEC2B2C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift in Sources */, + 7DEBEC2C2C5AE43C00A1BF15 /* RawSpan.swift in Sources */, + 7DEBEC2D2C5AE43C00A1BF15 /* Cell.swift in Sources */, + 7DEBEC2E2C5AE43C00A1BF15 /* Span.swift in Sources */, + 7DEBEC2F2C5AE43C00A1BF15 /* ContiguousStorage.swift in Sources */, 7DE9209E29CA70F4004483EB /* Rope+ForEachWhile.swift in Sources */, 7DE9217B29CA70F4004483EB /* Heap+Invariants.swift in Sources */, 7DE9212F29CA70F4004483EB /* TreeSet+SetAlgebra formIntersection.swift in Sources */, 7DE920A329CA70F4004483EB /* Rope+Index.swift in Sources */, 7DE9207329CA70F4004483EB /* BigString+RemoveSubrange.swift in Sources */, - 7DE9205829CA70F3004483EB /* Deque._UnsafeHandle.swift in Sources */, 7DE9213529CA70F4004483EB /* TreeSet+SetAlgebra symmetricDifference.swift in Sources */, 7DE920D429CA70F4004483EB /* BitSet+SetAlgebra union.swift in Sources */, 7DE920C329CA70F4004483EB /* BitSet+SetAlgebra formUnion.swift in Sources */, @@ -2168,7 +2193,6 @@ 7DE9206729CA70F4004483EB /* BigString+Builder.swift in Sources */, 7DE9208B29CA70F4004483EB /* RopeMetric.swift in Sources */, 7DE9205A29CA70F3004483EB /* Deque+Extras.swift in Sources */, - 7DEBDAF429CBEE5300ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */, 7DE9205129CA70F3004483EB /* Deque+Hashable.swift in Sources */, 7DE9209929CA70F4004483EB /* Rope+Extract.swift in Sources */, 7DE9202629CA70F3004483EB /* OrderedSet+Partial SetAlgebra isStrictSubset.swift in Sources */, @@ -2259,7 +2283,6 @@ 7DE9221429CA8576004483EB /* OrderedSet Diffing Tests.swift in Sources */, 7DEBDB1D29CBF6B200ADC226 /* RandomAccessCollection+Offsets.swift in Sources */, 7DEBDB8529CCE44A00ADC226 /* AllOnesRandomNumberGenerator.swift in Sources */, - 7DEBDB1429CBF68D00ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */, 7DE921FD29CA8576004483EB /* CombinatoricsChecks.swift in Sources */, 7DEBDB8B29CCE44A00ADC226 /* _CollectionState.swift in Sources */, 7DE9220229CA8576004483EB /* TreeDictionary Tests.swift in Sources */, @@ -2288,12 +2311,10 @@ 7DEBDB7D29CCE44A00ADC226 /* MinimalRangeReplaceableRandomAccessCollection.swift in Sources */, 7DEBDB8E29CCE44A00ADC226 /* Integer Square Root.swift in Sources */, 7DE9221E29CA8576004483EB /* MutableCollectionTests.swift in Sources */, - 7DEBDB1229CBF68D00ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */, 7DE9221529CA8576004483EB /* RandomAccessCollection+Extras.swift in Sources */, 7DEBDB7929CCE44A00ADC226 /* LifetimeTracked.swift in Sources */, 7DE921FC29CA8576004483EB /* IndexRangeCollectionTests.swift in Sources */, 7DE9221229CA8576004483EB /* OrderedDictionary+Elements Tests.swift in Sources */, - 7DB0AE772B6E06B300602A20 /* Specialize.swift in Sources */, 7DEBDB7629CCE44A00ADC226 /* MinimalIndex.swift in Sources */, 7DE9221329CA8576004483EB /* OrderedSetInternals.swift in Sources */, 7DE9220329CA8576004483EB /* Colliders.swift in Sources */, @@ -2325,7 +2346,6 @@ 7DEBDB1829CBF69F00ADC226 /* _UnsafeBitSet+_Word.swift in Sources */, 7DE921B929CA81DC004483EB /* _SortedCollection.swift in Sources */, 7DEBDB1629CBF69F00ADC226 /* _UnsafeBitSet+Index.swift in Sources */, - 7DEBDB1529CBF68D00ADC226 /* UnsafeRawPointer extensions.swift in Sources */, 7DEBDB7B29CCE44A00ADC226 /* IndexRangeCollection.swift in Sources */, 7DE9221929CA8576004483EB /* HeapTests.swift in Sources */, 7DEBDB1729CBF69F00ADC226 /* _UnsafeBitSet.swift in Sources */, @@ -2388,7 +2408,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */; buildSettings = { - DEAD_CODE_STRIPPING = YES; }; name = Debug; }; @@ -2396,7 +2415,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */; buildSettings = { - DEAD_CODE_STRIPPING = YES; }; name = Release; }; diff --git a/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme b/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme index 3266c5aac..88230e523 100644 --- a/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme +++ b/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 5 Aug 2024 18:59:21 -0700 Subject: [PATCH 066/195] =?UTF-8?q?[Future]=20BorrowingIterator:=20Make=20?= =?UTF-8?q?copyable;=20fix=20nextChunk=E2=80=99s=20lifetime=20constraint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/DequeModule/HypoDeque.swift | 6 ++---- Sources/Future/Containers/Container.swift | 8 ++++---- Sources/Future/Containers/HypoArray.swift | 6 ++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Sources/DequeModule/HypoDeque.swift b/Sources/DequeModule/HypoDeque.swift index 7631a5a57..c802b07b2 100644 --- a/Sources/DequeModule/HypoDeque.swift +++ b/Sources/DequeModule/HypoDeque.swift @@ -39,9 +39,7 @@ extension HypoDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} extension HypoDeque: RandomAccessContainer where Element: ~Copyable { @frozen - public struct BorrowingIterator: - BorrowingIteratorProtocol, ~Copyable, ~Escapable - { + public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { @usableFromInline internal let _segments: _UnsafeWrappedBuffer @@ -57,7 +55,7 @@ extension HypoDeque: RandomAccessContainer where Element: ~Copyable { @inlinable public mutating func nextChunk( maximumCount: Int - ) -> dependsOn(self) Span { + ) -> dependsOn(scoped self) Span { precondition(maximumCount > 0) if _offset < _segments.first.count { let d = Swift.min(maximumCount, _segments.first.count - _offset) diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index bef25aab7..40867983a 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -1,13 +1,13 @@ -public protocol BorrowingIteratorProtocol: ~Copyable, ~Escapable { +public protocol BorrowingIteratorProtocol: ~Escapable { associatedtype Element: ~Copyable - mutating func nextChunk(maximumCount: Int) -> dependsOn(self) Span + mutating func nextChunk(maximumCount: Int) -> dependsOn(scoped self) Span } public protocol Container: ~Copyable, ~Escapable { associatedtype Element: ~Copyable - associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Copyable, ~Escapable + associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Escapable where BorrowingIterator.Element == Element borrowing func startBorrowingIteration() -> BorrowingIterator @@ -123,7 +123,7 @@ extension RandomAccessContainer where Index == Int, Self: ~Copyable { public protocol Muterator: ~Copyable, ~Escapable { associatedtype Element: ~Copyable - mutating func nextChunk(maximumCount: Int) -> dependsOn(state) MutableSpan + mutating func nextChunk(maximumCount: Int) -> dependsOn(scoped state) MutableSpan } public protocol MutableContainer: Container, ~Copyable, ~Escapable { diff --git a/Sources/Future/Containers/HypoArray.swift b/Sources/Future/Containers/HypoArray.swift index a118f40ee..06daa52be 100644 --- a/Sources/Future/Containers/HypoArray.swift +++ b/Sources/Future/Containers/HypoArray.swift @@ -48,9 +48,7 @@ extension HypoArray where Element: ~Copyable { } extension HypoArray: RandomAccessContainer where Element: ~Copyable { - public struct BorrowingIterator: - BorrowingIteratorProtocol, ~Copyable, ~Escapable - { + public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { @usableFromInline internal let _items: UnsafeBufferPointer @@ -65,7 +63,7 @@ extension HypoArray: RandomAccessContainer where Element: ~Copyable { public mutating func nextChunk( maximumCount: Int - ) -> dependsOn(self) Span { + ) -> dependsOn(scoped self) Span { let end = _offset + Swift.min(maximumCount, _items.count - _offset) defer { _offset = end } let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) From f64adedbf4b62b42a0ba116bb6e6dfd533d779c9 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 6 Aug 2024 11:33:20 -0700 Subject: [PATCH 067/195] =?UTF-8?q?[Future]=20=5FCollectionsUtilities=20?= =?UTF-8?q?=E2=86=92=20InternalCollectionsUtilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/DequeModule/DynoDeque.swift | 2 +- Sources/DequeModule/HypoDeque.swift | 2 +- Sources/DequeModule/_UnsafeDequeHandle.swift | 2 +- Sources/Future/CMakeLists.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/DequeModule/DynoDeque.swift b/Sources/DequeModule/DynoDeque.swift index 4f85a4e3f..d9282cf17 100644 --- a/Sources/DequeModule/DynoDeque.swift +++ b/Sources/DequeModule/DynoDeque.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if !COLLECTIONS_SINGLE_MODULE -import _CollectionsUtilities +import InternalCollectionsUtilities import Future #endif diff --git a/Sources/DequeModule/HypoDeque.swift b/Sources/DequeModule/HypoDeque.swift index c802b07b2..5d41bb58a 100644 --- a/Sources/DequeModule/HypoDeque.swift +++ b/Sources/DequeModule/HypoDeque.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if !COLLECTIONS_SINGLE_MODULE -import _CollectionsUtilities +import InternalCollectionsUtilities import Future #endif diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index c2f43f759..6b1a6bd3a 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if !COLLECTIONS_SINGLE_MODULE -import _CollectionsUtilities +import InternalCollectionsUtilities #endif @frozen diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index fdd305f74..503d21b84 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(Future ) target_link_libraries(Future PRIVATE - _CollectionsUtilities) + InternalCollectionsUtilities) set_target_properties(Future PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) From 5d51572d69917fa9808a9c63fe14fb4db8a041c5 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 6 Aug 2024 19:17:04 -0700 Subject: [PATCH 068/195] [Deque] Stop using ManagedBuffer Instead, just have a regular class holding a HypoDeque. This requires two allocations per deque, but it makes representation a lot more straightforward. I do not have a workable solution for empty singletons, though. --- Sources/DequeModule/CMakeLists.txt | 10 +- Sources/DequeModule/Deque+Collection.swift | 133 ++++---- Sources/DequeModule/Deque+Equatable.swift | 2 +- Sources/DequeModule/Deque+Extras.swift | 26 +- Sources/DequeModule/Deque+Testing.swift | 25 +- Sources/DequeModule/Deque._Storage.swift | 306 +------------------ Sources/DequeModule/Deque.swift | 122 +++++++- Sources/DequeModule/HypoDeque.swift | 33 +- Sources/DequeModule/_DequeBuffer.swift | 49 --- Sources/DequeModule/_DequeBufferHeader.swift | 49 --- Sources/DequeModule/_DequeSlot.swift | 6 + Sources/DequeModule/_UnsafeDequeHandle.swift | 111 +++---- Sources/Future/CMakeLists.txt | 3 + Tests/DequeTests/DequeTests.swift | 32 +- 14 files changed, 351 insertions(+), 556 deletions(-) delete mode 100644 Sources/DequeModule/_DequeBuffer.swift delete mode 100644 Sources/DequeModule/_DequeBufferHeader.swift diff --git a/Sources/DequeModule/CMakeLists.txt b/Sources/DequeModule/CMakeLists.txt index 07364b66e..3a000181a 100644 --- a/Sources/DequeModule/CMakeLists.txt +++ b/Sources/DequeModule/CMakeLists.txt @@ -14,7 +14,8 @@ else() add_library(DequeModule ${COLLECTIONS_DEQUE_SOURCES}) target_link_libraries(DequeModule PRIVATE - InternalCollectionsUtilities) + InternalCollectionsUtilities + Future) set_target_properties(DequeModule PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) @@ -31,13 +32,12 @@ target_sources(${module_name} PRIVATE "Deque+ExpressibleByArrayLiteral.swift" "Deque+Extras.swift" "Deque+Hashable.swift" - "Deque+Sendable.swift" "Deque+Testing.swift" "Deque._Storage.swift" - "Deque._UnsafeHandle.swift" "Deque.swift" - "_DequeBuffer.swift" - "_DequeBufferHeader.swift" + "DynoDeque.swift" + "HypoDeque.swift" "_DequeSlot.swift" + "_UnsafeDequeHandle.swift" "_UnsafeWrappedBuffer.swift" ) diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index 278ec1e6b..b49fef62f 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -23,7 +23,7 @@ extension Deque: Sequence { @frozen public struct Iterator: IteratorProtocol { @usableFromInline - internal var _storage: Deque._Storage + internal var _base: Deque @usableFromInline internal var _nextSlot: _Slot @@ -32,32 +32,32 @@ extension Deque: Sequence { internal var _endSlot: _Slot @inlinable - internal init(_storage: Deque._Storage, start: _Slot, end: _Slot) { - self._storage = _storage + internal init(_base: Deque, start: _Slot, end: _Slot) { + self._base = _base self._nextSlot = start self._endSlot = end } @inlinable internal init(_base: Deque) { - self = _base._storage.read { handle in + self = _base._read { handle in let start = handle.startSlot let end = Swift.min(start.advanced(by: handle.count), handle.limSlot) - return Self(_storage: _base._storage, start: start, end: end) + return Self(_base: _base, start: start, end: end) } } @inlinable internal init(_base: Deque, from index: Int) { - self = _base._storage.read { handle in + self = _base._read { handle in assert(index >= 0 && index <= handle.count) let start = handle.slot(forOffset: index) if index == handle.count { - return Self(_storage: _base._storage, start: start, end: start) + return Self(_base: _base, start: start, end: start) } var end = handle.endSlot if start >= end { end = handle.limSlot } - return Self(_storage: _base._storage, start: start, end: end) + return Self(_base: _base, start: start, end: end) } } @@ -65,7 +65,7 @@ extension Deque: Sequence { @inline(never) internal mutating func _swapSegment() -> Bool { assert(_nextSlot == _endSlot) - return _storage.read { handle in + return _base._read { handle in let end = handle.endSlot if end == .zero || end == _nextSlot { return false @@ -88,7 +88,7 @@ extension Deque: Sequence { assert(_nextSlot < _endSlot) let slot = _nextSlot _nextSlot = _nextSlot.advanced(by: 1) - return _storage.read { handle in + return _base._read { handle in return handle.ptr(at: slot).pointee } } @@ -104,8 +104,8 @@ extension Deque: Sequence { @inlinable public __consuming func _copyToContiguousArray() -> ContiguousArray { - ContiguousArray(unsafeUninitializedCapacity: _storage.count) { target, count in - _storage.read { source in + ContiguousArray(unsafeUninitializedCapacity: count) { target, count in + _read { source in let segments = source.segments() let c = segments.first.count target[.. ) -> (Iterator, UnsafeMutableBufferPointer.Index) { - _storage.read { source in + _read { source in let segments = source.segments() let c1 = Swift.min(segments.first.count, target.count) target[..( _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { - return try _storage.read { handle in + return try _read { handle in + guard handle.count > 0 else { return nil } let endSlot = handle.startSlot.advanced(by: handle.count) guard endSlot.position <= handle.capacity else { return nil } return try body(handle.buffer(for: handle.startSlot ..< endSlot)) @@ -176,7 +177,7 @@ extension Deque: RandomAccessCollection { /// - Complexity: O(1) @inlinable @inline(__always) - public var count: Int { _storage.count } + public var count: Int { _storage._value.count } /// The position of the first element in a nonempty deque. /// @@ -355,12 +356,12 @@ extension Deque: RandomAccessCollection { public subscript(index: Int) -> Element { get { precondition(index >= 0 && index < count, "Index out of bounds") - return _storage.read { $0.ptr(at: $0.slot(forOffset: index)).pointee } + return _read { $0.ptr(at: $0.slot(forOffset: index)).pointee } } set { precondition(index >= 0 && index < count, "Index out of bounds") - _storage.ensureUnique() - _storage.update { handle in + _ensureUnique() + _update { handle in let slot = handle.slot(forOffset: index) handle.mutablePtr(at: slot).pointee = newValue } @@ -378,11 +379,11 @@ extension Deque: RandomAccessCollection { @inlinable internal mutating func _prepareForModify(at index: Int) -> (_Slot, Element) { - _storage.ensureUnique() + _ensureUnique() // We technically aren't supposed to escape storage pointers out of a // managed buffer, so we escape a `(slot, value)` pair instead, leaving // the corresponding slot temporarily uninitialized. - return _storage.update { handle in + return _update { handle in let slot = handle.slot(forOffset: index) return (slot, handle.mutablePtr(at: slot).move()) } @@ -390,7 +391,7 @@ extension Deque: RandomAccessCollection { @inlinable internal mutating func _finalizeModify(_ slot: _Slot, _ value: Element) { - _storage.update { handle in + _update { handle in handle.mutablePtr(at: slot).initialize(to: value) } } @@ -434,8 +435,8 @@ extension Deque: MutableCollection { public mutating func swapAt(_ i: Int, _ j: Int) { precondition(i >= 0 && i < count, "Index out of bounds") precondition(j >= 0 && j < count, "Index out of bounds") - _storage.ensureUnique() - _storage.update { handle in + _ensureUnique() + _update { handle in let slot1 = handle.slot(forOffset: i) let slot2 = handle.slot(forOffset: j) handle.mutableBuffer.swapAt(slot1.position, slot2.position) @@ -466,8 +467,8 @@ extension Deque: MutableCollection { public mutating func withContiguousMutableStorageIfAvailable( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R? { - _storage.ensureUnique() - return try _storage.update { handle in + _ensureUnique() + return try _update { handle in let endSlot = handle.startSlot.advanced(by: handle.count) guard endSlot.position <= handle.capacity else { // FIXME: Rotate storage such that it becomes contiguous. @@ -506,7 +507,8 @@ extension Deque: RangeReplaceableCollection { /// - Complexity: O(1) @inlinable public init() { - _storage = _Storage() + // FIXME: Can we do empty singletons in this world? Should _storage become optional? + _storage = _Storage(capacity: 0) } /// Reserves enough space to store the specified number of elements. @@ -522,7 +524,7 @@ extension Deque: RangeReplaceableCollection { /// - Complexity: O(`count`) @inlinable public mutating func reserveCapacity(_ minimumCapacity: Int) { - _storage.ensureUnique(minimumCapacity: minimumCapacity, linearGrowth: true) + _ensureUnique(minimumCapacity: minimumCapacity, linearGrowth: true) } /// Replaces a range of elements with the elements in the specified @@ -552,13 +554,13 @@ extension Deque: RangeReplaceableCollection { let removalCount = subrange.count let insertionCount = newElements.count let deltaCount = insertionCount - removalCount - _storage.ensureUnique(minimumCapacity: count + deltaCount) let replacementCount = Swift.min(removalCount, insertionCount) let targetCut = subrange.lowerBound + replacementCount let sourceCut = newElements.index(newElements.startIndex, offsetBy: replacementCount) - _storage.update { target in + _ensureUnique(minimumCapacity: count + deltaCount) + _update { target in target.uncheckedReplaceInPlace( inOffsets: subrange.lowerBound ..< targetCut, with: newElements[..= 0) self.init(minimumCapacity: count) - _storage.update { handle in + _update { handle in assert(handle.startSlot == .zero) if count > 0 { handle.mutablePtr(at: .zero).initialize( @@ -619,9 +621,9 @@ extension Deque: RangeReplaceableCollection { @inlinable public init(_ elements: some Collection) { let c = elements.count - guard c > 0 else { _storage = _Storage(); return } - self._storage = _Storage(minimumCapacity: c) - _storage.update { handle in + guard c > 0 else { self.init(); return } + self.init(_storage: _Storage(capacity: c)) + _update { handle in assert(handle.startSlot == .zero) let target = handle.mutableBuffer(for: .zero ..< _Slot(at: c)) let done: Void? = elements.withContiguousStorageIfAvailable { source in @@ -659,8 +661,8 @@ extension Deque: RangeReplaceableCollection { /// - SeeAlso: `prepend(_:)` @inlinable public mutating func append(_ newElement: Element) { - _storage.ensureUnique(minimumCapacity: count + 1) - _storage.update { + _ensureUnique(minimumCapacity: count + 1) + _update { $0.uncheckedAppend(newElement) } } @@ -682,24 +684,24 @@ extension Deque: RangeReplaceableCollection { @inlinable public mutating func append(contentsOf newElements: some Sequence) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedAppend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedAppend(contentsOf: source) } } if done != nil { return } let underestimatedCount = newElements.underestimatedCount - _storage.ensureUnique(minimumCapacity: count + underestimatedCount) - var it = _storage.update { target in + _ensureUnique(minimumCapacity: count + underestimatedCount) + var it = _update { target in let gaps = target.availableSegments() let (it, copied) = gaps.initialize(fromSequencePrefix: newElements) target.count += copied return it } while let next = it.next() { - _storage.ensureUnique(minimumCapacity: count + 1) - _storage.update { target in + _ensureUnique(minimumCapacity: count + 1) + _update { target in target.uncheckedAppend(next) let gaps = target.availableSegments() target.count += gaps.initialize(fromPrefixOf: &it) @@ -726,15 +728,15 @@ extension Deque: RangeReplaceableCollection { contentsOf newElements: some Collection ) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedAppend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedAppend(contentsOf: source) } } guard done == nil else { return } let c = newElements.count guard c > 0 else { return } - _storage.ensureUnique(minimumCapacity: count + c) - _storage.update { target in + _ensureUnique(minimumCapacity: count + c) + _update { target in let gaps = target.availableSegments().prefix(c) gaps.initialize(from: newElements) target.count += c @@ -760,8 +762,8 @@ extension Deque: RangeReplaceableCollection { public mutating func insert(_ newElement: Element, at index: Int) { precondition(index >= 0 && index <= count, "Can't insert element at invalid index") - _storage.ensureUnique(minimumCapacity: count + 1) - _storage.update { target in + _ensureUnique(minimumCapacity: count + 1) + _update { target in target.uncheckedInsert(newElement, at: index) } } @@ -791,8 +793,8 @@ extension Deque: RangeReplaceableCollection { precondition(index >= 0 && index <= count, "Can't insert elements at an invalid index") let newCount = newElements.count - _storage.ensureUnique(minimumCapacity: count + newCount) - _storage.update { target in + _ensureUnique(minimumCapacity: count + newCount) + _update { target in target.uncheckedInsert(contentsOf: newElements, count: newCount, atOffset: index) } } @@ -817,11 +819,10 @@ extension Deque: RangeReplaceableCollection { public mutating func remove(at index: Int) -> Element { precondition(index >= 0 && index < self.count, "Index out of bounds") // FIXME: Implement storage shrinking - _storage.ensureUnique() - return _storage.update { target in - // FIXME: Add direct implementation & see if it makes a difference - let result = self[index] - target.uncheckedRemove(offsets: index ..< index + 1) + _ensureUnique() + return _update { target in + let result = target.mutablePtr(at: target.slot(forOffset: index)).move() + target.closeGap(offsets: index ..< index + 1) return result } } @@ -843,23 +844,23 @@ extension Deque: RangeReplaceableCollection { public mutating func removeSubrange(_ bounds: Range) { precondition(bounds.lowerBound >= 0 && bounds.upperBound <= self.count, "Index range out of bounds") - _storage.ensureUnique() - _storage.update { $0.uncheckedRemove(offsets: bounds) } + _ensureUnique() + _update { $0.uncheckedRemove(offsets: bounds) } } @inlinable public mutating func _customRemoveLast() -> Element? { precondition(!isEmpty, "Cannot remove last element of an empty Deque") - _storage.ensureUnique() - return _storage.update { $0.uncheckedRemoveLast() } + _ensureUnique() + return _update { $0.uncheckedRemoveLast() } } @inlinable public mutating func _customRemoveLast(_ n: Int) -> Bool { precondition(n >= 0, "Can't remove a negative number of elements") precondition(n <= count, "Can't remove more elements than there are in the Collection") - _storage.ensureUnique() - _storage.update { $0.uncheckedRemoveLast(n) } + _ensureUnique() + _update { $0.uncheckedRemoveLast(n) } return true } @@ -875,8 +876,8 @@ extension Deque: RangeReplaceableCollection { @discardableResult public mutating func removeFirst() -> Element { precondition(!isEmpty, "Cannot remove first element of an empty Deque") - _storage.ensureUnique() - return _storage.update { $0.uncheckedRemoveFirst() } + _ensureUnique() + return _update { $0.uncheckedRemoveFirst() } } /// Removes the specified number of elements from the beginning of the deque. @@ -891,8 +892,8 @@ extension Deque: RangeReplaceableCollection { public mutating func removeFirst(_ n: Int) { precondition(n >= 0, "Can't remove a negative number of elements") precondition(n <= count, "Can't remove more elements than there are in the Collection") - _storage.ensureUnique() - return _storage.update { $0.uncheckedRemoveFirst(n) } + _ensureUnique() + return _update { $0.uncheckedRemoveFirst(n) } } /// Removes all elements from the deque. @@ -904,8 +905,8 @@ extension Deque: RangeReplaceableCollection { @inlinable public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { if keepCapacity { - _storage.ensureUnique() - _storage.update { $0.uncheckedRemoveAll() } + _ensureUnique() + _update { $0.uncheckedRemoveAll() } } else { self = Deque() } diff --git a/Sources/DequeModule/Deque+Equatable.swift b/Sources/DequeModule/Deque+Equatable.swift index 4a068d7fa..6906b9d79 100644 --- a/Sources/DequeModule/Deque+Equatable.swift +++ b/Sources/DequeModule/Deque+Equatable.swift @@ -23,7 +23,7 @@ extension Deque: Equatable where Element: Equatable { } // Test referential equality. - if lhsCount == 0 || left._storage.isIdentical(to: right._storage) { + if lhsCount == 0 || left._isIdentical(to: right) { return true } diff --git a/Sources/DequeModule/Deque+Extras.swift b/Sources/DequeModule/Deque+Extras.swift index 850e493fe..28d5abece 100644 --- a/Sources/DequeModule/Deque+Extras.swift +++ b/Sources/DequeModule/Deque+Extras.swift @@ -45,8 +45,8 @@ extension Deque { initializingWith initializer: (inout UnsafeMutableBufferPointer, inout Int) throws -> Void ) rethrows { - self._storage = .init(minimumCapacity: capacity) - try _storage.update { handle in + self._storage = .init(capacity: capacity) + try _update { handle in handle.startSlot = .zero var count = 0 var buffer = handle.mutableBuffer(for: .zero ..< _Slot(at: capacity)) @@ -76,8 +76,8 @@ extension Deque { // FIXME: Add this to the stdlib on BidirectionalCollection // where Self == Self.SubSequence guard count > 0 else { return nil } - _storage.ensureUnique() - return _storage.update { + _ensureUnique() + return _update { $0.uncheckedRemoveFirst() } } @@ -111,8 +111,8 @@ extension Deque { /// - SeeAlso: `append(_:)` @inlinable public mutating func prepend(_ newElement: Element) { - _storage.ensureUnique(minimumCapacity: count + 1) - return _storage.update { + _ensureUnique(minimumCapacity: count + 1) + return _update { $0.uncheckedPrepend(newElement) } } @@ -138,15 +138,15 @@ extension Deque { contentsOf newElements: some Collection ) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedPrepend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedPrepend(contentsOf: source) } } guard done == nil else { return } let c = newElements.count guard c > 0 else { return } - _storage.ensureUnique(minimumCapacity: count + c) - _storage.update { target in + _ensureUnique(minimumCapacity: count + c) + _update { target in let gaps = target.availableSegments().suffix(c) gaps.initialize(from: newElements) target.count += c @@ -173,8 +173,8 @@ extension Deque { @inlinable public mutating func prepend(contentsOf newElements: some Sequence) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedPrepend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedPrepend(contentsOf: source) } } guard done == nil else { return } @@ -182,7 +182,7 @@ extension Deque { self.append(contentsOf: newElements) let newCount = self.count let c = newCount - originalCount - _storage.update { target in + _update { target in target.startSlot = target.slot(forOffset: originalCount) target.count = target.capacity target.closeGap(offsets: c ..< c + (target.capacity - newCount)) diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index 0136886b9..2e6969566 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -65,24 +65,23 @@ extension Deque { precondition(capacity >= 0) precondition(startSlot >= 0 && (startSlot < capacity || (capacity == 0 && startSlot == 0))) precondition(contents.count <= capacity) + let startSlot = _Slot(at: startSlot) - let buffer = _DequeBuffer.create(minimumCapacity: capacity) { _ in - _DequeBufferHeader(capacity: capacity, count: contents.count, startSlot: startSlot) - } - let storage = Deque._Storage(unsafeDowncast(buffer, to: _DequeBuffer.self)) - if contents.count > 0 { + + var hd = HypoDeque(capacity: capacity) + hd._handle.count = contents.count + hd._handle.startSlot = startSlot + if hd._handle.count > 0 { contents.withUnsafeBufferPointer { source in - storage.update { target in - let segments = target.mutableSegments() - let c = segments.first.count - segments.first.initializeAll(fromContentsOf: source.prefix(c)) - if let second = segments.second { - second.initializeAll(fromContentsOf: source.dropFirst(c)) - } + let segments = hd._handle.mutableSegments() + let c = segments.first.count + segments.first.initializeAll(fromContentsOf: source.prefix(c)) + if let second = segments.second { + second.initializeAll(fromContentsOf: source.dropFirst(c)) } } } - self.init(_storage: storage) + self.init(_storage: _Storage(hd)) assert(self._capacity == capacity) assert(self._startSlot == startSlot.position) assert(self.count == contents.count) diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift index 9962168a9..e2d23527e 100644 --- a/Sources/DequeModule/Deque._Storage.swift +++ b/Sources/DequeModule/Deque._Storage.swift @@ -10,19 +10,20 @@ //===----------------------------------------------------------------------===// extension Deque { - @frozen @usableFromInline - struct _Storage { + internal final class _Storage { @usableFromInline - internal typealias _Buffer = ManagedBufferPointer<_DequeBufferHeader, Element> - - @usableFromInline - internal var _buffer: _Buffer + internal var _value: HypoDeque @inlinable @inline(__always) - internal init(_buffer: _Buffer) { - self._buffer = _buffer + internal init(_ value: consuming HypoDeque) { + self._value = value + } + + @inlinable + internal convenience init(capacity: Int) { + self.init(HypoDeque(capacity: capacity)) } } } @@ -30,301 +31,26 @@ extension Deque { extension Deque._Storage: CustomStringConvertible { @usableFromInline internal var description: String { - "Deque<\(Element.self)>._Storage\(_buffer.header)" + "Deque<\(Element.self)>._Storage\(_value._handle.description)" } } extension Deque._Storage { - @inlinable - internal init() { - self.init(_buffer: _Buffer(unsafeBufferObject: _emptyDequeStorage)) - } - - @inlinable - internal init(_ object: _DequeBuffer) { - self.init(_buffer: _Buffer(unsafeBufferObject: object)) - } - - @inlinable - internal init(minimumCapacity: Int) { - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { - #if os(OpenBSD) - let capacity = minimumCapacity - #else - let capacity = $0.capacity - #endif - return _DequeBufferHeader(capacity: capacity, count: 0, startSlot: .zero) - }) - self.init(_buffer: _Buffer(unsafeBufferObject: object)) - } -} - -extension Deque._Storage { - #if COLLECTIONS_INTERNAL_CHECKS +#if COLLECTIONS_INTERNAL_CHECKS @usableFromInline @inline(never) @_effects(releasenone) internal func _checkInvariants() { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee._checkInvariants() } + _value._checkInvariants() } - #else +#else @inlinable @inline(__always) internal func _checkInvariants() {} - #endif // COLLECTIONS_INTERNAL_CHECKS -} - -extension Deque._Storage { - @inlinable - @inline(__always) - internal var identity: AnyObject { _buffer.buffer } - - - @inlinable - @inline(__always) - internal var capacity: Int { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee.capacity } - } - - @inlinable - @inline(__always) - internal var count: Int { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee.count } - } - - @inlinable - @inline(__always) - internal var startSlot: _DequeSlot { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee.startSlot - } - } -} - -extension Deque._Storage { - @usableFromInline - internal typealias Index = Int - - @usableFromInline - internal typealias _UnsafeHandle = _UnsafeDequeHandle - - @inlinable - @inline(__always) - internal func read( - _ body: (borrowing _UnsafeHandle) throws(E) -> R - ) throws(E) -> R { - try _buffer.withUnsafeMutablePointers { (header, elements) throws(E) in - let handle = _UnsafeHandle( - _storage: .init(start: elements, count: header.pointee.capacity), - count: header.pointee.count, - startSlot: header.pointee.startSlot) - return try body(handle) - } - } - - @inlinable - @inline(__always) - internal func update( - _ body: (inout _UnsafeHandle) throws(E) -> R - ) throws(E) -> R { - try _buffer.withUnsafeMutablePointers { (header, elements) throws(E) in - var handle = _UnsafeHandle( - _storage: .init(start: elements, count: header.pointee.capacity), - count: header.pointee.count, - startSlot: header.pointee.startSlot) - defer { - handle._checkInvariants() - assert( - handle.capacity == header.pointee.capacity - && handle._storage.baseAddress == elements) - header.pointee.count = handle.count - header.pointee.startSlot = handle.startSlot - } - return try body(&handle) - } - } +#endif // COLLECTIONS_INTERNAL_CHECKS } extension Deque._Storage { - /// Return a boolean indicating whether this storage instance is known to have - /// a single unique reference. If this method returns true, then it is safe to - /// perform in-place mutations on the deque. @inlinable - @inline(__always) - internal mutating func isUnique() -> Bool { - _buffer.isUniqueReference() - } + internal var capacity: Int { _value.capacity } - /// Ensure that this storage refers to a uniquely held buffer by copying - /// elements if necessary. @inlinable - @inline(__always) - internal mutating func ensureUnique() { - if isUnique() { return } - self = makeUniqueCopy() - } - - /// Copy elements into a new storage instance without changing capacity or - /// layout. - @inlinable - @inline(never) - internal func makeUniqueCopy() -> Self { - self.read { source in - let object = _DequeBuffer.create( - minimumCapacity: capacity, - makingHeaderWith: { _ in - .init( - capacity: source.capacity, - count: source.count, - startSlot: source.startSlot) - }) - let result = Deque._Storage( - _buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard source.count > 0 else { return result } - result.update { target in - let src = source.segments() - target.initialize(at: startSlot, from: src.first) - if let second = src.second { - target.initialize(at: .zero, from: second) - } - } - return result - } - } - - /// Copy elements into a new storage instance with the specified minimum - /// capacity. This operation does not preserve layout. - @inlinable - internal func makeUniqueCopy(minimumCapacity: Int) -> Self { - assert(minimumCapacity >= count) - return self.read { source in - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { -#if os(OpenBSD) - let c = minimumCapacity -#else - let c = $0.capacity -#endif - return _DequeBufferHeader( - capacity: c, - count: source.count, - startSlot: .zero) - }) - let result = Deque._Storage( - _buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard source.count > 0 else { return result } - result.update { target in - assert(target.count == count && target.startSlot.position == 0) - let src = source.segments() - let next = target.initialize(at: .zero, from: src.first) - if let second = src.second { - target.initialize(at: next, from: second) - } - } - return result - } - } - - /// Move elements into a new storage instance with the specified minimum - /// capacity. Existing indices in `self` won't necessarily be valid in the - /// result. The old `self` is left empty. - @inlinable - internal mutating func resize(to minimumCapacity: Int) -> Self { - self.update { source in - let count = source.count - assert(minimumCapacity >= count) - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { -#if os(OpenBSD) - let c = minimumCapacity -#else - let c = $0.capacity -#endif - return _DequeBufferHeader( - capacity: c, - count: count, - startSlot: .zero) - }) - let result = Deque._Storage( - _buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard count > 0 else { return result } - result.update { target in - let src = source.mutableSegments() - let next = target.moveInitialize(at: .zero, from: src.first) - if let second = src.second { - target.moveInitialize(at: next, from: second) - } - } - source.count = 0 - return result - } - } - - /// The growth factor to use to increase storage size to make place for an - /// insertion. - @inlinable - @inline(__always) - internal static var growthFactor: Double { 1.5 } - - @usableFromInline - internal func _growCapacity( - to minimumCapacity: Int, - linearly: Bool - ) -> Int { - if linearly { return Swift.max(capacity, minimumCapacity) } - return Swift.max(Int((Self.growthFactor * Double(capacity)).rounded(.up)), - minimumCapacity) - } - - /// Ensure that we have a uniquely referenced buffer with enough space to - /// store at least `minimumCapacity` elements. - /// - /// - Parameter minimumCapacity: The minimum number of elements the buffer - /// needs to be able to hold on return. - /// - /// - Parameter linearGrowth: If true, then don't use an exponential growth - /// factor when reallocating the buffer -- just allocate space for the - /// requested number of elements - @inlinable - @inline(__always) - internal mutating func ensureUnique( - minimumCapacity: Int, - linearGrowth: Bool = false - ) { - let unique = isUnique() - if _slowPath(capacity < minimumCapacity || !unique) { - _ensureUnique(isUnique: unique, minimumCapacity: minimumCapacity, linearGrowth: linearGrowth) - } - } - - @inlinable - @inline(never) - internal mutating func _ensureUnique( - isUnique: Bool, - minimumCapacity: Int, - linearGrowth: Bool - ) { - if capacity >= minimumCapacity { - assert(!isUnique) - self = self.makeUniqueCopy() - return - } - - let minimumCapacity = _growCapacity(to: minimumCapacity, linearly: linearGrowth) - if isUnique { - self = self.update { source in - source.moveElements(minimumCapacity: minimumCapacity) - } - } else { - self = self.makeUniqueCopy(minimumCapacity: minimumCapacity) - } - } -} - -extension Deque._Storage { - @inlinable - @inline(__always) - internal func isIdentical(to other: Self) -> Bool { - self._buffer.buffer === other._buffer.buffer - } + internal var startSlot: _DequeSlot { _value._handle.startSlot } } diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index a0576b94f..096dccf4b 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -100,8 +100,128 @@ public struct Deque { /// storage buffer. @inlinable public init(minimumCapacity: Int) { - self._storage = _Storage(minimumCapacity: minimumCapacity) + self._storage = _Storage(capacity: minimumCapacity) } } extension Deque: @unchecked Sendable where Element: Sendable {} + +extension Deque { + @usableFromInline + internal typealias _UnsafeHandle = _UnsafeDequeHandle + + @inlinable + @inline(__always) + internal func _read( + _ body: (borrowing _UnsafeHandle) throws(E) -> R + ) throws(E) -> R { + try body(_storage._value._handle) + } + + @inlinable + @inline(__always) + internal mutating func _update( + _ body: (inout _UnsafeHandle) throws(E) -> R + ) throws(E) -> R { + defer { _fixLifetime(self) } + assert(_isUnique()) + return try body(&_storage._value._handle) + } + + /// Return a boolean indicating whether this storage instance is known to have + /// a single unique reference. If this method returns true, then it is safe to + /// perform in-place mutations on the deque. + @inlinable + internal mutating func _isUnique() -> Bool { + isKnownUniquelyReferenced(&_storage) + } + + /// Ensure that this storage refers to a uniquely held buffer by copying + /// elements if necessary. + @inlinable + @inline(__always) + internal mutating func _ensureUnique() { + if _isUnique() { return } + self = _makeUniqueCopy() + } + + /// Copy elements into a new storage instance without changing capacity or + /// layout. + @inlinable + @inline(never) + internal func _makeUniqueCopy() -> Self { + Deque(_storage: _Storage(_storage._value._copy())) + } + + @inlinable + @inline(never) + internal func _makeUniqueCopy(capacity: Int) -> Self { + Deque(_storage: _Storage(_storage._value._copy(capacity: capacity))) + } + + @usableFromInline + internal func _growCapacity( + to minimumCapacity: Int, + linearly: Bool + ) -> Int { + if linearly { + return Swift.max(_capacity, minimumCapacity) + } + let next = (3 * _capacity + 1) / 2 + return Swift.max(next, minimumCapacity) + } + + /// Ensure that we have a uniquely referenced buffer with enough space to + /// store at least `minimumCapacity` elements. + /// + /// - Parameter minimumCapacity: The minimum number of elements the buffer + /// needs to be able to hold on return. + /// + /// - Parameter linearGrowth: If true, then don't use an exponential growth + /// factor when reallocating the buffer -- just allocate space for the + /// requested number of elements + @inlinable + @inline(__always) + internal mutating func _ensureUnique( + minimumCapacity: Int, + linearGrowth: Bool = false + ) { + let unique = _isUnique() + if _slowPath(_storage.capacity < minimumCapacity || !unique) { + __ensureUnique( + isUnique: unique, + minimumCapacity: minimumCapacity, + linearGrowth: linearGrowth) + } + } + + @inlinable + @inline(never) + internal mutating func __ensureUnique( + isUnique: Bool, + minimumCapacity: Int, + linearGrowth: Bool + ) { + if _storage.capacity >= minimumCapacity { + assert(!isUnique) + self = self._makeUniqueCopy() + return + } + + let c = _growCapacity(to: minimumCapacity, linearly: linearGrowth) + if isUnique { + self._storage._value.resize(to: c) + } else { + self = self._makeUniqueCopy(capacity: c) + } + } +} + +extension Deque { + @inlinable + @inline(__always) + internal func _isIdentical(to other: Self) -> Bool { + self._storage === other._storage + } +} + diff --git a/Sources/DequeModule/HypoDeque.swift b/Sources/DequeModule/HypoDeque.swift index 5d41bb58a..6eaec2c67 100644 --- a/Sources/DequeModule/HypoDeque.swift +++ b/Sources/DequeModule/HypoDeque.swift @@ -25,9 +25,14 @@ public struct HypoDeque: ~Copyable { @usableFromInline var _handle: _Handle + @inlinable + internal init(_handle: consuming _Handle) { + self._handle = _handle + } + @inlinable public init(capacity: Int) { - _handle = .allocate(capacity: capacity) + self.init(_handle: .allocate(capacity: capacity)) } deinit { @@ -37,6 +42,18 @@ public struct HypoDeque: ~Copyable { extension HypoDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} +extension HypoDeque where Element: ~Copyable { +#if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + _handle._checkInvariants() + } +#else + @inlinable @inline(__always) + internal func _checkInvariants() {} +#endif // COLLECTIONS_INTERNAL_CHECKS +} + extension HypoDeque: RandomAccessContainer where Element: ~Copyable { @frozen public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { @@ -140,7 +157,7 @@ extension HypoDeque where Element: ~Copyable { if let second = source.second { newHandle.moveInitialize(at: next, from: second) } - self._handle._storage.deallocate() + self._handle._buffer.deallocate() self._handle = newHandle } } @@ -228,3 +245,15 @@ extension HypoDeque where Element: ~Copyable { return _handle.uncheckedRemoveLast() } } + +extension HypoDeque { + @inlinable + internal func _copy() -> Self { + HypoDeque(_handle: _handle.allocateCopy()) + } + + @inlinable + internal func _copy(capacity: Int) -> Self { + HypoDeque(_handle: _handle.allocateCopy(capacity: capacity)) + } +} diff --git a/Sources/DequeModule/_DequeBuffer.swift b/Sources/DequeModule/_DequeBuffer.swift deleted file mode 100644 index 337df1fdf..000000000 --- a/Sources/DequeModule/_DequeBuffer.swift +++ /dev/null @@ -1,49 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -@_fixed_layout -@usableFromInline -internal final class _DequeBuffer: ManagedBuffer<_DequeBufferHeader, Element> { - @inlinable - deinit { - self.withUnsafeMutablePointers { header, elements in - header.pointee._checkInvariants() - - let capacity = header.pointee.capacity - let count = header.pointee.count - let startSlot = header.pointee.startSlot - - if startSlot.position + count <= capacity { - (elements + startSlot.position).deinitialize(count: count) - } else { - let firstRegion = capacity - startSlot.position - (elements + startSlot.position).deinitialize(count: firstRegion) - elements.deinitialize(count: count - firstRegion) - } - } - } -} - -extension _DequeBuffer: CustomStringConvertible { - @usableFromInline - internal var description: String { - withUnsafeMutablePointerToHeader { "_DequeStorage<\(Element.self)>\($0.pointee)" } - } -} - -/// The type-punned empty singleton storage instance. -@usableFromInline -nonisolated(unsafe) internal let _emptyDequeStorage - = _DequeBuffer.create( - minimumCapacity: 0, - makingHeaderWith: { _ in - _DequeBufferHeader(capacity: 0, count: 0, startSlot: .init(at: 0)) - }) diff --git a/Sources/DequeModule/_DequeBufferHeader.swift b/Sources/DequeModule/_DequeBufferHeader.swift deleted file mode 100644 index c45f756dd..000000000 --- a/Sources/DequeModule/_DequeBufferHeader.swift +++ /dev/null @@ -1,49 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -@usableFromInline -internal struct _DequeBufferHeader { - @usableFromInline - var capacity: Int - - @usableFromInline - var count: Int - - @usableFromInline - var startSlot: _DequeSlot - - @usableFromInline - init(capacity: Int, count: Int, startSlot: _DequeSlot) { - self.capacity = capacity - self.count = count - self.startSlot = startSlot - _checkInvariants() - } - - #if COLLECTIONS_INTERNAL_CHECKS - @usableFromInline @inline(never) @_effects(releasenone) - internal func _checkInvariants() { - precondition(capacity >= 0) - precondition(count >= 0 && count <= capacity) - precondition(startSlot.position >= 0 && startSlot.position <= capacity) - } - #else - @inlinable @inline(__always) - internal func _checkInvariants() {} - #endif // COLLECTIONS_INTERNAL_CHECKS -} - -extension _DequeBufferHeader: CustomStringConvertible { - @usableFromInline - internal var description: String { - "(capacity: \(capacity), count: \(count), startSlot: \(startSlot))" - } -} diff --git a/Sources/DequeModule/_DequeSlot.swift b/Sources/DequeModule/_DequeSlot.swift index 2d281cb94..d1d8d5f4f 100644 --- a/Sources/DequeModule/_DequeSlot.swift +++ b/Sources/DequeModule/_DequeSlot.swift @@ -69,4 +69,10 @@ extension Range where Bound == _DequeSlot { @inlinable @inline(__always) internal var _count: Int { upperBound.position - lowerBound.position } + + @inlinable + @inline(__always) + internal var _offsets: Range { + Range(uncheckedBounds: (lowerBound.position, upperBound.position)) + } } diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index 6b1a6bd3a..515bab615 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -17,7 +17,7 @@ import InternalCollectionsUtilities @usableFromInline internal struct _UnsafeDequeHandle: ~Copyable { @usableFromInline - internal let _storage: UnsafeMutableBufferPointer + internal let _buffer: UnsafeMutableBufferPointer @usableFromInline internal var count: Int @@ -41,7 +41,7 @@ internal struct _UnsafeDequeHandle: ~Copyable { count: Int, startSlot: _DequeSlot ) { - self._storage = _storage + self._buffer = _storage self.count = count self.startSlot = startSlot } @@ -49,27 +49,27 @@ internal struct _UnsafeDequeHandle: ~Copyable { @inlinable internal consuming func dispose() { _checkInvariants() - if startSlot.position + count <= _storage.count { - _storage._ptr(at: startSlot.position).deinitialize( - count: _storage.count) - } else { - let firstRegion = _storage.count - startSlot.position - _storage._ptr(at: startSlot.position).deinitialize(count: firstRegion) - _storage._ptr(at: 0).deinitialize(count: count - firstRegion) - } - _storage.deallocate() + self.mutableSegments().deinitialize() + _buffer.deallocate() } } extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable @inline(__always) internal var _baseAddress: UnsafeMutablePointer { - _storage.baseAddress.unsafelyUnwrapped + _buffer.baseAddress.unsafelyUnwrapped } @inlinable internal var capacity: Int { - _storage.count + _buffer.count + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @usableFromInline + internal var description: String { + "(capacity: \(capacity), count: \(count), startSlot: \(startSlot))" } } @@ -108,21 +108,20 @@ extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable @inline(__always) internal var mutableBuffer: UnsafeMutableBufferPointer { mutating get { - _storage + _buffer } } @inlinable internal func buffer(for range: Range) -> UnsafeBufferPointer { assert(range.upperBound.position <= capacity) - return .init( - start: _baseAddress + range.lowerBound.position, - count: range._count) + return .init(_buffer.extracting(range._offsets)) } @inlinable @inline(__always) internal mutating func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { - return .init(mutating: buffer(for: range)) + assert(range.upperBound.position <= capacity) + return _buffer.extracting(range._offsets) } } @@ -191,6 +190,9 @@ extension _UnsafeDequeHandle where Element: ~Copyable { extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable internal func segments() -> _UnsafeWrappedBuffer { + guard _buffer.baseAddress != nil else { + return .init(._empty) + } let wrap = capacity - startSlot.position if count <= wrap { return .init(start: ptr(at: startSlot), count: count) @@ -204,6 +206,9 @@ extension _UnsafeDequeHandle where Element: ~Copyable { forOffsets offsets: Range ) -> _UnsafeWrappedBuffer { assert(offsets.lowerBound >= 0 && offsets.upperBound <= count) + guard _buffer.baseAddress != nil else { + return .init(._empty) + } let lower = slot(forOffset: offsets.lowerBound) let upper = slot(forOffset: offsets.upperBound) if offsets.count == 0 || lower < upper { @@ -231,6 +236,9 @@ extension _UnsafeDequeHandle where Element: ~Copyable { extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable internal mutating func availableSegments() -> _UnsafeMutableWrappedBuffer { + guard _buffer.baseAddress != nil else { + return .init(._empty) + } let endSlot = self.endSlot guard count < capacity else { return .init(start: mutablePtr(at: endSlot), count: 0) } if endSlot < startSlot { return .init(mutableBuffer(for: endSlot ..< startSlot)) } @@ -288,41 +296,6 @@ extension _UnsafeDequeHandle where Element: ~Copyable { -extension _UnsafeDequeHandle { - /// Move elements into a new storage instance with the specified minimum - /// capacity. Existing indices in `self` won't necessarily be valid in the - /// result. `self` is left empty. - @inlinable - internal mutating func moveElements(minimumCapacity: Int) -> Deque._Storage { - let count = self.count - assert(minimumCapacity >= count) - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { -#if os(OpenBSD) - let capacity = minimumCapacity -#else - let capacity = $0.capacity -#endif - return _DequeBufferHeader( - capacity: capacity, - count: count, - startSlot: .zero) - }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard count > 0 else { return result } - result.update { target in - let source = self.mutableSegments() - let next = target.moveInitialize(at: .zero, from: source.first) - if let second = source.second { - target.moveInitialize(at: next, from: second) - } - } - self.count = 0 - return result - } -} - extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable internal func withUnsafeSegment( @@ -820,3 +793,35 @@ extension _UnsafeDequeHandle where Element: ~Copyable { } } } + +extension _UnsafeDequeHandle { + /// Copy elements into a newly allocated handle without changing capacity or + /// layout. + @inlinable + func allocateCopy() -> Self { + var handle: Self = .allocate(capacity: self.capacity) + handle.count = self.count + handle.startSlot = self.startSlot + let src = self.segments() + handle.initialize(at: self.startSlot, from: src.first) + if let second = src.second { + handle.initialize(at: .zero, from: second) + } + return handle + } + + /// Copy elements into a new storage instance with the specified minimum + /// capacity. This operation does not preserve layout. + @inlinable + func allocateCopy(capacity: Int) -> Self { + assert(capacity >= count) + var handle: Self = .allocate(capacity: capacity) + handle.count = self.count + let src = self.segments() + let next = handle.initialize(at: .zero, from: src.first) + if let second = src.second { + handle.initialize(at: next, from: second) + } + return handle + } +} diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 503d21b84..0a4ef2515 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -14,6 +14,9 @@ add_library(Future "Inout.swift" "RawSpan.swift" "Span.swift" + "Containers/Container.swift" + "Containers/DynoArray.swift" + "Containers/HypoArray.swift" ) target_link_libraries(Future PRIVATE diff --git a/Tests/DequeTests/DequeTests.swift b/Tests/DequeTests/DequeTests.swift index 34c947fe2..d36cd6410 100644 --- a/Tests/DequeTests/DequeTests.swift +++ b/Tests/DequeTests/DequeTests.swift @@ -176,16 +176,18 @@ final class DequeTests: CollectionTestCase { let d1 = Deque>( unsafeUninitializedCapacity: cap, initializingWith: { target, c in - expectNotNil(target.baseAddress) expectEqual(target.count, cap) expectEqual(c, 0) - contents.withUnsafeBufferPointer { source in - precondition(source.count <= target.count) - target.baseAddress!.initialize( - from: source.baseAddress!, - count: source.count) + if target.count > 0 { + expectNotNil(target.baseAddress) + contents.withUnsafeBufferPointer { source in + precondition(source.count <= target.count) + target.baseAddress!.initialize( + from: source.baseAddress!, + count: source.count) + } + c = count } - c = count }) expectEqualElements(d1, contents) } @@ -206,16 +208,18 @@ final class DequeTests: CollectionTestCase { try Deque>( unsafeUninitializedCapacity: cap, initializingWith: { target, c in - expectNotNil(target.baseAddress) expectEqual(target.count, cap) expectEqual(c, 0) - contents.withUnsafeBufferPointer { source in - precondition(source.count <= target.count) - target.baseAddress!.initialize( - from: source.baseAddress!, - count: source.count) + if target.count > 0 { + expectNotNil(target.baseAddress) + contents.withUnsafeBufferPointer { source in + precondition(source.count <= target.count) + target.baseAddress!.initialize( + from: source.baseAddress!, + count: source.count) + } + c = count } - c = count throw TestError(count) }) ) { error in From 626e24fede89149ad1d174d81a6d839a013ce8d6 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 6 Aug 2024 19:58:39 -0700 Subject: [PATCH 069/195] [Deque] Add _UnsafeDequeHandle.reallocate(capacity:) --- Sources/DequeModule/HypoDeque.swift | 13 +----------- Sources/DequeModule/_UnsafeDequeHandle.swift | 22 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Sources/DequeModule/HypoDeque.swift b/Sources/DequeModule/HypoDeque.swift index 6eaec2c67..edbd97f5c 100644 --- a/Sources/DequeModule/HypoDeque.swift +++ b/Sources/DequeModule/HypoDeque.swift @@ -147,18 +147,7 @@ extension HypoDeque where Element: ~Copyable { @inlinable public mutating func resize(to newCapacity: Int) { - precondition(newCapacity >= count) - guard newCapacity != capacity else { return } - var newHandle = _Handle.allocate(capacity: newCapacity) - newHandle.startSlot = .zero - newHandle.count = _handle.count - let source = _handle.mutableSegments() - let next = newHandle.moveInitialize(at: .zero, from: source.first) - if let second = source.second { - newHandle.moveInitialize(at: next, from: second) - } - self._handle._buffer.deallocate() - self._handle = newHandle + _handle.reallocate(capacity: newCapacity) } } diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index 515bab615..4256315e0 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -798,7 +798,7 @@ extension _UnsafeDequeHandle { /// Copy elements into a newly allocated handle without changing capacity or /// layout. @inlinable - func allocateCopy() -> Self { + internal func allocateCopy() -> Self { var handle: Self = .allocate(capacity: self.capacity) handle.count = self.count handle.startSlot = self.startSlot @@ -813,7 +813,7 @@ extension _UnsafeDequeHandle { /// Copy elements into a new storage instance with the specified minimum /// capacity. This operation does not preserve layout. @inlinable - func allocateCopy(capacity: Int) -> Self { + internal func allocateCopy(capacity: Int) -> Self { assert(capacity >= count) var handle: Self = .allocate(capacity: capacity) handle.count = self.count @@ -825,3 +825,21 @@ extension _UnsafeDequeHandle { return handle } } + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func reallocate(capacity newCapacity: Int) { + precondition(newCapacity >= count) + guard newCapacity != capacity else { return } + var newHandle = Self.allocate(capacity: newCapacity) + newHandle.startSlot = .zero + newHandle.count = self.count + let source = self.mutableSegments() + let next = newHandle.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + newHandle.moveInitialize(at: next, from: second) + } + self._buffer.deallocate() + self = newHandle + } +} From 9dcd20b4736c5aa408bf68a76d67093ad3c2067f Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 7 Aug 2024 19:05:31 -0700 Subject: [PATCH 070/195] [Xcode] Update xcodeproj --- Xcode/Collections.xcodeproj/project.pbxproj | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 9c89e27af..bbfaefc1b 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -74,15 +74,12 @@ 7DE9204729CA70F3004483EB /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB229CA70F3004483EB /* _HashTable+Testing.swift */; }; 7DE9204829CA70F3004483EB /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB329CA70F3004483EB /* _HashTable+CustomStringConvertible.swift */; }; 7DE9204929CA70F3004483EB /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB429CA70F3004483EB /* _HashTable+BucketIterator.swift */; }; - 7DE9204B29CA70F3004483EB /* _DequeBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */; }; 7DE9204C29CA70F3004483EB /* Deque+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */; }; 7DE9204E29CA70F3004483EB /* Deque+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */; }; 7DE9204F29CA70F3004483EB /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBB29CA70F3004483EB /* Deque.swift */; }; 7DE9205029CA70F3004483EB /* _DequeSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */; }; 7DE9205129CA70F3004483EB /* Deque+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */; }; 7DE9205229CA70F3004483EB /* _UnsafeWrappedBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */; }; - 7DE9205329CA70F3004483EB /* _DequeBufferHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */; }; - 7DE9205429CA70F3004483EB /* Deque+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */; }; 7DE9205529CA70F3004483EB /* Deque+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */; }; 7DE9205629CA70F3004483EB /* Deque+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */; }; 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */; }; @@ -519,7 +516,6 @@ 7DE91EB329CA70F3004483EB /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; 7DE91EB429CA70F3004483EB /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; 7DE91EB529CA70F3004483EB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeBuffer.swift; sourceTree = ""; }; 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Collection.swift"; sourceTree = ""; }; 7DE91EB929CA70F3004483EB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+CustomReflectable.swift"; sourceTree = ""; }; @@ -527,8 +523,6 @@ 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeSlot.swift; sourceTree = ""; }; 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Hashable.swift"; sourceTree = ""; }; 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _UnsafeWrappedBuffer.swift; sourceTree = ""; }; - 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeBufferHeader.swift; sourceTree = ""; }; - 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Sendable.swift"; sourceTree = ""; }; 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Codable.swift"; sourceTree = ""; }; 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Testing.swift"; sourceTree = ""; }; 7DE91EC329CA70F3004483EB /* DequeModule.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DequeModule.docc; sourceTree = ""; }; @@ -1066,8 +1060,6 @@ 7DE91EB629CA70F3004483EB /* DequeModule */ = { isa = PBXGroup; children = ( - 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */, - 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */, 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */, 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */, 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */, @@ -1082,7 +1074,6 @@ 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */, 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */, 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */, - 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */, 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */, 7DE91EC329CA70F3004483EB /* DequeModule.docc */, 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */, @@ -2057,7 +2048,6 @@ 7DEBDAF829CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift in Sources */, 7DE9217229CA70F4004483EB /* TreeDictionary+Sendable.swift in Sources */, 7DE9207729CA70F4004483EB /* Range+BigString.swift in Sources */, - 7DE9204B29CA70F3004483EB /* _DequeBuffer.swift in Sources */, 7DE920DF29CA70F4004483EB /* Slice+Utilities.swift in Sources */, 7DE9215729CA70F4004483EB /* _RawHashNode.swift in Sources */, 7DE9209529CA70F4004483EB /* RopeSummary.swift in Sources */, @@ -2186,7 +2176,6 @@ 7DE9216329CA70F4004483EB /* _HashLevel.swift in Sources */, 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */, 7DE9216C29CA70F4004483EB /* TreeDictionary+Descriptions.swift in Sources */, - 7DE9205429CA70F3004483EB /* Deque+Sendable.swift in Sources */, 7DE920A229CA70F4004483EB /* Rope+RemoveSubrange.swift in Sources */, 7DE920C729CA70F4004483EB /* BitSet+ExpressibleByArrayLiteral.swift in Sources */, 7DE9214929CA70F4004483EB /* _HashNode+Structural isEqualSet.swift in Sources */, @@ -2202,7 +2191,6 @@ 7DEBDB0229CBEE5300ADC226 /* _UnsafeBitSet.swift in Sources */, 7DE9214329CA70F4004483EB /* _HashNode+Subtree Removals.swift in Sources */, 7DE9207929CA70F4004483EB /* BigString+UnicodeScalarView.swift in Sources */, - 7DE9205329CA70F3004483EB /* _DequeBufferHeader.swift in Sources */, 7DE9202429CA70F3004483EB /* OrderedSet+Invariants.swift in Sources */, 7DE920A729CA70F4004483EB /* _CharacterRecognizer.swift in Sources */, 7DE920A429CA70F4004483EB /* Rope+Sequence.swift in Sources */, From 6322ec54a0dd9512e4f3f504e70c85d4ac706b42 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 7 Aug 2024 19:08:13 -0700 Subject: [PATCH 071/195] [Deque] Disable runtime exclusivity checking for the ivar of the storage class It turns out the major slowdowns in individual element add/remove operations were mostly caused by runtime exclusivity checking on `Deque._Storage._value`. --- Sources/DequeModule/Deque._Storage.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift index e2d23527e..24a251950 100644 --- a/Sources/DequeModule/Deque._Storage.swift +++ b/Sources/DequeModule/Deque._Storage.swift @@ -13,6 +13,7 @@ extension Deque { @usableFromInline internal final class _Storage { @usableFromInline + @exclusivity(unchecked) internal var _value: HypoDeque @inlinable From dface3fb37c7d48f747efb50bfc836939d84a2e4 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 7 Aug 2024 19:10:29 -0700 Subject: [PATCH 072/195] [Deque] Mitigate some effects of the lack of an empty Deque singleton --- Sources/DequeModule/Deque+Codable.swift | 5 +++-- Sources/DequeModule/Deque+Collection.swift | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/DequeModule/Deque+Codable.swift b/Sources/DequeModule/Deque+Codable.swift index abef341ea..7e9e8d579 100644 --- a/Sources/DequeModule/Deque+Codable.swift +++ b/Sources/DequeModule/Deque+Codable.swift @@ -35,11 +35,12 @@ extension Deque: Decodable where Element: Decodable { /// - Parameter decoder: The decoder to read data from. @inlinable public init(from decoder: Decoder) throws { - self.init() var container = try decoder.unkeyedContainer() if let count = container.count { - self.reserveCapacity(count) + self.init(minimumCapacity: count) + } else { + self.init() } while !container.isAtEnd { let element = try container.decode(Element.self) diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index b49fef62f..ef528253d 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -608,7 +608,7 @@ extension Deque: RangeReplaceableCollection { /// - Complexity: O(*n*), where *n* is the number of elements in the sequence. @inlinable public init(_ elements: some Sequence) { - self.init() + self.init(minimumCapacity: elements.underestimatedCount) self.append(contentsOf: elements) } @@ -622,7 +622,7 @@ extension Deque: RangeReplaceableCollection { public init(_ elements: some Collection) { let c = elements.count guard c > 0 else { self.init(); return } - self.init(_storage: _Storage(capacity: c)) + self.init(minimumCapacity: c) _update { handle in assert(handle.startSlot == .zero) let target = handle.mutableBuffer(for: .zero ..< _Slot(at: c)) From 816fc788279ec0b387a1f57d6aae598c9f72f90e Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 7 Aug 2024 19:12:05 -0700 Subject: [PATCH 073/195] [_CollectionsUtilities] Add Unsafe[Mutable]BufferPointer._extracting(unchecked:) Also, use these to spare some unnecessary instructions. --- Sources/DequeModule/_UnsafeDequeHandle.swift | 4 ++-- .../UnsafeBufferPointer+Extras.swift.gyb | 10 ++++++++++ ...nsafeMutableBufferPointer+Extras.swift.gyb | 10 ++++++++++ .../UnsafeBufferPointer+Extras.swift | 20 +++++++++++++++++++ .../UnsafeMutableBufferPointer+Extras.swift | 20 +++++++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index 4256315e0..517226569 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -115,13 +115,13 @@ extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable internal func buffer(for range: Range) -> UnsafeBufferPointer { assert(range.upperBound.position <= capacity) - return .init(_buffer.extracting(range._offsets)) + return .init(_buffer._extracting(unchecked: range._offsets)) } @inlinable @inline(__always) internal mutating func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { assert(range.upperBound.position <= capacity) - return _buffer.extracting(range._offsets) + return _buffer._extracting(unchecked: range._offsets) } } diff --git a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb index 2f959fb7a..014c9d304 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb @@ -30,6 +30,16 @@ extension UnsafeBufferPointer where Element: ~Copyable { return baseAddress.unsafelyUnwrapped + index } + @_alwaysEmitIntoClient + ${modifier} func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + @inlinable ${modifier} func _extracting(first n: Int) -> Self { assert(n >= 0) diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index 1b2ef6d1a..ac6c0e86b 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -30,6 +30,16 @@ extension UnsafeMutableBufferPointer where Element: ~Copyable { return baseAddress.unsafelyUnwrapped + index } + @_alwaysEmitIntoClient + ${modifier} func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + @inlinable ${modifier} func _extracting(first n: Int) -> Self { assert(n >= 0) diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift index ed4fec34e..ec6a6f824 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift @@ -36,6 +36,16 @@ extension UnsafeBufferPointer where Element: ~Copyable { return baseAddress.unsafelyUnwrapped + index } + @_alwaysEmitIntoClient + internal func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + @inlinable internal func _extracting(first n: Int) -> Self { assert(n >= 0) @@ -66,6 +76,16 @@ extension UnsafeBufferPointer where Element: ~Copyable { return baseAddress.unsafelyUnwrapped + index } + @_alwaysEmitIntoClient + public func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + @inlinable public func _extracting(first n: Int) -> Self { assert(n >= 0) diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index 6adfefd7e..4e40b3693 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -36,6 +36,16 @@ extension UnsafeMutableBufferPointer where Element: ~Copyable { return baseAddress.unsafelyUnwrapped + index } + @_alwaysEmitIntoClient + internal func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + @inlinable internal func _extracting(first n: Int) -> Self { assert(n >= 0) @@ -196,6 +206,16 @@ extension UnsafeMutableBufferPointer where Element: ~Copyable { return baseAddress.unsafelyUnwrapped + index } + @_alwaysEmitIntoClient + public func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + @inlinable public func _extracting(first n: Int) -> Self { assert(n >= 0) From 04bf0cff3871c30ed926961974809acb9bf6b81b Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 7 Aug 2024 19:13:12 -0700 Subject: [PATCH 074/195] [_CollectionsUtilities] Unsafe[Mutable]BufferPointer: Add some initializeAll overloads --- ...nsafeMutableBufferPointer+Extras.swift.gyb | 20 ++++++++-- .../UnsafeMutableBufferPointer+Extras.swift | 40 +++++++++++++++---- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index ac6c0e86b..a2f840f7d 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -115,16 +115,28 @@ extension UnsafeMutableBufferPointer { assert(i == self.endIndex) } + @inlinable @inline(__always) + ${modifier} func initializeAll(fromContentsOf source: UnsafeBufferPointer) { + assert(self.count == source.count) + guard source.count > 0 else { return } + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) + } + + @inlinable @inline(__always) + ${modifier} func initializeAll(fromContentsOf source: Slice>) { + self.initializeAll(fromContentsOf: .init(rebasing: source)) + } + @inlinable @inline(__always) ${modifier} func initializeAll(fromContentsOf source: Self) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeBufferPointer(source)) } @inlinable @inline(__always) ${modifier} func initializeAll(fromContentsOf source: Slice) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) } } diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index 4e40b3693..5c8dd9489 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -121,16 +121,28 @@ extension UnsafeMutableBufferPointer { assert(i == self.endIndex) } + @inlinable @inline(__always) + internal func initializeAll(fromContentsOf source: UnsafeBufferPointer) { + assert(self.count == source.count) + guard source.count > 0 else { return } + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) + } + + @inlinable @inline(__always) + internal func initializeAll(fromContentsOf source: Slice>) { + self.initializeAll(fromContentsOf: .init(rebasing: source)) + } + @inlinable @inline(__always) internal func initializeAll(fromContentsOf source: Self) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeBufferPointer(source)) } @inlinable @inline(__always) internal func initializeAll(fromContentsOf source: Slice) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) } } @@ -291,16 +303,28 @@ extension UnsafeMutableBufferPointer { assert(i == self.endIndex) } + @inlinable @inline(__always) + public func initializeAll(fromContentsOf source: UnsafeBufferPointer) { + assert(self.count == source.count) + guard source.count > 0 else { return } + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) + } + + @inlinable @inline(__always) + public func initializeAll(fromContentsOf source: Slice>) { + self.initializeAll(fromContentsOf: .init(rebasing: source)) + } + @inlinable @inline(__always) public func initializeAll(fromContentsOf source: Self) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeBufferPointer(source)) } @inlinable @inline(__always) public func initializeAll(fromContentsOf source: Slice) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) } } From 75aa5b075534ab11b77bc694f4c6d7cc29eab26f Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 12 Aug 2024 13:35:42 -0700 Subject: [PATCH 075/195] =?UTF-8?q?Hypo=20=E2=86=92=20Rigid,=20Dyno=20?= =?UTF-8?q?=E2=86=92=20Dynamic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s try these on for size. The greek prefixes work great, but their capitalization looks bad, and they do not interact well with compound names. (`OrderedHyposet`?) --- Sources/DequeModule/CMakeLists.txt | 4 +-- Sources/DequeModule/Deque+Testing.swift | 2 +- Sources/DequeModule/Deque._Storage.swift | 6 ++-- .../{DynoDeque.swift => DynamicDeque.swift} | 16 ++++----- .../{HypoDeque.swift => RigidDeque.swift} | 34 +++++++++---------- Sources/Future/CMakeLists.txt | 4 +-- .../{DynoArray.swift => DynamicArray.swift} | 26 +++++++------- .../{HypoArray.swift => RigidArray.swift} | 24 ++++++------- Xcode/Collections.xcodeproj/project.pbxproj | 32 ++++++++--------- 9 files changed, 74 insertions(+), 74 deletions(-) rename Sources/DequeModule/{DynoDeque.swift => DynamicDeque.swift} (87%) rename Sources/DequeModule/{HypoDeque.swift => RigidDeque.swift} (89%) rename Sources/Future/Containers/{DynoArray.swift => DynamicArray.swift} (84%) rename Sources/Future/Containers/{HypoArray.swift => RigidArray.swift} (89%) diff --git a/Sources/DequeModule/CMakeLists.txt b/Sources/DequeModule/CMakeLists.txt index 3a000181a..f85a23d01 100644 --- a/Sources/DequeModule/CMakeLists.txt +++ b/Sources/DequeModule/CMakeLists.txt @@ -35,8 +35,8 @@ target_sources(${module_name} PRIVATE "Deque+Testing.swift" "Deque._Storage.swift" "Deque.swift" - "DynoDeque.swift" - "HypoDeque.swift" + "DynamicDeque.swift" + "RigidDeque.swift" "_DequeSlot.swift" "_UnsafeDequeHandle.swift" "_UnsafeWrappedBuffer.swift" diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index 2e6969566..bb5cf3be1 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -68,7 +68,7 @@ extension Deque { let startSlot = _Slot(at: startSlot) - var hd = HypoDeque(capacity: capacity) + var hd = RigidDeque(capacity: capacity) hd._handle.count = contents.count hd._handle.startSlot = startSlot if hd._handle.count > 0 { diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift index 24a251950..4c4a26484 100644 --- a/Sources/DequeModule/Deque._Storage.swift +++ b/Sources/DequeModule/Deque._Storage.swift @@ -14,17 +14,17 @@ extension Deque { internal final class _Storage { @usableFromInline @exclusivity(unchecked) - internal var _value: HypoDeque + internal var _value: RigidDeque @inlinable @inline(__always) - internal init(_ value: consuming HypoDeque) { + internal init(_ value: consuming RigidDeque) { self._value = value } @inlinable internal convenience init(capacity: Int) { - self.init(HypoDeque(capacity: capacity)) + self.init(RigidDeque(capacity: capacity)) } } } diff --git a/Sources/DequeModule/DynoDeque.swift b/Sources/DequeModule/DynamicDeque.swift similarity index 87% rename from Sources/DequeModule/DynoDeque.swift rename to Sources/DequeModule/DynamicDeque.swift index d9282cf17..c4ad8ef17 100644 --- a/Sources/DequeModule/DynoDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -15,9 +15,9 @@ import Future #endif @frozen -public struct DynoDeque: ~Copyable { +public struct DynamicDeque: ~Copyable { @usableFromInline - internal var _storage: HypoDeque + internal var _storage: RigidDeque @inlinable public init() { @@ -30,10 +30,10 @@ public struct DynoDeque: ~Copyable { } } -extension DynoDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} +extension DynamicDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} -extension DynoDeque: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = HypoDeque.BorrowingIterator +extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = RigidDeque.BorrowingIterator public typealias Index = Int public func startBorrowingIteration() -> BorrowingIterator { @@ -73,7 +73,7 @@ extension DynoDeque: RandomAccessContainer where Element: ~Copyable { } } -extension DynoDeque where Element: ~Copyable { +extension DynamicDeque where Element: ~Copyable { @inlinable internal var _capacity: Int { _storage.capacity } @@ -96,7 +96,7 @@ extension DynoDeque where Element: ~Copyable { } } -extension DynoDeque where Element: ~Copyable { +extension DynamicDeque where Element: ~Copyable { @inlinable public mutating func append(_ newElement: consuming Element) { _ensureFreeCapacity(1) @@ -116,7 +116,7 @@ extension DynoDeque where Element: ~Copyable { } } -extension DynoDeque where Element: ~Copyable { +extension DynamicDeque where Element: ~Copyable { @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { diff --git a/Sources/DequeModule/HypoDeque.swift b/Sources/DequeModule/RigidDeque.swift similarity index 89% rename from Sources/DequeModule/HypoDeque.swift rename to Sources/DequeModule/RigidDeque.swift index edbd97f5c..06c409d52 100644 --- a/Sources/DequeModule/HypoDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -15,7 +15,7 @@ import Future #endif @frozen -public struct HypoDeque: ~Copyable { +public struct RigidDeque: ~Copyable { @usableFromInline internal typealias _Slot = _DequeSlot @@ -40,9 +40,9 @@ public struct HypoDeque: ~Copyable { } } -extension HypoDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} +extension RigidDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} -extension HypoDeque where Element: ~Copyable { +extension RigidDeque where Element: ~Copyable { #if COLLECTIONS_INTERNAL_CHECKS @usableFromInline @inline(never) @_effects(releasenone) internal func _checkInvariants() { @@ -54,7 +54,7 @@ extension HypoDeque where Element: ~Copyable { #endif // COLLECTIONS_INTERNAL_CHECKS } -extension HypoDeque: RandomAccessContainer where Element: ~Copyable { +extension RigidDeque: RandomAccessContainer where Element: ~Copyable { @frozen public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { @usableFromInline @@ -135,7 +135,7 @@ extension HypoDeque: RandomAccessContainer where Element: ~Copyable { } } -extension HypoDeque where Element: ~Copyable { +extension RigidDeque where Element: ~Copyable { @inlinable public var capacity: Int { _handle.capacity } @@ -151,29 +151,29 @@ extension HypoDeque where Element: ~Copyable { } } -extension HypoDeque where Element: ~Copyable { +extension RigidDeque where Element: ~Copyable { @inlinable public mutating func append(_ newElement: consuming Element) { - precondition(!isFull, "HypoDeque is full") + precondition(!isFull, "RigidDeque is full") _handle.uncheckedAppend(newElement) } @inlinable public mutating func prepend(_ newElement: consuming Element) { - precondition(!isFull, "HypoDeque is full") + precondition(!isFull, "RigidDeque is full") _handle.uncheckedPrepend(newElement) } @inlinable public mutating func insert(_ newElement: consuming Element, at index: Int) { - precondition(!isFull, "HypoDeque is full") + precondition(!isFull, "RigidDeque is full") precondition(index >= 0 && index <= count, "Can't insert element at invalid index") _handle.uncheckedInsert(newElement, at: index) } } -extension HypoDeque where Element: ~Copyable { +extension RigidDeque where Element: ~Copyable { @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { @@ -192,28 +192,28 @@ extension HypoDeque where Element: ~Copyable { @inlinable @discardableResult public mutating func removeFirst() -> Element { - precondition(!isEmpty, "Cannot remove first element of an empty HypoDeque") + precondition(!isEmpty, "Cannot remove first element of an empty RigidDeque") return _handle.uncheckedRemoveFirst() } @inlinable @discardableResult public mutating func removeLast() -> Element { - precondition(!isEmpty, "Cannot remove last element of an empty HypoDeque") + precondition(!isEmpty, "Cannot remove last element of an empty RigidDeque") return _handle.uncheckedRemoveLast() } @inlinable public mutating func removeFirst(_ n: Int) { precondition(n >= 0, "Can't remove a negative number of elements") - precondition(n <= count, "Can't remove more elements than there are in a HypoDeque") + precondition(n <= count, "Can't remove more elements than there are in a RigidDeque") _handle.uncheckedRemoveFirst(n) } @inlinable public mutating func removeLast(_ n: Int) { precondition(n >= 0, "Can't remove a negative number of elements") - precondition(n <= count, "Can't remove more elements than there are in a HypoDeque") + precondition(n <= count, "Can't remove more elements than there are in a RigidDeque") _handle.uncheckedRemoveLast(n) } @@ -235,14 +235,14 @@ extension HypoDeque where Element: ~Copyable { } } -extension HypoDeque { +extension RigidDeque { @inlinable internal func _copy() -> Self { - HypoDeque(_handle: _handle.allocateCopy()) + RigidDeque(_handle: _handle.allocateCopy()) } @inlinable internal func _copy(capacity: Int) -> Self { - HypoDeque(_handle: _handle.allocateCopy(capacity: capacity)) + RigidDeque(_handle: _handle.allocateCopy(capacity: capacity)) } } diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 0a4ef2515..63b1192f2 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -15,8 +15,8 @@ add_library(Future "RawSpan.swift" "Span.swift" "Containers/Container.swift" - "Containers/DynoArray.swift" - "Containers/HypoArray.swift" + "Containers/DynamicArray.swift" + "Containers/RigidArray.swift" ) target_link_libraries(Future PRIVATE diff --git a/Sources/Future/Containers/DynoArray.swift b/Sources/Future/Containers/DynamicArray.swift similarity index 84% rename from Sources/Future/Containers/DynoArray.swift rename to Sources/Future/Containers/DynamicArray.swift index e2516c471..50a8f6a2c 100644 --- a/Sources/Future/Containers/DynoArray.swift +++ b/Sources/Future/Containers/DynamicArray.swift @@ -1,9 +1,9 @@ /// A dynamically self-resizing, heap allocated, noncopyable array /// of potentially noncopyable elements. @frozen -public struct DynoArray: ~Copyable { +public struct DynamicArray: ~Copyable { @usableFromInline - internal var _storage: HypoArray + internal var _storage: RigidArray @inlinable public init() { @@ -21,15 +21,15 @@ public struct DynoArray: ~Copyable { } } -extension DynoArray: Sendable where Element: Sendable & ~Copyable {} +extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable public var capacity: Int { _storage.capacity } } -extension DynoArray: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = HypoArray.BorrowingIterator +extension DynamicArray: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = RigidArray.BorrowingIterator public typealias Index = Int public func startBorrowingIteration() -> BorrowingIterator { @@ -108,7 +108,7 @@ extension DynoArray: RandomAccessContainer where Element: ~Copyable { } } -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable public func borrowElement ( at index: Int, @@ -126,7 +126,7 @@ extension DynoArray where Element: ~Copyable { } } -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { @@ -134,14 +134,14 @@ extension DynoArray where Element: ~Copyable { } } -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable public mutating func reserveCapacity(_ n: Int) { _storage.reserveCapacity(n) } } -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable internal static func _grow(_ capacity: Int) -> Int { 2 * capacity @@ -154,7 +154,7 @@ extension DynoArray where Element: ~Copyable { } } -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable public mutating func append(_ item: consuming Element) { _ensureFreeCapacity(1) @@ -162,7 +162,7 @@ extension DynoArray where Element: ~Copyable { } } -extension DynoArray where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable public mutating func insert(_ item: consuming Element, at index: Int) { precondition(index >= 0 && index <= count) @@ -171,7 +171,7 @@ extension DynoArray where Element: ~Copyable { } } -extension DynoArray { +extension DynamicArray { @inlinable public mutating func append(contentsOf items: some Sequence) { for item in items { diff --git a/Sources/Future/Containers/HypoArray.swift b/Sources/Future/Containers/RigidArray.swift similarity index 89% rename from Sources/Future/Containers/HypoArray.swift rename to Sources/Future/Containers/RigidArray.swift index 06daa52be..eb85fc756 100644 --- a/Sources/Future/Containers/HypoArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -1,7 +1,7 @@ /// A manually resizable, heap allocated, noncopyable array of /// potentially noncopyable elements. @frozen -public struct HypoArray: ~Copyable { +public struct RigidArray: ~Copyable { @usableFromInline internal var _storage: UnsafeMutableBufferPointer @@ -34,9 +34,9 @@ public struct HypoArray: ~Copyable { } } -extension HypoArray: @unchecked Sendable where Element: Sendable & ~Copyable {} +extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable public var capacity: Int { _storage.count } @@ -47,7 +47,7 @@ extension HypoArray where Element: ~Copyable { public var isFull: Bool { freeCapacity == 0 } } -extension HypoArray: RandomAccessContainer where Element: ~Copyable { +extension RigidArray: RandomAccessContainer where Element: ~Copyable { public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { @usableFromInline internal let _items: UnsafeBufferPointer @@ -56,7 +56,7 @@ extension HypoArray: RandomAccessContainer where Element: ~Copyable { internal var _offset: Int @inlinable - internal init(for array: borrowing HypoArray, startOffset: Int) { + internal init(for array: borrowing RigidArray, startOffset: Int) { self._items = UnsafeBufferPointer(array._items) self._offset = startOffset } @@ -114,7 +114,7 @@ extension HypoArray: RandomAccessContainer where Element: ~Copyable { } } -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable internal var _items: UnsafeMutableBufferPointer { _storage.extracting(Range(uncheckedBounds: (0, _count))) @@ -126,7 +126,7 @@ extension HypoArray where Element: ~Copyable { } } -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable public mutating func resize(to newCapacity: Int) { precondition(newCapacity >= count) @@ -146,7 +146,7 @@ extension HypoArray where Element: ~Copyable { } -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable public func borrowElement ( at index: Int, @@ -166,7 +166,7 @@ extension HypoArray where Element: ~Copyable { } } -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { @@ -181,7 +181,7 @@ extension HypoArray where Element: ~Copyable { } } -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable public mutating func append(_ item: consuming Element) { precondition(!isFull) @@ -190,7 +190,7 @@ extension HypoArray where Element: ~Copyable { } } -extension HypoArray where Element: ~Copyable { +extension RigidArray where Element: ~Copyable { @inlinable public mutating func insert(_ item: consuming Element, at index: Int) { precondition(index >= 0 && index <= count) @@ -206,7 +206,7 @@ extension HypoArray where Element: ~Copyable { } } -extension HypoArray { +extension RigidArray { @inlinable public mutating func append(contentsOf items: some Sequence) { for item in items { diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index bbfaefc1b..49f9630c3 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -410,12 +410,12 @@ 7DEBDB9129CCE44A00ADC226 /* CollectionTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2429CCE43600ADC226 /* CollectionTestCase.swift */; }; 7DEBDB9229CCE44A00ADC226 /* StringConvertibleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */; }; 7DEBDB9D29CCE73D00ADC226 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91F7B29CA70F3004483EB /* Collections.swift */; }; - 7DEBEC152C5AB45600A1BF15 /* DynoDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */; }; - 7DEBEC162C5AB45600A1BF15 /* HypoDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC142C5AB45600A1BF15 /* HypoDeque.swift */; }; + 7DEBEC152C5AB45600A1BF15 /* DynamicDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */; }; + 7DEBEC162C5AB45600A1BF15 /* RigidDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */; }; 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */; }; 7DEBEC252C5AE43C00A1BF15 /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */; }; - 7DEBEC262C5AE43C00A1BF15 /* HypoArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1A2C5AE43C00A1BF15 /* HypoArray.swift */; }; - 7DEBEC272C5AE43C00A1BF15 /* DynoArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC192C5AE43C00A1BF15 /* DynoArray.swift */; }; + 7DEBEC262C5AE43C00A1BF15 /* RigidArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */; }; + 7DEBEC272C5AE43C00A1BF15 /* DynamicArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */; }; 7DEBEC282C5AE43C00A1BF15 /* Inout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */; }; 7DEBEC292C5AE43C00A1BF15 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC182C5AE43C00A1BF15 /* Container.swift */; }; 7DEBEC2A2C5AE43C00A1BF15 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */; }; @@ -876,11 +876,11 @@ 7DEBDB9829CCE4A600ADC226 /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; 7DEBDB9929CCE4A600ADC226 /* Collections.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Collections.xcconfig; sourceTree = ""; }; 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _UnsafeDequeHandle.swift; sourceTree = ""; }; - 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynoDeque.swift; sourceTree = ""; }; - 7DEBEC142C5AB45600A1BF15 /* HypoDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HypoDeque.swift; sourceTree = ""; }; + 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDeque.swift; sourceTree = ""; }; + 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RigidDeque.swift; sourceTree = ""; }; 7DEBEC182C5AE43C00A1BF15 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; - 7DEBEC192C5AE43C00A1BF15 /* DynoArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynoArray.swift; sourceTree = ""; }; - 7DEBEC1A2C5AE43C00A1BF15 /* HypoArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HypoArray.swift; sourceTree = ""; }; + 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicArray.swift; sourceTree = ""; }; + 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RigidArray.swift; sourceTree = ""; }; 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; }; 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; @@ -1076,8 +1076,8 @@ 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */, 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */, 7DE91EC329CA70F3004483EB /* DequeModule.docc */, - 7DEBEC132C5AB45600A1BF15 /* DynoDeque.swift */, - 7DEBEC142C5AB45600A1BF15 /* HypoDeque.swift */, + 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */, + 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */, ); path = DequeModule; sourceTree = ""; @@ -1797,8 +1797,8 @@ isa = PBXGroup; children = ( 7DEBEC182C5AE43C00A1BF15 /* Container.swift */, - 7DEBEC192C5AE43C00A1BF15 /* DynoArray.swift */, - 7DEBEC1A2C5AE43C00A1BF15 /* HypoArray.swift */, + 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */, + 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */, ); path = Containers; sourceTree = ""; @@ -2033,8 +2033,8 @@ 7DE920E929CA70F4004483EB /* BitArray+Invariants.swift in Sources */, 7DE9215429CA70F4004483EB /* _HashNode+UnsafeHandle.swift in Sources */, 7DE9209829CA70F4004483EB /* Rope+_Node.swift in Sources */, - 7DEBEC152C5AB45600A1BF15 /* DynoDeque.swift in Sources */, - 7DEBEC162C5AB45600A1BF15 /* HypoDeque.swift in Sources */, + 7DEBEC152C5AB45600A1BF15 /* DynamicDeque.swift in Sources */, + 7DEBEC162C5AB45600A1BF15 /* RigidDeque.swift in Sources */, 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */, 7DE9204529CA70F3004483EB /* _HashTable+Constants.swift in Sources */, 7DE9214229CA70F4004483EB /* _HashStack.swift in Sources */, @@ -2107,8 +2107,8 @@ 7DE9203229CA70F3004483EB /* OrderedSet+Sendable.swift in Sources */, 7DE9206E29CA70F4004483EB /* BigString+Chunk+Splitting.swift in Sources */, 7DE920D829CA70F4004483EB /* BitSet+SetAlgebra subtracting.swift in Sources */, - 7DEBEC262C5AE43C00A1BF15 /* HypoArray.swift in Sources */, - 7DEBEC272C5AE43C00A1BF15 /* DynoArray.swift in Sources */, + 7DEBEC262C5AE43C00A1BF15 /* RigidArray.swift in Sources */, + 7DEBEC272C5AE43C00A1BF15 /* DynamicArray.swift in Sources */, 7DEBEC282C5AE43C00A1BF15 /* Inout.swift in Sources */, 7DEBEC292C5AE43C00A1BF15 /* Container.swift in Sources */, 7DEBEC2A2C5AE43C00A1BF15 /* Box.swift in Sources */, From c0802bd93fadffcd9f9d6160faeb54ce7dee58e1 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 18:48:24 -0700 Subject: [PATCH 076/195] [Future] Add unsafelyOverrideLifetime(of:to:) --- Sources/Future/CMakeLists.txt | 1 + Sources/Future/LifetimeOverride.swift | 23 +++++++++++++++++++++ Xcode/Collections.xcodeproj/project.pbxproj | 4 ++++ 3 files changed, 28 insertions(+) create mode 100644 Sources/Future/LifetimeOverride.swift diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 63b1192f2..285c45f27 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(Future "Cell.swift" "ContiguousStorage.swift" "Inout.swift" + "LifetimeOverride.swift" "RawSpan.swift" "Span.swift" "Containers/Container.swift" diff --git a/Sources/Future/LifetimeOverride.swift b/Sources/Future/LifetimeOverride.swift new file mode 100644 index 000000000..e7b8d8da4 --- /dev/null +++ b/Sources/Future/LifetimeOverride.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@_unsafeNonescapableResult +public func unsafelyOverrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + of dependent: consuming T, + to source: borrowing U +) -> dependsOn(source) T { + dependent +} diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 49f9630c3..61d92c798 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859229E4F74400B291CD /* BitArray+Descriptions.swift */; }; 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; @@ -437,6 +438,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; 7D5A64D229CCE8CC00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D329CCEE9A00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D429CCEF1500CB2595 /* generate-sources.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "generate-sources.sh"; sourceTree = ""; }; @@ -1808,6 +1810,7 @@ children = ( 7DEBEC1B2C5AE43C00A1BF15 /* Containers */, 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */, + 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */, 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */, 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */, 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */, @@ -1959,6 +1962,7 @@ 7DE9213629CA70F4004483EB /* TreeSet+SetAlgebra basics.swift in Sources */, 7DE9202C29CA70F3004483EB /* OrderedSet+Initializers.swift in Sources */, 7DE9213229CA70F4004483EB /* TreeSet+SetAlgebra formSymmetricDifference.swift in Sources */, + 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */, 7DE9203429CA70F3004483EB /* OrderedSet+Partial SetAlgebra formUnion.swift in Sources */, 7DE9214A29CA70F4004483EB /* _HashNode+Subtree Insertions.swift in Sources */, 7DE9215829CA70F4004483EB /* _HashNode+Structural union.swift in Sources */, From e7a55c87593013c497c4a98fd6e73929828a1b82 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 18:48:35 -0700 Subject: [PATCH 077/195] [Future] Inout: follow the leading underscore rule --- Sources/Future/Box.swift | 2 +- Sources/Future/Inout.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index 97670a5d7..6cf6b6601 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -24,7 +24,7 @@ public struct Box: ~Copyable { @_alwaysEmitIntoClient @_transparent public init(_ fromInout: consuming Inout) { - pointer = fromInout.pointer + pointer = fromInout._pointer } @_alwaysEmitIntoClient diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift index 1a45e002e..5cb126222 100644 --- a/Sources/Future/Inout.swift +++ b/Sources/Future/Inout.swift @@ -20,7 +20,7 @@ import Builtin @frozen public struct Inout: ~Copyable, ~Escapable { @usableFromInline - let pointer: UnsafeMutablePointer + internal let _pointer: UnsafeMutablePointer /// Initializes an instance of 'Inout' extending the exclusive access of the /// passed instance. @@ -29,7 +29,7 @@ public struct Inout: ~Copyable, ~Escapable { @_alwaysEmitIntoClient @_transparent public init(_ instance: inout T) { - pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) } /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' @@ -46,7 +46,7 @@ public struct Inout: ~Copyable, ~Escapable { unsafeAddress: UnsafeMutablePointer, owner: inout Owner ) { - pointer = unsafeAddress + _pointer = unsafeAddress } /// Unsafely initializes an instance of 'Inout' using the given @@ -60,7 +60,7 @@ public struct Inout: ~Copyable, ~Escapable { public init( unsafeImmortalAddress: UnsafeMutablePointer ) -> dependsOn(immortal) Self { - pointer = unsafeImmortalAddress + _pointer = unsafeImmortalAddress } } @@ -71,12 +71,12 @@ extension Inout where T: ~Copyable { public subscript() -> T { @_transparent unsafeAddress { - UnsafePointer(pointer) + UnsafePointer(_pointer) } @_transparent nonmutating unsafeMutableAddress { - pointer + _pointer } } } From af62dcd34f7d455f4c3cf457c532290927cb5a62 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 18:51:19 -0700 Subject: [PATCH 078/195] [Future] RandomAccessContainer: Reformulate default impls for Strideable indices --- Sources/Future/Containers/Container.swift | 72 ++++++++++++----------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 40867983a..58c359191 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -54,68 +54,74 @@ public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escap override associatedtype Element: ~Copyable } +extension Strideable { + @inlinable + public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { + if distance >= 0 { + guard limit >= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.min(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } else { + guard limit <= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.max(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } + } +} -extension RandomAccessContainer where Index == Int, Self: ~Copyable { +extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Self: ~Copyable { @inlinable - public func index(after index: Int) -> Int { + public func index(after index: Index) -> Index { // Note: Range checks are deferred until element access. - index + 1 + index.advanced(by: 1) } @inlinable - public func index(before index: Int) -> Int { + public func index(before index: Index) -> Index { // Note: Range checks are deferred until element access. - index - 1 + index.advanced(by: -1) } @inlinable - public func formIndex(after index: inout Int) { + public func formIndex(after index: inout Index) { // Note: Range checks are deferred until element access. - index += 1 + index = index.advanced(by: 1) } @inlinable - public func formIndex(before index: inout Int) { + public func formIndex(before index: inout Index) { // Note: Range checks are deferred until element access. - index -= 1 + index = index.advanced(by: -1) } @inlinable - public func distance(from start: Int, to end: Int) -> Int { + public func distance(from start: Index, to end: Index) -> Int { // Note: Range checks are deferred until element access. - end - start + start.distance(to: end) } @inlinable - public func index(_ index: Int, offsetBy n: Int) -> Int { + public func index(_ index: Index, offsetBy n: Int) -> Index { // Note: Range checks are deferred until element access. - index + n + index.advanced(by: n) } @inlinable public func formIndex( - _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int + _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index ) { // Note: Range checks are deferred until element access. - if distance >= 0 { - guard limit >= index else { - index += distance - distance = 0 - return - } - let d = Swift.min(distance, limit - index) - index += d - distance -= d - } else { - guard limit <= index else { - index += distance - distance = 0 - return - } - let d = Swift.max(distance, limit - index) - index += d - distance -= d - } + index.advance(by: &distance, limitedBy: limit) } } From 68a09aec170c75a17c8eab75a76b3a0232a629c0 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 18:52:05 -0700 Subject: [PATCH 079/195] [Future] Add Bovine --- Sources/Future/CMakeLists.txt | 1 + Sources/Future/Containers/Bovine.swift | 128 ++++++++++++++++++++ Xcode/Collections.xcodeproj/project.pbxproj | 4 + 3 files changed, 133 insertions(+) create mode 100644 Sources/Future/Containers/Bovine.swift diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 285c45f27..d1f400e13 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(Future "LifetimeOverride.swift" "RawSpan.swift" "Span.swift" + "Containers/Bovine.swift" "Containers/Container.swift" "Containers/DynamicArray.swift" "Containers/RigidArray.swift" diff --git a/Sources/Future/Containers/Bovine.swift b/Sources/Future/Containers/Bovine.swift new file mode 100644 index 000000000..fdc582c51 --- /dev/null +++ b/Sources/Future/Containers/Bovine.swift @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin // For Bovine.isIdentical + +/// A utility adapter that wraps a noncopyable storage type in a copy-on-write +/// struct, enabling efficient implementation of value semantics. The type +/// allows safe borrowing and mutating access to its storage, with minimal fuss. +/// +/// Like `ManagedBufferPointer`, this type is intended to be used within the +/// internal implementation of public types. Instances of it aren't designed +/// to be exposed as public. +/// +/// (The placeholder name `Bovine` is hinting at the common abbreviation of the +/// copy-on-write optimization.) +@frozen +public struct Bovine { + @usableFromInline + internal var _box: _Box + + @inlinable + public init(_ storage: consuming Storage) { + self._box = _Box(storage) + } +} + +extension Bovine: @unchecked Sendable where Storage: Sendable & ~Copyable {} + +#if true // FIXME: Silent error on class definition nested in noncopyable struct +@usableFromInline +internal final class _BovineBox { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + self.storage = storage + } +} + +extension Bovine where Storage: ~Copyable { + @usableFromInline + internal typealias _Box = _BovineBox +} +#else +extension Bovine where Storage: ~Copyable { + @usableFromInline + internal final class _Box { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + self.storage = storage + } + } +} +#endif + +extension Bovine where Storage: ~Copyable { + @inlinable + @inline(__always) + public mutating func isUnique() -> Bool { + isKnownUniquelyReferenced(&_box) + } + + @inlinable + public mutating func ensureUnique( + cloner: (borrowing Storage) -> Storage + ) { + if isUnique() { return } + _box = _Box(cloner(_box.storage)) + } +} + +extension Bovine where Storage: ~Copyable { + @inlinable + @inline(__always) + public var value: Storage { + _read { + yield _box.storage + } + _modify { + precondition(isUnique()) + yield &_box.storage + } + } + + @inlinable + @inline(__always) + public func read( + _ body: (borrowing Storage) throws(E) -> R + ) throws(E) -> R { + try body(_box.storage) + } + + @inlinable + @inline(__always) + public mutating func update( + _ body: (inout Storage) throws(E) -> R + ) throws(E) -> R { + precondition(isUnique()) + return try body(&_box.storage) + } +} + +extension Bovine where Storage: ~Copyable { + @inlinable + public func isIdentical(to other: Self) -> Bool { + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + return self._box === other._box + } else { + let a = Builtin.bridgeToRawPointer(self._box) + let b = Builtin.bridgeToRawPointer(other._box) + return Bool(Builtin.cmp_eq_RawPointer(a, b)) + } + } +} diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 61d92c798..07411cc00 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */; }; + 7D380E0D2C6D565300AD8F58 /* Bovine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E0C2C6D565000AD8F58 /* Bovine.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859229E4F74400B291CD /* BitArray+Descriptions.swift */; }; 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; @@ -439,6 +440,7 @@ /* Begin PBXFileReference section */ 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; + 7D380E0C2C6D565000AD8F58 /* Bovine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bovine.swift; sourceTree = ""; }; 7D5A64D229CCE8CC00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D329CCEE9A00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D429CCEF1500CB2595 /* generate-sources.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "generate-sources.sh"; sourceTree = ""; }; @@ -1799,6 +1801,7 @@ isa = PBXGroup; children = ( 7DEBEC182C5AE43C00A1BF15 /* Container.swift */, + 7D380E0C2C6D565000AD8F58 /* Bovine.swift */, 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */, 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */, ); @@ -2165,6 +2168,7 @@ 7DE9207A29CA70F4004483EB /* BigString+UTF8View.swift in Sources */, 7DE9201329CA70F3004483EB /* OrderedDictionary+CustomReflectable.swift in Sources */, 7DE9201C29CA70F3004483EB /* OrderedDictionary+Partial MutableCollection.swift in Sources */, + 7D380E0D2C6D565300AD8F58 /* Bovine.swift in Sources */, 7DE9202729CA70F3004483EB /* OrderedSet+Insertions.swift in Sources */, 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */, 7DE9208729CA70F4004483EB /* BigString+Sequence.swift in Sources */, From 4d389588045535824d421812e4b135fb5163c7dd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 19:20:15 -0700 Subject: [PATCH 080/195] [Future] Add NewArray proof of concept --- Sources/Future/CMakeLists.txt | 1 + Sources/Future/Containers/NewArray.swift | 95 +++++++++++++++++++++ Sources/Future/Containers/RigidArray.swift | 26 ++++++ Xcode/Collections.xcodeproj/project.pbxproj | 4 + 4 files changed, 126 insertions(+) create mode 100644 Sources/Future/Containers/NewArray.swift diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index d1f400e13..4767db1d6 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(Future "Containers/Bovine.swift" "Containers/Container.swift" "Containers/DynamicArray.swift" + "Containers/NewArray.swift" "Containers/RigidArray.swift" ) diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift new file mode 100644 index 000000000..26a62be34 --- /dev/null +++ b/Sources/Future/Containers/NewArray.swift @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +/// What `Array` might look like if we defined it today. +@frozen +public struct NewArray { + @usableFromInline + internal var _storage: Bovine> + + @inlinable + public init(minimumCapacity: Int) { + self._storage = Bovine(RigidArray(capacity: minimumCapacity)) + } +} + +extension NewArray { + @inlinable + public var capacity: Int { _storage.read { $0.capacity } } + + @inlinable + internal mutating func _ensureUnique() { + _storage.ensureUnique { $0._copy() } + } + + @inlinable + internal mutating func _ensureUnique(minimumCapacity: Int) { + if !_storage.isUnique() { + let c = Swift.max(count, minimumCapacity) + _storage = Bovine(_storage.read { $0._copy(capacity: c) }) + } else if minimumCapacity > self.capacity { + _storage = Bovine(_storage.update { $0._move(capacity: minimumCapacity) }) + } + } + + @inlinable + internal func _read( + _ body: (borrowing RigidArray) throws(E) -> Result + ) throws(E) -> Result { + try _storage.read(body) + } + + @inlinable + internal mutating func _update( + _ body: (inout RigidArray) throws(E) -> Result + ) throws(E) -> Result { + _ensureUnique() + return try _storage.update(body) + } + + @inlinable + internal mutating func _update( + minimumCapacity: Int, + _ body: (inout RigidArray) throws(E) -> Result + ) throws(E) -> Result { + _ensureUnique(minimumCapacity: minimumCapacity) + return try _storage.update(body) + } +} + +extension NewArray: RandomAccessCollection, MutableCollection { + public typealias Index = Int + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _storage.read { $0.count } } + + public subscript(position: Int) -> Element { + @inlinable + get { + _read { $0[position] } + } + @inlinable + @inline(__always) + _modify { + _ensureUnique() + yield &_storage.value[position] + } + } +} + +extension NewArray { + public var span: Span { + Span(unsafeElements: _storage.value._items, owner: self) + } +} diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index eb85fc756..95901effa 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -214,3 +214,29 @@ extension RigidArray { } } } + +extension RigidArray { + @inlinable + internal func _copy() -> Self { + _copy(capacity: capacity) + } + + @inlinable + internal func _copy(capacity: Int) -> Self { + precondition(capacity >= count) + var result = RigidArray(capacity: capacity) + result._storage.initializeAll(fromContentsOf: _storage) + result._count = count + return result + } + + @inlinable + internal mutating func _move(capacity: Int) -> Self { + precondition(capacity >= count) + var result = RigidArray(capacity: capacity) + result._storage.moveInitializeAll(fromContentsOf: _storage) + result._count = count + self._count = 0 + return result + } +} diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 07411cc00..6b03b4159 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E042C6ABF6000AD8F58 /* NewArray.swift */; }; 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */; }; 7D380E0D2C6D565300AD8F58 /* Bovine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E0C2C6D565000AD8F58 /* Bovine.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; @@ -439,6 +440,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7D380E042C6ABF6000AD8F58 /* NewArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArray.swift; sourceTree = ""; }; 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; 7D380E0C2C6D565000AD8F58 /* Bovine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bovine.swift; sourceTree = ""; }; 7D5A64D229CCE8CC00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -1804,6 +1806,7 @@ 7D380E0C2C6D565000AD8F58 /* Bovine.swift */, 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */, 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */, + 7D380E042C6ABF6000AD8F58 /* NewArray.swift */, ); path = Containers; sourceTree = ""; @@ -2174,6 +2177,7 @@ 7DE9208729CA70F4004483EB /* BigString+Sequence.swift in Sources */, 7DE9202229CA70F3004483EB /* OrderedSet+Diffing.swift in Sources */, 7DE920CA29CA70F4004483EB /* BitSet+CustomStringConvertible.swift in Sources */, + 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */, 7DE9212E29CA70F4004483EB /* TreeSet+SetAlgebra isStrictSuperset.swift in Sources */, 7DE920A829CA70F4004483EB /* String.Index+ABI.swift in Sources */, 7DE9204F29CA70F3004483EB /* Deque.swift in Sources */, From 0e7c4655be96cc2cf91a1de41b08dfd429ad5407 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 19:21:08 -0700 Subject: [PATCH 081/195] [Future] RigidArray, DynamicArray: Add `span` properties --- Sources/Future/Containers/DynamicArray.swift | 6 ++++++ Sources/Future/Containers/RigidArray.swift | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift index 50a8f6a2c..907368ecf 100644 --- a/Sources/Future/Containers/DynamicArray.swift +++ b/Sources/Future/Containers/DynamicArray.swift @@ -28,6 +28,12 @@ extension DynamicArray where Element: ~Copyable { public var capacity: Int { _storage.capacity } } +extension DynamicArray where Element: ~Copyable { + public var span: Span { + _storage.span + } +} + extension DynamicArray: RandomAccessContainer where Element: ~Copyable { public typealias BorrowingIterator = RigidArray.BorrowingIterator public typealias Index = Int diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index 95901effa..7e6b68bc2 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -47,6 +47,12 @@ extension RigidArray where Element: ~Copyable { public var isFull: Bool { freeCapacity == 0 } } +extension RigidArray where Element: ~Copyable { + public var span: Span { + Span(unsafeElements: _items, owner: self) + } +} + extension RigidArray: RandomAccessContainer where Element: ~Copyable { public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { @usableFromInline From ce97a048427819667014a69038359a0288d9d7c0 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 19:43:43 -0700 Subject: [PATCH 082/195] [Future][NFC] Add missing headers --- Sources/Future/Containers/Container.swift | 11 +++++++++++ Sources/Future/Containers/DynamicArray.swift | 11 +++++++++++ Sources/Future/Containers/RigidArray.swift | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 58c359191..b4d5a5be9 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + public protocol BorrowingIteratorProtocol: ~Escapable { associatedtype Element: ~Copyable diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift index 907368ecf..b84787aa3 100644 --- a/Sources/Future/Containers/DynamicArray.swift +++ b/Sources/Future/Containers/DynamicArray.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + /// A dynamically self-resizing, heap allocated, noncopyable array /// of potentially noncopyable elements. @frozen diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index 7e6b68bc2..adc654eb8 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + /// A manually resizable, heap allocated, noncopyable array of /// potentially noncopyable elements. @frozen From 27ce1d423c807717cf1f2a3b8652b89eba2a27bf Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Aug 2024 19:49:59 -0700 Subject: [PATCH 083/195] [Future] More leading underscores and explicit visibility modifiers --- Sources/Future/Box.swift | 24 ++++++++++++------------ Sources/Future/RawSpan.swift | 8 +++++--- Sources/Future/Span.swift | 9 +++++---- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index 6cf6b6601..49cae1360 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -12,26 +12,26 @@ @frozen public struct Box: ~Copyable { @usableFromInline - let pointer: UnsafeMutablePointer + internal let _pointer: UnsafeMutablePointer @_alwaysEmitIntoClient @_transparent public init(_ value: consuming T) { - pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.initialize(to: value) + _pointer = UnsafeMutablePointer.allocate(capacity: 1) + _pointer.initialize(to: value) } @_alwaysEmitIntoClient @_transparent public init(_ fromInout: consuming Inout) { - pointer = fromInout._pointer + _pointer = fromInout._pointer } @_alwaysEmitIntoClient @inlinable deinit { - pointer.deinitialize(count: 1) - pointer.deallocate() + _pointer.deinitialize(count: 1) + _pointer.deallocate() } } @@ -39,8 +39,8 @@ extension Box where T: ~Copyable { @_alwaysEmitIntoClient @_transparent public consuming func consume() -> T { - let result = pointer.move() - pointer.deallocate() + let result = _pointer.move() + _pointer.deallocate() discard self return result } @@ -48,7 +48,7 @@ extension Box where T: ~Copyable { @_alwaysEmitIntoClient @_transparent public consuming func leak() -> dependsOn(immortal) Inout { - let result = Inout(unsafeImmortalAddress: pointer) + let result = Inout(unsafeImmortalAddress: _pointer) discard self return result } @@ -57,12 +57,12 @@ extension Box where T: ~Copyable { public subscript() -> T { @_transparent unsafeAddress { - UnsafePointer(pointer) + UnsafePointer(_pointer) } @_transparent nonmutating unsafeMutableAddress { - pointer + _pointer } } } @@ -71,6 +71,6 @@ extension Box where T: Copyable { @_alwaysEmitIntoClient @_transparent public borrowing func copy() -> T { - pointer.pointee + _pointer.pointee } } diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 447ce3368..97b79d41a 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -16,12 +16,14 @@ import Builtin // of unspecified type. @frozen public struct RawSpan: Copyable, ~Escapable { - @usableFromInline let _pointer: UnsafeRawPointer? + @usableFromInline + internal let _pointer: UnsafeRawPointer? @usableFromInline @inline(__always) - var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } + internal var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } - @usableFromInline let _count: Int + @usableFromInline + internal let _count: Int @inlinable @inline(__always) internal init( diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 32cbeec60..738bd5692 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -16,16 +16,17 @@ import Builtin // contains initialized instances of `Element`. @frozen public struct Span: Copyable, ~Escapable { - @usableFromInline let _buffer: UnsafeBufferPointer + @usableFromInline + internal let _buffer: UnsafeBufferPointer @usableFromInline @inline(__always) - var _pointer: UnsafePointer? { _buffer.baseAddress } + internal var _pointer: UnsafePointer? { _buffer.baseAddress } @usableFromInline @inline(__always) - var _start: UnsafePointer { _pointer.unsafelyUnwrapped } + internal var _start: UnsafePointer { _pointer.unsafelyUnwrapped } @usableFromInline @inline(__always) - var _count: Int { _buffer.count } + internal var _count: Int { _buffer.count } @inlinable @inline(__always) internal init( From bfbb0f605e6b38113daaa431714fe06c075a6445 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sun, 1 Sep 2024 20:40:04 -0700 Subject: [PATCH 084/195] =?UTF-8?q?[Future]=20Bovine=20=E2=86=92=20Shared?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Future/CMakeLists.txt | 2 +- Sources/Future/Containers/Bovine.swift | 128 -------------- Sources/Future/Containers/NewArray.swift | 8 +- Sources/Future/Containers/Shared.swift | 186 ++++++++++++++++++++ Sources/Future/LifetimeOverride.swift | 4 +- Xcode/Collections.xcodeproj/project.pbxproj | 8 +- 6 files changed, 198 insertions(+), 138 deletions(-) delete mode 100644 Sources/Future/Containers/Bovine.swift create mode 100644 Sources/Future/Containers/Shared.swift diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 4767db1d6..6ade7dbd6 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -15,7 +15,7 @@ add_library(Future "LifetimeOverride.swift" "RawSpan.swift" "Span.swift" - "Containers/Bovine.swift" + "Containers/Shared.swift" "Containers/Container.swift" "Containers/DynamicArray.swift" "Containers/NewArray.swift" diff --git a/Sources/Future/Containers/Bovine.swift b/Sources/Future/Containers/Bovine.swift deleted file mode 100644 index fdc582c51..000000000 --- a/Sources/Future/Containers/Bovine.swift +++ /dev/null @@ -1,128 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import Builtin // For Bovine.isIdentical - -/// A utility adapter that wraps a noncopyable storage type in a copy-on-write -/// struct, enabling efficient implementation of value semantics. The type -/// allows safe borrowing and mutating access to its storage, with minimal fuss. -/// -/// Like `ManagedBufferPointer`, this type is intended to be used within the -/// internal implementation of public types. Instances of it aren't designed -/// to be exposed as public. -/// -/// (The placeholder name `Bovine` is hinting at the common abbreviation of the -/// copy-on-write optimization.) -@frozen -public struct Bovine { - @usableFromInline - internal var _box: _Box - - @inlinable - public init(_ storage: consuming Storage) { - self._box = _Box(storage) - } -} - -extension Bovine: @unchecked Sendable where Storage: Sendable & ~Copyable {} - -#if true // FIXME: Silent error on class definition nested in noncopyable struct -@usableFromInline -internal final class _BovineBox { - @exclusivity(unchecked) - @usableFromInline - internal var storage: Storage - - @inlinable - internal init(_ storage: consuming Storage) { - self.storage = storage - } -} - -extension Bovine where Storage: ~Copyable { - @usableFromInline - internal typealias _Box = _BovineBox -} -#else -extension Bovine where Storage: ~Copyable { - @usableFromInline - internal final class _Box { - @exclusivity(unchecked) - @usableFromInline - internal var storage: Storage - - @inlinable - internal init(_ storage: consuming Storage) { - self.storage = storage - } - } -} -#endif - -extension Bovine where Storage: ~Copyable { - @inlinable - @inline(__always) - public mutating func isUnique() -> Bool { - isKnownUniquelyReferenced(&_box) - } - - @inlinable - public mutating func ensureUnique( - cloner: (borrowing Storage) -> Storage - ) { - if isUnique() { return } - _box = _Box(cloner(_box.storage)) - } -} - -extension Bovine where Storage: ~Copyable { - @inlinable - @inline(__always) - public var value: Storage { - _read { - yield _box.storage - } - _modify { - precondition(isUnique()) - yield &_box.storage - } - } - - @inlinable - @inline(__always) - public func read( - _ body: (borrowing Storage) throws(E) -> R - ) throws(E) -> R { - try body(_box.storage) - } - - @inlinable - @inline(__always) - public mutating func update( - _ body: (inout Storage) throws(E) -> R - ) throws(E) -> R { - precondition(isUnique()) - return try body(&_box.storage) - } -} - -extension Bovine where Storage: ~Copyable { - @inlinable - public func isIdentical(to other: Self) -> Bool { - if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { - return self._box === other._box - } else { - let a = Builtin.bridgeToRawPointer(self._box) - let b = Builtin.bridgeToRawPointer(other._box) - return Bool(Builtin.cmp_eq_RawPointer(a, b)) - } - } -} diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift index 26a62be34..4e89aeb5d 100644 --- a/Sources/Future/Containers/NewArray.swift +++ b/Sources/Future/Containers/NewArray.swift @@ -13,11 +13,11 @@ @frozen public struct NewArray { @usableFromInline - internal var _storage: Bovine> + internal var _storage: Shared> @inlinable public init(minimumCapacity: Int) { - self._storage = Bovine(RigidArray(capacity: minimumCapacity)) + self._storage = Shared(RigidArray(capacity: minimumCapacity)) } } @@ -34,9 +34,9 @@ extension NewArray { internal mutating func _ensureUnique(minimumCapacity: Int) { if !_storage.isUnique() { let c = Swift.max(count, minimumCapacity) - _storage = Bovine(_storage.read { $0._copy(capacity: c) }) + _storage = Shared(_storage.read { $0._copy(capacity: c) }) } else if minimumCapacity > self.capacity { - _storage = Bovine(_storage.update { $0._move(capacity: minimumCapacity) }) + _storage = Shared(_storage.update { $0._move(capacity: minimumCapacity) }) } } diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift new file mode 100644 index 000000000..1df697b56 --- /dev/null +++ b/Sources/Future/Containers/Shared.swift @@ -0,0 +1,186 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin // For Shared.isIdentical + +/// A utility adapter that wraps a noncopyable storage type in a copy-on-write +/// struct, enabling efficient implementation of value semantics. The type +/// allows safe borrowing and mutating access to its storage, with minimal fuss. +/// +/// Like `ManagedBufferPointer`, this type is intended to be used within the +/// internal implementation of public types. Instances of it aren't designed +/// to be exposed as public. +@frozen +public struct Shared { + @usableFromInline + internal var _box: _Box + + @inlinable + public init(_ storage: consuming Storage) { + self._box = _Box(storage) + } +} + +extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} + +#if true // FIXME: Silent error on class definition nested in noncopyable struct +@usableFromInline +internal final class _SharedBox { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + self.storage = storage + } +} + +extension Shared where Storage: ~Copyable { + @usableFromInline + internal typealias _Box = _SharedBox +} +#else +extension Shared where Storage: ~Copyable { + @usableFromInline + internal final class _Box { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + self.storage = storage + } + } +} +#endif + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public mutating func isUnique() -> Bool { + isKnownUniquelyReferenced(&_box) + } + + @inlinable + public mutating func ensureUnique( + cloner: (borrowing Storage) -> Storage + ) { + if isUnique() { return } + _box = _Box(cloner(_box.storage)) + } +} + +import struct SwiftShims.HeapObject + +extension Shared where Storage: ~Copyable { + // FIXME: Can we avoid hacks like this? If not, perhaps `_Box.storage` should be tail-allocated. + @inlinable + internal var _address: UnsafePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = ( + UnsafeRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } + + @inlinable + internal var _mutableAddress: UnsafeMutablePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = ( + UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public var value: /*FIXME: dependsOn(self)*/ Storage { + // FIXME: This implements the wrong shape. + // FIXME: Semantically it yields a borrow scoped to an access of this `value` variable, + // FIXME: not the much wider borrow of `self`, which we'd actually want. + _read { + yield _box.storage + } + _modify { + precondition(isUnique()) + yield &_box.storage + } + } + + // FIXME: This builds, but attempts to use it don't: they fail with an unexpected exclusivity violation. + @inlinable + public subscript() -> dependsOn(self) Storage { + //@_transparent + unsafeAddress { + _address + } + + //@_transparent + unsafeMutableAddress { + precondition(isUnique()) + return _mutableAddress + } + } + + @inlinable + @inline(__always) + public func read( + _ body: (borrowing Storage) throws(E) -> R + ) throws(E) -> R { + // FIXME: This also implements the wrong shape. + // FIXME: The borrow of `Storage` isn't tied to `self` at all, and + // FIXME: it obviously cannot legally escape the `read` call. + try body(_box.storage) + } + + @inlinable + @inline(__always) + public mutating func update( + _ body: (inout Storage) throws(E) -> R + ) throws(E) -> R { + precondition(isUnique()) + return try body(&_box.storage) + } +} + +#if false // FIXME: Use it or lose it +extension Shared where Storage: ~Copyable { + // This is the actual shape we want. There is currently no way to express it. + @inlinable + public borrowing func read() -> dependsOn(self) Borrow { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage); but we have not + // (cannot) explain this to the compiler. + Borrow(unsafeAddress: _address, owner: self) + } +} +#endif + +extension Shared where Storage: ~Copyable { + @inlinable + public func isIdentical(to other: Self) -> Bool { + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + return self._box === other._box + } else { + // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions + // that are only supported in the Swift 6+ runtime. + let a = Builtin.bridgeToRawPointer(self._box) + let b = Builtin.bridgeToRawPointer(other._box) + return Bool(Builtin.cmp_eq_RawPointer(a, b)) + } + } +} + diff --git a/Sources/Future/LifetimeOverride.swift b/Sources/Future/LifetimeOverride.swift index e7b8d8da4..4a19ca73e 100644 --- a/Sources/Future/LifetimeOverride.swift +++ b/Sources/Future/LifetimeOverride.swift @@ -13,8 +13,10 @@ import Builtin @_unsafeNonescapableResult +@inlinable @inline(__always) public func unsafelyOverrideLifetime< - T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable + T: ~Copyable & ~Escapable, + U: ~Copyable & ~Escapable >( of dependent: consuming T, to source: borrowing U diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 6b03b4159..cdb159e15 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E042C6ABF6000AD8F58 /* NewArray.swift */; }; 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */; }; - 7D380E0D2C6D565300AD8F58 /* Bovine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E0C2C6D565000AD8F58 /* Bovine.swift */; }; + 7D380E0D2C6D565300AD8F58 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E0C2C6D565000AD8F58 /* Shared.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859229E4F74400B291CD /* BitArray+Descriptions.swift */; }; 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; @@ -442,7 +442,7 @@ /* Begin PBXFileReference section */ 7D380E042C6ABF6000AD8F58 /* NewArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArray.swift; sourceTree = ""; }; 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; - 7D380E0C2C6D565000AD8F58 /* Bovine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bovine.swift; sourceTree = ""; }; + 7D380E0C2C6D565000AD8F58 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; 7D5A64D229CCE8CC00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D329CCEE9A00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D429CCEF1500CB2595 /* generate-sources.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "generate-sources.sh"; sourceTree = ""; }; @@ -1803,7 +1803,7 @@ isa = PBXGroup; children = ( 7DEBEC182C5AE43C00A1BF15 /* Container.swift */, - 7D380E0C2C6D565000AD8F58 /* Bovine.swift */, + 7D380E0C2C6D565000AD8F58 /* Shared.swift */, 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */, 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */, 7D380E042C6ABF6000AD8F58 /* NewArray.swift */, @@ -2171,7 +2171,7 @@ 7DE9207A29CA70F4004483EB /* BigString+UTF8View.swift in Sources */, 7DE9201329CA70F3004483EB /* OrderedDictionary+CustomReflectable.swift in Sources */, 7DE9201C29CA70F3004483EB /* OrderedDictionary+Partial MutableCollection.swift in Sources */, - 7D380E0D2C6D565300AD8F58 /* Bovine.swift in Sources */, + 7D380E0D2C6D565300AD8F58 /* Shared.swift in Sources */, 7DE9202729CA70F3004483EB /* OrderedSet+Insertions.swift in Sources */, 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */, 7DE9208729CA70F4004483EB /* BigString+Sequence.swift in Sources */, From 3c66dab3b063a2135fb98ee39d4fdb167aad6489 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sun, 1 Sep 2024 20:46:43 -0700 Subject: [PATCH 085/195] [Deque] Turn Deque into a Shared> --- Sources/DequeModule/CMakeLists.txt | 2 +- Sources/DequeModule/Deque+Codable.swift | 1 - Sources/DequeModule/Deque+Collection.swift | 94 +-- Sources/DequeModule/Deque+Container.swift | 48 ++ Sources/DequeModule/Deque+Equatable.swift | 14 +- Sources/DequeModule/Deque+Extras.swift | 2 +- Sources/DequeModule/Deque+Testing.swift | 24 +- Sources/DequeModule/Deque._Storage.swift | 57 -- Sources/DequeModule/Deque.swift | 43 +- Sources/DequeModule/RigidDeque.swift | 102 +-- Sources/DequeModule/_UnsafeDequeHandle.swift | 610 +++++++++--------- ...uffer.swift => _UnsafeDequeSegments.swift} | 14 +- Tests/DequeTests/DequeTests.swift | 5 + Xcode/Collections.xcodeproj/project.pbxproj | 64 +- 14 files changed, 568 insertions(+), 512 deletions(-) create mode 100644 Sources/DequeModule/Deque+Container.swift delete mode 100644 Sources/DequeModule/Deque._Storage.swift rename Sources/DequeModule/{_UnsafeWrappedBuffer.swift => _UnsafeDequeSegments.swift} (94%) diff --git a/Sources/DequeModule/CMakeLists.txt b/Sources/DequeModule/CMakeLists.txt index f85a23d01..2e5cd3915 100644 --- a/Sources/DequeModule/CMakeLists.txt +++ b/Sources/DequeModule/CMakeLists.txt @@ -39,5 +39,5 @@ target_sources(${module_name} PRIVATE "RigidDeque.swift" "_DequeSlot.swift" "_UnsafeDequeHandle.swift" - "_UnsafeWrappedBuffer.swift" + "_UnsafeDequeSegments.swift" ) diff --git a/Sources/DequeModule/Deque+Codable.swift b/Sources/DequeModule/Deque+Codable.swift index 7e9e8d579..ee214b8cd 100644 --- a/Sources/DequeModule/Deque+Codable.swift +++ b/Sources/DequeModule/Deque+Codable.swift @@ -35,7 +35,6 @@ extension Deque: Decodable where Element: Decodable { /// - Parameter decoder: The decoder to read data from. @inlinable public init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() if let count = container.count { self.init(minimumCapacity: count) diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index ef528253d..8bf44b8f3 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -13,7 +13,7 @@ import InternalCollectionsUtilities #endif -extension Deque: Sequence { +extension Deque { // Implementation note: we could also use the default `IndexingIterator` here. // This custom implementation performs direct storage access to eliminate any // and all index validation overhead. It also optimizes away repeated @@ -21,7 +21,7 @@ extension Deque: Sequence { /// An iterator over the members of a deque. @frozen - public struct Iterator: IteratorProtocol { + public struct Iterator { @usableFromInline internal var _base: Deque @@ -60,40 +60,46 @@ extension Deque: Sequence { return Self(_base: _base, start: start, end: end) } } + } +} - @inlinable - @inline(never) - internal mutating func _swapSegment() -> Bool { - assert(_nextSlot == _endSlot) - return _base._read { handle in - let end = handle.endSlot - if end == .zero || end == _nextSlot { - return false - } - _endSlot = end - _nextSlot = .zero - return true +extension Deque.Iterator: Sendable where Element: Sendable {} + +extension Deque.Iterator: IteratorProtocol { + @inlinable + @inline(never) + internal mutating func _swapSegment() -> Bool { + assert(_nextSlot == _endSlot) + return _base._read { handle in + let end = handle.endSlot + if end == .zero || end == _nextSlot { + return false } + _endSlot = end + _nextSlot = .zero + return true } + } - /// Advances to the next element and returns it, or `nil` if no next element - /// exists. - /// - /// Once `nil` has been returned, all subsequent calls return `nil`. - @inlinable - public mutating func next() -> Element? { - if _nextSlot == _endSlot { - guard _swapSegment() else { return nil } - } - assert(_nextSlot < _endSlot) - let slot = _nextSlot - _nextSlot = _nextSlot.advanced(by: 1) - return _base._read { handle in - return handle.ptr(at: slot).pointee - } + /// Advances to the next element and returns it, or `nil` if no next element + /// exists. + /// + /// Once `nil` has been returned, all subsequent calls return `nil`. + @inlinable + public mutating func next() -> Element? { + if _nextSlot == _endSlot { + guard _swapSegment() else { return nil } + } + assert(_nextSlot < _endSlot) + let slot = _nextSlot + _nextSlot = _nextSlot.advanced(by: 1) + return _base._read { handle in + return handle.ptr(at: slot).pointee } } +} +extension Deque: Sequence { /// Returns an iterator over the elements of the deque. /// /// - Complexity: O(1) @@ -165,8 +171,6 @@ extension Deque: Sequence { } } -extension Deque.Iterator: Sendable where Element: Sendable {} - extension Deque: RandomAccessCollection { public typealias Index = Int public typealias SubSequence = Slice @@ -177,7 +181,7 @@ extension Deque: RandomAccessCollection { /// - Complexity: O(1) @inlinable @inline(__always) - public var count: Int { _storage._value.count } + public var count: Int { _storage.read { $0.count } } /// The position of the first element in a nonempty deque. /// @@ -355,16 +359,10 @@ extension Deque: RandomAccessCollection { @inlinable public subscript(index: Int) -> Element { get { - precondition(index >= 0 && index < count, "Index out of bounds") - return _read { $0.ptr(at: $0.slot(forOffset: index)).pointee } + return _storage.read { $0[index] } } set { - precondition(index >= 0 && index < count, "Index out of bounds") - _ensureUnique() - _update { handle in - let slot = handle.slot(forOffset: index) - handle.mutablePtr(at: slot).pointee = newValue - } + _storage.update { $0[index] = newValue } } @inline(__always) // https://github.com/apple/swift-collections/issues/164 _modify { @@ -390,7 +388,7 @@ extension Deque: RandomAccessCollection { } @inlinable - internal mutating func _finalizeModify(_ slot: _Slot, _ value: Element) { + internal mutating func _finalizeModify(_ slot: _Slot, _ value: __owned Element) { _update { handle in handle.mutablePtr(at: slot).initialize(to: value) } @@ -508,7 +506,7 @@ extension Deque: RangeReplaceableCollection { @inlinable public init() { // FIXME: Can we do empty singletons in this world? Should _storage become optional? - _storage = _Storage(capacity: 0) + self.init(minimumCapacity: 0) } /// Reserves enough space to store the specified number of elements. @@ -636,6 +634,18 @@ extension Deque: RangeReplaceableCollection { } } + @inlinable + public init(_ elements: UnsafeBufferPointer) { + guard elements.count > 0 else { self.init(); return } + self.init(minimumCapacity: elements.count) + _update { handle in + assert(handle.startSlot == .zero) + let target = handle.mutableBuffer(for: .zero ..< _Slot(at: elements.count)) + target.initializeAll(fromContentsOf: elements) + handle.count = elements.count + } + } + /// Adds a new element at the end of the deque. /// /// Use this method to append a single element to the end of a deque. diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift new file mode 100644 index 000000000..f0e2fb650 --- /dev/null +++ b/Sources/DequeModule/Deque+Container.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import Future +#endif + +extension Deque: RandomAccessContainer { + public typealias BorrowingIterator = RigidDeque.BorrowingIterator + + @inlinable + public func startBorrowingIteration() -> BorrowingIterator { + self.startBorrowingIteration(from: 0) + } + + @inlinable + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + // FIXME: This is unacceptably unsafe. We want to access `_storage.value` + // FIXME: as if it was a structural part of `self`, but there is no way + // FIXME: to express this in Swift. + BorrowingIterator( + _unsafeSegments: _storage.value._handle.segments(), + startOffset: start, + owner: self) + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + precondition(_read { $0.segments().isIdentical(to: position._segments) }) + return position._offset + } + + @inlinable + public func formIndex( + _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index + ) { + // Note: Range checks are deferred until element access. + index.advance(by: &distance, limitedBy: limit) + } +} diff --git a/Sources/DequeModule/Deque+Equatable.swift b/Sources/DequeModule/Deque+Equatable.swift index 6906b9d79..45d49c374 100644 --- a/Sources/DequeModule/Deque+Equatable.swift +++ b/Sources/DequeModule/Deque+Equatable.swift @@ -17,16 +17,10 @@ extension Deque: Equatable where Element: Equatable { /// - Complexity: O(`min(left.count, right.count)`) @inlinable public static func ==(left: Self, right: Self) -> Bool { - let lhsCount = left.count - if lhsCount != right.count { - return false - } - - // Test referential equality. - if lhsCount == 0 || left._isIdentical(to: right) { - return true - } - + let count = left.count + guard count == right.count else { return false } + if count == 0 { return true } + if left._storage.isIdentical(to: right._storage) { return true } return left.elementsEqual(right) } } diff --git a/Sources/DequeModule/Deque+Extras.swift b/Sources/DequeModule/Deque+Extras.swift index 28d5abece..8f4e6fb8a 100644 --- a/Sources/DequeModule/Deque+Extras.swift +++ b/Sources/DequeModule/Deque+Extras.swift @@ -45,7 +45,7 @@ extension Deque { initializingWith initializer: (inout UnsafeMutableBufferPointer, inout Int) throws -> Void ) rethrows { - self._storage = .init(capacity: capacity) + self.init(minimumCapacity: capacity) try _update { handle in handle.startSlot = .zero var count = 0 diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index bb5cf3be1..ce4dbea65 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -37,8 +37,8 @@ extension Deque { /// This property isn't intended to be used outside of `Deque`'s own test /// target. @_spi(Testing) - public var _capacity: Int { - _storage.capacity + public var _unstableCapacity: Int { + _capacity } /// The number of the storage slot in this deque that holds the first element. @@ -48,8 +48,8 @@ extension Deque { /// This property isn't intended to be used outside of `Deque`'s own test /// target. @_spi(Testing) - public var _startSlot: Int { - _storage.startSlot.position + public var _unstableStartSlot: Int { + _storage.read { $0._handle.startSlot.position } } /// Constructs a deque instance of the specified contents and layout. Exposed @@ -68,12 +68,12 @@ extension Deque { let startSlot = _Slot(at: startSlot) - var hd = RigidDeque(capacity: capacity) - hd._handle.count = contents.count - hd._handle.startSlot = startSlot - if hd._handle.count > 0 { + var d = RigidDeque(capacity: capacity) + d._handle.count = contents.count + d._handle.startSlot = startSlot + if d._handle.count > 0 { contents.withUnsafeBufferPointer { source in - let segments = hd._handle.mutableSegments() + let segments = d._handle.mutableSegments() let c = segments.first.count segments.first.initializeAll(fromContentsOf: source.prefix(c)) if let second = segments.second { @@ -81,9 +81,9 @@ extension Deque { } } } - self.init(_storage: _Storage(hd)) - assert(self._capacity == capacity) - assert(self._startSlot == startSlot.position) + self.init(_storage: d) + assert(self._unstableCapacity == capacity) + assert(self._unstableStartSlot == startSlot.position) assert(self.count == contents.count) } } diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift deleted file mode 100644 index 4c4a26484..000000000 --- a/Sources/DequeModule/Deque._Storage.swift +++ /dev/null @@ -1,57 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -extension Deque { - @usableFromInline - internal final class _Storage { - @usableFromInline - @exclusivity(unchecked) - internal var _value: RigidDeque - - @inlinable - @inline(__always) - internal init(_ value: consuming RigidDeque) { - self._value = value - } - - @inlinable - internal convenience init(capacity: Int) { - self.init(RigidDeque(capacity: capacity)) - } - } -} - -extension Deque._Storage: CustomStringConvertible { - @usableFromInline - internal var description: String { - "Deque<\(Element.self)>._Storage\(_value._handle.description)" - } -} - -extension Deque._Storage { -#if COLLECTIONS_INTERNAL_CHECKS - @usableFromInline @inline(never) @_effects(releasenone) - internal func _checkInvariants() { - _value._checkInvariants() - } -#else - @inlinable @inline(__always) - internal func _checkInvariants() {} -#endif // COLLECTIONS_INTERNAL_CHECKS -} - -extension Deque._Storage { - @inlinable - internal var capacity: Int { _value.capacity } - - @inlinable - internal var startSlot: _DequeSlot { _value._handle.startSlot } -} diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index 096dccf4b..33b2cadcc 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -85,11 +85,11 @@ public struct Deque { internal typealias _Slot = _DequeSlot @usableFromInline - internal var _storage: _Storage + internal var _storage: Shared> @inlinable - internal init(_storage: _Storage) { - self._storage = _storage + internal init(_storage: consuming RigidDeque) { + self._storage = Shared(_storage) } /// Creates and empty deque with preallocated space for at least the specified @@ -100,7 +100,7 @@ public struct Deque { /// storage buffer. @inlinable public init(minimumCapacity: Int) { - self._storage = _Storage(capacity: minimumCapacity) + self.init(_storage: RigidDeque(capacity: minimumCapacity)) } } @@ -115,7 +115,9 @@ extension Deque { internal func _read( _ body: (borrowing _UnsafeHandle) throws(E) -> R ) throws(E) -> R { - try body(_storage._value._handle) + try _storage.read { rigid throws(E) in + try body(rigid._handle) + } } @inlinable @@ -123,17 +125,20 @@ extension Deque { internal mutating func _update( _ body: (inout _UnsafeHandle) throws(E) -> R ) throws(E) -> R { - defer { _fixLifetime(self) } - assert(_isUnique()) - return try body(&_storage._value._handle) + _ensureUnique() + return try _storage.update { v throws(E) in + try body(&v._handle) + } } +} +extension Deque { /// Return a boolean indicating whether this storage instance is known to have /// a single unique reference. If this method returns true, then it is safe to /// perform in-place mutations on the deque. @inlinable internal mutating func _isUnique() -> Bool { - isKnownUniquelyReferenced(&_storage) + _storage.isUnique() } /// Ensure that this storage refers to a uniquely held buffer by copying @@ -141,8 +146,7 @@ extension Deque { @inlinable @inline(__always) internal mutating func _ensureUnique() { - if _isUnique() { return } - self = _makeUniqueCopy() + _storage.ensureUnique(cloner: { $0._copy() }) } /// Copy elements into a new storage instance without changing capacity or @@ -150,13 +154,18 @@ extension Deque { @inlinable @inline(never) internal func _makeUniqueCopy() -> Self { - Deque(_storage: _Storage(_storage._value._copy())) + Deque(_storage: _storage.read { $0._copy() }) } @inlinable @inline(never) internal func _makeUniqueCopy(capacity: Int) -> Self { - Deque(_storage: _Storage(_storage._value._copy(capacity: capacity))) + Deque(_storage: _storage.read { $0._copy(capacity: capacity) }) + } + + @inlinable + internal var _capacity: Int { + _storage.read { $0.capacity } } @usableFromInline @@ -187,7 +196,7 @@ extension Deque { linearGrowth: Bool = false ) { let unique = _isUnique() - if _slowPath(_storage.capacity < minimumCapacity || !unique) { + if _slowPath(_capacity < minimumCapacity || !unique) { __ensureUnique( isUnique: unique, minimumCapacity: minimumCapacity, @@ -202,7 +211,7 @@ extension Deque { minimumCapacity: Int, linearGrowth: Bool ) { - if _storage.capacity >= minimumCapacity { + if _capacity >= minimumCapacity { assert(!isUnique) self = self._makeUniqueCopy() return @@ -210,7 +219,7 @@ extension Deque { let c = _growCapacity(to: minimumCapacity, linearly: linearGrowth) if isUnique { - self._storage._value.resize(to: c) + self._storage.update { $0.resize(to: c) } } else { self = self._makeUniqueCopy(capacity: c) } @@ -221,7 +230,7 @@ extension Deque { @inlinable @inline(__always) internal func _isIdentical(to other: Self) -> Bool { - self._storage === other._storage + self._storage.isIdentical(to: other._storage) } } diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index 06c409d52..35b39f1e2 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -20,13 +20,13 @@ public struct RigidDeque: ~Copyable { internal typealias _Slot = _DequeSlot @usableFromInline - internal typealias _Handle = _UnsafeDequeHandle + internal typealias _UnsafeHandle = _UnsafeDequeHandle @usableFromInline - var _handle: _Handle + internal var _handle: _UnsafeHandle @inlinable - internal init(_handle: consuming _Handle) { + internal init(_handle: consuming _UnsafeHandle) { self._handle = _handle } @@ -54,49 +54,69 @@ extension RigidDeque where Element: ~Copyable { #endif // COLLECTIONS_INTERNAL_CHECKS } -extension RigidDeque: RandomAccessContainer where Element: ~Copyable { - @frozen - public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { - @usableFromInline - internal let _segments: _UnsafeWrappedBuffer - - @usableFromInline - internal var _offset: Int - - @inlinable - internal init(_for handle: borrowing _Handle, startOffset: Int) { - self._segments = handle.segments() - self._offset = startOffset - } +extension RigidDeque where Element: ~Copyable { + @usableFromInline + internal var description: String { + _handle.description + } +} + +public struct _DequeBorrowingIterator: BorrowingIteratorProtocol, ~Escapable { + @usableFromInline + internal typealias _UnsafeHandle = _UnsafeDequeHandle + + @usableFromInline + internal let _segments: _UnsafeDequeSegments + + @usableFromInline + internal var _offset: Int - @inlinable - public mutating func nextChunk( - maximumCount: Int - ) -> dependsOn(scoped self) Span { - precondition(maximumCount > 0) - if _offset < _segments.first.count { - let d = Swift.min(maximumCount, _segments.first.count - _offset) - let slice = _segments.first.extracting(_offset ..< _offset + d) - _offset += d - return Span(unsafeElements: slice, owner: self) - } - guard let second = _segments.second else { - return Span(unsafeElements: UnsafeBufferPointer._empty, owner: self) - } - let o = _offset - _segments.first.count - let d = Swift.min(maximumCount, second.count - o) - let slice = second.extracting(o ..< o + d) + @inlinable + internal init( + _unsafeSegments segments: _UnsafeDequeSegments, + startOffset: Int, + owner: borrowing T + ) { + self._segments = segments + self._offset = startOffset + } + + @inlinable + internal init(_for handle: borrowing _UnsafeHandle, startOffset: Int) { + self.init(_unsafeSegments: handle.segments(), startOffset: startOffset, owner: handle) + } + + @inlinable + public mutating func nextChunk( + maximumCount: Int + ) -> dependsOn(scoped self) Span { + precondition(maximumCount > 0) + if _offset < _segments.first.count { + let d = Swift.min(maximumCount, _segments.first.count - _offset) + let slice = _segments.first.extracting(_offset ..< _offset + d) _offset += d return Span(unsafeElements: slice, owner: self) } + guard let second = _segments.second else { + return Span(unsafeElements: UnsafeBufferPointer._empty, owner: self) + } + let o = _offset - _segments.first.count + let d = Swift.min(maximumCount, second.count - o) + let slice = second.extracting(o ..< o + d) + _offset += d + return Span(unsafeElements: slice, owner: self) } +} + +extension RigidDeque: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = _DequeBorrowingIterator public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(_for: _handle, startOffset: 0) + _handle.startBorrowingIteration() } public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(_for: _handle, startOffset: start) + _handle.startBorrowingIteration(from: start) } public typealias Index = Int @@ -111,21 +131,17 @@ extension RigidDeque: RandomAccessContainer where Element: ~Copyable { public var startIndex: Int { 0 } @inlinable - public var endIndex: Int { count } + public var endIndex: Int { _handle.count } @inlinable public subscript(position: Int) -> Element { @inline(__always) _read { - precondition(position >= 0 && position < count) - let slot = _handle.slot(forOffset: position) - yield _handle.ptr(at: slot).pointee + yield _handle[offset: position] } @inline(__always) _modify { - precondition(position >= 0 && position < count) - let slot = _handle.slot(forOffset: position) - yield &_handle.mutablePtr(at: slot).pointee + yield &_handle[offset: position] } } diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index 517226569..25d5b1bfe 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -17,31 +17,24 @@ import InternalCollectionsUtilities @usableFromInline internal struct _UnsafeDequeHandle: ~Copyable { @usableFromInline - internal let _buffer: UnsafeMutableBufferPointer + internal typealias Slot = _DequeSlot @usableFromInline - internal var count: Int + internal var _buffer: UnsafeMutableBufferPointer @usableFromInline - internal var startSlot: _DequeSlot + internal var count: Int - @inlinable - internal static func allocate( - capacity: Int - ) -> Self { - Self( - _storage: capacity > 0 ? .allocate(capacity: capacity) : ._empty, - count: 0, - startSlot: .zero) - } + @usableFromInline + internal var startSlot: Slot @inlinable internal init( - _storage: UnsafeMutableBufferPointer, + buffer: UnsafeMutableBufferPointer, count: Int, startSlot: _DequeSlot ) { - self._buffer = _storage + self._buffer = buffer self.count = count self.startSlot = startSlot } @@ -55,21 +48,19 @@ internal struct _UnsafeDequeHandle: ~Copyable { } extension _UnsafeDequeHandle where Element: ~Copyable { - @inlinable @inline(__always) - internal var _baseAddress: UnsafeMutablePointer { - _buffer.baseAddress.unsafelyUnwrapped - } - @inlinable - internal var capacity: Int { - _buffer.count + internal static var empty: Self { + Self(buffer: ._empty, count: 0, startSlot: .zero) } -} -extension _UnsafeDequeHandle where Element: ~Copyable { - @usableFromInline - internal var description: String { - "(capacity: \(capacity), count: \(count), startSlot: \(startSlot))" + @inlinable + internal static func allocate( + capacity: Int + ) -> Self { + Self( + buffer: capacity > 0 ? .allocate(capacity: capacity) : ._empty, + count: 0, + startSlot: .zero) } } @@ -89,42 +80,26 @@ extension _UnsafeDequeHandle where Element: ~Copyable { extension _UnsafeDequeHandle where Element: ~Copyable { @usableFromInline - internal typealias Slot = _DequeSlot - - @inlinable @inline(__always) - internal func ptr(at slot: Slot) -> UnsafePointer { - assert(slot.position >= 0 && slot.position <= capacity) - return UnsafePointer(_baseAddress + slot.position) - } - - @inlinable @inline(__always) - internal mutating func mutablePtr( - at slot: Slot - ) -> UnsafeMutablePointer { - assert(slot.position >= 0 && slot.position <= capacity) - return _baseAddress + slot.position + internal var description: String { + "(capacity: \(capacity), count: \(count), start: \(startSlot))" } +} +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable @inline(__always) - internal var mutableBuffer: UnsafeMutableBufferPointer { - mutating get { - _buffer - } + internal var _baseAddress: UnsafeMutablePointer { + _buffer.baseAddress.unsafelyUnwrapped } @inlinable - internal func buffer(for range: Range) -> UnsafeBufferPointer { - assert(range.upperBound.position <= capacity) - return .init(_buffer._extracting(unchecked: range._offsets)) - } - - @inlinable @inline(__always) - internal mutating func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { - assert(range.upperBound.position <= capacity) - return _buffer._extracting(unchecked: range._offsets) + internal var capacity: Int { + _buffer.count } } + +// MARK: Slots + extension _UnsafeDequeHandle where Element: ~Copyable { /// The slot immediately following the last valid one. (`endSlot` refers to /// the valid slot corresponding to `endIndex`, which is a different thing @@ -187,9 +162,100 @@ extension _UnsafeDequeHandle where Element: ~Copyable { } } +// MARK: Element Access + extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable @inline(__always) + internal func ptr(at slot: Slot) -> UnsafePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return UnsafePointer(_baseAddress + slot.position) + } + + @inlinable @inline(__always) + internal mutating func mutablePtr( + at slot: Slot + ) -> UnsafeMutablePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return _baseAddress + slot.position + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal subscript(offset offset: Int) -> Element { + @inline(__always) + _read { + precondition(offset >= 0 && offset < count, "Index out of bounds") + let slot = slot(forOffset: offset) + yield ptr(at: slot).pointee + } + @inline(__always) + _modify { + precondition(offset >= 0 && offset < count, "Index out of bounds") + let slot = slot(forOffset: offset) + yield &mutablePtr(at: slot).pointee + } + } +} + +// MARK: Access to contiguous regions + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable @inline(__always) + internal var mutableBuffer: UnsafeMutableBufferPointer { + mutating get { + _buffer + } + } + @inlinable - internal func segments() -> _UnsafeWrappedBuffer { + internal func buffer(for range: Range) -> UnsafeBufferPointer { + assert(range.upperBound.position <= capacity) + return .init(_buffer._extracting(unchecked: range._offsets)) + } + + @inlinable @inline(__always) + internal mutating func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { + assert(range.upperBound.position <= capacity) + return _buffer._extracting(unchecked: range._offsets) + } +} + +extension _UnsafeDequeHandle { + @inlinable + @discardableResult + internal mutating func initialize( + at start: Slot, + from source: UnsafeBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start).initialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + @inline(__always) + @discardableResult + internal mutating func moveInitialize( + at start: Slot, + from source: UnsafeMutableBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start) + .moveInitialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +// MARK: Access to Segments + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal func segments() -> _UnsafeDequeSegments { guard _buffer.baseAddress != nil else { return .init(._empty) } @@ -204,7 +270,7 @@ extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable internal func segments( forOffsets offsets: Range - ) -> _UnsafeWrappedBuffer { + ) -> _UnsafeDequeSegments { assert(offsets.lowerBound >= 0 && offsets.upperBound <= count) guard _buffer.baseAddress != nil else { return .init(._empty) @@ -220,7 +286,7 @@ extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable @inline(__always) - internal mutating func mutableSegments() -> _UnsafeMutableWrappedBuffer { + internal mutating func mutableSegments() -> _UnsafeMutableDequeSegments { .init(mutating: segments()) } @@ -228,14 +294,31 @@ extension _UnsafeDequeHandle where Element: ~Copyable { @inline(__always) internal mutating func mutableSegments( forOffsets range: Range - ) -> _UnsafeMutableWrappedBuffer { + ) -> _UnsafeMutableDequeSegments { .init(mutating: segments(forOffsets: range)) } + + @inlinable + internal mutating func mutableSegments( + between start: Slot, + and end: Slot + ) -> _UnsafeMutableDequeSegments { + assert(start.position <= capacity) + assert(end.position <= capacity) + if start < end { + return .init( + start: mutablePtr(at: start), + count: end.position - start.position) + } + return .init( + first: mutablePtr(at: start), count: capacity - start.position, + second: mutablePtr(at: .zero), count: end.position) + } } extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal mutating func availableSegments() -> _UnsafeMutableWrappedBuffer { + internal mutating func availableSegments() -> _UnsafeMutableDequeSegments { guard _buffer.baseAddress != nil else { return .init(._empty) } @@ -247,79 +330,76 @@ extension _UnsafeDequeHandle where Element: ~Copyable { } } +// MARK: Wholesale Copying and Reallocation + extension _UnsafeDequeHandle { + /// Copy elements in `handle` into a newly allocated handle without changing its + /// capacity or layout. @inlinable - @discardableResult - internal mutating func initialize( - at start: Slot, - from source: UnsafeBufferPointer - ) -> Slot { - assert(start.position + source.count <= capacity) - guard source.count > 0 else { return start } - mutablePtr(at: start).initialize(from: source.baseAddress!, count: source.count) - return Slot(at: start.position + source.count) + internal borrowing func allocateCopy() -> Self { + var result: _UnsafeDequeHandle = .allocate(capacity: self.capacity) + result.count = self.count + result.startSlot = self.startSlot + let src = self.segments() + result.initialize(at: self.startSlot, from: src.first) + if let second = src.second { + result.initialize(at: .zero, from: second) + } + return result } -} -extension _UnsafeDequeHandle where Element: ~Copyable { + /// Copy elements in `handle` into a newly allocated handle with the specified + /// minimum capacity. This operation does not preserve layout. @inlinable - @inline(__always) - @discardableResult - internal mutating func moveInitialize( - at start: Slot, - from source: UnsafeMutableBufferPointer - ) -> Slot { - assert(start.position + source.count <= capacity) - guard source.count > 0 else { return start } - mutablePtr(at: start) - .moveInitialize(from: source.baseAddress!, count: source.count) - return Slot(at: start.position + source.count) + internal borrowing func allocateCopy(capacity: Int) -> Self { + precondition(capacity >= self.count) + var result: _UnsafeDequeHandle = .allocate(capacity: capacity) + result.count = self.count + let src = self.segments() + let next = result.initialize(at: .zero, from: src.first) + if let second = src.second { + result.initialize(at: next, from: second) + } + return result } +} +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - @inline(__always) - @discardableResult - internal mutating func move( - from source: Slot, - to target: Slot, - count: Int - ) -> (source: Slot, target: Slot) { - assert(count >= 0) - assert(source.position + count <= self.capacity) - assert(target.position + count <= self.capacity) - guard count > 0 else { return (source, target) } - mutablePtr(at: target) - .moveInitialize(from: mutablePtr(at: source), count: count) - return (slot(source, offsetBy: count), slot(target, offsetBy: count)) + internal mutating func reallocate(capacity newCapacity: Int) { + precondition(newCapacity >= count) + guard newCapacity != capacity else { return } + + var new = _UnsafeDequeHandle.allocate(capacity: newCapacity) + let source = self.mutableSegments() + let next = new.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + new.moveInitialize(at: next, from: second) + } + _buffer.deallocate() + _buffer = new._buffer + startSlot = .zero } } - +// MARK: Iteration extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func withUnsafeSegment( - startingAt start: Int, - maximumCount: Int?, - _ body: (UnsafeBufferPointer) throws -> R - ) rethrows -> (end: Int, result: R) { - assert(start <= count) - guard start < count else { - return try (count, body(UnsafeBufferPointer(start: nil, count: 0))) - } - let endSlot = self.endSlot + internal func startBorrowingIteration() -> _DequeBorrowingIterator { + .init(_for: self, startOffset: 0) + } - let segmentStart = self.slot(forOffset: start) - let segmentEnd = segmentStart < endSlot ? endSlot : limSlot - let count = Swift.min(maximumCount ?? Int.max, segmentEnd.position - segmentStart.position) - let result = try body(UnsafeBufferPointer(start: ptr(at: segmentStart), count: count)) - return (start + count, result) + @inlinable + internal func startBorrowingIteration(from start: Int) -> _DequeBorrowingIterator { + precondition(start >= 0 && start <= count) + return .init(_for: self, startOffset: start) } } // MARK: Replacement -extension _UnsafeDequeHandle where Element: ~Copyable { +extension _UnsafeDequeHandle { /// Replace the elements in `range` with `newElements`. The deque's count must /// not change as a result of calling this function. /// @@ -399,75 +479,29 @@ extension _UnsafeDequeHandle { startSlot = newStart count += source.count - let gap = mutableWrappedBuffer(between: newStart, and: oldStart) + let gap = mutableSegments(between: newStart, and: oldStart) gap.initialize(from: source) } } -// MARK: Insertion - -extension _UnsafeDequeHandle where Element: ~Copyable { - @inlinable - internal mutating func uncheckedInsert( - _ newElement: consuming Element, at offset: Int - ) { - assert(count < capacity) - if offset == 0 { - uncheckedPrepend(newElement) - return - } - if offset == count { - uncheckedAppend(newElement) - return - } - let gap = openGap(ofSize: 1, atOffset: offset) - assert(gap.first.count == 1) - gap.first.baseAddress!.initialize(to: newElement) - } -} - -extension _UnsafeDequeHandle { - /// Insert all elements from `newElements` into this deque, starting at - /// `offset`. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - /// - /// - Parameter newElements: The elements to insert. - /// - Parameter newCount: Must be equal to `newElements.count`. Used to - /// prevent calling `count` more than once. - /// - Parameter offset: The desired offset from the start at which to place - /// the first element. - @inlinable - internal mutating func uncheckedInsert( - contentsOf newElements: __owned C, - count newCount: Int, - atOffset offset: Int - ) where C.Element == Element { - assert(offset <= count) - assert(newElements.count == newCount) - guard newCount > 0 else { return } - let gap = openGap(ofSize: newCount, atOffset: offset) - gap.initialize(from: newElements) - } -} +// MARK: Opening and Closing Gaps extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal mutating func mutableWrappedBuffer( - between start: Slot, - and end: Slot - ) -> _UnsafeMutableWrappedBuffer { - assert(start.position <= capacity) - assert(end.position <= capacity) - if start < end { - return .init( - start: mutablePtr(at: start), - count: end.position - start.position) - } - return .init( - first: mutablePtr(at: start), count: capacity - start.position, - second: mutablePtr(at: .zero), count: end.position) + @inline(__always) + @discardableResult + internal mutating func move( + from source: Slot, + to target: Slot, + count: Int + ) -> (source: Slot, target: Slot) { + assert(count >= 0) + assert(source.position + count <= self.capacity) + assert(target.position + count <= self.capacity) + guard count > 0 else { return (source, target) } + mutablePtr(at: target) + .moveInitialize(from: mutablePtr(at: source), count: count) + return (slot(source, offsetBy: count), slot(target, offsetBy: count)) } /// Slide elements around so that there is a gap of uninitialized slots of @@ -484,7 +518,7 @@ extension _UnsafeDequeHandle where Element: ~Copyable { internal mutating func openGap( ofSize gapSize: Int, atOffset offset: Int - ) -> _UnsafeMutableWrappedBuffer { + ) -> _UnsafeMutableDequeSegments { assert(offset >= 0 && offset <= self.count) assert(self.count + gapSize <= capacity) assert(gapSize > 0) @@ -546,7 +580,7 @@ extension _UnsafeDequeHandle where Element: ~Copyable { move(from: gapStart, to: gapEnd, count: tailCount - gapSize - originalEnd.position) } count += gapSize - return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) + return mutableSegments(between: gapStart, and: gapEnd.orIfZero(capacity)) } // Open the gap by sliding elements to the left. @@ -602,83 +636,7 @@ extension _UnsafeDequeHandle where Element: ~Copyable { } startSlot = newStart count += gapSize - return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) - } -} - -// MARK: Removal - -extension _UnsafeDequeHandle where Element: ~Copyable { - @inlinable - internal mutating func uncheckedRemove(at offset: Int) -> Element { - let slot = self.slot(forOffset: offset) - let result = mutablePtr(at: slot).move() - closeGap(offsets: Range(uncheckedBounds: (offset, offset + 1))) - return result - } - - @inlinable - internal mutating func uncheckedRemoveFirst() -> Element { - assert(count > 0) - let result = mutablePtr(at: startSlot).move() - startSlot = slot(after: startSlot) - count -= 1 - return result - } - - @inlinable - internal mutating func uncheckedRemoveLast() -> Element { - assert(count > 0) - let slot = self.slot(forOffset: count - 1) - let result = mutablePtr(at: slot).move() - count -= 1 - return result - } - - @inlinable - internal mutating func uncheckedRemoveFirst(_ n: Int) { - assert(count >= n) - guard n > 0 else { return } - let target = mutableSegments(forOffsets: 0 ..< n) - target.deinitialize() - startSlot = slot(startSlot, offsetBy: n) - count -= n - } - - @inlinable - internal mutating func uncheckedRemoveLast(_ n: Int) { - assert(count >= n) - guard n > 0 else { return } - let target = mutableSegments(forOffsets: count - n ..< count) - target.deinitialize() - count -= n - } - - /// Remove all elements stored in this instance, deinitializing their storage. - /// - /// This method does not ensure that the storage buffer is uniquely - /// referenced. - @inlinable - internal mutating func uncheckedRemoveAll() { - guard count > 0 else { return } - let target = mutableSegments() - target.deinitialize() - count = 0 - startSlot = .zero - } - - /// Remove all elements in `bounds`, deinitializing their storage and sliding - /// remaining elements to close the resulting gap. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal mutating func uncheckedRemove(offsets bounds: Range) { - assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) - - // Deinitialize elements in `bounds`. - mutableSegments(forOffsets: bounds).deinitialize() - closeGap(offsets: bounds) + return mutableSegments(between: gapStart, and: gapEnd.orIfZero(capacity)) } /// Close the gap of already uninitialized elements in `bounds`, sliding @@ -794,52 +752,126 @@ extension _UnsafeDequeHandle where Element: ~Copyable { } } -extension _UnsafeDequeHandle { - /// Copy elements into a newly allocated handle without changing capacity or - /// layout. +// MARK: Insertion + +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func allocateCopy() -> Self { - var handle: Self = .allocate(capacity: self.capacity) - handle.count = self.count - handle.startSlot = self.startSlot - let src = self.segments() - handle.initialize(at: self.startSlot, from: src.first) - if let second = src.second { - handle.initialize(at: .zero, from: second) + internal mutating func uncheckedInsert( + _ newElement: consuming Element, at offset: Int + ) { + assert(count < capacity) + if offset == 0 { + uncheckedPrepend(newElement) + return } - return handle + if offset == count { + uncheckedAppend(newElement) + return + } + let gap = openGap(ofSize: 1, atOffset: offset) + assert(gap.first.count == 1) + gap.first.baseAddress!.initialize(to: newElement) } +} - /// Copy elements into a new storage instance with the specified minimum - /// capacity. This operation does not preserve layout. +extension _UnsafeDequeHandle { + /// Insert all elements from `newElements` into this deque, starting at + /// `offset`. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter newElements: The elements to insert. + /// - Parameter newCount: Must be equal to `newElements.count`. Used to + /// prevent calling `count` more than once. + /// - Parameter offset: The desired offset from the start at which to place + /// the first element. @inlinable - internal func allocateCopy(capacity: Int) -> Self { - assert(capacity >= count) - var handle: Self = .allocate(capacity: capacity) - handle.count = self.count - let src = self.segments() - let next = handle.initialize(at: .zero, from: src.first) - if let second = src.second { - handle.initialize(at: next, from: second) - } - return handle + internal mutating func uncheckedInsert( + contentsOf newElements: __owned C, + count newCount: Int, + atOffset offset: Int + ) where C.Element == Element { + assert(offset <= count) + assert(newElements.count == newCount) + guard newCount > 0 else { return } + let gap = openGap(ofSize: newCount, atOffset: offset) + gap.initialize(from: newElements) } } +// MARK: Removal + extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal mutating func reallocate(capacity newCapacity: Int) { - precondition(newCapacity >= count) - guard newCapacity != capacity else { return } - var newHandle = Self.allocate(capacity: newCapacity) - newHandle.startSlot = .zero - newHandle.count = self.count - let source = self.mutableSegments() - let next = newHandle.moveInitialize(at: .zero, from: source.first) - if let second = source.second { - newHandle.moveInitialize(at: next, from: second) - } - self._buffer.deallocate() - self = newHandle + internal mutating func uncheckedRemove(at offset: Int) -> Element { + let slot = self.slot(forOffset: offset) + let result = mutablePtr(at: slot).move() + closeGap(offsets: Range(uncheckedBounds: (offset, offset + 1))) + return result + } + + @inlinable + internal mutating func uncheckedRemoveFirst() -> Element { + assert(count > 0) + let result = mutablePtr(at: startSlot).move() + startSlot = slot(after: startSlot) + count -= 1 + return result + } + + @inlinable + internal mutating func uncheckedRemoveLast() -> Element { + assert(count > 0) + let slot = self.slot(forOffset: count - 1) + let result = mutablePtr(at: slot).move() + count -= 1 + return result + } + + @inlinable + internal mutating func uncheckedRemoveFirst(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: 0 ..< n) + target.deinitialize() + startSlot = slot(startSlot, offsetBy: n) + count -= n + } + + @inlinable + internal mutating func uncheckedRemoveLast(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: count - n ..< count) + target.deinitialize() + count -= n + } + + /// Remove all elements stored in this instance, deinitializing their storage. + /// + /// This method does not ensure that the storage buffer is uniquely + /// referenced. + @inlinable + internal mutating func uncheckedRemoveAll() { + guard count > 0 else { return } + let target = mutableSegments() + target.deinitialize() + count = 0 + startSlot = .zero + } + + /// Remove all elements in `bounds`, deinitializing their storage and sliding + /// remaining elements to close the resulting gap. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedRemove(offsets bounds: Range) { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + + // Deinitialize elements in `bounds`. + mutableSegments(forOffsets: bounds).deinitialize() + closeGap(offsets: bounds) } } diff --git a/Sources/DequeModule/_UnsafeWrappedBuffer.swift b/Sources/DequeModule/_UnsafeDequeSegments.swift similarity index 94% rename from Sources/DequeModule/_UnsafeWrappedBuffer.swift rename to Sources/DequeModule/_UnsafeDequeSegments.swift index 71c2da763..7ca03a592 100644 --- a/Sources/DequeModule/_UnsafeWrappedBuffer.swift +++ b/Sources/DequeModule/_UnsafeDequeSegments.swift @@ -16,7 +16,7 @@ import Future @frozen @usableFromInline -internal struct _UnsafeWrappedBuffer { +internal struct _UnsafeDequeSegments { @usableFromInline internal let first: UnsafeBufferPointer @@ -69,7 +69,7 @@ internal struct _UnsafeWrappedBuffer { @frozen @usableFromInline -internal struct _UnsafeMutableWrappedBuffer { +internal struct _UnsafeMutableDequeSegments { @usableFromInline internal let first: UnsafeMutableBufferPointer @@ -110,13 +110,13 @@ internal struct _UnsafeMutableWrappedBuffer { @inlinable @inline(__always) - internal init(mutating buffer: _UnsafeWrappedBuffer) { + internal init(mutating buffer: _UnsafeDequeSegments) { self.init(.init(mutating: buffer.first), buffer.second.map { .init(mutating: $0) }) } } -extension _UnsafeMutableWrappedBuffer { +extension _UnsafeMutableDequeSegments { @inlinable @inline(__always) internal init( @@ -136,7 +136,7 @@ extension _UnsafeMutableWrappedBuffer { } } -extension _UnsafeMutableWrappedBuffer where Element: ~Copyable { +extension _UnsafeMutableDequeSegments where Element: ~Copyable { @inlinable @inline(__always) internal var count: Int { first.count + (second?.count ?? 0) } @@ -169,7 +169,7 @@ extension _UnsafeMutableWrappedBuffer where Element: ~Copyable { } } -extension _UnsafeMutableWrappedBuffer where Element: ~Copyable { +extension _UnsafeMutableDequeSegments where Element: ~Copyable { @inlinable internal func deinitialize() { first.deinitialize() @@ -177,7 +177,7 @@ extension _UnsafeMutableWrappedBuffer where Element: ~Copyable { } } -extension _UnsafeMutableWrappedBuffer { +extension _UnsafeMutableDequeSegments { @inlinable @discardableResult internal func initialize( diff --git a/Tests/DequeTests/DequeTests.swift b/Tests/DequeTests/DequeTests.swift index d36cd6410..310832567 100644 --- a/Tests/DequeTests/DequeTests.swift +++ b/Tests/DequeTests/DequeTests.swift @@ -17,6 +17,11 @@ import _CollectionsTestSupport @_spi(Testing) import DequeModule #endif +extension Deque { + var _capacity: Int { _unstableCapacity } + var _startSlot: Int { _unstableStartSlot } +} + final class DequeTests: CollectionTestCase { func test_testingSPIs() { let deque = Deque(_capacity: 5, startSlot: 2, contents: [10, 20, 30, 40]) diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index cdb159e15..fb666e152 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -10,6 +10,19 @@ 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E042C6ABF6000AD8F58 /* NewArray.swift */; }; 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */; }; 7D380E0D2C6D565300AD8F58 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E0C2C6D565000AD8F58 /* Shared.swift */; }; + 7D9514292C750E08009C37DD /* RigidDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */; }; + 7D95142A2C753862009C37DD /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBB29CA70F3004483EB /* Deque.swift */; }; + 7D95142B2C753955009C37DD /* Deque+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */; }; + 7D95142C2C7539B9009C37DD /* Deque+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */; }; + 7D95142D2C753C1B009C37DD /* Deque+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */; }; + 7D95142E2C753C2E009C37DD /* Deque+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */; }; + 7D95142F2C753C34009C37DD /* Deque+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */; }; + 7D9514302C753C45009C37DD /* Deque+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */; }; + 7D9514312C753C4A009C37DD /* Deque+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */; }; + 7D9514322C753C55009C37DD /* Deque+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */; }; + 7D9514332C753C5B009C37DD /* Deque+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */; }; + 7D9514342C753C63009C37DD /* DynamicDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */; }; + 7D9514352C753FC1009C37DD /* Deque+Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E022C6AAA9300AD8F58 /* Deque+Container.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859229E4F74400B291CD /* BitArray+Descriptions.swift */; }; 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; @@ -77,19 +90,8 @@ 7DE9204729CA70F3004483EB /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB229CA70F3004483EB /* _HashTable+Testing.swift */; }; 7DE9204829CA70F3004483EB /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB329CA70F3004483EB /* _HashTable+CustomStringConvertible.swift */; }; 7DE9204929CA70F3004483EB /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB429CA70F3004483EB /* _HashTable+BucketIterator.swift */; }; - 7DE9204C29CA70F3004483EB /* Deque+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */; }; - 7DE9204E29CA70F3004483EB /* Deque+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */; }; - 7DE9204F29CA70F3004483EB /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBB29CA70F3004483EB /* Deque.swift */; }; 7DE9205029CA70F3004483EB /* _DequeSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */; }; - 7DE9205129CA70F3004483EB /* Deque+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */; }; - 7DE9205229CA70F3004483EB /* _UnsafeWrappedBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */; }; - 7DE9205529CA70F3004483EB /* Deque+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */; }; - 7DE9205629CA70F3004483EB /* Deque+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */; }; - 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */; }; - 7DE9205A29CA70F3004483EB /* Deque+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */; }; - 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */; }; - 7DE9205C29CA70F3004483EB /* Deque+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */; }; - 7DE9205D29CA70F4004483EB /* Deque._Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */; }; + 7DE9205229CA70F3004483EB /* _UnsafeDequeSegments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBE29CA70F3004483EB /* _UnsafeDequeSegments.swift */; }; 7DE9205E29CA70F4004483EB /* BigString+Metrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91ECD29CA70F3004483EB /* BigString+Metrics.swift */; }; 7DE9205F29CA70F4004483EB /* BigString+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91ECE29CA70F3004483EB /* BigString+Index.swift */; }; 7DE9206029CA70F4004483EB /* BigString+Summary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91ECF29CA70F3004483EB /* BigString+Summary.swift */; }; @@ -413,8 +415,6 @@ 7DEBDB9129CCE44A00ADC226 /* CollectionTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2429CCE43600ADC226 /* CollectionTestCase.swift */; }; 7DEBDB9229CCE44A00ADC226 /* StringConvertibleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */; }; 7DEBDB9D29CCE73D00ADC226 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91F7B29CA70F3004483EB /* Collections.swift */; }; - 7DEBEC152C5AB45600A1BF15 /* DynamicDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */; }; - 7DEBEC162C5AB45600A1BF15 /* RigidDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */; }; 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */; }; 7DEBEC252C5AE43C00A1BF15 /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */; }; 7DEBEC262C5AE43C00A1BF15 /* RigidArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */; }; @@ -440,6 +440,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7D380E022C6AAA9300AD8F58 /* Deque+Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deque+Container.swift"; sourceTree = ""; }; 7D380E042C6ABF6000AD8F58 /* NewArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArray.swift; sourceTree = ""; }; 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; 7D380E0C2C6D565000AD8F58 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; @@ -528,7 +529,7 @@ 7DE91EBB29CA70F3004483EB /* Deque.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deque.swift; sourceTree = ""; }; 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeSlot.swift; sourceTree = ""; }; 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Hashable.swift"; sourceTree = ""; }; - 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _UnsafeWrappedBuffer.swift; sourceTree = ""; }; + 7DE91EBE29CA70F3004483EB /* _UnsafeDequeSegments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _UnsafeDequeSegments.swift; sourceTree = ""; }; 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Codable.swift"; sourceTree = ""; }; 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Testing.swift"; sourceTree = ""; }; 7DE91EC329CA70F3004483EB /* DequeModule.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DequeModule.docc; sourceTree = ""; }; @@ -536,7 +537,6 @@ 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Extras.swift"; sourceTree = ""; }; 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Equatable.swift"; sourceTree = ""; }; - 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deque._Storage.swift; sourceTree = ""; }; 7DE91ECD29CA70F3004483EB /* BigString+Metrics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BigString+Metrics.swift"; sourceTree = ""; }; 7DE91ECE29CA70F3004483EB /* BigString+Index.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BigString+Index.swift"; sourceTree = ""; }; 7DE91ECF29CA70F3004483EB /* BigString+Summary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BigString+Summary.swift"; sourceTree = ""; }; @@ -1068,12 +1068,12 @@ children = ( 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */, 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */, - 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */, + 7DE91EBE29CA70F3004483EB /* _UnsafeDequeSegments.swift */, 7DE91EB929CA70F3004483EB /* CMakeLists.txt */, 7DE91EBB29CA70F3004483EB /* Deque.swift */, - 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */, 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */, 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */, + 7D380E022C6AAA9300AD8F58 /* Deque+Container.swift */, 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */, 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */, 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */, @@ -1940,11 +1940,11 @@ 7DE920F129CA70F4004483EB /* BitArray+Initializers.swift in Sources */, 7DE9214529CA70F4004483EB /* _HashNode+Structural symmetricDifference.swift in Sources */, 7DE9201029CA70F3004483EB /* OrderedDictionary+Deprecations.swift in Sources */, - 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */, 7DE9212729CA70F4004483EB /* TreeSet+Equatable.swift in Sources */, 7DE9213429CA70F4004483EB /* TreeSet+ExpressibleByArrayLiteral.swift in Sources */, 7DE9209729CA70F4004483EB /* _RopePath.swift in Sources */, 7DE9216629CA70F4004483EB /* _HashNode+Storage.swift in Sources */, + 7D9514312C753C4A009C37DD /* Deque+Extras.swift in Sources */, 7DE920CE29CA70F4004483EB /* BitSet+CustomReflectable.swift in Sources */, 7DE9215029CA70F4004483EB /* _UnmanagedHashNode.swift in Sources */, 7DE920EF29CA70F4004483EB /* BitArray+Codable.swift in Sources */, @@ -1959,7 +1959,6 @@ 7DE9216229CA70F4004483EB /* _AncestorHashSlots.swift in Sources */, 7DE9216B29CA70F4004483EB /* TreeDictionary+Codable.swift in Sources */, 7DE9201A29CA70F3004483EB /* OrderedDictionary+Sendable.swift in Sources */, - 7DE9205D29CA70F4004483EB /* Deque._Storage.swift in Sources */, 7DE9203B29CA70F3004483EB /* OrderedSet+Partial SetAlgebra subtracting.swift in Sources */, 7DE9213A29CA70F4004483EB /* TreeSet.swift in Sources */, 7DE9213829CA70F4004483EB /* TreeSet+Filter.swift in Sources */, @@ -1982,15 +1981,17 @@ 7DE9203129CA70F3004483EB /* OrderedSet+Partial MutableCollection.swift in Sources */, 7DE9209B29CA70F4004483EB /* Rope+Split.swift in Sources */, 7DE9206629CA70F4004483EB /* BigString+Debugging.swift in Sources */, - 7DE9205229CA70F3004483EB /* _UnsafeWrappedBuffer.swift in Sources */, + 7DE9205229CA70F3004483EB /* _UnsafeDequeSegments.swift in Sources */, 7DE9201129CA70F3004483EB /* OrderedDictionary+Initializers.swift in Sources */, 7DE9212B29CA70F4004483EB /* TreeSet+SetAlgebra subtracting.swift in Sources */, 7DE920F629CA70F4004483EB /* BitArray+Equatable.swift in Sources */, 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */, + 7D95142C2C7539B9009C37DD /* Deque+Collection.swift in Sources */, 7DE9212329CA70F4004483EB /* TreeSet+SetAlgebra isEqualSet.swift in Sources */, 7DE9214629CA70F4004483EB /* _HashSlot.swift in Sources */, 7DEBDB0129CBEE5300ADC226 /* _UnsafeBitSet+Index.swift in Sources */, 7DE9204129CA70F3004483EB /* _UnsafeBitset.swift in Sources */, + 7D9514352C753FC1009C37DD /* Deque+Container.swift in Sources */, 7DE9208F29CA70F4004483EB /* RopeElement.swift in Sources */, 7DE9207229CA70F4004483EB /* BigString+Managing Breaks.swift in Sources */, 7DE9213F29CA70F4004483EB /* TreeSet+SetAlgebra isDisjoint.swift in Sources */, @@ -2011,6 +2012,7 @@ 7DE9209A29CA70F4004483EB /* Rope+Append.swift in Sources */, 7DE9203F29CA70F3004483EB /* OrderedSet+Partial SetAlgebra isDisjoint.swift in Sources */, 7DE9208629CA70F4004483EB /* BigString+LosslessStringConvertible.swift in Sources */, + 7D95142B2C753955009C37DD /* Deque+Equatable.swift in Sources */, 7DE9204329CA70F3004483EB /* _HashTable+Bucket.swift in Sources */, 7DE9216A29CA70F4004483EB /* TreeDictionary.swift in Sources */, 7DEBDB9D29CCE73D00ADC226 /* Collections.swift in Sources */, @@ -2028,6 +2030,7 @@ 7DE9206A29CA70F4004483EB /* BigString+Chunk+Counts.swift in Sources */, 7DE9207529CA70F4004483EB /* BigString+Insert.swift in Sources */, 7DE9216129CA70F4004483EB /* _HashNode+Structural merge.swift in Sources */, + 7D9514332C753C5B009C37DD /* Deque+Testing.swift in Sources */, 7DE9202F29CA70F3004483EB /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, 7DEBDAFC29CBEE5300ADC226 /* Debugging.swift in Sources */, 7DE9202B29CA70F3004483EB /* OrderedSet+Equatable.swift in Sources */, @@ -2041,10 +2044,10 @@ 7DE9215129CA70F4004483EB /* _HashNode+Subtree Modify.swift in Sources */, 7DE9204729CA70F3004483EB /* _HashTable+Testing.swift in Sources */, 7DE920E929CA70F4004483EB /* BitArray+Invariants.swift in Sources */, + 7D95142D2C753C1B009C37DD /* Deque+Codable.swift in Sources */, 7DE9215429CA70F4004483EB /* _HashNode+UnsafeHandle.swift in Sources */, + 7D9514302C753C45009C37DD /* Deque+ExpressibleByArrayLiteral.swift in Sources */, 7DE9209829CA70F4004483EB /* Rope+_Node.swift in Sources */, - 7DEBEC152C5AB45600A1BF15 /* DynamicDeque.swift in Sources */, - 7DEBEC162C5AB45600A1BF15 /* RigidDeque.swift in Sources */, 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */, 7DE9204529CA70F3004483EB /* _HashTable+Constants.swift in Sources */, 7DE9214229CA70F4004483EB /* _HashStack.swift in Sources */, @@ -2083,13 +2086,13 @@ 7DE920EB29CA70F4004483EB /* BitArray+Fill.swift in Sources */, 7DE9201929CA70F3004483EB /* OrderedDictionary+Values.swift in Sources */, 7DE9213929CA70F4004483EB /* TreeSet+SetAlgebra Initializers.swift in Sources */, + 7D9514292C750E08009C37DD /* RigidDeque.swift in Sources */, 7DE9217729CA70F4004483EB /* TreeDictionary+Keys.swift in Sources */, 7DE920DA29CA70F4004483EB /* BitSet._UnsafeHandle.swift in Sources */, 7DE920D129CA70F4004483EB /* BitSet.Index.swift in Sources */, 7DE9207429CA70F4004483EB /* BigString+ReplaceSubrange.swift in Sources */, 7DE920DC29CA70F4004483EB /* BitSet+Invariants.swift in Sources */, 7DE9216729CA70F4004483EB /* TreeDictionary+Equatable.swift in Sources */, - 7DE9204C29CA70F3004483EB /* Deque+Collection.swift in Sources */, 7DE9201F29CA70F3004483EB /* OrderedSet+RandomAccessCollection.swift in Sources */, 7DE9202E29CA70F3004483EB /* OrderedSet+UnorderedView.swift in Sources */, 7DE9215F29CA70F4004483EB /* _UnsafePath.swift in Sources */, @@ -2134,6 +2137,7 @@ 7DE9207329CA70F4004483EB /* BigString+RemoveSubrange.swift in Sources */, 7DE9213529CA70F4004483EB /* TreeSet+SetAlgebra symmetricDifference.swift in Sources */, 7DE920D429CA70F4004483EB /* BitSet+SetAlgebra union.swift in Sources */, + 7D9514322C753C55009C37DD /* Deque+Hashable.swift in Sources */, 7DE920C329CA70F4004483EB /* BitSet+SetAlgebra formUnion.swift in Sources */, 7DE9217429CA70F4004483EB /* TreeDictionary+Collection.swift in Sources */, 7DE9214729CA70F4004483EB /* _HashNode+Primitive Replacement.swift in Sources */, @@ -2159,6 +2163,7 @@ 7DE9212929CA70F4004483EB /* TreeSet+SetAlgebra formUnion.swift in Sources */, 7DE920C129CA70F4004483EB /* BitSet+SetAlgebra subtract.swift in Sources */, 7DE9201B29CA70F3004483EB /* OrderedDictionary+Descriptions.swift in Sources */, + 7D95142A2C753862009C37DD /* Deque.swift in Sources */, 7DE9213C29CA70F4004483EB /* TreeSet+Codable.swift in Sources */, 7DE9207129CA70F4004483EB /* BigString+Split.swift in Sources */, 7DE9214F29CA70F4004483EB /* _Bucket.swift in Sources */, @@ -2171,16 +2176,15 @@ 7DE9207A29CA70F4004483EB /* BigString+UTF8View.swift in Sources */, 7DE9201329CA70F3004483EB /* OrderedDictionary+CustomReflectable.swift in Sources */, 7DE9201C29CA70F3004483EB /* OrderedDictionary+Partial MutableCollection.swift in Sources */, + 7D9514342C753C63009C37DD /* DynamicDeque.swift in Sources */, 7D380E0D2C6D565300AD8F58 /* Shared.swift in Sources */, 7DE9202729CA70F3004483EB /* OrderedSet+Insertions.swift in Sources */, - 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */, 7DE9208729CA70F4004483EB /* BigString+Sequence.swift in Sources */, 7DE9202229CA70F3004483EB /* OrderedSet+Diffing.swift in Sources */, 7DE920CA29CA70F4004483EB /* BitSet+CustomStringConvertible.swift in Sources */, 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */, 7DE9212E29CA70F4004483EB /* TreeSet+SetAlgebra isStrictSuperset.swift in Sources */, 7DE920A829CA70F4004483EB /* String.Index+ABI.swift in Sources */, - 7DE9204F29CA70F3004483EB /* Deque.swift in Sources */, 7DE9214129CA70F4004483EB /* _HashNode+Initializers.swift in Sources */, 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */, 7DE9202D29CA70F3004483EB /* OrderedSet.swift in Sources */, @@ -2189,12 +2193,11 @@ 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */, 7DE9216C29CA70F4004483EB /* TreeDictionary+Descriptions.swift in Sources */, 7DE920A229CA70F4004483EB /* Rope+RemoveSubrange.swift in Sources */, + 7D95142F2C753C34009C37DD /* Deque+Descriptions.swift in Sources */, 7DE920C729CA70F4004483EB /* BitSet+ExpressibleByArrayLiteral.swift in Sources */, 7DE9214929CA70F4004483EB /* _HashNode+Structural isEqualSet.swift in Sources */, 7DE9206729CA70F4004483EB /* BigString+Builder.swift in Sources */, 7DE9208B29CA70F4004483EB /* RopeMetric.swift in Sources */, - 7DE9205A29CA70F3004483EB /* Deque+Extras.swift in Sources */, - 7DE9205129CA70F3004483EB /* Deque+Hashable.swift in Sources */, 7DE9209929CA70F4004483EB /* Rope+Extract.swift in Sources */, 7DE9202629CA70F3004483EB /* OrderedSet+Partial SetAlgebra isStrictSubset.swift in Sources */, 7DE920E729CA70F4004483EB /* BitArray+CustomReflectable.swift in Sources */, @@ -2209,7 +2212,6 @@ 7DE9201829CA70F3004483EB /* OrderedDictionary+Invariants.swift in Sources */, 7DE920CD29CA70F4004483EB /* BitSet+CustomDebugStringConvertible.swift in Sources */, 7DE9212529CA70F4004483EB /* TreeSet+SetAlgebra isStrictSubset.swift in Sources */, - 7DE9205C29CA70F3004483EB /* Deque+Equatable.swift in Sources */, 7DE9209229CA70F4004483EB /* Rope+_Storage.swift in Sources */, 7DE9203929CA70F3004483EB /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, 7DE9206329CA70F4004483EB /* BigString+Contents.swift in Sources */, @@ -2217,10 +2219,10 @@ 7DE920CC29CA70F4004483EB /* BitSet.Counted.swift in Sources */, 7DE9207F29CA70F4004483EB /* BigSubstring+UTF8View.swift in Sources */, 7DE920A129CA70F4004483EB /* Rope+Remove.swift in Sources */, + 7D95142E2C753C2E009C37DD /* Deque+CustomReflectable.swift in Sources */, 7DE9215929CA70F4004483EB /* _HashNode+Structural isSubset.swift in Sources */, 7DE920C629CA70F4004483EB /* BitSet+SetAlgebra formSymmetricDifference.swift in Sources */, 7DE9202129CA70F3004483EB /* OrderedSet+Partial SetAlgebra isEqualSet.swift in Sources */, - 7DE9205529CA70F3004483EB /* Deque+Codable.swift in Sources */, 7DE9206229CA70F4004483EB /* BigString+Iterators.swift in Sources */, 7DE9206029CA70F4004483EB /* BigString+Summary.swift in Sources */, 7DE9203829CA70F3004483EB /* OrderedSet+Partial SetAlgebra formIntersection.swift in Sources */, @@ -2259,7 +2261,6 @@ 7DE9206C29CA70F4004483EB /* BigString+Chunk.swift in Sources */, 7DE920F329CA70F4004483EB /* BitArray+Copy.swift in Sources */, 7DE9214029CA70F4004483EB /* _Hash.swift in Sources */, - 7DE9205629CA70F3004483EB /* Deque+Testing.swift in Sources */, 7DEBDB0829CBEE5300ADC226 /* UInt+reversed.swift in Sources */, 7DE9209329CA70F4004483EB /* Rope+Invariants.swift in Sources */, 7DE920EE29CA70F4004483EB /* BitArray+ExpressibleByArrayLiteral.swift in Sources */, @@ -2267,7 +2268,6 @@ 7DE9214C29CA70F4004483EB /* _HashNode+Primitive Insertions.swift in Sources */, 7DE920BE29CA70F4004483EB /* BitSet+SetAlgebra formIntersection.swift in Sources */, 7DE9217329CA70F4004483EB /* TreeDictionary+Merge.swift in Sources */, - 7DE9204E29CA70F3004483EB /* Deque+CustomReflectable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From f6c1d3e348b269014c35b8a762710f69493dfbb2 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sun, 1 Sep 2024 20:52:36 -0700 Subject: [PATCH 086/195] =?UTF-8?q?[Tests]=20DynoArrayTests=20=E2=86=92=20?= =?UTF-8?q?DynamicArrayTests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...rrayTests.swift => DynamicArrayTests.swift} | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename Tests/FutureTests/{DynoArrayTests.swift => DynamicArrayTests.swift} (89%) diff --git a/Tests/FutureTests/DynoArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift similarity index 89% rename from Tests/FutureTests/DynoArrayTests.swift rename to Tests/FutureTests/DynamicArrayTests.swift index 67d285eca..0a4331b97 100644 --- a/Tests/FutureTests/DynoArrayTests.swift +++ b/Tests/FutureTests/DynamicArrayTests.swift @@ -17,10 +17,10 @@ struct Counted: ~Copyable { } } -class DynoArrayTests: CollectionTestCase { +class DynamicArrayTests: CollectionTestCase { func test_basics() { - var array = DynoArray() + var array = DynamicArray() expectTrue(array.isEmpty) expectEqual(array.count, 0) expectEqual(array.capacity, 0) @@ -59,7 +59,7 @@ class DynoArrayTests: CollectionTestCase { func test_read_access() { let c = 100 - let array = DynoArray(count: c) { Counted($0) } + let array = DynamicArray(count: c) { Counted($0) } for i in 0 ..< c { expectEqual(array.borrowElement(at: i) { $0.value }, i) @@ -69,7 +69,7 @@ class DynoArrayTests: CollectionTestCase { func test_update_access() { let c = 100 - var array = DynoArray(count: c) { Counted($0) } + var array = DynamicArray(count: c) { Counted($0) } for i in 0 ..< c { array.updateElement(at: i) { $0.value += 100 } @@ -86,7 +86,7 @@ class DynoArrayTests: CollectionTestCase { } func test_append() { - var array = DynoArray() + var array = DynamicArray() let c = 100 for i in 0 ..< c { array.append(Counted(100 + i)) @@ -105,7 +105,7 @@ class DynoArrayTests: CollectionTestCase { } func test_insert() { - var array = DynoArray() + var array = DynamicArray() let c = 100 for i in 0 ..< c { array.insert(Counted(100 + i), at: 0) @@ -125,7 +125,7 @@ class DynoArrayTests: CollectionTestCase { func test_remove() { let c = 100 - var array = DynoArray(count: c) { Counted(100 + $0) } + var array = DynamicArray(count: c) { Counted(100 + $0) } expectEqual(Counted.instances, c) expectEqual(array.count, c) @@ -141,7 +141,7 @@ class DynoArrayTests: CollectionTestCase { func test_iterate_full() { let c = 100 - let array = DynoArray(count: c) { Counted(100 + $0) } + let array = DynamicArray(count: c) { Counted(100 + $0) } var state = array.startBorrowingIteration() do { @@ -159,7 +159,7 @@ class DynoArrayTests: CollectionTestCase { func test_iterate_stepped() { let c = 100 - let array = DynoArray(count: c) { Counted($0) } + let array = DynamicArray(count: c) { Counted($0) } withEvery("stride", in: 1 ... c) { stride in var state = array.startBorrowingIteration() From 5592b9342290088a23d9bd8fd56b4f6df02fd055 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sun, 1 Sep 2024 20:52:55 -0700 Subject: [PATCH 087/195] [Xcode] Add missing test files --- Xcode/Collections.xcodeproj/project.pbxproj | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index fb666e152..637baf8bf 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -427,6 +427,13 @@ 7DEBEC2D2C5AE43C00A1BF15 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */; }; 7DEBEC2E2C5AE43C00A1BF15 /* Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC222C5AE43C00A1BF15 /* Span.swift */; }; 7DEBEC2F2C5AE43C00A1BF15 /* ContiguousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */; }; + BBD683962C7F857900B567A9 /* SpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683942C7F857900B567A9 /* SpanTests.swift */; }; + BBD683972C7F857900B567A9 /* Inout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683922C7F857900B567A9 /* Inout.swift */; }; + BBD683982C7F857900B567A9 /* BoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD6838E2C7F857900B567A9 /* BoxTests.swift */; }; + BBD683992C7F857900B567A9 /* CellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD6838F2C7F857900B567A9 /* CellTests.swift */; }; + BBD6839A2C7F857900B567A9 /* RawSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683932C7F857900B567A9 /* RawSpanTests.swift */; }; + BBD6839B2C7F857900B567A9 /* DynamicArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683912C7F857900B567A9 /* DynamicArrayTests.swift */; }; + BBD6839C2C7F857900B567A9 /* ContiguousStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683902C7F857900B567A9 /* ContiguousStorageTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -895,6 +902,13 @@ 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawSpan.swift; sourceTree = ""; }; 7DEBEC222C5AE43C00A1BF15 /* Span.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Span.swift; sourceTree = ""; }; 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafeBufferPointer+Additions.swift"; sourceTree = ""; }; + BBD6838E2C7F857900B567A9 /* BoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxTests.swift; sourceTree = ""; }; + BBD6838F2C7F857900B567A9 /* CellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellTests.swift; sourceTree = ""; }; + BBD683902C7F857900B567A9 /* ContiguousStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContiguousStorageTests.swift; sourceTree = ""; }; + BBD683912C7F857900B567A9 /* DynamicArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicArrayTests.swift; sourceTree = ""; }; + BBD683922C7F857900B567A9 /* Inout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inout.swift; sourceTree = ""; }; + BBD683932C7F857900B567A9 /* RawSpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawSpanTests.swift; sourceTree = ""; }; + BBD683942C7F857900B567A9 /* SpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1532,6 +1546,7 @@ 7DE921CA29CA8575004483EB /* Tests */ = { isa = PBXGroup; children = ( + BBD683952C7F857900B567A9 /* FutureTests */, 7DEBDB1F29CCE43600ADC226 /* _CollectionsTestSupport */, 7DE921DA29CA8575004483EB /* BitCollectionsTests */, 7DE921CB29CA8575004483EB /* CollectionsTestSupportTests */, @@ -1828,6 +1843,20 @@ path = Future; sourceTree = ""; }; + BBD683952C7F857900B567A9 /* FutureTests */ = { + isa = PBXGroup; + children = ( + BBD6838E2C7F857900B567A9 /* BoxTests.swift */, + BBD6838F2C7F857900B567A9 /* CellTests.swift */, + BBD683902C7F857900B567A9 /* ContiguousStorageTests.swift */, + BBD683912C7F857900B567A9 /* DynamicArrayTests.swift */, + BBD683922C7F857900B567A9 /* Inout.swift */, + BBD683932C7F857900B567A9 /* RawSpanTests.swift */, + BBD683942C7F857900B567A9 /* SpanTests.swift */, + ); + path = FutureTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2351,6 +2380,13 @@ 7DEBDB1729CBF69F00ADC226 /* _UnsafeBitSet.swift in Sources */, 7DEBDB7E29CCE44A00ADC226 /* CheckComparable.swift in Sources */, 7DEBDB7C29CCE44A00ADC226 /* MinimalDecoder.swift in Sources */, + BBD683962C7F857900B567A9 /* SpanTests.swift in Sources */, + BBD683972C7F857900B567A9 /* Inout.swift in Sources */, + BBD683982C7F857900B567A9 /* BoxTests.swift in Sources */, + BBD683992C7F857900B567A9 /* CellTests.swift in Sources */, + BBD6839A2C7F857900B567A9 /* RawSpanTests.swift in Sources */, + BBD6839B2C7F857900B567A9 /* DynamicArrayTests.swift in Sources */, + BBD6839C2C7F857900B567A9 /* ContiguousStorageTests.swift in Sources */, 7DE9220829CA8576004483EB /* BitSetTests.swift in Sources */, 7DEBDB7329CCE44A00ADC226 /* _MinimalCollectionCore.swift in Sources */, 7DE921C829CA81DC004483EB /* _UniqueCollection.swift in Sources */, From ca0c7a28b9b94243cac476dc4275db29f5d0be46 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 11 Sep 2024 05:48:24 -0700 Subject: [PATCH 088/195] wip --- Sources/DequeModule/DynamicDeque.swift | 2 +- Sources/Future/Containers/Container.swift | 1 + Sources/Future/Containers/DynamicArray.swift | 4 +-- Sources/Future/Containers/NewArray.swift | 31 +++++++++++++++++--- Sources/Future/Containers/RigidArray.swift | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift index c4ad8ef17..4e6156b2b 100644 --- a/Sources/DequeModule/DynamicDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -86,7 +86,7 @@ extension DynamicDeque where Element: ~Copyable { @inlinable internal mutating func _grow(to minimumCapacity: Int) { guard minimumCapacity > _capacity else { return } - let c = Swift.max(minimumCapacity, 2 * _capacity) + let c = Swift.max(minimumCapacity, 7 * _capacity / 4) _storage.resize(to: c) } diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index b4d5a5be9..3473efb91 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -33,6 +33,7 @@ public protocol Container: ~Copyable, ~Escapable { var endIndex: Index { get } // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements + // FIXME: (Or rather, accessors with proper projection semantics.) @_borrowed subscript(index: Index) -> Element { get } func index(after index: Index) -> Index diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift index b84787aa3..0da61708d 100644 --- a/Sources/Future/Containers/DynamicArray.swift +++ b/Sources/Future/Containers/DynamicArray.swift @@ -40,8 +40,8 @@ extension DynamicArray where Element: ~Copyable { } extension DynamicArray where Element: ~Copyable { - public var span: Span { - _storage.span + public var storage: Span { + _storage.storage } } diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift index 4e89aeb5d..8c9b01118 100644 --- a/Sources/Future/Containers/NewArray.swift +++ b/Sources/Future/Containers/NewArray.swift @@ -21,6 +21,17 @@ public struct NewArray { } } +extension NewArray { + public var storage: Span { +#if false + // FIXME: This is what I want to write; alas, lifetimes are messed up. + return _storage.value.storage +#else + return Span(unsafeElements: _storage.value._items, owner: self) +#endif + } +} + extension NewArray { @inlinable public var capacity: Int { _storage.read { $0.capacity } } @@ -31,7 +42,10 @@ extension NewArray { } @inlinable - internal mutating func _ensureUnique(minimumCapacity: Int) { + internal mutating func _ensureUnique( + minimumCapacity: Int, + linear: Bool = false + ) { if !_storage.isUnique() { let c = Swift.max(count, minimumCapacity) _storage = Shared(_storage.read { $0._copy(capacity: c) }) @@ -88,8 +102,17 @@ extension NewArray: RandomAccessCollection, MutableCollection { } } -extension NewArray { - public var span: Span { - Span(unsafeElements: _storage.value._items, owner: self) +extension NewArray: RangeReplaceableCollection { + public init() { + // FIXME: Figure out if we can implement empty singletons in this setup. + self._storage = Shared(RigidArray(capacity: 0)) + } + + public func replaceSubrange( + _ subrange: Range, + with newElements: some Collection + ) { + let delta = newElements.count - subrange.count + _ensureUnique(minimumCapacity: capacity + delta) } } diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index adc654eb8..5d190222a 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -59,7 +59,7 @@ extension RigidArray where Element: ~Copyable { } extension RigidArray where Element: ~Copyable { - public var span: Span { + public var storage: Span { Span(unsafeElements: _items, owner: self) } } From 6c8dcadca4b201535443cfe4ca95a60b89fce3b0 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 19 Aug 2024 11:39:44 -0700 Subject: [PATCH 089/195] `swiftLanguageVersion` is now `swiftLanguageMode` --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 93325e1d2..a46642cd7 100644 --- a/Package.swift +++ b/Package.swift @@ -61,11 +61,11 @@ let _sharedSettings: [SwiftSetting] = defines.map { .define($0) } + [ ] let _settings: [SwiftSetting] = _sharedSettings + [ - .swiftLanguageVersion(.v6), + .swiftLanguageMode(.v6), ] let _testSettings: [SwiftSetting] = _sharedSettings + [ - .swiftLanguageVersion(.v5), + .swiftLanguageMode(.v5), ] struct CustomTarget { From c290bd3d0453385a6334e1afa671f04cb8f4700a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 29 Aug 2024 16:47:16 -0700 Subject: [PATCH 090/195] Copy `Span` prototype to its own branch --- Package.swift | 10 +- Sources/Future/ContiguousStorage.swift | 18 +- Sources/Future/RawSpan.swift | 122 +++-- Sources/Future/Span.swift | 155 +++---- Sources/Future/StdlibSpanExtensions.swift | 431 ++++++++++++++++++ Tests/FutureTests/RawSpanTests.swift | 53 +-- Tests/FutureTests/SpanTests.swift | 84 ++-- .../StdlibSpanExtensionTests.swift | 184 ++++++++ 8 files changed, 850 insertions(+), 207 deletions(-) create mode 100644 Sources/Future/StdlibSpanExtensions.swift create mode 100644 Tests/FutureTests/StdlibSpanExtensionTests.swift diff --git a/Package.swift b/Package.swift index a46642cd7..18ec08df3 100644 --- a/Package.swift +++ b/Package.swift @@ -55,18 +55,14 @@ var defines: [String] = [ let _sharedSettings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), - .enableExperimentalFeature("BitwiseCopyable"), .enableExperimentalFeature("RawLayout"), .enableExperimentalFeature("SuppressedAssociatedTypes"), ] -let _settings: [SwiftSetting] = _sharedSettings + [ - .swiftLanguageMode(.v6), -] -let _testSettings: [SwiftSetting] = _sharedSettings + [ - .swiftLanguageMode(.v5), -] +let _settings: [SwiftSetting] = _sharedSettings + [] + +let _testSettings: [SwiftSetting] = _sharedSettings + [] struct CustomTarget { enum Kind { diff --git a/Sources/Future/ContiguousStorage.swift b/Sources/Future/ContiguousStorage.swift index 3a747d9d1..27a4f00f9 100644 --- a/Sources/Future/ContiguousStorage.swift +++ b/Sources/Future/ContiguousStorage.swift @@ -24,13 +24,13 @@ extension Array: ContiguousStorage { public var storage: Span { _read { if let a = _baseAddressIfContiguous { - yield Span(unsafeStart: a, count: count, owner: self) + yield Span(_unsafeStart: a, count: count) } else { let a = ContiguousArray(copy self) #if true let s = Span( - unsafeStart: a._baseAddressIfContiguous!, count: a.count, owner: a + _unsafeStart: a._baseAddressIfContiguous!, count: a.count ) #else let s = a.storage @@ -45,7 +45,7 @@ extension ContiguousArray: ContiguousStorage { public var storage: Span { borrowing get { Span( - unsafeStart: _baseAddressIfContiguous!, count: count, owner: self + _unsafeStart: _baseAddressIfContiguous!, count: count ) } } @@ -57,13 +57,13 @@ extension CollectionOfOne: ContiguousStorage { /* ideally: (with strawman syntax) @addressable let value = self._element yield Span( - unsafePointer: Builtin.addressable(value), count: 1, owner: self - ) + unsafePointer: Builtin.addressable(value), count: 1 + ) */ let a = ContiguousArray(self) yield Span( - unsafeStart: a._baseAddressIfContiguous!, count: 1, owner: a + _unsafeStart: a._baseAddressIfContiguous!, count: 1 ) } } @@ -85,18 +85,18 @@ extension String.UTF8View: ContiguousStorage { let a = ContiguousArray(self) // yield a.storage yield Span( - unsafeStart: a._baseAddressIfContiguous!, count: 1, owner: a + _unsafeStart: a._baseAddressIfContiguous!, count: 1 ) } else if let buffer = withContiguousStorageIfAvailable({ $0 }) { // this is totally wrong, but there is a way with stdlib-internal API - yield Span(unsafeElements: buffer, owner: self) + yield Span(_unsafeElements: buffer) } else { // copy non-fast code units if we don't have eager bridging let a = ContiguousArray(self) // yield a.storage yield Span( - unsafeStart: a._baseAddressIfContiguous!, count: 1, owner: a + _unsafeStart: a._baseAddressIfContiguous!, count: 1 ) } } diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 97b79d41a..3f73b2ffc 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -10,27 +10,22 @@ // //===----------------------------------------------------------------------===// -import Builtin - // A RawSpan represents a span of initialized memory // of unspecified type. @frozen public struct RawSpan: Copyable, ~Escapable { - @usableFromInline - internal let _pointer: UnsafeRawPointer? + @usableFromInline let _pointer: UnsafeRawPointer? @usableFromInline @inline(__always) - internal var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } + var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } - @usableFromInline - internal let _count: Int + @usableFromInline let _count: Int - @inlinable @inline(__always) - internal init( + @_alwaysEmitIntoClient + internal init( _unchecked start: UnsafeRawPointer?, - byteCount: Int, - owner: borrowing Owner - ) { + byteCount: Int + ) -> dependsOn(immortal) Self { _pointer = start _count = byteCount } @@ -50,13 +45,12 @@ extension RawSpan { /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. - @inlinable @inline(__always) - public init( - unsafeBytes buffer: UnsafeRawBufferPointer, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: UnsafeRawBufferPointer + ) -> dependsOn(immortal) Self { self.init( - _unchecked: buffer.baseAddress, byteCount: buffer.count, owner: owner + _unchecked: buffer.baseAddress, byteCount: buffer.count ) } @@ -70,11 +64,10 @@ extension RawSpan { /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. @_alwaysEmitIntoClient - public init( - unsafeBytes buffer: UnsafeMutableRawBufferPointer, - owner: borrowing Owner - ) { - self.init(unsafeBytes: UnsafeRawBufferPointer(buffer), owner: owner) + public init( + _unsafeBytes buffer: UnsafeMutableRawBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } /// Unsafely create a `RawSpan` over initialized memory. @@ -88,14 +81,67 @@ extension RawSpan { /// - byteCount: the number of initialized bytes in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. - @inlinable @inline(__always) - public init( - unsafeStart pointer: UnsafeRawPointer, - byteCount: Int, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeStart pointer: UnsafeRawPointer, + byteCount: Int + ) -> dependsOn(immortal) Self { precondition(byteCount >= 0, "Count must not be negative") - self.init(_unchecked: pointer, byteCount: byteCount, owner: owner) + self.init(_unchecked: pointer, byteCount: byteCount) + } + + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `RawSpan`. + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: UnsafeBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) + } + + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableRawBufferPointer` to initialized memory. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `RawSpan`. + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: UnsafeMutableBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(buffer)) + } + + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory over `count` bytes starting at + /// `pointer` must be owned by the instance `owner`, + /// meaning that as long as `owner` is alive the memory will remain valid. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized byte. + /// - byteCount: the number of initialized bytes in the span. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `RawSpan`. + @_alwaysEmitIntoClient + public init( + _unsafeStart pointer: UnsafePointer, + count: Int + ) -> dependsOn(immortal) Self { + precondition(count >= 0, "Count must not be negative") + self.init( + _unchecked: pointer, byteCount: count*MemoryLayout.stride + ) } /// Create a `RawSpan` over the memory represented by a `Span` @@ -107,8 +153,7 @@ extension RawSpan { public init(_ span: borrowing Span) { self.init( _unchecked: UnsafeRawPointer(span._start), - byteCount: span.count * MemoryLayout.stride, - owner: span + byteCount: span.count * MemoryLayout.stride ) } } @@ -247,8 +292,7 @@ extension RawSpan { public func extracting(unchecked bounds: Range) -> Self { RawSpan( _unchecked: _pointer?.advanced(by: bounds.lowerBound), - byteCount: bounds.count, - owner: self + byteCount: bounds.count ) } @@ -352,7 +396,7 @@ extension RawSpan { public func unsafeView( as type: T.Type ) -> Span { - Span(unsafeStart: _start, byteCount: byteCount, owner: self) + Span(_unsafeStart: _start, byteCount: byteCount) } } @@ -520,7 +564,7 @@ extension RawSpan { public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, byteCount) - return Self(_unchecked: _pointer, byteCount: newCount, owner: self) + return Self(_unchecked: _pointer, byteCount: newCount) } /// Returns a span over all but the given number of trailing bytes. @@ -541,7 +585,7 @@ extension RawSpan { public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) - return Self(_unchecked: _pointer, byteCount: byteCount&-dc, owner: self) + return Self(_unchecked: _pointer, byteCount: byteCount&-dc) } /// Returns a span containing the trailing bytes of the span, @@ -564,7 +608,7 @@ extension RawSpan { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, byteCount) let newStart = _pointer?.advanced(by: byteCount&-newCount) - return Self(_unchecked: newStart, byteCount: newCount, owner: self) + return Self(_unchecked: newStart, byteCount: newCount) } /// Returns a span over all but the given number of initial bytes. @@ -586,7 +630,7 @@ extension RawSpan { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) let newStart = _pointer?.advanced(by: dc) - return Self(_unchecked: newStart, byteCount: byteCount&-dc, owner: self) + return Self(_unchecked: newStart, byteCount: byteCount&-dc) } } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 738bd5692..49f349406 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -16,33 +16,37 @@ import Builtin // contains initialized instances of `Element`. @frozen public struct Span: Copyable, ~Escapable { - @usableFromInline - internal let _buffer: UnsafeBufferPointer + @usableFromInline let _buffer: UnsafeBufferPointer @usableFromInline @inline(__always) - internal var _pointer: UnsafePointer? { _buffer.baseAddress } + var _pointer: UnsafePointer? { _buffer.baseAddress } @usableFromInline @inline(__always) - internal var _start: UnsafePointer { _pointer.unsafelyUnwrapped } + var _start: UnsafePointer { _pointer.unsafelyUnwrapped } + +// @usableFromInline @inline(__always) +// var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } @usableFromInline @inline(__always) - internal var _count: Int { _buffer.count } + var _count: Int { _buffer.count } @inlinable @inline(__always) - internal init( - _unchecked elements: UnsafeBufferPointer, - owner: borrowing Owner - ) { + internal init( + _unchecked elements: UnsafeBufferPointer + ) -> dependsOn(immortal) Self { _buffer = elements +// _pointer = .init(elements.baseAddress) +// _count = elements.count } - @inlinable @inline(__always) - internal init( + @_alwaysEmitIntoClient + internal init( _unchecked start: UnsafePointer?, - count: Int, - owner: borrowing Owner - ) { - self.init(_unchecked: .init(start: start, count: count), owner: owner) + count: Int + ) -> dependsOn(immortal) Self { + self.init(_unchecked: .init(start: start, count: count)) +// _pointer = .init(start) +// _count = count } } @@ -68,16 +72,15 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. - @inlinable @inline(__always) - public init( - unsafeElements buffer: UnsafeBufferPointer, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: UnsafeBufferPointer + ) -> dependsOn(immortal) Self { precondition( buffer.count == 0 || buffer.baseAddress.unsafelyUnwrapped.isAligned, "baseAddress must be properly aligned for accessing \(Element.self)" ) - self.init(_unchecked: buffer, owner: owner) + self.init(_unchecked: buffer) } /// Unsafely create a `Span` over initialized memory. @@ -90,11 +93,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. @_alwaysEmitIntoClient - public init( - unsafeElements buffer: UnsafeMutableBufferPointer, - owner: borrowing Owner - ) { - self.init(unsafeElements: UnsafeBufferPointer(buffer), owner: owner) + public init( + _unsafeElements buffer: UnsafeMutableBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } /// Unsafely create a `Span` over initialized memory. @@ -108,18 +110,17 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. - @inlinable @inline(__always) - public init( - unsafeStart start: UnsafePointer, - count: Int, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeStart start: UnsafePointer, + count: Int + ) -> dependsOn(immortal) Self { precondition(count >= 0, "Count must not be negative") precondition( start.isAligned, "baseAddress must be properly aligned for accessing \(Element.self)" ) - self.init(_unchecked: start, count: count, owner: owner) + self.init(_unchecked: start, count: count) } } @@ -134,12 +135,11 @@ extension Span where Element: BitwiseCopyable { /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. - @inlinable @inline(__always) - public init( - unsafeElements buffer: UnsafeBufferPointer, - owner: borrowing Owner - ) { - self.init(_unchecked: buffer, owner: owner) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: UnsafeBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unchecked: buffer) } /// Unsafely create a `Span` over initialized memory. @@ -152,11 +152,10 @@ extension Span where Element: BitwiseCopyable { /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. @_alwaysEmitIntoClient - public init( - unsafeElements buffer: UnsafeMutableBufferPointer, - owner: borrowing Owner - ) { - self.init(unsafeElements: UnsafeBufferPointer(buffer), owner: owner) + public init( + _unsafeElements buffer: UnsafeMutableBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } /// Unsafely create a `Span` over initialized memory. @@ -170,14 +169,13 @@ extension Span where Element: BitwiseCopyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. - @inlinable @inline(__always) - public init( - unsafeStart start: UnsafePointer, - count: Int, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeStart start: UnsafePointer, + count: Int + ) -> dependsOn(immortal) Self { precondition(count >= 0, "Count must not be negative") - self.init(_unchecked: start, count: count, owner: owner) + self.init(_unchecked: start, count: count) } /// Unsafely create a `Span` over initialized memory. @@ -194,19 +192,17 @@ extension Span where Element: BitwiseCopyable { /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. - @inlinable @inline(__always) - public init( - unsafeBytes buffer: UnsafeRawBufferPointer, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: UnsafeRawBufferPointer + ) -> dependsOn(immortal) Self { let (byteCount, stride) = (buffer.count, MemoryLayout.stride) - assert(byteCount >= 0, "Count must not be negative") + precondition(byteCount >= 0, "Count must not be negative") let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0) self.init( _unchecked: buffer.baseAddress?.assumingMemoryBound(to: Element.self), - count: count, - owner: owner + count: count ) } @@ -225,11 +221,10 @@ extension Span where Element: BitwiseCopyable { /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. @_alwaysEmitIntoClient - public init( - unsafeBytes buffer: UnsafeMutableRawBufferPointer, - owner: borrowing Owner - ) { - self.init(unsafeBytes: UnsafeRawBufferPointer(buffer), owner: owner) + public init( + _unsafeBytes buffer: UnsafeMutableRawBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } /// Unsafely create a `Span` over initialized memory. @@ -243,20 +238,18 @@ extension Span where Element: BitwiseCopyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. - @inlinable @inline(__always) - public init( - unsafeStart pointer: UnsafeRawPointer, - byteCount: Int, - owner: borrowing Owner - ) { + @_alwaysEmitIntoClient + public init( + _unsafeStart pointer: UnsafeRawPointer, + byteCount: Int + ) -> dependsOn(immortal) Self { precondition(byteCount >= 0, "Count must not be negative") let stride = MemoryLayout.stride let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0) self.init( _unchecked: pointer.assumingMemoryBound(to: Element.self), - count: count, - owner: owner + count: count ) } } @@ -459,7 +452,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable @inline(__always) public subscript(unchecked position: Int) -> Element { _read { - yield _start.advanced(by: position).pointee + let element = UnsafeRawPointer(_start).advanced(by: position&*MemoryLayout.stride) + let binding = Builtin.bindMemory(element._rawValue, count._builtinWordValue, Element.self) + defer { Builtin.rebindMemory(element._rawValue, binding) } + yield UnsafePointer(element._rawValue).pointee } } } @@ -491,7 +487,7 @@ extension Span where Element: BitwiseCopyable { @inlinable @inline(__always) public subscript(unchecked position: Int) -> Element { get { - let address = UnsafeRawPointer(_start.advanced(by: position)) + let address = UnsafeRawPointer(_start).advanced(by: position&*MemoryLayout.stride) return address.loadUnaligned(as: Element.self) } } @@ -538,8 +534,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting(unchecked bounds: Range) -> Self { Span( _unchecked: _pointer?.advanced(by: bounds.lowerBound), - count: bounds.count, - owner: self + count: bounds.count ) } @@ -730,7 +725,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, count) - return Self(_unchecked: _pointer, count: newCount, owner: self) + return Self(_unchecked: _pointer, count: newCount) } /// Returns a span over all but the given number of trailing elements. @@ -751,7 +746,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) - return Self(_unchecked: _pointer, count: count&-droppedCount, owner: self) + return Self(_unchecked: _pointer, count: count&-droppedCount) } /// Returns a span containing the final elements of the span, @@ -774,7 +769,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, count) let newStart = _pointer?.advanced(by: count&-newCount) - return Self(_unchecked: newStart, count: newCount, owner: self) + return Self(_unchecked: newStart, count: newCount) } /// Returns a span over all but the given number of initial elements. @@ -796,6 +791,6 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) let newStart = _pointer?.advanced(by: droppedCount) - return Self(_unchecked: newStart, count: count&-droppedCount, owner: self) + return Self(_unchecked: newStart, count: count&-droppedCount) } } diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift new file mode 100644 index 000000000..e6b3dbb07 --- /dev/null +++ b/Sources/Future/StdlibSpanExtensions.swift @@ -0,0 +1,431 @@ +//===--- StdlibSpanExtensions.swift ---------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +extension UnsafeBufferPointer where Element: ~Copyable { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + try body(Span(_unsafeElements: self)) + } + + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result where Element: BitwiseCopyable { + try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(self))) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + try body(Span(_unsafeElements: self)) + } + + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result where Element: BitwiseCopyable { + try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(self))) + } +} + +extension UnsafeRawBufferPointer { + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + try body(RawSpan(_unsafeBytes: self)) + } +} + +extension UnsafeMutableRawBufferPointer { + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + try body(RawSpan(_unsafeBytes: self)) + } +} + +extension Slice { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result + where Base == UnsafeBufferPointer { + try body(Span(_unsafeElements: UnsafeBufferPointer(rebasing: self))) + } + + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result + where Base == UnsafeBufferPointer { + try body(RawSpan(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: self)))) + } + + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result + where Base == UnsafeMutableBufferPointer { + try body(Span(_unsafeElements: UnsafeBufferPointer(rebasing: self))) + } + + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result + where Base == UnsafeMutableBufferPointer { + try body(RawSpan(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: self)))) + } + + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result + where Base == UnsafeRawBufferPointer { + try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(rebasing: self))) + } + + public func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result + where Base == UnsafeMutableRawBufferPointer { + try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(rebasing: self))) + } +} + +#if false //FIXME: rdar://134382237 +import struct Foundation.Data + +extension Data { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result = withUnsafeBytes { + do throws(E) { + return .success(try body(Span(_unsafeBytes: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } + + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result = withUnsafeBytes { + do throws(E) { + return .success(try body(RawSpan(_unsafeBytes: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} +#endif + +extension Array { + + /// Calls a closure with a `Span` of the array's contiguous storage. + /// + /// Often, the optimizer can eliminate bounds checks within an array + /// algorithm, but when that fails, invoking the same algorithm on the + /// buffer pointer passed into your closure lets you trade safety for speed. + /// + /// The following example shows how you can iterate over the contents of the + /// buffer pointer: + /// + /// let numbers = [1, 2, 3, 4, 5] + /// let sum = numbers.withUnsafeBufferPointer { buffer -> Int in + /// var result = 0 + /// for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) { + /// result += buffer[i] + /// } + /// return result + /// } + /// // 'sum' == 9 + /// + /// The pointer passed as an argument to `body` is valid only during the + /// execution of `withUnsafeBufferPointer(_:)`. Do not store or return the + /// pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter that + /// points to the contiguous storage for the array. If no such storage exists, it is created. If + /// `body` has a return value, that value is also used as the return value + /// for the `withUnsafeBufferPointer(_:)` method. The pointer argument is + /// valid only for the duration of the method's execution. + /// - Returns: The return value, if any, of the `body` closure parameter. + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBufferPointer { + elements in + do throws(E) { + let result = try body(Span(_unsafeElements: elements)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } + } + return buffer.moveElement(from: 0) + } + switch consume result { + case .success(let s): return s + case .failure(let e): throw e + } + } + + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result where Element: BitwiseCopyable { + let result: Swift.Result = withUnsafeBytes { + do throws(E) { + return .success(try body(RawSpan(_unsafeBytes: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension ContiguousArray { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result = withUnsafeBufferPointer { + do throws(E) { + return .success(try body(Span(_unsafeElements: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension ContiguousArray where Element: BitwiseCopyable { + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result = withUnsafeBytes { + do throws(E) { + return .success(try body(RawSpan(_unsafeBytes: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension ArraySlice { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result = withUnsafeBufferPointer { + do throws(E) { + let span = Span(_unsafeElements: $0) + print(span._start, span.count) + return .success(try body(span)) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension ArraySlice where Element: BitwiseCopyable { + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result = withUnsafeBytes { + do throws(E) { + return .success(try body(RawSpan(_unsafeBytes: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension String.UTF8View { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result? = withContiguousStorageIfAvailable { + do throws(E) { + return .success(try body(Span(_unsafeElements: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s)?: return s + case .failure(let e)?: throw e + case nil: return try ContiguousArray(self).withSpan(body) + } + } + + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result? = withContiguousStorageIfAvailable { + do throws(E) { + return .success(try body(RawSpan(_unsafeElements: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s)?: return s + case .failure(let e)?: throw e + case nil: return try ContiguousArray(self).withBytes(body) + } + } +} + +extension Substring.UTF8View { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result? = withContiguousStorageIfAvailable { + do throws(E) { + return .success(try body(Span(_unsafeElements: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s)?: return s + case .failure(let e)?: throw e + case nil: return try ContiguousArray(self).withSpan(body) + } + } + + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + let result: Swift.Result? = withContiguousStorageIfAvailable { + do throws(E) { + return .success(try body(RawSpan(_unsafeElements: $0))) + } catch { + return .failure(error) + } + } + switch result { + case .success(let s)?: return s + case .failure(let e)?: throw e + case nil: return try ContiguousArray(self).withBytes(body) + } + } +} + +extension CollectionOfOne { + public func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + var collection = self + let result: Swift.Result = withUnsafePointer(to: &collection) { + $0.withMemoryRebound(to: Element.self, capacity: 1) { + do throws(E) { + return .success(try body(Span(_unsafeStart: $0, count: 1))) + } catch { + return .failure(error) + } + } + } + switch result { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension CollectionOfOne where Element: BitwiseCopyable { + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + var collection = self + let r: Swift.Result = Swift.withUnsafeBytes(of: &collection) { + do throws(E) { + return .success(try body(RawSpan(_unsafeBytes: $0))) + } catch { + return .failure(error) + } + } + switch r { + case .success(let s): return s + case .failure(let e): throw e + } + } +} + +extension KeyValuePairs { + public func withSpan( + _ body: ( + _ elements: Span<(key: Key, value: Value)> + ) throws(E) -> Result + ) throws(E) -> Result { + try Array(self).withSpan(body) + } +} + +extension KeyValuePairs where Element: BitwiseCopyable { + public func withBytes( + _ body: (_ bytes: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + try Array(self).withBytes(body) + } +} + +extension Span where Element: ~Copyable /*& ~Escapable*/ { + public consuming func withSpan( + _ body: (_ elements: Span) throws(E) -> Result + ) throws(E) -> Result { + try body(self) + } +} + +extension Span where Element: BitwiseCopyable { + public consuming func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + try body(RawSpan(self)) + } +} + +//TODO: extend SIMD vectors with `withSpan` and with `withBytes`. diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index bea88a64f..d599da074 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -45,12 +45,12 @@ final class RawSpanTests: XCTestCase { let capacity = 4 var a = Array(0...stride) } a.withUnsafeMutableBytes { - let span = RawSpan(unsafeBytes: $0, owner: $0) + let span = RawSpan(_unsafeBytes: $0) XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) } } @@ -61,9 +61,8 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let pointer = $0.baseAddress! let span = RawSpan( - unsafeStart: pointer, - byteCount: capacity*MemoryLayout.stride, - owner: a + _unsafeStart: pointer, + byteCount: capacity*MemoryLayout.stride ) XCTAssertEqual(span.byteCount, $0.count) } @@ -71,9 +70,8 @@ final class RawSpanTests: XCTestCase { a.withUnsafeMutableBytes { let pointer = $0.baseAddress! let span = RawSpan( - unsafeStart: pointer, - byteCount: capacity*MemoryLayout.stride, - owner: $0 + _unsafeStart: pointer, + byteCount: capacity*MemoryLayout.stride ) XCTAssertEqual(span.byteCount, $0.count) } @@ -83,7 +81,7 @@ final class RawSpanTests: XCTestCase { let capacity = 4 let s = (0...stride let s0 = span.unsafeLoad(as: String.self) @@ -99,7 +97,7 @@ final class RawSpanTests: XCTestCase { let capacity = 64 let a = Array(0.. diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 8c9d32486..8446fc01f 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -31,19 +31,19 @@ final class SpanTests: XCTestCase { let capacity = 4 var s = (0..(unsafeBytes: $0, owner: $0) + let b = Span(_unsafeBytes: $0) XCTAssertEqual(b.count, capacity) - let r = Span(unsafeBytes: $0, owner: $0) + let r = Span(_unsafeBytes: $0) XCTAssertEqual(r.count, capacity*MemoryLayout.stride) let p = Span( - unsafeStart: $0.baseAddress!, - byteCount: capacity*MemoryLayout.stride, - owner: $0 + _unsafeStart: $0.baseAddress!, + byteCount: capacity*MemoryLayout.stride ) XCTAssertEqual(p.count, capacity) } a.withUnsafeMutableBytes { - let b = Span(unsafeBytes: $0, owner: $0) + let b = Span(_unsafeBytes: $0) XCTAssertEqual(b.count, capacity) let p = Span( - unsafeStart: $0.baseAddress!, - byteCount: capacity*MemoryLayout.stride, - owner: $0 + _unsafeStart: $0.baseAddress!, + byteCount: capacity*MemoryLayout.stride ) XCTAssertEqual(p.count, capacity) } @@ -100,11 +98,11 @@ final class SpanTests: XCTestCase { let capacity = 4 let a = Array(0...stride) } @@ -124,7 +122,7 @@ final class SpanTests: XCTestCase { let capacity = 4 let a = Array(0..(start: nil, count: 0) - let span = Span(unsafeElements: b, owner: b) + let span = Span(_unsafeElements: b) XCTAssertEqual(span.count, b.count) XCTAssertEqual(span.extracting(first: 1).count, b.count) XCTAssertEqual(span.extracting(droppingLast: 1).count, b.count) @@ -276,7 +274,7 @@ final class SpanTests: XCTestCase { let capacity = 4 let a = Array(0..(unsafeElements: $0, owner: $0) + let span = Span(_unsafeElements: $0) XCTAssertEqual(span.count, capacity) XCTAssertEqual(span.extracting(last: capacity).first, 0) XCTAssertEqual(span.extracting(last: capacity-1).first, 1) @@ -287,7 +285,7 @@ final class SpanTests: XCTestCase { do { let b = UnsafeBufferPointer(start: nil, count: 0) - let span = Span(unsafeElements: b, owner: b) + let span = Span(_unsafeElements: b) XCTAssertEqual(span.count, b.count) XCTAssertEqual(span.extracting(last: 1).count, b.count) XCTAssertEqual(span.extracting(droppingFirst: 1).count, b.count) @@ -298,7 +296,7 @@ final class SpanTests: XCTestCase { let a = Array(0..( - unsafeElements: UnsafeBufferPointer(start: nil, count: 0), owner: b + _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) ) XCTAssertTrue(span.contains(subSpan)) @@ -390,11 +388,11 @@ final class SpanTests: XCTestCase { _ = b.initialize(fromContentsOf: 0..<8) defer { b.deallocate() } - let span = Span(unsafeElements: .init(b), owner: b) + let span = Span(_unsafeElements: b) let subSpan = span.extracting(last: 2) let emptySpan = span.extracting(first: 0) let nilSpan = Span( - unsafeElements: UnsafeBufferPointer(start: nil, count: 0), owner: b + _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) ) var bounds: Range diff --git a/Tests/FutureTests/StdlibSpanExtensionTests.swift b/Tests/FutureTests/StdlibSpanExtensionTests.swift new file mode 100644 index 000000000..446bb7cd1 --- /dev/null +++ b/Tests/FutureTests/StdlibSpanExtensionTests.swift @@ -0,0 +1,184 @@ +//===--- StdlibSpanExtensionTests.swift -----------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +enum ErrorForTesting: Error, Equatable { case error, errorToo, errorAsWell } + +final class StdlibSpanExtensionTests: XCTestCase { + +#if false //FIXME: rdar://134382237 -- nightly toolchain cannot `import Darwin` + func testDataSpan() throws { + let a = Data(0..<4) + a.withSpan { + for i in $0._indices { + XCTAssertEqual($0[i], UInt8(i)) + } + } + do throws(ErrorForTesting) { + try a.withSpan { _ throws(ErrorForTesting) in throw .error } + } catch { + XCTAssertEqual(error, .error) + } + } + + func testDataRawSpan() throws { + let a = Data(0..<4) + a.withBytes { + for i in $0._byteOffsets { + XCTAssertEqual( + $0.unsafeLoad(fromByteOffset: i, as: UInt8.self), UInt8(i) + ) + } + } + do throws(ErrorForTesting) { + try a.withBytes { _ throws(ErrorForTesting) in throw .error } + } catch { + XCTAssertEqual(error, .error) + } + } +#endif + + func testArraySpan() throws { + let a = (0..<4).map(String.init(_:)) + do throws(ErrorForTesting) { + a.withSpan { + for i in $0._indices { + XCTAssertEqual($0[i], String(i)) + } + } + try a.withSpan { _ throws(ErrorForTesting) in throw .error } + } catch { + XCTAssertEqual(error, .error) + } + } + + func testArrayRawSpan() throws { + let c = 4 + let a = Array(0...stride, as: Int.self + ) + ) + } + } + do throws(ErrorForTesting) { + try a.withBytes { _ throws(ErrorForTesting) in throw .error } + } catch { + XCTAssertEqual(error, .error) + } + } + + func testContiguousArraySpan() throws { + let a = ContiguousArray((0..<4).map(String.init(_:))) + a.withSpan { + for i in $0._indices { + XCTAssertEqual($0[i], String(i)) + } + } + do throws(ErrorForTesting) { + try a.withSpan { _ throws(ErrorForTesting) in throw .error } + } catch { + XCTAssertEqual(error, .error) + } + } + + func testContiguousArrayRawSpan() throws { + let c = 4 + let a = ContiguousArray(0...stride, as: Int.self + ) + ) + } + } + XCTAssertThrowsError(try a.withBytes({ _ in throw ErrorForTesting.error })) + } + + func testArraySliceSpan() throws { + let a = (0..<7).map(String.init(_:)).prefix(upTo: 4) + print(a.count) + a.withSpan { +// print($0.count) + print($0._indices) + for i in $0._indices { + print(i) + let v = $0[i] + _ = v +// XCTAssertEqual($0[i], String(i)) + } + } +// do throws(ErrorForTesting) { +// try a.withSpan { _ throws(ErrorForTesting) in throw .error } +// } catch { +// XCTAssertEqual(error, .error) +// } + } + + func testArraySliceRawSpan() throws { + let c = 4 + let a = Array(0..<7).prefix(upTo: c) + a.withBytes { + for i in 0...stride, as: Int.self + ) + ) + } + } + XCTAssertThrowsError(try a.withBytes({ _ in throw ErrorForTesting.error })) + } + + func testCollectionOfOneSpan() throws { + let a = CollectionOfOne("CollectionOfOne is an obscure Collection type.") + a.withSpan { + XCTAssertTrue($0._elementsEqual(a)) + } + XCTAssertThrowsError(try a.withSpan({ _ in throw ErrorForTesting.error })) + } + + func testCollectionOfOneRawSpan() throws { + let a = CollectionOfOne(Int(UInt8.random(in: 0 ..< .max))) + a.withBytes { + for i in $0._byteOffsets { + let v = $0.unsafeLoad(fromByteOffset: i, as: UInt8.self) + if v != 0 { + XCTAssertEqual(Int(v), a.first) + } + } + } + XCTAssertThrowsError(try a.withBytes({ _ in throw ErrorForTesting.error })) + } + + func testUTF8ViewSpan() throws { + let strings: [String] = [ + "small", + "Not a small string, if I can count code units correctly.", + NSString("legacy string if I can get it to behave.") as String + ] + for s in strings { + s.utf8.withSpan { + XCTAssertEqual($0.count, s.utf8.count) + } + } + let a = strings[0].utf8 + XCTAssertThrowsError(try a.withSpan({ _ in throw ErrorForTesting.error })) + } +} From 7f13d84dd50f2b1fed453e300a28f2b1ab64de64 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 5 Sep 2024 17:49:37 -0700 Subject: [PATCH 091/195] update RawSpan to ~match proposal --- Sources/Future/RawSpan.swift | 54 ++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 3f73b2ffc..842798672 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -173,7 +173,7 @@ extension RawSpan { String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) } - public var description: String { + public var _description: String { "(0x\(_address), \(_count))" } } @@ -214,7 +214,7 @@ extension RawSpan { /// - position: an index to validate /// - Returns: true if `offset` is a valid index @inlinable @inline(__always) - public func validateBounds(_ offset: Int) -> Bool { + public func boundsContain(_ offset: Int) -> Bool { 0 <= offset && offset < byteCount } @@ -223,9 +223,9 @@ extension RawSpan { /// - Parameters: /// - position: an index to validate @inlinable @inline(__always) - public func assertValidity(_ offset: Int) { + public func boundsPrecondition(_ offset: Int) { precondition( - validateBounds(offset), "Offset out of bounds" + boundsContain(offset), "Offset out of bounds" ) } @@ -235,7 +235,7 @@ extension RawSpan { /// - offsets: a range of indices to validate /// - Returns: true if `offsets` is a valid range of indices @inlinable @inline(__always) - public func validateBounds(_ offsets: Range) -> Bool { + public func boundsContain(_ offsets: Range) -> Bool { 0 <= offsets.lowerBound && offsets.upperBound <= byteCount } @@ -244,13 +244,14 @@ extension RawSpan { /// - Parameters: /// - offsets: a range of indices to validate @inlinable @inline(__always) - public func assertValidity(_ offsets: Range) { + public func boundsPrecondition(_ offsets: Range) { precondition( - validateBounds(offsets), "Range of offsets out of bounds" + boundsContain(offsets), "Range of offsets out of bounds" ) } } +/* //MARK: extracting sub-spans extension RawSpan { @@ -269,7 +270,7 @@ extension RawSpan { /// - Complexity: O(1) @inlinable @inline(__always) public func extracting(_ bounds: Range) -> Self { - assertValidity(bounds) + boundsPrecondition(bounds) return extracting(unchecked: bounds) } @@ -350,6 +351,33 @@ extension RawSpan { self } } +*/ + +extension RawSpan { + @_alwaysEmitIntoClient + public mutating func _shrink(to bounds: Range) { + boundsPrecondition(bounds) + _shrink(unchecked: bounds) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(unchecked bounds: Range) { + self = RawSpan( + _unchecked: _pointer?.advanced(by: bounds.lowerBound), + byteCount: bounds.count + ) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(_ bounds: some RangeExpression) { + _shrink(to: bounds.relative(to: _byteOffsets)) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(unchecked bounds: some RangeExpression) { + _shrink(unchecked: bounds.relative(to: _byteOffsets)) + } +} extension RawSpan { @@ -424,7 +452,7 @@ extension RawSpan { public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - assertValidity( + boundsPrecondition( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) ) return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) @@ -475,7 +503,7 @@ extension RawSpan { public func unsafeLoadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - assertValidity( + boundsPrecondition( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) ) return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) @@ -543,6 +571,7 @@ extension RawSpan { } //MARK: one-sided slicing operations +/* extension RawSpan { /// Returns a span containing the initial bytes of this span, @@ -633,6 +662,7 @@ extension RawSpan { return Self(_unchecked: newStart, byteCount: byteCount&-dc) } } +*/ /// An error indicating that out-of-bounds access was attempted @frozen @@ -649,6 +679,7 @@ public struct OutOfBoundsError: Error { } } +/* extension RawSpan { /// Parse an instance of `T`, advancing `position`. @inlinable @@ -696,7 +727,7 @@ extension RawSpan { @inlinable public init(_ base: RawSpan, in range: Range) { - base.assertValidity(range) + base.boundsPrecondition(range) position = 0 self.base = base parseRange = range @@ -742,3 +773,4 @@ extension RawSpan { Cursor(self, in: range) } } +*/ From 3fda314bcd8574f37d5e771a956bf5c6fe605384 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 9 Sep 2024 15:45:44 -0700 Subject: [PATCH 092/195] updates to RawSpan --- Sources/Future/RawSpan.swift | 158 +++++++++++++-------------- Tests/FutureTests/RawSpanTests.swift | 58 +++++----- 2 files changed, 108 insertions(+), 108 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 842798672..432628332 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -149,7 +149,7 @@ extension RawSpan { /// - Parameters: /// - span: An existing `Span`, which will define both this /// `RawSpan`'s lifetime and the memory it represents. - @inlinable @inline(__always) + @_alwaysEmitIntoClient public init(_ span: borrowing Span) { self.init( _unchecked: UnsafeRawPointer(span._start), @@ -208,50 +208,37 @@ extension RawSpan { //MARK: Bounds Checking extension RawSpan { - /// Return true if `offset` is a valid offset into this `RawSpan` + /// Return true if `offset` is a valid byte offset into this `RawSpan` /// /// - Parameters: - /// - position: an index to validate - /// - Returns: true if `offset` is a valid index + /// - position: a byte offset to validate + /// - Returns: true if `offset` is a valid byte offset @inlinable @inline(__always) public func boundsContain(_ offset: Int) -> Bool { 0 <= offset && offset < byteCount } - /// Traps if `offset` is not a valid offset into this `RawSpan` - /// - /// - Parameters: - /// - position: an index to validate - @inlinable @inline(__always) - public func boundsPrecondition(_ offset: Int) { - precondition( - boundsContain(offset), "Offset out of bounds" - ) - } - /// Return true if `offsets` is a valid range of offsets into this `RawSpan` /// /// - Parameters: - /// - offsets: a range of indices to validate - /// - Returns: true if `offsets` is a valid range of indices + /// - offsets: a range of byte offsets to validate + /// - Returns: true if `offsets` is a valid range of byte offsets @inlinable @inline(__always) public func boundsContain(_ offsets: Range) -> Bool { 0 <= offsets.lowerBound && offsets.upperBound <= byteCount } - /// Traps if `offsets` is not a valid range of offsets into this `RawSpan` + /// Return true if `offsets` is a valid range of offsets into this `RawSpan` /// /// - Parameters: - /// - offsets: a range of indices to validate + /// - offsets: a range of byte offsets to validate + /// - Returns: true if `offsets` is a valid range of byte offsets @inlinable @inline(__always) - public func boundsPrecondition(_ offsets: Range) { - precondition( - boundsContain(offsets), "Range of offsets out of bounds" - ) + public func boundsContain(_ offsets: ClosedRange) -> Bool { + 0 <= offsets.lowerBound && offsets.upperBound < byteCount } } -/* //MARK: extracting sub-spans extension RawSpan { @@ -268,10 +255,15 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) - @inlinable @inline(__always) - public func extracting(_ bounds: Range) -> Self { - boundsPrecondition(bounds) - return extracting(unchecked: bounds) + @_alwaysEmitIntoClient + @usableFromInline func _extracting(_ bounds: Range) -> Self { + precondition(boundsContain(bounds)) + return _extracting(unchecked: bounds) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(to bounds: Range) { + self = _extracting(bounds) } /// Constructs a new span over the bytes within the supplied range of @@ -289,14 +281,19 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) - @inlinable @inline(__always) - public func extracting(unchecked bounds: Range) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(unchecked bounds: Range) -> Self { RawSpan( _unchecked: _pointer?.advanced(by: bounds.lowerBound), byteCount: bounds.count ) } + @_alwaysEmitIntoClient + public mutating func _shrink(unchecked bounds: Range) { + self = _extracting(unchecked: bounds) + } + /// Constructs a new span over the bytes within the supplied range of /// positions within this span. /// @@ -311,8 +308,13 @@ extension RawSpan { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public func extracting(_ bounds: some RangeExpression) -> Self { - extracting(bounds.relative(to: _byteOffsets)) + @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { + _extracting(bounds.relative(to: _byteOffsets)) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(_ bounds: some RangeExpression) { + self = _extracting(bounds) } /// Constructs a new span over the bytes within the supplied range of @@ -331,10 +333,15 @@ extension RawSpan { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public func extracting( + @usableFromInline func _extracting( unchecked bounds: some RangeExpression ) -> Self { - extracting(unchecked: bounds.relative(to: _byteOffsets)) + _extracting(unchecked: bounds.relative(to: _byteOffsets)) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(unchecked bounds: some RangeExpression) { + self = _extracting(unchecked: bounds) } /// Constructs a new span over all the bytes of this span. @@ -347,37 +354,10 @@ extension RawSpan { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public func extracting(_: UnboundedRange) -> Self { + @usableFromInline func _extracting(_: UnboundedRange) -> Self { self } } -*/ - -extension RawSpan { - @_alwaysEmitIntoClient - public mutating func _shrink(to bounds: Range) { - boundsPrecondition(bounds) - _shrink(unchecked: bounds) - } - - @_alwaysEmitIntoClient - public mutating func _shrink(unchecked bounds: Range) { - self = RawSpan( - _unchecked: _pointer?.advanced(by: bounds.lowerBound), - byteCount: bounds.count - ) - } - - @_alwaysEmitIntoClient - public mutating func _shrink(_ bounds: some RangeExpression) { - _shrink(to: bounds.relative(to: _byteOffsets)) - } - - @_alwaysEmitIntoClient - public mutating func _shrink(unchecked bounds: some RangeExpression) { - _shrink(unchecked: bounds.relative(to: _byteOffsets)) - } -} extension RawSpan { @@ -452,9 +432,9 @@ extension RawSpan { public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - boundsPrecondition( + precondition(boundsContain( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) - ) + )) return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) } @@ -503,9 +483,9 @@ extension RawSpan { public func unsafeLoadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - boundsPrecondition( + precondition(boundsContain( Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) - ) + )) return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) } @@ -571,7 +551,6 @@ extension RawSpan { } //MARK: one-sided slicing operations -/* extension RawSpan { /// Returns a span containing the initial bytes of this span, @@ -589,13 +568,18 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) - @inlinable - public func extracting(first maxLength: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, byteCount) return Self(_unchecked: _pointer, byteCount: newCount) } + @_alwaysEmitIntoClient + public mutating func _shrink(toFirst maxLength: Int) { + self = _extracting(first: maxLength) + } + /// Returns a span over all but the given number of trailing bytes. /// /// If the number of elements to drop exceeds the number of elements in @@ -610,13 +594,18 @@ extension RawSpan { /// - Returns: A span leaving off the specified number of bytes at the end. /// /// - Complexity: O(1) - @inlinable - public func extracting(droppingLast k: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) return Self(_unchecked: _pointer, byteCount: byteCount&-dc) } + @_alwaysEmitIntoClient + public mutating func _shrink(droppingLast k: Int) { + self = _extracting(droppingLast: k) + } + /// Returns a span containing the trailing bytes of the span, /// up to the given maximum length. /// @@ -632,14 +621,19 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) - @inlinable - public func extracting(last maxLength: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, byteCount) let newStart = _pointer?.advanced(by: byteCount&-newCount) return Self(_unchecked: newStart, byteCount: newCount) } + @_alwaysEmitIntoClient + public mutating func _shrink(toLast maxLength: Int) { + self = _extracting(last: maxLength) + } + /// Returns a span over all but the given number of initial bytes. /// /// If the number of elements to drop exceeds the number of bytes in @@ -654,15 +648,19 @@ extension RawSpan { /// - Returns: A span starting after the specified number of bytes. /// /// - Complexity: O(1) - @inlinable - public func extracting(droppingFirst k: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) let newStart = _pointer?.advanced(by: dc) return Self(_unchecked: newStart, byteCount: byteCount&-dc) } + + @_alwaysEmitIntoClient + public mutating func _shrink(droppingFirst k: Int) { + self = _extracting(droppingFirst: k) + } } -*/ /// An error indicating that out-of-bounds access was attempted @frozen @@ -679,7 +677,6 @@ public struct OutOfBoundsError: Error { } } -/* extension RawSpan { /// Parse an instance of `T`, advancing `position`. @inlinable @@ -710,7 +707,7 @@ extension RawSpan { guard end <= length else { throw OutOfBoundsError(expected: length, has: byteCount&-position) } - return extracting(position..) { - base.boundsPrecondition(range) + precondition(base.boundsContain(range)) position = 0 self.base = base parseRange = range @@ -758,7 +755,7 @@ extension RawSpan { /// The bytes that we've parsed so far @inlinable - public var parsedBytes: RawSpan { base.extracting(.. Date: Mon, 9 Sep 2024 16:44:58 -0700 Subject: [PATCH 093/195] updates to Span --- Sources/Future/Span.swift | 44 ++++++++++++++----------------- Tests/FutureTests/SpanTests.swift | 1 + 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 49f349406..6044024d9 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -16,37 +16,28 @@ import Builtin // contains initialized instances of `Element`. @frozen public struct Span: Copyable, ~Escapable { - @usableFromInline let _buffer: UnsafeBufferPointer + @usableFromInline let _pointer: UnsafeRawPointer? @usableFromInline @inline(__always) - var _pointer: UnsafePointer? { _buffer.baseAddress } + var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } - @usableFromInline @inline(__always) - var _start: UnsafePointer { _pointer.unsafelyUnwrapped } - -// @usableFromInline @inline(__always) -// var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } - - @usableFromInline @inline(__always) - var _count: Int { _buffer.count } + @usableFromInline let _count: Int @inlinable @inline(__always) internal init( _unchecked elements: UnsafeBufferPointer ) -> dependsOn(immortal) Self { - _buffer = elements -// _pointer = .init(elements.baseAddress) -// _count = elements.count + _pointer = .init(elements.baseAddress) + _count = elements.count } @_alwaysEmitIntoClient internal init( - _unchecked start: UnsafePointer?, + _unchecked start: UnsafeRawPointer?, count: Int ) -> dependsOn(immortal) Self { - self.init(_unchecked: .init(start: start, count: count)) -// _pointer = .init(start) -// _count = count + _pointer = .init(start) + _count = count } } @@ -57,7 +48,7 @@ extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { @usableFromInline @inline(__always) var isAligned: Bool { - (Int(bitPattern: self) & (MemoryLayout.alignment-1)) == 0 + (Int(bitPattern: self) & (MemoryLayout.alignment&-1)) == 0 } } @@ -533,7 +524,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { @inlinable @inline(__always) public func extracting(unchecked bounds: Range) -> Self { Span( - _unchecked: _pointer?.advanced(by: bounds.lowerBound), + _unchecked: _pointer?.advanced(by: bounds.lowerBound*MemoryLayout.stride), count: bounds.count ) } @@ -613,7 +604,12 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result ) throws(E) -> Result { - try body(.init(start: (count==0) ? nil : _start, count: count)) + guard let pointer = _pointer, count > 0 else { + return try body(.init(start: nil, count: 0)) + } + let binding = Builtin.bindMemory(pointer._rawValue, count._builtinWordValue, Element.self) + defer { Builtin.rebindMemory(pointer._rawValue, binding) } + return try body(.init(start: .init(pointer._rawValue), count: count)) } } @@ -680,7 +676,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { if span._count > _count { return false } if _count == 0 || span._count == 0 { return true } if _start > span._start { return false } - return span._start.advanced(by: span._count) <= _start.advanced(by: _count) + return span._start.advanced(by: span._count*MemoryLayout.stride) <= _start.advanced(by: _count*MemoryLayout.stride) } /// Returns the offsets where the memory of `span` is located within @@ -696,7 +692,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { precondition(contains(span)) var (s, e) = (0, 0) if _pointer != nil && span._pointer != nil { - s = _start.distance(to: span._start) + s = _start.distance(to: span._start)/MemoryLayout.stride e = s + span._count } return Range(uncheckedBounds: (s, e)) @@ -768,7 +764,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, count) - let newStart = _pointer?.advanced(by: count&-newCount) + let newStart = _pointer?.advanced(by: (count&-newCount)*MemoryLayout.stride) return Self(_unchecked: newStart, count: newCount) } @@ -790,7 +786,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { public func extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) - let newStart = _pointer?.advanced(by: droppedCount) + let newStart = _pointer?.advanced(by: droppedCount*MemoryLayout.stride) return Self(_unchecked: newStart, count: count&-droppedCount) } } diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 8446fc01f..956ce63eb 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -331,6 +331,7 @@ final class SpanTests: XCTestCase { XCTAssertEqual(emptyBuffer.baseAddress, ub.baseAddress) let empty = Span(_unsafeElements: emptyBuffer) + XCTAssertEqual(empty.count, 0) empty.withUnsafeBufferPointer { XCTAssertNil($0.baseAddress) } From 6d0848fc7f83bfa219487c731de663f4f220f930 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 10 Sep 2024 18:03:26 -0700 Subject: [PATCH 094/195] improve standard library Span extensions --- Sources/Future/StdlibSpanExtensions.swift | 359 +++++++++++------- .../StdlibSpanExtensionTests.swift | 13 +- 2 files changed, 218 insertions(+), 154 deletions(-) diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift index e6b3dbb07..5d8b2382d 100644 --- a/Sources/Future/StdlibSpanExtensions.swift +++ b/Sources/Future/StdlibSpanExtensions.swift @@ -102,36 +102,46 @@ extension Slice { import struct Foundation.Data extension Data { - public func withSpan( + public func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result = withUnsafeBytes { - do throws(E) { - return .success(try body(Span(_unsafeBytes: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBytes { + bytes in + do throws(E) { + let result = try body(Span(_unsafeBytes: bytes)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result = withUnsafeBytes { - do throws(E) { - return .success(try body(RawSpan(_unsafeBytes: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBytes { + bytes in + do throws(E) { + let result = try body(RawSpan(_unsafeBytes: bytes)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } } #endif @@ -185,235 +195,290 @@ extension Array { } return buffer.moveElement(from: 0) } - switch consume result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } - public func withBytes( +// public func withSpan( +// _ body: (_ elements: Span) throws(E) -> Result +// ) throws(E) -> Result { +// try withUnsafeTemporaryAllocation(of: Result.self, capacity: 1) { +// buffer -> Result in +// try self.withUnsafeBufferPointer { +// let result = try body(Span(_unsafeElements: $0)) +// buffer.initializeElement(at: 0, to: result) +// } +// return buffer.moveElement(from: 0) +// } +// } + +// public func withSpan( +// _ body: (_ elements: Span) throws(E) -> Result +// ) throws(E) -> Result { +// try self.withUnsafeBufferPointer { +// try body(Span(_unsafeElements: $0)) +// } +// } + + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result ) throws(E) -> Result where Element: BitwiseCopyable { - let result: Swift.Result = withUnsafeBytes { - do throws(E) { - return .success(try body(RawSpan(_unsafeBytes: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBytes { + bytes in + do throws(E) { + let result = try body(RawSpan(_unsafeBytes: bytes)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } } extension ContiguousArray { - public func withSpan( + public func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result = withUnsafeBufferPointer { - do throws(E) { - return .success(try body(Span(_unsafeElements: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBufferPointer { + elements in + do throws(E) { + let result = try body(Span(_unsafeElements: elements)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } } extension ContiguousArray where Element: BitwiseCopyable { - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - let result: Swift.Result = withUnsafeBytes { - do throws(E) { - return .success(try body(RawSpan(_unsafeBytes: $0))) - } catch { - return .failure(error) + ) throws(E) -> Result where Element: BitwiseCopyable { + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBytes { + bytes in + do throws(E) { + let result = try body(RawSpan(_unsafeBytes: bytes)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } } extension ArraySlice { - public func withSpan( + public func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result = withUnsafeBufferPointer { - do throws(E) { - let span = Span(_unsafeElements: $0) - print(span._start, span.count) - return .success(try body(span)) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBufferPointer { + elements in + do throws(E) { + let result = try body(Span(_unsafeElements: elements)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } } extension ArraySlice where Element: BitwiseCopyable { - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - let result: Swift.Result = withUnsafeBytes { - do throws(E) { - return .success(try body(RawSpan(_unsafeBytes: $0))) - } catch { - return .failure(error) + ) throws(E) -> Result where Element: BitwiseCopyable { + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result in + self.withUnsafeBytes { + bytes in + do throws(E) { + let result = try body(RawSpan(_unsafeBytes: bytes)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return buffer.moveElement(from: 0) } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } + return try result.get() } } extension String.UTF8View { - public func withSpan( + public func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result? = withContiguousStorageIfAvailable { - do throws(E) { - return .success(try body(Span(_unsafeElements: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result? in + let ran: Void? = self.withContiguousStorageIfAvailable { + elements in + do throws(E) { + let result = try body(Span(_unsafeElements: elements)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return (ran == nil) ? nil : buffer.moveElement(from: 0) } - switch result { - case .success(let s)?: return s - case .failure(let e)?: throw e - case nil: return try ContiguousArray(self).withSpan(body) + if let result { + return try result.get() } + return try ContiguousArray(self).withSpan(body) } - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result? = withContiguousStorageIfAvailable { - do throws(E) { - return .success(try body(RawSpan(_unsafeElements: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result? in + let ran: Void? = self.withContiguousStorageIfAvailable { + do throws(E) { + let result = try body(RawSpan(_unsafeElements: $0)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return (ran == nil) ? nil : buffer.moveElement(from: 0) } - switch result { - case .success(let s)?: return s - case .failure(let e)?: throw e - case nil: return try ContiguousArray(self).withBytes(body) + if let result { + return try result.get() } + return try ContiguousArray(self).withBytes(body) } } extension Substring.UTF8View { - public func withSpan( + public func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result? = withContiguousStorageIfAvailable { - do throws(E) { - return .success(try body(Span(_unsafeElements: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result? in + let ran: Void? = self.withContiguousStorageIfAvailable { + elements in + do throws(E) { + let result = try body(Span(_unsafeElements: elements)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return (ran == nil) ? nil : buffer.moveElement(from: 0) } - switch result { - case .success(let s)?: return s - case .failure(let e)?: throw e - case nil: return try ContiguousArray(self).withSpan(body) + if let result { + return try result.get() } + return try ContiguousArray(self).withSpan(body) } - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result ) throws(E) -> Result { - let result: Swift.Result? = withContiguousStorageIfAvailable { - do throws(E) { - return .success(try body(RawSpan(_unsafeElements: $0))) - } catch { - return .failure(error) + let result = withUnsafeTemporaryAllocation( + of: Swift.Result.self, capacity: 1 + ) { + buffer -> Swift.Result? in + let ran: Void? = self.withContiguousStorageIfAvailable { + elements in + do throws(E) { + let result = try body(RawSpan(_unsafeElements: elements)) + buffer.initializeElement(at: 0, to: .success(result)) + } catch { + buffer.initializeElement(at: 0, to: .failure(error)) + } } + return (ran == nil) ? nil : buffer.moveElement(from: 0) } - switch result { - case .success(let s)?: return s - case .failure(let e)?: throw e - case nil: return try ContiguousArray(self).withBytes(body) + if let result { + return try result.get() } + return try ContiguousArray(self).withBytes(body) } } extension CollectionOfOne { - public func withSpan( + public func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { var collection = self - let result: Swift.Result = withUnsafePointer(to: &collection) { - $0.withMemoryRebound(to: Element.self, capacity: 1) { - do throws(E) { - return .success(try body(Span(_unsafeStart: $0, count: 1))) - } catch { - return .failure(error) - } + return try withUnsafePointer(to: &collection) { + pointer throws(E) -> Result in + try pointer.withMemoryRebound(to: Element.self, capacity: 1) { + element throws(E) -> Result in + try body(Span(_unsafeStart: element, count: 1)) } } - switch result { - case .success(let s): return s - case .failure(let e): throw e - } } } extension CollectionOfOne where Element: BitwiseCopyable { - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result ) throws(E) -> Result { var collection = self - let r: Swift.Result = Swift.withUnsafeBytes(of: &collection) { - do throws(E) { - return .success(try body(RawSpan(_unsafeBytes: $0))) - } catch { - return .failure(error) - } - } - switch r { - case .success(let s): return s - case .failure(let e): throw e + return try Swift.withUnsafeBytes(of: &collection) { + bytes throws(E) -> Result in + try body(RawSpan(_unsafeBytes: bytes)) } } } extension KeyValuePairs { - public func withSpan( + public func withSpan( _ body: ( _ elements: Span<(key: Key, value: Value)> ) throws(E) -> Result ) throws(E) -> Result { - try Array(self).withSpan(body) + try ContiguousArray(self).withSpan(body) } } extension KeyValuePairs where Element: BitwiseCopyable { - public func withBytes( + public func withBytes( _ body: (_ bytes: RawSpan) throws(E) -> Result ) throws(E) -> Result { - try Array(self).withBytes(body) + try ContiguousArray(self).withBytes(body) } } extension Span where Element: ~Copyable /*& ~Escapable*/ { - public consuming func withSpan( + public consuming func withSpan( _ body: (_ elements: Span) throws(E) -> Result ) throws(E) -> Result { try body(self) @@ -421,7 +486,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { } extension Span where Element: BitwiseCopyable { - public consuming func withBytes( + public consuming func withBytes( _ body: (_ elements: RawSpan) throws(E) -> Result ) throws(E) -> Result { try body(RawSpan(self)) diff --git a/Tests/FutureTests/StdlibSpanExtensionTests.swift b/Tests/FutureTests/StdlibSpanExtensionTests.swift index 446bb7cd1..4516e7251 100644 --- a/Tests/FutureTests/StdlibSpanExtensionTests.swift +++ b/Tests/FutureTests/StdlibSpanExtensionTests.swift @@ -115,20 +115,19 @@ final class StdlibSpanExtensionTests: XCTestCase { let a = (0..<7).map(String.init(_:)).prefix(upTo: 4) print(a.count) a.withSpan { -// print($0.count) print($0._indices) for i in $0._indices { print(i) let v = $0[i] _ = v -// XCTAssertEqual($0[i], String(i)) + XCTAssertEqual($0[i], String(i)) } } -// do throws(ErrorForTesting) { -// try a.withSpan { _ throws(ErrorForTesting) in throw .error } -// } catch { -// XCTAssertEqual(error, .error) -// } + do throws(ErrorForTesting) { + try a.withSpan { _ throws(ErrorForTesting) in throw .error } + } catch { + XCTAssertEqual(error, .error) + } } func testArraySliceRawSpan() throws { From 46df1037866efd73278aa0f4116a77dbce1edbd8 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 10 Sep 2024 18:03:26 -0700 Subject: [PATCH 095/195] add missing stdlib Span extension --- Sources/Future/StdlibSpanExtensions.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift index 5d8b2382d..90f499a27 100644 --- a/Sources/Future/StdlibSpanExtensions.swift +++ b/Sources/Future/StdlibSpanExtensions.swift @@ -493,4 +493,12 @@ extension Span where Element: BitwiseCopyable { } } +extension RawSpan { + public consuming func withBytes( + _ body: (_ elements: RawSpan) throws(E) -> Result + ) throws(E) -> Result { + try body(self) + } +} + //TODO: extend SIMD vectors with `withSpan` and with `withBytes`. From 91506824d01d6024768886c8afd7dee70988fcee Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 12 Sep 2024 01:24:45 -0700 Subject: [PATCH 096/195] RawSpan review feedback --- Sources/Future/RawSpan.swift | 177 +++++----------------- Sources/Future/Span.swift | 4 +- Sources/Future/StdlibSpanExtensions.swift | 2 +- Tests/FutureTests/RawSpanTests.swift | 51 ++++--- 4 files changed, 66 insertions(+), 168 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 432628332..3e8963d71 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -150,7 +150,9 @@ extension RawSpan { /// - span: An existing `Span`, which will define both this /// `RawSpan`'s lifetime and the memory it represents. @_alwaysEmitIntoClient - public init(_ span: borrowing Span) { + public init( + _unsafeSpan span: borrowing Span + ) -> dependsOn(immortal) Self { self.init( _unchecked: UnsafeRawPointer(span._start), byteCount: span.count * MemoryLayout.stride @@ -158,15 +160,6 @@ extension RawSpan { } } -extension RawSpan { - /// Returns a Boolean value indicating whether two `RawSpan` instances - /// refer to the same region in memory. - @inlinable @inline(__always) - public static func ===(_ a: Self, _ b: Self) -> Bool { - (a._pointer == b._pointer) && (a._count == b._count) - } -} - extension RawSpan { private var _address: String { @@ -186,13 +179,13 @@ extension RawSpan { /// instead of comparing `count` to zero. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public var byteCount: Int { _count } /// A Boolean value indicating whether the span is empty. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public var isEmpty: Bool { byteCount == 0 } /// The indices that are valid for subscripting the span, in ascending @@ -213,7 +206,7 @@ extension RawSpan { /// - Parameters: /// - position: a byte offset to validate /// - Returns: true if `offset` is a valid byte offset - @inlinable @inline(__always) + @_alwaysEmitIntoClient public func boundsContain(_ offset: Int) -> Bool { 0 <= offset && offset < byteCount } @@ -223,7 +216,7 @@ extension RawSpan { /// - Parameters: /// - offsets: a range of byte offsets to validate /// - Returns: true if `offsets` is a valid range of byte offsets - @inlinable @inline(__always) + @_alwaysEmitIntoClient public func boundsContain(_ offsets: Range) -> Bool { 0 <= offsets.lowerBound && offsets.upperBound <= byteCount } @@ -233,7 +226,7 @@ extension RawSpan { /// - Parameters: /// - offsets: a range of byte offsets to validate /// - Returns: true if `offsets` is a valid range of byte offsets - @inlinable @inline(__always) + @_alwaysEmitIntoClient public func boundsContain(_ offsets: ClosedRange) -> Bool { 0 <= offsets.lowerBound && offsets.upperBound < byteCount } @@ -290,7 +283,7 @@ extension RawSpan { } @_alwaysEmitIntoClient - public mutating func _shrink(unchecked bounds: Range) { + public mutating func _shrink(toUnchecked bounds: Range) { self = _extracting(unchecked: bounds) } @@ -340,7 +333,7 @@ extension RawSpan { } @_alwaysEmitIntoClient - public mutating func _shrink(unchecked bounds: some RangeExpression) { + public mutating func _shrink(toUnchecked bounds: some RangeExpression) { self = _extracting(unchecked: bounds) } @@ -428,7 +421,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. - @inlinable @inline(__always) + @_alwaysEmitIntoClient public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type ) -> T { @@ -456,7 +449,7 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. - @inlinable @inline(__always) + @_alwaysEmitIntoClient public func unsafeLoad( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { @@ -515,6 +508,12 @@ extension RawSpan { } extension RawSpan { + /// Returns a Boolean value indicating whether two `RawSpan` instances + /// refer to the same region in memory. + @_alwaysEmitIntoClient + public func isIdentical(to other: Self) -> Bool { + (self._pointer == other._pointer) && (self._count == other._count) + } /// Returns true if the memory represented by `span` is a subrange of /// the memory represented by `self` @@ -522,12 +521,13 @@ extension RawSpan { /// Parameters: /// - span: a span of the same type as `self` /// Returns: whether `span` is a subrange of `self` - @inlinable @inline(__always) - public func contains(_ span: borrowing Self) -> Bool { - if span._count > _count { return false } - if _count == 0 || span._count == 0 { return true } - if _start > span._start { return false } - return span._start.advanced(by: span._count) <= _start.advanced(by: _count) + @_alwaysEmitIntoClient + public func isWithin(_ span: borrowing Self) -> Bool { + if _count > span._count { return false } + if _count == 0 { return true } + if _start < span._start { return false } + let lower = span._start.distance(to: _start) + return lower + _count <= span._count } /// Returns the offsets where the memory of `span` is located within @@ -538,15 +538,15 @@ extension RawSpan { /// Parameters: /// - span: a subrange of `self` /// Returns: A range of offsets within `self` - @inlinable @inline(__always) - public func offsets(of span: borrowing Self) -> Range { - precondition(contains(span)) - var (s, e) = (0, 0) - if _pointer != nil && span._pointer != nil { - s = _start.distance(to: span._start) - e = s + span._count - } - return Range(uncheckedBounds: (s, e)) + @_alwaysEmitIntoClient + public func byteOffsetsWithin(_ span: borrowing Self) -> Range? { + if _count > span._count { return nil } + if _count == 0 { return Range(uncheckedBounds: (0, 0)) } + if _start < span._start { return nil } + let lower = span._start.distance(to: _start) + let upper = lower + _count + guard upper <= span._count else { return nil } + return Range(uncheckedBounds: (lower, upper)) } } @@ -661,112 +661,3 @@ extension RawSpan { self = _extracting(droppingFirst: k) } } - -/// An error indicating that out-of-bounds access was attempted -@frozen -public struct OutOfBoundsError: Error { - /// The number of elements expected - public var expected: Int - - /// The number of elements found - public var has: Int - - @inlinable - public init(expected: Int, has: Int) { - (self.expected, self.has) = (expected, has) - } -} - -extension RawSpan { - /// Parse an instance of `T`, advancing `position`. - @inlinable - public func parse( - _ position: inout Int, as t: T.Type = T.self - ) throws(OutOfBoundsError) -> T { - let length = MemoryLayout.size - guard position >= 0 else { - throw OutOfBoundsError(expected: length, has: 0) - } - let end = position &+ length - guard end <= length else { - throw OutOfBoundsError(expected: length, has: byteCount&-position) - } - return unsafeLoadUnaligned(fromUncheckedByteOffset: position, as: T.self) - } - - /// Parse `numBytes` of data, advancing `position`. - @inlinable - public func parse( - _ position: inout Int, numBytes: some FixedWidthInteger - ) throws (OutOfBoundsError) -> Self { - let length = Int(numBytes) - guard position >= 0 else { - throw OutOfBoundsError(expected: length, has: 0) - } - let end = position &+ length - guard end <= length else { - throw OutOfBoundsError(expected: length, has: byteCount&-position) - } - return _extracting(position.. - - /// The current parsing position - public var position: Int - - @inlinable - public init(_ base: RawSpan, in range: Range) { - precondition(base.boundsContain(range)) - position = 0 - self.base = base - parseRange = range - } - - @inlinable - public init(_ base: RawSpan) { - position = 0 - self.base = base - parseRange = base._byteOffsets - } - - /// Parse an instance of `T` and advance - @inlinable - public mutating func parse( - _ t: T.Type = T.self - ) throws(OutOfBoundsError) -> T { - try base.parse(&position, as: T.self) - } - - /// Parse `numBytes`and advance - @inlinable - public mutating func parse( - numBytes: some FixedWidthInteger - ) throws (OutOfBoundsError) -> RawSpan { - try base.parse(&position, numBytes: numBytes) - } - - /// The bytes that we've parsed so far - @inlinable - public var parsedBytes: RawSpan { base._extracting(.. Cursor { Cursor(self) } - - @inlinable - public func makeCursor(in range: Range) -> Cursor { - Cursor(self, in: range) - } -} diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 6044024d9..4aeaa7682 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -412,7 +412,7 @@ extension Span where Element: BitwiseCopyable { /// /// - Returns: a RawSpan over the memory represented by this span @inlinable @inline(__always) - public var rawSpan: RawSpan { RawSpan(self) } + public var rawSpan: RawSpan { RawSpan(_unsafeSpan: self) } } //MARK: integer offset subscripts @@ -634,7 +634,7 @@ extension Span where Element: BitwiseCopyable { public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { - try RawSpan(self).withUnsafeBytes(body) + try RawSpan(_unsafeSpan: self).withUnsafeBytes(body) } } diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift index 90f499a27..5e5787b91 100644 --- a/Sources/Future/StdlibSpanExtensions.swift +++ b/Sources/Future/StdlibSpanExtensions.swift @@ -489,7 +489,7 @@ extension Span where Element: BitwiseCopyable { public consuming func withBytes( _ body: (_ elements: RawSpan) throws(E) -> Result ) throws(E) -> Result { - try body(RawSpan(self)) + try body(RawSpan(_unsafeSpan: self)) } } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index e4de3d986..c97f73b8f 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -30,14 +30,14 @@ final class RawSpanTests: XCTestCase { func testInitWithSpanOfIntegers() { let capacity = 4 let a = Array(0...stride) XCTAssertFalse(span.isEmpty) } func testInitWithEmptySpanOfIntegers() { let a: [Int] = [] - let span = RawSpan(a.storage) + let span = RawSpan(_unsafeSpan: a.storage) XCTAssertTrue(span.isEmpty) } @@ -140,7 +140,7 @@ final class RawSpanTests: XCTestCase { var prefix = span prefix._shrink(to: 0..<8) var beyond = prefix - beyond._shrink(unchecked: 16..<24) + beyond._shrink(toUnchecked: 16..<24) XCTAssertEqual(beyond.byteCount, 8) XCTAssertEqual(beyond.unsafeLoad(as: UInt8.self), 16) } @@ -149,7 +149,7 @@ final class RawSpanTests: XCTestCase { func testUnsafeBytes() { let capacity = 4 let array = Array(0.. - bounds = span.offsets(of: subSpan) - XCTAssertEqual(bounds, span._byteOffsets.suffix(2)) - bounds = span.offsets(of: emptySpan) - XCTAssertEqual(bounds, span._byteOffsets.prefix(0)) - bounds = span.offsets(of: nilSpan) + var bounds: Range? + bounds = subSpan1.byteOffsetsWithin(span) + XCTAssertEqual(bounds, span._byteOffsets.prefix(6)) + bounds = subSpan2.byteOffsetsWithin(span) + XCTAssertEqual(bounds, span._byteOffsets.suffix(6)) + bounds = subSpan1.byteOffsetsWithin(subSpan2) + XCTAssertNil(bounds) + bounds = subSpan2.byteOffsetsWithin(subSpan1) + XCTAssertNil(bounds) + bounds = span.byteOffsetsWithin(subSpan2) + XCTAssertNil(bounds) + bounds = emptySpan.byteOffsetsWithin(nilSpan) XCTAssertEqual(bounds, 0..<0) - bounds = nilSpan.offsets(of: emptySpan) + bounds = nilSpan.byteOffsetsWithin(span) XCTAssertEqual(bounds, 0..<0) } } From 888318c3a1b10563edd63a910234f418b9950dbb Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 12 Sep 2024 02:41:17 -0700 Subject: [PATCH 097/195] Span review feedback --- Sources/Future/Span.swift | 253 ++++++++++++++++++++++---------------- 1 file changed, 148 insertions(+), 105 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 4aeaa7682..1e5ed2855 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -15,7 +15,7 @@ import Builtin // A Span represents a span of memory which // contains initialized instances of `Element`. @frozen -public struct Span: Copyable, ~Escapable { +public struct Span: Copyable, ~Escapable { @usableFromInline let _pointer: UnsafeRawPointer? @usableFromInline @inline(__always) @@ -23,14 +23,6 @@ public struct Span: Copyable, ~Escapable { @usableFromInline let _count: Int - @inlinable @inline(__always) - internal init( - _unchecked elements: UnsafeBufferPointer - ) -> dependsOn(immortal) Self { - _pointer = .init(elements.baseAddress) - _count = elements.count - } - @_alwaysEmitIntoClient internal init( _unchecked start: UnsafeRawPointer?, @@ -44,15 +36,22 @@ public struct Span: Copyable, ~Escapable { @available(*, unavailable) extension Span: Sendable {} -extension UnsafePointer where Pointee: ~Copyable /*& ~Escapable*/ { +extension UnsafePointer where Pointee: ~Copyable { - @usableFromInline @inline(__always) + @_alwaysEmitIntoClient var isAligned: Bool { (Int(bitPattern: self) & (MemoryLayout.alignment&-1)) == 0 } } -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { + @_alwaysEmitIntoClient + internal init( + _unchecked elements: UnsafeBufferPointer + ) -> dependsOn(immortal) Self { + _pointer = .init(elements.baseAddress) + _count = elements.count + } /// Unsafely create a `Span` over initialized memory. /// @@ -69,7 +68,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { ) -> dependsOn(immortal) Self { precondition( buffer.count == 0 || buffer.baseAddress.unsafelyUnwrapped.isAligned, - "baseAddress must be properly aligned for accessing \(Element.self)" + "baseAddress must be properly aligned to access Element" ) self.init(_unchecked: buffer) } @@ -109,7 +108,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { precondition(count >= 0, "Count must not be negative") precondition( start.isAligned, - "baseAddress must be properly aligned for accessing \(Element.self)" + "baseAddress must be properly aligned to access Element" ) self.init(_unchecked: start, count: count) } @@ -257,6 +256,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. + @_alwaysEmitIntoClient public func _elementsEqual(_ other: Self) -> Bool { guard count == other.count else { return false } if count == 0 { return true } @@ -283,7 +283,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - @inlinable + @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { guard count == other.count else { return false } if count == 0 { return true } @@ -301,7 +301,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - @inlinable + @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Sequence) -> Bool { var offset = 0 for otherElement in other { @@ -313,27 +313,20 @@ extension Span where Element: Equatable { } } -extension Span where Element: ~Copyable /*& ~Escapable*/ { - /// Returns a Boolean value indicating whether two `RawSpan` instances - /// refer to the same region in memory. - @inlinable @inline(__always) - public static func ===(_ a: Self, _ b: Self) -> Bool { - (a._pointer == b._pointer) && (a._count == b._count) - } -} +extension Span where Element: ~Copyable { -extension Span where Element: ~Copyable /*& ~Escapable*/ { - - private var _address: String { + @_alwaysEmitIntoClient + var _address: String { String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) } + @_alwaysEmitIntoClient public var description: String { "(0x\(_address), \(_count))" } } -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { /// The number of elements in the span. /// @@ -341,13 +334,13 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// instead of comparing `count` to zero. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public var count: Int { _count } /// A Boolean value indicating whether the span is empty. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public var isEmpty: Bool { _count == 0 } /// The indices that are valid for subscripting the span, in ascending @@ -356,67 +349,56 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Complexity: O(1) @_alwaysEmitIntoClient public var _indices: Range { - .init(uncheckedBounds: (0, _count)) + Range(uncheckedBounds: (0, _count)) } } //MARK: Bounds Checking -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { /// Return true if `offset` is a valid offset into this `Span` /// /// - Parameters: /// - position: an index to validate /// - Returns: true if `offset` is a valid index - @inlinable @inline(__always) - public func validateBounds(_ offset: Int) -> Bool { + @_alwaysEmitIntoClient + public func boundsContain(_ offset: Int) -> Bool { 0 <= offset && offset < count } - /// Traps if `offset` is not a valid offset into this `Span` - /// - /// - Parameters: - /// - position: an index to validate - @inlinable @inline(__always) - public func assertValidity(_ offset: Int) { - precondition( - validateBounds(offset), "Offset out of bounds" - ) - } - /// Return true if `offsets` is a valid range of offsets into this `Span` /// /// - Parameters: /// - offsets: a range of indices to validate /// - Returns: true if `offsets` is a valid range of indices - @inlinable @inline(__always) - public func validateBounds(_ offsets: Range) -> Bool { + @_alwaysEmitIntoClient + public func boundsContain(_ offsets: Range) -> Bool { 0 <= offsets.lowerBound && offsets.upperBound <= count } - /// Traps if `offsets` is not a valid range of offsets into this `Span` + /// Return true if `offsets` is a valid range of offsets into this `Span` /// /// - Parameters: /// - offsets: a range of indices to validate - @inlinable @inline(__always) - public func assertValidity(_ offsets: Range) { - precondition( - validateBounds(offsets), "Range of offsets out of bounds" - ) + /// - Returns: true if `offsets` is a valid range of indices + @_alwaysEmitIntoClient + public func boundsContain(_ offsets: ClosedRange) -> Bool { + 0 <= offsets.lowerBound && offsets.upperBound < count } } extension Span where Element: BitwiseCopyable { - /// Construct a RawSpan over the memory represented by this span - /// - /// - Returns: a RawSpan over the memory represented by this span - @inlinable @inline(__always) - public var rawSpan: RawSpan { RawSpan(_unsafeSpan: self) } +#warning("Restore this computed property once lifetime annotations exist") +// /// Construct a RawSpan over the memory represented by this span +// /// +// /// - Returns: a RawSpan over the memory represented by this span +// @_alwaysEmitIntoClient +// public var rawSpan: RawSpan { RawSpan(self) } } //MARK: integer offset subscripts -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { /// Accesses the element at the specified position in the `Span`. /// @@ -424,10 +406,10 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public subscript(_ position: Int) -> Element { _read { - assertValidity(position) + precondition(boundsContain(position)) yield self[unchecked: position] } } @@ -440,7 +422,7 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { _read { let element = UnsafeRawPointer(_start).advanced(by: position&*MemoryLayout.stride) @@ -459,10 +441,10 @@ extension Span where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public subscript(_ position: Int) -> Element { get { - assertValidity(position) + precondition(boundsContain(position)) return self[unchecked: position] } } @@ -475,7 +457,7 @@ extension Span where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @inlinable @inline(__always) + @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { get { let address = UnsafeRawPointer(_start).advanced(by: position&*MemoryLayout.stride) @@ -485,7 +467,7 @@ extension Span where Element: BitwiseCopyable { } //MARK: extracting sub-spans -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { /// Constructs a new span over the items within the supplied range of /// positions within this span. @@ -500,10 +482,15 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @inlinable @inline(__always) - public func extracting(_ bounds: Range) -> Self { - assertValidity(bounds) - return extracting(unchecked: bounds) + @_alwaysEmitIntoClient + @usableFromInline func _extracting(_ bounds: Range) -> Self { + precondition(boundsContain(bounds)) + return _extracting(unchecked: bounds) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(to bounds: Range) { + self = _extracting(bounds) } /// Constructs a new span over the items within the supplied range of @@ -521,14 +508,19 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @inlinable @inline(__always) - public func extracting(unchecked bounds: Range) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(unchecked bounds: Range) -> Self { Span( _unchecked: _pointer?.advanced(by: bounds.lowerBound*MemoryLayout.stride), count: bounds.count ) } + @_alwaysEmitIntoClient + public mutating func _shrink(toUnchecked bounds: Range) { + self = _extracting(unchecked: bounds) + } + /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -543,8 +535,13 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public func extracting(_ bounds: some RangeExpression) -> Self { - extracting(bounds.relative(to: _indices)) + @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { + _extracting(bounds.relative(to: _indices)) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(to bounds: some RangeExpression) { + self = _extracting(bounds) } /// Constructs a new span over the items within the supplied range of @@ -563,10 +560,15 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public func extracting( + @usableFromInline func _extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { - extracting(unchecked: bounds.relative(to: _indices)) + _extracting(unchecked: bounds.relative(to: _indices)) + } + + @_alwaysEmitIntoClient + public mutating func _shrink(toUnchecked bounds: some RangeExpression) { + self = _extracting(uncheckedBounds: bounds) } /// Constructs a new span over all the items of this span. @@ -579,13 +581,13 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public func extracting(_: UnboundedRange) -> Self { + @usableFromInline func _extracting(_: UnboundedRange) -> Self { self } } //MARK: withUnsafePointer, etc. -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { //FIXME: mark closure parameter as non-escaping /// Calls a closure with a pointer to the viewed contiguous storage. @@ -647,7 +649,7 @@ extension Span where Element: Copyable { /// If the span is empty, the value of this property is `nil`. /// /// - Returns: The first element in the span, or `nil` if empty - @inlinable + @_alwaysEmitIntoClient public var first: Element? { isEmpty ? nil : self[unchecked: 0] } @@ -657,13 +659,19 @@ extension Span where Element: Copyable { /// If the span is empty, the value of this property is `nil`. /// /// - Returns: The last element in the span, or `nil` if empty - @inlinable + @_alwaysEmitIntoClient public var last: Element? { isEmpty ? nil : self[unchecked: count &- 1] } } -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { + /// Returns a Boolean value indicating whether two `Span` instances + /// refer to the same region in memory. + @_alwaysEmitIntoClient + public func isIdentical(to other: Self) -> Bool { + (self._pointer == other._pointer) && (self._count == other._count) + } /// Returns true if the memory represented by `span` is a subrange of /// the memory represented by `self` @@ -671,12 +679,24 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// Parameters: /// - span: a span of the same type as `self` /// Returns: whether `span` is a subrange of `self` - @inlinable @inline(__always) - public func contains(_ span: borrowing Self) -> Bool { - if span._count > _count { return false } - if _count == 0 || span._count == 0 { return true } - if _start > span._start { return false } - return span._start.advanced(by: span._count*MemoryLayout.stride) <= _start.advanced(by: _count*MemoryLayout.stride) + @_alwaysEmitIntoClient + public func isWithin(_ span: borrowing Self) -> Bool { + if _count > span._count { return false } + if _count == 0 { return true } + if _start < span._start { return false } + let stride = MemoryLayout.stride +#warning("use isPOD once it is generalized") +// if _isPOD(Element.self) { + let byteOffset = span._start.distance(to: _start) + let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) + guard r == 0 else { return false } + return lower + _count <= span._count +// } else { +// // we have an alignment precondition, so we can omit a stride check +// let selfEnd = self._start.advanced(by: self._count*stride) +// let spanEnd = span._start.advanced(by: span._count*stride) +// return selfEnd <= spanEnd +// } } /// Returns the offsets where the memory of `span` is located within @@ -687,20 +707,23 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// Parameters: /// - span: a subrange of `self` /// Returns: A range of offsets within `self` - @inlinable @inline(__always) - public func offsets(of span: borrowing Self) -> Range { - precondition(contains(span)) - var (s, e) = (0, 0) - if _pointer != nil && span._pointer != nil { - s = _start.distance(to: span._start)/MemoryLayout.stride - e = s + span._count - } - return Range(uncheckedBounds: (s, e)) + @_alwaysEmitIntoClient + public func indicesWithin(_ span: borrowing Self) -> Range? { + if _count > span._count { return nil } + if _count == 0 { return Range(uncheckedBounds: (0, 0)) } + if _start < span._start { return nil } + let stride = MemoryLayout.stride + let byteOffset = span._start.distance(to: _start) + let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) + guard r == 0 else { return nil } + let upper = lower + _count + guard upper <= span._count else { return nil } + return Range(uncheckedBounds: (lower, upper)) } } //MARK: one-sided slicing operations -extension Span where Element: ~Copyable /*& ~Escapable*/ { +extension Span where Element: ~Copyable { /// Returns a span containing the initial elements of this span, /// up to the specified maximum length. @@ -717,13 +740,18 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - @inlinable - public func extracting(first maxLength: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, count) return Self(_unchecked: _pointer, count: newCount) } + @_alwaysEmitIntoClient + public mutating func _shrink(toFirst maxLength: Int) { + self = _extracting(first: maxLength) + } + /// Returns a span over all but the given number of trailing elements. /// /// If the number of elements to drop exceeds the number of elements in @@ -738,13 +766,18 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) - @inlinable - public func extracting(droppingLast k: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) return Self(_unchecked: _pointer, count: count&-droppedCount) } + @_alwaysEmitIntoClient + public mutating func _shrink(droppingLast k: Int) { + self = _extracting(droppingLast: k) + } + /// Returns a span containing the final elements of the span, /// up to the given maximum length. /// @@ -760,14 +793,19 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - @inlinable - public func extracting(last maxLength: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, count) let newStart = _pointer?.advanced(by: (count&-newCount)*MemoryLayout.stride) return Self(_unchecked: newStart, count: newCount) } + @_alwaysEmitIntoClient + public mutating func _shrink(toLast maxLength: Int) { + self = _extracting(last: maxLength) + } + /// Returns a span over all but the given number of initial elements. /// /// If the number of elements to drop exceeds the number of elements in @@ -782,11 +820,16 @@ extension Span where Element: ~Copyable /*& ~Escapable*/ { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) - @inlinable - public func extracting(droppingFirst k: Int) -> Self { + @_alwaysEmitIntoClient + @usableFromInline func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) let newStart = _pointer?.advanced(by: droppedCount*MemoryLayout.stride) return Self(_unchecked: newStart, count: count&-droppedCount) } + + @_alwaysEmitIntoClient + public mutating func _shrink(droppingFirst k: Int) { + self = _extracting(droppingFirst: k) + } } From bd528968fc979e1a99b4692fe6737fc3f8de32d3 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 12 Sep 2024 16:52:04 -0700 Subject: [PATCH 098/195] Span iterator draft --- Sources/Future/Span+Iterator.swift | 59 ++++++++++++++++++++++++++++++ Tests/FutureTests/SpanTests.swift | 20 ++++++++++ 2 files changed, 79 insertions(+) create mode 100644 Sources/Future/Span+Iterator.swift diff --git a/Sources/Future/Span+Iterator.swift b/Sources/Future/Span+Iterator.swift new file mode 100644 index 000000000..2ea485f07 --- /dev/null +++ b/Sources/Future/Span+Iterator.swift @@ -0,0 +1,59 @@ +//===--- SpanIterator.swift -----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +extension Span where Element: ~Copyable { + @frozen + public struct Iterator: Copyable, ~Escapable { + var curPointer: UnsafeRawPointer? + let endPointer: UnsafeRawPointer? + + init(from span: consuming Span) -> dependsOn(immortal) Self { + curPointer = span._pointer + endPointer = span._pointer?.advanced( + by: span.count*MemoryLayout.stride + ) + } + } +} + +extension Span.Iterator where Element: Copyable { + + // This is the `IteratorProtocol` requirement, except that + // Span.Iterator does not conform to `Escapable` + public mutating func next() -> Element? { + guard curPointer != endPointer, + let cur = curPointer, cur < endPointer.unsafelyUnwrapped + else { return nil } + defer { + curPointer = cur.advanced(by: MemoryLayout.stride) + } + if _isPOD(Element.self) { + return cur.loadUnaligned(as: Element.self) + } + return cur.load(as: Element.self) + } +} + +extension Span.Iterator where Element: BitwiseCopyable { + + // This is the `IteratorProtocol` requirement, except that + // Span.Iterator does not conform to `Escapable` + public mutating func next() -> Element? { + guard curPointer != endPointer, + let cur = curPointer, cur < endPointer.unsafelyUnwrapped + else { return nil } + defer { + curPointer = cur.advanced(by: MemoryLayout.stride) + } + return cur.loadUnaligned(as: Element.self) + } +} diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 956ce63eb..e3826449e 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -406,4 +406,24 @@ final class SpanTests: XCTestCase { bounds = nilSpan.offsets(of: emptySpan) XCTAssertEqual(bounds, 0..<0) } + + func testSpanIterator() { + class C { + let id: Int + init(id: Int) { self.id = id } + } + + let b = UnsafeMutableBufferPointer.allocate(capacity: 8) + _ = b.initialize(fromContentsOf: (0..<8).map(C.init(id:))) + defer { + b.deinitialize(count: 8) + b.deallocate() + } + + let span = Span(_unsafeElements: b) + var iterator = Span.Iterator(span) + while let c = iterator.next() { + print(c.id) + } + } } From 8aebaa3ea8613367f0cb64f2384d94f0331c11a7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 17 Sep 2024 17:57:05 -0700 Subject: [PATCH 099/195] improve RawSpan containment --- Sources/Future/RawSpan.swift | 16 ++++++++++------ Tests/FutureTests/RawSpanTests.swift | 11 +++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 3e8963d71..816e1ad94 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -524,9 +524,11 @@ extension RawSpan { @_alwaysEmitIntoClient public func isWithin(_ span: borrowing Self) -> Bool { if _count > span._count { return false } - if _count == 0 { return true } - if _start < span._start { return false } - let lower = span._start.distance(to: _start) + guard let start = _pointer, span._count > 0 else { + return _pointer == span._pointer + } + if start < span._start { return false } + let lower = span._start.distance(to: start) return lower + _count <= span._count } @@ -541,9 +543,11 @@ extension RawSpan { @_alwaysEmitIntoClient public func byteOffsetsWithin(_ span: borrowing Self) -> Range? { if _count > span._count { return nil } - if _count == 0 { return Range(uncheckedBounds: (0, 0)) } - if _start < span._start { return nil } - let lower = span._start.distance(to: _start) + guard let start = _pointer, span._count > 0 else { + return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil + } + if start < span._start { return nil } + let lower = span._start.distance(to: start) let upper = lower + _count guard upper <= span._count else { return nil } return Range(uncheckedBounds: (lower, upper)) diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index c97f73b8f..9bfa23d90 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -261,14 +261,14 @@ final class RawSpanTests: XCTestCase { XCTAssertTrue(subSpan.isWithin(span)) XCTAssertFalse(span.isWithin(emptySpan)) XCTAssertTrue(emptySpan.isWithin(span)) - XCTAssertTrue(span.isWithin(fakeSpan)) - XCTAssertTrue(fakeSpan.isWithin(span)) + XCTAssertFalse(span.isWithin(fakeSpan)) + XCTAssertFalse(fakeSpan.isWithin(span)) XCTAssertFalse(span.isWithin(nilSpan)) XCTAssertFalse(fakeSpan.isWithin(nilSpan)) XCTAssertFalse(nilSpan.isWithin(emptySpan)) } - func testOffsets() { + func testByteOffsetsWithin() { let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) defer { b.deallocate() } @@ -292,8 +292,11 @@ final class RawSpanTests: XCTestCase { bounds = span.byteOffsetsWithin(subSpan2) XCTAssertNil(bounds) bounds = emptySpan.byteOffsetsWithin(nilSpan) - XCTAssertEqual(bounds, 0..<0) + XCTAssertNil(bounds) bounds = nilSpan.byteOffsetsWithin(span) + XCTAssertNil(bounds) + bounds = nilSpan.byteOffsetsWithin(nilSpan) XCTAssertEqual(bounds, 0..<0) + } } From c4435876d999367df9ef9431c431d4ae44106b18 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 17 Sep 2024 17:57:38 -0700 Subject: [PATCH 100/195] fix iterator test --- Sources/Future/Span+Iterator.swift | 2 +- Tests/FutureTests/SpanTests.swift | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/Future/Span+Iterator.swift b/Sources/Future/Span+Iterator.swift index 2ea485f07..d808ff387 100644 --- a/Sources/Future/Span+Iterator.swift +++ b/Sources/Future/Span+Iterator.swift @@ -16,7 +16,7 @@ extension Span where Element: ~Copyable { var curPointer: UnsafeRawPointer? let endPointer: UnsafeRawPointer? - init(from span: consuming Span) -> dependsOn(immortal) Self { + public init(from span: consuming Span) -> dependsOn(immortal) Self { curPointer = span._pointer endPointer = span._pointer?.advanced( by: span.count*MemoryLayout.stride diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index e3826449e..0bea690c0 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -416,14 +416,16 @@ final class SpanTests: XCTestCase { let b = UnsafeMutableBufferPointer.allocate(capacity: 8) _ = b.initialize(fromContentsOf: (0..<8).map(C.init(id:))) defer { - b.deinitialize(count: 8) + b.deinitialize() b.deallocate() } let span = Span(_unsafeElements: b) - var iterator = Span.Iterator(span) + var iterator = Span.Iterator(from: span) + var i = 0 while let c = iterator.next() { - print(c.id) + XCTAssertEqual(i, c.id) + i += 1 } } } From fbabfd5d664a267567fa44651edf7a3b2af31fd5 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 17 Sep 2024 17:59:12 -0700 Subject: [PATCH 101/195] improve Span containment functions --- Sources/Future/RawSpan.swift | 31 +---- Sources/Future/Span.swift | 63 +++------ Tests/FutureTests/RawSpanTests.swift | 44 ++---- Tests/FutureTests/SpanTests.swift | 194 +++++++++++++++++---------- 4 files changed, 155 insertions(+), 177 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 816e1ad94..09ae8797a 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -515,23 +515,6 @@ extension RawSpan { (self._pointer == other._pointer) && (self._count == other._count) } - /// Returns true if the memory represented by `span` is a subrange of - /// the memory represented by `self` - /// - /// Parameters: - /// - span: a span of the same type as `self` - /// Returns: whether `span` is a subrange of `self` - @_alwaysEmitIntoClient - public func isWithin(_ span: borrowing Self) -> Bool { - if _count > span._count { return false } - guard let start = _pointer, span._count > 0 else { - return _pointer == span._pointer - } - if start < span._start { return false } - let lower = span._start.distance(to: start) - return lower + _count <= span._count - } - /// Returns the offsets where the memory of `span` is located within /// the memory represented by `self` /// @@ -541,15 +524,15 @@ extension RawSpan { /// - span: a subrange of `self` /// Returns: A range of offsets within `self` @_alwaysEmitIntoClient - public func byteOffsetsWithin(_ span: borrowing Self) -> Range? { - if _count > span._count { return nil } - guard let start = _pointer, span._count > 0 else { + public func byteOffsets(of span: borrowing Self) -> Range? { + if span._count > _count { return nil } + guard let subspanStart = span._pointer, _count > 0 else { return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil } - if start < span._start { return nil } - let lower = span._start.distance(to: start) - let upper = lower + _count - guard upper <= span._count else { return nil } + if subspanStart < _start { return nil } + let lower = _start.distance(to: subspanStart) + let upper = lower + span._count + guard upper <= _count else { return nil } return Range(uncheckedBounds: (lower, upper)) } } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 1e5ed2855..3d0bab1b2 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -389,12 +389,11 @@ extension Span where Element: ~Copyable { extension Span where Element: BitwiseCopyable { -#warning("Restore this computed property once lifetime annotations exist") -// /// Construct a RawSpan over the memory represented by this span -// /// -// /// - Returns: a RawSpan over the memory represented by this span -// @_alwaysEmitIntoClient -// public var rawSpan: RawSpan { RawSpan(self) } + /// Construct a RawSpan over the memory represented by this span + /// + /// - Returns: a RawSpan over the memory represented by this span + @_alwaysEmitIntoClient + public var _rawSpan: RawSpan { RawSpan(_unsafeSpan: self) } } //MARK: integer offset subscripts @@ -673,51 +672,25 @@ extension Span where Element: ~Copyable { (self._pointer == other._pointer) && (self._count == other._count) } - /// Returns true if the memory represented by `span` is a subrange of - /// the memory represented by `self` - /// - /// Parameters: - /// - span: a span of the same type as `self` - /// Returns: whether `span` is a subrange of `self` - @_alwaysEmitIntoClient - public func isWithin(_ span: borrowing Self) -> Bool { - if _count > span._count { return false } - if _count == 0 { return true } - if _start < span._start { return false } - let stride = MemoryLayout.stride -#warning("use isPOD once it is generalized") -// if _isPOD(Element.self) { - let byteOffset = span._start.distance(to: _start) - let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) - guard r == 0 else { return false } - return lower + _count <= span._count -// } else { -// // we have an alignment precondition, so we can omit a stride check -// let selfEnd = self._start.advanced(by: self._count*stride) -// let spanEnd = span._start.advanced(by: span._count*stride) -// return selfEnd <= spanEnd -// } - } - - /// Returns the offsets where the memory of `span` is located within - /// the memory represented by `self` - /// - /// Note: `span` must be a subrange of `self` + /// Returns the indices within `self` where the memory represented by `span` + /// is located, or `nil` if `span` is not located within `self`. /// /// Parameters: - /// - span: a subrange of `self` - /// Returns: A range of offsets within `self` + /// - span: a span that may be a subrange of `self` + /// Returns: A range of offsets within `self`, or `nil` @_alwaysEmitIntoClient - public func indicesWithin(_ span: borrowing Self) -> Range? { - if _count > span._count { return nil } - if _count == 0 { return Range(uncheckedBounds: (0, 0)) } - if _start < span._start { return nil } + public func indices(of span: borrowing Self) -> Range? { + if span._count > _count { return nil } + guard let subspanStart = span._pointer, _count > 0 else { + return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil + } + if subspanStart < _start { return nil } + let byteOffset = _start.distance(to: subspanStart) let stride = MemoryLayout.stride - let byteOffset = span._start.distance(to: _start) let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) guard r == 0 else { return nil } - let upper = lower + _count - guard upper <= span._count else { return nil } + let upper = lower + span._count + guard upper <= _count else { return nil } return Range(uncheckedBounds: (lower, upper)) } } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 9bfa23d90..984833c1c 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -243,32 +243,7 @@ final class RawSpanTests: XCTestCase { XCTAssertFalse(span.boundsContain(span.byteCount)) } - func testContainment() { - let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) - defer { b.deallocate() } - - let span = RawSpan(_unsafeBytes: b) - let subSpan = span._extracting(last: 2) - let emptySpan = span._extracting(first: 0) - let fakeSpan = RawSpan( - _unsafeStart: b.baseAddress!.advanced(by: 8), byteCount: 8 - ) - let nilSpan = RawSpan( - _unsafeBytes: UnsafeRawBufferPointer(start: nil, count: 0) - ) - - XCTAssertFalse(span.isWithin(subSpan)) - XCTAssertTrue(subSpan.isWithin(span)) - XCTAssertFalse(span.isWithin(emptySpan)) - XCTAssertTrue(emptySpan.isWithin(span)) - XCTAssertFalse(span.isWithin(fakeSpan)) - XCTAssertFalse(fakeSpan.isWithin(span)) - XCTAssertFalse(span.isWithin(nilSpan)) - XCTAssertFalse(fakeSpan.isWithin(nilSpan)) - XCTAssertFalse(nilSpan.isWithin(emptySpan)) - } - - func testByteOffsetsWithin() { + func testByteOffsetsOf() { let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 8) defer { b.deallocate() } @@ -281,22 +256,21 @@ final class RawSpanTests: XCTestCase { ) var bounds: Range? - bounds = subSpan1.byteOffsetsWithin(span) + bounds = span.byteOffsets(of: subSpan1) XCTAssertEqual(bounds, span._byteOffsets.prefix(6)) - bounds = subSpan2.byteOffsetsWithin(span) + bounds = span.byteOffsets(of: subSpan2) XCTAssertEqual(bounds, span._byteOffsets.suffix(6)) - bounds = subSpan1.byteOffsetsWithin(subSpan2) + bounds = subSpan2.byteOffsets(of: subSpan1) XCTAssertNil(bounds) - bounds = subSpan2.byteOffsetsWithin(subSpan1) + bounds = subSpan1.byteOffsets(of: subSpan2) XCTAssertNil(bounds) - bounds = span.byteOffsetsWithin(subSpan2) + bounds = subSpan2.byteOffsets(of: span) XCTAssertNil(bounds) - bounds = emptySpan.byteOffsetsWithin(nilSpan) + bounds = nilSpan.byteOffsets(of: emptySpan) XCTAssertNil(bounds) - bounds = nilSpan.byteOffsetsWithin(span) + bounds = span.byteOffsets(of: nilSpan) XCTAssertNil(bounds) - bounds = nilSpan.byteOffsetsWithin(nilSpan) + bounds = nilSpan.byteOffsets(of: nilSpan) XCTAssertEqual(bounds, 0..<0) - } } diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 0bea690c0..a30b107f6 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Future +@testable import Future final class SpanTests: XCTestCase { @@ -113,7 +113,7 @@ final class SpanTests: XCTestCase { let array = Array(0...stride) } } @@ -144,10 +144,10 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(_unsafeElements: $0) - XCTAssertEqual(span._elementsEqual(span.extracting(first: 1)), false) - XCTAssertEqual(span.extracting(0..<0)._elementsEqual(span.extracting(last: 0)), true) + XCTAssertEqual(span._elementsEqual(span._extracting(first: 1)), false) + XCTAssertEqual(span._extracting(0..<0)._elementsEqual(span._extracting(last: 0)), true) XCTAssertEqual(span._elementsEqual(span), true) - XCTAssertEqual(span.extracting(0..<3)._elementsEqual(span.extracting(last: 3)), false) + XCTAssertEqual(span._extracting(0..<3)._elementsEqual(span._extracting(last: 3)), false) let copy = span.withUnsafeBufferPointer(Array.init) copy.withUnsafeBufferPointer { @@ -164,7 +164,7 @@ final class SpanTests: XCTestCase { let span = Span(_unsafeElements: $0) XCTAssertEqual(span._elementsEqual(a), true) - XCTAssertEqual(span.extracting(0..<0)._elementsEqual([]), true) + XCTAssertEqual(span._extracting(0..<0)._elementsEqual([]), true) XCTAssertEqual(span._elementsEqual(a.dropLast()), false) } } @@ -177,7 +177,7 @@ final class SpanTests: XCTestCase { let s = AnySequence(a) XCTAssertEqual(span._elementsEqual(s), true) - XCTAssertEqual(span.extracting(0..<(capacity-1))._elementsEqual(s), false) + XCTAssertEqual(span._extracting(0..<(capacity-1))._elementsEqual(s), false) XCTAssertEqual(span._elementsEqual(s.dropFirst()), false) } } @@ -187,10 +187,10 @@ final class SpanTests: XCTestCase { let a = (0..(start: nil, count: 0) let span = Span(_unsafeElements: b) XCTAssertEqual(span.count, b.count) - XCTAssertEqual(span.extracting(first: 1).count, b.count) - XCTAssertEqual(span.extracting(droppingLast: 1).count, b.count) + XCTAssertEqual(span._extracting(first: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingLast: 1).count, b.count) } } @@ -276,19 +276,19 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(_unsafeElements: $0) XCTAssertEqual(span.count, capacity) - XCTAssertEqual(span.extracting(last: capacity).first, 0) - XCTAssertEqual(span.extracting(last: capacity-1).first, 1) - XCTAssertEqual(span.extracting(last: 1).first, capacity-1) - XCTAssertEqual(span.extracting(droppingFirst: capacity).first, nil) - XCTAssertEqual(span.extracting(droppingFirst: 1).first, 1) + XCTAssertEqual(span._extracting(last: capacity).first, 0) + XCTAssertEqual(span._extracting(last: capacity-1).first, 1) + XCTAssertEqual(span._extracting(last: 1).first, capacity-1) + XCTAssertEqual(span._extracting(droppingFirst: capacity).first, nil) + XCTAssertEqual(span._extracting(droppingFirst: 1).first, 1) } do { let b = UnsafeBufferPointer(start: nil, count: 0) let span = Span(_unsafeElements: b) XCTAssertEqual(span.count, b.count) - XCTAssertEqual(span.extracting(last: 1).count, b.count) - XCTAssertEqual(span.extracting(droppingFirst: 1).count, b.count) + XCTAssertEqual(span._extracting(last: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingFirst: 1).count, b.count) } } public func testWithUnsafeBytes() { @@ -342,8 +342,8 @@ final class SpanTests: XCTestCase { let capacity = 8 let a = Array(0...allocate(capacity: 8) - _ = b.initialize(fromContentsOf: 0..<8) - defer { b.deallocate() } - - let span = Span(_unsafeElements: b) - let subSpan = span.extracting(last: 2) - let emptySpan = span.extracting(first: 0) - let fakeSpan = Span( - _unsafeStart: b.baseAddress!.advanced(by: 8), count: 8 - ) - let nilSpan = Span( - _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) - ) - - XCTAssertTrue(span.contains(subSpan)) - XCTAssertFalse(subSpan.contains(span)) - XCTAssertTrue(span.contains(emptySpan)) - XCTAssertFalse(emptySpan.contains(span)) - XCTAssertFalse(span.contains(fakeSpan)) - XCTAssertFalse(fakeSpan.contains(span)) - XCTAssertTrue(span.contains(nilSpan)) - XCTAssertTrue(fakeSpan.contains(nilSpan)) - XCTAssertTrue(nilSpan.contains(emptySpan)) - } - - func testOffsets() { +// func testContainment() { +// let b = UnsafeMutableBufferPointer.allocate(capacity: 8) +// _ = b.initialize(fromContentsOf: 0..<8) +// defer { b.deallocate() } +// +// let span = Span(_unsafeElements: b) +// let subSpan = span._extracting(last: 2) +// let emptySpan = span._extracting(first: 0) +// let fakeSpan = Span( +// _unsafeStart: b.baseAddress!.advanced(by: 8), count: 8 +// ) +// let nilSpan = Span( +// _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) +// ) +// let unalignedSpan = Span( +// _unsafeStart: UnsafeRawPointer(b.baseAddress!).advanced(by: 2).assumingMemoryBound(to: Int.self), +// count: 2 +// ) +// +// XCTAssertTrue(subSpan.isWithin(span))//.contains(subSpan)) +// XCTAssertFalse(span.isWithin(subSpan))//.contains(span)) +// XCTAssertTrue(emptySpan.isWithin(span))//.contains(emptySpan)) +// XCTAssertFalse(span.isWithin(emptySpan))//.contains(span)) +// XCTAssertFalse(fakeSpan.isWithin(span))//.contains(fakeSpan)) +// XCTAssertFalse(span.isWithin(fakeSpan))//.contains(span)) +// XCTAssertFalse(nilSpan.isWithin(span))//.contains(nilSpan)) +// XCTAssertFalse(nilSpan.isWithin(fakeSpan))//.contains(nilSpan)) +// XCTAssertTrue(nilSpan.isWithin(nilSpan)) +// XCTAssertFalse(emptySpan.isWithin(nilSpan))//.contains(emptySpan)) +// XCTAssertFalse(unalignedSpan.isWithin(span)) +// } + +// func testIndicesWithin() { +// let b = UnsafeMutableBufferPointer.allocate(capacity: 8) +// _ = b.initialize(fromContentsOf: 0..<8) +// defer { b.deallocate() } +// +// let span = Span(_unsafeElements: b) +// let subSpan = span._extracting(last: 2) +// let emptySpan = span._extracting(first: 0) +// let nilSpan = Span( +// _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) +// ) +// let unalignedSpan = Span( +// _unsafeStart: UnsafeRawPointer(b.baseAddress!).advanced(by: 2).assumingMemoryBound(to: Int.self), +// count: 2 +// ) +// +// var bounds: Range? +// bounds = subSpan.indicesWithin(span) +// XCTAssertEqual(bounds, span._indices.suffix(2)) +// bounds = span.offsets(of: emptySpan) +// XCTAssertEqual(bounds, span._indices.prefix(0)) +// bounds = span.offsets(of: nilSpan) +// XCTAssertEqual(bounds, 0..<0) +// bounds = nilSpan.offsets(of: emptySpan) +// XCTAssertEqual(bounds, 0..<0) +// } + + func testIndicesOf() { let b = UnsafeMutableBufferPointer.allocate(capacity: 8) _ = b.initialize(fromContentsOf: 0..<8) defer { b.deallocate() } let span = Span(_unsafeElements: b) - let subSpan = span.extracting(last: 2) - let emptySpan = span.extracting(first: 0) + let subSpan1 = span._extracting(first: 6) + let subSpan2 = span._extracting(last: 6) + let emptySpan = span._extracting(first: 0) + let unalignedSpan = RawSpan(_unsafeSpan: span) + ._extracting(droppingFirst: 6) + ._extracting(droppingLast: 2) + .unsafeView(as: Int.self) let nilSpan = Span( _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) ) - var bounds: Range - bounds = span.offsets(of: subSpan) - XCTAssertEqual(bounds, span._indices.suffix(2)) - bounds = span.offsets(of: emptySpan) - XCTAssertEqual(bounds, span._indices.prefix(0)) - bounds = span.offsets(of: nilSpan) - XCTAssertEqual(bounds, 0..<0) - bounds = nilSpan.offsets(of: emptySpan) + var bounds: Range? + bounds = span.indices(of: subSpan1) + XCTAssertEqual(bounds, span._indices.prefix(6)) + bounds = span.indices(of: subSpan2) + XCTAssertEqual(bounds, span._indices.suffix(6)) + bounds = subSpan2.indices(of: subSpan1) + XCTAssertNil(bounds) + bounds = subSpan1.indices(of: subSpan2) + XCTAssertNil(bounds) + bounds = subSpan2.indices(of: span) + XCTAssertNil(bounds) + bounds = nilSpan.indices(of: emptySpan) + XCTAssertNil(bounds) + bounds = span.indices(of: nilSpan) + XCTAssertNil(bounds) + bounds = span.indices(of: unalignedSpan) + XCTAssertNil(bounds) + bounds = nilSpan.indices(of: nilSpan) XCTAssertEqual(bounds, 0..<0) } From f18b91b719d37649b0f4fb228ffe6bb3fd48698a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 06:10:38 -0700 Subject: [PATCH 102/195] fix a bounds-checking detail --- Sources/Future/RawSpan.swift | 4 ++-- Sources/Future/Span.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 09ae8797a..56078e261 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -218,7 +218,7 @@ extension RawSpan { /// - Returns: true if `offsets` is a valid range of byte offsets @_alwaysEmitIntoClient public func boundsContain(_ offsets: Range) -> Bool { - 0 <= offsets.lowerBound && offsets.upperBound <= byteCount + boundsContain(offsets.lowerBound) && offsets.upperBound <= byteCount } /// Return true if `offsets` is a valid range of offsets into this `RawSpan` @@ -228,7 +228,7 @@ extension RawSpan { /// - Returns: true if `offsets` is a valid range of byte offsets @_alwaysEmitIntoClient public func boundsContain(_ offsets: ClosedRange) -> Bool { - 0 <= offsets.lowerBound && offsets.upperBound < byteCount + boundsContain(offsets.lowerBound) && offsets.upperBound < byteCount } } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 3d0bab1b2..998fa64ed 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -373,7 +373,7 @@ extension Span where Element: ~Copyable { /// - Returns: true if `offsets` is a valid range of indices @_alwaysEmitIntoClient public func boundsContain(_ offsets: Range) -> Bool { - 0 <= offsets.lowerBound && offsets.upperBound <= count + boundsContain(offsets.lowerBound) && offsets.upperBound <= count } /// Return true if `offsets` is a valid range of offsets into this `Span` @@ -383,7 +383,7 @@ extension Span where Element: ~Copyable { /// - Returns: true if `offsets` is a valid range of indices @_alwaysEmitIntoClient public func boundsContain(_ offsets: ClosedRange) -> Bool { - 0 <= offsets.lowerBound && offsets.upperBound < count + boundsContain(offsets.lowerBound) && offsets.upperBound < count } } From d1bc334b8cda12ae04df37a2de55423a6e29a58f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 27 Sep 2024 19:05:02 -0700 Subject: [PATCH 103/195] add OutputSpan --- Sources/Future/OutputSpan.swift | 293 ++++++++++++++++++++++++ Tests/FutureTests/OutputSpanTests.swift | 253 ++++++++++++++++++++ 2 files changed, 546 insertions(+) create mode 100644 Sources/Future/OutputSpan.swift create mode 100644 Tests/FutureTests/OutputSpanTests.swift diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift new file mode 100644 index 000000000..95101fd51 --- /dev/null +++ b/Sources/Future/OutputSpan.swift @@ -0,0 +1,293 @@ +//===--- OutputSpan.swift -------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +public struct OutputSpan: ~Copyable, ~Escapable { + @usableFromInline let _start: UnsafeMutablePointer + public let capacity: Int + public private(set) var initialized: Int = 0 + + deinit { + // `self` always borrows memory, and it shouldn't have gotten here. + // Failing to use `relinquishBorrowedMemory()` is an error. + if initialized > 0 { + fatalError() + } + } + + public init( + initializing pointer: UnsafeMutablePointer, + capacity: Int, + initialized: Int = 0, + owner: borrowing Owner + ) -> dependsOn(owner) Self { + self._start = pointer + self.capacity = capacity + self.initialized = initialized + } +} + +extension OutputSpan where Element: ~Copyable /*& ~Escapable*/ { + public mutating func appendElement(_ value: consuming Element) { + precondition(initialized < capacity, "Output buffer overflow") + _start.advanced(by: initialized).initialize(to: value) + initialized &+= 1 + } + + public mutating func deinitializeLastElement() -> Element? { + guard initialized > 0 else { return nil } + initialized &-= 1 + return _start.advanced(by: initialized).move() + } +} + +extension OutputSpan where Element: ~Copyable { + public mutating func deinitialize() { + let b = UnsafeMutableBufferPointer(start: _start, count: initialized) + b.deinitialize() + initialized = 0 + } +} + +extension OutputSpan { + public mutating func append( + from elements: S + ) -> S.Iterator where S: Sequence, S.Element == Element { + var iterator = elements.makeIterator() + append(from: &iterator) + return iterator + } + + public mutating func append( + from elements: inout some IteratorProtocol + ) { + while initialized < capacity { + guard let element = elements.next() else { break } + _start.advanced(by: initialized).initialize(to: element) + initialized &+= 1 + } + } + + public mutating func append( + fromContentsOf source: some Collection + ) { + let count = source.withContiguousStorageIfAvailable { + guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { + return 0 + } + let available = capacity &- initialized + precondition( + $0.count <= available, + "buffer cannot contain every element from source." + ) + let tail = _start.advanced(by: initialized) + tail.initialize(from: sourceAddress, count: $0.count) + return $0.count + } + if let count { + initialized &+= count + return + } + + let available = capacity &- initialized + let tail = _start.advanced(by: initialized) + let suffix = UnsafeMutableBufferPointer(start: tail, count: available) + var (iterator, copied) = source._copyContents(initializing: suffix) + precondition( + iterator.next() == nil, + "buffer cannot contain every element from source." + ) + assert(initialized + copied <= capacity) + initialized &+= copied + } + + //FIXME: rdar://136838539 & rdar://136849171 + public mutating func append(fromContentsOf source: Span) { + let available = capacity &- initialized + precondition( + source.count <= available, + "buffer cannot contain every element from source." + ) + source.withUnsafeBufferPointer { + let tail = _start.advanced(by: initialized) + tail.initialize(from: $0.baseAddress!, count: $0.count) + } + initialized &+= source.count + } +} + +extension OutputSpan where Element: ~Copyable /*& ~Escapable*/ { + + public mutating func moveAppend( + fromContentsOf source: UnsafeMutableBufferPointer + ) { + guard let sourceAddress = source.baseAddress, !source.isEmpty else { + return + } + let available = capacity &- initialized + precondition( + source.count <= available, + "buffer cannot contain every element from source." + ) + let tail = _start.advanced(by: initialized) + tail.moveInitialize(from: sourceAddress, count: source.count) + initialized &+= source.count + } +} + +extension OutputSpan { + + public mutating func moveAppend( + fromContentsOf source: Slice> + ) { + moveAppend(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) + } +} + +//extension OutputSpan where Element: ~Copyable /*& ~Escapable*/ { +// public mutating func initializeSuffixOutOfOrder( +// _ count: Int, +// body: (inout RandomAccessOutputSpan) throws(E) -> R +// ) throws(E) -> R { +// precondition(initialized + count < capacity) +// var out = RandomAccessOutputSpan( +// initializing: _start.advanced(by: initialized), capacity: count, owner: self +// ) +// let result = try body(&out) +// let buffer = out.relinquishBorrowedMemory() +// assert( +// buffer.baseAddress == _start.advanced(by: initialized) && +// initialized + buffer.count < capacity +// ) +// initialized &+= buffer.count +// return result +// } +//} + +extension OutputSpan { + public var initializedPrefix: Span { + get { Span(_unsafeStart: _start, count: initialized) } + } + + public func withSpan( + _ body: (Span) throws(E) -> R + ) throws(E) -> R { + try body(initializedPrefix) + } +} + +// public var mutatingInitializedPrefix: /*inout*/ MutableBufferView { +// mutating /* _read */ get /* mutating(self) */ { +// /* yield */ MutableBufferView( +// unsafeMutablePointer: _start, +// count: initialized, +// dependsOn: /* self */ _start +// ) +// } +// } + +// public mutating func withMutableBufferView( +// _ body: (inout MutableBufferView) throws -> R +// ) rethrows -> R { +// var view = MutableBufferView( +// unsafeMutablePointer: _start, +// count: initialized, +// dependsOn: /* self */ _start +// ) +// return try body(&view) +// } + +extension OutputSpan { + + public consuming func relinquishBorrowedMemory() -> UnsafeMutableBufferPointer { + let (start, initialized) = (self._start, self.initialized) + discard self + return .init(start: start, count: initialized) + } +} + +extension Array { + + public init( + capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + try self.init( + unsafeUninitializedCapacity: capacity, + initializingWith: { (buffer, count) in + var output = OutputSpan( + initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: buffer.count, + owner: buffer + ) + try initializer(&output) + let initialized = output.relinquishBorrowedMemory() + assert(initialized.baseAddress == buffer.baseAddress) + count = initialized.count + } + ) + } +} + +extension String { + + // also see https://github.com/apple/swift/pull/23050 + // and `final class __SharedStringStorage` + + @available(macOS 11, *) + public init( + utf8Capacity capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + try self.init( + unsafeUninitializedCapacity: capacity, + initializingUTF8With: { buffer in + var output = OutputSpan( + initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: capacity, + owner: buffer + ) + try initializer(&output) + let initialized = output.relinquishBorrowedMemory() + assert(initialized.baseAddress == buffer.baseAddress) + return initialized.count + } + ) + } +} + +import Foundation + +extension Data { + + public init( + capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + self = Data(count: capacity) // initialized with zeroed buffer + let count = try self.withUnsafeMutableBytes { rawBuffer in + try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in + buffer.deinitialize() + var output = OutputSpan( + initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: capacity, + owner: buffer + ) + try initializer(&output) + let initialized = output.relinquishBorrowedMemory() + assert(initialized.baseAddress == buffer.baseAddress) + return initialized.count + } + } + assert(count <= self.count) + self.replaceSubrange(count..: ~Copyable { + let allocation: UnsafeMutablePointer + let capacity: Int + var count: Int? = nil + + init(of count: Int = 1, _ t: T.Type) { + precondition(count >= 0) + capacity = count + allocation = UnsafeMutablePointer.allocate(capacity: capacity) + } + + var isInitialized: Bool { count != nil } + + mutating func initialize( + _ body: (/* mutating */ inout OutputSpan) throws -> Void + ) rethrows { + if count != nil { fatalError() } + var outputBuffer = OutputSpan( + initializing: allocation, capacity: capacity, owner: self + ) + do { + try body(&outputBuffer) + let initialized = outputBuffer.relinquishBorrowedMemory() + assert(initialized.baseAddress == allocation) + count = initialized.count + } + catch { + outputBuffer.deinitialize() + let empty = outputBuffer.relinquishBorrowedMemory() + assert(empty.baseAddress == allocation) + assert(empty.count == 0) + throw error + } + } + + borrowing func examine( + _ body: (borrowing Span) throws(E) -> R + ) throws(E) -> R { + try body(initializedPrefix) + } + + var initializedPrefix: Span { + Span(_unsafeStart: allocation, count: count ?? 0) + } + +// mutating func mutate( +// _ body: (/* mutating */ inout MutableBufferView) -> R +// ) -> R { +// guard let count else { fatalError() } +// var mutable = MutableBufferView( +// baseAddress: allocation, count: count, dependsOn: /*self*/ allocation +// ) +// return body(&mutable) +// } + + deinit { + if let count { + allocation.deinitialize(count: count) + } + allocation.deallocate() + } +} + +enum MyTestError: Error { case error } + +final class OutputBufferTests: XCTestCase { + + func testOutputBufferInitialization() { + let c = 48 + let allocation = UnsafeMutablePointer.allocate(capacity: c) + defer { allocation.deallocate() } + + let ob = OutputSpan(initializing: allocation, capacity: c, owner: allocation) + let initialized = ob.relinquishBorrowedMemory() + XCTAssertNotNil(initialized.baseAddress) + XCTAssertEqual(initialized.count, 0) + } + + func testInitializeBufferByAppendingElements() { + var a = Allocation(of: 48, Int.self) + let c = 10 + a.initialize { + for i in 0...c { + $0.appendElement(i) + } + let oops = $0.deinitializeLastElement() + XCTAssertEqual(oops, c) + } + a.examine { + XCTAssertEqual($0.count, c) + XCTAssert($0._elementsEqual(0..(_unsafeElements: UnsafeBufferPointer(start: nil, count: 0)) + $0.append(fromContentsOf: storage) + #else + $0.append(fromContentsOf: array) + #endif + } + + let span = a.initializedPrefix + XCTAssertEqual(span.count, c) + XCTAssert(span._elementsEqual(0...allocate(capacity: c) + for i in 0.. 0) + throw MyTestError.error + } + } + catch MyTestError.error { + XCTAssertEqual(a.isInitialized, false) + } + } +} + +final class OutputBufferUsageTests: XCTestCase { + + func testArrayInitializationExample() { + var array: [UInt8] + array = Array(capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.appendElement(UInt8(clamping: i)) + } + }) + XCTAssertEqual(array.count, 16) + XCTAssert(array.elementsEqual(0..<16)) + XCTAssertGreaterThanOrEqual(array.capacity, 32) + } + + func testDataInitializationExample() { + var data: Data + data = Data(capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.appendElement(UInt8(clamping: i)) + } + }) + XCTAssertEqual(data.count, 16) + XCTAssert(data.elementsEqual(0..<16)) + } + + func testStringInitializationExample() { + var string: String + let c = UInt8(ascii: "A") + string = String(utf8Capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.appendElement(c + UInt8(clamping: i)) + } + }) + XCTAssertEqual(string.utf8.count, 16) + XCTAssert(string.utf8.elementsEqual(c..<(c+16))) + } +} From d374c35bdfce9d3be93d172b0c3115115bf7528a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 06:07:42 -0700 Subject: [PATCH 104/195] move OutputSpan-providing extensions to their own file - and their tests, too --- Sources/Future/OutputSpan.swift | 78 ---------------- .../Future/StdlibOutputSpanExtensions.swift | 89 +++++++++++++++++++ Tests/FutureTests/OutputSpanTests.swift | 39 -------- .../StdlibOutputSpanExtensionTests.swift | 53 +++++++++++ 4 files changed, 142 insertions(+), 117 deletions(-) create mode 100644 Sources/Future/StdlibOutputSpanExtensions.swift create mode 100644 Tests/FutureTests/StdlibOutputSpanExtensionTests.swift diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift index 95101fd51..83a8e5869 100644 --- a/Sources/Future/OutputSpan.swift +++ b/Sources/Future/OutputSpan.swift @@ -213,81 +213,3 @@ extension OutputSpan { return .init(start: start, count: initialized) } } - -extension Array { - - public init( - capacity: Int, - initializingWith initializer: (inout OutputSpan) throws -> Void - ) rethrows { - try self.init( - unsafeUninitializedCapacity: capacity, - initializingWith: { (buffer, count) in - var output = OutputSpan( - initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: buffer.count, - owner: buffer - ) - try initializer(&output) - let initialized = output.relinquishBorrowedMemory() - assert(initialized.baseAddress == buffer.baseAddress) - count = initialized.count - } - ) - } -} - -extension String { - - // also see https://github.com/apple/swift/pull/23050 - // and `final class __SharedStringStorage` - - @available(macOS 11, *) - public init( - utf8Capacity capacity: Int, - initializingWith initializer: (inout OutputSpan) throws -> Void - ) rethrows { - try self.init( - unsafeUninitializedCapacity: capacity, - initializingUTF8With: { buffer in - var output = OutputSpan( - initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: capacity, - owner: buffer - ) - try initializer(&output) - let initialized = output.relinquishBorrowedMemory() - assert(initialized.baseAddress == buffer.baseAddress) - return initialized.count - } - ) - } -} - -import Foundation - -extension Data { - - public init( - capacity: Int, - initializingWith initializer: (inout OutputSpan) throws -> Void - ) rethrows { - self = Data(count: capacity) // initialized with zeroed buffer - let count = try self.withUnsafeMutableBytes { rawBuffer in - try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in - buffer.deinitialize() - var output = OutputSpan( - initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: capacity, - owner: buffer - ) - try initializer(&output) - let initialized = output.relinquishBorrowedMemory() - assert(initialized.baseAddress == buffer.baseAddress) - return initialized.count - } - } - assert(count <= self.count) - self.replaceSubrange(count..) throws -> Void + ) rethrows { + try self.init( + unsafeUninitializedCapacity: capacity, + initializingWith: { (buffer, count) in + var output = OutputSpan( + initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: buffer.count, + owner: buffer + ) + try initializer(&output) + let initialized = output.relinquishBorrowedMemory() + assert(initialized.baseAddress == buffer.baseAddress) + count = initialized.count + } + ) + } +} + +extension String { + + // also see https://github.com/apple/swift/pull/23050 + // and `final class __SharedStringStorage` + + @available(macOS 11, *) + public init( + utf8Capacity capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + try self.init( + unsafeUninitializedCapacity: capacity, + initializingUTF8With: { buffer in + var output = OutputSpan( + initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: capacity, + owner: buffer + ) + try initializer(&output) + let initialized = output.relinquishBorrowedMemory() + assert(initialized.baseAddress == buffer.baseAddress) + return initialized.count + } + ) + } +} + +import Foundation + +extension Data { + + public init( + capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + self = Data(count: capacity) // initialized with zeroed buffer + let count = try self.withUnsafeMutableBytes { rawBuffer in + try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in + buffer.deinitialize() + var output = OutputSpan( + initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: capacity, + owner: buffer + ) + try initializer(&output) + let initialized = output.relinquishBorrowedMemory() + assert(initialized.baseAddress == buffer.baseAddress) + return initialized.count + } + } + assert(count <= self.count) + self.replaceSubrange(count..: ~Copyable { @@ -213,41 +212,3 @@ final class OutputBufferTests: XCTestCase { } } } - -final class OutputBufferUsageTests: XCTestCase { - - func testArrayInitializationExample() { - var array: [UInt8] - array = Array(capacity: 32, initializingWith: { output in - for i in 0..<(output.capacity/2) { - output.appendElement(UInt8(clamping: i)) - } - }) - XCTAssertEqual(array.count, 16) - XCTAssert(array.elementsEqual(0..<16)) - XCTAssertGreaterThanOrEqual(array.capacity, 32) - } - - func testDataInitializationExample() { - var data: Data - data = Data(capacity: 32, initializingWith: { output in - for i in 0..<(output.capacity/2) { - output.appendElement(UInt8(clamping: i)) - } - }) - XCTAssertEqual(data.count, 16) - XCTAssert(data.elementsEqual(0..<16)) - } - - func testStringInitializationExample() { - var string: String - let c = UInt8(ascii: "A") - string = String(utf8Capacity: 32, initializingWith: { output in - for i in 0..<(output.capacity/2) { - output.appendElement(c + UInt8(clamping: i)) - } - }) - XCTAssertEqual(string.utf8.count, 16) - XCTAssert(string.utf8.elementsEqual(c..<(c+16))) - } -} diff --git a/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift b/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift new file mode 100644 index 000000000..a3c6847d3 --- /dev/null +++ b/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift @@ -0,0 +1,53 @@ +//===--- StdlibOutputSpanExtensionTests.swift -----------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Foundation +import Future + +final class OutputBufferUsageTests: XCTestCase { + + func testArrayInitializationExample() { + var array: [UInt8] + array = Array(capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.appendElement(UInt8(clamping: i)) + } + }) + XCTAssertEqual(array.count, 16) + XCTAssert(array.elementsEqual(0..<16)) + XCTAssertGreaterThanOrEqual(array.capacity, 32) + } + + func testDataInitializationExample() { + var data: Data + data = Data(capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.appendElement(UInt8(clamping: i)) + } + }) + XCTAssertEqual(data.count, 16) + XCTAssert(data.elementsEqual(0..<16)) + } + + func testStringInitializationExample() { + var string: String + let c = UInt8(ascii: "A") + string = String(utf8Capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.appendElement(c + UInt8(clamping: i)) + } + }) + XCTAssertEqual(string.utf8.count, 16) + XCTAssert(string.utf8.elementsEqual(c..<(c+16))) + } +} From 98bf179defcd05a5e5cd0ae425f7c167a69646ab Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 06:17:34 -0700 Subject: [PATCH 105/195] update CMakeLists.txt --- Sources/Future/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 6ade7dbd6..f9ef82e02 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -12,9 +12,13 @@ add_library(Future "Cell.swift" "ContiguousStorage.swift" "Inout.swift" + "OutputSpan.swift" "LifetimeOverride.swift" "RawSpan.swift" "Span.swift" + "Span+Iterator.swift" + "StdlibOutputSpanExtensions.swift" + "StdlibSpanExtensions.swift" "Containers/Shared.swift" "Containers/Container.swift" "Containers/DynamicArray.swift" From 6ae687fc318355901f1c66b8bc715f131ee3376d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 14:32:49 -0700 Subject: [PATCH 106/195] update span to match stdlib prototype implementation --- Package.swift | 1 + Sources/Future/Span.swift | 159 +++++++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 62 deletions(-) diff --git a/Package.swift b/Package.swift index 18ec08df3..ccd8a07a3 100644 --- a/Package.swift +++ b/Package.swift @@ -53,6 +53,7 @@ var defines: [String] = [ ] let _sharedSettings: [SwiftSetting] = defines.map { .define($0) } + [ + .enableExperimentalFeature("AllowUnsafeAttribute"), .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("NonescapableTypes"), .enableExperimentalFeature("RawLayout"), diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 998fa64ed..9942905a7 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -14,6 +14,7 @@ import Builtin // A Span represents a span of memory which // contains initialized instances of `Element`. +@_disallowFeatureSuppression(NonescapableTypes) @frozen public struct Span: Copyable, ~Escapable { @usableFromInline let _pointer: UnsafeRawPointer? @@ -23,29 +24,26 @@ public struct Span: Copyable, ~Escapable { @usableFromInline let _count: Int - @_alwaysEmitIntoClient - internal init( + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) + init( _unchecked start: UnsafeRawPointer?, count: Int ) -> dependsOn(immortal) Self { - _pointer = .init(start) + _pointer = start _count = count } } +@_disallowFeatureSuppression(NonescapableTypes) @available(*, unavailable) extension Span: Sendable {} -extension UnsafePointer where Pointee: ~Copyable { - - @_alwaysEmitIntoClient - var isAligned: Bool { - (Int(bitPattern: self) & (MemoryLayout.alignment&-1)) == 0 - } -} - +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { - @_alwaysEmitIntoClient + + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) internal init( _unchecked elements: UnsafeBufferPointer ) -> dependsOn(immortal) Self { @@ -62,12 +60,14 @@ extension Span where Element: ~Copyable { /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeElements buffer: UnsafeBufferPointer ) -> dependsOn(immortal) Self { precondition( - buffer.count == 0 || buffer.baseAddress.unsafelyUnwrapped.isAligned, + ((Int(bitPattern: buffer.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), "baseAddress must be properly aligned to access Element" ) self.init(_unchecked: buffer) @@ -82,6 +82,7 @@ extension Span where Element: ~Copyable { /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeElements buffer: UnsafeMutableBufferPointer @@ -100,20 +101,18 @@ extension Span where Element: ~Copyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeStart start: UnsafePointer, count: Int ) -> dependsOn(immortal) Self { precondition(count >= 0, "Count must not be negative") - precondition( - start.isAligned, - "baseAddress must be properly aligned to access Element" - ) - self.init(_unchecked: start, count: count) + self.init(_unsafeElements: .init(start: start, count: count)) } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { /// Unsafely create a `Span` over initialized memory. @@ -125,6 +124,7 @@ extension Span where Element: BitwiseCopyable { /// - buffer: an `UnsafeBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeElements buffer: UnsafeBufferPointer @@ -141,6 +141,7 @@ extension Span where Element: BitwiseCopyable { /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeElements buffer: UnsafeMutableBufferPointer @@ -159,6 +160,7 @@ extension Span where Element: BitwiseCopyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeStart start: UnsafePointer, @@ -182,6 +184,7 @@ extension Span where Element: BitwiseCopyable { /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeBytes buffer: UnsafeRawBufferPointer @@ -190,10 +193,8 @@ extension Span where Element: BitwiseCopyable { precondition(byteCount >= 0, "Count must not be negative") let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0) - self.init( - _unchecked: buffer.baseAddress?.assumingMemoryBound(to: Element.self), - count: count - ) + _pointer = buffer.baseAddress + _count = count } /// Unsafely create a `Span` over initialized memory. @@ -210,6 +211,7 @@ extension Span where Element: BitwiseCopyable { /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer @@ -228,6 +230,7 @@ extension Span where Element: BitwiseCopyable { /// - count: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeStart pointer: UnsafeRawPointer, @@ -237,13 +240,12 @@ extension Span where Element: BitwiseCopyable { let stride = MemoryLayout.stride let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0) - self.init( - _unchecked: pointer.assumingMemoryBound(to: Element.self), - count: count - ) + _pointer = pointer + _count = count } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: Equatable { /// Returns a Boolean value indicating whether this and another span @@ -256,6 +258,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: Self) -> Bool { guard count == other.count else { return false } @@ -283,6 +286,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { guard count == other.count else { return false } @@ -301,6 +305,7 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Sequence) -> Bool { var offset = 0 @@ -313,19 +318,17 @@ extension Span where Element: Equatable { } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { - @_alwaysEmitIntoClient - var _address: String { - String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) - } - @_alwaysEmitIntoClient public var description: String { - "(0x\(_address), \(_count))" + let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) + return "(0x\(addr), \(_count))" } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// The number of elements in the span. @@ -354,49 +357,57 @@ extension Span where Element: ~Copyable { } //MARK: Bounds Checking +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { - /// Return true if `offset` is a valid offset into this `Span` + /// Return true if `index` is a valid offset into this `Span` /// /// - Parameters: - /// - position: an index to validate - /// - Returns: true if `offset` is a valid index + /// - index: an index to validate + /// - Returns: true if `index` is valid + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func boundsContain(_ offset: Int) -> Bool { - 0 <= offset && offset < count + public func boundsContain(_ index: Int) -> Bool { + 0 <= index && index < _count } - /// Return true if `offsets` is a valid range of offsets into this `Span` + /// Return true if `indices` is a valid range of indices into this `Span` /// /// - Parameters: - /// - offsets: a range of indices to validate - /// - Returns: true if `offsets` is a valid range of indices + /// - indices: a range of indices to validate + /// - Returns: true if `indices` is a valid range of indices + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func boundsContain(_ offsets: Range) -> Bool { - boundsContain(offsets.lowerBound) && offsets.upperBound <= count + public func boundsContain(_ indices: Range) -> Bool { + boundsContain(indices.lowerBound) && indices.upperBound <= _count } - /// Return true if `offsets` is a valid range of offsets into this `Span` + /// Return true if `indices` is a valid range of indices into this `Span` /// /// - Parameters: - /// - offsets: a range of indices to validate - /// - Returns: true if `offsets` is a valid range of indices + /// - indices: a range of indices to validate + /// - Returns: true if `indices` is a valid range of indices + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func boundsContain(_ offsets: ClosedRange) -> Bool { - boundsContain(offsets.lowerBound) && offsets.upperBound < count + public func boundsContain(_ indices: ClosedRange) -> Bool { + boundsContain(indices.lowerBound) && indices.upperBound < _count } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { /// Construct a RawSpan over the memory represented by this span /// /// - Returns: a RawSpan over the memory represented by this span + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe //FIXME: remove when the lifetime inference is fixed @_alwaysEmitIntoClient - public var _rawSpan: RawSpan { RawSpan(_unsafeSpan: self) } + public var _unsafeRawSpan: RawSpan { RawSpan(_unsafeSpan: self) } } //MARK: integer offset subscripts +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Accesses the element at the specified position in the `Span`. @@ -421,6 +432,7 @@ extension Span where Element: ~Copyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { _read { @@ -432,6 +444,7 @@ extension Span where Element: ~Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { /// Accesses the element at the specified position in the `Span`. @@ -456,6 +469,7 @@ extension Span where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { get { @@ -466,6 +480,7 @@ extension Span where Element: BitwiseCopyable { } //MARK: extracting sub-spans +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Constructs a new span over the items within the supplied range of @@ -481,12 +496,13 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(_ bounds: Range) -> Self { precondition(boundsContain(bounds)) return _extracting(unchecked: bounds) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(to bounds: Range) { self = _extracting(bounds) @@ -507,14 +523,17 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @usableFromInline func _extracting(unchecked bounds: Range) -> Self { Span( - _unchecked: _pointer?.advanced(by: bounds.lowerBound*MemoryLayout.stride), + _unchecked: _pointer?.advanced(by: bounds.lowerBound&*MemoryLayout.stride), count: bounds.count ) } + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public mutating func _shrink(toUnchecked bounds: Range) { self = _extracting(unchecked: bounds) @@ -533,11 +552,12 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { _extracting(bounds.relative(to: _indices)) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(to bounds: some RangeExpression) { self = _extracting(bounds) @@ -558,13 +578,16 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @usableFromInline func _extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { _extracting(unchecked: bounds.relative(to: _indices)) } + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public mutating func _shrink(toUnchecked bounds: some RangeExpression) { self = _extracting(uncheckedBounds: bounds) @@ -579,14 +602,15 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over all the items of this span. /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(_: UnboundedRange) -> Self { self } } //MARK: withUnsafePointer, etc. -extension Span where Element: ~Copyable { +@_disallowFeatureSuppression(NonescapableTypes) +extension Span where Element: ~Copyable { //FIXME: mark closure parameter as non-escaping /// Calls a closure with a pointer to the viewed contiguous storage. @@ -601,8 +625,9 @@ extension Span where Element: ~Copyable { /// for the `withUnsafeBufferPointer(_:)` method. The closure's /// parameter is valid only for the duration of its execution. /// - Returns: The return value of the `body` closure parameter. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func withUnsafeBufferPointer( + public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result ) throws(E) -> Result { guard let pointer = _pointer, count > 0 else { @@ -614,6 +639,7 @@ extension Span where Element: ~Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { //FIXME: mark closure parameter as non-escaping @@ -631,8 +657,9 @@ extension Span where Element: BitwiseCopyable { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func withUnsafeBytes( + public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { try RawSpan(_unsafeSpan: self).withUnsafeBytes(body) @@ -664,9 +691,11 @@ extension Span where Element: Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Returns a Boolean value indicating whether two `Span` instances /// refer to the same region in memory. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func isIdentical(to other: Self) -> Bool { (self._pointer == other._pointer) && (self._count == other._count) @@ -677,7 +706,8 @@ extension Span where Element: ~Copyable { /// /// Parameters: /// - span: a span that may be a subrange of `self` - /// Returns: A range of offsets within `self`, or `nil` + /// Returns: A range of indices within `self`, or `nil` + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func indices(of span: borrowing Self) -> Range? { if span._count > _count { return nil } @@ -696,6 +726,7 @@ extension Span where Element: ~Copyable { } //MARK: one-sided slicing operations +@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Returns a span containing the initial elements of this span, @@ -713,13 +744,14 @@ extension Span where Element: ~Copyable { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, count) return Self(_unchecked: _pointer, count: newCount) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(toFirst maxLength: Int) { self = _extracting(first: maxLength) @@ -739,13 +771,14 @@ extension Span where Element: ~Copyable { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) return Self(_unchecked: _pointer, count: count&-droppedCount) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(droppingLast k: Int) { self = _extracting(droppingLast: k) @@ -766,7 +799,7 @@ extension Span where Element: ~Copyable { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, count) @@ -774,6 +807,7 @@ extension Span where Element: ~Copyable { return Self(_unchecked: newStart, count: newCount) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(toLast maxLength: Int) { self = _extracting(last: maxLength) @@ -793,7 +827,7 @@ extension Span where Element: ~Copyable { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) - @_alwaysEmitIntoClient + @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) @@ -801,6 +835,7 @@ extension Span where Element: ~Copyable { return Self(_unchecked: newStart, count: count&-droppedCount) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(droppingFirst k: Int) { self = _extracting(droppingFirst: k) From 82eeb9c6e9dfaf1221b72bb2bfe1332f243d2c70 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 14:47:17 -0700 Subject: [PATCH 107/195] restore span extensions to `Foundation.Data` --- Sources/Future/StdlibSpanExtensions.swift | 2 -- Tests/FutureTests/StdlibSpanExtensionTests.swift | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift index 5e5787b91..55621c0db 100644 --- a/Sources/Future/StdlibSpanExtensions.swift +++ b/Sources/Future/StdlibSpanExtensions.swift @@ -98,7 +98,6 @@ extension Slice { } } -#if false //FIXME: rdar://134382237 import struct Foundation.Data extension Data { @@ -144,7 +143,6 @@ extension Data { return try result.get() } } -#endif extension Array { diff --git a/Tests/FutureTests/StdlibSpanExtensionTests.swift b/Tests/FutureTests/StdlibSpanExtensionTests.swift index 4516e7251..4c31cfda0 100644 --- a/Tests/FutureTests/StdlibSpanExtensionTests.swift +++ b/Tests/FutureTests/StdlibSpanExtensionTests.swift @@ -11,13 +11,13 @@ //===----------------------------------------------------------------------===// import XCTest +import Foundation import Future enum ErrorForTesting: Error, Equatable { case error, errorToo, errorAsWell } final class StdlibSpanExtensionTests: XCTestCase { -#if false //FIXME: rdar://134382237 -- nightly toolchain cannot `import Darwin` func testDataSpan() throws { let a = Data(0..<4) a.withSpan { @@ -47,7 +47,6 @@ final class StdlibSpanExtensionTests: XCTestCase { XCTAssertEqual(error, .error) } } -#endif func testArraySpan() throws { let a = (0..<4).map(String.init(_:)) From feabec869557c060e9f3391440942f3105df3977 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 30 Sep 2024 16:07:38 -0700 Subject: [PATCH 108/195] name a generic parameter properly --- Sources/Future/RawSpan.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 56078e261..431ccec50 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -150,12 +150,12 @@ extension RawSpan { /// - span: An existing `Span`, which will define both this /// `RawSpan`'s lifetime and the memory it represents. @_alwaysEmitIntoClient - public init( - _unsafeSpan span: borrowing Span + public init( + _unsafeSpan span: borrowing Span ) -> dependsOn(immortal) Self { self.init( _unchecked: UnsafeRawPointer(span._start), - byteCount: span.count * MemoryLayout.stride + byteCount: span.count &* MemoryLayout.stride ) } } From 8cb60d46d47ddb8c0580b3bd31838ed46886b0cd Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 14:27:11 -0700 Subject: [PATCH 109/195] add mutable span --- Sources/Future/MutableSpan.swift | 562 +++++++++++++++++++++++++++++++ 1 file changed, 562 insertions(+) create mode 100644 Sources/Future/MutableSpan.swift diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift new file mode 100644 index 000000000..db1a3ed42 --- /dev/null +++ b/Sources/Future/MutableSpan.swift @@ -0,0 +1,562 @@ +//===--- MutableSpan.swift ------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +// A MutableSpan represents a span of memory which +// contains initialized `Element` instances. +@_disallowFeatureSuppression(NonescapableTypes) +@frozen +public struct MutableSpan: ~Copyable & ~Escapable { + @usableFromInline let _pointer: UnsafeMutablePointer? + + @usableFromInline let _count: Int + + @usableFromInline @inline(__always) + var _start: UnsafeMutablePointer { _pointer.unsafelyUnwrapped } + + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) + init( + _unchecked start: UnsafeMutablePointer?, + count: Int + ) -> dependsOn(immortal) Self { + _pointer = start + _count = count + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +@available(*, unavailable) +extension MutableSpan: Sendable {} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) + internal init( + _unchecked elements: UnsafeMutableBufferPointer + ) -> dependsOn(immortal) Self { + _pointer = .init(elements.baseAddress) + _count = elements.count + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: UnsafeMutableBufferPointer + ) -> dependsOn(immortal) Self { + precondition( + buffer.count == 0 || + ((Int(bitPattern: buffer.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) + self.init(_unchecked: buffer) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeStart start: UnsafeMutablePointer, + count: Int + ) -> dependsOn(immortal) Self { + precondition(count >= 0, "Count must not be negative") + self.init(_unsafeElements: .init(start: start, count: count)) + } +} + +//TODO: initializers where Element: BitwiseCopyable + +//extension MutableSpan /*where Element: BitwiseCopyable*/ { +// +// public init( //FIXME: should be failable +// unsafeMutableRawBufferPointer buffer: UnsafeMutableRawBufferPointer, +// dependsOn owner: borrowing Owner +// ) { +// guard _isPOD(Element.self) else { fatalError() } +// guard let p = buffer.baseAddress else { /*return nil*/ fatalError() } +// let (q, r) = buffer.count.quotientAndRemainder(dividingBy: MemoryLayout.stride) +// precondition(r == 0) +// self.init(unsafeMutablePointer: p.assumingMemoryBound(to: Element.self), count: q, dependsOn: owner) +// } +//} + +//MARK: Sequence + +//extension MutableSpan /*: Sequence */ { +// +// public func makeIterator() -> SpanIterator { +// .init(from: 0, to: _count, dependsOn: /* self */ baseAddress) +// } +// +// //FIXME: mark closure parameter as non-escaping +// public func withContiguousStorageIfAvailable( +// _ body: (UnsafeBufferPointer) throws -> R +// ) rethrows -> R? { +// try Span(self).withContiguousStorageIfAvailable(body) +// } +//} + +@_disallowFeatureSuppression(NonescapableTypes) +extension Span where Element: ~Copyable { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init(_ mutableSpan: borrowing MutableSpan) { + self.init(_unchecked: mutableSpan._start, count: mutableSpan.count) + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension RawSpan { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeMutableSpan mutableSpan: borrowing MutableSpan + ) { + self.init( + _unchecked: mutableSpan._start, + byteCount: mutableSpan.count &* MemoryLayout.stride + ) + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: Equatable { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: borrowing Self) -> Bool { + _elementsEqual(Span(other)) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: Span) -> Bool { + Span(self)._elementsEqual(other) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: some Collection) -> Bool { + Span(self)._elementsEqual(other) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: some Sequence) -> Bool { + Span(self)._elementsEqual(other) + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + public var description: String { + let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) + return "(0x\(addr), \(_count))" + } +} + +//MARK: Collection, RandomAccessCollection +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + public var count: Int { _count } + + @_alwaysEmitIntoClient + public var isEmpty: Bool { _count == 0 } + + @_alwaysEmitIntoClient + public var _indices: Range { + Range(uncheckedBounds: (0, _count)) + } +} + +//MARK: Bounds Checking +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + /// Return true if `index` is a valid offset into this `Span` + /// + /// - Parameters: + /// - index: an index to validate + /// - Returns: true if `index` is valid + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func boundsContain(_ index: Int) -> Bool { + 0 <= index && index < _count + } + + /// Return true if `indices` is a valid range of indices into this `Span` + /// + /// - Parameters: + /// - indices: a range of indices to validate + /// - Returns: true if `indices` is a valid range of indices + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func boundsContain(_ indices: Range) -> Bool { + boundsContain(indices.lowerBound) && indices.upperBound <= _count + } + + /// Return true if `indices` is a valid range of indices into this `Span` + /// + /// - Parameters: + /// - indices: a range of indices to validate + /// - Returns: true if `indices` is a valid range of indices + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func boundsContain(_ indices: ClosedRange) -> Bool { + boundsContain(indices.lowerBound) && indices.upperBound < _count + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: BitwiseCopyable { + + /// Construct a RawSpan over the memory represented by this span + /// + /// - Returns: a RawSpan over the memory represented by this span + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe //FIXME: remove when the lifetime inference is fixed + @_alwaysEmitIntoClient + public var _unsafeRawSpan: RawSpan { Span(self)._unsafeRawSpan } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + + /// Accesses the element at the specified position in the `Span`. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public subscript(_ position: Int) -> Element { + _read { + precondition(boundsContain(position)) + yield self[unchecked: position] + } + _modify { + precondition(boundsContain(position)) + yield &self[unchecked: position] + } + } + + /// Accesses the element at the specified position in the `Span`. + /// + /// This subscript does not validate `position`; this is an unsafe operation. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public subscript(unchecked position: Int) -> Element { + _read { + let stride = MemoryLayout.stride + let p = _start.advanced(by: position&*stride)._rawValue + let binding = Builtin.bindMemory(p, count._builtinWordValue, Element.self) + defer { Builtin.rebindMemory(p, binding) } + yield UnsafePointer(p).pointee + } + _modify { + let stride = MemoryLayout.stride + let p = _start.advanced(by: position&*stride)._rawValue + let binding = Builtin.bindMemory(p, 1._builtinWordValue, Element.self) + defer { Builtin.rebindMemory(p, binding) } + yield &(UnsafeMutablePointer(p).pointee) + } + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: BitwiseCopyable { + + /// Accesses the element at the specified position in the `Span`. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public subscript(_ position: Int) -> Element { + get { + precondition(boundsContain(position)) + return self[unchecked: position] + } + set { + precondition(boundsContain(position)) + self[unchecked: position] = newValue + } + } + + /// Accesses the element at the specified position in the `Span`. + /// + /// This subscript does not validate `position`; this is an unsafe operation. + /// + /// - Parameter position: The offset of the element to access. `position` + /// must be greater or equal to zero, and less than `count`. + /// + /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public subscript(unchecked position: Int) -> Element { + get { + let offset = position&*MemoryLayout.stride + return UnsafeRawPointer(_start).loadUnaligned(fromByteOffset: offset, as: Element.self) + } + set { + let offset = position&*MemoryLayout.stride + UnsafeMutableRawPointer(_start) + .storeBytes(of: newValue, toByteOffset: offset, as: Element.self) + } + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + //FIXME: mark closure parameter as non-escaping + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func withUnsafeBufferPointer( + _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + try Span(self).withUnsafeBufferPointer(body) + } + + //FIXME: mark closure parameter as non-escaping + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func withUnsafeMutableBufferPointer( + _ body: (inout UnsafeMutableBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + guard let pointer = _pointer, count > 0 else { + var buffer = UnsafeMutableBufferPointer(start: nil, count: 0) + return try body(&buffer) + } + return try pointer.withMemoryRebound(to: Element.self, capacity: count) { + ptr throws(E) -> Result in + var buf = UnsafeMutableBufferPointer(start: ptr, count: count) + defer { + precondition( + (buf.baseAddress, buf.count) == (ptr, count), + "MutableSpan.withUnsafeMutableBufferPointer: replacing the buffer is not allowed" + ) + } + return try body(&buf) + } + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: BitwiseCopyable { + + //FIXME: mark closure parameter as non-escaping + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func withUnsafeBytes( + _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + try RawSpan(_unsafeMutableSpan: self).withUnsafeBytes(body) + } + + //FIXME: mark closure parameter as non-escaping + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func withUnsafeMutableBytes( + _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + let bytes = UnsafeMutableRawBufferPointer( + start: (_count == 0) ? nil : UnsafeMutableRawPointer(_start), + count: _count &* MemoryLayout.stride + ) + return try body(bytes) + } +} + +//MARK: UMBP-style update functions +extension MutableSpan { + + public func update(repeating repeatedValue: Element) { + _start.withMemoryRebound(to: Element.self, capacity: count) { + $0.update(repeating: repeatedValue, count: count) + } + } + + public mutating func update( + from source: S + ) -> (unwritten: S.Iterator, index: Int) where S.Element == Element { + var iterator = source.makeIterator() + guard !self.isEmpty else { return (iterator, 0) } + var index = 0 + while index < _count { + guard let value = iterator.next() else { break } + self[unchecked: index] = value + index &+= 1 + } + return (iterator, index) + } + + public mutating func update(fromContentsOf source: some Collection) -> Int { + let updated = source.withContiguousStorageIfAvailable { + [ count = self.count, baseAddress = self._start ] sourceBuffer in + guard let sourceAddress = sourceBuffer.baseAddress else { + return 0 + } + precondition( + sourceBuffer.count <= count, + "destination buffer view cannot contain every element from source." + ) + baseAddress.withMemoryRebound(to: Element.self, capacity: sourceBuffer.count) { + $0.update(from: sourceAddress, count: sourceBuffer.count) + } + return sourceBuffer.count + } + if let updated { + return 0.advanced(by: updated) + } + + if self.isEmpty { + precondition( + source.isEmpty, + "destination buffer view cannot contain every element from source." + ) + return 0 + } + + var iterator = source.makeIterator() + var index = 0 + while let value = iterator.next() { + guard index < _count else { + preconditionFailure( + "destination buffer view cannot contain every element from source." + ) + break + } + self[unchecked: index] = value + index &+= 1 + } + return index + } +} + +extension MutableSpan where Element: Copyable { + + public mutating func update(fromContentsOf source: Span) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: borrowing MutableSpan) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: UnsafeBufferPointer) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: Slice>) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: Slice>) -> Int { + return 0 + } +} + +extension MutableSpan where Element: ~Copyable { + + public mutating func moveUpdate(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { + return 0 + } +} + +extension MutableSpan where Element: BitwiseCopyable { + //TODO: overload the various `update` functions to use memcpy + + public mutating func update(fromContentsOf source: some Collection) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: Span) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: borrowing MutableSpan) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: UnsafeBufferPointer) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: Slice>) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: Slice>) -> Int { + return 0 + } + + public mutating func moveUpdate(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { + return 0 + } + + public mutating func moveUpdate(fromContentsOf source: Slice>) -> Int { + return 0 + } +} + +//MARK: copyMemory +//FIXME: move these to a MutableRawSpan +extension MutableSpan where Element: BitwiseCopyable { + + @_alwaysEmitIntoClient + public func copyMemory( + from source: borrowing MutableSpan + ) -> Int { + guard _isPOD(Element.self) else { fatalError() } + return copyMemory(from: .init(source)) + } + + public func copyMemory(from source: Span) -> Int { + guard _isPOD(Element.self) else { fatalError() } + precondition( + source.count <= count, + "MutableSpan.copyMemory source has too many elements" + ) + UnsafeMutableRawPointer(_start).copyMemory( + from: source._start, + byteCount: source.count&*MemoryLayout.stride + ) + return 0.advanced(by: source.count) + } +} From 693ff4a389052cf37c7e1808331e70d2c040f12a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 30 Sep 2024 17:51:40 -0700 Subject: [PATCH 110/195] add MutableSpan initializers where Element: BitwiseCopyable --- Sources/Future/MutableSpan.swift | 77 +++++++++++++++++++------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index db1a3ed42..22c685746 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -76,37 +76,54 @@ extension MutableSpan where Element: ~Copyable { } } -//TODO: initializers where Element: BitwiseCopyable +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: BitwiseCopyable { -//extension MutableSpan /*where Element: BitwiseCopyable*/ { -// -// public init( //FIXME: should be failable -// unsafeMutableRawBufferPointer buffer: UnsafeMutableRawBufferPointer, -// dependsOn owner: borrowing Owner -// ) { -// guard _isPOD(Element.self) else { fatalError() } -// guard let p = buffer.baseAddress else { /*return nil*/ fatalError() } -// let (q, r) = buffer.count.quotientAndRemainder(dividingBy: MemoryLayout.stride) -// precondition(r == 0) -// self.init(unsafeMutablePointer: p.assumingMemoryBound(to: Element.self), count: q, dependsOn: owner) -// } -//} - -//MARK: Sequence - -//extension MutableSpan /*: Sequence */ { -// -// public func makeIterator() -> SpanIterator { -// .init(from: 0, to: _count, dependsOn: /* self */ baseAddress) -// } -// -// //FIXME: mark closure parameter as non-escaping -// public func withContiguousStorageIfAvailable( -// _ body: (UnsafeBufferPointer) throws -> R -// ) rethrows -> R? { -// try Span(self).withContiguousStorageIfAvailable(body) -// } -//} + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: UnsafeMutableBufferPointer + ) -> dependsOn(immortal) Self { + self.init(_unchecked: buffer) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeStart start: UnsafeMutablePointer, + count: Int + ) -> dependsOn(immortal) Self { + precondition(count >= 0, "Count must not be negative") + self.init(_unchecked: start, count: count) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: UnsafeMutableRawBufferPointer + ) -> dependsOn(immortal) Self { + let (byteCount, stride) = (buffer.count, MemoryLayout.stride) + precondition(byteCount >= 0, "Count must not be negative") + let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(remainder == 0) + _pointer = buffer.baseAddress?.assumingMemoryBound(to: Element.self) + _count = count + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeStart pointer: UnsafeMutableRawPointer, + byteCount: Int + ) -> dependsOn(immortal) Self { + precondition(byteCount >= 0, "Count must not be negative") + let stride = MemoryLayout.stride + let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(remainder == 0) + _pointer = pointer.assumingMemoryBound(to: Element.self) + _count = count + } +} @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { From 8af1843d071dd01053b5b22b3e5db7aba74f7844 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 1 Oct 2024 16:00:42 -0700 Subject: [PATCH 111/195] Span precondition tweaks --- Sources/Future/Span.swift | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 9942905a7..b4fdfe077 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -190,11 +190,9 @@ extension Span where Element: BitwiseCopyable { _unsafeBytes buffer: UnsafeRawBufferPointer ) -> dependsOn(immortal) Self { let (byteCount, stride) = (buffer.count, MemoryLayout.stride) - precondition(byteCount >= 0, "Count must not be negative") let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0) - _pointer = buffer.baseAddress - _count = count + precondition(remainder == 0, "Span must contain a whole number of elements") + self.init(_unchecked: buffer.baseAddress, count: count) } /// Unsafely create a `Span` over initialized memory. @@ -239,9 +237,8 @@ extension Span where Element: BitwiseCopyable { precondition(byteCount >= 0, "Count must not be negative") let stride = MemoryLayout.stride let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0) - _pointer = pointer - _count = count + precondition(remainder == 0, "Span must contain a whole number of elements") + self.init(_unchecked: pointer, count: count) } } @@ -419,7 +416,7 @@ extension Span where Element: ~Copyable { @_alwaysEmitIntoClient public subscript(_ position: Int) -> Element { _read { - precondition(boundsContain(position)) + precondition(boundsContain(position), "index out of bounds") yield self[unchecked: position] } } @@ -456,7 +453,7 @@ extension Span where Element: BitwiseCopyable { @_alwaysEmitIntoClient public subscript(_ position: Int) -> Element { get { - precondition(boundsContain(position)) + precondition(boundsContain(position), "index out of bounds") return self[unchecked: position] } } @@ -498,7 +495,7 @@ extension Span where Element: ~Copyable { /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(_ bounds: Range) -> Self { - precondition(boundsContain(bounds)) + precondition(boundsContain(bounds), "index out of bounds") return _extracting(unchecked: bounds) } From cb84b912c213987c08418874b19513c3b17345e6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 1 Oct 2024 16:04:33 -0700 Subject: [PATCH 112/195] Span test tweaks --- Tests/FutureTests/SpanTests.swift | 79 ++++++++----------------------- 1 file changed, 20 insertions(+), 59 deletions(-) diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index a30b107f6..df3a11486 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -113,7 +113,7 @@ final class SpanTests: XCTestCase { let array = Array(0...stride) } } @@ -358,64 +358,25 @@ final class SpanTests: XCTestCase { XCTAssertEqual(prefix.count, 2) } -// func testContainment() { -// let b = UnsafeMutableBufferPointer.allocate(capacity: 8) -// _ = b.initialize(fromContentsOf: 0..<8) -// defer { b.deallocate() } -// -// let span = Span(_unsafeElements: b) -// let subSpan = span._extracting(last: 2) -// let emptySpan = span._extracting(first: 0) -// let fakeSpan = Span( -// _unsafeStart: b.baseAddress!.advanced(by: 8), count: 8 -// ) -// let nilSpan = Span( -// _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) -// ) -// let unalignedSpan = Span( -// _unsafeStart: UnsafeRawPointer(b.baseAddress!).advanced(by: 2).assumingMemoryBound(to: Int.self), -// count: 2 -// ) -// -// XCTAssertTrue(subSpan.isWithin(span))//.contains(subSpan)) -// XCTAssertFalse(span.isWithin(subSpan))//.contains(span)) -// XCTAssertTrue(emptySpan.isWithin(span))//.contains(emptySpan)) -// XCTAssertFalse(span.isWithin(emptySpan))//.contains(span)) -// XCTAssertFalse(fakeSpan.isWithin(span))//.contains(fakeSpan)) -// XCTAssertFalse(span.isWithin(fakeSpan))//.contains(span)) -// XCTAssertFalse(nilSpan.isWithin(span))//.contains(nilSpan)) -// XCTAssertFalse(nilSpan.isWithin(fakeSpan))//.contains(nilSpan)) -// XCTAssertTrue(nilSpan.isWithin(nilSpan)) -// XCTAssertFalse(emptySpan.isWithin(nilSpan))//.contains(emptySpan)) -// XCTAssertFalse(unalignedSpan.isWithin(span)) -// } - -// func testIndicesWithin() { -// let b = UnsafeMutableBufferPointer.allocate(capacity: 8) -// _ = b.initialize(fromContentsOf: 0..<8) -// defer { b.deallocate() } -// -// let span = Span(_unsafeElements: b) -// let subSpan = span._extracting(last: 2) -// let emptySpan = span._extracting(first: 0) -// let nilSpan = Span( -// _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) -// ) -// let unalignedSpan = Span( -// _unsafeStart: UnsafeRawPointer(b.baseAddress!).advanced(by: 2).assumingMemoryBound(to: Int.self), -// count: 2 -// ) -// -// var bounds: Range? -// bounds = subSpan.indicesWithin(span) -// XCTAssertEqual(bounds, span._indices.suffix(2)) -// bounds = span.offsets(of: emptySpan) -// XCTAssertEqual(bounds, span._indices.prefix(0)) -// bounds = span.offsets(of: nilSpan) -// XCTAssertEqual(bounds, 0..<0) -// bounds = nilSpan.offsets(of: emptySpan) -// XCTAssertEqual(bounds, 0..<0) -// } + func testIdentity() { + let b = UnsafeMutableBufferPointer.allocate(capacity: 8) + _ = b.initialize(fromContentsOf: 0..<8) + defer { b.deallocate() } + + let span = Span(_unsafeElements: b) + let pre = span._extracting(first: 6) + let suf = span._extracting(last: 6) + + XCTAssertFalse( + pre.isIdentical(to: suf) + ) + XCTAssertFalse( + pre.isIdentical(to: span) + ) + XCTAssertTrue( + pre._extracting(last: 4).isIdentical(to: suf._extracting(first: 4)) + ) + } func testIndicesOf() { let b = UnsafeMutableBufferPointer.allocate(capacity: 8) From 1b7168f7273ddfc7d8b654474b25945a465c48c7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 2 Oct 2024 17:23:25 -0700 Subject: [PATCH 113/195] more additions and fixes for MutableSpan --- Sources/Future/MutableSpan.swift | 168 +++++++++++++++++++------------ 1 file changed, 101 insertions(+), 67 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 22c685746..92343b875 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -17,17 +17,17 @@ import Builtin @_disallowFeatureSuppression(NonescapableTypes) @frozen public struct MutableSpan: ~Copyable & ~Escapable { - @usableFromInline let _pointer: UnsafeMutablePointer? + @usableFromInline let _pointer: UnsafeMutableRawPointer? @usableFromInline let _count: Int @usableFromInline @inline(__always) - var _start: UnsafeMutablePointer { _pointer.unsafelyUnwrapped } + var _start: UnsafeMutableRawPointer { _pointer.unsafelyUnwrapped } @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) init( - _unchecked start: UnsafeMutablePointer?, + _unchecked start: UnsafeMutableRawPointer?, count: Int ) -> dependsOn(immortal) Self { _pointer = start @@ -57,7 +57,6 @@ extension MutableSpan where Element: ~Copyable { _unsafeElements buffer: UnsafeMutableBufferPointer ) -> dependsOn(immortal) Self { precondition( - buffer.count == 0 || ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment&-1)) == 0), "baseAddress must be properly aligned to access Element" @@ -103,11 +102,9 @@ extension MutableSpan where Element: BitwiseCopyable { _unsafeBytes buffer: UnsafeMutableRawBufferPointer ) -> dependsOn(immortal) Self { let (byteCount, stride) = (buffer.count, MemoryLayout.stride) - precondition(byteCount >= 0, "Count must not be negative") let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0) - _pointer = buffer.baseAddress?.assumingMemoryBound(to: Element.self) - _count = count + precondition(remainder == 0, "Span must contain a whole number of elements") + self.init(_unchecked: buffer.baseAddress, count: count) } @_disallowFeatureSuppression(NonescapableTypes) @@ -119,9 +116,8 @@ extension MutableSpan where Element: BitwiseCopyable { precondition(byteCount >= 0, "Count must not be negative") let stride = MemoryLayout.stride let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0) - _pointer = pointer.assumingMemoryBound(to: Element.self) - _count = count + precondition(remainder == 0, "Span must contain a whole number of elements") + self.init(_unchecked: pointer, count: count) } } @@ -130,7 +126,7 @@ extension Span where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public init(_ mutableSpan: borrowing MutableSpan) { + public init(_unsafeMutableSpan mutableSpan: borrowing MutableSpan) { self.init(_unchecked: mutableSpan._start, count: mutableSpan.count) } } @@ -156,25 +152,25 @@ extension MutableSpan where Element: Equatable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: borrowing Self) -> Bool { - _elementsEqual(Span(other)) + _elementsEqual(Span(_unsafeMutableSpan: other)) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: Span) -> Bool { - Span(self)._elementsEqual(other) + Span(_unsafeMutableSpan: self)._elementsEqual(other) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { - Span(self)._elementsEqual(other) + Span(_unsafeMutableSpan: self)._elementsEqual(other) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Sequence) -> Bool { - Span(self)._elementsEqual(other) + Span(_unsafeMutableSpan: self)._elementsEqual(other) } } @@ -251,7 +247,7 @@ extension MutableSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @unsafe //FIXME: remove when the lifetime inference is fixed @_alwaysEmitIntoClient - public var _unsafeRawSpan: RawSpan { Span(self)._unsafeRawSpan } + public var _unsafeRawSpan: RawSpan { RawSpan(_unsafeMutableSpan: self) } } @_disallowFeatureSuppression(NonescapableTypes) @@ -268,11 +264,11 @@ extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient public subscript(_ position: Int) -> Element { _read { - precondition(boundsContain(position)) + precondition(boundsContain(position), "index out of bounds") yield self[unchecked: position] } _modify { - precondition(boundsContain(position)) + precondition(boundsContain(position), "index out of bounds") yield &self[unchecked: position] } } @@ -289,15 +285,15 @@ extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { _read { - let stride = MemoryLayout.stride - let p = _start.advanced(by: position&*stride)._rawValue + let offset = position&*MemoryLayout.stride + let p = UnsafeRawPointer(_start).advanced(by: offset)._rawValue let binding = Builtin.bindMemory(p, count._builtinWordValue, Element.self) defer { Builtin.rebindMemory(p, binding) } yield UnsafePointer(p).pointee } _modify { - let stride = MemoryLayout.stride - let p = _start.advanced(by: position&*stride)._rawValue + let offset = position&*MemoryLayout.stride + let p = UnsafeMutableRawPointer(_start).advanced(by: offset)._rawValue let binding = Builtin.bindMemory(p, 1._builtinWordValue, Element.self) defer { Builtin.rebindMemory(p, binding) } yield &(UnsafeMutablePointer(p).pointee) @@ -340,12 +336,11 @@ extension MutableSpan where Element: BitwiseCopyable { public subscript(unchecked position: Int) -> Element { get { let offset = position&*MemoryLayout.stride - return UnsafeRawPointer(_start).loadUnaligned(fromByteOffset: offset, as: Element.self) + return _start.loadUnaligned(fromByteOffset: offset, as: Element.self) } set { let offset = position&*MemoryLayout.stride - UnsafeMutableRawPointer(_start) - .storeBytes(of: newValue, toByteOffset: offset, as: Element.self) + _start.storeBytes(of: newValue, toByteOffset: offset, as: Element.self) } } } @@ -359,30 +354,34 @@ extension MutableSpan where Element: ~Copyable { public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result ) throws(E) -> Result { - try Span(self).withUnsafeBufferPointer(body) + try Span(_unsafeMutableSpan: self).withUnsafeBufferPointer(body) } //FIXME: mark closure parameter as non-escaping @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func withUnsafeMutableBufferPointer( + public mutating func withUnsafeMutableBufferPointer( _ body: (inout UnsafeMutableBufferPointer) throws(E) -> Result ) throws(E) -> Result { - guard let pointer = _pointer, count > 0 else { - var buffer = UnsafeMutableBufferPointer(start: nil, count: 0) - return try body(&buffer) - } - return try pointer.withMemoryRebound(to: Element.self, capacity: count) { - ptr throws(E) -> Result in - var buf = UnsafeMutableBufferPointer(start: ptr, count: count) + func executeBody( + _ start: UnsafeMutablePointer?, _ count: Int + ) throws(E) -> Result { + var buf = UnsafeMutableBufferPointer(start: start, count: count) defer { precondition( - (buf.baseAddress, buf.count) == (ptr, count), + (buf.baseAddress, buf.count) == (start, count), "MutableSpan.withUnsafeMutableBufferPointer: replacing the buffer is not allowed" ) } return try body(&buf) } + guard let pointer = _pointer, count > 0 else { + return try executeBody(nil, 0) + } + return try pointer.withMemoryRebound(to: Element.self, capacity: count) { + pointer throws(E) -> Result in + return try executeBody(pointer, count) + } } } @@ -401,11 +400,11 @@ extension MutableSpan where Element: BitwiseCopyable { //FIXME: mark closure parameter as non-escaping @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func withUnsafeMutableBytes( + public mutating func withUnsafeMutableBytes( _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { let bytes = UnsafeMutableRawBufferPointer( - start: (_count == 0) ? nil : UnsafeMutableRawPointer(_start), + start: (_count == 0) ? nil : _start, count: _count &* MemoryLayout.stride ) return try body(bytes) @@ -415,12 +414,14 @@ extension MutableSpan where Element: BitwiseCopyable { //MARK: UMBP-style update functions extension MutableSpan { - public func update(repeating repeatedValue: Element) { + @_alwaysEmitIntoClient + public mutating func update(repeating repeatedValue: Element) { _start.withMemoryRebound(to: Element.self, capacity: count) { $0.update(repeating: repeatedValue, count: count) } } + @_alwaysEmitIntoClient public mutating func update( from source: S ) -> (unwritten: S.Iterator, index: Int) where S.Element == Element { @@ -435,20 +436,10 @@ extension MutableSpan { return (iterator, index) } + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: some Collection) -> Int { let updated = source.withContiguousStorageIfAvailable { - [ count = self.count, baseAddress = self._start ] sourceBuffer in - guard let sourceAddress = sourceBuffer.baseAddress else { - return 0 - } - precondition( - sourceBuffer.count <= count, - "destination buffer view cannot contain every element from source." - ) - baseAddress.withMemoryRebound(to: Element.self, capacity: sourceBuffer.count) { - $0.update(from: sourceAddress, count: sourceBuffer.count) - } - return sourceBuffer.count + self.update(fromContentsOf: $0) } if let updated { return 0.advanced(by: updated) @@ -464,56 +455,85 @@ extension MutableSpan { var iterator = source.makeIterator() var index = 0 + let _count = _count while let value = iterator.next() { + print(value, index, _count) guard index < _count else { - preconditionFailure( + fatalError( "destination buffer view cannot contain every element from source." ) - break } self[unchecked: index] = value index &+= 1 } return index } -} - -extension MutableSpan where Element: Copyable { + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: Span) -> Int { - return 0 + source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: borrowing MutableSpan) -> Int { - return 0 + source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: UnsafeBufferPointer) -> Int { - return 0 + guard let sourceAddress = source.baseAddress, source.count > 0 else { return 0 } + precondition( + source.count <= self.count, + "destination span cannot contain every element from source." + ) + _start.withMemoryRebound(to: Element.self, capacity: source.count) { + $0.update(from: sourceAddress, count: source.count) + } + return source.count } + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { - return 0 + self.update(fromContentsOf: .init(source)) } + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: Slice>) -> Int { - return 0 + self.update(fromContentsOf: .init(rebasing: source)) } + @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: Slice>) -> Int { - return 0 + self.update(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) } } extension MutableSpan where Element: ~Copyable { public mutating func moveUpdate(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { - return 0 + guard let sourceAddress = source.baseAddress, source.count > 0 else { return 0 } + precondition( + source.count <= self.count, + "destination span cannot contain every element from source." + ) + _start.withMemoryRebound(to: Element.self, capacity: source.count) { + $0.moveUpdate(from: sourceAddress, count: source.count) + } + return source.count + } +} + +extension MutableSpan { + + public mutating func moveUpdate(fromContentsOf source: Slice>) -> Int { + self.moveUpdate(fromContentsOf: .init(rebasing: source)) } } + +#if false +//TODO: overload the various `update` functions to use memcpy extension MutableSpan where Element: BitwiseCopyable { - //TODO: overload the various `update` functions to use memcpy public mutating func update(fromContentsOf source: some Collection) -> Int { return 0 @@ -523,6 +543,11 @@ extension MutableSpan where Element: BitwiseCopyable { return 0 } + @unsafe + public mutating func update(fromContentsOf source: RawSpan) -> Int { + return 0 + } + public mutating func update(fromContentsOf source: borrowing MutableSpan) -> Int { return 0 } @@ -535,6 +560,14 @@ extension MutableSpan where Element: BitwiseCopyable { return 0 } + public mutating func update(fromContentsOf source: UnsafeRawBufferPointer) -> Int { + return 0 + } + + public mutating func update(fromContentsOf source: UnsafeMutableRawBufferPointer) -> Int { + return 0 + } + public mutating func update(fromContentsOf source: Slice>) -> Int { return 0 } @@ -543,14 +576,15 @@ extension MutableSpan where Element: BitwiseCopyable { return 0 } - public mutating func moveUpdate(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { + public mutating func update(fromContentsOf source: Slice) -> Int { return 0 } - public mutating func moveUpdate(fromContentsOf source: Slice>) -> Int { + public mutating func update(fromContentsOf source: Slice) -> Int { return 0 } } +#endif //MARK: copyMemory //FIXME: move these to a MutableRawSpan @@ -561,7 +595,7 @@ extension MutableSpan where Element: BitwiseCopyable { from source: borrowing MutableSpan ) -> Int { guard _isPOD(Element.self) else { fatalError() } - return copyMemory(from: .init(source)) + return copyMemory(from: .init(_unsafeMutableSpan: source)) } public func copyMemory(from source: Span) -> Int { From 515165834186503b6e61661794e54526bbd32d79 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 3 Oct 2024 13:45:03 -0700 Subject: [PATCH 114/195] add more Span initializers --- Sources/Future/Span.swift | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index b4fdfe077..9a276b490 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -112,6 +112,26 @@ extension Span where Element: ~Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) +extension Span { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: Slice> + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: Slice> + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) + } +} + @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { @@ -240,6 +260,38 @@ extension Span where Element: BitwiseCopyable { precondition(remainder == 0, "Span must contain a whole number of elements") self.init(_unchecked: pointer, count: count) } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: Slice> + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeElements buffer: Slice> + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: Slice + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: Slice + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) + } } @_disallowFeatureSuppression(NonescapableTypes) From b432175fae6ccf9321b91f1be40f944f142c9d4d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 3 Oct 2024 13:45:23 -0700 Subject: [PATCH 115/195] add more MutableSpan initializers --- Sources/Future/MutableSpan.swift | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 92343b875..47fa80ab7 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -75,6 +75,18 @@ extension MutableSpan where Element: ~Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + internal init( + _unsafeElements elements: Slice> + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeMutableBufferPointer(rebasing: elements)) + } +} + @_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan where Element: BitwiseCopyable { @@ -119,6 +131,22 @@ extension MutableSpan where Element: BitwiseCopyable { precondition(remainder == 0, "Span must contain a whole number of elements") self.init(_unchecked: pointer, count: count) } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + internal init( + _unsafeElements elements: Slice> + ) -> dependsOn(immortal) Self { + self.init(_unsafeElements: UnsafeMutableBufferPointer(rebasing: elements)) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + internal init( + _unsafeBytes buffer: Slice + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeMutableRawBufferPointer(rebasing: buffer)) + } } @_disallowFeatureSuppression(NonescapableTypes) From 5b590b95cf2e998c4b706c0af8ada62ca60121e9 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 3 Oct 2024 13:46:07 -0700 Subject: [PATCH 116/195] implement bulk-update functions for MutableSpan --- Sources/Future/MutableSpan.swift | 197 ++++++++++++++++++++++++------- 1 file changed, 157 insertions(+), 40 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 47fa80ab7..118a8b407 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -439,7 +439,7 @@ extension MutableSpan where Element: BitwiseCopyable { } } -//MARK: UMBP-style update functions +//MARK: bulk-update functions extension MutableSpan { @_alwaysEmitIntoClient @@ -465,7 +465,9 @@ extension MutableSpan { } @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: some Collection) -> Int { + public mutating func update( + fromContentsOf source: some Collection + ) -> Int { let updated = source.withContiguousStorageIfAvailable { self.update(fromContentsOf: $0) } @@ -483,9 +485,7 @@ extension MutableSpan { var iterator = source.makeIterator() var index = 0 - let _count = _count while let value = iterator.next() { - print(value, index, _count) guard index < _count else { fatalError( "destination buffer view cannot contain every element from source." @@ -503,12 +503,16 @@ extension MutableSpan { } @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: borrowing MutableSpan) -> Int { + public mutating func update( + fromContentsOf source: borrowing MutableSpan + ) -> Int { source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: UnsafeBufferPointer) -> Int { + public mutating func update( + fromContentsOf source: UnsafeBufferPointer + ) -> Int { guard let sourceAddress = source.baseAddress, source.count > 0 else { return 0 } precondition( source.count <= self.count, @@ -521,25 +525,35 @@ extension MutableSpan { } @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { + public mutating func update( + fromContentsOf source: UnsafeMutableBufferPointer + ) -> Int { self.update(fromContentsOf: .init(source)) } @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: Slice>) -> Int { + public mutating func update( + fromContentsOf source: Slice> + ) -> Int { self.update(fromContentsOf: .init(rebasing: source)) } @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: Slice>) -> Int { - self.update(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) + public mutating func update( + fromContentsOf source: Slice> + ) -> Int { + self.update(fromContentsOf: UnsafeBufferPointer(rebasing: source)) } } extension MutableSpan where Element: ~Copyable { - public mutating func moveUpdate(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { - guard let sourceAddress = source.baseAddress, source.count > 0 else { return 0 } + public mutating func moveUpdate( + fromContentsOf source: UnsafeMutableBufferPointer + ) -> Int { + guard let sourceAddress = source.baseAddress, source.count > 0 else { + return 0 + } precondition( source.count <= self.count, "destination span cannot contain every element from source." @@ -553,66 +567,169 @@ extension MutableSpan where Element: ~Copyable { extension MutableSpan { - public mutating func moveUpdate(fromContentsOf source: Slice>) -> Int { + public mutating func moveUpdate( + fromContentsOf source: Slice> + ) -> Int { self.moveUpdate(fromContentsOf: .init(rebasing: source)) } } -#if false -//TODO: overload the various `update` functions to use memcpy extension MutableSpan where Element: BitwiseCopyable { - public mutating func update(fromContentsOf source: some Collection) -> Int { - return 0 + @_alwaysEmitIntoClient + public mutating func update( + repeating repeatedValue: Element + ) where Element: BitwiseCopyable { + guard count > 0 else { return } + // rebind _start manually in order to avoid assumptions about alignment. + let rp = _start._rawValue + let binding = Builtin.bindMemory(rp, count._builtinWordValue, Element.self) + UnsafeMutablePointer(rp).update(repeating: repeatedValue, count: count) + Builtin.rebindMemory(rp, binding) } - public mutating func update(fromContentsOf source: Span) -> Int { - return 0 + @_alwaysEmitIntoClient + public mutating func update( + from source: S + ) -> (unwritten: S.Iterator, index: Int) + where S.Element == Element, Element: BitwiseCopyable { + var iterator = source.makeIterator() + guard !self.isEmpty else { return (iterator, 0) } + var index = 0 + while index < _count { + guard let value = iterator.next() else { break } + self[unchecked: index] = value + index &+= 1 + } + return (iterator, index) + } + + @_alwaysEmitIntoClient + public mutating func update( + fromContentsOf source: some Collection + ) -> Int where Element: BitwiseCopyable { + let updated = source.withContiguousStorageIfAvailable { + self.update(fromContentsOf: $0) + } + if let updated { + return 0.advanced(by: updated) + } + + if self.isEmpty { + precondition( + source.isEmpty, + "destination buffer view cannot contain every element from source." + ) + return 0 + } + + var iterator = source.makeIterator() + var index = 0 + while let value = iterator.next() { + guard index < _count else { + fatalError( + "destination buffer view cannot contain every element from source." + ) + } + self[unchecked: index] = value + index &+= 1 + } + return index + } + + public mutating func update( + fromContentsOf source: Span + ) -> Int where Element: BitwiseCopyable { + source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } @unsafe - public mutating func update(fromContentsOf source: RawSpan) -> Int { - return 0 + public mutating func update( + fromContentsOf source: RawSpan + ) -> Int where Element: BitwiseCopyable { + source.withUnsafeBytes { self.update(fromContentsOf: $0) } } - public mutating func update(fromContentsOf source: borrowing MutableSpan) -> Int { - return 0 + public mutating func update( + fromContentsOf source: borrowing MutableSpan + ) -> Int where Element: BitwiseCopyable { + source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } - public mutating func update(fromContentsOf source: UnsafeBufferPointer) -> Int { - return 0 + @_alwaysEmitIntoClient + public mutating func update( + fromContentsOf source: UnsafeBufferPointer + ) -> Int where Element: BitwiseCopyable { + guard let sourceAddress = source.baseAddress, source.count > 0 else { + return 0 + } + precondition( + source.count <= self.count, + "destination span cannot contain every element from source." + ) + _start.copyMemory( + from: sourceAddress, byteCount: source.count*MemoryLayout.stride + ) + return source.count } - public mutating func update(fromContentsOf source: UnsafeMutableBufferPointer) -> Int { - return 0 + public mutating func update( + fromContentsOf source: UnsafeMutableBufferPointer + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: .init(source)) } - public mutating func update(fromContentsOf source: UnsafeRawBufferPointer) -> Int { - return 0 + public mutating func update( + fromContentsOf source: UnsafeRawBufferPointer + ) -> Int where Element: BitwiseCopyable { + guard let sourceAddress = source.baseAddress, source.count > 0 else { + return 0 + } + let stride = MemoryLayout.stride + let (count, remainder) = source.count.quotientAndRemainder(dividingBy: stride) + precondition( + remainder == 0, + "source buffer must be a multiple of the destination's element's stride." + ) + precondition( + count <= self.count, + "destination span cannot contain every element from source." + ) + _start.copyMemory(from: sourceAddress, byteCount: source.count) + return count } - public mutating func update(fromContentsOf source: UnsafeMutableRawBufferPointer) -> Int { - return 0 + public mutating func update( + fromContentsOf source: UnsafeMutableRawBufferPointer + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: UnsafeRawBufferPointer(source)) } - public mutating func update(fromContentsOf source: Slice>) -> Int { - return 0 + public mutating func update( + fromContentsOf source: Slice> + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: .init(rebasing: source)) } - public mutating func update(fromContentsOf source: Slice>) -> Int { - return 0 + public mutating func update( + fromContentsOf source: Slice> + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: UnsafeBufferPointer(rebasing: source)) } - public mutating func update(fromContentsOf source: Slice) -> Int { - return 0 + public mutating func update( + fromContentsOf source: Slice + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: UnsafeRawBufferPointer(rebasing: source)) } - public mutating func update(fromContentsOf source: Slice) -> Int { - return 0 + public mutating func update( + fromContentsOf source: Slice + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: UnsafeRawBufferPointer(rebasing: source)) } } -#endif //MARK: copyMemory //FIXME: move these to a MutableRawSpan From 7311e07ff9953e9d94ac396b9926d52bf3a09728 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 2 Oct 2024 17:23:36 -0700 Subject: [PATCH 117/195] tests for MutableSpan --- Tests/FutureTests/MutableSpanTests.swift | 488 +++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 Tests/FutureTests/MutableSpanTests.swift diff --git a/Tests/FutureTests/MutableSpanTests.swift b/Tests/FutureTests/MutableSpanTests.swift new file mode 100644 index 000000000..411dab9ad --- /dev/null +++ b/Tests/FutureTests/MutableSpanTests.swift @@ -0,0 +1,488 @@ +//===--- MutableSpanTests.swift -------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +@testable import Future + +class ID { + let id: Int + init(id: Int) { + self.id = id + } + deinit { + // print(id) + } +} + +final class MutableSpanTests: XCTestCase { + + func testOptionalStorage() { +// XCTAssertEqual( +// MemoryLayout>.size, MemoryLayout?>.size +// ) +// XCTAssertEqual( +// MemoryLayout>.stride, MemoryLayout?>.stride +// ) +// XCTAssertEqual( +// MemoryLayout>.alignment, MemoryLayout?>.alignment +// ) + } + + func testInitOrdinaryElement() { + let capacity = 4 + var s = (0..(_unsafeBytes: $0) + XCTAssertEqual(b.count, capacity) + + let r = MutableSpan(_unsafeBytes: $0) + XCTAssertEqual(r.count, capacity*MemoryLayout.stride) + } + +// let v = UnsafeRawBufferPointer(start: nil, count: 0) +// XCTAssertNil(MutableSpan(unsafeRawBufferPointer: v)) + } + +// public func testIteratorOrdinaryElement() { +// let capacity = 4 +// var s = (0...stride) +// let bytes = count*MemoryLayout.stride + offset +// let align = MemoryLayout.alignment +// let p = UnsafeMutableRawPointer.allocate(byteCount: bytes, alignment: align) +// defer { p.deallocate() } +// for i in 0..(_unsafeStart: p+offset, byteCount: count*8) +// +// var buffered = 0 +// for value in 0...stride) + } + } + + func testSpanIndices() { + let capacity = 4 + var a = Array(0..(unsafeUninitializedCapacity: capacity) { + for i in $0.indices { + $0.initializeElement(at: i, to: .random(in: 0..<10)) + } + $1 = $0.count + } + a.withUnsafeMutableBufferPointer { + var v1 = MutableSpan(_unsafeElements: $0) + + XCTAssertEqual(v1._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 1)), false) + XCTAssertEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 0)._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(last: 0)), true) + XCTAssertEqual(v1._elementsEqual(v1), true) + XCTAssertEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 3)._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(last: 3)), false) + + var b = v1.withUnsafeMutableBufferPointer { Array($0) } + b.withUnsafeMutableBufferPointer { + let v2 = MutableSpan(_unsafeElements: $0) + XCTAssertEqual(v1._elementsEqual(v2), true) + } + } + } + + func testElementsEqualCollection() { + let capacity = 4 + var a = Array(0..( + _unsafeElements: .init(start: $0.baseAddress, count: 0) + ) + emptySpan.withUnsafeMutableBufferPointer { + XCTAssertEqual($0.count, 0) + XCTAssertNil($0.baseAddress) + } + } + XCTAssertEqual(Int(a[i]), i+1) + } + + public func testWithUnsafeMutableBytes() { + let capacity: UInt8 = 64 + var a = Array(0..( + _unsafeElements: .init(start: $0.baseAddress, count: 0) + ) + emptySpan.withUnsafeMutableBytes { + XCTAssertEqual($0.count, 0) + XCTAssertNil($0.baseAddress) + } + } + XCTAssertEqual(Int(a[i]), i+1) + } + + public func testUpdateRepeatingBitwiseCopyable() { + var a = Array(0..<8) + XCTAssertEqual(a.contains(.max), false) + a.withUnsafeMutableBufferPointer { + var span = MutableSpan(_unsafeElements: $0) + span.update(repeating: .max) + } + XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) + } + + public func testUpdateRepeating() { + var a = (0..<8).map(ID.init(id:)) + XCTAssertEqual(a.map(\.id).contains(.max), false) + a.withUnsafeMutableBufferPointer { + var span = MutableSpan(_unsafeElements: $0) + span.update(repeating: ID(id: .max)) + } + XCTAssertEqual(a.allSatisfy({ $0.id == .max }), true) + } + + public func testUpdateFromSequenceBitwiseCopyable() { + let capacity = 8 + var a = Array(repeating: Int.max, count: capacity) + XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) + a.withUnsafeMutableBufferPointer { + var span = MutableSpan(_unsafeElements: .init(start: nil, count: 0)) + var (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + span = MutableSpan(_unsafeElements: $0) + (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + (iterator, updated) = span.update(from: 0..<10000) + XCTAssertNotNil(iterator.next()) + XCTAssertEqual(updated, capacity) + } + XCTAssertEqual(a.elementsEqual(0...allocate(capacity: capacity) + var i = b.initialize(fromContentsOf: (0.. Date: Thu, 3 Oct 2024 14:32:35 -0700 Subject: [PATCH 118/195] annotations for mutablespan --- Sources/Future/MutableSpan.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 118a8b407..8ac971f85 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -440,6 +440,7 @@ extension MutableSpan where Element: BitwiseCopyable { } //MARK: bulk-update functions +@_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan { @_alwaysEmitIntoClient @@ -497,11 +498,13 @@ extension MutableSpan { return index } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: Span) -> Int { source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: borrowing MutableSpan @@ -546,6 +549,7 @@ extension MutableSpan { } } +@_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan where Element: ~Copyable { public mutating func moveUpdate( @@ -565,6 +569,7 @@ extension MutableSpan where Element: ~Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan { public mutating func moveUpdate( @@ -575,6 +580,7 @@ extension MutableSpan { } +@_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient @@ -638,12 +644,16 @@ extension MutableSpan where Element: BitwiseCopyable { return index } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Span ) -> Int where Element: BitwiseCopyable { source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient @unsafe public mutating func update( fromContentsOf source: RawSpan @@ -651,6 +661,8 @@ extension MutableSpan where Element: BitwiseCopyable { source.withUnsafeBytes { self.update(fromContentsOf: $0) } } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: borrowing MutableSpan ) -> Int where Element: BitwiseCopyable { @@ -674,12 +686,14 @@ extension MutableSpan where Element: BitwiseCopyable { return source.count } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: UnsafeMutableBufferPointer ) -> Int where Element: BitwiseCopyable { self.update(fromContentsOf: .init(source)) } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: UnsafeRawBufferPointer ) -> Int where Element: BitwiseCopyable { @@ -700,30 +714,35 @@ extension MutableSpan where Element: BitwiseCopyable { return count } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: UnsafeMutableRawBufferPointer ) -> Int where Element: BitwiseCopyable { self.update(fromContentsOf: UnsafeRawBufferPointer(source)) } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Slice> ) -> Int where Element: BitwiseCopyable { self.update(fromContentsOf: .init(rebasing: source)) } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Slice> ) -> Int where Element: BitwiseCopyable { self.update(fromContentsOf: UnsafeBufferPointer(rebasing: source)) } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Slice ) -> Int where Element: BitwiseCopyable { self.update(fromContentsOf: UnsafeRawBufferPointer(rebasing: source)) } + @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Slice ) -> Int where Element: BitwiseCopyable { @@ -733,8 +752,10 @@ extension MutableSpan where Element: BitwiseCopyable { //MARK: copyMemory //FIXME: move these to a MutableRawSpan +@_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan where Element: BitwiseCopyable { + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func copyMemory( from source: borrowing MutableSpan @@ -743,6 +764,8 @@ extension MutableSpan where Element: BitwiseCopyable { return copyMemory(from: .init(_unsafeMutableSpan: source)) } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public func copyMemory(from source: Span) -> Int { guard _isPOD(Element.self) else { fatalError() } precondition( From 28b93832bc7bb5791031f0b561ba895a0a994433 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 4 Oct 2024 08:21:11 -0700 Subject: [PATCH 119/195] clean up `update()` overloads for `MutableSpan` --- Sources/Future/MutableSpan.swift | 169 ++++++++++--------------------- Sources/Future/RawSpan.swift | 14 +++ 2 files changed, 69 insertions(+), 114 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 8ac971f85..52e346092 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -159,6 +159,22 @@ extension Span where Element: ~Copyable { } } +@_disallowFeatureSuppression(NonescapableTypes) +extension MutableSpan where Element: ~Copyable { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public var storage: Span { Span(_unsafeMutableSpan: self) } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public func withSpan( + _ body: (Span) throws(E) -> Result + ) throws(E) -> Result { + try body(Span(_unsafeMutableSpan: self)) + } +} + @_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { @@ -470,7 +486,7 @@ extension MutableSpan { fromContentsOf source: some Collection ) -> Int { let updated = source.withContiguousStorageIfAvailable { - self.update(fromContentsOf: $0) + self.update(fromContentsOf: Span(_unsafeElements: $0)) } if let updated { return 0.advanced(by: updated) @@ -487,11 +503,10 @@ extension MutableSpan { var iterator = source.makeIterator() var index = 0 while let value = iterator.next() { - guard index < _count else { - fatalError( - "destination buffer view cannot contain every element from source." - ) - } + precondition( + index < _count, + "destination buffer view cannot contain every element from source." + ) self[unchecked: index] = value index &+= 1 } @@ -501,51 +516,25 @@ extension MutableSpan { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update(fromContentsOf source: Span) -> Int { - source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: borrowing MutableSpan - ) -> Int { - source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: UnsafeBufferPointer - ) -> Int { - guard let sourceAddress = source.baseAddress, source.count > 0 else { return 0 } + guard !source.isEmpty else { return 0 } precondition( source.count <= self.count, "destination span cannot contain every element from source." ) - _start.withMemoryRebound(to: Element.self, capacity: source.count) { - $0.update(from: sourceAddress, count: source.count) + _start.withMemoryRebound(to: Element.self, capacity: source.count) { dest in + source._start.withMemoryRebound(to: Element.self, capacity: source.count) { + dest.update(from: $0, count: source.count) + } } return source.count } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Int { - self.update(fromContentsOf: .init(source)) - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: Slice> - ) -> Int { - self.update(fromContentsOf: .init(rebasing: source)) - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: Slice> + fromContentsOf source: borrowing MutableSpan ) -> Int { - self.update(fromContentsOf: UnsafeBufferPointer(rebasing: source)) + source.withSpan { self.update(fromContentsOf: $0) } } } @@ -616,7 +605,7 @@ extension MutableSpan where Element: BitwiseCopyable { fromContentsOf source: some Collection ) -> Int where Element: BitwiseCopyable { let updated = source.withContiguousStorageIfAvailable { - self.update(fromContentsOf: $0) + self.update(fromContentsOf: Span(_unsafeElements: $0)) } if let updated { return 0.advanced(by: updated) @@ -649,104 +638,64 @@ extension MutableSpan where Element: BitwiseCopyable { public mutating func update( fromContentsOf source: Span ) -> Int where Element: BitwiseCopyable { - source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - @unsafe - public mutating func update( - fromContentsOf source: RawSpan - ) -> Int where Element: BitwiseCopyable { - source.withUnsafeBytes { self.update(fromContentsOf: $0) } - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: borrowing MutableSpan - ) -> Int where Element: BitwiseCopyable { - source.withUnsafeBufferPointer { self.update(fromContentsOf: $0) } - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: UnsafeBufferPointer - ) -> Int where Element: BitwiseCopyable { - guard let sourceAddress = source.baseAddress, source.count > 0 else { - return 0 - } + guard !source.isEmpty else { return 0 } precondition( source.count <= self.count, "destination span cannot contain every element from source." ) _start.copyMemory( - from: sourceAddress, byteCount: source.count*MemoryLayout.stride + from: source._start, byteCount: source.count&*MemoryLayout.stride ) return source.count } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update( - fromContentsOf source: UnsafeMutableBufferPointer + fromContentsOf source: borrowing MutableSpan ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: .init(source)) + self.update(fromContentsOf: source.storage) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @unsafe public mutating func update( - fromContentsOf source: UnsafeRawBufferPointer + fromContentsOf source: RawSpan ) -> Int where Element: BitwiseCopyable { - guard let sourceAddress = source.baseAddress, source.count > 0 else { - return 0 - } - let stride = MemoryLayout.stride - let (count, remainder) = source.count.quotientAndRemainder(dividingBy: stride) - precondition( - remainder == 0, - "source buffer must be a multiple of the destination's element's stride." - ) - precondition( - count <= self.count, - "destination span cannot contain every element from source." - ) - _start.copyMemory(from: sourceAddress, byteCount: source.count) - return count + self.update(fromContentsOf: source.unsafeView(as: Element.self)) } - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: UnsafeMutableRawBufferPointer - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: UnsafeRawBufferPointer(source)) - } + // We have to define the overloads for raw buffers and their slices, + // otherwise they would try to use their `Collection` conformance + // and fail due to the mismatch in `Element` type. @_alwaysEmitIntoClient public mutating func update( - fromContentsOf source: Slice> + fromContentsOf source: UnsafeRawBufferPointer ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: .init(rebasing: source)) + self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) } @_alwaysEmitIntoClient public mutating func update( - fromContentsOf source: Slice> + fromContentsOf source: UnsafeMutableRawBufferPointer ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: UnsafeBufferPointer(rebasing: source)) + self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) } @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Slice ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: UnsafeRawBufferPointer(rebasing: source)) + self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) } @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Slice ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: UnsafeRawBufferPointer(rebasing: source)) + self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) } } @@ -757,25 +706,17 @@ extension MutableSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func copyMemory( + public mutating func copyMemory( from source: borrowing MutableSpan - ) -> Int { - guard _isPOD(Element.self) else { fatalError() } - return copyMemory(from: .init(_unsafeMutableSpan: source)) + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: source.storage) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func copyMemory(from source: Span) -> Int { - guard _isPOD(Element.self) else { fatalError() } - precondition( - source.count <= count, - "MutableSpan.copyMemory source has too many elements" - ) - UnsafeMutableRawPointer(_start).copyMemory( - from: source._start, - byteCount: source.count&*MemoryLayout.stride - ) - return 0.advanced(by: source.count) + public mutating func copyMemory( + from source: Span + ) -> Int where Element: BitwiseCopyable { + self.update(fromContentsOf: source) } } diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 431ccec50..e96f4567c 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -54,6 +54,13 @@ extension RawSpan { ) } + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: Slice + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) + } + /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -70,6 +77,13 @@ extension RawSpan { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } + @_alwaysEmitIntoClient + public init( + _unsafeBytes buffer: Slice + ) -> dependsOn(immortal) Self { + self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) + } + /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory over `count` bytes starting at From ffebc71d54165fcc7c77aa85af6c7310d01d0673 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 28 Sep 2024 06:07:01 -0700 Subject: [PATCH 120/195] OutputSpan modernization --- Sources/Future/OutputSpan.swift | 424 +++++++++++++----- .../Future/StdlibOutputSpanExtensions.swift | 15 +- Tests/FutureTests/OutputSpanTests.swift | 58 ++- 3 files changed, 375 insertions(+), 122 deletions(-) diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift index 83a8e5869..1009cc77f 100644 --- a/Sources/Future/OutputSpan.swift +++ b/Sources/Future/OutputSpan.swift @@ -10,54 +10,214 @@ // //===----------------------------------------------------------------------===// -public struct OutputSpan: ~Copyable, ~Escapable { - @usableFromInline let _start: UnsafeMutablePointer +// OutputSpan represents a span of memory which contains +// a variable number of `Element` instances, followed by uninitialized memory. +@_disallowFeatureSuppression(NonescapableTypes) +@frozen +public struct OutputSpan: ~Copyable, ~Escapable { + @usableFromInline let _pointer: UnsafeMutableRawPointer? + public let capacity: Int - public private(set) var initialized: Int = 0 + + @usableFromInline + var _initialized: Int = 0 + + @usableFromInline @inline(__always) + var _start: UnsafeMutableRawPointer { _pointer.unsafelyUnwrapped } + + @_alwaysEmitIntoClient + public var available: Int { capacity &- _initialized } + + @_alwaysEmitIntoClient + public var count: Int { _initialized } + + @_alwaysEmitIntoClient + public var isEmpty: Bool { _initialized == 0 } deinit { // `self` always borrows memory, and it shouldn't have gotten here. // Failing to use `relinquishBorrowedMemory()` is an error. - if initialized > 0 { + if _initialized > 0 { +#if false + _start.withMemoryRebound(to: Element.self, capacity: _initialized) { + $0.deinitialize(count: _initialized) + } +#else fatalError() +#endif } } - public init( - initializing pointer: UnsafeMutablePointer, + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) + init( + _unchecked start: UnsafeMutableRawPointer?, capacity: Int, - initialized: Int = 0, - owner: borrowing Owner - ) -> dependsOn(owner) Self { - self._start = pointer + initialized: Int + ) -> dependsOn(immortal) Self { + _pointer = start self.capacity = capacity - self.initialized = initialized + _initialized = initialized } } -extension OutputSpan where Element: ~Copyable /*& ~Escapable*/ { - public mutating func appendElement(_ value: consuming Element) { - precondition(initialized < capacity, "Output buffer overflow") - _start.advanced(by: initialized).initialize(to: value) - initialized &+= 1 +@_disallowFeatureSuppression(NonescapableTypes) +@available(*, unavailable) +extension OutputSpan: Sendable {} + +@_disallowFeatureSuppression(NonescapableTypes) +extension OutputSpan where Element: ~Copyable { + + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) + init( + _unchecked buffer: UnsafeMutableBufferPointer, + initialized: Int + ) -> dependsOn(immortal) Self { + _pointer = .init(buffer.baseAddress) + capacity = buffer.count + _initialized = initialized } - public mutating func deinitializeLastElement() -> Element? { - guard initialized > 0 else { return nil } - initialized &-= 1 - return _start.advanced(by: initialized).move() + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _initializing buffer: UnsafeMutableBufferPointer, + initialized: Int = 0 + ) -> dependsOn(immortal) Self { + precondition( + ((Int(bitPattern: buffer.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) + self.init(_unchecked: buffer, initialized: initialized) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _initializing pointer: UnsafeMutablePointer, + capacity: Int, + initialized: Int = 0 + ) -> dependsOn(immortal) Self { + precondition(capacity >= 0, "Capacity must be 0 or greater") + self.init( + _initializing: .init(start: pointer, count: capacity), + initialized: initialized + ) + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension OutputSpan { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _initializing buffer: Slice>, + initialized: Int = 0 + ) -> dependsOn(immortal) Self { + self.init(_initializing: .init(rebasing: buffer), initialized: initialized) + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension OutputSpan where Element: BitwiseCopyable { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _initializing bytes: UnsafeMutableRawBufferPointer, + initialized: Int = 0 + ) -> dependsOn(immortal) Self { + precondition( + ((Int(bitPattern: bytes.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) + let (byteCount, stride) = (bytes.count, MemoryLayout.stride) + let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(remainder == 0, "Span must contain a whole number of elements") + self.init( + _unchecked: bytes.baseAddress, capacity: count, initialized: initialized + ) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _initializing pointer: UnsafeMutableRawPointer, + capacity: Int, + initialized: Int = 0 + ) -> dependsOn(immortal) Self { + precondition(capacity >= 0, "Capacity must be 0 or greater") + self.init( + _initializing: .init(start: pointer, count: capacity), + initialized: initialized + ) + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public init( + _initializing buffer: Slice, + initialized: Int = 0 + ) -> dependsOn(immortal) Self { + self.init( + _initializing: UnsafeMutableRawBufferPointer(rebasing: buffer), + initialized: initialized + ) } } +@_disallowFeatureSuppression(NonescapableTypes) extension OutputSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + public mutating func appendElement(_ value: consuming Element) { + precondition(_initialized < capacity, "Output buffer overflow") + let p = _start.advanced(by: _initialized&*MemoryLayout.stride) + p.initializeMemory(as: Element.self, to: value) + _initialized &+= 1 + } + + @_alwaysEmitIntoClient + public mutating func deinitializeLastElement() -> Element? { + guard _initialized > 0 else { return nil } + _initialized &-= 1 + let p = _start.advanced(by: _initialized&*MemoryLayout.stride) + return p.withMemoryRebound(to: Element.self, capacity: 1, { $0.move() }) + } + + @_alwaysEmitIntoClient public mutating func deinitialize() { - let b = UnsafeMutableBufferPointer(start: _start, count: initialized) - b.deinitialize() - initialized = 0 + _ = _start.withMemoryRebound(to: Element.self, capacity: _initialized) { + $0.deinitialize(count: _initialized) + } + _initialized = 0 } } +//MARK: bulk-update functions +@_disallowFeatureSuppression(NonescapableTypes) extension OutputSpan { + + @_alwaysEmitIntoClient + public mutating func append(repeating repeatedValue: Element, count: Int) { + let available = capacity &- _initialized + precondition( + count <= available, + "destination span cannot contain number of elements requested." + ) + let offset = _initialized&*MemoryLayout.stride + let p = _start.advanced(by: offset) + p.withMemoryRebound(to: Element.self, capacity: count) { + $0.initialize(repeating: repeatedValue, count: count) + } + _initialized &+= count + } + + @_alwaysEmitIntoClient public mutating func append( from elements: S ) -> S.Iterator where S: Sequence, S.Element == Element { @@ -66,85 +226,138 @@ extension OutputSpan { return iterator } + @_alwaysEmitIntoClient public mutating func append( from elements: inout some IteratorProtocol ) { - while initialized < capacity { + while _initialized < capacity { guard let element = elements.next() else { break } - _start.advanced(by: initialized).initialize(to: element) - initialized &+= 1 + let p = _start.advanced(by: _initialized&*MemoryLayout.stride) + p.initializeMemory(as: Element.self, to: element) + _initialized &+= 1 } } + @_alwaysEmitIntoClient public mutating func append( fromContentsOf source: some Collection ) { - let count = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - let available = capacity &- initialized - precondition( - $0.count <= available, - "buffer cannot contain every element from source." - ) - let tail = _start.advanced(by: initialized) - tail.initialize(from: sourceAddress, count: $0.count) - return $0.count + let void: Void? = source.withContiguousStorageIfAvailable { +#if false + append(fromContentsOf: Span(_unsafeElements: $0)) +#else //FIXME: remove once rdar://136838539 & rdar://136849171 are fixed + append(fromContentsOf: $0) +#endif } - if let count { - initialized &+= count + if void != nil { return } - let available = capacity &- initialized - let tail = _start.advanced(by: initialized) - let suffix = UnsafeMutableBufferPointer(start: tail, count: available) - var (iterator, copied) = source._copyContents(initializing: suffix) + let available = capacity &- _initialized + let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + var (iterator, copied) = + tail.withMemoryRebound(to: Element.self, capacity: available) { + let suffix = UnsafeMutableBufferPointer(start: $0, count: available) + return source._copyContents(initializing: suffix) + } precondition( iterator.next() == nil, - "buffer cannot contain every element from source." + "destination span cannot contain every element from source." ) - assert(initialized + copied <= capacity) - initialized &+= copied + assert(_initialized + copied <= capacity) // invariant check + _initialized &+= copied + } + + //FIXME: remove once rdar://136838539 & rdar://136849171 are fixed + public mutating func append( + fromContentsOf source: UnsafeBufferPointer + ) { + guard !source.isEmpty else { return } + precondition( + source.count <= available, + "destination span cannot contain every element from source." + ) + let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + source.baseAddress!.withMemoryRebound(to: Element.self, capacity: source.count) { + _ = tail.initializeMemory(as: Element.self, from: $0, count: source.count) + } + _initialized += source.count } //FIXME: rdar://136838539 & rdar://136849171 - public mutating func append(fromContentsOf source: Span) { - let available = capacity &- initialized + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public mutating func append( + fromContentsOf source: Span + ) { + guard !source.isEmpty else { return } precondition( source.count <= available, - "buffer cannot contain every element from source." + "destination span cannot contain every element from source." ) - source.withUnsafeBufferPointer { - let tail = _start.advanced(by: initialized) - tail.initialize(from: $0.baseAddress!, count: $0.count) + let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + source._start.withMemoryRebound(to: Element.self, capacity: source.count) { + _ = tail.initializeMemory(as: Element.self, from: $0, count: source.count) } - initialized &+= source.count + _initialized += source.count + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public mutating func append(fromContentsOf source: borrowing MutableSpan) { + source.withUnsafeBufferPointer { append(fromContentsOf: $0) } } } -extension OutputSpan where Element: ~Copyable /*& ~Escapable*/ { +@_disallowFeatureSuppression(NonescapableTypes) +extension OutputSpan where Element: ~Copyable { + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public mutating func moveAppend( + fromContentsOf source: consuming Self + ) { + guard !source.isEmpty else { return } + precondition( + source.count <= available, + "buffer cannot contain every element from source." + ) + let buffer = source.relinquishBorrowedMemory() + // we must now deinitialize the returned UMBP + let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + tail.moveInitializeMemory( + as: Element.self, from: buffer.baseAddress!, count: buffer.count + ) + _initialized &+= buffer.count + } + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public mutating func moveAppend( fromContentsOf source: UnsafeMutableBufferPointer ) { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return - } - let available = capacity &- initialized +#if false //FIXME: rdar://136838539 & rdar://136849171 + let source = OutputSpan(_initializing: source, initialized: source.count) + moveAppend(fromContentsOf: source) +#else + guard !source.isEmpty else { return } precondition( source.count <= available, "buffer cannot contain every element from source." ) - let tail = _start.advanced(by: initialized) - tail.moveInitialize(from: sourceAddress, count: source.count) - initialized &+= source.count + let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + tail.moveInitializeMemory( + as: Element.self, from: source.baseAddress!, count: source.count + ) + _initialized &+= source.count +#endif } } extension OutputSpan { + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public mutating func moveAppend( fromContentsOf source: Slice> ) { @@ -152,64 +365,61 @@ extension OutputSpan { } } -//extension OutputSpan where Element: ~Copyable /*& ~Escapable*/ { -// public mutating func initializeSuffixOutOfOrder( -// _ count: Int, -// body: (inout RandomAccessOutputSpan) throws(E) -> R -// ) throws(E) -> R { -// precondition(initialized + count < capacity) -// var out = RandomAccessOutputSpan( -// initializing: _start.advanced(by: initialized), capacity: count, owner: self -// ) -// let result = try body(&out) -// let buffer = out.relinquishBorrowedMemory() -// assert( -// buffer.baseAddress == _start.advanced(by: initialized) && -// initialized + buffer.count < capacity -// ) -// initialized &+= buffer.count -// return result -// } -//} +extension OutputSpan where Element: BitwiseCopyable { -extension OutputSpan { +} + +extension OutputSpan where Element: ~Copyable { + + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public var initializedPrefix: Span { - get { Span(_unsafeStart: _start, count: initialized) } + get { Span(_unchecked: _pointer, count: _initialized) } } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient public func withSpan( _ body: (Span) throws(E) -> R ) throws(E) -> R { try body(initializedPrefix) } -} -// public var mutatingInitializedPrefix: /*inout*/ MutableBufferView { -// mutating /* _read */ get /* mutating(self) */ { -// /* yield */ MutableBufferView( -// unsafeMutablePointer: _start, -// count: initialized, -// dependsOn: /* self */ _start -// ) -// } -// } - -// public mutating func withMutableBufferView( -// _ body: (inout MutableBufferView) throws -> R -// ) rethrows -> R { -// var view = MutableBufferView( -// unsafeMutablePointer: _start, -// count: initialized, -// dependsOn: /* self */ _start -// ) -// return try body(&view) -// } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public mutating func withMutableSpan( + _ body: (inout MutableSpan) throws(E) -> Result + ) throws(E) -> Result { + var span = MutableSpan(_unchecked: _pointer, count: _initialized) + defer { + precondition( + span.count == _initialized && span._pointer == _start, + "Substituting the MutableSpan is unsound and unsafe." + ) + } + return try body(&span) + } +} -extension OutputSpan { +@_disallowFeatureSuppression(NonescapableTypes) +extension OutputSpan where Element: ~Copyable { + @_alwaysEmitIntoClient public consuming func relinquishBorrowedMemory() -> UnsafeMutableBufferPointer { - let (start, initialized) = (self._start, self.initialized) + let (start, count) = (self._pointer, self._initialized) + discard self + let typed = start?.bindMemory(to: Element.self, capacity: count) + return .init(start: typed, count: count) + } +} + +@_disallowFeatureSuppression(NonescapableTypes) +extension OutputSpan where Element: BitwiseCopyable { + + @_alwaysEmitIntoClient + public consuming func relinquishBorrowedBytes() -> UnsafeMutableRawBufferPointer { + let (start, count) = (self._pointer, self._initialized) discard self - return .init(start: start, count: initialized) + return .init(start: start, count: count&*MemoryLayout.stride) } } diff --git a/Sources/Future/StdlibOutputSpanExtensions.swift b/Sources/Future/StdlibOutputSpanExtensions.swift index 83e424b30..cc392a3c7 100644 --- a/Sources/Future/StdlibOutputSpanExtensions.swift +++ b/Sources/Future/StdlibOutputSpanExtensions.swift @@ -20,9 +20,8 @@ extension Array { unsafeUninitializedCapacity: capacity, initializingWith: { (buffer, count) in var output = OutputSpan( - initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: buffer.count, - owner: buffer + _initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: buffer.count ) try initializer(&output) let initialized = output.relinquishBorrowedMemory() @@ -47,9 +46,8 @@ extension String { unsafeUninitializedCapacity: capacity, initializingUTF8With: { buffer in var output = OutputSpan( - initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: capacity, - owner: buffer + _initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: capacity ) try initializer(&output) let initialized = output.relinquishBorrowedMemory() @@ -73,9 +71,8 @@ extension Data { try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in buffer.deinitialize() var output = OutputSpan( - initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: capacity, - owner: buffer + _initializing: buffer.baseAddress.unsafelyUnwrapped, + capacity: capacity ) try initializer(&output) let initialized = output.relinquishBorrowedMemory() diff --git a/Tests/FutureTests/OutputSpanTests.swift b/Tests/FutureTests/OutputSpanTests.swift index b475c206d..7b5ef5434 100644 --- a/Tests/FutureTests/OutputSpanTests.swift +++ b/Tests/FutureTests/OutputSpanTests.swift @@ -31,7 +31,7 @@ struct Allocation: ~Copyable { ) rethrows { if count != nil { fatalError() } var outputBuffer = OutputSpan( - initializing: allocation, capacity: capacity, owner: self + _initializing: allocation, capacity: capacity ) do { try body(&outputBuffer) @@ -85,7 +85,7 @@ final class OutputBufferTests: XCTestCase { let allocation = UnsafeMutablePointer.allocate(capacity: c) defer { allocation.deallocate() } - let ob = OutputSpan(initializing: allocation, capacity: c, owner: allocation) + let ob = OutputSpan(_initializing: allocation, capacity: c) let initialized = ob.relinquishBorrowedMemory() XCTAssertNotNil(initialized.baseAddress) XCTAssertEqual(initialized.count, 0) @@ -110,6 +110,24 @@ final class OutputBufferTests: XCTestCase { XCTAssert(span._elementsEqual(0..(_unsafeElements: UnsafeBufferPointer(start: nil, count: 0)) - $0.append(fromContentsOf: storage) + array.withUnsafeBufferPointer { + // let storage = Span(_unsafeElements: $0) + let storage = Span(_unsafeElements: .init(start: nil, count: 0)) + $0.append(fromContentsOf: storage) + } #else $0.append(fromContentsOf: array) #endif @@ -203,7 +223,7 @@ final class OutputBufferTests: XCTestCase { try a.initialize { $0.appendElement(0) $0.appendElement(1) - XCTAssertTrue($0.initialized > 0) + XCTAssertTrue($0.count > 0) throw MyTestError.error } } @@ -211,4 +231,30 @@ final class OutputBufferTests: XCTestCase { XCTAssertEqual(a.isInitialized, false) } } + + func testMutateOutputSpan() throws { + let b = UnsafeMutableBufferPointer.allocate(capacity: 10) + defer { b.deallocate() } + + var span = OutputSpan(_initializing: b) + XCTAssertEqual(span.count, 0) + span.append(fromContentsOf: 0..<10) + XCTAssertEqual(span.count, 10) + + span.withMutableSpan { +#if false + let b = UnsafeMutableBufferPointer.allocate(capacity: 8) + b.initialize(repeating: .max) + $0 = MutableSpan(_unsafeElements: b) +#else + for i in 0..<$0.count { + $0[i] *= 2 + } +#endif + } + + let r = span.relinquishBorrowedMemory() + print(Array(r)) + r.deinitialize() + } } From c8fc80381ec671b45e5aa1bba66e61d09249c90f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 4 Oct 2024 15:20:11 -0700 Subject: [PATCH 121/195] remove workarounds --- Sources/Future/Span.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 9a276b490..f6cfc1392 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -485,7 +485,7 @@ extension Span where Element: ~Copyable { @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { _read { - let element = UnsafeRawPointer(_start).advanced(by: position&*MemoryLayout.stride) + let element = _start.advanced(by: position&*MemoryLayout.stride) let binding = Builtin.bindMemory(element._rawValue, count._builtinWordValue, Element.self) defer { Builtin.rebindMemory(element._rawValue, binding) } yield UnsafePointer(element._rawValue).pointee @@ -522,7 +522,7 @@ extension Span where Element: BitwiseCopyable { @_alwaysEmitIntoClient public subscript(unchecked position: Int) -> Element { get { - let address = UnsafeRawPointer(_start).advanced(by: position&*MemoryLayout.stride) + let address = _start.advanced(by: position&*MemoryLayout.stride) return address.loadUnaligned(as: Element.self) } } From e9d7ea806a154e23aa51b8650c3f43e2ac8930b7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 4 Oct 2024 15:21:26 -0700 Subject: [PATCH 122/195] require `Span` to be aligned --- Sources/Future/Span.swift | 5 +++++ Tests/FutureTests/SpanTests.swift | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index f6cfc1392..cd63c5afa 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -209,6 +209,11 @@ extension Span where Element: BitwiseCopyable { public init( _unsafeBytes buffer: UnsafeRawBufferPointer ) -> dependsOn(immortal) Self { + precondition( + ((Int(bitPattern: buffer.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) let (byteCount, stride) = (buffer.count, MemoryLayout.stride) let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0, "Span must contain a whole number of elements") diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index df3a11486..b951eb3e9 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -387,10 +387,12 @@ final class SpanTests: XCTestCase { let subSpan1 = span._extracting(first: 6) let subSpan2 = span._extracting(last: 6) let emptySpan = span._extracting(first: 0) +/* This isn't relevant until we can support unaligned spans let unalignedSpan = RawSpan(_unsafeSpan: span) ._extracting(droppingFirst: 6) ._extracting(droppingLast: 2) .unsafeView(as: Int.self) +*/ let nilSpan = Span( _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) ) @@ -410,8 +412,6 @@ final class SpanTests: XCTestCase { XCTAssertNil(bounds) bounds = span.indices(of: nilSpan) XCTAssertNil(bounds) - bounds = span.indices(of: unalignedSpan) - XCTAssertNil(bounds) bounds = nilSpan.indices(of: nilSpan) XCTAssertEqual(bounds, 0..<0) } @@ -437,4 +437,21 @@ final class SpanTests: XCTestCase { i += 1 } } + + func testTypeErasedSpanOfBitwiseCopyable() { + let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 64, alignment: 8) + defer { b.deallocate() } + let initialized = b.initializeMemory(as: UInt8.self, fromContentsOf: 0..<64) + XCTAssertEqual(initialized.count, 64) + defer { initialized.deinitialize() } + + func test(_ span: Span) -> T { + span[0] + } + + // let span = Span(_unsafeBytes: b.dropFirst().dropLast(7)) + let span = Span(_unsafeBytes: b.dropFirst(4)) + let first = test(span) + XCTAssertEqual(first, 0x07060504) + } } From 57b5388c858f816f99a6c10ceb5a7736aabd1c5d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 4 Oct 2024 15:22:40 -0700 Subject: [PATCH 123/195] remove duplicated initializers - these have become duplicates in a world where Span must always be aligned --- Sources/Future/Span.swift | 76 +-------------------------------------- 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index cd63c5afa..8be06ca11 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -135,61 +135,6 @@ extension Span { @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. - /// - /// - Parameters: - /// - buffer: an `UnsafeBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeElements buffer: UnsafeBufferPointer - ) -> dependsOn(immortal) Self { - self.init(_unchecked: buffer) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. - /// - /// - Parameters: - /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeElements buffer: UnsafeMutableBufferPointer - ) -> dependsOn(immortal) Self { - self.init(_unsafeElements: UnsafeBufferPointer(buffer)) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory representing `count` instances starting at - /// `pointer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the span. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeStart start: UnsafePointer, - count: Int - ) -> dependsOn(immortal) Self { - precondition(count >= 0, "Count must not be negative") - self.init(_unchecked: start, count: count) - } - /// Unsafely create a `Span` over initialized memory. /// /// The memory in `unsafeBytes` must be owned by the instance `owner` @@ -260,26 +205,7 @@ extension Span where Element: BitwiseCopyable { byteCount: Int ) -> dependsOn(immortal) Self { precondition(byteCount >= 0, "Count must not be negative") - let stride = MemoryLayout.stride - let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0, "Span must contain a whole number of elements") - self.init(_unchecked: pointer, count: count) - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeElements buffer: Slice> - ) -> dependsOn(immortal) Self { - self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeElements buffer: Slice> - ) -> dependsOn(immortal) Self { - self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) + self.init(_unsafeBytes: .init(start: pointer, count: byteCount)) } @_disallowFeatureSuppression(NonescapableTypes) From f5f02e2f6a55c5382ee9171c5bc3bc5aa4c98850 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 4 Oct 2024 15:23:04 -0700 Subject: [PATCH 124/195] rename some tests --- Tests/FutureTests/SpanTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index b951eb3e9..eede50cfe 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -206,7 +206,7 @@ final class SpanTests: XCTestCase { } } - func testOffsetSubscript() { + func testElementSubscript() { let capacity = 4 let a = (0.. Date: Fri, 4 Oct 2024 15:41:26 -0700 Subject: [PATCH 125/195] `MutableSpan` must be aligned --- Sources/Future/MutableSpan.swift | 40 +++++++------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 52e346092..10bf47d60 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -80,7 +80,7 @@ extension MutableSpan { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - internal init( + public init( _unsafeElements elements: Slice> ) -> dependsOn(immortal) Self { self.init(_unsafeElements: UnsafeMutableBufferPointer(rebasing: elements)) @@ -90,29 +90,16 @@ extension MutableSpan { @_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan where Element: BitwiseCopyable { - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeElements buffer: UnsafeMutableBufferPointer - ) -> dependsOn(immortal) Self { - self.init(_unchecked: buffer) - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public init( - _unsafeStart start: UnsafeMutablePointer, - count: Int - ) -> dependsOn(immortal) Self { - precondition(count >= 0, "Count must not be negative") - self.init(_unchecked: start, count: count) - } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer ) -> dependsOn(immortal) Self { + precondition( + ((Int(bitPattern: buffer.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) let (byteCount, stride) = (buffer.count, MemoryLayout.stride) let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0, "Span must contain a whole number of elements") @@ -126,23 +113,12 @@ extension MutableSpan where Element: BitwiseCopyable { byteCount: Int ) -> dependsOn(immortal) Self { precondition(byteCount >= 0, "Count must not be negative") - let stride = MemoryLayout.stride - let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0, "Span must contain a whole number of elements") - self.init(_unchecked: pointer, count: count) - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - internal init( - _unsafeElements elements: Slice> - ) -> dependsOn(immortal) Self { - self.init(_unsafeElements: UnsafeMutableBufferPointer(rebasing: elements)) + self.init(_unsafeBytes: .init(start: pointer, count: byteCount)) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - internal init( + public init( _unsafeBytes buffer: Slice ) -> dependsOn(immortal) Self { self.init(_unsafeBytes: UnsafeMutableRawBufferPointer(rebasing: buffer)) From b82dd4eb9b3d80f945fdc2a5ada2e9d8f1703bb2 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 5 Oct 2024 00:10:37 -0700 Subject: [PATCH 126/195] refine update-from-sequence functions - a function that mutates an iterator is much more modular --- Sources/Future/MutableSpan.swift | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 10bf47d60..43d35ccbe 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -447,14 +447,21 @@ extension MutableSpan { from source: S ) -> (unwritten: S.Iterator, index: Int) where S.Element == Element { var iterator = source.makeIterator() - guard !self.isEmpty else { return (iterator, 0) } + let index = update(from: &iterator) + return (iterator, index) + } + + @_alwaysEmitIntoClient + public mutating func update( + from elements: inout some IteratorProtocol + ) -> Int { var index = 0 while index < _count { - guard let value = iterator.next() else { break } - self[unchecked: index] = value + guard let element = elements.next() else { break } + self[unchecked: index] = element index &+= 1 } - return (iterator, index) + return index } @_alwaysEmitIntoClient @@ -566,14 +573,21 @@ extension MutableSpan where Element: BitwiseCopyable { ) -> (unwritten: S.Iterator, index: Int) where S.Element == Element, Element: BitwiseCopyable { var iterator = source.makeIterator() - guard !self.isEmpty else { return (iterator, 0) } + let index = update(from: &iterator) + return (iterator, index) + } + + @_alwaysEmitIntoClient + public mutating func update( + from elements: inout some IteratorProtocol + ) -> Int { var index = 0 while index < _count { - guard let value = iterator.next() else { break } - self[unchecked: index] = value + guard let element = elements.next() else { break } + self[unchecked: index] = element index &+= 1 } - return (iterator, index) + return index } @_alwaysEmitIntoClient From dc63d5e048d6cae40999ae8badb08d12f86157ec Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 5 Oct 2024 00:51:41 -0700 Subject: [PATCH 127/195] add a safer `moveUpdate` --- Sources/Future/MutableSpan.swift | 23 +++++++++++++++++++++++ Tests/FutureTests/MutableSpanTests.swift | 11 +++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 43d35ccbe..76a3a41e5 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -524,9 +524,28 @@ extension MutableSpan { @_disallowFeatureSuppression(NonescapableTypes) extension MutableSpan where Element: ~Copyable { + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + public mutating func moveUpdate( + fromContentsOf source: consuming OutputSpan + ) -> Int { + guard !source.isEmpty else { return 0 } + precondition( + source.count <= self.count, + "destination span cannot contain every element from source." + ) + let buffer = source.relinquishBorrowedMemory() + // we must now deinitialize the returned UMBP + _start.moveInitializeMemory( + as: Element.self, from: buffer.baseAddress!, count: buffer.count + ) + return buffer.count + } + public mutating func moveUpdate( fromContentsOf source: UnsafeMutableBufferPointer ) -> Int { +#if false guard let sourceAddress = source.baseAddress, source.count > 0 else { return 0 } @@ -538,6 +557,10 @@ extension MutableSpan where Element: ~Copyable { $0.moveUpdate(from: sourceAddress, count: source.count) } return source.count +#else + let source = OutputSpan(_initializing: source, initialized: source.count) + return self.moveUpdate(fromContentsOf: source) +#endif } } diff --git a/Tests/FutureTests/MutableSpanTests.swift b/Tests/FutureTests/MutableSpanTests.swift index 411dab9ad..8fa98487b 100644 --- a/Tests/FutureTests/MutableSpanTests.swift +++ b/Tests/FutureTests/MutableSpanTests.swift @@ -459,19 +459,22 @@ final class MutableSpanTests: XCTestCase { XCTAssertEqual(a.allSatisfy({ $0.id == .max }), true) var b = UnsafeMutableBufferPointer.allocate(capacity: capacity) - var i = b.initialize(fromContentsOf: (0.. Date: Sun, 6 Oct 2024 11:16:43 -0700 Subject: [PATCH 128/195] post-rebase compatibility fixes --- Sources/DequeModule/Deque+Collection.swift | 2 ++ Sources/DequeModule/Deque+Equatable.swift | 2 ++ Sources/DequeModule/Deque.swift | 2 ++ Sources/DequeModule/RigidDeque.swift | 6 +++--- Sources/Future/Containers/NewArray.swift | 4 ++-- Sources/Future/Containers/RigidArray.swift | 10 ++++++---- .../OrderedSet/OrderedSet Diffing Tests.swift | 4 ++-- .../AssertionContexts/TestContext.swift | 2 +- 8 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index 8bf44b8f3..adb896a8b 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -13,6 +13,8 @@ import InternalCollectionsUtilities #endif +import Future + extension Deque { // Implementation note: we could also use the default `IndexingIterator` here. // This custom implementation performs direct storage access to eliminate any diff --git a/Sources/DequeModule/Deque+Equatable.swift b/Sources/DequeModule/Deque+Equatable.swift index 45d49c374..eb0f4709e 100644 --- a/Sources/DequeModule/Deque+Equatable.swift +++ b/Sources/DequeModule/Deque+Equatable.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import Future + extension Deque: Equatable where Element: Equatable { /// Returns a Boolean value indicating whether two values are equal. Two /// deques are considered equal if they contain the same elements in the same diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index 33b2cadcc..034bb806b 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import Future + /// A collection implementing a double-ended queue. `Deque` (pronounced "deck") /// implements an ordered random-access collection that supports efficient /// insertions and removals from both ends. diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index 35b39f1e2..118a4290f 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -95,16 +95,16 @@ public struct _DequeBorrowingIterator: BorrowingIteratorProt let d = Swift.min(maximumCount, _segments.first.count - _offset) let slice = _segments.first.extracting(_offset ..< _offset + d) _offset += d - return Span(unsafeElements: slice, owner: self) + return Span(_unsafeElements: slice) } guard let second = _segments.second else { - return Span(unsafeElements: UnsafeBufferPointer._empty, owner: self) + return Span(_unsafeElements: UnsafeBufferPointer._empty) } let o = _offset - _segments.first.count let d = Swift.min(maximumCount, second.count - o) let slice = second.extracting(o ..< o + d) _offset += d - return Span(unsafeElements: slice, owner: self) + return Span(_unsafeElements: slice) } } diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift index 8c9b01118..423fb565b 100644 --- a/Sources/Future/Containers/NewArray.swift +++ b/Sources/Future/Containers/NewArray.swift @@ -27,7 +27,7 @@ extension NewArray { // FIXME: This is what I want to write; alas, lifetimes are messed up. return _storage.value.storage #else - return Span(unsafeElements: _storage.value._items, owner: self) + return Span(_unsafeElements: _storage.value._items) #endif } } @@ -108,7 +108,7 @@ extension NewArray: RangeReplaceableCollection { self._storage = Shared(RigidArray(capacity: 0)) } - public func replaceSubrange( + mutating public func replaceSubrange( _ subrange: Range, with newElements: some Collection ) { diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index 5d190222a..86c049d88 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -60,7 +60,7 @@ extension RigidArray where Element: ~Copyable { extension RigidArray where Element: ~Copyable { public var storage: Span { - Span(unsafeElements: _items, owner: self) + Span(_unsafeElements: _items) } } @@ -84,7 +84,7 @@ extension RigidArray: RandomAccessContainer where Element: ~Copyable { let end = _offset + Swift.min(maximumCount, _items.count - _offset) defer { _offset = end } let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) - return Span(unsafeElements: chunk, owner: self) + return Span(_unsafeElements: chunk) } } @@ -242,7 +242,8 @@ extension RigidArray { internal func _copy(capacity: Int) -> Self { precondition(capacity >= count) var result = RigidArray(capacity: capacity) - result._storage.initializeAll(fromContentsOf: _storage) + let initialized = result._storage.initialize(fromContentsOf: _storage) + precondition(initialized == count) result._count = count return result } @@ -251,7 +252,8 @@ extension RigidArray { internal mutating func _move(capacity: Int) -> Self { precondition(capacity >= count) var result = RigidArray(capacity: capacity) - result._storage.moveInitializeAll(fromContentsOf: _storage) + let initialized = result._storage.moveInitialize(fromContentsOf: _storage) + precondition(initialized == count) result._count = count self._count = 0 return result diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift index b5ea03f20..9d012a8f0 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift @@ -18,13 +18,13 @@ import _CollectionsTestSupport #endif class MeasuringHashable: Hashable { - static var equalityChecks = 0 + nonisolated(unsafe) static var equalityChecks = 0 static func == (lhs: MeasuringHashable, rhs: MeasuringHashable) -> Bool { MeasuringHashable.equalityChecks += 1 return lhs._inner == rhs._inner } - static var hashChecks = 0 + nonisolated(unsafe) static var hashChecks = 0 func hash(into hasher: inout Hasher) { MeasuringHashable.hashChecks += 1 _inner.hash(into: &hasher) diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift index ad3b89af4..59816e677 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift @@ -20,7 +20,7 @@ public final class TestContext { internal var _trace: [Entry] = [] // FIXME: This ought to be a thread-local variable. - internal static var _current: TestContext? + nonisolated(unsafe) internal static var _current: TestContext? public init() {} } From bc567dfc315597b44caec1e87c4dfda5e5c4856c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Oct 2024 10:25:14 -0700 Subject: [PATCH 129/195] =?UTF-8?q?workaround=20for=20OutputSpan=E2=80=99s?= =?UTF-8?q?=20`deinit`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Future/OutputSpan.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift index 1009cc77f..628768195 100644 --- a/Sources/Future/OutputSpan.swift +++ b/Sources/Future/OutputSpan.swift @@ -35,16 +35,11 @@ public struct OutputSpan: ~Copyable, ~Escapable { public var isEmpty: Bool { _initialized == 0 } deinit { - // `self` always borrows memory, and it shouldn't have gotten here. - // Failing to use `relinquishBorrowedMemory()` is an error. if _initialized > 0 { -#if false _start.withMemoryRebound(to: Element.self, capacity: _initialized) { - $0.deinitialize(count: _initialized) + [ workaround = _initialized ] in + _ = $0.deinitialize(count: workaround) } -#else - fatalError() -#endif } } From 7f4bedbf0d9de4095f4bdf1489f0f49e2ab82982 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Oct 2024 23:33:21 -0700 Subject: [PATCH 130/195] improve OutputSpan tests --- Tests/FutureTests/OutputSpanTests.swift | 78 ++++++++----------- .../StdlibOutputSpanExtensionTests.swift | 2 +- 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/Tests/FutureTests/OutputSpanTests.swift b/Tests/FutureTests/OutputSpanTests.swift index 7b5ef5434..91aa91e62 100644 --- a/Tests/FutureTests/OutputSpanTests.swift +++ b/Tests/FutureTests/OutputSpanTests.swift @@ -24,11 +24,11 @@ struct Allocation: ~Copyable { allocation = UnsafeMutablePointer.allocate(capacity: capacity) } - var isInitialized: Bool { count != nil } + var isEmpty: Bool { (count ?? 0) == 0 } - mutating func initialize( - _ body: (/* mutating */ inout OutputSpan) throws -> Void - ) rethrows { + mutating func initialize( + _ body: (/* mutating */ inout OutputSpan) throws(E) -> Void + ) throws(E) { if count != nil { fatalError() } var outputBuffer = OutputSpan( _initializing: allocation, capacity: capacity @@ -48,26 +48,12 @@ struct Allocation: ~Copyable { } } - borrowing func examine( + borrowing func withSpan( _ body: (borrowing Span) throws(E) -> R ) throws(E) -> R { - try body(initializedPrefix) + try body(Span(_unsafeStart: allocation, count: count ?? 0)) } - var initializedPrefix: Span { - Span(_unsafeStart: allocation, count: count ?? 0) - } - -// mutating func mutate( -// _ body: (/* mutating */ inout MutableBufferView) -> R -// ) -> R { -// guard let count else { fatalError() } -// var mutable = MutableBufferView( -// baseAddress: allocation, count: count, dependsOn: /*self*/ allocation -// ) -// return body(&mutable) -// } - deinit { if let count { allocation.deinitialize(count: count) @@ -78,7 +64,7 @@ struct Allocation: ~Copyable { enum MyTestError: Error { case error } -final class OutputBufferTests: XCTestCase { +final class OutputSpanTests: XCTestCase { func testOutputBufferInitialization() { let c = 48 @@ -101,13 +87,10 @@ final class OutputBufferTests: XCTestCase { let oops = $0.deinitializeLastElement() XCTAssertEqual(oops, c) } - a.examine { + a.withSpan { XCTAssertEqual($0.count, c) XCTAssert($0._elementsEqual(0.. Date: Thu, 17 Oct 2024 09:51:38 -0700 Subject: [PATCH 131/195] add proper `indices` property, remove `boundsContain` --- Sources/Future/RawSpan.swift | 62 +++++---------- Sources/Future/Span.swift | 75 ++++++------------- .../FutureTests/ContiguousStorageTests.swift | 2 +- Tests/FutureTests/RawSpanTests.swift | 10 +-- Tests/FutureTests/SpanTests.swift | 8 +- .../StdlibSpanExtensionTests.swift | 13 ++-- 6 files changed, 56 insertions(+), 114 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index e96f4567c..20c0e7de8 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -207,45 +207,11 @@ extension RawSpan { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public var _byteOffsets: Range { + public var byteOffsets: Range { .init(uncheckedBounds: (0, byteCount)) } } -//MARK: Bounds Checking -extension RawSpan { - - /// Return true if `offset` is a valid byte offset into this `RawSpan` - /// - /// - Parameters: - /// - position: a byte offset to validate - /// - Returns: true if `offset` is a valid byte offset - @_alwaysEmitIntoClient - public func boundsContain(_ offset: Int) -> Bool { - 0 <= offset && offset < byteCount - } - - /// Return true if `offsets` is a valid range of offsets into this `RawSpan` - /// - /// - Parameters: - /// - offsets: a range of byte offsets to validate - /// - Returns: true if `offsets` is a valid range of byte offsets - @_alwaysEmitIntoClient - public func boundsContain(_ offsets: Range) -> Bool { - boundsContain(offsets.lowerBound) && offsets.upperBound <= byteCount - } - - /// Return true if `offsets` is a valid range of offsets into this `RawSpan` - /// - /// - Parameters: - /// - offsets: a range of byte offsets to validate - /// - Returns: true if `offsets` is a valid range of byte offsets - @_alwaysEmitIntoClient - public func boundsContain(_ offsets: ClosedRange) -> Bool { - boundsContain(offsets.lowerBound) && offsets.upperBound < byteCount - } -} - //MARK: extracting sub-spans extension RawSpan { @@ -264,7 +230,11 @@ extension RawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @usableFromInline func _extracting(_ bounds: Range) -> Self { - precondition(boundsContain(bounds)) + precondition( + UInt(bitPattern: bounds.lowerBound) < UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "byte offset range out of bounds" + ) return _extracting(unchecked: bounds) } @@ -316,7 +286,7 @@ extension RawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { - _extracting(bounds.relative(to: _byteOffsets)) + _extracting(bounds.relative(to: byteOffsets)) } @_alwaysEmitIntoClient @@ -343,7 +313,7 @@ extension RawSpan { @usableFromInline func _extracting( unchecked bounds: some RangeExpression ) -> Self { - _extracting(unchecked: bounds.relative(to: _byteOffsets)) + _extracting(unchecked: bounds.relative(to: byteOffsets)) } @_alwaysEmitIntoClient @@ -439,9 +409,11 @@ extension RawSpan { public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - precondition(boundsContain( - Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) - )) + precondition( + UInt(bitPattern: offset) < UInt(bitPattern: _count) && + UInt(bitPattern: offset&+MemoryLayout.size) <= UInt(bitPattern: _count), + "byte offset range out of bounds" + ) return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) } @@ -490,9 +462,11 @@ extension RawSpan { public func unsafeLoadUnaligned( fromByteOffset offset: Int = 0, as: T.Type ) -> T { - precondition(boundsContain( - Range(uncheckedBounds: (offset, offset+MemoryLayout.size)) - )) + precondition( + UInt(bitPattern: offset) < UInt(bitPattern: _count) && + UInt(bitPattern: offset&+MemoryLayout.size) <= UInt(bitPattern: _count), + "byte offset range out of bounds" + ) return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 8be06ca11..56cef40a7 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -326,54 +326,19 @@ extension Span where Element: ~Copyable { @_alwaysEmitIntoClient public var isEmpty: Bool { _count == 0 } + /// The representation for a position in `Span`. + public typealias Index = Int + /// The indices that are valid for subscripting the span, in ascending /// order. /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public var _indices: Range { + public var indices: Range { Range(uncheckedBounds: (0, _count)) } } -//MARK: Bounds Checking -@_disallowFeatureSuppression(NonescapableTypes) -extension Span where Element: ~Copyable { - - /// Return true if `index` is a valid offset into this `Span` - /// - /// - Parameters: - /// - index: an index to validate - /// - Returns: true if `index` is valid - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func boundsContain(_ index: Int) -> Bool { - 0 <= index && index < _count - } - - /// Return true if `indices` is a valid range of indices into this `Span` - /// - /// - Parameters: - /// - indices: a range of indices to validate - /// - Returns: true if `indices` is a valid range of indices - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func boundsContain(_ indices: Range) -> Bool { - boundsContain(indices.lowerBound) && indices.upperBound <= _count - } - - /// Return true if `indices` is a valid range of indices into this `Span` - /// - /// - Parameters: - /// - indices: a range of indices to validate - /// - Returns: true if `indices` is a valid range of indices - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func boundsContain(_ indices: ClosedRange) -> Bool { - boundsContain(indices.lowerBound) && indices.upperBound < _count - } -} - @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { @@ -397,9 +362,9 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public subscript(_ position: Int) -> Element { + public subscript(_ position: Index) -> Element { _read { - precondition(boundsContain(position), "index out of bounds") + precondition(indices.contains(position), "index out of bounds") yield self[unchecked: position] } } @@ -414,7 +379,7 @@ extension Span where Element: ~Copyable { /// - Complexity: O(1) @unsafe @_alwaysEmitIntoClient - public subscript(unchecked position: Int) -> Element { + public subscript(unchecked position: Index) -> Element { _read { let element = _start.advanced(by: position&*MemoryLayout.stride) let binding = Builtin.bindMemory(element._rawValue, count._builtinWordValue, Element.self) @@ -434,9 +399,9 @@ extension Span where Element: BitwiseCopyable { /// /// - Complexity: O(1) @_alwaysEmitIntoClient - public subscript(_ position: Int) -> Element { + public subscript(_ position: Index) -> Element { get { - precondition(boundsContain(position), "index out of bounds") + precondition(indices.contains(position), "index out of bounds") return self[unchecked: position] } } @@ -451,7 +416,7 @@ extension Span where Element: BitwiseCopyable { /// - Complexity: O(1) @unsafe @_alwaysEmitIntoClient - public subscript(unchecked position: Int) -> Element { + public subscript(unchecked position: Index) -> Element { get { let address = _start.advanced(by: position&*MemoryLayout.stride) return address.loadUnaligned(as: Element.self) @@ -477,14 +442,18 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(_ bounds: Range) -> Self { - precondition(boundsContain(bounds), "index out of bounds") + @usableFromInline func _extracting(_ bounds: Range) -> Self { + precondition( + UInt(bitPattern: bounds.lowerBound) < UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "index range out of bounds" + ) return _extracting(unchecked: bounds) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public mutating func _shrink(to bounds: Range) { + public mutating func _shrink(to bounds: Range) { self = _extracting(bounds) } @@ -505,7 +474,7 @@ extension Span where Element: ~Copyable { /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) @unsafe - @usableFromInline func _extracting(unchecked bounds: Range) -> Self { + @usableFromInline func _extracting(unchecked bounds: Range) -> Self { Span( _unchecked: _pointer?.advanced(by: bounds.lowerBound&*MemoryLayout.stride), count: bounds.count @@ -515,7 +484,7 @@ extension Span where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient - public mutating func _shrink(toUnchecked bounds: Range) { + public mutating func _shrink(toUnchecked bounds: Range) { self = _extracting(unchecked: bounds) } @@ -534,7 +503,7 @@ extension Span where Element: ~Copyable { /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { - _extracting(bounds.relative(to: _indices)) + _extracting(bounds.relative(to: indices)) } @_disallowFeatureSuppression(NonescapableTypes) @@ -563,7 +532,7 @@ extension Span where Element: ~Copyable { @usableFromInline func _extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { - _extracting(unchecked: bounds.relative(to: _indices)) + _extracting(unchecked: bounds.relative(to: indices)) } @_disallowFeatureSuppression(NonescapableTypes) @@ -689,7 +658,7 @@ extension Span where Element: ~Copyable { /// Returns: A range of indices within `self`, or `nil` @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func indices(of span: borrowing Self) -> Range? { + public func indices(of span: borrowing Self) -> Range? { if span._count > _count { return nil } guard let subspanStart = span._pointer, _count > 0 else { return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift index 07762a35f..b5a0ad33f 100644 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -12,7 +12,7 @@ final class ContiguousStorageTests: XCTestCase { let span = a.storage XCTAssertEqual(span.count, capacity) - for i in span._indices { + for i in span.indices { XCTAssertEqual(span[i], a[i]) } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 984833c1c..6ebfc689c 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -237,10 +237,10 @@ final class RawSpanTests: XCTestCase { let capacity = 4 let a = Array(0..? bounds = span.byteOffsets(of: subSpan1) - XCTAssertEqual(bounds, span._byteOffsets.prefix(6)) + XCTAssertEqual(bounds, span.byteOffsets.prefix(6)) bounds = span.byteOffsets(of: subSpan2) - XCTAssertEqual(bounds, span._byteOffsets.suffix(6)) + XCTAssertEqual(bounds, span.byteOffsets.suffix(6)) bounds = subSpan2.byteOffsets(of: subSpan1) XCTAssertNil(bounds) bounds = subSpan1.byteOffsets(of: subSpan2) diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index eede50cfe..8828524d1 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -123,10 +123,10 @@ final class SpanTests: XCTestCase { let a = Array(0..? bounds = span.indices(of: subSpan1) - XCTAssertEqual(bounds, span._indices.prefix(6)) + XCTAssertEqual(bounds, span.indices.prefix(6)) bounds = span.indices(of: subSpan2) - XCTAssertEqual(bounds, span._indices.suffix(6)) + XCTAssertEqual(bounds, span.indices.suffix(6)) bounds = subSpan2.indices(of: subSpan1) XCTAssertNil(bounds) bounds = subSpan1.indices(of: subSpan2) diff --git a/Tests/FutureTests/StdlibSpanExtensionTests.swift b/Tests/FutureTests/StdlibSpanExtensionTests.swift index 4c31cfda0..006c10663 100644 --- a/Tests/FutureTests/StdlibSpanExtensionTests.swift +++ b/Tests/FutureTests/StdlibSpanExtensionTests.swift @@ -21,7 +21,7 @@ final class StdlibSpanExtensionTests: XCTestCase { func testDataSpan() throws { let a = Data(0..<4) a.withSpan { - for i in $0._indices { + for i in $0.indices { XCTAssertEqual($0[i], UInt8(i)) } } @@ -35,7 +35,7 @@ final class StdlibSpanExtensionTests: XCTestCase { func testDataRawSpan() throws { let a = Data(0..<4) a.withBytes { - for i in $0._byteOffsets { + for i in $0.byteOffsets { XCTAssertEqual( $0.unsafeLoad(fromByteOffset: i, as: UInt8.self), UInt8(i) ) @@ -52,7 +52,7 @@ final class StdlibSpanExtensionTests: XCTestCase { let a = (0..<4).map(String.init(_:)) do throws(ErrorForTesting) { a.withSpan { - for i in $0._indices { + for i in $0.indices { XCTAssertEqual($0[i], String(i)) } } @@ -84,7 +84,7 @@ final class StdlibSpanExtensionTests: XCTestCase { func testContiguousArraySpan() throws { let a = ContiguousArray((0..<4).map(String.init(_:))) a.withSpan { - for i in $0._indices { + for i in $0.indices { XCTAssertEqual($0[i], String(i)) } } @@ -114,8 +114,7 @@ final class StdlibSpanExtensionTests: XCTestCase { let a = (0..<7).map(String.init(_:)).prefix(upTo: 4) print(a.count) a.withSpan { - print($0._indices) - for i in $0._indices { + for i in $0.indices { print(i) let v = $0[i] _ = v @@ -155,7 +154,7 @@ final class StdlibSpanExtensionTests: XCTestCase { func testCollectionOfOneRawSpan() throws { let a = CollectionOfOne(Int(UInt8.random(in: 0 ..< .max))) a.withBytes { - for i in $0._byteOffsets { + for i in $0.byteOffsets { let v = $0.unsafeLoad(fromByteOffset: i, as: UInt8.self) if v != 0 { XCTAssertEqual(Int(v), a.first) From fdc4b83b6807999cb4a9c7a36ea11a519de13d24 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 22 Oct 2024 17:48:29 -0700 Subject: [PATCH 132/195] span updates and tweaks --- Sources/Future/RawSpan.swift | 133 +++++++++++++++++------ Sources/Future/Span.swift | 204 +++++++++++++++++++++++------------ 2 files changed, 235 insertions(+), 102 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 20c0e7de8..4b4ac6ece 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -12,8 +12,9 @@ // A RawSpan represents a span of initialized memory // of unspecified type. +@_disallowFeatureSuppression(NonescapableTypes) @frozen -public struct RawSpan: Copyable, ~Escapable { +public struct RawSpan: ~Escapable, Copyable, BitwiseCopyable { @usableFromInline let _pointer: UnsafeRawPointer? @usableFromInline @inline(__always) @@ -21,19 +22,22 @@ public struct RawSpan: Copyable, ~Escapable { @usableFromInline let _count: Int - @_alwaysEmitIntoClient - internal init( - _unchecked start: UnsafeRawPointer?, + @_disallowFeatureSuppression(NonescapableTypes) + @usableFromInline @inline(__always) + @lifetime(immortal) + init( + _unchecked pointer: UnsafeRawPointer?, byteCount: Int - ) -> dependsOn(immortal) Self { - _pointer = start + ) { + _pointer = pointer _count = byteCount } } -@available(*, unavailable) -extension RawSpan: Sendable {} +@_disallowFeatureSuppression(NonescapableTypes) +extension RawSpan: @unchecked Sendable {} +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Unsafely create a `RawSpan` over initialized memory. @@ -45,19 +49,23 @@ extension RawSpan { /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: UnsafeRawBufferPointer - ) -> dependsOn(immortal) Self { + ) { self.init( _unchecked: buffer.baseAddress, byteCount: buffer.count ) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: Slice - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } @@ -70,17 +78,21 @@ extension RawSpan { /// - buffer: an `UnsafeMutableRawBufferPointer` to initialized memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: Slice - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } @@ -95,11 +107,13 @@ extension RawSpan { /// - byteCount: the number of initialized bytes in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeStart pointer: UnsafeRawPointer, byteCount: Int - ) -> dependsOn(immortal) Self { + ) { precondition(byteCount >= 0, "Count must not be negative") self.init(_unchecked: pointer, byteCount: byteCount) } @@ -113,10 +127,12 @@ extension RawSpan { /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeElements buffer: UnsafeBufferPointer - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } @@ -129,10 +145,12 @@ extension RawSpan { /// - buffer: an `UnsafeMutableRawBufferPointer` to initialized memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeElements buffer: UnsafeMutableBufferPointer - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } @@ -147,11 +165,13 @@ extension RawSpan { /// - byteCount: the number of initialized bytes in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `RawSpan`. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeStart pointer: UnsafePointer, count: Int - ) -> dependsOn(immortal) Self { + ) { precondition(count >= 0, "Count must not be negative") self.init( _unchecked: pointer, byteCount: count*MemoryLayout.stride @@ -163,10 +183,13 @@ extension RawSpan { /// - Parameters: /// - span: An existing `Span`, which will define both this /// `RawSpan`'s lifetime and the memory it represents. + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe // remove when fixing the lifetime annotation @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeSpan span: borrowing Span - ) -> dependsOn(immortal) Self { + ) { self.init( _unchecked: UnsafeRawPointer(span._start), byteCount: span.count &* MemoryLayout.stride @@ -176,15 +199,14 @@ extension RawSpan { extension RawSpan { - private var _address: String { - String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) - } - - public var _description: String { - "(0x\(_address), \(_count))" + @_alwaysEmitIntoClient + public var description: String { + let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) + return "(0x\(addr), \(_count))" } } +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// The number of bytes in the span. @@ -213,6 +235,7 @@ extension RawSpan { } //MARK: extracting sub-spans +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Constructs a new span over the bytes within the supplied range of @@ -228,16 +251,18 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(_ bounds: Range) -> Self { + public func _extracting(_ bounds: Range) -> Self { precondition( - UInt(bitPattern: bounds.lowerBound) < UInt(bitPattern: _count) && + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "byte offset range out of bounds" ) return _extracting(unchecked: bounds) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(to bounds: Range) { self = _extracting(bounds) @@ -258,14 +283,18 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient - @usableFromInline func _extracting(unchecked bounds: Range) -> Self { + public func _extracting(unchecked bounds: Range) -> Self { RawSpan( _unchecked: _pointer?.advanced(by: bounds.lowerBound), byteCount: bounds.count ) } + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public mutating func _shrink(toUnchecked bounds: Range) { self = _extracting(unchecked: bounds) @@ -284,11 +313,13 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { + public func _extracting(_ bounds: some RangeExpression) -> Self { _extracting(bounds.relative(to: byteOffsets)) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(_ bounds: some RangeExpression) { self = _extracting(bounds) @@ -309,13 +340,17 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient - @usableFromInline func _extracting( + public func _extracting( unchecked bounds: some RangeExpression ) -> Self { _extracting(unchecked: bounds.relative(to: byteOffsets)) } + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public mutating func _shrink(toUnchecked bounds: some RangeExpression) { self = _extracting(unchecked: bounds) @@ -330,12 +365,14 @@ extension RawSpan { /// - Returns: A span over all the bytes of this span. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(_: UnboundedRange) -> Self { + public func _extracting(_: UnboundedRange) -> Self { self } } +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { //FIXME: mark closure parameter as non-escaping @@ -353,14 +390,16 @@ extension RawSpan { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func withUnsafeBytes( + public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { try body(.init(start: (byteCount==0) ? nil : _start, count: byteCount)) } } +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// View the bytes of this span as type `T` @@ -377,15 +416,19 @@ extension RawSpan { /// - Parameters: /// - type: The type as which to view the bytes of this span. /// - Returns: A typed span viewing these bytes as instances of `T`. + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient + @lifetime(immortal) public func unsafeView( as type: T.Type ) -> Span { - Span(_unsafeStart: _start, byteCount: byteCount) + Span(_unsafeBytes: .init(start: _pointer, count: _count)) } } //MARK: load +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Returns a new instance of the given type, constructed from the raw memory @@ -405,6 +448,8 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public func unsafeLoad( fromByteOffset offset: Int = 0, as: T.Type @@ -435,6 +480,8 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public func unsafeLoad( fromUncheckedByteOffset offset: Int, as: T.Type @@ -458,6 +505,8 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public func unsafeLoadUnaligned( fromByteOffset offset: Int = 0, as: T.Type @@ -487,6 +536,8 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. + @_disallowFeatureSuppression(NonescapableTypes) + @unsafe @_alwaysEmitIntoClient public func unsafeLoadUnaligned( fromUncheckedByteOffset offset: Int, as: T.Type @@ -495,9 +546,11 @@ extension RawSpan { } } +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Returns a Boolean value indicating whether two `RawSpan` instances /// refer to the same region in memory. + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func isIdentical(to other: Self) -> Bool { (self._pointer == other._pointer) && (self._count == other._count) @@ -511,6 +564,7 @@ extension RawSpan { /// Parameters: /// - span: a subrange of `self` /// Returns: A range of offsets within `self` + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func byteOffsets(of span: borrowing Self) -> Range? { if span._count > _count { return nil } @@ -526,6 +580,7 @@ extension RawSpan { } //MARK: one-sided slicing operations +@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Returns a span containing the initial bytes of this span, @@ -543,13 +598,15 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(first maxLength: Int) -> Self { + public func _extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, byteCount) return Self(_unchecked: _pointer, byteCount: newCount) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(toFirst maxLength: Int) { self = _extracting(first: maxLength) @@ -569,13 +626,15 @@ extension RawSpan { /// - Returns: A span leaving off the specified number of bytes at the end. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(droppingLast k: Int) -> Self { + public func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) return Self(_unchecked: _pointer, byteCount: byteCount&-dc) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(droppingLast k: Int) { self = _extracting(droppingLast: k) @@ -596,14 +655,16 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(last maxLength: Int) -> Self { + public func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, byteCount) let newStart = _pointer?.advanced(by: byteCount&-newCount) return Self(_unchecked: newStart, byteCount: newCount) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(toLast maxLength: Int) { self = _extracting(last: maxLength) @@ -623,14 +684,16 @@ extension RawSpan { /// - Returns: A span starting after the specified number of bytes. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @usableFromInline func _extracting(droppingFirst k: Int) -> Self { + public func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let dc = min(k, byteCount) let newStart = _pointer?.advanced(by: dc) return Self(_unchecked: newStart, byteCount: byteCount&-dc) } + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func _shrink(droppingFirst k: Int) { self = _extracting(droppingFirst: k) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 56cef40a7..c26ee201b 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -16,7 +16,8 @@ import Builtin // contains initialized instances of `Element`. @_disallowFeatureSuppression(NonescapableTypes) @frozen -public struct Span: Copyable, ~Escapable { +public struct Span +: ~Escapable, Copyable, BitwiseCopyable { @usableFromInline let _pointer: UnsafeRawPointer? @usableFromInline @inline(__always) @@ -26,35 +27,26 @@ public struct Span: Copyable, ~Escapable { @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) + @lifetime(immortal) init( - _unchecked start: UnsafeRawPointer?, + _unchecked pointer: UnsafeRawPointer?, count: Int - ) -> dependsOn(immortal) Self { - _pointer = start + ) { + _pointer = pointer _count = count } } @_disallowFeatureSuppression(NonescapableTypes) -@available(*, unavailable) -extension Span: Sendable {} +extension Span: @unchecked Sendable where Element: Sendable {} @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline @inline(__always) - internal init( - _unchecked elements: UnsafeBufferPointer - ) -> dependsOn(immortal) Self { - _pointer = .init(elements.baseAddress) - _count = elements.count - } - - /// Unsafely create a `Span` over initialized memory. + /// Unsafely creates a `Span` over initialized memory. /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. /// /// - Parameters: /// - buffer: an `UnsafeBufferPointer` to initialized elements. @@ -62,21 +54,22 @@ extension Span where Element: ~Copyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeElements buffer: UnsafeBufferPointer - ) -> dependsOn(immortal) Self { + ) { precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment&-1)) == 0), "baseAddress must be properly aligned to access Element" ) - self.init(_unchecked: buffer) + self.init(_unchecked: buffer.baseAddress, count: buffer.count) } - /// Unsafely create a `Span` over initialized memory. + /// Unsafely creates a `Span` over initialized memory. /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. /// /// - Parameters: /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. @@ -84,17 +77,18 @@ extension Span where Element: ~Copyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeElements buffer: UnsafeMutableBufferPointer - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } - /// Unsafely create a `Span` over initialized memory. + /// Unsafely creates a `Span` over initialized memory. /// /// The memory representing `count` instances starting at - /// `pointer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// `pointer` must remain valid throughout the lifetime of + /// the newly-created `Span`. /// /// - Parameters: /// - pointer: a pointer to the first initialized element. @@ -103,10 +97,11 @@ extension Span where Element: ~Copyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeStart start: UnsafePointer, count: Int - ) -> dependsOn(immortal) Self { + ) { precondition(count >= 0, "Count must not be negative") self.init(_unsafeElements: .init(start: start, count: count)) } @@ -115,19 +110,39 @@ extension Span where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) extension Span { + /// Unsafely creates a `Span` over initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. + /// + /// - Parameters: + /// - buffer: an `UnsafeBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeElements buffer: Slice> - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) } + /// Unsafely creates a `Span` over initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeElements buffer: Slice> - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) } } @@ -135,25 +150,26 @@ extension Span { @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { - /// Unsafely create a `Span` over initialized memory. + /// Unsafely creates a `Span` over initialized memory. /// - /// The memory in `unsafeBytes` must be owned by the instance `owner` - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. /// - /// `unsafeBytes` must be correctly aligned for accessing + /// `buffer` must be correctly aligned for accessing /// an element of type `Element`, and must contain a number of bytes /// that is an exact multiple of `Element`'s stride. /// /// - Parameters: - /// - unsafeBytes: a buffer to initialized elements. + /// - buffer: a buffer to initialized elements. /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: UnsafeRawBufferPointer - ) -> dependsOn(immortal) Self { + ) { precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment&-1)) == 0), @@ -165,62 +181,98 @@ extension Span where Element: BitwiseCopyable { self.init(_unchecked: buffer.baseAddress, count: count) } - /// Unsafely create a `Span` over initialized memory. + /// Unsafely creates a `Span` over initialized memory. /// - /// The memory in `unsafeBytes` must be owned by the instance `owner` - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. /// - /// `unsafeBytes` must be correctly aligned for accessing + /// `buffer` must be correctly aligned for accessing /// an element of type `Element`, and must contain a number of bytes /// that is an exact multiple of `Element`'s stride. /// /// - Parameters: - /// - unsafeBytes: a buffer to initialized elements. + /// - buffer: a buffer to initialized elements. /// - type: the type to use when interpreting the bytes in memory. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } - /// Unsafely create a `Span` over initialized memory. + /// Unsafely creates a `Span` over initialized memory. /// /// The memory representing `count` instances starting at - /// `pointer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// `pointer` must remain valid throughout the lifetime of + /// the newly-created `Span`. + /// + /// `pointer` must be correctly aligned for accessing + /// an element of type `Element`, and `byteCount` + /// must be an exact multiple of `Element`'s stride. /// /// - Parameters: /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the span. + /// - byteCount: the number of initialized elements in the span. /// - owner: a binding whose lifetime must exceed that of /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeStart pointer: UnsafeRawPointer, byteCount: Int - ) -> dependsOn(immortal) Self { + ) { precondition(byteCount >= 0, "Count must not be negative") self.init(_unsafeBytes: .init(start: pointer, count: byteCount)) } + /// Unsafely creates a `Span` over initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. + /// + /// `buffer` must be correctly aligned for accessing + /// an element of type `Element`, and must contain a number of bytes + /// that is an exact multiple of `Element`'s stride. + /// + /// - Parameters: + /// - buffer: a buffer to initialized elements. + /// - type: the type to use when interpreting the bytes in memory. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: Slice - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } + /// Unsafely creates a `Span` over initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime of + /// the newly-created `Span`. + /// + /// `buffer` must be correctly aligned for accessing + /// an element of type `Element`, and must contain a number of bytes + /// that is an exact multiple of `Element`'s stride. + /// + /// - Parameters: + /// - buffer: a buffer to initialized elements. + /// - type: the type to use when interpreting the bytes in memory. + /// - owner: a binding whose lifetime must exceed that of + /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(immortal) public init( _unsafeBytes buffer: Slice - ) -> dependsOn(immortal) Self { + ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } } @@ -333,6 +385,7 @@ extension Span where Element: ~Copyable { /// order. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public var indices: Range { Range(uncheckedBounds: (0, _count)) @@ -351,7 +404,6 @@ extension Span where Element: BitwiseCopyable { public var _unsafeRawSpan: RawSpan { RawSpan(_unsafeSpan: self) } } -//MARK: integer offset subscripts @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { @@ -361,8 +413,10 @@ extension Span where Element: ~Copyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public subscript(_ position: Index) -> Element { + //FIXME: change to unsafeRawAddress or unsafeAddress when ready _read { precondition(indices.contains(position), "index out of bounds") yield self[unchecked: position] @@ -377,9 +431,11 @@ extension Span where Element: ~Copyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { + //FIXME: change to unsafeRawAddress or unsafeAddress when ready _read { let element = _start.advanced(by: position&*MemoryLayout.stride) let binding = Builtin.bindMemory(element._rawValue, count._builtinWordValue, Element.self) @@ -398,10 +454,14 @@ extension Span where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public subscript(_ position: Index) -> Element { get { - precondition(indices.contains(position), "index out of bounds") + precondition( + UInt(bitPattern: position) < UInt(bitPattern: _count), + "index out of bounds" + ) return self[unchecked: position] } } @@ -414,6 +474,7 @@ extension Span where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { @@ -442,9 +503,10 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(_ bounds: Range) -> Self { + @_alwaysEmitIntoClient + public func _extracting(_ bounds: Range) -> Self { precondition( - UInt(bitPattern: bounds.lowerBound) < UInt(bitPattern: _count) && + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "index range out of bounds" ) @@ -474,11 +536,10 @@ extension Span where Element: ~Copyable { /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) @unsafe - @usableFromInline func _extracting(unchecked bounds: Range) -> Self { - Span( - _unchecked: _pointer?.advanced(by: bounds.lowerBound&*MemoryLayout.stride), - count: bounds.count - ) + @_alwaysEmitIntoClient + public func _extracting(unchecked bounds: Range) -> Self { + let delta = bounds.lowerBound&*MemoryLayout.stride + return Span(_unchecked: _pointer?.advanced(by: delta), count: bounds.count) } @_disallowFeatureSuppression(NonescapableTypes) @@ -502,7 +563,8 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(_ bounds: some RangeExpression) -> Self { + @_alwaysEmitIntoClient + public func _extracting(_ bounds: some RangeExpression) -> Self { _extracting(bounds.relative(to: indices)) } @@ -529,7 +591,8 @@ extension Span where Element: ~Copyable { /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) @unsafe - @usableFromInline func _extracting( + @_alwaysEmitIntoClient + public func _extracting( uncheckedBounds bounds: some RangeExpression ) -> Self { _extracting(unchecked: bounds.relative(to: indices)) @@ -552,7 +615,8 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(_: UnboundedRange) -> Self { + @_alwaysEmitIntoClient + public func _extracting(_: UnboundedRange) -> Self { self } } @@ -582,7 +646,9 @@ extension Span where Element: ~Copyable { guard let pointer = _pointer, count > 0 else { return try body(.init(start: nil, count: 0)) } - let binding = Builtin.bindMemory(pointer._rawValue, count._builtinWordValue, Element.self) + let binding = Builtin.bindMemory( + pointer._rawValue, count._builtinWordValue, Element.self + ) defer { Builtin.rebindMemory(pointer._rawValue, binding) } return try body(.init(start: .init(pointer._rawValue), count: count)) } @@ -674,7 +740,7 @@ extension Span where Element: ~Copyable { } } -//MARK: one-sided slicing operations +//MARK: prefixes and suffixes @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { @@ -694,7 +760,8 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(first maxLength: Int) -> Self { + @_alwaysEmitIntoClient + public func _extracting(first maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a prefix of negative length.") let newCount = min(maxLength, count) return Self(_unchecked: _pointer, count: newCount) @@ -721,7 +788,8 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(droppingLast k: Int) -> Self { + @_alwaysEmitIntoClient + public func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) return Self(_unchecked: _pointer, count: count&-droppedCount) @@ -749,7 +817,8 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(last maxLength: Int) -> Self { + @_alwaysEmitIntoClient + public func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, count) let newStart = _pointer?.advanced(by: (count&-newCount)*MemoryLayout.stride) @@ -777,7 +846,8 @@ extension Span where Element: ~Copyable { /// /// - Complexity: O(1) @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline func _extracting(droppingFirst k: Int) -> Self { + @_alwaysEmitIntoClient + public func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) let newStart = _pointer?.advanced(by: droppedCount*MemoryLayout.stride) From 8f646669f8941cf6c62132643bf82893ca04f3fb Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 22 Oct 2024 17:48:39 -0700 Subject: [PATCH 133/195] update span tests --- Tests/FutureTests/RawSpanTests.swift | 17 +++++------------ Tests/FutureTests/SpanTests.swift | 5 ++++- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 6ebfc689c..0701a4887 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -157,8 +157,8 @@ final class RawSpanTests: XCTestCase { } // Should we be able to derive a non-escapable value from a Span via unsafe pointers? - let copy = span.withUnsafeBytes { RawSpan(_unsafeBytes: $0) } - _ = copy +// let copy: RawSpan = span.withUnsafeBytes { RawSpan(_unsafeBytes: $0) } +// _ = copy } func testStrangeBorrow() { @@ -200,6 +200,9 @@ final class RawSpanTests: XCTestCase { ), UInt8(capacity-2) ) + let emptySpan = span._extracting(first: 0) + let emptierSpan = emptySpan._extracting(0..<0) + XCTAssertTrue(emptySpan.isIdentical(to: emptierSpan)) } do { @@ -233,16 +236,6 @@ final class RawSpanTests: XCTestCase { } } - func testBoundsChecking() { - let capacity = 4 - let a = Array(0...stride) } } @@ -191,6 +191,9 @@ final class SpanTests: XCTestCase { XCTAssertTrue(span._elementsEqual(span._extracting(0...))) XCTAssertTrue(span._elementsEqual(span._extracting(uncheckedBounds: .. Date: Wed, 23 Oct 2024 15:11:34 -0700 Subject: [PATCH 134/195] more minor tweaks (and also remove _shrink) --- Sources/Future/MutableSpan.swift | 2 +- Sources/Future/RawSpan.swift | 73 +++++----------------------- Sources/Future/Span.swift | 72 +++++---------------------- Tests/FutureTests/RawSpanTests.swift | 17 +++---- Tests/FutureTests/SpanTests.swift | 6 --- 5 files changed, 32 insertions(+), 138 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 76a3a41e5..1b9b050f2 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -676,7 +676,7 @@ extension MutableSpan where Element: BitwiseCopyable { public mutating func update( fromContentsOf source: RawSpan ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: source.unsafeView(as: Element.self)) + self.update(fromContentsOf: source._unsafeView(as: Element.self)) } // We have to define the overloads for raw buffers and their slices, diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 4b4ac6ece..aeb49158c 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -262,12 +262,6 @@ extension RawSpan { return _extracting(unchecked: bounds) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(to bounds: Range) { - self = _extracting(bounds) - } - /// Constructs a new span over the bytes within the supplied range of /// positions within this span. /// @@ -293,13 +287,6 @@ extension RawSpan { ) } - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe - @_alwaysEmitIntoClient - public mutating func _shrink(toUnchecked bounds: Range) { - self = _extracting(unchecked: bounds) - } - /// Constructs a new span over the bytes within the supplied range of /// positions within this span. /// @@ -319,12 +306,6 @@ extension RawSpan { _extracting(bounds.relative(to: byteOffsets)) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(_ bounds: some RangeExpression) { - self = _extracting(bounds) - } - /// Constructs a new span over the bytes within the supplied range of /// positions within this span. /// @@ -349,13 +330,6 @@ extension RawSpan { _extracting(unchecked: bounds.relative(to: byteOffsets)) } - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe - @_alwaysEmitIntoClient - public mutating func _shrink(toUnchecked bounds: some RangeExpression) { - self = _extracting(unchecked: bounds) - } - /// Constructs a new span over all the bytes of this span. /// /// The returned span's first byte is always at offset 0; unlike buffer @@ -395,7 +369,7 @@ extension RawSpan { public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { - try body(.init(start: (byteCount==0) ? nil : _start, count: byteCount)) + try body(.init(start: _pointer, count: byteCount)) } } @@ -420,7 +394,7 @@ extension RawSpan { @unsafe @_alwaysEmitIntoClient @lifetime(immortal) - public func unsafeView( + public func _unsafeView( as type: T.Type ) -> Span { Span(_unsafeBytes: .init(start: _pointer, count: _count)) @@ -455,8 +429,8 @@ extension RawSpan { fromByteOffset offset: Int = 0, as: T.Type ) -> T { precondition( - UInt(bitPattern: offset) < UInt(bitPattern: _count) && - UInt(bitPattern: offset&+MemoryLayout.size) <= UInt(bitPattern: _count), + UInt(bitPattern: offset) <= UInt(bitPattern: _count) && + MemoryLayout.size <= (_count &- offset), "byte offset range out of bounds" ) return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) @@ -512,8 +486,8 @@ extension RawSpan { fromByteOffset offset: Int = 0, as: T.Type ) -> T { precondition( - UInt(bitPattern: offset) < UInt(bitPattern: _count) && - UInt(bitPattern: offset&+MemoryLayout.size) <= UInt(bitPattern: _count), + UInt(bitPattern: offset) <= UInt(bitPattern: _count) && + MemoryLayout.size <= (_count &- offset), "byte offset range out of bounds" ) return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) @@ -568,14 +542,13 @@ extension RawSpan { @_alwaysEmitIntoClient public func byteOffsets(of span: borrowing Self) -> Range? { if span._count > _count { return nil } - guard let subspanStart = span._pointer, _count > 0 else { + guard let spanStart = span._pointer, _count > 0 else { return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil } - if subspanStart < _start { return nil } - let lower = _start.distance(to: subspanStart) - let upper = lower + span._count - guard upper <= _count else { return nil } - return Range(uncheckedBounds: (lower, upper)) + let spanEnd = spanStart + span._count + if spanStart < _start || (_start + _count) < spanEnd { return nil } + let lower = _start.distance(to: spanStart) + return Range(uncheckedBounds: (lower, lower &+ span._count)) } } @@ -606,12 +579,6 @@ extension RawSpan { return Self(_unchecked: _pointer, byteCount: newCount) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(toFirst maxLength: Int) { - self = _extracting(first: maxLength) - } - /// Returns a span over all but the given number of trailing bytes. /// /// If the number of elements to drop exceeds the number of elements in @@ -634,12 +601,6 @@ extension RawSpan { return Self(_unchecked: _pointer, byteCount: byteCount&-dc) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(droppingLast k: Int) { - self = _extracting(droppingLast: k) - } - /// Returns a span containing the trailing bytes of the span, /// up to the given maximum length. /// @@ -664,12 +625,6 @@ extension RawSpan { return Self(_unchecked: newStart, byteCount: newCount) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(toLast maxLength: Int) { - self = _extracting(last: maxLength) - } - /// Returns a span over all but the given number of initial bytes. /// /// If the number of elements to drop exceeds the number of bytes in @@ -692,10 +647,4 @@ extension RawSpan { let newStart = _pointer?.advanced(by: dc) return Self(_unchecked: newStart, byteCount: byteCount&-dc) } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(droppingFirst k: Int) { - self = _extracting(droppingFirst: k) - } } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index c26ee201b..aceda3c97 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -321,6 +321,11 @@ extension Span where Element: Equatable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { + let equal = other.withContiguousStorageIfAvailable { + _elementsEqual(Span(_unsafeElements: $0)) + } + if let equal { return equal } + guard count == other.count else { return false } if count == 0 { return true } @@ -485,7 +490,7 @@ extension Span where Element: BitwiseCopyable { } } -//MARK: extracting sub-spans +//MARK: sub-spans @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { @@ -513,12 +518,6 @@ extension Span where Element: ~Copyable { return _extracting(unchecked: bounds) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(to bounds: Range) { - self = _extracting(bounds) - } - /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -542,13 +541,6 @@ extension Span where Element: ~Copyable { return Span(_unchecked: _pointer?.advanced(by: delta), count: bounds.count) } - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe - @_alwaysEmitIntoClient - public mutating func _shrink(toUnchecked bounds: Range) { - self = _extracting(unchecked: bounds) - } - /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -568,12 +560,6 @@ extension Span where Element: ~Copyable { _extracting(bounds.relative(to: indices)) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(to bounds: some RangeExpression) { - self = _extracting(bounds) - } - /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -598,13 +584,6 @@ extension Span where Element: ~Copyable { _extracting(unchecked: bounds.relative(to: indices)) } - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe - @_alwaysEmitIntoClient - public mutating func _shrink(toUnchecked bounds: some RangeExpression) { - self = _extracting(uncheckedBounds: bounds) - } - /// Constructs a new span over all the items of this span. /// /// The returned span's first item is always at offset 0; unlike buffer @@ -621,7 +600,7 @@ extension Span where Element: ~Copyable { } } -//MARK: withUnsafePointer, etc. +//MARK: UnsafeBufferPointer access hatch @_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { @@ -643,7 +622,7 @@ extension Span where Element: ~Copyable { public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result ) throws(E) -> Result { - guard let pointer = _pointer, count > 0 else { + guard let pointer = _pointer else { return try body(.init(start: nil, count: 0)) } let binding = Builtin.bindMemory( @@ -726,17 +705,16 @@ extension Span where Element: ~Copyable { @_alwaysEmitIntoClient public func indices(of span: borrowing Self) -> Range? { if span._count > _count { return nil } - guard let subspanStart = span._pointer, _count > 0 else { + guard let spanStart = span._pointer, _count > 0 else { return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil } - if subspanStart < _start { return nil } - let byteOffset = _start.distance(to: subspanStart) let stride = MemoryLayout.stride + let spanEnd = spanStart + stride&*span._count + if spanStart < _start || spanEnd > (_start + stride&*_count) { return nil } + let byteOffset = _start.distance(to: spanStart) let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) guard r == 0 else { return nil } - let upper = lower + span._count - guard upper <= _count else { return nil } - return Range(uncheckedBounds: (lower, upper)) + return Range(uncheckedBounds: (lower, lower &+ span._count)) } } @@ -767,12 +745,6 @@ extension Span where Element: ~Copyable { return Self(_unchecked: _pointer, count: newCount) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(toFirst maxLength: Int) { - self = _extracting(first: maxLength) - } - /// Returns a span over all but the given number of trailing elements. /// /// If the number of elements to drop exceeds the number of elements in @@ -795,12 +767,6 @@ extension Span where Element: ~Copyable { return Self(_unchecked: _pointer, count: count&-droppedCount) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(droppingLast k: Int) { - self = _extracting(droppingLast: k) - } - /// Returns a span containing the final elements of the span, /// up to the given maximum length. /// @@ -825,12 +791,6 @@ extension Span where Element: ~Copyable { return Self(_unchecked: newStart, count: newCount) } - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(toLast maxLength: Int) { - self = _extracting(last: maxLength) - } - /// Returns a span over all but the given number of initial elements. /// /// If the number of elements to drop exceeds the number of elements in @@ -853,10 +813,4 @@ extension Span where Element: ~Copyable { let newStart = _pointer?.advanced(by: droppedCount*MemoryLayout.stride) return Self(_unchecked: newStart, count: count&-droppedCount) } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func _shrink(droppingFirst k: Int) { - self = _extracting(droppingFirst: k) - } } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 0701a4887..435010989 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -99,9 +99,8 @@ final class RawSpanTests: XCTestCase { a.withUnsafeBytes { let span = RawSpan(_unsafeBytes: $0) - var copy = span - copy._shrink(droppingFirst: 2) - let u0 = copy.unsafeLoadUnaligned(as: UInt64.self) + let suffix = span._extracting(droppingFirst: 2) + let u0 = suffix.unsafeLoadUnaligned(as: UInt64.self) XCTAssertEqual(u0 & 0xff, 2) XCTAssertEqual(u0.byteSwapped & 0xff, 9) let u1 = span.unsafeLoadUnaligned(fromByteOffset: 6, as: UInt64.self) @@ -121,13 +120,13 @@ final class RawSpanTests: XCTestCase { let sub3 = span._extracting(...) let sub4 = span._extracting(unchecked: 2...) XCTAssertTrue( - sub1.unsafeView(as: UInt8.self)._elementsEqual(sub2.unsafeView(as: UInt8.self)) + sub1._unsafeView(as: UInt8.self)._elementsEqual(sub2._unsafeView(as: UInt8.self)) ) XCTAssertTrue( - sub3.unsafeView(as: Int8.self)._elementsEqual(span.unsafeView(as: Int8.self)) + sub3._unsafeView(as: Int8.self)._elementsEqual(span._unsafeView(as: Int8.self)) ) XCTAssertFalse( - sub4.unsafeView(as: Int8.self)._elementsEqual(sub3.unsafeView(as: Int8.self)) + sub4._unsafeView(as: Int8.self)._elementsEqual(sub3._unsafeView(as: Int8.self)) ) } } @@ -137,10 +136,8 @@ final class RawSpanTests: XCTestCase { let b = (0.. Date: Thu, 24 Oct 2024 15:11:55 -0700 Subject: [PATCH 135/195] readability updates --- Sources/Future/MutableSpan.swift | 4 +-- Sources/Future/OutputSpan.swift | 2 +- Sources/Future/RawSpan.swift | 35 +++++++++++---------- Sources/Future/Span.swift | 53 +++++++++++++++++++------------- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 1b9b050f2..fba7094b0 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -505,7 +505,7 @@ extension MutableSpan { "destination span cannot contain every element from source." ) _start.withMemoryRebound(to: Element.self, capacity: source.count) { dest in - source._start.withMemoryRebound(to: Element.self, capacity: source.count) { + source._start().withMemoryRebound(to: Element.self, capacity: source.count) { dest.update(from: $0, count: source.count) } } @@ -657,7 +657,7 @@ extension MutableSpan where Element: BitwiseCopyable { "destination span cannot contain every element from source." ) _start.copyMemory( - from: source._start, byteCount: source.count&*MemoryLayout.stride + from: source._start(), byteCount: source.count&*MemoryLayout.stride ) return source.count } diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift index 628768195..df1b8e57f 100644 --- a/Sources/Future/OutputSpan.swift +++ b/Sources/Future/OutputSpan.swift @@ -291,7 +291,7 @@ extension OutputSpan { "destination span cannot contain every element from source." ) let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) - source._start.withMemoryRebound(to: Element.self, capacity: source.count) { + source._start().withMemoryRebound(to: Element.self, capacity: source.count) { _ = tail.initializeMemory(as: Element.self, from: $0, count: source.count) } _initialized += source.count diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index aeb49158c..910a2b166 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -17,15 +17,17 @@ public struct RawSpan: ~Escapable, Copyable, BitwiseCopyable { @usableFromInline let _pointer: UnsafeRawPointer? - @usableFromInline @inline(__always) - var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } + @_alwaysEmitIntoClient + internal func _start() -> UnsafeRawPointer { + _pointer.unsafelyUnwrapped + } @usableFromInline let _count: Int @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline @inline(__always) + @_alwaysEmitIntoClient @lifetime(immortal) - init( + internal init( _unchecked pointer: UnsafeRawPointer?, byteCount: Int ) { @@ -174,7 +176,7 @@ extension RawSpan { ) { precondition(count >= 0, "Count must not be negative") self.init( - _unchecked: pointer, byteCount: count*MemoryLayout.stride + _unchecked: pointer, byteCount: count * MemoryLayout.stride ) } @@ -191,7 +193,7 @@ extension RawSpan { _unsafeSpan span: borrowing Span ) { self.init( - _unchecked: UnsafeRawPointer(span._start), + _unchecked: span._pointer, byteCount: span.count &* MemoryLayout.stride ) } @@ -460,7 +462,7 @@ extension RawSpan { public func unsafeLoad( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { - _start.load(fromByteOffset: offset, as: T.self) + _start().load(fromByteOffset: offset, as: T.self) } /// Returns a new instance of the given type, constructed from the raw memory @@ -516,7 +518,7 @@ extension RawSpan { public func unsafeLoadUnaligned( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { - _start.loadUnaligned(fromByteOffset: offset, as: T.self) + _start().loadUnaligned(fromByteOffset: offset, as: T.self) } } @@ -545,9 +547,10 @@ extension RawSpan { guard let spanStart = span._pointer, _count > 0 else { return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil } + let start = _start() let spanEnd = spanStart + span._count - if spanStart < _start || (_start + _count) < spanEnd { return nil } - let lower = _start.distance(to: spanStart) + if spanStart < start || (start + _count) < spanEnd { return nil } + let lower = start.distance(to: spanStart) return Range(uncheckedBounds: (lower, lower &+ span._count)) } } @@ -597,8 +600,8 @@ extension RawSpan { @_alwaysEmitIntoClient public func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let dc = min(k, byteCount) - return Self(_unchecked: _pointer, byteCount: byteCount&-dc) + let droppedCount = min(k, byteCount) + return Self(_unchecked: _pointer, byteCount: byteCount &- droppedCount) } /// Returns a span containing the trailing bytes of the span, @@ -621,7 +624,7 @@ extension RawSpan { public func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, byteCount) - let newStart = _pointer?.advanced(by: byteCount&-newCount) + let newStart = _pointer?.advanced(by: byteCount &- newCount) return Self(_unchecked: newStart, byteCount: newCount) } @@ -643,8 +646,8 @@ extension RawSpan { @_alwaysEmitIntoClient public func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") - let dc = min(k, byteCount) - let newStart = _pointer?.advanced(by: dc) - return Self(_unchecked: newStart, byteCount: byteCount&-dc) + let droppedCount = min(k, byteCount) + let newStart = _pointer?.advanced(by: droppedCount) + return Self(_unchecked: newStart, byteCount: byteCount &- droppedCount) } } diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index aceda3c97..8ee2a18d9 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -18,17 +18,19 @@ import Builtin @frozen public struct Span : ~Escapable, Copyable, BitwiseCopyable { - @usableFromInline let _pointer: UnsafeRawPointer? + @usableFromInline internal let _pointer: UnsafeRawPointer? - @usableFromInline @inline(__always) - var _start: UnsafeRawPointer { _pointer.unsafelyUnwrapped } + @_alwaysEmitIntoClient + internal func _start() -> UnsafeRawPointer { + _pointer.unsafelyUnwrapped + } @usableFromInline let _count: Int @_disallowFeatureSuppression(NonescapableTypes) - @usableFromInline @inline(__always) + @_alwaysEmitIntoClient @lifetime(immortal) - init( + internal init( _unchecked pointer: UnsafeRawPointer?, count: Int ) { @@ -60,7 +62,7 @@ extension Span where Element: ~Copyable { ) { precondition( ((Int(bitPattern: buffer.baseAddress) & - (MemoryLayout.alignment&-1)) == 0), + (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" ) self.init(_unchecked: buffer.baseAddress, count: buffer.count) @@ -172,12 +174,14 @@ extension Span where Element: BitwiseCopyable { ) { precondition( ((Int(bitPattern: buffer.baseAddress) & - (MemoryLayout.alignment&-1)) == 0), + (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" ) let (byteCount, stride) = (buffer.count, MemoryLayout.stride) let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0, "Span must contain a whole number of elements") + precondition( + remainder == 0, "Span must contain a whole number of elements" + ) self.init(_unchecked: buffer.baseAddress, count: count) } @@ -442,10 +446,13 @@ extension Span where Element: ~Copyable { public subscript(unchecked position: Index) -> Element { //FIXME: change to unsafeRawAddress or unsafeAddress when ready _read { - let element = _start.advanced(by: position&*MemoryLayout.stride) - let binding = Builtin.bindMemory(element._rawValue, count._builtinWordValue, Element.self) - defer { Builtin.rebindMemory(element._rawValue, binding) } - yield UnsafePointer(element._rawValue).pointee + let elementOffset = position &* MemoryLayout.stride + let address = _start().advanced(by: elementOffset) + let binding = Builtin.bindMemory( + address._rawValue, 1._builtinWordValue, Element.self + ) + defer { Builtin.rebindMemory(address._rawValue, binding) } + yield UnsafePointer(address._rawValue).pointee } } } @@ -484,7 +491,8 @@ extension Span where Element: BitwiseCopyable { @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { get { - let address = _start.advanced(by: position&*MemoryLayout.stride) + let elementOffset = position &* MemoryLayout.stride + let address = _start().advanced(by: elementOffset) return address.loadUnaligned(as: Element.self) } } @@ -537,7 +545,7 @@ extension Span where Element: ~Copyable { @unsafe @_alwaysEmitIntoClient public func _extracting(unchecked bounds: Range) -> Self { - let delta = bounds.lowerBound&*MemoryLayout.stride + let delta = bounds.lowerBound &* MemoryLayout.stride return Span(_unchecked: _pointer?.advanced(by: delta), count: bounds.count) } @@ -708,10 +716,11 @@ extension Span where Element: ~Copyable { guard let spanStart = span._pointer, _count > 0 else { return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil } + let start = _start() let stride = MemoryLayout.stride - let spanEnd = spanStart + stride&*span._count - if spanStart < _start || spanEnd > (_start + stride&*_count) { return nil } - let byteOffset = _start.distance(to: spanStart) + let spanEnd = spanStart + stride &* span._count + if spanStart < start || spanEnd > (start + stride &* _count) { return nil } + let byteOffset = start.distance(to: spanStart) let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) guard r == 0 else { return nil } return Range(uncheckedBounds: (lower, lower &+ span._count)) @@ -764,7 +773,7 @@ extension Span where Element: ~Copyable { public func _extracting(droppingLast k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) - return Self(_unchecked: _pointer, count: count&-droppedCount) + return Self(_unchecked: _pointer, count: count &- droppedCount) } /// Returns a span containing the final elements of the span, @@ -787,7 +796,8 @@ extension Span where Element: ~Copyable { public func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length.") let newCount = min(maxLength, count) - let newStart = _pointer?.advanced(by: (count&-newCount)*MemoryLayout.stride) + let offset = (count &- newCount) * MemoryLayout.stride + let newStart = _pointer?.advanced(by: offset) return Self(_unchecked: newStart, count: newCount) } @@ -810,7 +820,8 @@ extension Span where Element: ~Copyable { public func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of elements.") let droppedCount = min(k, count) - let newStart = _pointer?.advanced(by: droppedCount*MemoryLayout.stride) - return Self(_unchecked: newStart, count: count&-droppedCount) + let offset = droppedCount * MemoryLayout.stride + let newStart = _pointer?.advanced(by: offset) + return Self(_unchecked: newStart, count: count &- droppedCount) } } From fd29394499f0a3e1ea1fdaf023c39d81f7a6f46c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 24 Oct 2024 17:17:04 -0700 Subject: [PATCH 136/195] fix lifetime annotations --- Sources/Future/RawSpan.swift | 28 ++++++++++++++-------------- Sources/Future/Span.swift | 34 +++++++++++++++++----------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 910a2b166..5a0ec4464 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -26,7 +26,7 @@ public struct RawSpan: ~Escapable, Copyable, BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow pointer) internal init( _unchecked pointer: UnsafeRawPointer?, byteCount: Int @@ -53,9 +53,9 @@ extension RawSpan { /// the newly created `RawSpan`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeBytes buffer: UnsafeRawBufferPointer + _unsafeBytes buffer: borrowing UnsafeRawBufferPointer ) { self.init( _unchecked: buffer.baseAddress, byteCount: buffer.count @@ -64,9 +64,9 @@ extension RawSpan { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeBytes buffer: Slice + _unsafeBytes buffer: borrowing Slice ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } @@ -82,7 +82,7 @@ extension RawSpan { /// the newly created `RawSpan`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer ) { @@ -91,9 +91,9 @@ extension RawSpan { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeBytes buffer: Slice + _unsafeBytes buffer: borrowing Slice ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } @@ -111,7 +111,7 @@ extension RawSpan { /// the newly created `RawSpan`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow pointer) public init( _unsafeStart pointer: UnsafeRawPointer, byteCount: Int @@ -131,7 +131,7 @@ extension RawSpan { /// the newly created `RawSpan`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeElements buffer: UnsafeBufferPointer ) { @@ -149,7 +149,7 @@ extension RawSpan { /// the newly created `RawSpan`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeElements buffer: UnsafeMutableBufferPointer ) { @@ -169,7 +169,7 @@ extension RawSpan { /// the newly created `RawSpan`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow pointer) public init( _unsafeStart pointer: UnsafePointer, count: Int @@ -188,7 +188,7 @@ extension RawSpan { @_disallowFeatureSuppression(NonescapableTypes) @unsafe // remove when fixing the lifetime annotation @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(span) public init( _unsafeSpan span: borrowing Span ) { @@ -395,7 +395,7 @@ extension RawSpan { @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(self) public func _unsafeView( as type: T.Type ) -> Span { diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 8ee2a18d9..491312dbc 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -29,7 +29,7 @@ public struct Span @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow pointer) internal init( _unchecked pointer: UnsafeRawPointer?, count: Int @@ -56,7 +56,7 @@ extension Span where Element: ~Copyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeElements buffer: UnsafeBufferPointer ) { @@ -79,7 +79,7 @@ extension Span where Element: ~Copyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeElements buffer: UnsafeMutableBufferPointer ) { @@ -99,13 +99,13 @@ extension Span where Element: ~Copyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow pointer) public init( - _unsafeStart start: UnsafePointer, + _unsafeStart pointer: UnsafePointer, count: Int ) { precondition(count >= 0, "Count must not be negative") - self.init(_unsafeElements: .init(start: start, count: count)) + self.init(_unsafeElements: .init(start: pointer, count: count)) } } @@ -123,9 +123,9 @@ extension Span { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeElements buffer: Slice> + _unsafeElements buffer: borrowing Slice> ) { self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) } @@ -141,9 +141,9 @@ extension Span { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeElements buffer: Slice> + _unsafeElements buffer: borrowing Slice> ) { self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) } @@ -168,7 +168,7 @@ extension Span where Element: BitwiseCopyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeBytes buffer: UnsafeRawBufferPointer ) { @@ -201,7 +201,7 @@ extension Span where Element: BitwiseCopyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer ) { @@ -225,7 +225,7 @@ extension Span where Element: BitwiseCopyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow pointer) public init( _unsafeStart pointer: UnsafeRawPointer, byteCount: Int @@ -250,9 +250,9 @@ extension Span where Element: BitwiseCopyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeBytes buffer: Slice + _unsafeBytes buffer: borrowing Slice ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } @@ -273,9 +273,9 @@ extension Span where Element: BitwiseCopyable { /// the newly created `Span`. @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - @lifetime(immortal) + @lifetime(borrow buffer) public init( - _unsafeBytes buffer: Slice + _unsafeBytes buffer: borrowing Slice ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } From 1a6dbeb3d6012e3370986bac5e4010268dc5180d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 24 Oct 2024 18:19:22 -0700 Subject: [PATCH 137/195] add missing initializers from `Slice` --- Sources/Future/RawSpan.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index 5a0ec4464..bb962159b 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -138,6 +138,15 @@ extension RawSpan { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + _unsafeElements buffer: borrowing Slice> + ) { + self.init(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: buffer))) + } + /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory in `buffer` must be owned by the instance `owner`, @@ -156,6 +165,15 @@ extension RawSpan { self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } + @_disallowFeatureSuppression(NonescapableTypes) + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + _unsafeElements buffer: borrowing Slice> + ) { + self.init(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: buffer))) + } + /// Unsafely create a `RawSpan` over initialized memory. /// /// The memory over `count` bytes starting at From 09290768f4709b01b95c24ee220beecc27bc3b76 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 19 Nov 2024 17:37:28 -0500 Subject: [PATCH 138/195] fix lifetime syntax for newer toolchains --- Sources/Future/Box.swift | 3 +- Sources/Future/Containers/Container.swift | 6 ++-- Sources/Future/Containers/RigidArray.swift | 3 +- Sources/Future/Containers/Shared.swift | 6 ++-- Sources/Future/Inout.swift | 3 +- Sources/Future/LifetimeOverride.swift | 3 +- Sources/Future/MutableSpan.swift | 28 ++++++++++++------- Sources/Future/OutputSpan.swift | 28 ++++++++++++------- Sources/Future/Span+Iterator.swift | 3 +- .../FutureTests/ContiguousStorageTests.swift | 3 +- 10 files changed, 56 insertions(+), 30 deletions(-) diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index 49cae1360..42f7f95f3 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -47,7 +47,8 @@ extension Box where T: ~Copyable { @_alwaysEmitIntoClient @_transparent - public consuming func leak() -> dependsOn(immortal) Inout { + @lifetime(immortal) + public consuming func leak() -> Inout { let result = Inout(unsafeImmortalAddress: _pointer) discard self return result diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 3473efb91..4df0f6b81 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -12,7 +12,8 @@ public protocol BorrowingIteratorProtocol: ~Escapable { associatedtype Element: ~Copyable - mutating func nextChunk(maximumCount: Int) -> dependsOn(scoped self) Span + @lifetime(self) + mutating func nextChunk(maximumCount: Int) -> Span } public protocol Container: ~Copyable, ~Escapable { @@ -141,7 +142,8 @@ extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Se public protocol Muterator: ~Copyable, ~Escapable { associatedtype Element: ~Copyable - mutating func nextChunk(maximumCount: Int) -> dependsOn(scoped state) MutableSpan + @lifetime(self) + mutating func nextChunk(maximumCount: Int) -> MutableSpan } public protocol MutableContainer: Container, ~Copyable, ~Escapable { diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index 86c049d88..eda928f76 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -78,9 +78,10 @@ extension RigidArray: RandomAccessContainer where Element: ~Copyable { self._offset = startOffset } + @lifetime(self) public mutating func nextChunk( maximumCount: Int - ) -> dependsOn(scoped self) Span { + ) -> Span { let end = _offset + Swift.min(maximumCount, _items.count - _offset) defer { _offset = end } let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift index 1df697b56..1ebcc284c 100644 --- a/Sources/Future/Containers/Shared.swift +++ b/Sources/Future/Containers/Shared.swift @@ -121,7 +121,8 @@ extension Shared where Storage: ~Copyable { // FIXME: This builds, but attempts to use it don't: they fail with an unexpected exclusivity violation. @inlinable - public subscript() -> dependsOn(self) Storage { + @lifetime(self) + public subscript() -> Storage { //@_transparent unsafeAddress { _address @@ -159,7 +160,8 @@ extension Shared where Storage: ~Copyable { extension Shared where Storage: ~Copyable { // This is the actual shape we want. There is currently no way to express it. @inlinable - public borrowing func read() -> dependsOn(self) Borrow { + @lifetime(borrow self) + public borrowing func read() -> Borrow { // This is gloriously (and very explicitly) unsafe, as it should be. // `Shared` is carefully constructed to guarantee that // lifetime(self) == lifetime(_box.storage); but we have not diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift index 5cb126222..deb06ce5f 100644 --- a/Sources/Future/Inout.swift +++ b/Sources/Future/Inout.swift @@ -57,9 +57,10 @@ public struct Inout: ~Copyable, ~Escapable { /// an immortal instance of type 'T'. @_alwaysEmitIntoClient @_transparent + @lifetime(immortal) public init( unsafeImmortalAddress: UnsafeMutablePointer - ) -> dependsOn(immortal) Self { + ) { _pointer = unsafeImmortalAddress } } diff --git a/Sources/Future/LifetimeOverride.swift b/Sources/Future/LifetimeOverride.swift index 4a19ca73e..4b32cb6e4 100644 --- a/Sources/Future/LifetimeOverride.swift +++ b/Sources/Future/LifetimeOverride.swift @@ -14,12 +14,13 @@ import Builtin @_unsafeNonescapableResult @inlinable @inline(__always) +@lifetime(borrow source) public func unsafelyOverrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable >( of dependent: consuming T, to source: borrowing U -) -> dependsOn(source) T { +) -> T { dependent } diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index fba7094b0..75da72b7a 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -26,10 +26,11 @@ public struct MutableSpan: ~Copyable & ~Escapable { @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) + @lifetime(borrow start) init( _unchecked start: UnsafeMutableRawPointer?, count: Int - ) -> dependsOn(immortal) Self { + ) { _pointer = start _count = count } @@ -44,18 +45,20 @@ extension MutableSpan where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) + @lifetime(borrow elements) internal init( _unchecked elements: UnsafeMutableBufferPointer - ) -> dependsOn(immortal) Self { + ) { _pointer = .init(elements.baseAddress) _count = elements.count } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow buffer) public init( _unsafeElements buffer: UnsafeMutableBufferPointer - ) -> dependsOn(immortal) Self { + ) { precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment&-1)) == 0), @@ -66,10 +69,11 @@ extension MutableSpan where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow start) public init( _unsafeStart start: UnsafeMutablePointer, count: Int - ) -> dependsOn(immortal) Self { + ) { precondition(count >= 0, "Count must not be negative") self.init(_unsafeElements: .init(start: start, count: count)) } @@ -80,9 +84,10 @@ extension MutableSpan { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow elements) public init( - _unsafeElements elements: Slice> - ) -> dependsOn(immortal) Self { + _unsafeElements elements: borrowing Slice> + ) { self.init(_unsafeElements: UnsafeMutableBufferPointer(rebasing: elements)) } } @@ -92,9 +97,10 @@ extension MutableSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow buffer) public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer - ) -> dependsOn(immortal) Self { + ) { precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment&-1)) == 0), @@ -108,19 +114,21 @@ extension MutableSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow pointer) public init( _unsafeStart pointer: UnsafeMutableRawPointer, byteCount: Int - ) -> dependsOn(immortal) Self { + ) { precondition(byteCount >= 0, "Count must not be negative") self.init(_unsafeBytes: .init(start: pointer, count: byteCount)) } @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow buffer) public init( - _unsafeBytes buffer: Slice - ) -> dependsOn(immortal) Self { + _unsafeBytes buffer: borrowing Slice + ) { self.init(_unsafeBytes: UnsafeMutableRawBufferPointer(rebasing: buffer)) } } diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift index df1b8e57f..f8ed7f07f 100644 --- a/Sources/Future/OutputSpan.swift +++ b/Sources/Future/OutputSpan.swift @@ -45,11 +45,12 @@ public struct OutputSpan: ~Copyable, ~Escapable { @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) + @lifetime(borrow start) init( _unchecked start: UnsafeMutableRawPointer?, capacity: Int, initialized: Int - ) -> dependsOn(immortal) Self { + ) { _pointer = start self.capacity = capacity _initialized = initialized @@ -65,10 +66,11 @@ extension OutputSpan where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) + @lifetime(borrow buffer) init( _unchecked buffer: UnsafeMutableBufferPointer, initialized: Int - ) -> dependsOn(immortal) Self { + ) { _pointer = .init(buffer.baseAddress) capacity = buffer.count _initialized = initialized @@ -76,10 +78,11 @@ extension OutputSpan where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow buffer) public init( _initializing buffer: UnsafeMutableBufferPointer, initialized: Int = 0 - ) -> dependsOn(immortal) Self { + ) { precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment&-1)) == 0), @@ -90,11 +93,12 @@ extension OutputSpan where Element: ~Copyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow pointer) public init( _initializing pointer: UnsafeMutablePointer, capacity: Int, initialized: Int = 0 - ) -> dependsOn(immortal) Self { + ) { precondition(capacity >= 0, "Capacity must be 0 or greater") self.init( _initializing: .init(start: pointer, count: capacity), @@ -108,10 +112,11 @@ extension OutputSpan { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow buffer) public init( - _initializing buffer: Slice>, + _initializing buffer: borrowing Slice>, initialized: Int = 0 - ) -> dependsOn(immortal) Self { + ) { self.init(_initializing: .init(rebasing: buffer), initialized: initialized) } } @@ -121,10 +126,11 @@ extension OutputSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow bytes) public init( _initializing bytes: UnsafeMutableRawBufferPointer, initialized: Int = 0 - ) -> dependsOn(immortal) Self { + ) { precondition( ((Int(bitPattern: bytes.baseAddress) & (MemoryLayout.alignment&-1)) == 0), @@ -140,11 +146,12 @@ extension OutputSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow pointer) public init( _initializing pointer: UnsafeMutableRawPointer, capacity: Int, initialized: Int = 0 - ) -> dependsOn(immortal) Self { + ) { precondition(capacity >= 0, "Capacity must be 0 or greater") self.init( _initializing: .init(start: pointer, count: capacity), @@ -154,10 +161,11 @@ extension OutputSpan where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(borrow buffer) public init( - _initializing buffer: Slice, + _initializing buffer: borrowing Slice, initialized: Int = 0 - ) -> dependsOn(immortal) Self { + ) { self.init( _initializing: UnsafeMutableRawBufferPointer(rebasing: buffer), initialized: initialized diff --git a/Sources/Future/Span+Iterator.swift b/Sources/Future/Span+Iterator.swift index d808ff387..4db0f891e 100644 --- a/Sources/Future/Span+Iterator.swift +++ b/Sources/Future/Span+Iterator.swift @@ -16,7 +16,8 @@ extension Span where Element: ~Copyable { var curPointer: UnsafeRawPointer? let endPointer: UnsafeRawPointer? - public init(from span: consuming Span) -> dependsOn(immortal) Self { + @lifetime(span) + public init(from span: consuming Span) { curPointer = span._pointer endPointer = span._pointer?.advanced( by: span.count*MemoryLayout.stride diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift index b5a0ad33f..b0fa7a067 100644 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -36,9 +36,10 @@ final class ContiguousStorageTests: XCTestCase { } @inline(never) + @lifetime(borrow array) private func skip( along array: borrowing Array - ) -> dependsOn(array) Skipper { + ) -> Skipper { Skipper(array.storage) } From 5d21e3e59fac796a509f8783634bb9a8b588f831 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 19 Nov 2024 19:04:48 -0500 Subject: [PATCH 139/195] update RawSpan to match the stdlib --- Sources/Future/RawSpan.swift | 222 ++++++++++++---------- Sources/Future/Span.swift | 4 +- Sources/Future/StdlibSpanExtensions.swift | 2 +- Tests/FutureTests/RawSpanTests.swift | 6 +- Tests/FutureTests/SpanTests.swift | 2 +- 5 files changed, 129 insertions(+), 107 deletions(-) diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift index bb962159b..343b9fba2 100644 --- a/Sources/Future/RawSpan.swift +++ b/Sources/Future/RawSpan.swift @@ -10,48 +10,76 @@ // //===----------------------------------------------------------------------===// -// A RawSpan represents a span of initialized memory -// of unspecified type. -@_disallowFeatureSuppression(NonescapableTypes) +/// `RawSpan` represents a contiguous region of memory +/// which contains initialized bytes. +/// +/// A `RawSpan` instance is a non-owning, non-escaping view into memory. +/// When a `RawSpan` is created, it inherits the lifetime of the container +/// owning the contiguous memory, ensuring temporal safety and avoiding +/// use-after-free errors. Operations on `RawSpan` are bounds-checked, +/// ensuring spcial safety and avoiding buffer overflow errors. @frozen public struct RawSpan: ~Escapable, Copyable, BitwiseCopyable { - @usableFromInline let _pointer: UnsafeRawPointer? + + /// The starting address of this `RawSpan`. + /// + /// `_pointer` can be `nil` if and only if `_count` equals 0. + /// Otherwise, `_pointer` must point to memory that will remain + /// valid and not mutated as long as this `Span` exists. + /// The memory at `_pointer` must consist of `_count` initialized bytes. + @usableFromInline + internal let _pointer: UnsafeRawPointer? @_alwaysEmitIntoClient internal func _start() -> UnsafeRawPointer { _pointer.unsafelyUnwrapped } - @usableFromInline let _count: Int + /// The number of bytes in this `RawSpan`. + /// + /// If `_count` equals 0, then `_pointer` may be either `nil` or valid. + /// Any `_count` greater than 0 indicates a valid non-nil `_pointer`. + /// Any `_count` less than 0 is invalid and is undefined behaviour. + @usableFromInline + internal let _count: Int - @_disallowFeatureSuppression(NonescapableTypes) + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// `pointer` must point to a region of `byteCount` initialized bytes, + /// or may be `nil` if `count` is 0. + /// + /// The region of `byteCount` bytes of memory starting at `pointer` + /// must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized byte. + /// - byteCount: the number of initialized bytes in the span. @_alwaysEmitIntoClient + @inline(__always) @lifetime(borrow pointer) internal init( - _unchecked pointer: UnsafeRawPointer?, + _unchecked pointer: borrowing UnsafeRawPointer?, byteCount: Int ) { - _pointer = pointer + _pointer = copy pointer _count = byteCount } } -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan: @unchecked Sendable {} -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Unsafely create a `RawSpan` over initialized memory. /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `RawSpan`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -62,7 +90,14 @@ extension RawSpan { ) } - @_disallowFeatureSuppression(NonescapableTypes) + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. + /// + /// - Parameters: + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -73,23 +108,20 @@ extension RawSpan { /// Unsafely create a `RawSpan` over initialized memory. /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: - /// - buffer: an `UnsafeMutableRawBufferPointer` to initialized memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `RawSpan`. - @_disallowFeatureSuppression(NonescapableTypes) + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeBytes buffer: UnsafeMutableRawBufferPointer + _unsafeBytes buffer: borrowing UnsafeMutableRawBufferPointer ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -100,72 +132,82 @@ extension RawSpan { /// Unsafely create a `RawSpan` over initialized memory. /// - /// The memory over `count` bytes starting at - /// `pointer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The region of memory representing `byteCount` bytes starting at `pointer` + /// must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - pointer: a pointer to the first initialized byte. /// - byteCount: the number of initialized bytes in the span. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `RawSpan`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( - _unsafeStart pointer: UnsafeRawPointer, + _unsafeStart pointer: borrowing UnsafeRawPointer, byteCount: Int ) { precondition(byteCount >= 0, "Count must not be negative") - self.init(_unchecked: pointer, byteCount: byteCount) + self.init(_unchecked: copy pointer, byteCount: byteCount) } /// Unsafely create a `RawSpan` over initialized memory. /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `RawSpan`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeElements buffer: UnsafeBufferPointer + _unsafeElements buffer: borrowing UnsafeBufferPointer ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } - @_disallowFeatureSuppression(NonescapableTypes) + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. + /// + /// - Parameters: + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( _unsafeElements buffer: borrowing Slice> ) { - self.init(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: buffer))) + self.init( + _unsafeBytes: .init(UnsafeBufferPointer(rebasing: buffer)) + ) } /// Unsafely create a `RawSpan` over initialized memory. /// - /// The memory in `buffer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: - /// - buffer: an `UnsafeMutableRawBufferPointer` to initialized memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `RawSpan`. - @_disallowFeatureSuppression(NonescapableTypes) + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeElements buffer: UnsafeMutableBufferPointer + _unsafeElements buffer: borrowing UnsafeMutableBufferPointer ) { self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } - @_disallowFeatureSuppression(NonescapableTypes) + /// Unsafely create a `RawSpan` over initialized memory. + /// + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. + /// + /// - Parameters: + /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -176,25 +218,23 @@ extension RawSpan { /// Unsafely create a `RawSpan` over initialized memory. /// - /// The memory over `count` bytes starting at - /// `pointer` must be owned by the instance `owner`, - /// meaning that as long as `owner` is alive the memory will remain valid. + /// The region of memory representing `byteCount` bytes starting at `pointer` + /// must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `RawSpan`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - pointer: a pointer to the first initialized byte. /// - byteCount: the number of initialized bytes in the span. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `RawSpan`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( - _unsafeStart pointer: UnsafePointer, + _unsafeStart pointer: borrowing UnsafePointer, count: Int ) { precondition(count >= 0, "Count must not be negative") self.init( - _unchecked: pointer, byteCount: count * MemoryLayout.stride + _unchecked: copy pointer, byteCount: count * MemoryLayout.stride ) } @@ -203,12 +243,10 @@ extension RawSpan { /// - Parameters: /// - span: An existing `Span`, which will define both this /// `RawSpan`'s lifetime and the memory it represents. - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe // remove when fixing the lifetime annotation @_alwaysEmitIntoClient - @lifetime(span) + @lifetime(borrow span) public init( - _unsafeSpan span: borrowing Span + _elements span: borrowing Span ) { self.init( _unchecked: span._pointer, @@ -226,7 +264,6 @@ extension RawSpan { } } -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// The number of bytes in the span. @@ -255,7 +292,6 @@ extension RawSpan { } //MARK: extracting sub-spans -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Constructs a new span over the bytes within the supplied range of @@ -271,13 +307,13 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(_ bounds: Range) -> Self { precondition( UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "byte offset range out of bounds" + "Byte offset range out of bounds" ) return _extracting(unchecked: bounds) } @@ -297,9 +333,9 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(unchecked bounds: Range) -> Self { RawSpan( _unchecked: _pointer?.advanced(by: bounds.lowerBound), @@ -320,8 +356,8 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(_ bounds: some RangeExpression) -> Self { _extracting(bounds.relative(to: byteOffsets)) } @@ -341,9 +377,9 @@ extension RawSpan { /// - Returns: A span over the bytes within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient + @lifetime(self) public func _extracting( unchecked bounds: some RangeExpression ) -> Self { @@ -359,17 +395,15 @@ extension RawSpan { /// - Returns: A span over all the bytes of this span. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(_: UnboundedRange) -> Self { self } } -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { - //FIXME: mark closure parameter as non-escaping /// Calls the given closure with a pointer to the underlying bytes of /// the viewed contiguous storage. /// @@ -384,7 +418,6 @@ extension RawSpan { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result @@ -393,7 +426,6 @@ extension RawSpan { } } -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// View the bytes of this span as type `T` @@ -410,11 +442,10 @@ extension RawSpan { /// - Parameters: /// - type: The type as which to view the bytes of this span. /// - Returns: A typed span viewing these bytes as instances of `T`. - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient @lifetime(self) - public func _unsafeView( + consuming public func _unsafeView( as type: T.Type ) -> Span { Span(_unsafeBytes: .init(start: _pointer, count: _count)) @@ -422,7 +453,6 @@ extension RawSpan { } //MARK: load -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Returns a new instance of the given type, constructed from the raw memory @@ -442,7 +472,6 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public func unsafeLoad( @@ -451,7 +480,7 @@ extension RawSpan { precondition( UInt(bitPattern: offset) <= UInt(bitPattern: _count) && MemoryLayout.size <= (_count &- offset), - "byte offset range out of bounds" + "Byte offset range out of bounds" ) return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) } @@ -474,7 +503,6 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance is memory-managed and unassociated /// with the value in the memory referenced by this pointer. - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public func unsafeLoad( @@ -499,7 +527,6 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public func unsafeLoadUnaligned( @@ -508,7 +535,7 @@ extension RawSpan { precondition( UInt(bitPattern: offset) <= UInt(bitPattern: _count) && MemoryLayout.size <= (_count &- offset), - "byte offset range out of bounds" + "Byte offset range out of bounds" ) return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) } @@ -530,7 +557,6 @@ extension RawSpan { /// - Returns: A new instance of type `T`, read from the raw bytes at /// `offset`. The returned instance isn't associated /// with the value in the range of memory referenced by this pointer. - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public func unsafeLoadUnaligned( @@ -540,11 +566,9 @@ extension RawSpan { } } -@_disallowFeatureSuppression(NonescapableTypes) extension RawSpan { /// Returns a Boolean value indicating whether two `RawSpan` instances /// refer to the same region in memory. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func isIdentical(to other: Self) -> Bool { (self._pointer == other._pointer) && (self._count == other._count) @@ -558,23 +582,21 @@ extension RawSpan { /// Parameters: /// - span: a subrange of `self` /// Returns: A range of offsets within `self` - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func byteOffsets(of span: borrowing Self) -> Range? { - if span._count > _count { return nil } - guard let spanStart = span._pointer, _count > 0 else { - return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil + public func byteOffsets(of other: borrowing Self) -> Range? { + if other._count > _count { return nil } + guard let spanStart = other._pointer, _count > 0 else { + return _pointer == other._pointer ? Range(uncheckedBounds: (0, 0)) : nil } let start = _start() - let spanEnd = spanStart + span._count + let spanEnd = spanStart + other._count if spanStart < start || (start + _count) < spanEnd { return nil } let lower = start.distance(to: spanStart) - return Range(uncheckedBounds: (lower, lower &+ span._count)) + return Range(uncheckedBounds: (lower, lower &+ other._count)) } } -//MARK: one-sided slicing operations -@_disallowFeatureSuppression(NonescapableTypes) +//MARK: prefixes and suffixes extension RawSpan { /// Returns a span containing the initial bytes of this span, @@ -592,10 +614,10 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(first maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a prefix of negative length.") + precondition(maxLength >= 0, "Can't have a prefix of negative length") let newCount = min(maxLength, byteCount) return Self(_unchecked: _pointer, byteCount: newCount) } @@ -614,10 +636,10 @@ extension RawSpan { /// - Returns: A span leaving off the specified number of bytes at the end. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(droppingLast k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements.") + precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, byteCount) return Self(_unchecked: _pointer, byteCount: byteCount &- droppedCount) } @@ -637,10 +659,10 @@ extension RawSpan { /// - Returns: A span with at most `maxLength` bytes. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(last maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a suffix of negative length.") + precondition(maxLength >= 0, "Can't have a suffix of negative length") let newCount = min(maxLength, byteCount) let newStart = _pointer?.advanced(by: byteCount &- newCount) return Self(_unchecked: newStart, byteCount: newCount) @@ -660,10 +682,10 @@ extension RawSpan { /// - Returns: A span starting after the specified number of bytes. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(droppingFirst k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements.") + precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, byteCount) let newStart = _pointer?.advanced(by: droppedCount) return Self(_unchecked: newStart, byteCount: byteCount &- droppedCount) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index 491312dbc..a63729ff5 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -410,7 +410,7 @@ extension Span where Element: BitwiseCopyable { @_disallowFeatureSuppression(NonescapableTypes) @unsafe //FIXME: remove when the lifetime inference is fixed @_alwaysEmitIntoClient - public var _unsafeRawSpan: RawSpan { RawSpan(_unsafeSpan: self) } + public var _unsafeRawSpan: RawSpan { RawSpan(_elements: self) } } @_disallowFeatureSuppression(NonescapableTypes) @@ -664,7 +664,7 @@ extension Span where Element: BitwiseCopyable { public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { - try RawSpan(_unsafeSpan: self).withUnsafeBytes(body) + try RawSpan(_elements: self).withUnsafeBytes(body) } } diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift index 55621c0db..cc6f4a05c 100644 --- a/Sources/Future/StdlibSpanExtensions.swift +++ b/Sources/Future/StdlibSpanExtensions.swift @@ -487,7 +487,7 @@ extension Span where Element: BitwiseCopyable { public consuming func withBytes( _ body: (_ elements: RawSpan) throws(E) -> Result ) throws(E) -> Result { - try body(RawSpan(_unsafeSpan: self)) + try body(RawSpan(_elements: self)) } } diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/FutureTests/RawSpanTests.swift index 435010989..49f4a6467 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/FutureTests/RawSpanTests.swift @@ -30,14 +30,14 @@ final class RawSpanTests: XCTestCase { func testInitWithSpanOfIntegers() { let capacity = 4 let a = Array(0...stride) XCTAssertFalse(span.isEmpty) } func testInitWithEmptySpanOfIntegers() { let a: [Int] = [] - let span = RawSpan(_unsafeSpan: a.storage) + let span = RawSpan(_elements: a.storage) XCTAssertTrue(span.isEmpty) } @@ -146,7 +146,7 @@ final class RawSpanTests: XCTestCase { func testUnsafeBytes() { let capacity = 4 let array = Array(0...stride) } } From 98d345bd66496d36a1d62506d405e6d7b07e8cde Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 19 Nov 2024 19:14:18 -0500 Subject: [PATCH 140/195] update Span to match the stdlib --- Sources/Future/Span.swift | 331 +++++++++++++++--------------- Tests/FutureTests/SpanTests.swift | 6 +- 2 files changed, 170 insertions(+), 167 deletions(-) diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift index a63729ff5..affe56436 100644 --- a/Sources/Future/Span.swift +++ b/Sources/Future/Span.swift @@ -12,116 +12,149 @@ import Builtin -// A Span represents a span of memory which -// contains initialized instances of `Element`. -@_disallowFeatureSuppression(NonescapableTypes) +/// `Span` represents a contiguous region of memory +/// which contains initialized instances of `Element`. +/// +/// A `Span` instance is a non-owning, non-escaping view into memory. +/// When a `Span` is created, it inherits the lifetime of the container +/// owning the contiguous memory, ensuring temporal safety and avoiding +/// use-after-free errors. Operations on `Span` are bounds-checked, +/// ensuring spcial safety and avoiding buffer overflow errors. @frozen public struct Span : ~Escapable, Copyable, BitwiseCopyable { - @usableFromInline internal let _pointer: UnsafeRawPointer? + + /// The starting address of this `Span`. + /// + /// `_pointer` can be `nil` if and only if `_count` equals 0. + /// Otherwise, `_pointer` must point to memory that will remain + /// valid and not mutated as long as this `Span` exists. + /// The memory at `_pointer` must be initialized + /// as `_count` instances of `Element`. + @usableFromInline + internal let _pointer: UnsafeRawPointer? @_alwaysEmitIntoClient internal func _start() -> UnsafeRawPointer { _pointer.unsafelyUnwrapped } - @usableFromInline let _count: Int + /// The number of elements in this `Span`. + /// + /// If `_count` equals 0, then `_pointer` may be either `nil` or valid. + /// Any `_count` greater than 0 indicates a valid non-nil `_pointer`. + /// Any `_count` less than 0 is invalid and is undefined behaviour. + @usableFromInline + internal let _count: Int + + /// FIXME: Remove once supported old compilers can recognize lifetime dependence + @_unsafeNonescapableResult + @_alwaysEmitIntoClient + @inline(__always) + internal init() { + _pointer = nil + _count = 0 + } - @_disallowFeatureSuppression(NonescapableTypes) + /// Unsafely create a `Span` over initialized memory. + /// + /// `pointer` must point to a region of `count` initialized instances, + /// or may be `nil` if `count` is 0. + /// + /// The region of memory representing `count` instances starting at `pointer` + /// must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. + /// + /// - Parameters: + /// - pointer: a pointer to the first initialized element. + /// - count: the number of initialized elements in the span. @_alwaysEmitIntoClient + @inline(__always) @lifetime(borrow pointer) internal init( - _unchecked pointer: UnsafeRawPointer?, + _unchecked pointer: borrowing UnsafeRawPointer?, count: Int ) { - _pointer = pointer + _pointer = copy pointer _count = count } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span: @unchecked Sendable where Element: Sendable {} -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - buffer: an `UnsafeBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeElements buffer: UnsafeBufferPointer + _unsafeElements buffer: borrowing UnsafeBufferPointer ) { + //FIXME: Workaround for https://github.com/swiftlang/swift/issues/77235 + let baseAddress = buffer.baseAddress precondition( - ((Int(bitPattern: buffer.baseAddress) & + ((Int(bitPattern: baseAddress) & (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" ) - self.init(_unchecked: buffer.baseAddress, count: buffer.count) + self.init(_unchecked: baseAddress, count: buffer.count) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeElements buffer: UnsafeMutableBufferPointer + _unsafeElements buffer: borrowing UnsafeMutableBufferPointer ) { self.init(_unsafeElements: UnsafeBufferPointer(buffer)) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory representing `count` instances starting at - /// `pointer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The region of memory representing `count` instances starting at `pointer` + /// must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - pointer: a pointer to the first initialized element. /// - count: the number of initialized elements in the span. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( - _unsafeStart pointer: UnsafePointer, + _unsafeStart pointer: borrowing UnsafePointer, count: Int ) { precondition(count >= 0, "Count must not be negative") - self.init(_unsafeElements: .init(start: pointer, count: count)) + self.init(_unsafeElements: .init(start: copy pointer, count: count)) } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span { - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - buffer: an `UnsafeBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -130,16 +163,14 @@ extension Span { self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// - Parameters: /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -149,13 +180,13 @@ extension Span { } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// `buffer` must be correctly aligned for accessing /// an element of type `Element`, and must contain a number of bytes @@ -163,17 +194,15 @@ extension Span where Element: BitwiseCopyable { /// /// - Parameters: /// - buffer: a buffer to initialized elements. - /// - type: the type to use when interpreting the bytes in memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeBytes buffer: UnsafeRawBufferPointer + _unsafeBytes buffer: borrowing UnsafeRawBufferPointer ) { + //FIXME: Workaround for https://github.com/swiftlang/swift/issues/77235 + let baseAddress = buffer.baseAddress precondition( - ((Int(bitPattern: buffer.baseAddress) & + ((Int(bitPattern: baseAddress) & (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" ) @@ -182,13 +211,14 @@ extension Span where Element: BitwiseCopyable { precondition( remainder == 0, "Span must contain a whole number of elements" ) - self.init(_unchecked: buffer.baseAddress, count: count) + self.init(_unchecked: baseAddress, count: count) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// `buffer` must be correctly aligned for accessing /// an element of type `Element`, and must contain a number of bytes @@ -196,23 +226,20 @@ extension Span where Element: BitwiseCopyable { /// /// - Parameters: /// - buffer: a buffer to initialized elements. - /// - type: the type to use when interpreting the bytes in memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( - _unsafeBytes buffer: UnsafeMutableRawBufferPointer + _unsafeBytes buffer: borrowing UnsafeMutableRawBufferPointer ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory representing `count` instances starting at - /// `pointer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The region of memory representing the instances starting at `pointer` + /// must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// `pointer` must be correctly aligned for accessing /// an element of type `Element`, and `byteCount` @@ -221,23 +248,21 @@ extension Span where Element: BitwiseCopyable { /// - Parameters: /// - pointer: a pointer to the first initialized element. /// - byteCount: the number of initialized elements in the span. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( - _unsafeStart pointer: UnsafeRawPointer, + _unsafeStart pointer: borrowing UnsafeRawPointer, byteCount: Int ) { precondition(byteCount >= 0, "Count must not be negative") - self.init(_unsafeBytes: .init(start: pointer, count: byteCount)) + self.init(_unsafeBytes: .init(start: copy pointer, count: byteCount)) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// `buffer` must be correctly aligned for accessing /// an element of type `Element`, and must contain a number of bytes @@ -245,10 +270,6 @@ extension Span where Element: BitwiseCopyable { /// /// - Parameters: /// - buffer: a buffer to initialized elements. - /// - type: the type to use when interpreting the bytes in memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -257,10 +278,11 @@ extension Span where Element: BitwiseCopyable { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } - /// Unsafely creates a `Span` over initialized memory. + /// Unsafely create a `Span` over initialized memory. /// - /// The memory in `buffer` must remain valid throughout the lifetime of - /// the newly-created `Span`. + /// The memory in `buffer` must remain valid, initialized and immutable + /// throughout the lifetime of the newly-created `Span`. + /// Failure to maintain this invariant results in undefined behaviour. /// /// `buffer` must be correctly aligned for accessing /// an element of type `Element`, and must contain a number of bytes @@ -268,10 +290,6 @@ extension Span where Element: BitwiseCopyable { /// /// - Parameters: /// - buffer: a buffer to initialized elements. - /// - type: the type to use when interpreting the bytes in memory. - /// - owner: a binding whose lifetime must exceed that of - /// the newly created `Span`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -279,9 +297,21 @@ extension Span where Element: BitwiseCopyable { ) { self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) } + + /// Create a `Span` over the bytes represented by a `RawSpan` + /// + /// - Parameters: + /// - bytes: An existing `RawSpan`, which will define both this + /// `Span`'s lifetime and the memory it represents. + @_alwaysEmitIntoClient + @lifetime(bytes) + public init(_bytes bytes: consuming RawSpan) { + self.init( + _unsafeBytes: .init(start: bytes._pointer, count: bytes.byteCount) + ) + } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: Equatable { /// Returns a Boolean value indicating whether this and another span @@ -294,7 +324,6 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: Self) -> Bool { guard count == other.count else { return false } @@ -322,7 +351,6 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { let equal = other.withContiguousStorageIfAvailable { @@ -346,7 +374,6 @@ extension Span where Element: Equatable { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Sequence) -> Bool { var offset = 0 @@ -359,7 +386,6 @@ extension Span where Element: Equatable { } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { @_alwaysEmitIntoClient @@ -369,7 +395,6 @@ extension Span where Element: ~Copyable { } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// The number of elements in the span. @@ -394,26 +419,12 @@ extension Span where Element: ~Copyable { /// order. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public var indices: Range { Range(uncheckedBounds: (0, _count)) } } -@_disallowFeatureSuppression(NonescapableTypes) -extension Span where Element: BitwiseCopyable { - - /// Construct a RawSpan over the memory represented by this span - /// - /// - Returns: a RawSpan over the memory represented by this span - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe //FIXME: remove when the lifetime inference is fixed - @_alwaysEmitIntoClient - public var _unsafeRawSpan: RawSpan { RawSpan(_elements: self) } -} - -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Accesses the element at the specified position in the `Span`. @@ -422,42 +433,44 @@ extension Span where Element: ~Copyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public subscript(_ position: Index) -> Element { - //FIXME: change to unsafeRawAddress or unsafeAddress when ready - _read { - precondition(indices.contains(position), "index out of bounds") - yield self[unchecked: position] + //FIXME: change to unsafeRawAddress when ready + unsafeAddress { + precondition(indices.contains(position), "Index out of bounds") + return _unsafeAddressOfElement(unchecked: position) } } /// Accesses the element at the specified position in the `Span`. /// - /// This subscript does not validate `position`; this is an unsafe operation. + /// This subscript does not validate `position`. Using this subscript + /// with an invalid `position` results in undefined behaviour. /// /// - Parameter position: The offset of the element to access. `position` /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { - //FIXME: change to unsafeRawAddress or unsafeAddress when ready - _read { - let elementOffset = position &* MemoryLayout.stride - let address = _start().advanced(by: elementOffset) - let binding = Builtin.bindMemory( - address._rawValue, 1._builtinWordValue, Element.self - ) - defer { Builtin.rebindMemory(address._rawValue, binding) } - yield UnsafePointer(address._rawValue).pointee + //FIXME: change to unsafeRawAddress when ready + unsafeAddress { + _unsafeAddressOfElement(unchecked: position) } } + + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + unchecked position: Index + ) -> UnsafePointer { + let elementOffset = position &* MemoryLayout.stride + let address = _start().advanced(by: elementOffset) + return address.assumingMemoryBound(to: Element.self) + } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { /// Accesses the element at the specified position in the `Span`. @@ -466,13 +479,12 @@ extension Span where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public subscript(_ position: Index) -> Element { get { precondition( UInt(bitPattern: position) < UInt(bitPattern: _count), - "index out of bounds" + "Index out of bounds" ) return self[unchecked: position] } @@ -480,13 +492,13 @@ extension Span where Element: BitwiseCopyable { /// Accesses the element at the specified position in the `Span`. /// - /// This subscript does not validate `position`; this is an unsafe operation. + /// This subscript does not validate `position`. Using this subscript + /// with an invalid `position` results in undefined behaviour. /// /// - Parameter position: The offset of the element to access. `position` /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { @@ -499,7 +511,6 @@ extension Span where Element: BitwiseCopyable { } //MARK: sub-spans -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Constructs a new span over the items within the supplied range of @@ -515,13 +526,13 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(_ bounds: Range) -> Self { precondition( UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "index range out of bounds" + "Index range out of bounds" ) return _extracting(unchecked: bounds) } @@ -541,9 +552,9 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(unchecked bounds: Range) -> Self { let delta = bounds.lowerBound &* MemoryLayout.stride return Span(_unchecked: _pointer?.advanced(by: delta), count: bounds.count) @@ -562,8 +573,8 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(_ bounds: some RangeExpression) -> Self { _extracting(bounds.relative(to: indices)) } @@ -583,11 +594,11 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over the items within `bounds` /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @unsafe @_alwaysEmitIntoClient + @lifetime(self) public func _extracting( - uncheckedBounds bounds: some RangeExpression + unchecked bounds: some RangeExpression ) -> Self { _extracting(unchecked: bounds.relative(to: indices)) } @@ -601,18 +612,16 @@ extension Span where Element: ~Copyable { /// - Returns: A `Span` over all the items of this span. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(_: UnboundedRange) -> Self { self } } //MARK: UnsafeBufferPointer access hatch -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { - //FIXME: mark closure parameter as non-escaping /// Calls a closure with a pointer to the viewed contiguous storage. /// /// The buffer pointer passed as an argument to `body` is valid only @@ -625,7 +634,6 @@ extension Span where Element: ~Copyable { /// for the `withUnsafeBufferPointer(_:)` method. The closure's /// parameter is valid only for the duration of its execution. /// - Returns: The return value of the `body` closure parameter. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result @@ -641,10 +649,8 @@ extension Span where Element: ~Copyable { } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: BitwiseCopyable { - //FIXME: mark closure parameter as non-escaping /// Calls the given closure with a pointer to the underlying bytes of /// the viewed contiguous storage. /// @@ -659,12 +665,13 @@ extension Span where Element: BitwiseCopyable { /// The closure's parameter is valid only for the duration of /// its execution. /// - Returns: The return value of the `body` closure parameter. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { - try RawSpan(_elements: self).withUnsafeBytes(body) + try body( + .init(start: _pointer, count: _count * MemoryLayout.stride) + ) } } @@ -693,11 +700,9 @@ extension Span where Element: Copyable { } } -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Returns a Boolean value indicating whether two `Span` instances /// refer to the same region in memory. - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func isIdentical(to other: Self) -> Bool { (self._pointer == other._pointer) && (self._count == other._count) @@ -709,26 +714,24 @@ extension Span where Element: ~Copyable { /// Parameters: /// - span: a span that may be a subrange of `self` /// Returns: A range of indices within `self`, or `nil` - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func indices(of span: borrowing Self) -> Range? { - if span._count > _count { return nil } - guard let spanStart = span._pointer, _count > 0 else { - return _pointer == span._pointer ? Range(uncheckedBounds: (0, 0)) : nil + public func indices(of other: borrowing Self) -> Range? { + if other._count > _count { return nil } + guard let spanStart = other._pointer, _count > 0 else { + return _pointer == other._pointer ? Range(uncheckedBounds: (0, 0)) : nil } let start = _start() let stride = MemoryLayout.stride - let spanEnd = spanStart + stride &* span._count + let spanEnd = spanStart + stride &* other._count if spanStart < start || spanEnd > (start + stride &* _count) { return nil } let byteOffset = start.distance(to: spanStart) let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) guard r == 0 else { return nil } - return Range(uncheckedBounds: (lower, lower &+ span._count)) + return Range(uncheckedBounds: (lower, lower &+ other._count)) } } //MARK: prefixes and suffixes -@_disallowFeatureSuppression(NonescapableTypes) extension Span where Element: ~Copyable { /// Returns a span containing the initial elements of this span, @@ -746,10 +749,10 @@ extension Span where Element: ~Copyable { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(first maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a prefix of negative length.") + precondition(maxLength >= 0, "Can't have a prefix of negative length") let newCount = min(maxLength, count) return Self(_unchecked: _pointer, count: newCount) } @@ -768,10 +771,10 @@ extension Span where Element: ~Copyable { /// - Returns: A span leaving off the specified number of elements at the end. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(droppingLast k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements.") + precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, count) return Self(_unchecked: _pointer, count: count &- droppedCount) } @@ -791,10 +794,10 @@ extension Span where Element: ~Copyable { /// - Returns: A span with at most `maxLength` elements. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(last maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a suffix of negative length.") + precondition(maxLength >= 0, "Can't have a suffix of negative length") let newCount = min(maxLength, count) let offset = (count &- newCount) * MemoryLayout.stride let newStart = _pointer?.advanced(by: offset) @@ -815,10 +818,10 @@ extension Span where Element: ~Copyable { /// - Returns: A span starting after the specified number of elements. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient + @lifetime(self) public func _extracting(droppingFirst k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements.") + precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, count) let offset = droppedCount * MemoryLayout.stride let newStart = _pointer?.advanced(by: offset) diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index 6f2b34e7a..ae006dfa7 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -189,7 +189,7 @@ final class SpanTests: XCTestCase { let span = Span(_unsafeElements: $0) XCTAssertTrue(span._elementsEqual(span._extracting(0.. Date: Wed, 18 Dec 2024 14:01:07 -0800 Subject: [PATCH 141/195] Update MutableSpan and MutableRawSpan --- Package.swift | 3 +- Sources/Future/Box.swift | 77 -- Sources/Future/CMakeLists.txt | 17 +- Sources/Future/Cell.swift | 57 -- Sources/Future/Containers/Container.swift | 158 ---- Sources/Future/Containers/DynamicArray.swift | 198 ----- Sources/Future/Containers/NewArray.swift | 118 --- Sources/Future/Containers/RigidArray.swift | 262 ------ Sources/Future/Containers/Shared.swift | 188 ---- Sources/Future/ContiguousStorage.swift | 104 --- Sources/Future/Inout.swift | 83 -- Sources/Future/LifetimeOverride.swift | 53 +- Sources/Future/MutableRawSpan.swift | 392 +++++++++ Sources/Future/MutableSpan.swift | 472 ++++------ Sources/Future/MutableSpanSlicing.swift | 142 +++ Sources/Future/OutputSpan.swift | 114 ++- Sources/Future/RawSpan.swift | 693 --------------- Sources/Future/Span+Iterator.swift | 60 -- Sources/Future/Span.swift | 830 ------------------ Sources/Future/SpanExtensions.swift | 110 +++ .../Future/StdlibOutputSpanExtensions.swift | 22 +- Sources/Future/StdlibSpanExtensions.swift | 502 ----------- Tests/FutureTests/BoxTests.swift | 43 - Tests/FutureTests/CellTests.swift | 37 - .../FutureTests/ContiguousStorageTests.swift | 59 -- Tests/FutureTests/DynamicArrayTests.swift | 180 ---- Tests/FutureTests/Inout.swift | 29 - Tests/FutureTests/MutableSpanTests.swift | 293 +++++-- Tests/FutureTests/OutputSpanTests.swift | 36 +- Tests/FutureTests/RawSpanTests.swift | 46 +- Tests/FutureTests/SpanTests.swift | 166 ++-- .../StdlibOutputSpanExtensionTests.swift | 7 +- .../StdlibSpanExtensionTests.swift | 181 ---- .../AssertionContexts/Assertions.swift | 2 +- .../AssertionContexts/TestContext.swift | 2 +- 35 files changed, 1281 insertions(+), 4455 deletions(-) delete mode 100644 Sources/Future/Box.swift delete mode 100644 Sources/Future/Cell.swift delete mode 100644 Sources/Future/Containers/Container.swift delete mode 100644 Sources/Future/Containers/DynamicArray.swift delete mode 100644 Sources/Future/Containers/NewArray.swift delete mode 100644 Sources/Future/Containers/RigidArray.swift delete mode 100644 Sources/Future/Containers/Shared.swift delete mode 100644 Sources/Future/ContiguousStorage.swift delete mode 100644 Sources/Future/Inout.swift create mode 100644 Sources/Future/MutableRawSpan.swift create mode 100644 Sources/Future/MutableSpanSlicing.swift delete mode 100644 Sources/Future/RawSpan.swift delete mode 100644 Sources/Future/Span+Iterator.swift delete mode 100644 Sources/Future/Span.swift create mode 100644 Sources/Future/SpanExtensions.swift delete mode 100644 Sources/Future/StdlibSpanExtensions.swift delete mode 100644 Tests/FutureTests/BoxTests.swift delete mode 100644 Tests/FutureTests/CellTests.swift delete mode 100644 Tests/FutureTests/ContiguousStorageTests.swift delete mode 100644 Tests/FutureTests/DynamicArrayTests.swift delete mode 100644 Tests/FutureTests/Inout.swift delete mode 100644 Tests/FutureTests/StdlibSpanExtensionTests.swift diff --git a/Package.swift b/Package.swift index ccd8a07a3..ccd0adf2c 100644 --- a/Package.swift +++ b/Package.swift @@ -55,8 +55,7 @@ var defines: [String] = [ let _sharedSettings: [SwiftSetting] = defines.map { .define($0) } + [ .enableExperimentalFeature("AllowUnsafeAttribute"), .enableExperimentalFeature("BuiltinModule"), - .enableExperimentalFeature("NonescapableTypes"), - .enableExperimentalFeature("RawLayout"), + .enableExperimentalFeature("LifetimeDependence"), .enableExperimentalFeature("SuppressedAssociatedTypes"), ] diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift deleted file mode 100644 index 42f7f95f3..000000000 --- a/Sources/Future/Box.swift +++ /dev/null @@ -1,77 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -@frozen -public struct Box: ~Copyable { - @usableFromInline - internal let _pointer: UnsafeMutablePointer - - @_alwaysEmitIntoClient - @_transparent - public init(_ value: consuming T) { - _pointer = UnsafeMutablePointer.allocate(capacity: 1) - _pointer.initialize(to: value) - } - - @_alwaysEmitIntoClient - @_transparent - public init(_ fromInout: consuming Inout) { - _pointer = fromInout._pointer - } - - @_alwaysEmitIntoClient - @inlinable - deinit { - _pointer.deinitialize(count: 1) - _pointer.deallocate() - } -} - -extension Box where T: ~Copyable { - @_alwaysEmitIntoClient - @_transparent - public consuming func consume() -> T { - let result = _pointer.move() - _pointer.deallocate() - discard self - return result - } - - @_alwaysEmitIntoClient - @_transparent - @lifetime(immortal) - public consuming func leak() -> Inout { - let result = Inout(unsafeImmortalAddress: _pointer) - discard self - return result - } - - @_alwaysEmitIntoClient - public subscript() -> T { - @_transparent - unsafeAddress { - UnsafePointer(_pointer) - } - - @_transparent - nonmutating unsafeMutableAddress { - _pointer - } - } -} - -extension Box where T: Copyable { - @_alwaysEmitIntoClient - @_transparent - public borrowing func copy() -> T { - _pointer.pointee - } -} diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index f9ef82e02..9761e8856 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -8,22 +8,13 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(Future - "Box.swift" - "Cell.swift" - "ContiguousStorage.swift" - "Inout.swift" - "OutputSpan.swift" "LifetimeOverride.swift" - "RawSpan.swift" - "Span.swift" - "Span+Iterator.swift" + "MutableSpan.swift" + "OutputSpan.swift" + "SpanExtensions.swift" "StdlibOutputSpanExtensions.swift" "StdlibSpanExtensions.swift" - "Containers/Shared.swift" - "Containers/Container.swift" - "Containers/DynamicArray.swift" - "Containers/NewArray.swift" - "Containers/RigidArray.swift" + "UnsafeBufferPointer+Additions.swift" ) target_link_libraries(Future PRIVATE diff --git a/Sources/Future/Cell.swift b/Sources/Future/Cell.swift deleted file mode 100644 index f712836dd..000000000 --- a/Sources/Future/Cell.swift +++ /dev/null @@ -1,57 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import Builtin - -@frozen -@_rawLayout(like: T, movesAsLike) -public struct Cell: ~Copyable { - @_alwaysEmitIntoClient - @_transparent - public var unsafeAddress: UnsafeMutablePointer { - UnsafeMutablePointer(Builtin.addressOfRawLayout(self)) - } - - @_alwaysEmitIntoClient - @_transparent - public init(_ value: consuming T) { - unsafeAddress.initialize(to: value) - } -} - -extension Cell where T: ~Copyable { - @_alwaysEmitIntoClient - @_transparent - public mutating func asInout() -> Inout { - Inout(unsafeAddress: unsafeAddress, owner: &self) - } - - @_alwaysEmitIntoClient - public subscript() -> T { - @_transparent - unsafeAddress { - UnsafePointer(unsafeAddress) - } - - @_transparent - nonmutating unsafeMutableAddress { - unsafeAddress - } - } -} - -extension Cell where T: Copyable { - @_alwaysEmitIntoClient - @_transparent - public borrowing func copy() -> T { - unsafeAddress.pointee - } -} diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift deleted file mode 100644 index 4df0f6b81..000000000 --- a/Sources/Future/Containers/Container.swift +++ /dev/null @@ -1,158 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -public protocol BorrowingIteratorProtocol: ~Escapable { - associatedtype Element: ~Copyable - - @lifetime(self) - mutating func nextChunk(maximumCount: Int) -> Span -} - -public protocol Container: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable - - associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Escapable - where BorrowingIterator.Element == Element - - borrowing func startBorrowingIteration() -> BorrowingIterator - borrowing func startBorrowingIteration(from start: Index) -> BorrowingIterator - - associatedtype Index: Comparable - - var isEmpty: Bool { get } - var count: Int { get } - - var startIndex: Index { get } - var endIndex: Index { get } - - // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements - // FIXME: (Or rather, accessors with proper projection semantics.) - @_borrowed subscript(index: Index) -> Element { get } - - func index(after index: Index) -> Index - func formIndex(after i: inout Index) - - func index(at position: borrowing BorrowingIterator) -> Index - - func distance(from start: Index, to end: Index) -> Int - - func index(_ index: Index, offsetBy n: Int) -> Index - - func formIndex( - _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index - ) -} - -public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { - override associatedtype Element: ~Copyable - - func index(before i: Index) -> Index - func formIndex(before i: inout Index) - - @_nonoverride func index(_ i: Index, offsetBy distance: Int) -> Index - @_nonoverride func formIndex( - _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index - ) -} - -public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { - override associatedtype Element: ~Copyable -} - -extension Strideable { - @inlinable - public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { - if distance >= 0 { - guard limit >= self else { - self = self.advanced(by: distance) - distance = 0 - return - } - let d = Swift.min(distance, self.distance(to: limit)) - self = self.advanced(by: d) - distance -= d - } else { - guard limit <= self else { - self = self.advanced(by: distance) - distance = 0 - return - } - let d = Swift.max(distance, self.distance(to: limit)) - self = self.advanced(by: d) - distance -= d - } - } -} - -extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Self: ~Copyable { - @inlinable - public func index(after index: Index) -> Index { - // Note: Range checks are deferred until element access. - index.advanced(by: 1) - } - - @inlinable - public func index(before index: Index) -> Index { - // Note: Range checks are deferred until element access. - index.advanced(by: -1) - } - - @inlinable - public func formIndex(after index: inout Index) { - // Note: Range checks are deferred until element access. - index = index.advanced(by: 1) - } - - @inlinable - public func formIndex(before index: inout Index) { - // Note: Range checks are deferred until element access. - index = index.advanced(by: -1) - } - - @inlinable - public func distance(from start: Index, to end: Index) -> Int { - // Note: Range checks are deferred until element access. - start.distance(to: end) - } - - @inlinable - public func index(_ index: Index, offsetBy n: Int) -> Index { - // Note: Range checks are deferred until element access. - index.advanced(by: n) - } - - @inlinable - public func formIndex( - _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index - ) { - // Note: Range checks are deferred until element access. - index.advance(by: &distance, limitedBy: limit) - } -} - -#if false // TODO -public protocol Muterator: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable - - @lifetime(self) - mutating func nextChunk(maximumCount: Int) -> MutableSpan -} - -public protocol MutableContainer: Container, ~Copyable, ~Escapable { - associatedtype MutatingIterationState: ~Copyable, ~Escapable - - mutating func startMutatingIteration() -> MutatingIterationState - - // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements - @_borrowed subscript(index: Index) -> Element { get set } - -} -#endif diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift deleted file mode 100644 index 0da61708d..000000000 --- a/Sources/Future/Containers/DynamicArray.swift +++ /dev/null @@ -1,198 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/// A dynamically self-resizing, heap allocated, noncopyable array -/// of potentially noncopyable elements. -@frozen -public struct DynamicArray: ~Copyable { - @usableFromInline - internal var _storage: RigidArray - - @inlinable - public init() { - _storage = .init(capacity: 0) - } - - @inlinable - public init(minimumCapacity: Int) { - _storage = .init(capacity: minimumCapacity) - } - - @inlinable - public init(count: Int, initializedBy generator: (Int) -> Element) { - _storage = .init(count: count, initializedBy: generator) - } -} - -extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} - -extension DynamicArray where Element: ~Copyable { - @inlinable - public var capacity: Int { _storage.capacity } -} - -extension DynamicArray where Element: ~Copyable { - public var storage: Span { - _storage.storage - } -} - -extension DynamicArray: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = RigidArray.BorrowingIterator - public typealias Index = Int - - public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(for: _storage, startOffset: 0) - } - - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(for: _storage, startOffset: start) - } - - @inlinable - public var isEmpty: Bool { _storage.isEmpty } - - @inlinable - public var count: Int { _storage.count } - - @inlinable - public var startIndex: Int { 0 } - - @inlinable - public var endIndex: Int { _storage.count } - - @inlinable - public subscript(position: Int) -> Element { - @inline(__always) - _read { - yield _storage[position] - } - @inline(__always) - _modify { - yield &_storage[position] - } - } - - @inlinable - public func index(after i: Int) -> Int { i + 1 } - - @inlinable - public func index(before i: Int) -> Int { i - 1 } - - @inlinable - public func formIndex(after index: inout Int) { - // Note: Range checks are deferred until element access. - index += 1 - } - - @inlinable - public func formIndex(before index: inout Int) { - // Note: Range checks are deferred until element access. - index -= 1 - } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - // Note: Range checks are deferred until element access. - position._offset - } - - @inlinable - public func distance(from start: Int, to end: Int) -> Int { - // Note: Range checks are deferred until element access. - end - start - } - - @inlinable - public func index(_ index: Int, offsetBy n: Int) -> Int { - // Note: Range checks are deferred until element access. - index + n - } - - @inlinable - public func formIndex( - _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int - ) { - _storage.formIndex(&index, offsetBy: &distance, limitedBy: limit) - } -} - -extension DynamicArray where Element: ~Copyable { - @inlinable - public func borrowElement ( - at index: Int, - by body: (borrowing Element) throws(E) -> R - ) throws(E) -> R { - try _storage.borrowElement(at: index, by: body) - } - - @inlinable - public mutating func updateElement ( - at index: Int, - by body: (inout Element) throws(E) -> R - ) throws(E) -> R { - try _storage.updateElement(at: index, by: body) - } -} - -extension DynamicArray where Element: ~Copyable { - @inlinable - @discardableResult - public mutating func remove(at index: Int) -> Element { - _storage.remove(at: index) - } -} - -extension DynamicArray where Element: ~Copyable { - @inlinable - public mutating func reserveCapacity(_ n: Int) { - _storage.reserveCapacity(n) - } -} - -extension DynamicArray where Element: ~Copyable { - @inlinable - internal static func _grow(_ capacity: Int) -> Int { - 2 * capacity - } - - @inlinable - public mutating func _ensureFreeCapacity(_ minimumCapacity: Int) { - guard _storage.freeCapacity < minimumCapacity else { return } - reserveCapacity(max(count + minimumCapacity, Self._grow(capacity))) - } -} - -extension DynamicArray where Element: ~Copyable { - @inlinable - public mutating func append(_ item: consuming Element) { - _ensureFreeCapacity(1) - _storage.append(item) - } -} - -extension DynamicArray where Element: ~Copyable { - @inlinable - public mutating func insert(_ item: consuming Element, at index: Int) { - precondition(index >= 0 && index <= count) - _ensureFreeCapacity(1) - _storage.insert(item, at: index) - } -} - -extension DynamicArray { - @inlinable - public mutating func append(contentsOf items: some Sequence) { - for item in items { - append(item) - } - } -} diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift deleted file mode 100644 index 423fb565b..000000000 --- a/Sources/Future/Containers/NewArray.swift +++ /dev/null @@ -1,118 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/// What `Array` might look like if we defined it today. -@frozen -public struct NewArray { - @usableFromInline - internal var _storage: Shared> - - @inlinable - public init(minimumCapacity: Int) { - self._storage = Shared(RigidArray(capacity: minimumCapacity)) - } -} - -extension NewArray { - public var storage: Span { -#if false - // FIXME: This is what I want to write; alas, lifetimes are messed up. - return _storage.value.storage -#else - return Span(_unsafeElements: _storage.value._items) -#endif - } -} - -extension NewArray { - @inlinable - public var capacity: Int { _storage.read { $0.capacity } } - - @inlinable - internal mutating func _ensureUnique() { - _storage.ensureUnique { $0._copy() } - } - - @inlinable - internal mutating func _ensureUnique( - minimumCapacity: Int, - linear: Bool = false - ) { - if !_storage.isUnique() { - let c = Swift.max(count, minimumCapacity) - _storage = Shared(_storage.read { $0._copy(capacity: c) }) - } else if minimumCapacity > self.capacity { - _storage = Shared(_storage.update { $0._move(capacity: minimumCapacity) }) - } - } - - @inlinable - internal func _read( - _ body: (borrowing RigidArray) throws(E) -> Result - ) throws(E) -> Result { - try _storage.read(body) - } - - @inlinable - internal mutating func _update( - _ body: (inout RigidArray) throws(E) -> Result - ) throws(E) -> Result { - _ensureUnique() - return try _storage.update(body) - } - - @inlinable - internal mutating func _update( - minimumCapacity: Int, - _ body: (inout RigidArray) throws(E) -> Result - ) throws(E) -> Result { - _ensureUnique(minimumCapacity: minimumCapacity) - return try _storage.update(body) - } -} - -extension NewArray: RandomAccessCollection, MutableCollection { - public typealias Index = Int - - @inlinable - public var startIndex: Int { 0 } - - @inlinable - public var endIndex: Int { _storage.read { $0.count } } - - public subscript(position: Int) -> Element { - @inlinable - get { - _read { $0[position] } - } - @inlinable - @inline(__always) - _modify { - _ensureUnique() - yield &_storage.value[position] - } - } -} - -extension NewArray: RangeReplaceableCollection { - public init() { - // FIXME: Figure out if we can implement empty singletons in this setup. - self._storage = Shared(RigidArray(capacity: 0)) - } - - mutating public func replaceSubrange( - _ subrange: Range, - with newElements: some Collection - ) { - let delta = newElements.count - subrange.count - _ensureUnique(minimumCapacity: capacity + delta) - } -} diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift deleted file mode 100644 index eda928f76..000000000 --- a/Sources/Future/Containers/RigidArray.swift +++ /dev/null @@ -1,262 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/// A manually resizable, heap allocated, noncopyable array of -/// potentially noncopyable elements. -@frozen -public struct RigidArray: ~Copyable { - @usableFromInline - internal var _storage: UnsafeMutableBufferPointer - - @usableFromInline - internal var _count: Int - - @inlinable - public init(capacity: Int) { - precondition(capacity >= 0) - if capacity > 0 { - _storage = .allocate(capacity: capacity) - } else { - _storage = .init(start: nil, count: 0) - } - _count = 0 - } - - @inlinable - public init(count: Int, initializedBy generator: (Int) -> Element) { - _storage = .allocate(capacity: count) - for i in 0 ..< count { - _storage.initializeElement(at: i, to: generator(i)) - } - _count = count - } - - deinit { - _storage.extracting(0 ..< count).deinitialize() - _storage.deallocate() - } -} - -extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} - -extension RigidArray where Element: ~Copyable { - @inlinable - public var capacity: Int { _storage.count } - - @inlinable - public var freeCapacity: Int { capacity - count } - - @inlinable - public var isFull: Bool { freeCapacity == 0 } -} - -extension RigidArray where Element: ~Copyable { - public var storage: Span { - Span(_unsafeElements: _items) - } -} - -extension RigidArray: RandomAccessContainer where Element: ~Copyable { - public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { - @usableFromInline - internal let _items: UnsafeBufferPointer - - @usableFromInline - internal var _offset: Int - - @inlinable - internal init(for array: borrowing RigidArray, startOffset: Int) { - self._items = UnsafeBufferPointer(array._items) - self._offset = startOffset - } - - @lifetime(self) - public mutating func nextChunk( - maximumCount: Int - ) -> Span { - let end = _offset + Swift.min(maximumCount, _items.count - _offset) - defer { _offset = end } - let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) - return Span(_unsafeElements: chunk) - } - } - - public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(for: self, startOffset: 0) - } - - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(for: self, startOffset: start) - } - - public typealias Index = Int - - @inlinable - public var isEmpty: Bool { count == 0 } - - @inlinable - public var count: Int { _count } - - @inlinable - public var startIndex: Int { 0 } - - @inlinable - public var endIndex: Int { count } - - @inlinable - public subscript(position: Int) -> Element { - @inline(__always) - _read { - precondition(position >= 0 && position < _count) - yield _storage[position] - } - @inline(__always) - _modify { - precondition(position >= 0 && position < _count) - yield &_storage[position] - } - } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - precondition(position._items === UnsafeBufferPointer(self._items)) - return position._offset - } -} - -extension RigidArray where Element: ~Copyable { - @inlinable - internal var _items: UnsafeMutableBufferPointer { - _storage.extracting(Range(uncheckedBounds: (0, _count))) - } - - @inlinable - internal var _freeSpace: UnsafeMutableBufferPointer { - _storage.extracting(Range(uncheckedBounds: (_count, capacity))) - } -} - -extension RigidArray where Element: ~Copyable { - @inlinable - public mutating func resize(to newCapacity: Int) { - precondition(newCapacity >= count) - guard newCapacity != capacity else { return } - let newStorage: UnsafeMutableBufferPointer = .allocate(capacity: newCapacity) - let i = newStorage.moveInitialize(fromContentsOf: self._items) - assert(i == count) - _storage.deallocate() - _storage = newStorage - } - - @inlinable - public mutating func reserveCapacity(_ n: Int) { - guard capacity < n else { return } - resize(to: n) - } -} - - -extension RigidArray where Element: ~Copyable { - @inlinable - public func borrowElement ( - at index: Int, - by body: (borrowing Element) throws(E) -> R - ) throws(E) -> R { - precondition(index >= 0 && index < _count) - return try body(_storage[index]) - } - - @inlinable - public mutating func updateElement ( - at index: Int, - by body: (inout Element) throws(E) -> R - ) throws(E) -> R { - precondition(index >= 0 && index < _count) - return try body(&_storage[index]) - } -} - -extension RigidArray where Element: ~Copyable { - @inlinable - @discardableResult - public mutating func remove(at index: Int) -> Element { - precondition(index >= 0 && index < count) - let old = _storage.moveElement(from: index) - let source = _storage.extracting(index + 1 ..< count) - let target = _storage.extracting(index ..< count - 1) - let i = target.moveInitialize(fromContentsOf: source) - assert(i == target.endIndex) - _count -= 1 - return old - } -} - -extension RigidArray where Element: ~Copyable { - @inlinable - public mutating func append(_ item: consuming Element) { - precondition(!isFull) - _storage.initializeElement(at: _count, to: item) - _count += 1 - } -} - -extension RigidArray where Element: ~Copyable { - @inlinable - public mutating func insert(_ item: consuming Element, at index: Int) { - precondition(index >= 0 && index <= count) - precondition(!isFull) - if index < count { - let source = _storage.extracting(index ..< count) - let target = _storage.extracting(index + 1 ..< count + 1) - let last = target.moveInitialize(fromContentsOf: source) - assert(last == target.endIndex) - } - _storage.initializeElement(at: index, to: item) - _count += 1 - } -} - -extension RigidArray { - @inlinable - public mutating func append(contentsOf items: some Sequence) { - for item in items { - append(item) - } - } -} - -extension RigidArray { - @inlinable - internal func _copy() -> Self { - _copy(capacity: capacity) - } - - @inlinable - internal func _copy(capacity: Int) -> Self { - precondition(capacity >= count) - var result = RigidArray(capacity: capacity) - let initialized = result._storage.initialize(fromContentsOf: _storage) - precondition(initialized == count) - result._count = count - return result - } - - @inlinable - internal mutating func _move(capacity: Int) -> Self { - precondition(capacity >= count) - var result = RigidArray(capacity: capacity) - let initialized = result._storage.moveInitialize(fromContentsOf: _storage) - precondition(initialized == count) - result._count = count - self._count = 0 - return result - } -} diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift deleted file mode 100644 index 1ebcc284c..000000000 --- a/Sources/Future/Containers/Shared.swift +++ /dev/null @@ -1,188 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import Builtin // For Shared.isIdentical - -/// A utility adapter that wraps a noncopyable storage type in a copy-on-write -/// struct, enabling efficient implementation of value semantics. The type -/// allows safe borrowing and mutating access to its storage, with minimal fuss. -/// -/// Like `ManagedBufferPointer`, this type is intended to be used within the -/// internal implementation of public types. Instances of it aren't designed -/// to be exposed as public. -@frozen -public struct Shared { - @usableFromInline - internal var _box: _Box - - @inlinable - public init(_ storage: consuming Storage) { - self._box = _Box(storage) - } -} - -extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} - -#if true // FIXME: Silent error on class definition nested in noncopyable struct -@usableFromInline -internal final class _SharedBox { - @exclusivity(unchecked) - @usableFromInline - internal var storage: Storage - - @inlinable - internal init(_ storage: consuming Storage) { - self.storage = storage - } -} - -extension Shared where Storage: ~Copyable { - @usableFromInline - internal typealias _Box = _SharedBox -} -#else -extension Shared where Storage: ~Copyable { - @usableFromInline - internal final class _Box { - @exclusivity(unchecked) - @usableFromInline - internal var storage: Storage - - @inlinable - internal init(_ storage: consuming Storage) { - self.storage = storage - } - } -} -#endif - -extension Shared where Storage: ~Copyable { - @inlinable - @inline(__always) - public mutating func isUnique() -> Bool { - isKnownUniquelyReferenced(&_box) - } - - @inlinable - public mutating func ensureUnique( - cloner: (borrowing Storage) -> Storage - ) { - if isUnique() { return } - _box = _Box(cloner(_box.storage)) - } -} - -import struct SwiftShims.HeapObject - -extension Shared where Storage: ~Copyable { - // FIXME: Can we avoid hacks like this? If not, perhaps `_Box.storage` should be tail-allocated. - @inlinable - internal var _address: UnsafePointer { - // Adapted from _getUnsafePointerToStoredProperties - let p = ( - UnsafeRawPointer(Builtin.bridgeToRawPointer(_box)) - + MemoryLayout.size) - return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) - } - - @inlinable - internal var _mutableAddress: UnsafeMutablePointer { - // Adapted from _getUnsafePointerToStoredProperties - let p = ( - UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_box)) - + MemoryLayout.size) - return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) - } -} - -extension Shared where Storage: ~Copyable { - @inlinable - @inline(__always) - public var value: /*FIXME: dependsOn(self)*/ Storage { - // FIXME: This implements the wrong shape. - // FIXME: Semantically it yields a borrow scoped to an access of this `value` variable, - // FIXME: not the much wider borrow of `self`, which we'd actually want. - _read { - yield _box.storage - } - _modify { - precondition(isUnique()) - yield &_box.storage - } - } - - // FIXME: This builds, but attempts to use it don't: they fail with an unexpected exclusivity violation. - @inlinable - @lifetime(self) - public subscript() -> Storage { - //@_transparent - unsafeAddress { - _address - } - - //@_transparent - unsafeMutableAddress { - precondition(isUnique()) - return _mutableAddress - } - } - - @inlinable - @inline(__always) - public func read( - _ body: (borrowing Storage) throws(E) -> R - ) throws(E) -> R { - // FIXME: This also implements the wrong shape. - // FIXME: The borrow of `Storage` isn't tied to `self` at all, and - // FIXME: it obviously cannot legally escape the `read` call. - try body(_box.storage) - } - - @inlinable - @inline(__always) - public mutating func update( - _ body: (inout Storage) throws(E) -> R - ) throws(E) -> R { - precondition(isUnique()) - return try body(&_box.storage) - } -} - -#if false // FIXME: Use it or lose it -extension Shared where Storage: ~Copyable { - // This is the actual shape we want. There is currently no way to express it. - @inlinable - @lifetime(borrow self) - public borrowing func read() -> Borrow { - // This is gloriously (and very explicitly) unsafe, as it should be. - // `Shared` is carefully constructed to guarantee that - // lifetime(self) == lifetime(_box.storage); but we have not - // (cannot) explain this to the compiler. - Borrow(unsafeAddress: _address, owner: self) - } -} -#endif - -extension Shared where Storage: ~Copyable { - @inlinable - public func isIdentical(to other: Self) -> Bool { - if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { - return self._box === other._box - } else { - // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions - // that are only supported in the Swift 6+ runtime. - let a = Builtin.bridgeToRawPointer(self._box) - let b = Builtin.bridgeToRawPointer(other._box) - return Bool(Builtin.cmp_eq_RawPointer(a, b)) - } - } -} - diff --git a/Sources/Future/ContiguousStorage.swift b/Sources/Future/ContiguousStorage.swift deleted file mode 100644 index 27a4f00f9..000000000 --- a/Sources/Future/ContiguousStorage.swift +++ /dev/null @@ -1,104 +0,0 @@ -//===--- ContiguousStorage.swift ------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -public protocol ContiguousStorage: ~Copyable, ~Escapable { - associatedtype Element/*: ~Copyable & ~Escapable*/ - - var storage: Span { borrowing get } -} - -extension Span: ContiguousStorage /*where Element: ~Copyable & ~Escapable*/ { - public var storage: Self { self } -} - -extension Array: ContiguousStorage { - public var storage: Span { - _read { - if let a = _baseAddressIfContiguous { - yield Span(_unsafeStart: a, count: count) - } - else { - let a = ContiguousArray(copy self) - #if true - let s = Span( - _unsafeStart: a._baseAddressIfContiguous!, count: a.count - ) - #else - let s = a.storage - #endif - yield s - } - } - } -} - -extension ContiguousArray: ContiguousStorage { - public var storage: Span { - borrowing get { - Span( - _unsafeStart: _baseAddressIfContiguous!, count: count - ) - } - } -} - -extension CollectionOfOne: ContiguousStorage { - public var storage: Span { - _read { -/* ideally: (with strawman syntax) - @addressable let value = self._element - yield Span( - unsafePointer: Builtin.addressable(value), count: 1 - ) -*/ - - let a = ContiguousArray(self) - yield Span( - _unsafeStart: a._baseAddressIfContiguous!, count: 1 - ) - } - } -} - -extension String.UTF8View: ContiguousStorage { - public var storage: Span { - _read { - if count < 16 { // Wrong way to know whether the String is smol -// if _guts.isSmall { -// let /*@addressable*/ rawStorage = _guts.asSmall._storage -// let span = RawSpan( -// unsafeRawPointer: UnsafeRawPointer(Builtin.adressable(rawStorage)), -// count: MemoryLayout<_SmallString.RawBitPattern>.size, -// owner: self -// ) -// yield span.view(as: UTF8.CodeUnit.self) - - let a = ContiguousArray(self) -// yield a.storage - yield Span( - _unsafeStart: a._baseAddressIfContiguous!, count: 1 - ) - } - else if let buffer = withContiguousStorageIfAvailable({ $0 }) { - // this is totally wrong, but there is a way with stdlib-internal API - yield Span(_unsafeElements: buffer) - } - else { // copy non-fast code units if we don't have eager bridging - let a = ContiguousArray(self) -// yield a.storage - yield Span( - _unsafeStart: a._baseAddressIfContiguous!, count: 1 - ) - } - } - } -} diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift deleted file mode 100644 index deb06ce5f..000000000 --- a/Sources/Future/Inout.swift +++ /dev/null @@ -1,83 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import Builtin - -// FIXME: A better name for the generic argument. - -/// A safe mutable reference allowing in-place mutation to an exclusive value. -/// -/// In order to get an instance of an `Inout`, one must have exclusive access -/// to the instance of `T`. This is achieved through the 'inout' operator, '&'. -@frozen -public struct Inout: ~Copyable, ~Escapable { - @usableFromInline - internal let _pointer: UnsafeMutablePointer - - /// Initializes an instance of 'Inout' extending the exclusive access of the - /// passed instance. - /// - /// - Parameter instance: The desired instance to get a mutable reference to. - @_alwaysEmitIntoClient - @_transparent - public init(_ instance: inout T) { - _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) - } - - /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' - /// as the mutable reference based on the lifetime of the given 'owner' - /// argument. - /// - /// - Parameter unsafeAddress: The address to use to mutably reference an - /// instance of type 'T'. - /// - Parameter owner: The owning instance that this 'Inout' instance's - /// lifetime is based on. - @_alwaysEmitIntoClient - @_transparent - public init( - unsafeAddress: UnsafeMutablePointer, - owner: inout Owner - ) { - _pointer = unsafeAddress - } - - /// Unsafely initializes an instance of 'Inout' using the given - /// 'unsafeImmortalAddress' as the mutable reference acting as though its - /// lifetime is immortal. - /// - /// - Parameter unsafeImmortalAddress: The address to use to mutably reference - /// an immortal instance of type 'T'. - @_alwaysEmitIntoClient - @_transparent - @lifetime(immortal) - public init( - unsafeImmortalAddress: UnsafeMutablePointer - ) { - _pointer = unsafeImmortalAddress - } -} - -extension Inout where T: ~Copyable { - /// Dereferences the mutable reference allowing for in-place reads and writes - /// to the underlying instance. - @_alwaysEmitIntoClient - public subscript() -> T { - @_transparent - unsafeAddress { - UnsafePointer(_pointer) - } - - @_transparent - nonmutating unsafeMutableAddress { - _pointer - } - } -} diff --git a/Sources/Future/LifetimeOverride.swift b/Sources/Future/LifetimeOverride.swift index 4b32cb6e4..ed416cfa9 100644 --- a/Sources/Future/LifetimeOverride.swift +++ b/Sources/Future/LifetimeOverride.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 @@ -10,17 +10,58 @@ // //===----------------------------------------------------------------------===// -import Builtin +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(borrow source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` that inherits +/// all lifetime dependencies from the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, copying source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's exclusive borrow scope of the `source` argument. +@unsafe @_unsafeNonescapableResult -@inlinable @inline(__always) +@_alwaysEmitIntoClient +@_transparent @lifetime(borrow source) -public func unsafelyOverrideLifetime< +public func _overrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable >( - of dependent: consuming T, - to source: borrowing U + _ dependent: consuming T, + mutating source: inout U ) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. dependent } diff --git a/Sources/Future/MutableRawSpan.swift b/Sources/Future/MutableRawSpan.swift new file mode 100644 index 000000000..7b38f5355 --- /dev/null +++ b/Sources/Future/MutableRawSpan.swift @@ -0,0 +1,392 @@ +//===--- MutableRawSpan.swift ---------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +// A MutableRawSpan represents a span of memory which +// contains initialized `Element` instances. +@frozen +@available(macOS 9999, *) +public struct MutableRawSpan: ~Copyable & ~Escapable { + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? + + @usableFromInline + internal let _count: Int + + @_alwaysEmitIntoClient + internal func _start() -> UnsafeMutableRawPointer { + _pointer.unsafelyUnwrapped + } + + @usableFromInline @inline(__always) + @lifetime(borrow pointer) + init( + _unchecked pointer: UnsafeMutableRawPointer?, + count: Int + ) { + _pointer = pointer + _count = count + } +} + +@available(macOS 9999, *) +extension MutableRawSpan: @unchecked Sendable {} + +@available(macOS 9999, *) +extension MutableRawSpan { + + @lifetime(borrow bytes) + public init( + _unsafeBytes bytes: UnsafeMutableRawBufferPointer + ) { + let baseAddress = bytes.baseAddress + let span = MutableRawSpan(_unchecked: baseAddress, count: bytes.count) + self = _overrideLifetime(span, borrowing: bytes) + } + + @lifetime(borrow bytes) + public init( + _unsafeBytes bytes: borrowing Slice + ) { + let rebased = UnsafeMutableRawBufferPointer(rebasing: bytes) + let span = MutableRawSpan(_unsafeBytes: rebased) + self = _overrideLifetime(span, borrowing: bytes) + } + + @lifetime(borrow pointer) + public init( + _unsafeStart pointer: UnsafeMutableRawPointer, + byteCount: Int + ) { + precondition(byteCount >= 0, "Count must not be negative") + self.init(_unchecked: pointer, count: byteCount) + } + + @lifetime(borrow elements) + public init( + _unsafeElements elements: UnsafeMutableBufferPointer + ) { + let bytes = UnsafeMutableRawBufferPointer(elements) + let span = MutableRawSpan(_unsafeBytes: bytes) + self = _overrideLifetime(span, borrowing: elements) + } + + @lifetime(borrow elements) + public init( + _unsafeElements elements: borrowing Slice> + ) { + let rebased = UnsafeMutableBufferPointer(rebasing: elements) + let span = MutableRawSpan(_unsafeElements: rebased) + self = _overrideLifetime(span, borrowing: elements) + } + + @lifetime(elements) + public init( + _elements elements: consuming MutableSpan + ) { + let bytes = UnsafeMutableRawBufferPointer( + start: elements._pointer, + count: elements.count &* MemoryLayout.stride + ) + let span = MutableRawSpan(_unsafeBytes: bytes) + self = _overrideLifetime(span, copying: elements) + } +} + +@available(macOS 9999, *) +extension MutableRawSpan { + @_alwaysEmitIntoClient + public var byteCount: Int { _count } + + @_alwaysEmitIntoClient + public var isEmpty: Bool { byteCount == 0 } + + @_alwaysEmitIntoClient + public var byteOffsets: Range { + .init(uncheckedBounds: (0, byteCount)) + } +} + +@available(macOS 9999, *) +extension MutableRawSpan { + + public func withUnsafeBytes( + _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + guard let pointer = _pointer, _count > 0 else { + return try body(.init(start: nil, count: 0)) + } + return try body(.init(start: pointer, count: _count)) + } + + public mutating func withUnsafeMutableBytes( + _ body: (UnsafeMutableRawBufferPointer) throws(E) -> Result + ) throws(E) -> Result { + guard let pointer = _pointer, _count > 0 else { + return try body(.init(start: nil, count: 0)) + } + return try body(.init(start: pointer, count: _count)) + } +} + +@available(macOS 9999, *) +extension MutableRawSpan { + + public var bytes: RawSpan { + @lifetime(borrow self) + borrowing get { + let start = _start() + let span = RawSpan(_unsafeStart: start, byteCount: byteCount) + return _overrideLifetime(span, borrowing: self) + } + } + + @unsafe + @lifetime(borrow self) + public borrowing func _unsafeView( + as type: T.Type + ) -> Span { + let bytes = UnsafeRawBufferPointer(start: _pointer, count: _count) + let span = Span(_unsafeBytes: bytes) + return _overrideLifetime(span, borrowing: self) + } + + @unsafe + @lifetime(borrow self) + public mutating func _unsafeMutableView( + as type: T.Type + ) -> MutableSpan { + let bytes = UnsafeMutableRawBufferPointer(start: _pointer, count: _count) + let span = MutableSpan(_unsafeBytes: bytes) + return _overrideLifetime(span, mutating: &self) + } +} + +@available(macOS 9999, *) +extension MutableRawSpan { + + + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// The memory at this pointer plus `offset` must be properly aligned for + /// accessing `T` and initialized to `T` or another type that is layout + /// compatible with `T`. + /// + /// This is an unsafe operation. Failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance is memory-managed and unassociated + /// with the value in the memory referenced by this pointer. + @unsafe + @_alwaysEmitIntoClient + public func unsafeLoad( + fromByteOffset offset: Int = 0, as: T.Type + ) -> T { + precondition( + UInt(bitPattern: offset) <= UInt(bitPattern: _count) && + MemoryLayout.size <= (_count &- offset), + "Byte offset range out of bounds" + ) + return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) + } + + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// The memory at this pointer plus `offset` must be properly aligned for + /// accessing `T` and initialized to `T` or another type that is layout + /// compatible with `T`. + /// + /// This is an unsafe operation. This function does not validate the bounds + /// of the memory access, and failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance is memory-managed and unassociated + /// with the value in the memory referenced by this pointer. + @unsafe + @_alwaysEmitIntoClient + public func unsafeLoad( + fromUncheckedByteOffset offset: Int, as: T.Type + ) -> T { + _start().load(fromByteOffset: offset, as: T.self) + } + + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// The memory at this pointer plus `offset` must be initialized to `T` + /// or another type that is layout compatible with `T`. + /// + /// This is an unsafe operation. Failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance isn't associated + /// with the value in the range of memory referenced by this pointer. + @unsafe + @_alwaysEmitIntoClient + public func unsafeLoadUnaligned( + fromByteOffset offset: Int = 0, as: T.Type + ) -> T { + precondition( + UInt(bitPattern: offset) <= UInt(bitPattern: _count) && + MemoryLayout.size <= (_count &- offset), + "Byte offset range out of bounds" + ) + return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) + } + + /// Returns a new instance of the given type, constructed from the raw memory + /// at the specified offset. + /// + /// The memory at this pointer plus `offset` must be initialized to `T` + /// or another type that is layout compatible with `T`. + /// + /// This is an unsafe operation. This function does not validate the bounds + /// of the memory access, and failure to meet the preconditions + /// above may produce an invalid value of `T`. + /// + /// - Parameters: + /// - offset: The offset from this pointer, in bytes. `offset` must be + /// nonnegative. The default is zero. + /// - type: The type of the instance to create. + /// - Returns: A new instance of type `T`, read from the raw bytes at + /// `offset`. The returned instance isn't associated + /// with the value in the range of memory referenced by this pointer. + @unsafe + @_alwaysEmitIntoClient + public func unsafeLoadUnaligned( + fromUncheckedByteOffset offset: Int, as: T.Type + ) -> T { + _start().loadUnaligned(fromByteOffset: offset, as: T.self) + } + + @_alwaysEmitIntoClient + public func storeBytes( + of value: T, toByteOffset offset: Int = 0, as type: T.Type + ) { + precondition( + UInt(bitPattern: offset) <= UInt(bitPattern: _count) && + MemoryLayout.size <= (_count &- offset), + "Byte offset range out of bounds" + ) + storeBytes(of: value, toUncheckedByteOffset: offset, as: type) + } + + @unsafe + @_alwaysEmitIntoClient + public func storeBytes( + of value: T, toUncheckedByteOffset offset: Int, as type: T.Type + ) { + _start().storeBytes(of: value, toByteOffset: offset, as: type) + } +} + +//MARK: copyMemory +@available(macOS 9999, *) +extension MutableRawSpan { + + public mutating func update( + startingAt byteOffset: Int = 0, + from source: S + ) -> (unwritten: S.Iterator, byteOffset: Int) where S.Element: BitwiseCopyable { + var iterator = source.makeIterator() + let offset = update(startingAt: byteOffset, from: &iterator) + return (iterator, offset) + } + + public mutating func update( + startingAt byteOffset: Int = 0, + from elements: inout some IteratorProtocol + ) -> Int { + var offset = byteOffset + while offset + MemoryLayout.stride <= _count { + guard let element = elements.next() else { break } + storeBytes(of: element, toUncheckedByteOffset: offset, as: Element.self) + offset &+= MemoryLayout.stride + } + return offset + } + + public mutating func update( + startingAt byteOffset: Int = 0, + fromContentsOf source: C + ) -> Int where C.Element: BitwiseCopyable { + let newOffset = source.withContiguousStorageIfAvailable { + self.update( + startingAt: byteOffset, fromContentsOf: Span(_unsafeElements: $0) + ) + } + if let newOffset { return newOffset } + + var elements = source.makeIterator() + let lastOffset = update(startingAt: byteOffset, from: &elements) + precondition( + elements.next() == nil, + "destination span cannot contain every element from source." + ) + return lastOffset + } + + public mutating func update( + startingAt byteOffset: Int = 0, + fromContentsOf source: Span + ) -> Int { +// update(startingAt: byteOffset, from: source.bytes) + source.withUnsafeBytes { + update(startingAt: byteOffset, fromContentsOf: $0) + } + } + + public mutating func update( + startingAt byteOffset: Int = 0, + fromContentsOf source: borrowing MutableSpan + ) -> Int { +// update(startingAt: byteOffset, from: source.storage.bytes) + source.withUnsafeBytes { + update(startingAt: byteOffset, fromContentsOf: $0) + } + } + + public mutating func update( + startingAt byteOffset: Int = 0, + from source: RawSpan + ) -> Int { + if source.byteCount == 0 { return byteOffset } + source.withUnsafeBytes { + _start().advanced(by: byteOffset) + .copyMemory(from: $0.baseAddress!, byteCount: $0.count) + } + return byteOffset &+ source.byteCount + } + + public mutating func update( + startingAt byteOffset: Int = 0, + from source: borrowing MutableRawSpan + ) -> Int { + update(startingAt: byteOffset, from: source.bytes) + } +} diff --git a/Sources/Future/MutableSpan.swift b/Sources/Future/MutableSpan.swift index 75da72b7a..dfa709cbc 100644 --- a/Sources/Future/MutableSpan.swift +++ b/Sources/Future/MutableSpan.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 @@ -14,17 +14,19 @@ import Builtin // A MutableSpan represents a span of memory which // contains initialized `Element` instances. -@_disallowFeatureSuppression(NonescapableTypes) @frozen -public struct MutableSpan: ~Copyable & ~Escapable { +@available(macOS 9999, *) +public struct MutableSpan +: ~Copyable, ~Escapable { @usableFromInline let _pointer: UnsafeMutableRawPointer? @usableFromInline let _count: Int - @usableFromInline @inline(__always) - var _start: UnsafeMutableRawPointer { _pointer.unsafelyUnwrapped } + @_alwaysEmitIntoClient + internal func _start() -> UnsafeMutableRawPointer { + _pointer.unsafelyUnwrapped + } - @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) @lifetime(borrow start) init( @@ -36,14 +38,12 @@ public struct MutableSpan: ~Copyable & ~Escapable { } } -@_disallowFeatureSuppression(NonescapableTypes) -@available(*, unavailable) -extension MutableSpan: Sendable {} +@available(macOS 9999, *) +extension MutableSpan: @unchecked Sendable where Element: Sendable {} -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) @lifetime(borrow elements) internal init( @@ -53,7 +53,6 @@ extension MutableSpan where Element: ~Copyable { _count = elements.count } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -64,10 +63,10 @@ extension MutableSpan where Element: ~Copyable { (MemoryLayout.alignment&-1)) == 0), "baseAddress must be properly aligned to access Element" ) - self.init(_unchecked: buffer) + let ms = MutableSpan(_unchecked: buffer) + self = _overrideLifetime(ms, borrowing: buffer) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow start) public init( @@ -75,27 +74,29 @@ extension MutableSpan where Element: ~Copyable { count: Int ) { precondition(count >= 0, "Count must not be negative") - self.init(_unsafeElements: .init(start: start, count: count)) + let buffer = UnsafeMutableBufferPointer(start: start, count: count) + let ms = MutableSpan(_unsafeElements: buffer) + self = _overrideLifetime(ms, borrowing: start) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow elements) public init( _unsafeElements elements: borrowing Slice> ) { - self.init(_unsafeElements: UnsafeMutableBufferPointer(rebasing: elements)) + let rb = UnsafeMutableBufferPointer(rebasing: elements) + let ms = MutableSpan(_unsafeElements: rb) + self = _overrideLifetime(ms, borrowing: elements) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: BitwiseCopyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -109,10 +110,14 @@ extension MutableSpan where Element: BitwiseCopyable { let (byteCount, stride) = (buffer.count, MemoryLayout.stride) let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0, "Span must contain a whole number of elements") - self.init(_unchecked: buffer.baseAddress, count: count) + let elements = UnsafeMutableBufferPointer( + start: buffer.baseAddress?.assumingMemoryBound(to: Element.self), + count: count + ) + let ms = MutableSpan(_unsafeElements: elements) + self = _overrideLifetime(ms, borrowing: buffer) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( @@ -120,101 +125,98 @@ extension MutableSpan where Element: BitwiseCopyable { byteCount: Int ) { precondition(byteCount >= 0, "Count must not be negative") - self.init(_unsafeBytes: .init(start: pointer, count: byteCount)) + let bytes = UnsafeMutableRawBufferPointer(start: pointer, count: byteCount) + let ms = MutableSpan(_unsafeBytes: bytes) + self = _overrideLifetime(ms, borrowing: pointer) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( _unsafeBytes buffer: borrowing Slice ) { - self.init(_unsafeBytes: UnsafeMutableRawBufferPointer(rebasing: buffer)) + let bytes = UnsafeMutableRawBufferPointer(rebasing: buffer) + let ms = MutableSpan(_unsafeBytes: bytes) + self = _overrideLifetime(ms, borrowing: buffer) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension Span where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init(_unsafeMutableSpan mutableSpan: borrowing MutableSpan) { - self.init(_unchecked: mutableSpan._start, count: mutableSpan.count) + let pointer = mutableSpan._pointer?.assumingMemoryBound(to: Element.self) + let buffer = UnsafeBufferPointer(start: pointer, count: mutableSpan.count) + let span = Span(_unsafeElements: buffer) + self = _overrideLifetime(span, borrowing: mutableSpan) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public var storage: Span { Span(_unsafeMutableSpan: self) } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func withSpan( - _ body: (Span) throws(E) -> Result - ) throws(E) -> Result { - try body(Span(_unsafeMutableSpan: self)) + public var span: Span { + @lifetime(borrow self) + borrowing get { + Span(_unsafeMutableSpan: self) + } } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension RawSpan { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public init( _unsafeMutableSpan mutableSpan: borrowing MutableSpan ) { - self.init( - _unchecked: mutableSpan._start, - byteCount: mutableSpan.count &* MemoryLayout.stride - ) + let pointer = mutableSpan._pointer + let byteCount = mutableSpan.count &* MemoryLayout.stride + let buffer = UnsafeRawBufferPointer(start: pointer, count: byteCount) + let rawSpan = RawSpan(_unsafeBytes: buffer) + self = _overrideLifetime(rawSpan, borrowing: mutableSpan) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: Equatable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: borrowing Self) -> Bool { _elementsEqual(Span(_unsafeMutableSpan: other)) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: Span) -> Bool { Span(_unsafeMutableSpan: self)._elementsEqual(other) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { Span(_unsafeMutableSpan: self)._elementsEqual(other) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Sequence) -> Bool { Span(_unsafeMutableSpan: self)._elementsEqual(other) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient - public var description: String { + public var _description: String { let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) return "(0x\(addr), \(_count))" } } //MARK: Collection, RandomAccessCollection -@_disallowFeatureSuppression(NonescapableTypes) -extension MutableSpan where Element: ~Copyable { +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable & ~Escapable { @_alwaysEmitIntoClient public var count: Int { _count } @@ -222,82 +224,47 @@ extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient public var isEmpty: Bool { _count == 0 } - @_alwaysEmitIntoClient - public var _indices: Range { - Range(uncheckedBounds: (0, _count)) - } -} + public typealias Index = Int -//MARK: Bounds Checking -@_disallowFeatureSuppression(NonescapableTypes) -extension MutableSpan where Element: ~Copyable { - - /// Return true if `index` is a valid offset into this `Span` - /// - /// - Parameters: - /// - index: an index to validate - /// - Returns: true if `index` is valid - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func boundsContain(_ index: Int) -> Bool { - 0 <= index && index < _count - } - - /// Return true if `indices` is a valid range of indices into this `Span` - /// - /// - Parameters: - /// - indices: a range of indices to validate - /// - Returns: true if `indices` is a valid range of indices - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public func boundsContain(_ indices: Range) -> Bool { - boundsContain(indices.lowerBound) && indices.upperBound <= _count - } - - /// Return true if `indices` is a valid range of indices into this `Span` - /// - /// - Parameters: - /// - indices: a range of indices to validate - /// - Returns: true if `indices` is a valid range of indices - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func boundsContain(_ indices: ClosedRange) -> Bool { - boundsContain(indices.lowerBound) && indices.upperBound < _count + public var indices: Range { + Range(uncheckedBounds: (0, _count)) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: BitwiseCopyable { /// Construct a RawSpan over the memory represented by this span /// /// - Returns: a RawSpan over the memory represented by this span - @_disallowFeatureSuppression(NonescapableTypes) - @unsafe //FIXME: remove when the lifetime inference is fixed @_alwaysEmitIntoClient - public var _unsafeRawSpan: RawSpan { RawSpan(_unsafeMutableSpan: self) } + public var bytes: RawSpan { + @lifetime(borrow self) + borrowing get { + RawSpan(_unsafeMutableSpan: self) + } + } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { - /// Accesses the element at the specified position in the `Span`. /// /// - Parameter position: The offset of the element to access. `position` /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public subscript(_ position: Int) -> Element { - _read { - precondition(boundsContain(position), "index out of bounds") - yield self[unchecked: position] + public subscript(_ position: Index) -> Element { + unsafeAddress { + precondition(indices.contains(position), "index out of bounds") + return UnsafePointer(_unsafeAddressOfElement(unchecked: position)) } - _modify { - precondition(boundsContain(position), "index out of bounds") - yield &self[unchecked: position] + unsafeMutableAddress { + precondition(indices.contains(position), "index out of bounds") + return _unsafeAddressOfElement(unchecked: position) } } @@ -309,27 +276,48 @@ extension MutableSpan where Element: ~Copyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public subscript(unchecked position: Int) -> Element { - _read { - let offset = position&*MemoryLayout.stride - let p = UnsafeRawPointer(_start).advanced(by: offset)._rawValue - let binding = Builtin.bindMemory(p, count._builtinWordValue, Element.self) - defer { Builtin.rebindMemory(p, binding) } - yield UnsafePointer(p).pointee + public subscript(unchecked position: Index) -> Element { + unsafeAddress { + UnsafePointer(_unsafeAddressOfElement(unchecked: position)) } - _modify { - let offset = position&*MemoryLayout.stride - let p = UnsafeMutableRawPointer(_start).advanced(by: offset)._rawValue - let binding = Builtin.bindMemory(p, 1._builtinWordValue, Element.self) - defer { Builtin.rebindMemory(p, binding) } - yield &(UnsafeMutablePointer(p).pointee) + unsafeMutableAddress { + _unsafeAddressOfElement(unchecked: position) } } + + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + unchecked position: Index + ) -> UnsafeMutablePointer { + let elementOffset = position &* MemoryLayout.stride + let address = _start().advanced(by: elementOffset) + return address.assumingMemoryBound(to: Element.self) + } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + public mutating func swapAt(_ i: Index, _ j: Index) { + precondition(indices.contains(Index(i))) + precondition(indices.contains(Index(j))) + swapAt(unchecked: i, unchecked: j) + } + + @_alwaysEmitIntoClient + public mutating func swapAt(unchecked i: Index, unchecked j: Index) { + let pi = _unsafeAddressOfElement(unchecked: i) + let pj = _unsafeAddressOfElement(unchecked: j) + let temporary = pi.move() + pi.initialize(to: pj.move()) + pj.initialize(to: consume temporary) + } +} + +@available(macOS 9999, *) extension MutableSpan where Element: BitwiseCopyable { /// Accesses the element at the specified position in the `Span`. @@ -338,15 +326,14 @@ extension MutableSpan where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public subscript(_ position: Int) -> Element { + public subscript(_ position: Index) -> Element { get { - precondition(boundsContain(position)) + precondition(indices.contains(position), "index out of bounds") return self[unchecked: position] } set { - precondition(boundsContain(position)) + precondition(indices.contains(position), "index out of bounds") self[unchecked: position] = newValue } } @@ -359,25 +346,23 @@ extension MutableSpan where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public subscript(unchecked position: Int) -> Element { + public subscript(unchecked position: Index) -> Element { get { let offset = position&*MemoryLayout.stride - return _start.loadUnaligned(fromByteOffset: offset, as: Element.self) + return _start().loadUnaligned(fromByteOffset: offset, as: Element.self) } set { let offset = position&*MemoryLayout.stride - _start.storeBytes(of: newValue, toByteOffset: offset, as: Element.self) + _start().storeBytes(of: newValue, toByteOffset: offset, as: Element.self) } } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { //FIXME: mark closure parameter as non-escaping - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result @@ -386,38 +371,26 @@ extension MutableSpan where Element: ~Copyable { } //FIXME: mark closure parameter as non-escaping - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func withUnsafeMutableBufferPointer( - _ body: (inout UnsafeMutableBufferPointer) throws(E) -> Result + _ body: (UnsafeMutableBufferPointer) throws(E) -> Result ) throws(E) -> Result { - func executeBody( - _ start: UnsafeMutablePointer?, _ count: Int - ) throws(E) -> Result { - var buf = UnsafeMutableBufferPointer(start: start, count: count) - defer { - precondition( - (buf.baseAddress, buf.count) == (start, count), - "MutableSpan.withUnsafeMutableBufferPointer: replacing the buffer is not allowed" - ) - } - return try body(&buf) - } guard let pointer = _pointer, count > 0 else { - return try executeBody(nil, 0) - } - return try pointer.withMemoryRebound(to: Element.self, capacity: count) { - pointer throws(E) -> Result in - return try executeBody(pointer, count) + return try body(.init(start: nil, count: 0)) } + // bind memory by hand to sidestep alignment concerns + let binding = Builtin.bindMemory( + pointer._rawValue, count._builtinWordValue, Element.self + ) + defer { Builtin.rebindMemory(pointer._rawValue, binding) } + return try body(.init(start: .init(pointer._rawValue), count: count)) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: BitwiseCopyable { //FIXME: mark closure parameter as non-escaping - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result @@ -426,13 +399,12 @@ extension MutableSpan where Element: BitwiseCopyable { } //FIXME: mark closure parameter as non-escaping - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func withUnsafeMutableBytes( _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { let bytes = UnsafeMutableRawBufferPointer( - start: (_count == 0) ? nil : _start, + start: (_count == 0) ? nil : _start(), count: _count &* MemoryLayout.stride ) return try body(bytes) @@ -440,12 +412,12 @@ extension MutableSpan where Element: BitwiseCopyable { } //MARK: bulk-update functions -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan { @_alwaysEmitIntoClient - public mutating func update(repeating repeatedValue: Element) { - _start.withMemoryRebound(to: Element.self, capacity: count) { + public mutating func update(repeating repeatedValue: consuming Element) { + _start().withMemoryRebound(to: Element.self, capacity: count) { $0.update(repeating: repeatedValue, count: count) } } @@ -453,7 +425,7 @@ extension MutableSpan { @_alwaysEmitIntoClient public mutating func update( from source: S - ) -> (unwritten: S.Iterator, index: Int) where S.Element == Element { + ) -> (unwritten: S.Iterator, index: Index) where S.Element == Element { var iterator = source.makeIterator() let index = update(from: &iterator) return (iterator, index) @@ -462,7 +434,7 @@ extension MutableSpan { @_alwaysEmitIntoClient public mutating func update( from elements: inout some IteratorProtocol - ) -> Int { + ) -> Index { var index = 0 while index < _count { guard let element = elements.next() else { break } @@ -475,68 +447,55 @@ extension MutableSpan { @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: some Collection - ) -> Int { + ) -> Index { let updated = source.withContiguousStorageIfAvailable { self.update(fromContentsOf: Span(_unsafeElements: $0)) } if let updated { - return 0.advanced(by: updated) + return updated } - if self.isEmpty { - precondition( - source.isEmpty, - "destination buffer view cannot contain every element from source." - ) - return 0 - } + //TODO: use _copyContents here var iterator = source.makeIterator() - var index = 0 - while let value = iterator.next() { - precondition( - index < _count, - "destination buffer view cannot contain every element from source." - ) - self[unchecked: index] = value - index &+= 1 - } + let index = update(from: &iterator) + precondition( + iterator.next() == nil, + "destination buffer view cannot contain every element from source." + ) return index } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public mutating func update(fromContentsOf source: Span) -> Int { + public mutating func update(fromContentsOf source: Span) -> Index { guard !source.isEmpty else { return 0 } precondition( source.count <= self.count, "destination span cannot contain every element from source." ) - _start.withMemoryRebound(to: Element.self, capacity: source.count) { dest in - source._start().withMemoryRebound(to: Element.self, capacity: source.count) { - dest.update(from: $0, count: source.count) + _start().withMemoryRebound(to: Element.self, capacity: source.count) { dest in + source.withUnsafeBufferPointer { + dest.update(from: $0.baseAddress!, count: $0.count) } } return source.count } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: borrowing MutableSpan - ) -> Int { - source.withSpan { self.update(fromContentsOf: $0) } + ) -> Index { + update(fromContentsOf: source.span) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func moveUpdate( fromContentsOf source: consuming OutputSpan - ) -> Int { + ) -> Index { guard !source.isEmpty else { return 0 } precondition( source.count <= self.count, @@ -544,7 +503,7 @@ extension MutableSpan where Element: ~Copyable { ) let buffer = source.relinquishBorrowedMemory() // we must now deinitialize the returned UMBP - _start.moveInitializeMemory( + _start().moveInitializeMemory( as: Element.self, from: buffer.baseAddress!, count: buffer.count ) return buffer.count @@ -552,38 +511,23 @@ extension MutableSpan where Element: ~Copyable { public mutating func moveUpdate( fromContentsOf source: UnsafeMutableBufferPointer - ) -> Int { -#if false - guard let sourceAddress = source.baseAddress, source.count > 0 else { - return 0 - } - precondition( - source.count <= self.count, - "destination span cannot contain every element from source." - ) - _start.withMemoryRebound(to: Element.self, capacity: source.count) { - $0.moveUpdate(from: sourceAddress, count: source.count) - } - return source.count -#else + ) -> Index { let source = OutputSpan(_initializing: source, initialized: source.count) return self.moveUpdate(fromContentsOf: source) -#endif } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan { public mutating func moveUpdate( fromContentsOf source: Slice> - ) -> Int { + ) -> Index { self.moveUpdate(fromContentsOf: .init(rebasing: source)) } } - -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension MutableSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient @@ -592,7 +536,7 @@ extension MutableSpan where Element: BitwiseCopyable { ) where Element: BitwiseCopyable { guard count > 0 else { return } // rebind _start manually in order to avoid assumptions about alignment. - let rp = _start._rawValue + let rp = _start()._rawValue let binding = Builtin.bindMemory(rp, count._builtinWordValue, Element.self) UnsafeMutablePointer(rp).update(repeating: repeatedValue, count: count) Builtin.rebindMemory(rp, binding) @@ -601,7 +545,7 @@ extension MutableSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient public mutating func update( from source: S - ) -> (unwritten: S.Iterator, index: Int) + ) -> (unwritten: S.Iterator, index: Index) where S.Element == Element, Element: BitwiseCopyable { var iterator = source.makeIterator() let index = update(from: &iterator) @@ -611,7 +555,7 @@ extension MutableSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient public mutating func update( from elements: inout some IteratorProtocol - ) -> Int { + ) -> Index { var index = 0 while index < _count { guard let element = elements.next() else { break } @@ -624,120 +568,46 @@ extension MutableSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: some Collection - ) -> Int where Element: BitwiseCopyable { + ) -> Index where Element: BitwiseCopyable { let updated = source.withContiguousStorageIfAvailable { self.update(fromContentsOf: Span(_unsafeElements: $0)) } if let updated { - return 0.advanced(by: updated) + return updated } - if self.isEmpty { - precondition( - source.isEmpty, - "destination buffer view cannot contain every element from source." - ) - return 0 - } + //TODO: use _copyContents here var iterator = source.makeIterator() - var index = 0 - while let value = iterator.next() { - guard index < _count else { - fatalError( - "destination buffer view cannot contain every element from source." - ) - } - self[unchecked: index] = value - index &+= 1 - } + let index = update(from: &iterator) + precondition( + iterator.next() == nil, + "destination buffer view cannot contain every element from source." + ) return index } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: Span - ) -> Int where Element: BitwiseCopyable { + ) -> Index where Element: BitwiseCopyable { guard !source.isEmpty else { return 0 } precondition( source.count <= self.count, "destination span cannot contain every element from source." ) - _start.copyMemory( - from: source._start(), byteCount: source.count&*MemoryLayout.stride - ) + source.withUnsafeBufferPointer { + _start().copyMemory( + from: $0.baseAddress!, byteCount: $0.count&*MemoryLayout.stride + ) + } return source.count } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func update( fromContentsOf source: borrowing MutableSpan - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: source.storage) - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - @unsafe - public mutating func update( - fromContentsOf source: RawSpan - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: source._unsafeView(as: Element.self)) - } - - // We have to define the overloads for raw buffers and their slices, - // otherwise they would try to use their `Collection` conformance - // and fail due to the mismatch in `Element` type. - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: UnsafeRawBufferPointer - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: UnsafeMutableRawBufferPointer - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: Slice - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: Slice - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: RawSpan(_unsafeBytes: source)) - } -} - -//MARK: copyMemory -//FIXME: move these to a MutableRawSpan -@_disallowFeatureSuppression(NonescapableTypes) -extension MutableSpan where Element: BitwiseCopyable { - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func copyMemory( - from source: borrowing MutableSpan - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: source.storage) - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public mutating func copyMemory( - from source: Span - ) -> Int where Element: BitwiseCopyable { - self.update(fromContentsOf: source) + ) -> Index where Element: BitwiseCopyable { + update(fromContentsOf: source.span) } } diff --git a/Sources/Future/MutableSpanSlicing.swift b/Sources/Future/MutableSpanSlicing.swift new file mode 100644 index 000000000..dbd6c8094 --- /dev/null +++ b/Sources/Future/MutableSpanSlicing.swift @@ -0,0 +1,142 @@ +//MARK: Option 1 extracting() function +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + + @lifetime(borrow self) + public mutating func extracting(_ bounds: Range) -> Self { + precondition( + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "Index range out of bounds" + ) + return extracting(unchecked: bounds) + } + + @lifetime(borrow self) + public mutating func extracting(unchecked bounds: Range) -> Self { + let delta = bounds.lowerBound &* MemoryLayout.stride + let newStart = _pointer?.advanced(by: delta) + let newSpan = MutableSpan(_unchecked: newStart, count: bounds.count) + return _overrideLifetime(newSpan, mutating: &self) + } +} + +//MARK: Option 2 extracting subscript +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + + public subscript(extracting bounds: Range) -> Self { + @lifetime(borrow self) + mutating get { + precondition( + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "Index range out of bounds" + ) + return self[extractingUnchecked: bounds] + } + } + + public subscript(extractingUnchecked bounds: Range) -> Self { + @lifetime(borrow self) + mutating get { + let delta = bounds.lowerBound &* MemoryLayout.stride + let newStart = _pointer?.advanced(by: delta) + let newSpan = MutableSpan(_unchecked: newStart, count: bounds.count) + return _overrideLifetime(newSpan, mutating: &self) + } + } +} + +//MARK: Option 3 specific slicing wrapper type +@available(macOS 9999, *) +public struct SubMutableSpan +: ~Copyable, ~Escapable { + public typealias Index = MutableSpan.Index + + public fileprivate(set) var offset: MutableSpan.Index + public /*exclusive*/ var base: MutableSpan + + init(_ _offset: Index, _ _base: consuming MutableSpan) { + offset = _offset + base = _base + } + + public var count: Int { base.count - offset } +} + +@available(macOS 9999, *) +extension SubMutableSpan where Element: ~Copyable { + + public subscript (index: Index) -> Element { + unsafeAddress { + precondition(offset <= index && index < base.count) + return UnsafePointer(base._unsafeAddressOfElement(unchecked: index)) + } + unsafeMutableAddress { + precondition(offset <= index && index < base.count) + return base._unsafeAddressOfElement(unchecked: index) + } + } + + public var span: Span { + @lifetime(borrow self) + borrowing get { + let delta = offset &* MemoryLayout.stride + let newStart = base._pointer?.advanced(by: delta).assumingMemoryBound(to: Element.self) + let buffer = UnsafeBufferPointer(start: newStart, count: count) + let newSpan = Span(_unsafeElements: buffer) + return _overrideLifetime(newSpan, borrowing: self) + } + } +} + +@available(macOS 9999, *) +extension SubMutableSpan { + public mutating func update(repeating repeatedValue: consuming Element) { + base.update(repeating: repeatedValue) + } +} + +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + + public subscript(slicing bounds: Range) -> SubMutableSpan { + @lifetime(borrow self) + mutating get { + precondition( + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "Index range out of bounds" + ) + let prefixBuffer = UnsafeMutableBufferPointer( + start: _start().assumingMemoryBound(to: Element.self), + count: bounds.upperBound + ) + let prefix = MutableSpan(_unsafeElements: prefixBuffer) + var subSpan = SubMutableSpan(bounds.lowerBound, prefix) + subSpan.offset = bounds.lowerBound + return _overrideLifetime(subSpan, mutating: &self) + } + } +} + +//MARK: Option 4 range parameters to bulk mutation functions +@available(macOS 9999, *) +extension MutableSpan where Element: Copyable { + + public mutating func update( + in bounds: Range, + repeating repeatedValue: consuming Element + ) { + precondition( + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "Index range out of bounds" + ) + let start = _start().advanced(by: bounds.lowerBound &* MemoryLayout.stride) + start.withMemoryRebound(to: Element.self, capacity: bounds.count) { + $0.update(repeating: repeatedValue, count: bounds.count) + } + } +} diff --git a/Sources/Future/OutputSpan.swift b/Sources/Future/OutputSpan.swift index f8ed7f07f..413a4c49b 100644 --- a/Sources/Future/OutputSpan.swift +++ b/Sources/Future/OutputSpan.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 @@ -12,8 +12,8 @@ // OutputSpan represents a span of memory which contains // a variable number of `Element` instances, followed by uninitialized memory. -@_disallowFeatureSuppression(NonescapableTypes) @frozen +@available(macOS 9999, *) public struct OutputSpan: ~Copyable, ~Escapable { @usableFromInline let _pointer: UnsafeMutableRawPointer? @@ -43,7 +43,6 @@ public struct OutputSpan: ~Copyable, ~Escapable { } } - @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) @lifetime(borrow start) init( @@ -57,14 +56,13 @@ public struct OutputSpan: ~Copyable, ~Escapable { } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) @available(*, unavailable) extension OutputSpan: Sendable {} -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @usableFromInline @inline(__always) @lifetime(borrow buffer) init( @@ -76,7 +74,6 @@ extension OutputSpan where Element: ~Copyable { _initialized = initialized } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( @@ -91,7 +88,6 @@ extension OutputSpan where Element: ~Copyable { self.init(_unchecked: buffer, initialized: initialized) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( @@ -100,31 +96,30 @@ extension OutputSpan where Element: ~Copyable { initialized: Int = 0 ) { precondition(capacity >= 0, "Capacity must be 0 or greater") - self.init( - _initializing: .init(start: pointer, count: capacity), - initialized: initialized - ) + let buffer = UnsafeMutableBufferPointer(start: pointer, count: capacity) + let os = OutputSpan(_initializing: buffer, initialized: initialized) + self = _overrideLifetime(os, borrowing: pointer) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( _initializing buffer: borrowing Slice>, initialized: Int = 0 ) { - self.init(_initializing: .init(rebasing: buffer), initialized: initialized) + let rebased = UnsafeMutableBufferPointer(rebasing: buffer) + let os = OutputSpan(_initializing: rebased, initialized: 0) + self = _overrideLifetime(os, borrowing: buffer) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan where Element: BitwiseCopyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow bytes) public init( @@ -139,12 +134,13 @@ extension OutputSpan where Element: BitwiseCopyable { let (byteCount, stride) = (bytes.count, MemoryLayout.stride) let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0, "Span must contain a whole number of elements") - self.init( - _unchecked: bytes.baseAddress, capacity: count, initialized: initialized + let pointer = bytes.baseAddress + let os = OutputSpan( + _unchecked: pointer, capacity: count, initialized: initialized ) + self = _overrideLifetime(os, borrowing: bytes) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( @@ -153,31 +149,28 @@ extension OutputSpan where Element: BitwiseCopyable { initialized: Int = 0 ) { precondition(capacity >= 0, "Capacity must be 0 or greater") - self.init( - _initializing: .init(start: pointer, count: capacity), - initialized: initialized - ) + let buffer = UnsafeMutableRawBufferPointer(start: pointer, count: capacity) + let os = OutputSpan(_initializing: buffer, initialized: initialized) + self = _overrideLifetime(os, borrowing: pointer) } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient @lifetime(borrow buffer) public init( _initializing buffer: borrowing Slice, initialized: Int = 0 ) { - self.init( - _initializing: UnsafeMutableRawBufferPointer(rebasing: buffer), - initialized: initialized - ) + let rebased = UnsafeMutableRawBufferPointer(rebasing: buffer) + let os = OutputSpan(_initializing: rebased, initialized: initialized) + self = _overrideLifetime(os, borrowing: buffer) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient - public mutating func appendElement(_ value: consuming Element) { + public mutating func append(_ value: consuming Element) { precondition(_initialized < capacity, "Output buffer overflow") let p = _start.advanced(by: _initialized&*MemoryLayout.stride) p.initializeMemory(as: Element.self, to: value) @@ -202,7 +195,7 @@ extension OutputSpan where Element: ~Copyable { } //MARK: bulk-update functions -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan { @_alwaysEmitIntoClient @@ -288,7 +281,6 @@ extension OutputSpan { } //FIXME: rdar://136838539 & rdar://136849171 - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func append( fromContentsOf source: Span @@ -299,23 +291,23 @@ extension OutputSpan { "destination span cannot contain every element from source." ) let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) - source._start().withMemoryRebound(to: Element.self, capacity: source.count) { - _ = tail.initializeMemory(as: Element.self, from: $0, count: source.count) + _ = source.withUnsafeBufferPointer { + tail.initializeMemory( + as: Element.self, from: $0.baseAddress!, count: $0.count + ) } _initialized += source.count } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func append(fromContentsOf source: borrowing MutableSpan) { source.withUnsafeBufferPointer { append(fromContentsOf: $0) } } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func moveAppend( fromContentsOf source: consuming Self @@ -334,7 +326,6 @@ extension OutputSpan where Element: ~Copyable { _initialized &+= buffer.count } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func moveAppend( fromContentsOf source: UnsafeMutableBufferPointer @@ -357,9 +348,9 @@ extension OutputSpan where Element: ~Copyable { } } +@available(macOS 9999, *) extension OutputSpan { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient public mutating func moveAppend( fromContentsOf source: Slice> @@ -368,43 +359,38 @@ extension OutputSpan { } } +@available(macOS 9999, *) extension OutputSpan where Element: BitwiseCopyable { } +@available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public var initializedPrefix: Span { - get { Span(_unchecked: _pointer, count: _initialized) } - } - - @_disallowFeatureSuppression(NonescapableTypes) - @_alwaysEmitIntoClient - public func withSpan( - _ body: (Span) throws(E) -> R - ) throws(E) -> R { - try body(initializedPrefix) + public var span: Span { + @lifetime(borrow self) + borrowing get { + let pointer = _pointer?.assumingMemoryBound(to: Element.self) + let buffer = UnsafeBufferPointer(start: pointer, count: _initialized) + let span = Span(_unsafeElements: buffer) + return _overrideLifetime(span, borrowing: self) + } } - @_disallowFeatureSuppression(NonescapableTypes) @_alwaysEmitIntoClient - public mutating func withMutableSpan( - _ body: (inout MutableSpan) throws(E) -> Result - ) throws(E) -> Result { - var span = MutableSpan(_unchecked: _pointer, count: _initialized) - defer { - precondition( - span.count == _initialized && span._pointer == _start, - "Substituting the MutableSpan is unsound and unsafe." - ) + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + mutating get { + let pointer = _pointer?.assumingMemoryBound(to: Element.self) + let buffer = UnsafeMutableBufferPointer(start: pointer, count: _initialized) + let span = MutableSpan(_unsafeElements: buffer) + return _overrideLifetime(span, mutating: &self) } - return try body(&span) } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient @@ -416,7 +402,7 @@ extension OutputSpan where Element: ~Copyable { } } -@_disallowFeatureSuppression(NonescapableTypes) +@available(macOS 9999, *) extension OutputSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient diff --git a/Sources/Future/RawSpan.swift b/Sources/Future/RawSpan.swift deleted file mode 100644 index 343b9fba2..000000000 --- a/Sources/Future/RawSpan.swift +++ /dev/null @@ -1,693 +0,0 @@ -//===--- RawSpan.swift ----------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/// `RawSpan` represents a contiguous region of memory -/// which contains initialized bytes. -/// -/// A `RawSpan` instance is a non-owning, non-escaping view into memory. -/// When a `RawSpan` is created, it inherits the lifetime of the container -/// owning the contiguous memory, ensuring temporal safety and avoiding -/// use-after-free errors. Operations on `RawSpan` are bounds-checked, -/// ensuring spcial safety and avoiding buffer overflow errors. -@frozen -public struct RawSpan: ~Escapable, Copyable, BitwiseCopyable { - - /// The starting address of this `RawSpan`. - /// - /// `_pointer` can be `nil` if and only if `_count` equals 0. - /// Otherwise, `_pointer` must point to memory that will remain - /// valid and not mutated as long as this `Span` exists. - /// The memory at `_pointer` must consist of `_count` initialized bytes. - @usableFromInline - internal let _pointer: UnsafeRawPointer? - - @_alwaysEmitIntoClient - internal func _start() -> UnsafeRawPointer { - _pointer.unsafelyUnwrapped - } - - /// The number of bytes in this `RawSpan`. - /// - /// If `_count` equals 0, then `_pointer` may be either `nil` or valid. - /// Any `_count` greater than 0 indicates a valid non-nil `_pointer`. - /// Any `_count` less than 0 is invalid and is undefined behaviour. - @usableFromInline - internal let _count: Int - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// `pointer` must point to a region of `byteCount` initialized bytes, - /// or may be `nil` if `count` is 0. - /// - /// The region of `byteCount` bytes of memory starting at `pointer` - /// must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized byte. - /// - byteCount: the number of initialized bytes in the span. - @_alwaysEmitIntoClient - @inline(__always) - @lifetime(borrow pointer) - internal init( - _unchecked pointer: borrowing UnsafeRawPointer?, - byteCount: Int - ) { - _pointer = copy pointer - _count = byteCount - } -} - -extension RawSpan: @unchecked Sendable {} - -extension RawSpan { - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing UnsafeRawBufferPointer - ) { - self.init( - _unchecked: buffer.baseAddress, byteCount: buffer.count - ) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing Slice - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing UnsafeMutableRawBufferPointer - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) - } - - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing Slice - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The region of memory representing `byteCount` bytes starting at `pointer` - /// must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized byte. - /// - byteCount: the number of initialized bytes in the span. - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - public init( - _unsafeStart pointer: borrowing UnsafeRawPointer, - byteCount: Int - ) { - precondition(byteCount >= 0, "Count must not be negative") - self.init(_unchecked: copy pointer, byteCount: byteCount) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing UnsafeBufferPointer - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing Slice> - ) { - self.init( - _unsafeBytes: .init(UnsafeBufferPointer(rebasing: buffer)) - ) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing UnsafeMutableBufferPointer - ) { - self.init(_unsafeElements: UnsafeBufferPointer(buffer)) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeRawBufferPointer` to initialized memory. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing Slice> - ) { - self.init(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: buffer))) - } - - /// Unsafely create a `RawSpan` over initialized memory. - /// - /// The region of memory representing `byteCount` bytes starting at `pointer` - /// must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `RawSpan`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized byte. - /// - byteCount: the number of initialized bytes in the span. - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - public init( - _unsafeStart pointer: borrowing UnsafePointer, - count: Int - ) { - precondition(count >= 0, "Count must not be negative") - self.init( - _unchecked: copy pointer, byteCount: count * MemoryLayout.stride - ) - } - - /// Create a `RawSpan` over the memory represented by a `Span` - /// - /// - Parameters: - /// - span: An existing `Span`, which will define both this - /// `RawSpan`'s lifetime and the memory it represents. - @_alwaysEmitIntoClient - @lifetime(borrow span) - public init( - _elements span: borrowing Span - ) { - self.init( - _unchecked: span._pointer, - byteCount: span.count &* MemoryLayout.stride - ) - } -} - -extension RawSpan { - - @_alwaysEmitIntoClient - public var description: String { - let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) - return "(0x\(addr), \(_count))" - } -} - -extension RawSpan { - - /// The number of bytes in the span. - /// - /// To check whether the span is empty, use its `isEmpty` property - /// instead of comparing `count` to zero. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public var byteCount: Int { _count } - - /// A Boolean value indicating whether the span is empty. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public var isEmpty: Bool { byteCount == 0 } - - /// The indices that are valid for subscripting the span, in ascending - /// order. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public var byteOffsets: Range { - .init(uncheckedBounds: (0, byteCount)) - } -} - -//MARK: extracting sub-spans -extension RawSpan { - - /// Constructs a new span over the bytes within the supplied range of - /// positions within this span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `RawSpan`. - /// - /// - Returns: A span over the bytes within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(_ bounds: Range) -> Self { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Byte offset range out of bounds" - ) - return _extracting(unchecked: bounds) - } - - /// Constructs a new span over the bytes within the supplied range of - /// positions within this span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `RawSpan`. - /// - /// - Returns: A span over the bytes within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(unchecked bounds: Range) -> Self { - RawSpan( - _unchecked: _pointer?.advanced(by: bounds.lowerBound), - byteCount: bounds.count - ) - } - - /// Constructs a new span over the bytes within the supplied range of - /// positions within this span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `RawSpan`. - /// - /// - Returns: A span over the bytes within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(_ bounds: some RangeExpression) -> Self { - _extracting(bounds.relative(to: byteOffsets)) - } - - /// Constructs a new span over the bytes within the supplied range of - /// positions within this span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `RawSpan`. - /// - /// - Returns: A span over the bytes within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting( - unchecked bounds: some RangeExpression - ) -> Self { - _extracting(unchecked: bounds.relative(to: byteOffsets)) - } - - /// Constructs a new span over all the bytes of this span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Returns: A span over all the bytes of this span. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(_: UnboundedRange) -> Self { - self - } -} - -extension RawSpan { - - /// Calls the given closure with a pointer to the underlying bytes of - /// the viewed contiguous storage. - /// - /// The buffer pointer passed as an argument to `body` is valid only - /// during the execution of `withUnsafeBytes(_:)`. - /// Do not store or return the pointer for later use. - /// - /// - Parameter body: A closure with an `UnsafeRawBufferPointer` - /// parameter that points to the viewed contiguous storage. - /// If `body` has a return value, that value is also - /// used as the return value for the `withUnsafeBytes(_:)` method. - /// The closure's parameter is valid only for the duration of - /// its execution. - /// - Returns: The return value of the `body` closure parameter. - @_alwaysEmitIntoClient - public func withUnsafeBytes( - _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - try body(.init(start: _pointer, count: byteCount)) - } -} - -extension RawSpan { - - /// View the bytes of this span as type `T` - /// - /// This is the equivalent of `unsafeBitCast(_:to:)`. The - /// underlying bytes must be initialized as type `T`, be - /// initialized to a type that is layout-compatible with `T`, - /// or the function mapping bit patterns of length `8*MemoryLayout.size` - /// to instances of `T` must be surjective. - /// - /// This is an unsafe operation. Failure to meet the preconditions - /// above may produce invalid values of `T`. - /// - /// - Parameters: - /// - type: The type as which to view the bytes of this span. - /// - Returns: A typed span viewing these bytes as instances of `T`. - @unsafe - @_alwaysEmitIntoClient - @lifetime(self) - consuming public func _unsafeView( - as type: T.Type - ) -> Span { - Span(_unsafeBytes: .init(start: _pointer, count: _count)) - } -} - -//MARK: load -extension RawSpan { - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be properly aligned for - /// accessing `T` and initialized to `T` or another type that is layout - /// compatible with `T`. - /// - /// This is an unsafe operation. Failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance is memory-managed and unassociated - /// with the value in the memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoad( - fromByteOffset offset: Int = 0, as: T.Type - ) -> T { - precondition( - UInt(bitPattern: offset) <= UInt(bitPattern: _count) && - MemoryLayout.size <= (_count &- offset), - "Byte offset range out of bounds" - ) - return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) - } - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be properly aligned for - /// accessing `T` and initialized to `T` or another type that is layout - /// compatible with `T`. - /// - /// This is an unsafe operation. This function does not validate the bounds - /// of the memory access, and failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance is memory-managed and unassociated - /// with the value in the memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoad( - fromUncheckedByteOffset offset: Int, as: T.Type - ) -> T { - _start().load(fromByteOffset: offset, as: T.self) - } - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be initialized to `T` - /// or another type that is layout compatible with `T`. - /// - /// This is an unsafe operation. Failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance isn't associated - /// with the value in the range of memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoadUnaligned( - fromByteOffset offset: Int = 0, as: T.Type - ) -> T { - precondition( - UInt(bitPattern: offset) <= UInt(bitPattern: _count) && - MemoryLayout.size <= (_count &- offset), - "Byte offset range out of bounds" - ) - return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) - } - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be initialized to `T` - /// or another type that is layout compatible with `T`. - /// - /// This is an unsafe operation. This function does not validate the bounds - /// of the memory access, and failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance isn't associated - /// with the value in the range of memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoadUnaligned( - fromUncheckedByteOffset offset: Int, as: T.Type - ) -> T { - _start().loadUnaligned(fromByteOffset: offset, as: T.self) - } -} - -extension RawSpan { - /// Returns a Boolean value indicating whether two `RawSpan` instances - /// refer to the same region in memory. - @_alwaysEmitIntoClient - public func isIdentical(to other: Self) -> Bool { - (self._pointer == other._pointer) && (self._count == other._count) - } - - /// Returns the offsets where the memory of `span` is located within - /// the memory represented by `self` - /// - /// Note: `span` must be a subrange of `self` - /// - /// Parameters: - /// - span: a subrange of `self` - /// Returns: A range of offsets within `self` - @_alwaysEmitIntoClient - public func byteOffsets(of other: borrowing Self) -> Range? { - if other._count > _count { return nil } - guard let spanStart = other._pointer, _count > 0 else { - return _pointer == other._pointer ? Range(uncheckedBounds: (0, 0)) : nil - } - let start = _start() - let spanEnd = spanStart + other._count - if spanStart < start || (start + _count) < spanEnd { return nil } - let lower = start.distance(to: spanStart) - return Range(uncheckedBounds: (lower, lower &+ other._count)) - } -} - -//MARK: prefixes and suffixes -extension RawSpan { - - /// Returns a span containing the initial bytes of this span, - /// up to the specified maximum byte count. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the bytes. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of bytes to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` bytes. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(first maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a prefix of negative length") - let newCount = min(maxLength, byteCount) - return Self(_unchecked: _pointer, byteCount: newCount) - } - - /// Returns a span over all but the given number of trailing bytes. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of bytes to drop off the end of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span leaving off the specified number of bytes at the end. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(droppingLast k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let droppedCount = min(k, byteCount) - return Self(_unchecked: _pointer, byteCount: byteCount &- droppedCount) - } - - /// Returns a span containing the trailing bytes of the span, - /// up to the given maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the bytes. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of bytes to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` bytes. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(last maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a suffix of negative length") - let newCount = min(maxLength, byteCount) - let newStart = _pointer?.advanced(by: byteCount &- newCount) - return Self(_unchecked: newStart, byteCount: newCount) - } - - /// Returns a span over all but the given number of initial bytes. - /// - /// If the number of elements to drop exceeds the number of bytes in - /// the span, the result is an empty span. - /// - /// The returned span's first byte is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of bytes to drop from the beginning of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span starting after the specified number of bytes. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(droppingFirst k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let droppedCount = min(k, byteCount) - let newStart = _pointer?.advanced(by: droppedCount) - return Self(_unchecked: newStart, byteCount: byteCount &- droppedCount) - } -} diff --git a/Sources/Future/Span+Iterator.swift b/Sources/Future/Span+Iterator.swift deleted file mode 100644 index 4db0f891e..000000000 --- a/Sources/Future/Span+Iterator.swift +++ /dev/null @@ -1,60 +0,0 @@ -//===--- SpanIterator.swift -----------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -extension Span where Element: ~Copyable { - @frozen - public struct Iterator: Copyable, ~Escapable { - var curPointer: UnsafeRawPointer? - let endPointer: UnsafeRawPointer? - - @lifetime(span) - public init(from span: consuming Span) { - curPointer = span._pointer - endPointer = span._pointer?.advanced( - by: span.count*MemoryLayout.stride - ) - } - } -} - -extension Span.Iterator where Element: Copyable { - - // This is the `IteratorProtocol` requirement, except that - // Span.Iterator does not conform to `Escapable` - public mutating func next() -> Element? { - guard curPointer != endPointer, - let cur = curPointer, cur < endPointer.unsafelyUnwrapped - else { return nil } - defer { - curPointer = cur.advanced(by: MemoryLayout.stride) - } - if _isPOD(Element.self) { - return cur.loadUnaligned(as: Element.self) - } - return cur.load(as: Element.self) - } -} - -extension Span.Iterator where Element: BitwiseCopyable { - - // This is the `IteratorProtocol` requirement, except that - // Span.Iterator does not conform to `Escapable` - public mutating func next() -> Element? { - guard curPointer != endPointer, - let cur = curPointer, cur < endPointer.unsafelyUnwrapped - else { return nil } - defer { - curPointer = cur.advanced(by: MemoryLayout.stride) - } - return cur.loadUnaligned(as: Element.self) - } -} diff --git a/Sources/Future/Span.swift b/Sources/Future/Span.swift deleted file mode 100644 index affe56436..000000000 --- a/Sources/Future/Span.swift +++ /dev/null @@ -1,830 +0,0 @@ -//===--- Span.swift -------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import Builtin - -/// `Span` represents a contiguous region of memory -/// which contains initialized instances of `Element`. -/// -/// A `Span` instance is a non-owning, non-escaping view into memory. -/// When a `Span` is created, it inherits the lifetime of the container -/// owning the contiguous memory, ensuring temporal safety and avoiding -/// use-after-free errors. Operations on `Span` are bounds-checked, -/// ensuring spcial safety and avoiding buffer overflow errors. -@frozen -public struct Span -: ~Escapable, Copyable, BitwiseCopyable { - - /// The starting address of this `Span`. - /// - /// `_pointer` can be `nil` if and only if `_count` equals 0. - /// Otherwise, `_pointer` must point to memory that will remain - /// valid and not mutated as long as this `Span` exists. - /// The memory at `_pointer` must be initialized - /// as `_count` instances of `Element`. - @usableFromInline - internal let _pointer: UnsafeRawPointer? - - @_alwaysEmitIntoClient - internal func _start() -> UnsafeRawPointer { - _pointer.unsafelyUnwrapped - } - - /// The number of elements in this `Span`. - /// - /// If `_count` equals 0, then `_pointer` may be either `nil` or valid. - /// Any `_count` greater than 0 indicates a valid non-nil `_pointer`. - /// Any `_count` less than 0 is invalid and is undefined behaviour. - @usableFromInline - internal let _count: Int - - /// FIXME: Remove once supported old compilers can recognize lifetime dependence - @_unsafeNonescapableResult - @_alwaysEmitIntoClient - @inline(__always) - internal init() { - _pointer = nil - _count = 0 - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// `pointer` must point to a region of `count` initialized instances, - /// or may be `nil` if `count` is 0. - /// - /// The region of memory representing `count` instances starting at `pointer` - /// must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the span. - @_alwaysEmitIntoClient - @inline(__always) - @lifetime(borrow pointer) - internal init( - _unchecked pointer: borrowing UnsafeRawPointer?, - count: Int - ) { - _pointer = copy pointer - _count = count - } -} - -extension Span: @unchecked Sendable where Element: Sendable {} - -extension Span where Element: ~Copyable { - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeBufferPointer` to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing UnsafeBufferPointer - ) { - //FIXME: Workaround for https://github.com/swiftlang/swift/issues/77235 - let baseAddress = buffer.baseAddress - precondition( - ((Int(bitPattern: baseAddress) & - (MemoryLayout.alignment &- 1)) == 0), - "baseAddress must be properly aligned to access Element" - ) - self.init(_unchecked: baseAddress, count: buffer.count) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing UnsafeMutableBufferPointer - ) { - self.init(_unsafeElements: UnsafeBufferPointer(buffer)) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The region of memory representing `count` instances starting at `pointer` - /// must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized element. - /// - count: the number of initialized elements in the span. - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - public init( - _unsafeStart pointer: borrowing UnsafePointer, - count: Int - ) { - precondition(count >= 0, "Count must not be negative") - self.init(_unsafeElements: .init(start: copy pointer, count: count)) - } -} - -extension Span { - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeBufferPointer` to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing Slice> - ) { - self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// - Parameters: - /// - buffer: an `UnsafeMutableBufferPointer` to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: borrowing Slice> - ) { - self.init(_unsafeElements: UnsafeBufferPointer(rebasing: buffer)) - } -} - -extension Span where Element: BitwiseCopyable { - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// `buffer` must be correctly aligned for accessing - /// an element of type `Element`, and must contain a number of bytes - /// that is an exact multiple of `Element`'s stride. - /// - /// - Parameters: - /// - buffer: a buffer to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing UnsafeRawBufferPointer - ) { - //FIXME: Workaround for https://github.com/swiftlang/swift/issues/77235 - let baseAddress = buffer.baseAddress - precondition( - ((Int(bitPattern: baseAddress) & - (MemoryLayout.alignment &- 1)) == 0), - "baseAddress must be properly aligned to access Element" - ) - let (byteCount, stride) = (buffer.count, MemoryLayout.stride) - let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition( - remainder == 0, "Span must contain a whole number of elements" - ) - self.init(_unchecked: baseAddress, count: count) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// `buffer` must be correctly aligned for accessing - /// an element of type `Element`, and must contain a number of bytes - /// that is an exact multiple of `Element`'s stride. - /// - /// - Parameters: - /// - buffer: a buffer to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing UnsafeMutableRawBufferPointer - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(buffer)) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The region of memory representing the instances starting at `pointer` - /// must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// `pointer` must be correctly aligned for accessing - /// an element of type `Element`, and `byteCount` - /// must be an exact multiple of `Element`'s stride. - /// - /// - Parameters: - /// - pointer: a pointer to the first initialized element. - /// - byteCount: the number of initialized elements in the span. - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - public init( - _unsafeStart pointer: borrowing UnsafeRawPointer, - byteCount: Int - ) { - precondition(byteCount >= 0, "Count must not be negative") - self.init(_unsafeBytes: .init(start: copy pointer, count: byteCount)) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// `buffer` must be correctly aligned for accessing - /// an element of type `Element`, and must contain a number of bytes - /// that is an exact multiple of `Element`'s stride. - /// - /// - Parameters: - /// - buffer: a buffer to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing Slice - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) - } - - /// Unsafely create a `Span` over initialized memory. - /// - /// The memory in `buffer` must remain valid, initialized and immutable - /// throughout the lifetime of the newly-created `Span`. - /// Failure to maintain this invariant results in undefined behaviour. - /// - /// `buffer` must be correctly aligned for accessing - /// an element of type `Element`, and must contain a number of bytes - /// that is an exact multiple of `Element`'s stride. - /// - /// - Parameters: - /// - buffer: a buffer to initialized elements. - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing Slice - ) { - self.init(_unsafeBytes: UnsafeRawBufferPointer(rebasing: buffer)) - } - - /// Create a `Span` over the bytes represented by a `RawSpan` - /// - /// - Parameters: - /// - bytes: An existing `RawSpan`, which will define both this - /// `Span`'s lifetime and the memory it represents. - @_alwaysEmitIntoClient - @lifetime(bytes) - public init(_bytes bytes: consuming RawSpan) { - self.init( - _unsafeBytes: .init(start: bytes._pointer, count: bytes.byteCount) - ) - } -} - -extension Span where Element: Equatable { - - /// Returns a Boolean value indicating whether this and another span - /// contain equal elements in the same order. - /// - /// - Parameters: - /// - other: A span to compare to this one. - /// - Returns: `true` if this sequence and `other` contain equivalent items, - /// using `areEquivalent` as the equivalence test; otherwise, `false.` - /// - /// - Complexity: O(*m*), where *m* is the lesser of the length of the - /// sequence and the length of `other`. - @_alwaysEmitIntoClient - public func _elementsEqual(_ other: Self) -> Bool { - guard count == other.count else { return false } - if count == 0 { return true } - - //FIXME: This could be short-cut - // with a layout constraint where stride equals size, - // as long as there is at most 1 unused bit pattern. - // if Element is BitwiseEquatable { - // return _swift_stdlib_memcmp(lhs.baseAddress, rhs.baseAddress, count) == 0 - // } - for o in 0..) -> Bool { - let equal = other.withContiguousStorageIfAvailable { - _elementsEqual(Span(_unsafeElements: $0)) - } - if let equal { return equal } - - guard count == other.count else { return false } - if count == 0 { return true } - - return _elementsEqual(AnySequence(other)) - } - - /// Returns a Boolean value indicating whether this span and a Sequence - /// contain equal elements in the same order. - /// - /// - Parameters: - /// - other: A Sequence to compare to this span. - /// - Returns: `true` if this sequence and `other` contain equivalent items, - /// using `areEquivalent` as the equivalence test; otherwise, `false.` - /// - /// - Complexity: O(*m*), where *m* is the lesser of the length of the - /// sequence and the length of `other`. - @_alwaysEmitIntoClient - public func _elementsEqual(_ other: some Sequence) -> Bool { - var offset = 0 - for otherElement in other { - if offset >= count { return false } - if self[unchecked: offset] != otherElement { return false } - offset += 1 - } - return offset == count - } -} - -extension Span where Element: ~Copyable { - - @_alwaysEmitIntoClient - public var description: String { - let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) - return "(0x\(addr), \(_count))" - } -} - -extension Span where Element: ~Copyable { - - /// The number of elements in the span. - /// - /// To check whether the span is empty, use its `isEmpty` property - /// instead of comparing `count` to zero. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public var count: Int { _count } - - /// A Boolean value indicating whether the span is empty. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public var isEmpty: Bool { _count == 0 } - - /// The representation for a position in `Span`. - public typealias Index = Int - - /// The indices that are valid for subscripting the span, in ascending - /// order. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public var indices: Range { - Range(uncheckedBounds: (0, _count)) - } -} - -extension Span where Element: ~Copyable { - - /// Accesses the element at the specified position in the `Span`. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public subscript(_ position: Index) -> Element { - //FIXME: change to unsafeRawAddress when ready - unsafeAddress { - precondition(indices.contains(position), "Index out of bounds") - return _unsafeAddressOfElement(unchecked: position) - } - } - - /// Accesses the element at the specified position in the `Span`. - /// - /// This subscript does not validate `position`. Using this subscript - /// with an invalid `position` results in undefined behaviour. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - public subscript(unchecked position: Index) -> Element { - //FIXME: change to unsafeRawAddress when ready - unsafeAddress { - _unsafeAddressOfElement(unchecked: position) - } - } - - @unsafe - @_alwaysEmitIntoClient - internal func _unsafeAddressOfElement( - unchecked position: Index - ) -> UnsafePointer { - let elementOffset = position &* MemoryLayout.stride - let address = _start().advanced(by: elementOffset) - return address.assumingMemoryBound(to: Element.self) - } -} - -extension Span where Element: BitwiseCopyable { - - /// Accesses the element at the specified position in the `Span`. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public subscript(_ position: Index) -> Element { - get { - precondition( - UInt(bitPattern: position) < UInt(bitPattern: _count), - "Index out of bounds" - ) - return self[unchecked: position] - } - } - - /// Accesses the element at the specified position in the `Span`. - /// - /// This subscript does not validate `position`. Using this subscript - /// with an invalid `position` results in undefined behaviour. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - public subscript(unchecked position: Index) -> Element { - get { - let elementOffset = position &* MemoryLayout.stride - let address = _start().advanced(by: elementOffset) - return address.loadUnaligned(as: Element.self) - } - } -} - -//MARK: sub-spans -extension Span where Element: ~Copyable { - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `Span`. - /// - /// - Returns: A `Span` over the items within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(_ bounds: Range) -> Self { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - return _extracting(unchecked: bounds) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `Span`. - /// - /// - Returns: A `Span` over the items within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(unchecked bounds: Range) -> Self { - let delta = bounds.lowerBound &* MemoryLayout.stride - return Span(_unchecked: _pointer?.advanced(by: delta), count: bounds.count) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `Span`. - /// - /// - Returns: A `Span` over the items within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(_ bounds: some RangeExpression) -> Self { - _extracting(bounds.relative(to: indices)) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `Span`. - /// - /// - Returns: A `Span` over the items within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting( - unchecked bounds: some RangeExpression - ) -> Self { - _extracting(unchecked: bounds.relative(to: indices)) - } - - /// Constructs a new span over all the items of this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Returns: A `Span` over all the items of this span. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(_: UnboundedRange) -> Self { - self - } -} - -//MARK: UnsafeBufferPointer access hatch -extension Span where Element: ~Copyable { - - /// Calls a closure with a pointer to the viewed contiguous storage. - /// - /// The buffer pointer passed as an argument to `body` is valid only - /// during the execution of `withUnsafeBufferPointer(_:)`. - /// Do not store or return the pointer for later use. - /// - /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter - /// that points to the viewed contiguous storage. If `body` has - /// a return value, that value is also used as the return value - /// for the `withUnsafeBufferPointer(_:)` method. The closure's - /// parameter is valid only for the duration of its execution. - /// - Returns: The return value of the `body` closure parameter. - @_alwaysEmitIntoClient - public func withUnsafeBufferPointer( - _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - guard let pointer = _pointer else { - return try body(.init(start: nil, count: 0)) - } - let binding = Builtin.bindMemory( - pointer._rawValue, count._builtinWordValue, Element.self - ) - defer { Builtin.rebindMemory(pointer._rawValue, binding) } - return try body(.init(start: .init(pointer._rawValue), count: count)) - } -} - -extension Span where Element: BitwiseCopyable { - - /// Calls the given closure with a pointer to the underlying bytes of - /// the viewed contiguous storage. - /// - /// The buffer pointer passed as an argument to `body` is valid only - /// during the execution of `withUnsafeBytes(_:)`. - /// Do not store or return the pointer for later use. - /// - /// - Parameter body: A closure with an `UnsafeRawBufferPointer` - /// parameter that points to the viewed contiguous storage. - /// If `body` has a return value, that value is also - /// used as the return value for the `withUnsafeBytes(_:)` method. - /// The closure's parameter is valid only for the duration of - /// its execution. - /// - Returns: The return value of the `body` closure parameter. - @_alwaysEmitIntoClient - public func withUnsafeBytes( - _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - try body( - .init(start: _pointer, count: _count * MemoryLayout.stride) - ) - } -} - -// `first` and `last` can't exist where Element: ~Copyable -// because we can't construct an Optional of a borrow -extension Span where Element: Copyable { - - /// The first element in the span. - /// - /// If the span is empty, the value of this property is `nil`. - /// - /// - Returns: The first element in the span, or `nil` if empty - @_alwaysEmitIntoClient - public var first: Element? { - isEmpty ? nil : self[unchecked: 0] - } - - /// The last element in the span. - /// - /// If the span is empty, the value of this property is `nil`. - /// - /// - Returns: The last element in the span, or `nil` if empty - @_alwaysEmitIntoClient - public var last: Element? { - isEmpty ? nil : self[unchecked: count &- 1] - } -} - -extension Span where Element: ~Copyable { - /// Returns a Boolean value indicating whether two `Span` instances - /// refer to the same region in memory. - @_alwaysEmitIntoClient - public func isIdentical(to other: Self) -> Bool { - (self._pointer == other._pointer) && (self._count == other._count) - } - - /// Returns the indices within `self` where the memory represented by `span` - /// is located, or `nil` if `span` is not located within `self`. - /// - /// Parameters: - /// - span: a span that may be a subrange of `self` - /// Returns: A range of indices within `self`, or `nil` - @_alwaysEmitIntoClient - public func indices(of other: borrowing Self) -> Range? { - if other._count > _count { return nil } - guard let spanStart = other._pointer, _count > 0 else { - return _pointer == other._pointer ? Range(uncheckedBounds: (0, 0)) : nil - } - let start = _start() - let stride = MemoryLayout.stride - let spanEnd = spanStart + stride &* other._count - if spanStart < start || spanEnd > (start + stride &* _count) { return nil } - let byteOffset = start.distance(to: spanStart) - let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride) - guard r == 0 else { return nil } - return Range(uncheckedBounds: (lower, lower &+ other._count)) - } -} - -//MARK: prefixes and suffixes -extension Span where Element: ~Copyable { - - /// Returns a span containing the initial elements of this span, - /// up to the specified maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the elements. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of elements to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(first maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a prefix of negative length") - let newCount = min(maxLength, count) - return Self(_unchecked: _pointer, count: newCount) - } - - /// Returns a span over all but the given number of trailing elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of elements to drop off the end of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span leaving off the specified number of elements at the end. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(droppingLast k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let droppedCount = min(k, count) - return Self(_unchecked: _pointer, count: count &- droppedCount) - } - - /// Returns a span containing the final elements of the span, - /// up to the given maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the elements. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of elements to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(last maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a suffix of negative length") - let newCount = min(maxLength, count) - let offset = (count &- newCount) * MemoryLayout.stride - let newStart = _pointer?.advanced(by: offset) - return Self(_unchecked: newStart, count: newCount) - } - - /// Returns a span over all but the given number of initial elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of elements to drop from the beginning of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span starting after the specified number of elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(self) - public func _extracting(droppingFirst k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let droppedCount = min(k, count) - let offset = droppedCount * MemoryLayout.stride - let newStart = _pointer?.advanced(by: offset) - return Self(_unchecked: newStart, count: count &- droppedCount) - } -} diff --git a/Sources/Future/SpanExtensions.swift b/Sources/Future/SpanExtensions.swift new file mode 100644 index 000000000..87546beab --- /dev/null +++ b/Sources/Future/SpanExtensions.swift @@ -0,0 +1,110 @@ +//===--- SpanExtensions.swift ---------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +private let immortalThing: [Void] = [] + +@available(macOS 9999, *) +extension Span { + + @available(macOS 9999, *) + public static var empty: Span { + @lifetime(immortal) + get { + let nilBasedBuffer = UnsafeBufferPointer(start: nil, count: 0) + let span = Span(_unsafeElements: nilBasedBuffer) + return _overrideLifetime(span, borrowing: immortalThing) + } + } + + @available(macOS 9999, *) + @lifetime(immortal) + public init() { + let nilBasedBuffer = UnsafeBufferPointer(start: nil, count: 0) + let span = Span(_unsafeElements: nilBasedBuffer) + self = _overrideLifetime(span, borrowing: immortalThing) + } +} + +@available(macOS 9999, *) +extension Span where Element: Equatable { + + /// Returns a Boolean value indicating whether this and another span + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A span to compare to this one. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: Self) -> Bool { + guard count == other.count else { return false } + if count == 0 { return true } + + //FIXME: This could be short-cut + // with a layout constraint where stride equals size, + // as long as there is at most 1 unused bit pattern. + // if Element is BitwiseEquatable { + // return _swift_stdlib_memcmp(lhs.baseAddress, rhs.baseAddress, count) == 0 + // } + for o in 0..) -> Bool { + let equal = other.withContiguousStorageIfAvailable { + _elementsEqual(Span(_unsafeElements: $0)) + } + if let equal { return equal } + + guard count == other.count else { return false } + if count == 0 { return true } + + return _elementsEqual(AnySequence(other)) + } + + /// Returns a Boolean value indicating whether this span and a Sequence + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A Sequence to compare to this span. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: some Sequence) -> Bool { + var offset = 0 + for otherElement in other { + if offset >= count { return false } + if self[unchecked: offset] != otherElement { return false } + offset += 1 + } + return offset == count + } +} diff --git a/Sources/Future/StdlibOutputSpanExtensions.swift b/Sources/Future/StdlibOutputSpanExtensions.swift index cc392a3c7..decf5d294 100644 --- a/Sources/Future/StdlibOutputSpanExtensions.swift +++ b/Sources/Future/StdlibOutputSpanExtensions.swift @@ -12,6 +12,7 @@ extension Array { + @available(macOS 9999, *) public init( capacity: Int, initializingWith initializer: (inout OutputSpan) throws -> Void @@ -19,9 +20,9 @@ extension Array { try self.init( unsafeUninitializedCapacity: capacity, initializingWith: { (buffer, count) in + let pointer = buffer.baseAddress.unsafelyUnwrapped var output = OutputSpan( - _initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: buffer.count + _initializing: pointer, capacity: buffer.count ) try initializer(&output) let initialized = output.relinquishBorrowedMemory() @@ -37,17 +38,17 @@ extension String { // also see https://github.com/apple/swift/pull/23050 // and `final class __SharedStringStorage` - @available(macOS 11, *) + @available(macOS 9999, *) public init( utf8Capacity capacity: Int, - initializingWith initializer: (inout OutputSpan) throws -> Void + initializingWith initializer: (inout OutputSpan) throws -> Void ) rethrows { try self.init( unsafeUninitializedCapacity: capacity, initializingUTF8With: { buffer in - var output = OutputSpan( - _initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: capacity + let pointer = buffer.baseAddress.unsafelyUnwrapped + var output = OutputSpan( + _initializing: pointer, capacity: buffer.count ) try initializer(&output) let initialized = output.relinquishBorrowedMemory() @@ -62,6 +63,7 @@ import Foundation extension Data { + @available(macOS 9999, *) public init( capacity: Int, initializingWith initializer: (inout OutputSpan) throws -> Void @@ -70,9 +72,9 @@ extension Data { let count = try self.withUnsafeMutableBytes { rawBuffer in try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in buffer.deinitialize() - var output = OutputSpan( - _initializing: buffer.baseAddress.unsafelyUnwrapped, - capacity: capacity + let pointer = buffer.baseAddress.unsafelyUnwrapped + var output = OutputSpan( + _initializing: pointer, capacity: capacity ) try initializer(&output) let initialized = output.relinquishBorrowedMemory() diff --git a/Sources/Future/StdlibSpanExtensions.swift b/Sources/Future/StdlibSpanExtensions.swift deleted file mode 100644 index cc6f4a05c..000000000 --- a/Sources/Future/StdlibSpanExtensions.swift +++ /dev/null @@ -1,502 +0,0 @@ -//===--- StdlibSpanExtensions.swift ---------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -extension UnsafeBufferPointer where Element: ~Copyable { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - try body(Span(_unsafeElements: self)) - } - - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result where Element: BitwiseCopyable { - try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(self))) - } -} - -extension UnsafeMutableBufferPointer where Element: ~Copyable { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - try body(Span(_unsafeElements: self)) - } - - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result where Element: BitwiseCopyable { - try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(self))) - } -} - -extension UnsafeRawBufferPointer { - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - try body(RawSpan(_unsafeBytes: self)) - } -} - -extension UnsafeMutableRawBufferPointer { - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - try body(RawSpan(_unsafeBytes: self)) - } -} - -extension Slice { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result - where Base == UnsafeBufferPointer { - try body(Span(_unsafeElements: UnsafeBufferPointer(rebasing: self))) - } - - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result - where Base == UnsafeBufferPointer { - try body(RawSpan(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: self)))) - } - - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result - where Base == UnsafeMutableBufferPointer { - try body(Span(_unsafeElements: UnsafeBufferPointer(rebasing: self))) - } - - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result - where Base == UnsafeMutableBufferPointer { - try body(RawSpan(_unsafeBytes: .init(UnsafeBufferPointer(rebasing: self)))) - } - - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result - where Base == UnsafeRawBufferPointer { - try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(rebasing: self))) - } - - public func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result - where Base == UnsafeMutableRawBufferPointer { - try body(RawSpan(_unsafeBytes: UnsafeRawBufferPointer(rebasing: self))) - } -} - -import struct Foundation.Data - -extension Data { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBytes { - bytes in - do throws(E) { - let result = try body(Span(_unsafeBytes: bytes)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } - - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBytes { - bytes in - do throws(E) { - let result = try body(RawSpan(_unsafeBytes: bytes)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } -} - -extension Array { - - /// Calls a closure with a `Span` of the array's contiguous storage. - /// - /// Often, the optimizer can eliminate bounds checks within an array - /// algorithm, but when that fails, invoking the same algorithm on the - /// buffer pointer passed into your closure lets you trade safety for speed. - /// - /// The following example shows how you can iterate over the contents of the - /// buffer pointer: - /// - /// let numbers = [1, 2, 3, 4, 5] - /// let sum = numbers.withUnsafeBufferPointer { buffer -> Int in - /// var result = 0 - /// for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) { - /// result += buffer[i] - /// } - /// return result - /// } - /// // 'sum' == 9 - /// - /// The pointer passed as an argument to `body` is valid only during the - /// execution of `withUnsafeBufferPointer(_:)`. Do not store or return the - /// pointer for later use. - /// - /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter that - /// points to the contiguous storage for the array. If no such storage exists, it is created. If - /// `body` has a return value, that value is also used as the return value - /// for the `withUnsafeBufferPointer(_:)` method. The pointer argument is - /// valid only for the duration of the method's execution. - /// - Returns: The return value, if any, of the `body` closure parameter. - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBufferPointer { - elements in - do throws(E) { - let result = try body(Span(_unsafeElements: elements)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } - -// public func withSpan( -// _ body: (_ elements: Span) throws(E) -> Result -// ) throws(E) -> Result { -// try withUnsafeTemporaryAllocation(of: Result.self, capacity: 1) { -// buffer -> Result in -// try self.withUnsafeBufferPointer { -// let result = try body(Span(_unsafeElements: $0)) -// buffer.initializeElement(at: 0, to: result) -// } -// return buffer.moveElement(from: 0) -// } -// } - -// public func withSpan( -// _ body: (_ elements: Span) throws(E) -> Result -// ) throws(E) -> Result { -// try self.withUnsafeBufferPointer { -// try body(Span(_unsafeElements: $0)) -// } -// } - - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result where Element: BitwiseCopyable { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBytes { - bytes in - do throws(E) { - let result = try body(RawSpan(_unsafeBytes: bytes)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } -} - -extension ContiguousArray { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBufferPointer { - elements in - do throws(E) { - let result = try body(Span(_unsafeElements: elements)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } -} - -extension ContiguousArray where Element: BitwiseCopyable { - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result where Element: BitwiseCopyable { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBytes { - bytes in - do throws(E) { - let result = try body(RawSpan(_unsafeBytes: bytes)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } -} - -extension ArraySlice { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBufferPointer { - elements in - do throws(E) { - let result = try body(Span(_unsafeElements: elements)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } -} - -extension ArraySlice where Element: BitwiseCopyable { - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result where Element: BitwiseCopyable { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result in - self.withUnsafeBytes { - bytes in - do throws(E) { - let result = try body(RawSpan(_unsafeBytes: bytes)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return buffer.moveElement(from: 0) - } - return try result.get() - } -} - -extension String.UTF8View { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result? in - let ran: Void? = self.withContiguousStorageIfAvailable { - elements in - do throws(E) { - let result = try body(Span(_unsafeElements: elements)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return (ran == nil) ? nil : buffer.moveElement(from: 0) - } - if let result { - return try result.get() - } - return try ContiguousArray(self).withSpan(body) - } - - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result? in - let ran: Void? = self.withContiguousStorageIfAvailable { - do throws(E) { - let result = try body(RawSpan(_unsafeElements: $0)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return (ran == nil) ? nil : buffer.moveElement(from: 0) - } - if let result { - return try result.get() - } - return try ContiguousArray(self).withBytes(body) - } -} - -extension Substring.UTF8View { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result? in - let ran: Void? = self.withContiguousStorageIfAvailable { - elements in - do throws(E) { - let result = try body(Span(_unsafeElements: elements)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return (ran == nil) ? nil : buffer.moveElement(from: 0) - } - if let result { - return try result.get() - } - return try ContiguousArray(self).withSpan(body) - } - - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - let result = withUnsafeTemporaryAllocation( - of: Swift.Result.self, capacity: 1 - ) { - buffer -> Swift.Result? in - let ran: Void? = self.withContiguousStorageIfAvailable { - elements in - do throws(E) { - let result = try body(RawSpan(_unsafeElements: elements)) - buffer.initializeElement(at: 0, to: .success(result)) - } catch { - buffer.initializeElement(at: 0, to: .failure(error)) - } - } - return (ran == nil) ? nil : buffer.moveElement(from: 0) - } - if let result { - return try result.get() - } - return try ContiguousArray(self).withBytes(body) - } -} - -extension CollectionOfOne { - public func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - var collection = self - return try withUnsafePointer(to: &collection) { - pointer throws(E) -> Result in - try pointer.withMemoryRebound(to: Element.self, capacity: 1) { - element throws(E) -> Result in - try body(Span(_unsafeStart: element, count: 1)) - } - } - } -} - -extension CollectionOfOne where Element: BitwiseCopyable { - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - var collection = self - return try Swift.withUnsafeBytes(of: &collection) { - bytes throws(E) -> Result in - try body(RawSpan(_unsafeBytes: bytes)) - } - } -} - -extension KeyValuePairs { - public func withSpan( - _ body: ( - _ elements: Span<(key: Key, value: Value)> - ) throws(E) -> Result - ) throws(E) -> Result { - try ContiguousArray(self).withSpan(body) - } -} - -extension KeyValuePairs where Element: BitwiseCopyable { - public func withBytes( - _ body: (_ bytes: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - try ContiguousArray(self).withBytes(body) - } -} - -extension Span where Element: ~Copyable /*& ~Escapable*/ { - public consuming func withSpan( - _ body: (_ elements: Span) throws(E) -> Result - ) throws(E) -> Result { - try body(self) - } -} - -extension Span where Element: BitwiseCopyable { - public consuming func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - try body(RawSpan(_elements: self)) - } -} - -extension RawSpan { - public consuming func withBytes( - _ body: (_ elements: RawSpan) throws(E) -> Result - ) throws(E) -> Result { - try body(self) - } -} - -//TODO: extend SIMD vectors with `withSpan` and with `withBytes`. diff --git a/Tests/FutureTests/BoxTests.swift b/Tests/FutureTests/BoxTests.swift deleted file mode 100644 index 9f1075e85..000000000 --- a/Tests/FutureTests/BoxTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import XCTest -import Future - -final class FutureBoxTests: XCTestCase { - func test_basic() { - let intOnHeap = Box(0) - - XCTAssertEqual(intOnHeap[], 0) - - intOnHeap[] = 123 - - XCTAssertEqual(intOnHeap[], 123) - - let inoutToIntOnHeap = intOnHeap.leak() - - XCTAssertEqual(inoutToIntOnHeap[], 123) - - inoutToIntOnHeap[] = 321 - - XCTAssertEqual(inoutToIntOnHeap[], 321) - - let intOnHeapAgain = Box(inoutToIntOnHeap) - - XCTAssertEqual(intOnHeapAgain[], 321) - - XCTAssertEqual(intOnHeapAgain.copy(), 321) - - let intInRegister = intOnHeapAgain.consume() - - XCTAssertEqual(intInRegister, 321) - } -} diff --git a/Tests/FutureTests/CellTests.swift b/Tests/FutureTests/CellTests.swift deleted file mode 100644 index 7588e1642..000000000 --- a/Tests/FutureTests/CellTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import XCTest -import Future - -final class FutureCellTests: XCTestCase { - struct IntOnStack: ~Copyable { - var value = Cell(0) - } - - func test_basic() { - var myInt = IntOnStack() - - XCTAssertEqual(myInt.value[], 0) - - myInt.value[] = 123 - - XCTAssertEqual(myInt.value[], 123) - - let inoutToIntOnStack = myInt.value.asInout() - - inoutToIntOnStack[] = 321 - - XCTAssertEqual(myInt.value[], 321) - - XCTAssertEqual(myInt.value.copy(), 321) - } -} diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift deleted file mode 100644 index b0fa7a067..000000000 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ /dev/null @@ -1,59 +0,0 @@ -import XCTest -import Future - -final class ContiguousStorageTests: XCTestCase { - - func testBorrowArrayStorage() throws { - - let capacity = 10 - var a: [Int] = [] - a = Array(0.. - - var startIndex: Int { 0 } - var endIndex: Int { span.count } - func index(after i: Int) -> Int { i+2 } - - init(_ contiguous: borrowing Span) { - span = copy contiguous - } - - subscript(_ p: Int) -> Int { span[p] } - } - - @inline(never) - @lifetime(borrow array) - private func skip( - along array: borrowing Array - ) -> Skipper { - Skipper(array.storage) - } - - func testSpanWrapper() { - let capacity = 8 - let a = Array(0..() - expectTrue(array.isEmpty) - expectEqual(array.count, 0) - expectEqual(array.capacity, 0) - expectEqual(Counted.instances, 0) - - array.append(Counted(42)) - expectFalse(array.isEmpty) - expectEqual(array.count, 1) - expectEqual(array[0].value, 42) - expectEqual(Counted.instances, 1) - - array.append(Counted(23)) - expectFalse(array.isEmpty) - expectEqual(array.count, 2) - expectEqual(array[0].value, 42) - expectEqual(array[1].value, 23) - expectEqual(Counted.instances, 2) - - let old = array.remove(at: 0) - expectEqual(old.value, 42) - expectFalse(array.isEmpty) - expectEqual(array.count, 1) - expectEqual(array[0].value, 23) - expectEqual(Counted.instances, 2) - _ = consume old - expectEqual(Counted.instances, 1) - - let old2 = array.remove(at: 0) - expectEqual(old2.value, 23) - expectEqual(array.count, 0) - expectTrue(array.isEmpty) - expectEqual(Counted.instances, 1) - _ = consume old2 - expectEqual(Counted.instances, 0) - } - - func test_read_access() { - let c = 100 - let array = DynamicArray(count: c) { Counted($0) } - - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i) { $0.value }, i) - expectEqual(array[i].value, i) - } - } - - func test_update_access() { - let c = 100 - var array = DynamicArray(count: c) { Counted($0) } - - for i in 0 ..< c { - array.updateElement(at: i) { $0.value += 100 } - array[i].value += 100 - } - - for i in 0 ..< c { - expectEqual(array[i].value, 200 + i) - } - - expectEqual(Counted.instances, c) - _ = consume array - expectEqual(Counted.instances, 0) - } - - func test_append() { - var array = DynamicArray() - let c = 100 - for i in 0 ..< c { - array.append(Counted(100 + i)) - } - expectEqual(Counted.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - // FIXME: unexpected exclusivity violation (rdar://128441125) - //expectEqual(array.borrowElement(at: i) { $0.value }, 100 + i) - expectEqual(array[i].value, 100 + i) - } - - _ = consume array - expectEqual(Counted.instances, 0) - } - - func test_insert() { - var array = DynamicArray() - let c = 100 - for i in 0 ..< c { - array.insert(Counted(100 + i), at: 0) - } - expectEqual(Counted.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - // FIXME: unexpected exclusivity violation (rdar://128441125) - //expectEqual(array.borrowElement(at: i) { $0.value }, c + 99 - i) - expectEqual(array[i].value, c + 99 - i) - } - - _ = consume array - expectEqual(Counted.instances, 0) - } - - func test_remove() { - let c = 100 - var array = DynamicArray(count: c) { Counted(100 + $0) } - expectEqual(Counted.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - array.remove(at: 0) - expectEqual(array.count, c - 1 - i) - expectEqual(Counted.instances, c - 1 - i) - } - - expectTrue(array.isEmpty) - expectEqual(Counted.instances, 0) - } - - func test_iterate_full() { - let c = 100 - let array = DynamicArray(count: c) { Counted(100 + $0) } - - var state = array.startBorrowingIteration() - do { - let span = state.nextChunk(maximumCount: Int.max) - expectEqual(span.count, c) - for i in 0 ..< span.count { - expectEqual(span[i].value, 100 + i) - } - } - do { - let span2 = state.nextChunk(maximumCount: Int.max) - expectEqual(span2.count, 0) - } - } - - func test_iterate_stepped() { - let c = 100 - let array = DynamicArray(count: c) { Counted($0) } - - withEvery("stride", in: 1 ... c) { stride in - var state = array.startBorrowingIteration() - var i = 0 - while true { - let span = state.nextChunk(maximumCount: stride) - if span.count == 0 { break } - expectEqual(span.count, i + stride <= c ? stride : c % stride) - for j in 0 ..< span.count { - expectEqual(span[j].value, i) - i += 1 - } - } - expectEqual(i, c) - expectEqual(state.nextChunk(maximumCount: Int.max).count, 0) - } - } -} diff --git a/Tests/FutureTests/Inout.swift b/Tests/FutureTests/Inout.swift deleted file mode 100644 index 8ef1a1001..000000000 --- a/Tests/FutureTests/Inout.swift +++ /dev/null @@ -1,29 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import XCTest -import Future - -final class FutureInoutTests: XCTestCase { - func test_basic() { - var x = 0 - let y = Inout(&x) - - var v = y[] - XCTAssertEqual(v, 0) - - y[] += 10 - - v = y[] - XCTAssertEqual(v, 10) - XCTAssertEqual(x, 10) - } -} diff --git a/Tests/FutureTests/MutableSpanTests.swift b/Tests/FutureTests/MutableSpanTests.swift index 8fa98487b..9b351bae2 100644 --- a/Tests/FutureTests/MutableSpanTests.swift +++ b/Tests/FutureTests/MutableSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import Future +import Future class ID { let id: Int @@ -23,26 +23,16 @@ class ID { } } +@available(macOS 9999, *) final class MutableSpanTests: XCTestCase { - func testOptionalStorage() { -// XCTAssertEqual( -// MemoryLayout>.size, MemoryLayout?>.size -// ) -// XCTAssertEqual( -// MemoryLayout>.stride, MemoryLayout?>.stride -// ) -// XCTAssertEqual( -// MemoryLayout>.alignment, MemoryLayout?>.alignment -// ) - } - func testInitOrdinaryElement() { let capacity = 4 var s = (0...stride) + let bytes = span.bytes + XCTAssertEqual(bytes.byteCount, count*MemoryLayout.stride) } } @@ -143,8 +139,9 @@ final class MutableSpanTests: XCTestCase { var a = Array(0..( - _unsafeElements: .init(start: $0.baseAddress, count: 0) - ) + let empty0 = UnsafeMutableBufferPointer(start: $0.baseAddress, count: 0) + var emptySpan = MutableSpan(_unsafeElements: empty0) emptySpan.withUnsafeMutableBufferPointer { XCTAssertEqual($0.count, 0) XCTAssertNil($0.baseAddress) @@ -279,9 +283,8 @@ final class MutableSpanTests: XCTestCase { $0.storeBytes(of: UInt8(i+1), toByteOffset: i, as: UInt8.self) } - var emptySpan = MutableSpan( - _unsafeElements: .init(start: $0.baseAddress, count: 0) - ) + let empty0 = UnsafeMutableBufferPointer(start: $0.baseAddress, count: 0) + var emptySpan = MutableSpan(_unsafeElements: empty0) emptySpan.withUnsafeMutableBytes { XCTAssertEqual($0.count, 0) XCTAssertNil($0.baseAddress) @@ -315,7 +318,8 @@ final class MutableSpanTests: XCTestCase { var a = Array(repeating: Int.max, count: capacity) XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: .init(start: nil, count: 0)) + let empty = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableSpan(_unsafeElements: empty) var (iterator, updated) = span.update(from: 0..<0) XCTAssertNil(iterator.next()) XCTAssertEqual(updated, 0) @@ -337,7 +341,8 @@ final class MutableSpanTests: XCTestCase { var a = Array(repeating: ID(id: .max), count: capacity) XCTAssertEqual(a.allSatisfy({ $0.id == .max }), true) a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: $0.prefix(0)) + let emptyPrefix = $0.prefix(0) + var span = MutableSpan(_unsafeElements: emptyPrefix) var (iterator, updated) = span.update(from: []) XCTAssertNil(iterator.next()) XCTAssertEqual(updated, 0) @@ -359,7 +364,8 @@ final class MutableSpanTests: XCTestCase { var a = Array(repeating: Int.max, count: capacity) XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: $0.prefix(0)) + let emptyPrefix = $0.prefix(0) + var span = MutableSpan(_unsafeElements: emptyPrefix) var updated = span.update(fromContentsOf: []) XCTAssertEqual(updated, 0) @@ -379,7 +385,8 @@ final class MutableSpanTests: XCTestCase { var a = Array(repeating: ID(id: .max), count: capacity) XCTAssertEqual(a.allSatisfy({ $0.id == .max }), true) a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: $0.prefix(0)) + let emptyPrefix = $0.prefix(0) + var span = MutableSpan(_unsafeElements: emptyPrefix) var updated = span.update(fromContentsOf: []) XCTAssertEqual(updated, 0) @@ -399,7 +406,8 @@ final class MutableSpanTests: XCTestCase { var a = Array(repeating: Int.max, count: capacity) XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: $0.prefix(0)) + let emptyPrefix = $0.prefix(0) + var span = MutableSpan(_unsafeElements: emptyPrefix) var update = span.update(fromContentsOf: []) XCTAssertEqual(update, 0) @@ -416,7 +424,8 @@ final class MutableSpanTests: XCTestCase { var a = Array(repeating: ID(id: .max), count: capacity) XCTAssertEqual(a.allSatisfy({ $0.id == .max }), true) a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: $0.prefix(0)) + let emptyPrefix = $0.prefix(0) + var span = MutableSpan(_unsafeElements: emptyPrefix) var updated = span.update(fromContentsOf: UnsafeBufferPointer(start: nil, count: 0)) XCTAssertEqual(updated, 0) @@ -428,24 +437,6 @@ final class MutableSpanTests: XCTestCase { XCTAssertEqual(a.map(\.id).elementsEqual(0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..(start: nil, count: 0) + defer { _ = e } + + var m = MutableSpan(_unsafeElements: b) + m[0] = 100 + XCTAssertEqual(m.count, count) + XCTAssertEqual(m[0], 100) + + var s = m.span + XCTAssertEqual(s.count, m.count) + XCTAssertEqual(s[0], m[0]) + + // we're done using `s` before it gets reassigned + m.update(repeating: 7) + + s = m.span + +// m[0] = -1 // exclusivity violation + + XCTAssertEqual(s.count, m.count) + XCTAssertEqual(s[0], m[0]) + } + + public func testSwapAt() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..) { + s[0] += 100 + } + + public func testSliceAsExtractingFunction() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..) { + s[s.offset] += 100 + } + + public func testSliceAsWrapperType() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..: ~Copyable { let allocation: UnsafeMutablePointer let capacity: Int @@ -64,6 +65,7 @@ struct Allocation: ~Copyable { enum MyTestError: Error { case error } +@available(macOS 9999, *) final class OutputSpanTests: XCTestCase { func testOutputBufferInitialization() { @@ -82,7 +84,7 @@ final class OutputSpanTests: XCTestCase { let c = 10 a.initialize { for i in 0...c { - $0.appendElement(i) + $0.append(i) } let oops = $0.deinitializeLastElement() XCTAssertEqual(oops, c) @@ -126,7 +128,7 @@ final class OutputSpanTests: XCTestCase { a.initialize { $0.append(fromContentsOf: 0.. 0) throw MyTestError.error } @@ -224,23 +226,19 @@ final class OutputSpanTests: XCTestCase { var span = OutputSpan(_initializing: b) XCTAssertEqual(span.count, 0) - span.append(fromContentsOf: 0..<10) - XCTAssertEqual(span.count, 10) - - span.withMutableSpan { -#if false - let b = UnsafeMutableBufferPointer.allocate(capacity: 8) - b.initialize(repeating: .max) - $0 = MutableSpan(_unsafeElements: b) -#else - for i in 0..<$0.count { - $0[i] *= 2 - } -#endif + span.append(fromContentsOf: 1...9) + XCTAssertEqual(span.count, 9) + + var mutable = span.mutableSpan +// span.append(20) // exclusivity violation + for i in 0...size, MemoryLayout.size -// ) -// XCTAssertEqual( -// MemoryLayout.stride, MemoryLayout.stride -// ) -// XCTAssertEqual( -// MemoryLayout.alignment, MemoryLayout.alignment -// ) - } - func testInitWithSpanOfIntegers() { let capacity = 4 - let a = Array(0...stride) - XCTAssertFalse(span.isEmpty) + Array(0...stride) + XCTAssertFalse(span.isEmpty) + } } func testInitWithEmptySpanOfIntegers() { let a: [Int] = [] - let span = RawSpan(_elements: a.storage) - XCTAssertTrue(span.isEmpty) + a.withUnsafeBufferPointer { + let intSpan = Span(_unsafeElements: $0) + let span = RawSpan(_elements: intSpan) + XCTAssertTrue(span.isEmpty) + } } func testInitWithRawBytes() { @@ -146,10 +140,13 @@ final class RawSpanTests: XCTestCase { func testUnsafeBytes() { let capacity = 4 let array = Array(0..? bounds = span.byteOffsets(of: subSpan1) diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/FutureTests/SpanTests.swift index ae006dfa7..805ba1ccc 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests.swift @@ -11,22 +11,11 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import Future +import Future +@available(macOS 9999, *) final class SpanTests: XCTestCase { - func testOptionalStorage() { -// XCTAssertEqual( -// MemoryLayout>.size, MemoryLayout?>.size -// ) -// XCTAssertEqual( -// MemoryLayout>.stride, MemoryLayout?>.stride -// ) -// XCTAssertEqual( -// MemoryLayout>.alignment, MemoryLayout?>.alignment -// ) - } - func testInitWithOrdinaryElement() { let capacity = 4 var s = (0..(_unsafeBytes: $0) XCTAssertEqual(r.count, capacity*MemoryLayout.stride) - let p = Span( - _unsafeStart: $0.baseAddress!, - byteCount: capacity*MemoryLayout.stride + let p = $0.baseAddress! + let span = Span( + _unsafeStart: p, byteCount: capacity*MemoryLayout.stride ) - XCTAssertEqual(p.count, capacity) + XCTAssertEqual(span.count, capacity) } a.withUnsafeMutableBytes { let b = Span(_unsafeBytes: $0) XCTAssertEqual(b.count, capacity) - let p = Span( - _unsafeStart: $0.baseAddress!, - byteCount: capacity*MemoryLayout.stride + let p = $0.baseAddress! + let span = Span( + _unsafeStart: p, byteCount: capacity*MemoryLayout.stride ) - XCTAssertEqual(p.count, capacity) + XCTAssertEqual(span.count, capacity) } } @@ -101,9 +92,8 @@ final class SpanTests: XCTestCase { let span = Span(_unsafeElements: $0) XCTAssertFalse(span.isEmpty) - let empty = Span( - _unsafeElements: .init(rebasing: $0.dropFirst(capacity)) - ) + let emptyBuffer = UnsafeBufferPointer(rebasing: $0.dropFirst(capacity)) + let empty = Span(_unsafeElements: emptyBuffer) XCTAssertTrue(empty.isEmpty) } } @@ -238,19 +228,19 @@ final class SpanTests: XCTestCase { } } - func testFirstAndLast() { - let r = Int.random(in: 0..<1000) - let a = [r] - a.withUnsafeBufferPointer { - let span = Span(_unsafeElements: $0) - XCTAssertEqual(span.first, r) - XCTAssertEqual(span.last, r) - - let emptySpan = span._extracting(0..<0) - XCTAssertEqual(emptySpan.first, nil) - XCTAssertEqual(emptySpan.last, nil) - } - } +// func testFirstAndLast() { +// let r = Int.random(in: 0..<1000) +// let a = [r] +// a.withUnsafeBufferPointer { +// let span = Span(_unsafeElements: $0) +// XCTAssertEqual(span.first, r) +// XCTAssertEqual(span.last, r) +// +// let emptySpan = span._extracting(0..<0) +// XCTAssertEqual(emptySpan.first, nil) +// XCTAssertEqual(emptySpan.last, nil) +// } +// } func testPrefix() { let capacity = 4 @@ -258,10 +248,10 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(_unsafeElements: $0) XCTAssertEqual(span.count, capacity) - XCTAssertEqual(span._extracting(first: 1).last, 0) - XCTAssertEqual(span._extracting(first: capacity).last, capacity-1) - XCTAssertEqual(span._extracting(droppingLast: capacity).last, nil) - XCTAssertEqual(span._extracting(droppingLast: 1).last, capacity-2) + XCTAssertEqual(span._extracting(first: 1)[0], 0) + XCTAssertEqual(span._extracting(first: capacity)[capacity-1], capacity-1) + XCTAssertEqual(span._extracting(droppingLast: capacity).count, 0) + XCTAssertEqual(span._extracting(droppingLast: 1)[capacity-2], capacity-2) } do { @@ -279,11 +269,11 @@ final class SpanTests: XCTestCase { a.withUnsafeBufferPointer { let span = Span(_unsafeElements: $0) XCTAssertEqual(span.count, capacity) - XCTAssertEqual(span._extracting(last: capacity).first, 0) - XCTAssertEqual(span._extracting(last: capacity-1).first, 1) - XCTAssertEqual(span._extracting(last: 1).first, capacity-1) - XCTAssertEqual(span._extracting(droppingFirst: capacity).first, nil) - XCTAssertEqual(span._extracting(droppingFirst: 1).first, 1) + XCTAssertEqual(span._extracting(last: capacity)[0], 0) + XCTAssertEqual(span._extracting(last: capacity-1)[0], 1) + XCTAssertEqual(span._extracting(last: 1)[0], capacity-1) + XCTAssertEqual(span._extracting(droppingFirst: capacity).count, 0) + XCTAssertEqual(span._extracting(droppingFirst: 1)[0], 1) } do { @@ -292,7 +282,8 @@ final class SpanTests: XCTestCase { XCTAssertEqual(span.count, b.count) XCTAssertEqual(span._extracting(last: 1).count, b.count) XCTAssertEqual(span._extracting(droppingFirst: 1).count, b.count) - } } + } + } public func testWithUnsafeBytes() { let capacity: UInt8 = 64 @@ -337,22 +328,24 @@ final class SpanTests: XCTestCase { public func testBorrowing1() { let capacity = 8 - let a = Array(0..( - _unsafeElements: UnsafeBufferPointer(start: nil, count: 0) - ) + let nilBuffer = UnsafeBufferPointer(start: nil, count: 0) + let nilSpan = Span(_unsafeElements: nilBuffer) var bounds: Range? bounds = span.indices(of: subSpan1) @@ -413,27 +405,27 @@ final class SpanTests: XCTestCase { XCTAssertEqual(bounds, 0..<0) } - func testSpanIterator() { - class C { - let id: Int - init(id: Int) { self.id = id } - } - - let b = UnsafeMutableBufferPointer.allocate(capacity: 8) - _ = b.initialize(fromContentsOf: (0..<8).map(C.init(id:))) - defer { - b.deinitialize() - b.deallocate() - } - - let span = Span(_unsafeElements: b) - var iterator = Span.Iterator(from: span) - var i = 0 - while let c = iterator.next() { - XCTAssertEqual(i, c.id) - i += 1 - } - } +// func testSpanIterator() { +// class C { +// let id: Int +// init(id: Int) { self.id = id } +// } +// +// let b = UnsafeMutableBufferPointer.allocate(capacity: 8) +// _ = b.initialize(fromContentsOf: (0..<8).map(C.init(id:))) +// defer { +// b.deinitialize() +// b.deallocate() +// } +// +// let span = Span(_unsafeElements: b) +// var iterator = Span.Iterator(from: span) +// var i = 0 +// while let c = iterator.next() { +// XCTAssertEqual(i, c.id) +// i += 1 +// } +// } func testTypeErasedSpanOfBitwiseCopyable() { let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 64, alignment: 8) @@ -447,7 +439,9 @@ final class SpanTests: XCTestCase { } // let span = Span(_unsafeBytes: b.dropFirst().dropLast(7)) - let span = Span(_unsafeBytes: b.dropFirst(4)) + + let suffix = b.dropFirst(4) + let span = Span(_unsafeBytes: suffix) let first = test(span) XCTAssertEqual(first, 0x07060504) } diff --git a/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift b/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift index 25d40d8a4..9dc8e8e9f 100644 --- a/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift +++ b/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift @@ -14,13 +14,14 @@ import XCTest import Foundation import Future +@available(macOS 9999, *) final class StdlibOutputSpanExtensionTests: XCTestCase { func testArrayInitializationExample() { var array: [UInt8] array = Array(capacity: 32, initializingWith: { output in for i in 0..<(output.capacity/2) { - output.appendElement(UInt8(clamping: i)) + output.append(UInt8(clamping: i)) } }) XCTAssertEqual(array.count, 16) @@ -32,7 +33,7 @@ final class StdlibOutputSpanExtensionTests: XCTestCase { var data: Data data = Data(capacity: 32, initializingWith: { output in for i in 0..<(output.capacity/2) { - output.appendElement(UInt8(clamping: i)) + output.append(UInt8(clamping: i)) } }) XCTAssertEqual(data.count, 16) @@ -44,7 +45,7 @@ final class StdlibOutputSpanExtensionTests: XCTestCase { let c = UInt8(ascii: "A") string = String(utf8Capacity: 32, initializingWith: { output in for i in 0..<(output.capacity/2) { - output.appendElement(c + UInt8(clamping: i)) + output.append(c + UInt8(clamping: i)) } }) XCTAssertEqual(string.utf8.count, 16) diff --git a/Tests/FutureTests/StdlibSpanExtensionTests.swift b/Tests/FutureTests/StdlibSpanExtensionTests.swift deleted file mode 100644 index 006c10663..000000000 --- a/Tests/FutureTests/StdlibSpanExtensionTests.swift +++ /dev/null @@ -1,181 +0,0 @@ -//===--- StdlibSpanExtensionTests.swift -----------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import XCTest -import Foundation -import Future - -enum ErrorForTesting: Error, Equatable { case error, errorToo, errorAsWell } - -final class StdlibSpanExtensionTests: XCTestCase { - - func testDataSpan() throws { - let a = Data(0..<4) - a.withSpan { - for i in $0.indices { - XCTAssertEqual($0[i], UInt8(i)) - } - } - do throws(ErrorForTesting) { - try a.withSpan { _ throws(ErrorForTesting) in throw .error } - } catch { - XCTAssertEqual(error, .error) - } - } - - func testDataRawSpan() throws { - let a = Data(0..<4) - a.withBytes { - for i in $0.byteOffsets { - XCTAssertEqual( - $0.unsafeLoad(fromByteOffset: i, as: UInt8.self), UInt8(i) - ) - } - } - do throws(ErrorForTesting) { - try a.withBytes { _ throws(ErrorForTesting) in throw .error } - } catch { - XCTAssertEqual(error, .error) - } - } - - func testArraySpan() throws { - let a = (0..<4).map(String.init(_:)) - do throws(ErrorForTesting) { - a.withSpan { - for i in $0.indices { - XCTAssertEqual($0[i], String(i)) - } - } - try a.withSpan { _ throws(ErrorForTesting) in throw .error } - } catch { - XCTAssertEqual(error, .error) - } - } - - func testArrayRawSpan() throws { - let c = 4 - let a = Array(0...stride, as: Int.self - ) - ) - } - } - do throws(ErrorForTesting) { - try a.withBytes { _ throws(ErrorForTesting) in throw .error } - } catch { - XCTAssertEqual(error, .error) - } - } - - func testContiguousArraySpan() throws { - let a = ContiguousArray((0..<4).map(String.init(_:))) - a.withSpan { - for i in $0.indices { - XCTAssertEqual($0[i], String(i)) - } - } - do throws(ErrorForTesting) { - try a.withSpan { _ throws(ErrorForTesting) in throw .error } - } catch { - XCTAssertEqual(error, .error) - } - } - - func testContiguousArrayRawSpan() throws { - let c = 4 - let a = ContiguousArray(0...stride, as: Int.self - ) - ) - } - } - XCTAssertThrowsError(try a.withBytes({ _ in throw ErrorForTesting.error })) - } - - func testArraySliceSpan() throws { - let a = (0..<7).map(String.init(_:)).prefix(upTo: 4) - print(a.count) - a.withSpan { - for i in $0.indices { - print(i) - let v = $0[i] - _ = v - XCTAssertEqual($0[i], String(i)) - } - } - do throws(ErrorForTesting) { - try a.withSpan { _ throws(ErrorForTesting) in throw .error } - } catch { - XCTAssertEqual(error, .error) - } - } - - func testArraySliceRawSpan() throws { - let c = 4 - let a = Array(0..<7).prefix(upTo: c) - a.withBytes { - for i in 0...stride, as: Int.self - ) - ) - } - } - XCTAssertThrowsError(try a.withBytes({ _ in throw ErrorForTesting.error })) - } - - func testCollectionOfOneSpan() throws { - let a = CollectionOfOne("CollectionOfOne is an obscure Collection type.") - a.withSpan { - XCTAssertTrue($0._elementsEqual(a)) - } - XCTAssertThrowsError(try a.withSpan({ _ in throw ErrorForTesting.error })) - } - - func testCollectionOfOneRawSpan() throws { - let a = CollectionOfOne(Int(UInt8.random(in: 0 ..< .max))) - a.withBytes { - for i in $0.byteOffsets { - let v = $0.unsafeLoad(fromByteOffset: i, as: UInt8.self) - if v != 0 { - XCTAssertEqual(Int(v), a.first) - } - } - } - XCTAssertThrowsError(try a.withBytes({ _ in throw ErrorForTesting.error })) - } - - func testUTF8ViewSpan() throws { - let strings: [String] = [ - "small", - "Not a small string, if I can count code units correctly.", - NSString("legacy string if I can get it to behave.") as String - ] - for s in strings { - s.utf8.withSpan { - XCTAssertEqual($0.count, s.utf8.count) - } - } - let a = strings[0].utf8 - XCTAssertThrowsError(try a.withSpan({ _ in throw ErrorForTesting.error })) - } -} diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift index 7224be07c..554a4c935 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift @@ -15,7 +15,7 @@ import XCTest public func expectFailure( _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = (#file), line: UInt = #line ) { let message = message() diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift index 59816e677..d5eaef556 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift @@ -208,7 +208,7 @@ extension TestContext { @inline(never) public func debuggerBreak( _ message: String, - file: StaticString = #file, + file: StaticString = (#file), line: UInt = #line ) { XCTFail(message, file: file, line: line) From 8cb5b628ae5d33c33dbe8b71a813bcdb095560f6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Feb 2025 19:13:29 -0800 Subject: [PATCH 142/195] Rename module to Span --- Package.swift | 8 ++++---- Sources/DequeModule/Deque+Collection.swift | 2 +- Sources/DequeModule/Deque+Container.swift | 2 +- Sources/DequeModule/Deque+Equatable.swift | 2 +- Sources/DequeModule/Deque.swift | 2 +- Sources/DequeModule/DynamicDeque.swift | 2 +- Sources/DequeModule/RigidDeque.swift | 2 +- Sources/DequeModule/_UnsafeDequeSegments.swift | 2 +- Sources/{Future => Span}/CMakeLists.txt | 3 ++- Sources/{Future => Span}/LifetimeOverride.swift | 0 Sources/{Future => Span}/MutableRawSpan.swift | 0 Sources/{Future => Span}/MutableSpan.swift | 0 Sources/{Future => Span}/MutableSpanSlicing.swift | 0 Sources/{Future => Span}/OutputSpan.swift | 0 Sources/{Future => Span}/SpanExtensions.swift | 0 Sources/{Future => Span}/StdlibOutputSpanExtensions.swift | 0 .../{Future => Span}/UnsafeBufferPointer+Additions.swift | 0 Tests/{FutureTests => SpanTests}/MutableSpanTests.swift | 2 +- Tests/{FutureTests => SpanTests}/OutputSpanTests.swift | 2 +- Tests/{FutureTests => SpanTests}/RawSpanTests.swift | 2 +- Tests/{FutureTests => SpanTests}/SpanTests.swift | 2 +- .../StdlibOutputSpanExtensionTests.swift | 2 +- 22 files changed, 18 insertions(+), 17 deletions(-) rename Sources/{Future => Span}/CMakeLists.txt (93%) rename Sources/{Future => Span}/LifetimeOverride.swift (100%) rename Sources/{Future => Span}/MutableRawSpan.swift (100%) rename Sources/{Future => Span}/MutableSpan.swift (100%) rename Sources/{Future => Span}/MutableSpanSlicing.swift (100%) rename Sources/{Future => Span}/OutputSpan.swift (100%) rename Sources/{Future => Span}/SpanExtensions.swift (100%) rename Sources/{Future => Span}/StdlibOutputSpanExtensions.swift (100%) rename Sources/{Future => Span}/UnsafeBufferPointer+Additions.swift (100%) rename Tests/{FutureTests => SpanTests}/MutableSpanTests.swift (99%) rename Tests/{FutureTests => SpanTests}/OutputSpanTests.swift (99%) rename Tests/{FutureTests => SpanTests}/RawSpanTests.swift (99%) rename Tests/{FutureTests => SpanTests}/SpanTests.swift (99%) rename Tests/{FutureTests => SpanTests}/StdlibOutputSpanExtensionTests.swift (99%) diff --git a/Package.swift b/Package.swift index ccd0adf2c..f3de148fa 100644 --- a/Package.swift +++ b/Package.swift @@ -217,13 +217,13 @@ let targets: [CustomTarget] = [ .target( kind: .exported, - name: "Future", + name: "Span", dependencies: ["InternalCollectionsUtilities"], exclude: ["CMakeLists.txt"]), .target( kind: .test, - name: "FutureTests", - dependencies: ["Future", "_CollectionsTestSupport"]), + name: "SpanTests", + dependencies: ["Span", "_CollectionsTestSupport"]), .target( kind: .exported, @@ -240,7 +240,7 @@ let targets: [CustomTarget] = [ .target( kind: .exported, name: "DequeModule", - dependencies: ["InternalCollectionsUtilities", "Future"], + dependencies: ["InternalCollectionsUtilities", "Span"], exclude: ["CMakeLists.txt"]), .target( kind: .test, diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index adb896a8b..0e308724d 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -13,7 +13,7 @@ import InternalCollectionsUtilities #endif -import Future +import Span extension Deque { // Implementation note: we could also use the default `IndexingIterator` here. diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index f0e2fb650..0b714a24d 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if !COLLECTIONS_SINGLE_MODULE -import Future +import Span #endif extension Deque: RandomAccessContainer { diff --git a/Sources/DequeModule/Deque+Equatable.swift b/Sources/DequeModule/Deque+Equatable.swift index eb0f4709e..0c64445e3 100644 --- a/Sources/DequeModule/Deque+Equatable.swift +++ b/Sources/DequeModule/Deque+Equatable.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import Future +import Span extension Deque: Equatable where Element: Equatable { /// Returns a Boolean value indicating whether two values are equal. Two diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index 034bb806b..c52bbc0a6 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import Future +import Span /// A collection implementing a double-ended queue. `Deque` (pronounced "deck") /// implements an ordered random-access collection that supports efficient diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift index 4e6156b2b..58eb43039 100644 --- a/Sources/DequeModule/DynamicDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -11,7 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities -import Future +import Span #endif @frozen diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index 118a4290f..cdf3f3f43 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -11,7 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities -import Future +import Span #endif @frozen diff --git a/Sources/DequeModule/_UnsafeDequeSegments.swift b/Sources/DequeModule/_UnsafeDequeSegments.swift index 7ca03a592..49d5592f9 100644 --- a/Sources/DequeModule/_UnsafeDequeSegments.swift +++ b/Sources/DequeModule/_UnsafeDequeSegments.swift @@ -11,7 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities -import Future +import Span #endif @frozen diff --git a/Sources/Future/CMakeLists.txt b/Sources/Span/CMakeLists.txt similarity index 93% rename from Sources/Future/CMakeLists.txt rename to Sources/Span/CMakeLists.txt index 9761e8856..b85b0b5e3 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Span/CMakeLists.txt @@ -9,11 +9,12 @@ See https://swift.org/LICENSE.txt for license information add_library(Future "LifetimeOverride.swift" + "MutableRawSpan.swift" "MutableSpan.swift" + "MutableSpanSlicing.swift" "OutputSpan.swift" "SpanExtensions.swift" "StdlibOutputSpanExtensions.swift" - "StdlibSpanExtensions.swift" "UnsafeBufferPointer+Additions.swift" ) diff --git a/Sources/Future/LifetimeOverride.swift b/Sources/Span/LifetimeOverride.swift similarity index 100% rename from Sources/Future/LifetimeOverride.swift rename to Sources/Span/LifetimeOverride.swift diff --git a/Sources/Future/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift similarity index 100% rename from Sources/Future/MutableRawSpan.swift rename to Sources/Span/MutableRawSpan.swift diff --git a/Sources/Future/MutableSpan.swift b/Sources/Span/MutableSpan.swift similarity index 100% rename from Sources/Future/MutableSpan.swift rename to Sources/Span/MutableSpan.swift diff --git a/Sources/Future/MutableSpanSlicing.swift b/Sources/Span/MutableSpanSlicing.swift similarity index 100% rename from Sources/Future/MutableSpanSlicing.swift rename to Sources/Span/MutableSpanSlicing.swift diff --git a/Sources/Future/OutputSpan.swift b/Sources/Span/OutputSpan.swift similarity index 100% rename from Sources/Future/OutputSpan.swift rename to Sources/Span/OutputSpan.swift diff --git a/Sources/Future/SpanExtensions.swift b/Sources/Span/SpanExtensions.swift similarity index 100% rename from Sources/Future/SpanExtensions.swift rename to Sources/Span/SpanExtensions.swift diff --git a/Sources/Future/StdlibOutputSpanExtensions.swift b/Sources/Span/StdlibOutputSpanExtensions.swift similarity index 100% rename from Sources/Future/StdlibOutputSpanExtensions.swift rename to Sources/Span/StdlibOutputSpanExtensions.swift diff --git a/Sources/Future/UnsafeBufferPointer+Additions.swift b/Sources/Span/UnsafeBufferPointer+Additions.swift similarity index 100% rename from Sources/Future/UnsafeBufferPointer+Additions.swift rename to Sources/Span/UnsafeBufferPointer+Additions.swift diff --git a/Tests/FutureTests/MutableSpanTests.swift b/Tests/SpanTests/MutableSpanTests.swift similarity index 99% rename from Tests/FutureTests/MutableSpanTests.swift rename to Tests/SpanTests/MutableSpanTests.swift index 9b351bae2..a0e47e5ed 100644 --- a/Tests/FutureTests/MutableSpanTests.swift +++ b/Tests/SpanTests/MutableSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Future +import Span class ID { let id: Int diff --git a/Tests/FutureTests/OutputSpanTests.swift b/Tests/SpanTests/OutputSpanTests.swift similarity index 99% rename from Tests/FutureTests/OutputSpanTests.swift rename to Tests/SpanTests/OutputSpanTests.swift index 547037ac4..c14bd0e54 100644 --- a/Tests/FutureTests/OutputSpanTests.swift +++ b/Tests/SpanTests/OutputSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Future +import Span @available(macOS 9999, *) struct Allocation: ~Copyable { diff --git a/Tests/FutureTests/RawSpanTests.swift b/Tests/SpanTests/RawSpanTests.swift similarity index 99% rename from Tests/FutureTests/RawSpanTests.swift rename to Tests/SpanTests/RawSpanTests.swift index d5edf3811..834251262 100644 --- a/Tests/FutureTests/RawSpanTests.swift +++ b/Tests/SpanTests/RawSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import Future +@testable import Span @available(macOS 9999, *) final class RawSpanTests: XCTestCase { diff --git a/Tests/FutureTests/SpanTests.swift b/Tests/SpanTests/SpanTests.swift similarity index 99% rename from Tests/FutureTests/SpanTests.swift rename to Tests/SpanTests/SpanTests.swift index 805ba1ccc..853602bff 100644 --- a/Tests/FutureTests/SpanTests.swift +++ b/Tests/SpanTests/SpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Future +import Span @available(macOS 9999, *) final class SpanTests: XCTestCase { diff --git a/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift b/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift similarity index 99% rename from Tests/FutureTests/StdlibOutputSpanExtensionTests.swift rename to Tests/SpanTests/StdlibOutputSpanExtensionTests.swift index 9dc8e8e9f..b8508e7d8 100644 --- a/Tests/FutureTests/StdlibOutputSpanExtensionTests.swift +++ b/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation -import Future +import Span @available(macOS 9999, *) final class StdlibOutputSpanExtensionTests: XCTestCase { From 0131e7aae225f48fdd23ba60643bcac09dede810 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 24 Feb 2025 09:03:59 -0800 Subject: [PATCH 143/195] small fixes --- Sources/Span/MutableRawSpan.swift | 16 +++++++++++++--- Sources/Span/MutableSpan.swift | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index 7b38f5355..245a96f8f 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -137,15 +137,25 @@ extension MutableRawSpan { } } +@available(macOS 9999, *) +extension RawSpan { + + @_alwaysEmitIntoClient + @lifetime(borrow mutableSpan) + public init(_unsafeMutableRawSpan mutableSpan: borrowing MutableRawSpan) { + let start = mutableSpan._start() + let span = RawSpan(_unsafeStart: start, byteCount: mutableSpan.byteCount) + self = _overrideLifetime(span, borrowing: mutableSpan) + } +} + @available(macOS 9999, *) extension MutableRawSpan { public var bytes: RawSpan { @lifetime(borrow self) borrowing get { - let start = _start() - let span = RawSpan(_unsafeStart: start, byteCount: byteCount) - return _overrideLifetime(span, borrowing: self) + return RawSpan(_unsafeMutableRawSpan: self) } } diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index dfa709cbc..c0001eb1e 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -145,6 +145,7 @@ extension MutableSpan where Element: BitwiseCopyable { extension Span where Element: ~Copyable { @_alwaysEmitIntoClient + @lifetime(borrow mutableSpan) public init(_unsafeMutableSpan mutableSpan: borrowing MutableSpan) { let pointer = mutableSpan._pointer?.assumingMemoryBound(to: Element.self) let buffer = UnsafeBufferPointer(start: pointer, count: mutableSpan.count) From e352bf057cca4152c26898a87c04a30c90fdfed2 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 24 Feb 2025 09:11:03 -0800 Subject: [PATCH 144/195] adjust copyright years --- Sources/Span/MutableSpanSlicing.swift | 12 ++++++++++++ Sources/Span/StdlibOutputSpanExtensions.swift | 2 +- Tests/SpanTests/MutableSpanTests.swift | 2 +- Tests/SpanTests/OutputSpanTests.swift | 2 +- Tests/SpanTests/RawSpanTests.swift | 2 +- Tests/SpanTests/SpanTests.swift | 2 +- Tests/SpanTests/StdlibOutputSpanExtensionTests.swift | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Sources/Span/MutableSpanSlicing.swift b/Sources/Span/MutableSpanSlicing.swift index dbd6c8094..17ed2415e 100644 --- a/Sources/Span/MutableSpanSlicing.swift +++ b/Sources/Span/MutableSpanSlicing.swift @@ -1,3 +1,15 @@ +//===--- MutableSpanSlicing.swift -----------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + //MARK: Option 1 extracting() function @available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { diff --git a/Sources/Span/StdlibOutputSpanExtensions.swift b/Sources/Span/StdlibOutputSpanExtensions.swift index decf5d294..35a6e4ce3 100644 --- a/Sources/Span/StdlibOutputSpanExtensions.swift +++ b/Sources/Span/StdlibOutputSpanExtensions.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 diff --git a/Tests/SpanTests/MutableSpanTests.swift b/Tests/SpanTests/MutableSpanTests.swift index a0e47e5ed..8c76c2c70 100644 --- a/Tests/SpanTests/MutableSpanTests.swift +++ b/Tests/SpanTests/MutableSpanTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 diff --git a/Tests/SpanTests/OutputSpanTests.swift b/Tests/SpanTests/OutputSpanTests.swift index c14bd0e54..b24a0986b 100644 --- a/Tests/SpanTests/OutputSpanTests.swift +++ b/Tests/SpanTests/OutputSpanTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 diff --git a/Tests/SpanTests/RawSpanTests.swift b/Tests/SpanTests/RawSpanTests.swift index 834251262..51be5871b 100644 --- a/Tests/SpanTests/RawSpanTests.swift +++ b/Tests/SpanTests/RawSpanTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 diff --git a/Tests/SpanTests/SpanTests.swift b/Tests/SpanTests/SpanTests.swift index 853602bff..bacc56307 100644 --- a/Tests/SpanTests/SpanTests.swift +++ b/Tests/SpanTests/SpanTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 diff --git a/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift b/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift index b8508e7d8..a8f883226 100644 --- a/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift +++ b/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 From 5b81ead26c8ee6728c4b81938ec4e55d652c7997 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 22 Feb 2025 05:37:49 -0800 Subject: [PATCH 145/195] simpler slicing --- Sources/Span/MutableSpanSlicing.swift | 63 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/Sources/Span/MutableSpanSlicing.swift b/Sources/Span/MutableSpanSlicing.swift index 17ed2415e..e6e4fb33a 100644 --- a/Sources/Span/MutableSpanSlicing.swift +++ b/Sources/Span/MutableSpanSlicing.swift @@ -21,15 +21,39 @@ extension MutableSpan where Element: ~Copyable { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - return extracting(unchecked: bounds) + let newSpan = MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) + return _overrideLifetime(newSpan, mutating: &self) } +} - @lifetime(borrow self) - public mutating func extracting(unchecked bounds: Range) -> Self { +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + @_alwaysEmitIntoClient + @lifetime(borrow pointer) + fileprivate init( + _unchecked pointer: UnsafeMutableRawPointer, + uncheckedBounds bounds: Range + ) { let delta = bounds.lowerBound &* MemoryLayout.stride - let newStart = _pointer?.advanced(by: delta) - let newSpan = MutableSpan(_unchecked: newStart, count: bounds.count) - return _overrideLifetime(newSpan, mutating: &self) + let newStart = pointer.advanced(by: delta) + let newSpan = Self( + _unchecked: newStart, count: bounds.upperBound &- bounds.lowerBound + ) + self = _overrideLifetime(newSpan, borrowing: pointer) + } +} + +@available(macOS 9999, *) +extension Span where Element: ~Copyable { + @_alwaysEmitIntoClient + @lifetime(borrow pointer) + fileprivate init( + _unchecked pointer: UnsafeMutableRawPointer, + uncheckedBounds bounds: Range + ) { + let mut = MutableSpan(_unchecked: pointer, uncheckedBounds: bounds) + let newSpan = mut.span + self = _overrideLifetime(newSpan, borrowing: pointer) } } @@ -45,16 +69,7 @@ extension MutableSpan where Element: ~Copyable { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - return self[extractingUnchecked: bounds] - } - } - - public subscript(extractingUnchecked bounds: Range) -> Self { - @lifetime(borrow self) - mutating get { - let delta = bounds.lowerBound &* MemoryLayout.stride - let newStart = _pointer?.advanced(by: delta) - let newSpan = MutableSpan(_unchecked: newStart, count: bounds.count) + let newSpan = MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) return _overrideLifetime(newSpan, mutating: &self) } } @@ -94,10 +109,9 @@ extension SubMutableSpan where Element: ~Copyable { public var span: Span { @lifetime(borrow self) borrowing get { - let delta = offset &* MemoryLayout.stride - let newStart = base._pointer?.advanced(by: delta).assumingMemoryBound(to: Element.self) - let buffer = UnsafeBufferPointer(start: newStart, count: count) - let newSpan = Span(_unsafeElements: buffer) + let newSpan = Span( + _unchecked: base._start(), uncheckedBounds: offset..( - start: _start().assumingMemoryBound(to: Element.self), - count: bounds.upperBound + let prefix = Self( + _unchecked: _start(), uncheckedBounds: 0..(_unsafeElements: prefixBuffer) - var subSpan = SubMutableSpan(bounds.lowerBound, prefix) - subSpan.offset = bounds.lowerBound + let subSpan = SubMutableSpan(bounds.lowerBound, prefix) return _overrideLifetime(subSpan, mutating: &self) } } From f2aea0f8e6eff0e0a6bbc352ecd945acea911786 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 24 Feb 2025 09:11:13 -0800 Subject: [PATCH 146/195] separate slicing tests --- Tests/SpanTests/MutableSpanSlicing.swift | 144 +++++++++++++++++++++++ Tests/SpanTests/MutableSpanTests.swift | 130 -------------------- 2 files changed, 144 insertions(+), 130 deletions(-) create mode 100644 Tests/SpanTests/MutableSpanSlicing.swift diff --git a/Tests/SpanTests/MutableSpanSlicing.swift b/Tests/SpanTests/MutableSpanSlicing.swift new file mode 100644 index 000000000..60cfe4bdb --- /dev/null +++ b/Tests/SpanTests/MutableSpanSlicing.swift @@ -0,0 +1,144 @@ +//===--- MutableSpanSlicing.swift -----------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Span + +//MARK: slicing tests and examples +@available(macOS 9999, *) +extension MutableSpanTests { + + private func modify(_ s: inout MutableSpan) { + s[0] += 100 + } + + public func testSliceAsExtractingFunction() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..) { + s[s.offset] += 100 + } + + public func testSliceAsWrapperType() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..) { - s[0] += 100 - } - - public func testSliceAsExtractingFunction() { - let count = 8 - let b = UnsafeMutableBufferPointer.allocate(capacity: count) - _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) - _ = b.initialize(fromContentsOf: 0..) { - s[s.offset] += 100 - } - - public func testSliceAsWrapperType() { - let count = 8 - let b = UnsafeMutableBufferPointer.allocate(capacity: count) - _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) - _ = b.initialize(fromContentsOf: 0.. Date: Wed, 26 Feb 2025 16:31:33 -0800 Subject: [PATCH 147/195] update MutableSpan tests --- Tests/SpanTests/MutableSpanTests.swift | 101 +++++++++---------------- 1 file changed, 36 insertions(+), 65 deletions(-) diff --git a/Tests/SpanTests/MutableSpanTests.swift b/Tests/SpanTests/MutableSpanTests.swift index 2e413eabb..eadfb81a5 100644 --- a/Tests/SpanTests/MutableSpanTests.swift +++ b/Tests/SpanTests/MutableSpanTests.swift @@ -46,59 +46,21 @@ final class MutableSpanTests: XCTestCase { } a.withUnsafeMutableBytes { - let b = MutableSpan(_unsafeBytes: $0) + let (rp, bc) = ($0.baseAddress!, $0.count) + let b = MutableSpan(_unsafeStart: rp, byteCount: bc) XCTAssertEqual(b.count, capacity) - let r = MutableSpan(_unsafeBytes: $0) - XCTAssertEqual(r.count, capacity*MemoryLayout.stride) + let stride = MemoryLayout.stride + let r = MutableSpan(_unsafeBytes: $0.dropFirst(stride)) + XCTAssertEqual(r.count, (capacity-1)*stride) + XCTAssertEqual(r.count, bc-stride) } -// let v = UnsafeRawBufferPointer(start: nil, count: 0) -// XCTAssertNil(MutableSpan(unsafeRawBufferPointer: v)) + let v = UnsafeMutableRawBufferPointer(start: nil, count: 0) + let m = MutableSpan(_unsafeBytes: v) + XCTAssertEqual(m.count, 0) } -// public func testIteratorOrdinaryElement() { -// let capacity = 4 -// var s = (0...stride) -// let bytes = count*MemoryLayout.stride + offset -// let align = MemoryLayout.alignment -// let p = UnsafeMutableRawPointer.allocate(byteCount: bytes, alignment: align) -// defer { p.deallocate() } -// for i in 0..(_unsafeStart: p+offset, byteCount: count*8) -// -// var buffered = 0 -// for value in 0...stride) } @@ -162,14 +125,14 @@ final class MutableSpanTests: XCTestCase { XCTAssertEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 3)._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(last: 3)), false) v1[0] = 0 -// -// let s = v1.storage -// var b = s.withUnsafeBufferPointer { Array($0) } -// b.withUnsafeMutableBufferPointer { -// let v2 = MutableSpan(_unsafeElements: $0) -// let equal = v1._elementsEqual(v2) -// XCTAssertEqual(equal, true) -// } + + let s = v1.span + var b = s.withUnsafeBufferPointer { Array($0) } + b.withUnsafeMutableBufferPointer { + let v2 = MutableSpan(_unsafeElements: $0) + let equal = v1._elementsEqual(v2) + XCTAssertEqual(equal, true) + } } } @@ -225,7 +188,7 @@ final class MutableSpanTests: XCTestCase { XCTAssertEqual(v[0], first) v[0].append("!") - XCTAssertEqual(v[0], first?.appending("!")) + XCTAssertEqual(v[0], first.map({$0+"!"})) } } @@ -408,13 +371,16 @@ final class MutableSpanTests: XCTestCase { a.withUnsafeMutableBufferPointer { let emptyPrefix = $0.prefix(0) var span = MutableSpan(_unsafeElements: emptyPrefix) - var update = span.update(fromContentsOf: []) + let update = span.update(fromContentsOf: []) XCTAssertEqual(update, 0) span = MutableSpan(_unsafeElements: $0) - let array = Array(0.. Date: Wed, 26 Feb 2025 16:31:43 -0800 Subject: [PATCH 148/195] add missing annotations --- Sources/Span/MutableSpan.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index c0001eb1e..f890227f7 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -510,6 +510,7 @@ extension MutableSpan where Element: ~Copyable { return buffer.count } + @_alwaysEmitIntoClient public mutating func moveUpdate( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index { @@ -521,6 +522,7 @@ extension MutableSpan where Element: ~Copyable { @available(macOS 9999, *) extension MutableSpan { + @_alwaysEmitIntoClient public mutating func moveUpdate( fromContentsOf source: Slice> ) -> Index { From a9fdaef1ad25a27847a331da3cab9831cea89be1 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 26 Feb 2025 16:48:54 -0800 Subject: [PATCH 149/195] update test of swapAt() --- Tests/SpanTests/MutableSpanTests.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Tests/SpanTests/MutableSpanTests.swift b/Tests/SpanTests/MutableSpanTests.swift index eadfb81a5..bcab9470b 100644 --- a/Tests/SpanTests/MutableSpanTests.swift +++ b/Tests/SpanTests/MutableSpanTests.swift @@ -481,15 +481,14 @@ final class MutableSpanTests: XCTestCase { public func testSwapAt() { let count = 8 - let b = UnsafeMutableBufferPointer.allocate(capacity: count) - _ = b.initialize(fromContentsOf: 0.. Date: Thu, 27 Feb 2025 03:02:58 -0800 Subject: [PATCH 150/195] fix a parameter name --- Sources/Span/MutableRawSpan.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index 245a96f8f..c78d39a80 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -30,10 +30,10 @@ public struct MutableRawSpan: ~Copyable & ~Escapable { @lifetime(borrow pointer) init( _unchecked pointer: UnsafeMutableRawPointer?, - count: Int + byteCount: Int ) { _pointer = pointer - _count = count + _count = byteCount } } @@ -48,7 +48,7 @@ extension MutableRawSpan { _unsafeBytes bytes: UnsafeMutableRawBufferPointer ) { let baseAddress = bytes.baseAddress - let span = MutableRawSpan(_unchecked: baseAddress, count: bytes.count) + let span = MutableRawSpan(_unchecked: baseAddress, byteCount: bytes.count) self = _overrideLifetime(span, borrowing: bytes) } @@ -67,7 +67,7 @@ extension MutableRawSpan { byteCount: Int ) { precondition(byteCount >= 0, "Count must not be negative") - self.init(_unchecked: pointer, count: byteCount) + self.init(_unchecked: pointer, byteCount: byteCount) } @lifetime(borrow elements) From 6808d4f0b51478716cb2ea9a4a82e9a4482fb261 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 27 Feb 2025 03:03:32 -0800 Subject: [PATCH 151/195] add extracting functions to MutableSpan and MutableRawSpan --- Sources/Span/MutableRawSpan.swift | 229 +++++++++++++++++++++++++++++ Sources/Span/MutableSpan.swift | 234 ++++++++++++++++++++++++++++++ 2 files changed, 463 insertions(+) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index c78d39a80..c3291a5d2 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -400,3 +400,232 @@ extension MutableRawSpan { update(startingAt: byteOffset, from: source.bytes) } } + +// MARK: sub-spans +@available(macOS 9999, *) +extension MutableRawSpan { + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_ bounds: Range) -> Self { + precondition( + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "Index range out of bounds" + ) + return _extracting(unchecked: bounds) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(unchecked bounds: Range) -> Self { + let newStart = _pointer?.advanced(by: bounds.lowerBound) + let newSpan = Self(_unchecked: newStart, byteCount: bounds.count) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_ bounds: some RangeExpression) -> Self { + _extracting(bounds.relative(to: byteOffsets).clamped(to: byteOffsets)) + } + + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_ bounds: ClosedRange) -> Self { + let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) + return _extracting(range) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting( + unchecked bounds: some RangeExpression + ) -> Self { + _extracting( + unchecked: bounds.relative(to: byteOffsets).clamped(to: byteOffsets) + ) + } + + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(unchecked bounds: ClosedRange) -> Self { + let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) + return _extracting(unchecked: range) + } + + /// Constructs a new span over all the items of this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Returns: A `MutableSpan` over all the items of this span. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_: UnboundedRange) -> Self { + let newSpan = Self(_unchecked: _start(), byteCount: _count) + return _overrideLifetime(newSpan, mutating: &self) + } +} + +// MARK: prefixes and suffixes +@available(macOS 9999, *) +extension MutableRawSpan { + + /// Returns a span containing the initial elements of this span, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the elements. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = min(maxLength, byteCount) + let newSpan = Self(_unchecked: _pointer, byteCount: newCount) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Returns a span over all but the given number of trailing elements. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter k: The number of elements to drop off the end of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span leaving off the specified number of elements at the end. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(droppingLast k: Int) -> Self { + precondition(k >= 0, "Can't drop a negative number of elements") + let dropped = min(k, byteCount) + let newSpan = Self(_unchecked: _pointer, byteCount: byteCount &- dropped) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Returns a span containing the final elements of the span, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the elements. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = min(maxLength, byteCount) + let newStart = _pointer?.advanced(by: byteCount &- newCount) + let newSpan = Self(_unchecked: newStart, byteCount: newCount) + return _overrideLifetime(newSpan, copying: self) + } + + /// Returns a span over all but the given number of initial elements. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter k: The number of elements to drop from the beginning of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span starting after the specified number of elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(droppingFirst k: Int) -> Self { + precondition(k >= 0, "Can't drop a negative number of bytes") + let dropped = min(k, byteCount) + let newStart = _pointer?.advanced(by: dropped) + let newSpan = Self(_unchecked: newStart, byteCount: byteCount &- dropped) + return _overrideLifetime(newSpan, mutating: &self) + } +} diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index f890227f7..99834aae4 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -614,3 +614,237 @@ extension MutableSpan where Element: BitwiseCopyable { update(fromContentsOf: source.span) } } + +// MARK: sub-spans +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_ bounds: Range) -> Self { + precondition( + UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && + UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), + "Index range out of bounds" + ) + return _extracting(unchecked: bounds) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(unchecked bounds: Range) -> Self { + let delta = bounds.lowerBound &* MemoryLayout.stride + let newStart = _pointer?.advanced(by: delta) + let newSpan = Self(_unchecked: newStart, count: bounds.count) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting( + _ bounds: some RangeExpression + ) -> Self { + _extracting(bounds.relative(to: indices).clamped(to: indices)) + } + + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_ bounds: ClosedRange) -> Self { + let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) + return _extracting(range) + } + + /// Constructs a new span over the items within the supplied range of + /// positions within this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// This function does not validate `bounds`; this is an unsafe operation. + /// + /// - Parameter bounds: A valid range of positions. Every position in + /// this range must be within the bounds of this `MutableSpan`. + /// + /// - Returns: A `MutableSpan` over the items within `bounds` + /// + /// - Complexity: O(1) + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting( + unchecked bounds: some RangeExpression + ) -> Self { + _extracting(unchecked: bounds.relative(to: indices).clamped(to: indices)) + } + + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting( + unchecked bounds: ClosedRange + ) -> Self { + let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) + return _extracting(unchecked: range) + } + + /// Constructs a new span over all the items of this span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Returns: A `MutableSpan` over all the items of this span. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(_: UnboundedRange) -> Self { + let newSpan = Self(_unchecked: _start(), count: _count) + return _overrideLifetime(newSpan, mutating: &self) + } +} + +// MARK: prefixes and suffixes +@available(macOS 9999, *) +extension MutableSpan where Element: ~Copyable { + + /// Returns a span containing the initial elements of this span, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the elements. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = min(maxLength, count) + let newSpan = Self(_unchecked: _pointer, count: newCount) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Returns a span over all but the given number of trailing elements. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter k: The number of elements to drop off the end of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span leaving off the specified number of elements at the end. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(droppingLast k: Int) -> Self { + precondition(k >= 0, "Can't drop a negative number of elements") + let droppedCount = min(k, count) + let newSpan = Self(_unchecked: _pointer, count: count &- droppedCount) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Returns a span containing the final elements of the span, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this span, + /// the result contains all the elements. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A span with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = min(maxLength, count) + let offset = (count &- newCount) * MemoryLayout.stride + let newStart = _pointer?.advanced(by: offset) + let newSpan = Self(_unchecked: newStart, count: newCount) + return _overrideLifetime(newSpan, mutating: &self) + } + + /// Returns a span over all but the given number of initial elements. + /// + /// If the number of elements to drop exceeds the number of elements in + /// the span, the result is an empty span. + /// + /// The returned span's first item is always at offset 0; unlike buffer + /// slices, extracted spans do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter k: The number of elements to drop from the beginning of + /// the span. `k` must be greater than or equal to zero. + /// - Returns: A span starting after the specified number of elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating public func _extracting(droppingFirst k: Int) -> Self { + precondition(k >= 0, "Can't drop a negative number of elements") + let droppedCount = min(k, count) + let offset = droppedCount * MemoryLayout.stride + let newStart = _pointer?.advanced(by: offset) + let newSpan = Self(_unchecked: newStart, count: count &- droppedCount) + return _overrideLifetime(newSpan, mutating: &self) + } +} From f6eff9d7f7541cee398b9a079683d00ec78ac392 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 27 Feb 2025 21:09:22 -0800 Subject: [PATCH 152/195] Update OutputSpan --- Sources/Span/OutputSpan.swift | 69 ++++++++++------------------------- 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/Sources/Span/OutputSpan.swift b/Sources/Span/OutputSpan.swift index 413a4c49b..9bb2a8b6c 100644 --- a/Sources/Span/OutputSpan.swift +++ b/Sources/Span/OutputSpan.swift @@ -15,15 +15,18 @@ @frozen @available(macOS 9999, *) public struct OutputSpan: ~Copyable, ~Escapable { - @usableFromInline let _pointer: UnsafeMutableRawPointer? + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? public let capacity: Int @usableFromInline - var _initialized: Int = 0 + internal var _initialized: Int = 0 - @usableFromInline @inline(__always) - var _start: UnsafeMutableRawPointer { _pointer.unsafelyUnwrapped } + @_alwaysEmitIntoClient + internal func _start() -> UnsafeMutableRawPointer { + _pointer.unsafelyUnwrapped + } @_alwaysEmitIntoClient public var available: Int { capacity &- _initialized } @@ -36,16 +39,16 @@ public struct OutputSpan: ~Copyable, ~Escapable { deinit { if _initialized > 0 { - _start.withMemoryRebound(to: Element.self, capacity: _initialized) { + _start().withMemoryRebound(to: Element.self, capacity: _initialized) { [ workaround = _initialized ] in _ = $0.deinitialize(count: workaround) } } } - @usableFromInline @inline(__always) @lifetime(borrow start) - init( + @_alwaysEmitIntoClient + internal init( _unchecked start: UnsafeMutableRawPointer?, capacity: Int, initialized: Int @@ -65,7 +68,7 @@ extension OutputSpan where Element: ~Copyable { @usableFromInline @inline(__always) @lifetime(borrow buffer) - init( + internal init( _unchecked buffer: UnsafeMutableBufferPointer, initialized: Int ) { @@ -172,7 +175,7 @@ extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient public mutating func append(_ value: consuming Element) { precondition(_initialized < capacity, "Output buffer overflow") - let p = _start.advanced(by: _initialized&*MemoryLayout.stride) + let p = _start().advanced(by: _initialized&*MemoryLayout.stride) p.initializeMemory(as: Element.self, to: value) _initialized &+= 1 } @@ -181,13 +184,13 @@ extension OutputSpan where Element: ~Copyable { public mutating func deinitializeLastElement() -> Element? { guard _initialized > 0 else { return nil } _initialized &-= 1 - let p = _start.advanced(by: _initialized&*MemoryLayout.stride) + let p = _start().advanced(by: _initialized&*MemoryLayout.stride) return p.withMemoryRebound(to: Element.self, capacity: 1, { $0.move() }) } @_alwaysEmitIntoClient public mutating func deinitialize() { - _ = _start.withMemoryRebound(to: Element.self, capacity: _initialized) { + _ = _start().withMemoryRebound(to: Element.self, capacity: _initialized) { $0.deinitialize(count: _initialized) } _initialized = 0 @@ -206,7 +209,7 @@ extension OutputSpan { "destination span cannot contain number of elements requested." ) let offset = _initialized&*MemoryLayout.stride - let p = _start.advanced(by: offset) + let p = _start().advanced(by: offset) p.withMemoryRebound(to: Element.self, capacity: count) { $0.initialize(repeating: repeatedValue, count: count) } @@ -228,7 +231,7 @@ extension OutputSpan { ) { while _initialized < capacity { guard let element = elements.next() else { break } - let p = _start.advanced(by: _initialized&*MemoryLayout.stride) + let p = _start().advanced(by: _initialized&*MemoryLayout.stride) p.initializeMemory(as: Element.self, to: element) _initialized &+= 1 } @@ -239,18 +242,14 @@ extension OutputSpan { fromContentsOf source: some Collection ) { let void: Void? = source.withContiguousStorageIfAvailable { -#if false append(fromContentsOf: Span(_unsafeElements: $0)) -#else //FIXME: remove once rdar://136838539 & rdar://136849171 are fixed - append(fromContentsOf: $0) -#endif } if void != nil { return } let available = capacity &- _initialized - let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + let tail = _start().advanced(by: _initialized&*MemoryLayout.stride) var (iterator, copied) = tail.withMemoryRebound(to: Element.self, capacity: available) { let suffix = UnsafeMutableBufferPointer(start: $0, count: available) @@ -264,23 +263,6 @@ extension OutputSpan { _initialized &+= copied } - //FIXME: remove once rdar://136838539 & rdar://136849171 are fixed - public mutating func append( - fromContentsOf source: UnsafeBufferPointer - ) { - guard !source.isEmpty else { return } - precondition( - source.count <= available, - "destination span cannot contain every element from source." - ) - let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) - source.baseAddress!.withMemoryRebound(to: Element.self, capacity: source.count) { - _ = tail.initializeMemory(as: Element.self, from: $0, count: source.count) - } - _initialized += source.count - } - - //FIXME: rdar://136838539 & rdar://136849171 @_alwaysEmitIntoClient public mutating func append( fromContentsOf source: Span @@ -290,7 +272,7 @@ extension OutputSpan { source.count <= available, "destination span cannot contain every element from source." ) - let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + let tail = _start().advanced(by: _initialized&*MemoryLayout.stride) _ = source.withUnsafeBufferPointer { tail.initializeMemory( as: Element.self, from: $0.baseAddress!, count: $0.count @@ -319,7 +301,7 @@ extension OutputSpan where Element: ~Copyable { ) let buffer = source.relinquishBorrowedMemory() // we must now deinitialize the returned UMBP - let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) + let tail = _start().advanced(by: _initialized&*MemoryLayout.stride) tail.moveInitializeMemory( as: Element.self, from: buffer.baseAddress!, count: buffer.count ) @@ -330,21 +312,8 @@ extension OutputSpan where Element: ~Copyable { public mutating func moveAppend( fromContentsOf source: UnsafeMutableBufferPointer ) { -#if false //FIXME: rdar://136838539 & rdar://136849171 let source = OutputSpan(_initializing: source, initialized: source.count) moveAppend(fromContentsOf: source) -#else - guard !source.isEmpty else { return } - precondition( - source.count <= available, - "buffer cannot contain every element from source." - ) - let tail = _start.advanced(by: _initialized&*MemoryLayout.stride) - tail.moveInitializeMemory( - as: Element.self, from: source.baseAddress!, count: source.count - ) - _initialized &+= source.count -#endif } } From 5d5384c4620abd3dafc80a893ea403bed9b911a5 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 27 Feb 2025 21:09:38 -0800 Subject: [PATCH 153/195] update OutputSpanTests --- Tests/SpanTests/OutputSpanTests.swift | 42 ++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/Tests/SpanTests/OutputSpanTests.swift b/Tests/SpanTests/OutputSpanTests.swift index b24a0986b..883fc6c34 100644 --- a/Tests/SpanTests/OutputSpanTests.swift +++ b/Tests/SpanTests/OutputSpanTests.swift @@ -68,7 +68,7 @@ enum MyTestError: Error { case error } @available(macOS 9999, *) final class OutputSpanTests: XCTestCase { - func testOutputBufferInitialization() { + func testOutputBufferCreation() { let c = 48 let allocation = UnsafeMutablePointer.allocate(capacity: c) defer { allocation.deallocate() } @@ -79,6 +79,17 @@ final class OutputSpanTests: XCTestCase { XCTAssertEqual(initialized.count, 0) } + func testOutputBufferDeinitWithoutRelinquishingMemory() { + let c = 48 + let allocation = UnsafeMutableBufferPointer.allocate(capacity: c) + defer { allocation.deallocate() } + + var ob = OutputSpan(_initializing: Slice(base: allocation, bounds: 0..(_initializing: allocation[8...]) + let r = Int16.max>>2 + ob.append(r) + _ = ob.relinquishBorrowedBytes() + + let o = allocation.load(fromByteOffset: 8, as: Int16.self) + XCTAssertEqual(o, r) + } + func testInitializeBufferByAppendingRepeatedElements() { var a = Allocation(of: 48, Int.self) let c = 10 @@ -154,16 +180,12 @@ final class OutputSpanTests: XCTestCase { var a = Allocation(of: 48, Int.self) let c = 24 a.initialize { - let array = Array(0..(_unsafeElements: .init(start: nil, count: 0)) - $0.append(fromContentsOf: storage) + os in + var array = Array(0.. Date: Thu, 27 Feb 2025 21:10:43 -0800 Subject: [PATCH 154/195] add a note --- Sources/Span/OutputSpan.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Span/OutputSpan.swift b/Sources/Span/OutputSpan.swift index 9bb2a8b6c..6b8420b76 100644 --- a/Sources/Span/OutputSpan.swift +++ b/Sources/Span/OutputSpan.swift @@ -330,7 +330,7 @@ extension OutputSpan { @available(macOS 9999, *) extension OutputSpan where Element: BitwiseCopyable { - +// TODO: alternative append() implementations for BitwiseCopyable elements } @available(macOS 9999, *) From 4334415fc0f7d1f5594e60661d7d3d27af4559e9 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 28 Feb 2025 09:12:24 -0800 Subject: [PATCH 155/195] annotation cleanup --- Sources/Span/OutputSpan.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Span/OutputSpan.swift b/Sources/Span/OutputSpan.swift index 6b8420b76..61251abc2 100644 --- a/Sources/Span/OutputSpan.swift +++ b/Sources/Span/OutputSpan.swift @@ -46,8 +46,8 @@ public struct OutputSpan: ~Copyable, ~Escapable { } } - @lifetime(borrow start) @_alwaysEmitIntoClient + @lifetime(borrow start) internal init( _unchecked start: UnsafeMutableRawPointer?, capacity: Int, @@ -66,7 +66,7 @@ extension OutputSpan: Sendable {} @available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { - @usableFromInline @inline(__always) + @_alwaysEmitIntoClient @lifetime(borrow buffer) internal init( _unchecked buffer: UnsafeMutableBufferPointer, From 81699ed60f30c9e8524ed52463154d2f2d8af497 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 28 Feb 2025 10:54:22 -0800 Subject: [PATCH 156/195] test tweaks --- Tests/SpanTests/RawSpanTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SpanTests/RawSpanTests.swift b/Tests/SpanTests/RawSpanTests.swift index 51be5871b..874af7546 100644 --- a/Tests/SpanTests/RawSpanTests.swift +++ b/Tests/SpanTests/RawSpanTests.swift @@ -104,7 +104,7 @@ final class RawSpanTests: XCTestCase { } } - func testSubscript() { + func testExtracting() { let capacity = 4 let b = (0.. Date: Tue, 4 Mar 2025 16:39:35 -0800 Subject: [PATCH 157/195] `storeBytes()` must be `mutating` --- Sources/Span/MutableRawSpan.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index c3291a5d2..f3bc49f7d 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -295,7 +295,7 @@ extension MutableRawSpan { } @_alwaysEmitIntoClient - public func storeBytes( + public mutating func storeBytes( of value: T, toByteOffset offset: Int = 0, as type: T.Type ) { precondition( @@ -308,7 +308,7 @@ extension MutableRawSpan { @unsafe @_alwaysEmitIntoClient - public func storeBytes( + public mutating func storeBytes( of value: T, toUncheckedByteOffset offset: Int, as type: T.Type ) { _start().storeBytes(of: value, toByteOffset: offset, as: type) From 91526b4e513742ee42a623b5ec55c491cb347807 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 4 Mar 2025 16:40:30 -0800 Subject: [PATCH 158/195] update bulk-copy operations for MutableRawSpan --- Sources/Span/MutableRawSpan.swift | 45 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index f3bc49f7d..a50b7a0a6 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -319,20 +319,20 @@ extension MutableRawSpan { @available(macOS 9999, *) extension MutableRawSpan { + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, from source: S ) -> (unwritten: S.Iterator, byteOffset: Int) where S.Element: BitwiseCopyable { var iterator = source.makeIterator() - let offset = update(startingAt: byteOffset, from: &iterator) + let offset = update(from: &iterator) return (iterator, offset) } + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, from elements: inout some IteratorProtocol ) -> Int { - var offset = byteOffset + var offset = 0 while offset + MemoryLayout.stride <= _count { guard let element = elements.next() else { break } storeBytes(of: element, toUncheckedByteOffset: offset, as: Element.self) @@ -341,19 +341,17 @@ extension MutableRawSpan { return offset } + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, fromContentsOf source: C ) -> Int where C.Element: BitwiseCopyable { let newOffset = source.withContiguousStorageIfAvailable { - self.update( - startingAt: byteOffset, fromContentsOf: Span(_unsafeElements: $0) - ) + self.update(fromContentsOf: RawSpan(_unsafeElements: $0)) } if let newOffset { return newOffset } var elements = source.makeIterator() - let lastOffset = update(startingAt: byteOffset, from: &elements) + let lastOffset = update(from: &elements) precondition( elements.next() == nil, "destination span cannot contain every element from source." @@ -361,43 +359,42 @@ extension MutableRawSpan { return lastOffset } + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, fromContentsOf source: Span ) -> Int { -// update(startingAt: byteOffset, from: source.bytes) +// update(from: source.bytes) source.withUnsafeBytes { - update(startingAt: byteOffset, fromContentsOf: $0) + update(fromContentsOf: $0) } } + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, fromContentsOf source: borrowing MutableSpan ) -> Int { -// update(startingAt: byteOffset, from: source.storage.bytes) +// update(from: source.span.bytes) source.withUnsafeBytes { - update(startingAt: byteOffset, fromContentsOf: $0) + update(fromContentsOf: $0) } } + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, - from source: RawSpan + fromContentsOf source: RawSpan ) -> Int { - if source.byteCount == 0 { return byteOffset } + if source.byteCount == 0 { return 0 } source.withUnsafeBytes { - _start().advanced(by: byteOffset) - .copyMemory(from: $0.baseAddress!, byteCount: $0.count) + _start().copyMemory(from: $0.baseAddress!, byteCount: $0.count) } - return byteOffset &+ source.byteCount + return source.byteCount } + @_alwaysEmitIntoClient public mutating func update( - startingAt byteOffset: Int = 0, - from source: borrowing MutableRawSpan + fromContentsOf source: borrowing MutableRawSpan ) -> Int { - update(startingAt: byteOffset, from: source.bytes) + update(fromContentsOf: source.bytes) } } From e299604433ac5385559a53b68ecd21f9c8100881 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 4 Mar 2025 16:40:46 -0800 Subject: [PATCH 159/195] trim the set of `extracting()` functions --- Sources/Span/MutableRawSpan.swift | 17 ----------------- Sources/Span/MutableSpan.swift | 15 --------------- 2 files changed, 32 deletions(-) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index a50b7a0a6..e626be832 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -469,13 +469,6 @@ extension MutableRawSpan { _extracting(bounds.relative(to: byteOffsets).clamped(to: byteOffsets)) } - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(_ bounds: ClosedRange) -> Self { - let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) - return _extracting(range) - } - /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -492,16 +485,6 @@ extension MutableRawSpan { /// /// - Complexity: O(1) @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting( - unchecked bounds: some RangeExpression - ) -> Self { - _extracting( - unchecked: bounds.relative(to: byteOffsets).clamped(to: byteOffsets) - ) - } - @_alwaysEmitIntoClient @lifetime(borrow self) mutating public func _extracting(unchecked bounds: ClosedRange) -> Self { diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index 99834aae4..5c9ca4c6f 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -689,13 +689,6 @@ extension MutableSpan where Element: ~Copyable { _extracting(bounds.relative(to: indices).clamped(to: indices)) } - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(_ bounds: ClosedRange) -> Self { - let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) - return _extracting(range) - } - /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -712,14 +705,6 @@ extension MutableSpan where Element: ~Copyable { /// /// - Complexity: O(1) @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting( - unchecked bounds: some RangeExpression - ) -> Self { - _extracting(unchecked: bounds.relative(to: indices).clamped(to: indices)) - } - @_alwaysEmitIntoClient @lifetime(borrow self) mutating public func _extracting( From 0b4c812e0f42f1299bb1c632e3210377fa8e268e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 24 Feb 2025 09:04:46 -0800 Subject: [PATCH 160/195] add `MutableRawSpan` tests --- Tests/SpanTests/MutableRawSpanTests.swift | 356 ++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 Tests/SpanTests/MutableRawSpanTests.swift diff --git a/Tests/SpanTests/MutableRawSpanTests.swift b/Tests/SpanTests/MutableRawSpanTests.swift new file mode 100644 index 000000000..2dd1a4367 --- /dev/null +++ b/Tests/SpanTests/MutableRawSpanTests.swift @@ -0,0 +1,356 @@ +//===--- MutableSpanTests.swift -------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Span + +@available(macOS 9999, *) +final class MutableRawSpanTests: XCTestCase { + + func testBasicInitializer() { + var s = Array("\(#file)+\(#function)--\(Int.random(in: 1000...9999))".utf8) + s.withUnsafeMutableBytes { + let b = MutableRawSpan(_unsafeBytes: $0) + XCTAssertEqual(b.byteCount, $0.count) + XCTAssertFalse(b.isEmpty) + XCTAssertEqual(b.byteOffsets, 0..<$0.count) + } + } + + func testIsEmpty() { + var array = [0, 1, 2] + array.withUnsafeMutableBufferPointer { + var span = MutableRawSpan(_unsafeElements: $0) + XCTAssertFalse(span.isEmpty) + + let e = $0.extracting(0..<0) + span = MutableRawSpan(_unsafeElements: e) + XCTAssertTrue(span.isEmpty) + } + } + + func testIndices() { + let capacity = 4 as UInt8 + var a = Array(0...stride + + let s0 = span.unsafeLoad(as: String.self) + XCTAssertEqual(s0.contains("0"), true) + let s1 = span.unsafeLoad(fromByteOffset: stride, as: String.self) + XCTAssertEqual(s1.contains("1"), true) + let s2 = span.unsafeLoad(fromUncheckedByteOffset: 2*stride, as: String.self) + XCTAssertEqual(s2.contains("2"), true) + } + } + + func testLoadUnaligned() { + let capacity = 64 + var a = Array(0...stride/2, as: UInt.self + ) + } + XCTAssertEqual(a[0].littleEndian & 0xffff, 0xff) + XCTAssertEqual(a[0].bigEndian & 0xffff, 0xffff) + } + + public func testUpdateFromSequence() { + let capacity = 8 + var a = Array(repeating: Int.max, count: capacity) + XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) + a.withUnsafeMutableBufferPointer { + let empty = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableRawSpan(_unsafeElements: empty) + var (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + span = MutableRawSpan(_unsafeElements: $0) + (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + (iterator, updated) = span.update(from: 0..<10000) + XCTAssertNotNil(iterator.next()) + XCTAssertEqual(updated, capacity*MemoryLayout.stride) + } + XCTAssertEqual(a.elementsEqual(0..()) + XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) + a.withUnsafeMutableBytes { + let emptyPrefix = $0.prefix(0) + var span = MutableRawSpan(_unsafeBytes: emptyPrefix) + var updated = span.update(fromContentsOf: e) + XCTAssertEqual(updated, 0) + + + updated = span.update(fromContentsOf: AnyCollection(e)) + XCTAssertEqual(updated, 0) + + span = MutableRawSpan(_unsafeBytes: $0) + updated = span.update(fromContentsOf: 0...stride) + } + XCTAssertEqual(a.elementsEqual(0...stride) + } + XCTAssertEqual(a.elementsEqual(0...stride) + } + } + XCTAssertEqual(a.allSatisfy({ $0 == Int.min }), true) + + a.withUnsafeMutableBytes { + var span = MutableRawSpan(_unsafeBytes: $0) + let array = Array(0...stride) + } + } + XCTAssertEqual(a.elementsEqual(0.. Date: Tue, 4 Mar 2025 17:00:34 -0800 Subject: [PATCH 161/195] complete `MutableSpan` tests --- Tests/SpanTests/MutableSpanTests.swift | 99 ++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/Tests/SpanTests/MutableSpanTests.swift b/Tests/SpanTests/MutableSpanTests.swift index bcab9470b..a830fcdd0 100644 --- a/Tests/SpanTests/MutableSpanTests.swift +++ b/Tests/SpanTests/MutableSpanTests.swift @@ -491,4 +491,103 @@ final class MutableSpanTests: XCTestCase { XCTAssertEqual(array, (0.. + var span = MutableSpan(_unsafeElements: $0) + XCTAssertEqual(span.count, capacity) + + prefix = span._extracting(first: 1) + XCTAssertEqual(prefix[0], 0) + + prefix = span._extracting(first: capacity) + XCTAssertEqual(prefix[capacity-1], UInt8(capacity-1)) + + prefix = span._extracting(droppingLast: capacity) + XCTAssertEqual(prefix.isEmpty, true) + + prefix = span._extracting(droppingLast: 1) + XCTAssertEqual(prefix[capacity-2], UInt8(capacity-2)) + } + + do { + let b = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableSpan(_unsafeElements: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span._extracting(first: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingLast: 1).count, b.count) + } + } + + func testSuffix() { + let capacity = 4 + var a = Array(0.. + var span = MutableSpan(_unsafeElements: $0) + XCTAssertEqual(span.count, capacity) + + suffix = span._extracting(last: capacity) + XCTAssertEqual(suffix[0], 0) + + suffix = span._extracting(last: capacity-1) + XCTAssertEqual(suffix[0], 1) + + suffix = span._extracting(last: 1) + XCTAssertEqual(suffix[0], UInt8(capacity-1)) + + suffix = span._extracting(droppingFirst: capacity) + XCTAssertTrue(suffix.isEmpty) + + suffix = span._extracting(droppingFirst: 1) + XCTAssertEqual(suffix[0], 1) + } + + do { + let b = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableSpan(_unsafeElements: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span._extracting(last: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingFirst: 1).count, b.count) + } + } } From c37a48dff1b2d1eb801fd84aabfcb4c0462971ff Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 4 Mar 2025 17:41:55 -0800 Subject: [PATCH 162/195] enable strict memory safety --- Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index f3de148fa..540dbfc3c 100644 --- a/Package.swift +++ b/Package.swift @@ -219,7 +219,9 @@ let targets: [CustomTarget] = [ kind: .exported, name: "Span", dependencies: ["InternalCollectionsUtilities"], - exclude: ["CMakeLists.txt"]), + exclude: ["CMakeLists.txt"], + settings: _sharedSettings + [.unsafeFlags(["-Xfrontend", "-strict-memory-safety"])], + ), .target( kind: .test, name: "SpanTests", From cd48e83c46e2f1e4f73143443ae831692fed3292 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 4 Mar 2025 17:42:17 -0800 Subject: [PATCH 163/195] add required unsafe annotations to `MutableSpan` --- Sources/Span/MutableSpan.swift | 179 +++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 77 deletions(-) diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index 5c9ca4c6f..9e0ba01a7 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -14,26 +14,29 @@ import Builtin // A MutableSpan represents a span of memory which // contains initialized `Element` instances. +@safe @frozen @available(macOS 9999, *) public struct MutableSpan : ~Copyable, ~Escapable { - @usableFromInline let _pointer: UnsafeMutableRawPointer? + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? - @usableFromInline let _count: Int + @usableFromInline + internal let _count: Int @_alwaysEmitIntoClient internal func _start() -> UnsafeMutableRawPointer { - _pointer.unsafelyUnwrapped + unsafe _pointer.unsafelyUnwrapped } - @usableFromInline @inline(__always) + @_alwaysEmitIntoClient @lifetime(borrow start) - init( + internal init( _unchecked start: UnsafeMutableRawPointer?, count: Int ) { - _pointer = start + _pointer = unsafe start _count = count } } @@ -44,7 +47,7 @@ extension MutableSpan: @unchecked Sendable where Element: Sendable {} @available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { - @usableFromInline @inline(__always) + @usableFromInline @lifetime(borrow elements) internal init( _unchecked elements: UnsafeMutableBufferPointer @@ -58,13 +61,13 @@ extension MutableSpan where Element: ~Copyable { public init( _unsafeElements buffer: UnsafeMutableBufferPointer ) { - precondition( + unsafe precondition( ((Int(bitPattern: buffer.baseAddress) & - (MemoryLayout.alignment&-1)) == 0), + (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" ) let ms = MutableSpan(_unchecked: buffer) - self = _overrideLifetime(ms, borrowing: buffer) + self = unsafe _overrideLifetime(ms, borrowing: buffer) } @_alwaysEmitIntoClient @@ -74,9 +77,9 @@ extension MutableSpan where Element: ~Copyable { count: Int ) { precondition(count >= 0, "Count must not be negative") - let buffer = UnsafeMutableBufferPointer(start: start, count: count) + let buffer = unsafe UnsafeMutableBufferPointer(start: start, count: count) let ms = MutableSpan(_unsafeElements: buffer) - self = _overrideLifetime(ms, borrowing: start) + self = unsafe _overrideLifetime(ms, borrowing: start) } } @@ -88,9 +91,9 @@ extension MutableSpan { public init( _unsafeElements elements: borrowing Slice> ) { - let rb = UnsafeMutableBufferPointer(rebasing: elements) + let rb = unsafe UnsafeMutableBufferPointer(rebasing: elements) let ms = MutableSpan(_unsafeElements: rb) - self = _overrideLifetime(ms, borrowing: elements) + self = unsafe _overrideLifetime(ms, borrowing: elements) } } @@ -102,20 +105,20 @@ extension MutableSpan where Element: BitwiseCopyable { public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer ) { - precondition( + unsafe precondition( ((Int(bitPattern: buffer.baseAddress) & - (MemoryLayout.alignment&-1)) == 0), + (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" ) let (byteCount, stride) = (buffer.count, MemoryLayout.stride) let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0, "Span must contain a whole number of elements") - let elements = UnsafeMutableBufferPointer( + let elements = unsafe UnsafeMutableBufferPointer( start: buffer.baseAddress?.assumingMemoryBound(to: Element.self), count: count ) let ms = MutableSpan(_unsafeElements: elements) - self = _overrideLifetime(ms, borrowing: buffer) + self = unsafe _overrideLifetime(ms, borrowing: buffer) } @_alwaysEmitIntoClient @@ -125,9 +128,11 @@ extension MutableSpan where Element: BitwiseCopyable { byteCount: Int ) { precondition(byteCount >= 0, "Count must not be negative") - let bytes = UnsafeMutableRawBufferPointer(start: pointer, count: byteCount) + let bytes = unsafe UnsafeMutableRawBufferPointer( + start: pointer, count: byteCount + ) let ms = MutableSpan(_unsafeBytes: bytes) - self = _overrideLifetime(ms, borrowing: pointer) + self = unsafe _overrideLifetime(ms, borrowing: pointer) } @_alwaysEmitIntoClient @@ -135,9 +140,9 @@ extension MutableSpan where Element: BitwiseCopyable { public init( _unsafeBytes buffer: borrowing Slice ) { - let bytes = UnsafeMutableRawBufferPointer(rebasing: buffer) + let bytes = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) let ms = MutableSpan(_unsafeBytes: bytes) - self = _overrideLifetime(ms, borrowing: buffer) + self = unsafe _overrideLifetime(ms, borrowing: buffer) } } @@ -147,10 +152,13 @@ extension Span where Element: ~Copyable { @_alwaysEmitIntoClient @lifetime(borrow mutableSpan) public init(_unsafeMutableSpan mutableSpan: borrowing MutableSpan) { - let pointer = mutableSpan._pointer?.assumingMemoryBound(to: Element.self) - let buffer = UnsafeBufferPointer(start: pointer, count: mutableSpan.count) + let pointer = + unsafe mutableSpan._pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeBufferPointer( + start: pointer, count: mutableSpan.count + ) let span = Span(_unsafeElements: buffer) - self = _overrideLifetime(span, borrowing: mutableSpan) + self = unsafe _overrideLifetime(span, borrowing: mutableSpan) } } @@ -175,9 +183,9 @@ extension RawSpan { ) { let pointer = mutableSpan._pointer let byteCount = mutableSpan.count &* MemoryLayout.stride - let buffer = UnsafeRawBufferPointer(start: pointer, count: byteCount) + let buffer = unsafe UnsafeRawBufferPointer(start: pointer, count: byteCount) let rawSpan = RawSpan(_unsafeBytes: buffer) - self = _overrideLifetime(rawSpan, borrowing: mutableSpan) + self = unsafe _overrideLifetime(rawSpan, borrowing: mutableSpan) } } @@ -210,7 +218,9 @@ extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient public var _description: String { - let addr = String(UInt(bitPattern: _pointer), radix: 16, uppercase: false) + let addr = String( + unsafe UInt(bitPattern: _pointer), radix: 16, uppercase: false + ) return "(0x\(addr), \(_count))" } } @@ -229,7 +239,7 @@ extension MutableSpan where Element: ~Copyable & ~Escapable { @_alwaysEmitIntoClient public var indices: Range { - Range(uncheckedBounds: (0, _count)) + unsafe Range(uncheckedBounds: (0, _count)) } } @@ -261,11 +271,11 @@ extension MutableSpan where Element: ~Copyable { public subscript(_ position: Index) -> Element { unsafeAddress { precondition(indices.contains(position), "index out of bounds") - return UnsafePointer(_unsafeAddressOfElement(unchecked: position)) + return unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: position)) } unsafeMutableAddress { precondition(indices.contains(position), "index out of bounds") - return _unsafeAddressOfElement(unchecked: position) + return unsafe _unsafeAddressOfElement(unchecked: position) } } @@ -277,13 +287,14 @@ extension MutableSpan where Element: ~Copyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { unsafeAddress { - UnsafePointer(_unsafeAddressOfElement(unchecked: position)) + unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: position)) } unsafeMutableAddress { - _unsafeAddressOfElement(unchecked: position) + unsafe _unsafeAddressOfElement(unchecked: position) } } @@ -293,8 +304,8 @@ extension MutableSpan where Element: ~Copyable { unchecked position: Index ) -> UnsafeMutablePointer { let elementOffset = position &* MemoryLayout.stride - let address = _start().advanced(by: elementOffset) - return address.assumingMemoryBound(to: Element.self) + let address = unsafe _start().advanced(by: elementOffset) + return unsafe address.assumingMemoryBound(to: Element.self) } } @@ -305,16 +316,17 @@ extension MutableSpan where Element: ~Copyable { public mutating func swapAt(_ i: Index, _ j: Index) { precondition(indices.contains(Index(i))) precondition(indices.contains(Index(j))) - swapAt(unchecked: i, unchecked: j) + unsafe swapAt(unchecked: i, unchecked: j) } + @unsafe @_alwaysEmitIntoClient public mutating func swapAt(unchecked i: Index, unchecked j: Index) { - let pi = _unsafeAddressOfElement(unchecked: i) - let pj = _unsafeAddressOfElement(unchecked: j) - let temporary = pi.move() - pi.initialize(to: pj.move()) - pj.initialize(to: consume temporary) + let pi = unsafe _unsafeAddressOfElement(unchecked: i) + let pj = unsafe _unsafeAddressOfElement(unchecked: j) + let temporary = unsafe pi.move() + unsafe pi.initialize(to: pj.move()) + unsafe pj.initialize(to: consume temporary) } } @@ -331,11 +343,11 @@ extension MutableSpan where Element: BitwiseCopyable { public subscript(_ position: Index) -> Element { get { precondition(indices.contains(position), "index out of bounds") - return self[unchecked: position] + return unsafe self[unchecked: position] } set { precondition(indices.contains(position), "index out of bounds") - self[unchecked: position] = newValue + unsafe self[unchecked: position] = newValue } } @@ -347,15 +359,20 @@ extension MutableSpan where Element: BitwiseCopyable { /// must be greater or equal to zero, and less than `count`. /// /// - Complexity: O(1) + @unsafe @_alwaysEmitIntoClient public subscript(unchecked position: Index) -> Element { get { let offset = position&*MemoryLayout.stride - return _start().loadUnaligned(fromByteOffset: offset, as: Element.self) + return unsafe _start().loadUnaligned( + fromByteOffset: offset, as: Element.self + ) } set { let offset = position&*MemoryLayout.stride - _start().storeBytes(of: newValue, toByteOffset: offset, as: Element.self) + unsafe _start().storeBytes( + of: newValue, toByteOffset: offset, as: Element.self + ) } } } @@ -373,18 +390,20 @@ extension MutableSpan where Element: ~Copyable { //FIXME: mark closure parameter as non-escaping @_alwaysEmitIntoClient - public mutating func withUnsafeMutableBufferPointer( + public mutating func withUnsafeMutableBufferPointer< + E: Error, Result: ~Copyable + >( _ body: (UnsafeMutableBufferPointer) throws(E) -> Result ) throws(E) -> Result { guard let pointer = _pointer, count > 0 else { - return try body(.init(start: nil, count: 0)) + return try unsafe body(.init(start: nil, count: 0)) } // bind memory by hand to sidestep alignment concerns let binding = Builtin.bindMemory( pointer._rawValue, count._builtinWordValue, Element.self ) defer { Builtin.rebindMemory(pointer._rawValue, binding) } - return try body(.init(start: .init(pointer._rawValue), count: count)) + return try unsafe body(.init(start: .init(pointer._rawValue), count: count)) } } @@ -404,11 +423,11 @@ extension MutableSpan where Element: BitwiseCopyable { public mutating func withUnsafeMutableBytes( _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { - let bytes = UnsafeMutableRawBufferPointer( + let bytes = unsafe UnsafeMutableRawBufferPointer( start: (_count == 0) ? nil : _start(), count: _count &* MemoryLayout.stride ) - return try body(bytes) + return try unsafe body(bytes) } } @@ -418,8 +437,8 @@ extension MutableSpan { @_alwaysEmitIntoClient public mutating func update(repeating repeatedValue: consuming Element) { - _start().withMemoryRebound(to: Element.self, capacity: count) { - $0.update(repeating: repeatedValue, count: count) + unsafe _start().withMemoryRebound(to: Element.self, capacity: count) { + unsafe $0.update(repeating: repeatedValue, count: count) } } @@ -439,7 +458,7 @@ extension MutableSpan { var index = 0 while index < _count { guard let element = elements.next() else { break } - self[unchecked: index] = element + unsafe self[unchecked: index] = element index &+= 1 } return index @@ -474,9 +493,11 @@ extension MutableSpan { source.count <= self.count, "destination span cannot contain every element from source." ) - _start().withMemoryRebound(to: Element.self, capacity: source.count) { dest in + unsafe _start().withMemoryRebound( + to: Element.self, capacity: source.count + ) { dest in source.withUnsafeBufferPointer { - dest.update(from: $0.baseAddress!, count: $0.count) + unsafe dest.update(from: $0.baseAddress!, count: $0.count) } } return source.count @@ -502,9 +523,9 @@ extension MutableSpan where Element: ~Copyable { source.count <= self.count, "destination span cannot contain every element from source." ) - let buffer = source.relinquishBorrowedMemory() + let buffer = unsafe source.relinquishBorrowedMemory() // we must now deinitialize the returned UMBP - _start().moveInitializeMemory( + unsafe _start().moveInitializeMemory( as: Element.self, from: buffer.baseAddress!, count: buffer.count ) return buffer.count @@ -514,8 +535,8 @@ extension MutableSpan where Element: ~Copyable { public mutating func moveUpdate( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index { - let source = OutputSpan(_initializing: source, initialized: source.count) - return self.moveUpdate(fromContentsOf: source) + let s = unsafe OutputSpan(_initializing: source, initialized: source.count) + return self.moveUpdate(fromContentsOf: s) } } @@ -526,7 +547,7 @@ extension MutableSpan { public mutating func moveUpdate( fromContentsOf source: Slice> ) -> Index { - self.moveUpdate(fromContentsOf: .init(rebasing: source)) + self.moveUpdate(fromContentsOf: unsafe .init(rebasing: source)) } } @@ -541,7 +562,8 @@ extension MutableSpan where Element: BitwiseCopyable { // rebind _start manually in order to avoid assumptions about alignment. let rp = _start()._rawValue let binding = Builtin.bindMemory(rp, count._builtinWordValue, Element.self) - UnsafeMutablePointer(rp).update(repeating: repeatedValue, count: count) + let rebound = unsafe UnsafeMutablePointer(rp) + unsafe rebound.update(repeating: repeatedValue, count: count) Builtin.rebindMemory(rp, binding) } @@ -562,7 +584,7 @@ extension MutableSpan where Element: BitwiseCopyable { var index = 0 while index < _count { guard let element = elements.next() else { break } - self[unchecked: index] = element + unsafe self[unchecked: index] = element index &+= 1 } return index @@ -600,8 +622,9 @@ extension MutableSpan where Element: BitwiseCopyable { "destination span cannot contain every element from source." ) source.withUnsafeBufferPointer { - _start().copyMemory( - from: $0.baseAddress!, byteCount: $0.count&*MemoryLayout.stride + unsafe _start().copyMemory( + from: $0.baseAddress!, + byteCount: $0.count &* MemoryLayout.stride ) } return source.count @@ -640,7 +663,7 @@ extension MutableSpan where Element: ~Copyable { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - return _extracting(unchecked: bounds) + return unsafe _extracting(unchecked: bounds) } /// Constructs a new span over the items within the supplied range of @@ -663,9 +686,9 @@ extension MutableSpan where Element: ~Copyable { @lifetime(borrow self) mutating public func _extracting(unchecked bounds: Range) -> Self { let delta = bounds.lowerBound &* MemoryLayout.stride - let newStart = _pointer?.advanced(by: delta) + let newStart = unsafe _pointer?.advanced(by: delta) let newSpan = Self(_unchecked: newStart, count: bounds.count) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Constructs a new span over the items within the supplied range of @@ -686,7 +709,7 @@ extension MutableSpan where Element: ~Copyable { mutating public func _extracting( _ bounds: some RangeExpression ) -> Self { - _extracting(bounds.relative(to: indices).clamped(to: indices)) + _extracting(bounds.relative(to: indices)) } /// Constructs a new span over the items within the supplied range of @@ -710,8 +733,10 @@ extension MutableSpan where Element: ~Copyable { mutating public func _extracting( unchecked bounds: ClosedRange ) -> Self { - let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) - return _extracting(unchecked: range) + let range = unsafe Range( + uncheckedBounds: (bounds.lowerBound, bounds.upperBound&+1) + ) + return unsafe _extracting(unchecked: range) } /// Constructs a new span over all the items of this span. @@ -727,7 +752,7 @@ extension MutableSpan where Element: ~Copyable { @lifetime(borrow self) mutating public func _extracting(_: UnboundedRange) -> Self { let newSpan = Self(_unchecked: _start(), count: _count) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } } @@ -756,7 +781,7 @@ extension MutableSpan where Element: ~Copyable { precondition(maxLength >= 0, "Can't have a prefix of negative length") let newCount = min(maxLength, count) let newSpan = Self(_unchecked: _pointer, count: newCount) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Returns a span over all but the given number of trailing elements. @@ -779,7 +804,7 @@ extension MutableSpan where Element: ~Copyable { precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, count) let newSpan = Self(_unchecked: _pointer, count: count &- droppedCount) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Returns a span containing the final elements of the span, @@ -803,9 +828,9 @@ extension MutableSpan where Element: ~Copyable { precondition(maxLength >= 0, "Can't have a suffix of negative length") let newCount = min(maxLength, count) let offset = (count &- newCount) * MemoryLayout.stride - let newStart = _pointer?.advanced(by: offset) + let newStart = unsafe _pointer?.advanced(by: offset) let newSpan = Self(_unchecked: newStart, count: newCount) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Returns a span over all but the given number of initial elements. @@ -828,8 +853,8 @@ extension MutableSpan where Element: ~Copyable { precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, count) let offset = droppedCount * MemoryLayout.stride - let newStart = _pointer?.advanced(by: offset) + let newStart = unsafe _pointer?.advanced(by: offset) let newSpan = Self(_unchecked: newStart, count: count &- droppedCount) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } } From eaecf56fbe8a32a911f01c468ae31e03c2f39d7c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 5 Mar 2025 10:05:18 -0800 Subject: [PATCH 164/195] add required unsafe annotations to `MutableRawSpan` --- Sources/Span/MutableRawSpan.swift | 109 ++++++++++++++++++------------ 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Span/MutableRawSpan.swift index e626be832..3a2770a14 100644 --- a/Sources/Span/MutableRawSpan.swift +++ b/Sources/Span/MutableRawSpan.swift @@ -12,6 +12,7 @@ // A MutableRawSpan represents a span of memory which // contains initialized `Element` instances. +@safe @frozen @available(macOS 9999, *) public struct MutableRawSpan: ~Copyable & ~Escapable { @@ -23,16 +24,16 @@ public struct MutableRawSpan: ~Copyable & ~Escapable { @_alwaysEmitIntoClient internal func _start() -> UnsafeMutableRawPointer { - _pointer.unsafelyUnwrapped + unsafe _pointer.unsafelyUnwrapped } - @usableFromInline @inline(__always) + @_alwaysEmitIntoClient @lifetime(borrow pointer) - init( + internal init( _unchecked pointer: UnsafeMutableRawPointer?, byteCount: Int ) { - _pointer = pointer + _pointer = unsafe pointer _count = byteCount } } @@ -43,24 +44,27 @@ extension MutableRawSpan: @unchecked Sendable {} @available(macOS 9999, *) extension MutableRawSpan { + @_alwaysEmitIntoClient @lifetime(borrow bytes) public init( _unsafeBytes bytes: UnsafeMutableRawBufferPointer ) { let baseAddress = bytes.baseAddress let span = MutableRawSpan(_unchecked: baseAddress, byteCount: bytes.count) - self = _overrideLifetime(span, borrowing: bytes) + self = unsafe _overrideLifetime(span, borrowing: bytes) } + @_alwaysEmitIntoClient @lifetime(borrow bytes) public init( _unsafeBytes bytes: borrowing Slice ) { - let rebased = UnsafeMutableRawBufferPointer(rebasing: bytes) + let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: bytes) let span = MutableRawSpan(_unsafeBytes: rebased) - self = _overrideLifetime(span, borrowing: bytes) + self = unsafe _overrideLifetime(span, borrowing: bytes) } + @_alwaysEmitIntoClient @lifetime(borrow pointer) public init( _unsafeStart pointer: UnsafeMutableRawPointer, @@ -70,34 +74,37 @@ extension MutableRawSpan { self.init(_unchecked: pointer, byteCount: byteCount) } + @_alwaysEmitIntoClient @lifetime(borrow elements) public init( _unsafeElements elements: UnsafeMutableBufferPointer ) { let bytes = UnsafeMutableRawBufferPointer(elements) let span = MutableRawSpan(_unsafeBytes: bytes) - self = _overrideLifetime(span, borrowing: elements) + self = unsafe _overrideLifetime(span, borrowing: elements) } + @_alwaysEmitIntoClient @lifetime(borrow elements) public init( _unsafeElements elements: borrowing Slice> ) { - let rebased = UnsafeMutableBufferPointer(rebasing: elements) + let rebased = unsafe UnsafeMutableBufferPointer(rebasing: elements) let span = MutableRawSpan(_unsafeElements: rebased) - self = _overrideLifetime(span, borrowing: elements) + self = unsafe _overrideLifetime(span, borrowing: elements) } + @_alwaysEmitIntoClient @lifetime(elements) public init( _elements elements: consuming MutableSpan ) { - let bytes = UnsafeMutableRawBufferPointer( + let bytes = unsafe UnsafeMutableRawBufferPointer( start: elements._pointer, count: elements.count &* MemoryLayout.stride ) let span = MutableRawSpan(_unsafeBytes: bytes) - self = _overrideLifetime(span, copying: elements) + self = unsafe _overrideLifetime(span, copying: elements) } } @@ -111,29 +118,31 @@ extension MutableRawSpan { @_alwaysEmitIntoClient public var byteOffsets: Range { - .init(uncheckedBounds: (0, byteCount)) + unsafe Range(uncheckedBounds: (0, byteCount)) } } @available(macOS 9999, *) extension MutableRawSpan { + @_alwaysEmitIntoClient public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { guard let pointer = _pointer, _count > 0 else { - return try body(.init(start: nil, count: 0)) + return try unsafe body(.init(start: nil, count: 0)) } - return try body(.init(start: pointer, count: _count)) + return try unsafe body(.init(start: pointer, count: _count)) } + @_alwaysEmitIntoClient public mutating func withUnsafeMutableBytes( _ body: (UnsafeMutableRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { guard let pointer = _pointer, _count > 0 else { - return try body(.init(start: nil, count: 0)) + return try unsafe body(.init(start: nil, count: 0)) } - return try body(.init(start: pointer, count: _count)) + return try unsafe body(.init(start: pointer, count: _count)) } } @@ -145,7 +154,7 @@ extension RawSpan { public init(_unsafeMutableRawSpan mutableSpan: borrowing MutableRawSpan) { let start = mutableSpan._start() let span = RawSpan(_unsafeStart: start, byteCount: mutableSpan.byteCount) - self = _overrideLifetime(span, borrowing: mutableSpan) + self = unsafe _overrideLifetime(span, borrowing: mutableSpan) } } @@ -153,6 +162,7 @@ extension RawSpan { extension MutableRawSpan { public var bytes: RawSpan { + @_alwaysEmitIntoClient @lifetime(borrow self) borrowing get { return RawSpan(_unsafeMutableRawSpan: self) @@ -160,30 +170,33 @@ extension MutableRawSpan { } @unsafe + @_alwaysEmitIntoClient @lifetime(borrow self) public borrowing func _unsafeView( as type: T.Type ) -> Span { - let bytes = UnsafeRawBufferPointer(start: _pointer, count: _count) + let bytes = unsafe UnsafeRawBufferPointer(start: _pointer, count: _count) let span = Span(_unsafeBytes: bytes) - return _overrideLifetime(span, borrowing: self) + return unsafe _overrideLifetime(span, borrowing: self) } @unsafe + @_alwaysEmitIntoClient @lifetime(borrow self) public mutating func _unsafeMutableView( as type: T.Type ) -> MutableSpan { - let bytes = UnsafeMutableRawBufferPointer(start: _pointer, count: _count) + let bytes = unsafe UnsafeMutableRawBufferPointer( + start: _pointer, count: _count + ) let span = MutableSpan(_unsafeBytes: bytes) - return _overrideLifetime(span, mutating: &self) + return unsafe _overrideLifetime(span, mutating: &self) } } @available(macOS 9999, *) extension MutableRawSpan { - /// Returns a new instance of the given type, constructed from the raw memory /// at the specified offset. /// @@ -211,7 +224,7 @@ extension MutableRawSpan { MemoryLayout.size <= (_count &- offset), "Byte offset range out of bounds" ) - return unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) + return unsafe unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) } /// Returns a new instance of the given type, constructed from the raw memory @@ -237,7 +250,7 @@ extension MutableRawSpan { public func unsafeLoad( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { - _start().load(fromByteOffset: offset, as: T.self) + unsafe _start().load(fromByteOffset: offset, as: T.self) } /// Returns a new instance of the given type, constructed from the raw memory @@ -266,7 +279,7 @@ extension MutableRawSpan { MemoryLayout.size <= (_count &- offset), "Byte offset range out of bounds" ) - return unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) + return unsafe unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) } /// Returns a new instance of the given type, constructed from the raw memory @@ -291,7 +304,7 @@ extension MutableRawSpan { public func unsafeLoadUnaligned( fromUncheckedByteOffset offset: Int, as: T.Type ) -> T { - _start().loadUnaligned(fromByteOffset: offset, as: T.self) + unsafe _start().loadUnaligned(fromByteOffset: offset, as: T.self) } @_alwaysEmitIntoClient @@ -303,7 +316,7 @@ extension MutableRawSpan { MemoryLayout.size <= (_count &- offset), "Byte offset range out of bounds" ) - storeBytes(of: value, toUncheckedByteOffset: offset, as: type) + unsafe storeBytes(of: value, toUncheckedByteOffset: offset, as: type) } @unsafe @@ -311,7 +324,7 @@ extension MutableRawSpan { public mutating func storeBytes( of value: T, toUncheckedByteOffset offset: Int, as type: T.Type ) { - _start().storeBytes(of: value, toByteOffset: offset, as: type) + unsafe _start().storeBytes(of: value, toByteOffset: offset, as: type) } } @@ -335,7 +348,9 @@ extension MutableRawSpan { var offset = 0 while offset + MemoryLayout.stride <= _count { guard let element = elements.next() else { break } - storeBytes(of: element, toUncheckedByteOffset: offset, as: Element.self) + unsafe storeBytes( + of: element, toUncheckedByteOffset: offset, as: Element.self + ) offset &+= MemoryLayout.stride } return offset @@ -385,7 +400,7 @@ extension MutableRawSpan { ) -> Int { if source.byteCount == 0 { return 0 } source.withUnsafeBytes { - _start().copyMemory(from: $0.baseAddress!, byteCount: $0.count) + unsafe _start().copyMemory(from: $0.baseAddress!, byteCount: $0.count) } return source.byteCount } @@ -423,7 +438,7 @@ extension MutableRawSpan { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - return _extracting(unchecked: bounds) + return unsafe _extracting(unchecked: bounds) } /// Constructs a new span over the items within the supplied range of @@ -445,9 +460,9 @@ extension MutableRawSpan { @_alwaysEmitIntoClient @lifetime(borrow self) mutating public func _extracting(unchecked bounds: Range) -> Self { - let newStart = _pointer?.advanced(by: bounds.lowerBound) + let newStart = unsafe _pointer?.advanced(by: bounds.lowerBound) let newSpan = Self(_unchecked: newStart, byteCount: bounds.count) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Constructs a new span over the items within the supplied range of @@ -465,8 +480,10 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(borrow self) - mutating public func _extracting(_ bounds: some RangeExpression) -> Self { - _extracting(bounds.relative(to: byteOffsets).clamped(to: byteOffsets)) + mutating public func _extracting( + _ bounds: some RangeExpression + ) -> Self { + _extracting(bounds.relative(to: byteOffsets)) } /// Constructs a new span over the items within the supplied range of @@ -488,8 +505,10 @@ extension MutableRawSpan { @_alwaysEmitIntoClient @lifetime(borrow self) mutating public func _extracting(unchecked bounds: ClosedRange) -> Self { - let range = Range(uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1)) - return _extracting(unchecked: range) + let range = unsafe Range( + uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1) + ) + return unsafe _extracting(unchecked: range) } /// Constructs a new span over all the items of this span. @@ -505,7 +524,7 @@ extension MutableRawSpan { @lifetime(borrow self) mutating public func _extracting(_: UnboundedRange) -> Self { let newSpan = Self(_unchecked: _start(), byteCount: _count) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } } @@ -534,7 +553,7 @@ extension MutableRawSpan { precondition(maxLength >= 0, "Can't have a prefix of negative length") let newCount = min(maxLength, byteCount) let newSpan = Self(_unchecked: _pointer, byteCount: newCount) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Returns a span over all but the given number of trailing elements. @@ -557,7 +576,7 @@ extension MutableRawSpan { precondition(k >= 0, "Can't drop a negative number of elements") let dropped = min(k, byteCount) let newSpan = Self(_unchecked: _pointer, byteCount: byteCount &- dropped) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } /// Returns a span containing the final elements of the span, @@ -580,9 +599,9 @@ extension MutableRawSpan { mutating public func _extracting(last maxLength: Int) -> Self { precondition(maxLength >= 0, "Can't have a suffix of negative length") let newCount = min(maxLength, byteCount) - let newStart = _pointer?.advanced(by: byteCount &- newCount) + let newStart = unsafe _pointer?.advanced(by: byteCount &- newCount) let newSpan = Self(_unchecked: newStart, byteCount: newCount) - return _overrideLifetime(newSpan, copying: self) + return unsafe _overrideLifetime(newSpan, copying: self) } /// Returns a span over all but the given number of initial elements. @@ -604,8 +623,8 @@ extension MutableRawSpan { mutating public func _extracting(droppingFirst k: Int) -> Self { precondition(k >= 0, "Can't drop a negative number of bytes") let dropped = min(k, byteCount) - let newStart = _pointer?.advanced(by: dropped) + let newStart = unsafe _pointer?.advanced(by: dropped) let newSpan = Self(_unchecked: newStart, byteCount: byteCount &- dropped) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } } From 0b96db119e7c42c6f15c5a952df6a738b173af8e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 5 Mar 2025 13:56:26 -0800 Subject: [PATCH 165/195] remove unneeded `unsafe` keywords --- Sources/Span/MutableSpan.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index 9e0ba01a7..aa4dff70a 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -61,7 +61,7 @@ extension MutableSpan where Element: ~Copyable { public init( _unsafeElements buffer: UnsafeMutableBufferPointer ) { - unsafe precondition( + precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" @@ -105,7 +105,7 @@ extension MutableSpan where Element: BitwiseCopyable { public init( _unsafeBytes buffer: UnsafeMutableRawBufferPointer ) { - unsafe precondition( + precondition( ((Int(bitPattern: buffer.baseAddress) & (MemoryLayout.alignment &- 1)) == 0), "baseAddress must be properly aligned to access Element" @@ -219,7 +219,7 @@ extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient public var _description: String { let addr = String( - unsafe UInt(bitPattern: _pointer), radix: 16, uppercase: false + UInt(bitPattern: _pointer), radix: 16, uppercase: false ) return "(0x\(addr), \(_count))" } From 8e76a05f4f055d6a2758ea6de2bd47aff9287e49 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 5 Mar 2025 13:56:41 -0800 Subject: [PATCH 166/195] annotate slicing examples --- Sources/Span/MutableSpanSlicing.swift | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/Span/MutableSpanSlicing.swift b/Sources/Span/MutableSpanSlicing.swift index e6e4fb33a..7db6229b9 100644 --- a/Sources/Span/MutableSpanSlicing.swift +++ b/Sources/Span/MutableSpanSlicing.swift @@ -22,7 +22,7 @@ extension MutableSpan where Element: ~Copyable { "Index range out of bounds" ) let newSpan = MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } } @@ -35,11 +35,11 @@ extension MutableSpan where Element: ~Copyable { uncheckedBounds bounds: Range ) { let delta = bounds.lowerBound &* MemoryLayout.stride - let newStart = pointer.advanced(by: delta) + let newStart = unsafe pointer.advanced(by: delta) let newSpan = Self( _unchecked: newStart, count: bounds.upperBound &- bounds.lowerBound ) - self = _overrideLifetime(newSpan, borrowing: pointer) + self = unsafe _overrideLifetime(newSpan, borrowing: pointer) } } @@ -53,7 +53,7 @@ extension Span where Element: ~Copyable { ) { let mut = MutableSpan(_unchecked: pointer, uncheckedBounds: bounds) let newSpan = mut.span - self = _overrideLifetime(newSpan, borrowing: pointer) + self = unsafe _overrideLifetime(newSpan, borrowing: pointer) } } @@ -70,7 +70,7 @@ extension MutableSpan where Element: ~Copyable { "Index range out of bounds" ) let newSpan = MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) - return _overrideLifetime(newSpan, mutating: &self) + return unsafe _overrideLifetime(newSpan, mutating: &self) } } } @@ -98,11 +98,11 @@ extension SubMutableSpan where Element: ~Copyable { public subscript (index: Index) -> Element { unsafeAddress { precondition(offset <= index && index < base.count) - return UnsafePointer(base._unsafeAddressOfElement(unchecked: index)) + return unsafe UnsafePointer(base._unsafeAddressOfElement(unchecked: index)) } unsafeMutableAddress { precondition(offset <= index && index < base.count) - return base._unsafeAddressOfElement(unchecked: index) + return unsafe base._unsafeAddressOfElement(unchecked: index) } } @@ -112,7 +112,7 @@ extension SubMutableSpan where Element: ~Copyable { let newSpan = Span( _unchecked: base._start(), uncheckedBounds: offset..(bounds.lowerBound, prefix) - return _overrideLifetime(subSpan, mutating: &self) + return unsafe _overrideLifetime(subSpan, mutating: &self) } } } @@ -157,9 +157,9 @@ extension MutableSpan where Element: Copyable { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - let start = _start().advanced(by: bounds.lowerBound &* MemoryLayout.stride) - start.withMemoryRebound(to: Element.self, capacity: bounds.count) { - $0.update(repeating: repeatedValue, count: bounds.count) + let start = unsafe _start() + bounds.lowerBound &* MemoryLayout.stride + unsafe start.withMemoryRebound(to: Element.self, capacity: bounds.count) { + unsafe $0.update(repeating: repeatedValue, count: bounds.count) } } } From 4523e7a0631aefd171888ce89b14ad8f06dd5045 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 5 Mar 2025 14:38:46 -0800 Subject: [PATCH 167/195] add required unsafe annotations to `OutputSpan` --- Sources/Span/MutableSpan.swift | 2 +- Sources/Span/OutputSpan.swift | 106 ++++++++++-------- Sources/Span/StdlibOutputSpanExtensions.swift | 28 ++--- Tests/SpanTests/OutputSpanTests.swift | 6 +- 4 files changed, 77 insertions(+), 65 deletions(-) diff --git a/Sources/Span/MutableSpan.swift b/Sources/Span/MutableSpan.swift index aa4dff70a..d552a93f1 100644 --- a/Sources/Span/MutableSpan.swift +++ b/Sources/Span/MutableSpan.swift @@ -535,7 +535,7 @@ extension MutableSpan where Element: ~Copyable { public mutating func moveUpdate( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index { - let s = unsafe OutputSpan(_initializing: source, initialized: source.count) + let s = OutputSpan(_initializing: source, initialized: source.count) return self.moveUpdate(fromContentsOf: s) } } diff --git a/Sources/Span/OutputSpan.swift b/Sources/Span/OutputSpan.swift index 61251abc2..0d0c65081 100644 --- a/Sources/Span/OutputSpan.swift +++ b/Sources/Span/OutputSpan.swift @@ -12,6 +12,7 @@ // OutputSpan represents a span of memory which contains // a variable number of `Element` instances, followed by uninitialized memory. +@safe @frozen @available(macOS 9999, *) public struct OutputSpan: ~Copyable, ~Escapable { @@ -25,7 +26,7 @@ public struct OutputSpan: ~Copyable, ~Escapable { @_alwaysEmitIntoClient internal func _start() -> UnsafeMutableRawPointer { - _pointer.unsafelyUnwrapped + unsafe _pointer.unsafelyUnwrapped } @_alwaysEmitIntoClient @@ -39,9 +40,11 @@ public struct OutputSpan: ~Copyable, ~Escapable { deinit { if _initialized > 0 { - _start().withMemoryRebound(to: Element.self, capacity: _initialized) { + unsafe _start().withMemoryRebound( + to: Element.self, capacity: _initialized + ) { [ workaround = _initialized ] in - _ = $0.deinitialize(count: workaround) + _ = unsafe $0.deinitialize(count: workaround) } } } @@ -53,7 +56,7 @@ public struct OutputSpan: ~Copyable, ~Escapable { capacity: Int, initialized: Int ) { - _pointer = start + _pointer = unsafe start self.capacity = capacity _initialized = initialized } @@ -99,9 +102,9 @@ extension OutputSpan where Element: ~Copyable { initialized: Int = 0 ) { precondition(capacity >= 0, "Capacity must be 0 or greater") - let buffer = UnsafeMutableBufferPointer(start: pointer, count: capacity) - let os = OutputSpan(_initializing: buffer, initialized: initialized) - self = _overrideLifetime(os, borrowing: pointer) + let buf = unsafe UnsafeMutableBufferPointer(start: pointer, count: capacity) + let os = OutputSpan(_initializing: buf, initialized: initialized) + self = unsafe _overrideLifetime(os, borrowing: pointer) } } @@ -114,9 +117,9 @@ extension OutputSpan { _initializing buffer: borrowing Slice>, initialized: Int = 0 ) { - let rebased = UnsafeMutableBufferPointer(rebasing: buffer) + let rebased = unsafe UnsafeMutableBufferPointer(rebasing: buffer) let os = OutputSpan(_initializing: rebased, initialized: 0) - self = _overrideLifetime(os, borrowing: buffer) + self = unsafe unsafe _overrideLifetime(os, borrowing: buffer) } } @@ -141,7 +144,7 @@ extension OutputSpan where Element: BitwiseCopyable { let os = OutputSpan( _unchecked: pointer, capacity: count, initialized: initialized ) - self = _overrideLifetime(os, borrowing: bytes) + self = unsafe _overrideLifetime(os, borrowing: bytes) } @_alwaysEmitIntoClient @@ -152,9 +155,9 @@ extension OutputSpan where Element: BitwiseCopyable { initialized: Int = 0 ) { precondition(capacity >= 0, "Capacity must be 0 or greater") - let buffer = UnsafeMutableRawBufferPointer(start: pointer, count: capacity) - let os = OutputSpan(_initializing: buffer, initialized: initialized) - self = _overrideLifetime(os, borrowing: pointer) + let buf = unsafe UnsafeMutableRawBufferPointer(start: pointer, count: capacity) + let os = OutputSpan(_initializing: buf, initialized: initialized) + self = unsafe _overrideLifetime(os, borrowing: pointer) } @_alwaysEmitIntoClient @@ -163,9 +166,9 @@ extension OutputSpan where Element: BitwiseCopyable { _initializing buffer: borrowing Slice, initialized: Int = 0 ) { - let rebased = UnsafeMutableRawBufferPointer(rebasing: buffer) + let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) let os = OutputSpan(_initializing: rebased, initialized: initialized) - self = _overrideLifetime(os, borrowing: buffer) + self = unsafe _overrideLifetime(os, borrowing: buffer) } } @@ -175,8 +178,8 @@ extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient public mutating func append(_ value: consuming Element) { precondition(_initialized < capacity, "Output buffer overflow") - let p = _start().advanced(by: _initialized&*MemoryLayout.stride) - p.initializeMemory(as: Element.self, to: value) + let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + unsafe p.initializeMemory(as: Element.self, to: value) _initialized &+= 1 } @@ -184,14 +187,14 @@ extension OutputSpan where Element: ~Copyable { public mutating func deinitializeLastElement() -> Element? { guard _initialized > 0 else { return nil } _initialized &-= 1 - let p = _start().advanced(by: _initialized&*MemoryLayout.stride) - return p.withMemoryRebound(to: Element.self, capacity: 1, { $0.move() }) + let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + return unsafe p.withMemoryRebound(to: Element.self, capacity: 1, { unsafe $0.move() }) } @_alwaysEmitIntoClient public mutating func deinitialize() { - _ = _start().withMemoryRebound(to: Element.self, capacity: _initialized) { - $0.deinitialize(count: _initialized) + _ = unsafe _start().withMemoryRebound(to: Element.self, capacity: _initialized) { + unsafe $0.deinitialize(count: _initialized) } _initialized = 0 } @@ -209,9 +212,9 @@ extension OutputSpan { "destination span cannot contain number of elements requested." ) let offset = _initialized&*MemoryLayout.stride - let p = _start().advanced(by: offset) - p.withMemoryRebound(to: Element.self, capacity: count) { - $0.initialize(repeating: repeatedValue, count: count) + let p = unsafe _start().advanced(by: offset) + unsafe p.withMemoryRebound(to: Element.self, capacity: count) { + unsafe $0.initialize(repeating: repeatedValue, count: count) } _initialized &+= count } @@ -231,8 +234,8 @@ extension OutputSpan { ) { while _initialized < capacity { guard let element = elements.next() else { break } - let p = _start().advanced(by: _initialized&*MemoryLayout.stride) - p.initializeMemory(as: Element.self, to: element) + let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + unsafe p.initializeMemory(as: Element.self, to: element) _initialized &+= 1 } } @@ -249,11 +252,11 @@ extension OutputSpan { } let available = capacity &- _initialized - let tail = _start().advanced(by: _initialized&*MemoryLayout.stride) + let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) var (iterator, copied) = - tail.withMemoryRebound(to: Element.self, capacity: available) { - let suffix = UnsafeMutableBufferPointer(start: $0, count: available) - return source._copyContents(initializing: suffix) + unsafe tail.withMemoryRebound(to: Element.self, capacity: available) { + let suffix = unsafe UnsafeMutableBufferPointer(start: $0, count: available) + return unsafe source._copyContents(initializing: suffix) } precondition( iterator.next() == nil, @@ -272,9 +275,9 @@ extension OutputSpan { source.count <= available, "destination span cannot contain every element from source." ) - let tail = _start().advanced(by: _initialized&*MemoryLayout.stride) + let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) _ = source.withUnsafeBufferPointer { - tail.initializeMemory( + unsafe tail.initializeMemory( as: Element.self, from: $0.baseAddress!, count: $0.count ) } @@ -299,10 +302,10 @@ extension OutputSpan where Element: ~Copyable { source.count <= available, "buffer cannot contain every element from source." ) - let buffer = source.relinquishBorrowedMemory() + let buffer = unsafe source.relinquishBorrowedMemory() // we must now deinitialize the returned UMBP - let tail = _start().advanced(by: _initialized&*MemoryLayout.stride) - tail.moveInitializeMemory( + let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + unsafe tail.moveInitializeMemory( as: Element.self, from: buffer.baseAddress!, count: buffer.count ) _initialized &+= buffer.count @@ -324,7 +327,9 @@ extension OutputSpan { public mutating func moveAppend( fromContentsOf source: Slice> ) { - moveAppend(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) + moveAppend( + fromContentsOf: unsafe UnsafeMutableBufferPointer(rebasing: source) + ) } } @@ -340,10 +345,10 @@ extension OutputSpan where Element: ~Copyable { public var span: Span { @lifetime(borrow self) borrowing get { - let pointer = _pointer?.assumingMemoryBound(to: Element.self) - let buffer = UnsafeBufferPointer(start: pointer, count: _initialized) + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeBufferPointer(start: pointer, count: _initialized) let span = Span(_unsafeElements: buffer) - return _overrideLifetime(span, borrowing: self) + return unsafe _overrideLifetime(span, borrowing: self) } } @@ -351,10 +356,12 @@ extension OutputSpan where Element: ~Copyable { public var mutableSpan: MutableSpan { @lifetime(borrow self) mutating get { - let pointer = _pointer?.assumingMemoryBound(to: Element.self) - let buffer = UnsafeMutableBufferPointer(start: pointer, count: _initialized) + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeMutableBufferPointer( + start: pointer, count: _initialized + ) let span = MutableSpan(_unsafeElements: buffer) - return _overrideLifetime(span, mutating: &self) + return unsafe _overrideLifetime(span, mutating: &self) } } } @@ -362,22 +369,27 @@ extension OutputSpan where Element: ~Copyable { @available(macOS 9999, *) extension OutputSpan where Element: ~Copyable { + @unsafe @_alwaysEmitIntoClient - public consuming func relinquishBorrowedMemory() -> UnsafeMutableBufferPointer { + public consuming func relinquishBorrowedMemory( + ) -> UnsafeMutableBufferPointer { let (start, count) = (self._pointer, self._initialized) discard self - let typed = start?.bindMemory(to: Element.self, capacity: count) - return .init(start: typed, count: count) + let typed = unsafe start?.bindMemory(to: Element.self, capacity: count) + return unsafe UnsafeMutableBufferPointer(start: typed, count: count) } } @available(macOS 9999, *) extension OutputSpan where Element: BitwiseCopyable { + @unsafe @_alwaysEmitIntoClient - public consuming func relinquishBorrowedBytes() -> UnsafeMutableRawBufferPointer { + public consuming func relinquishBorrowedBytes( + ) -> UnsafeMutableRawBufferPointer { let (start, count) = (self._pointer, self._initialized) discard self - return .init(start: start, count: count&*MemoryLayout.stride) + let byteCount = count&*MemoryLayout.stride + return unsafe UnsafeMutableRawBufferPointer(start: start, count: byteCount) } } diff --git a/Sources/Span/StdlibOutputSpanExtensions.swift b/Sources/Span/StdlibOutputSpanExtensions.swift index 35a6e4ce3..fce41a049 100644 --- a/Sources/Span/StdlibOutputSpanExtensions.swift +++ b/Sources/Span/StdlibOutputSpanExtensions.swift @@ -17,16 +17,16 @@ extension Array { capacity: Int, initializingWith initializer: (inout OutputSpan) throws -> Void ) rethrows { - try self.init( + try unsafe self.init( unsafeUninitializedCapacity: capacity, initializingWith: { (buffer, count) in - let pointer = buffer.baseAddress.unsafelyUnwrapped + let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped var output = OutputSpan( _initializing: pointer, capacity: buffer.count ) try initializer(&output) - let initialized = output.relinquishBorrowedMemory() - assert(initialized.baseAddress == buffer.baseAddress) + let initialized = unsafe output.relinquishBorrowedMemory() + unsafe assert(initialized.baseAddress == buffer.baseAddress) count = initialized.count } ) @@ -43,16 +43,16 @@ extension String { utf8Capacity capacity: Int, initializingWith initializer: (inout OutputSpan) throws -> Void ) rethrows { - try self.init( + try unsafe self.init( unsafeUninitializedCapacity: capacity, initializingUTF8With: { buffer in - let pointer = buffer.baseAddress.unsafelyUnwrapped + let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped var output = OutputSpan( _initializing: pointer, capacity: buffer.count ) try initializer(&output) - let initialized = output.relinquishBorrowedMemory() - assert(initialized.baseAddress == buffer.baseAddress) + let initialized = unsafe output.relinquishBorrowedMemory() + unsafe assert(initialized.baseAddress == buffer.baseAddress) return initialized.count } ) @@ -69,16 +69,16 @@ extension Data { initializingWith initializer: (inout OutputSpan) throws -> Void ) rethrows { self = Data(count: capacity) // initialized with zeroed buffer - let count = try self.withUnsafeMutableBytes { rawBuffer in - try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in - buffer.deinitialize() - let pointer = buffer.baseAddress.unsafelyUnwrapped + let count = unsafe try self.withUnsafeMutableBytes { rawBuffer in + unsafe try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in + unsafe buffer.deinitialize() + let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped var output = OutputSpan( _initializing: pointer, capacity: capacity ) try initializer(&output) - let initialized = output.relinquishBorrowedMemory() - assert(initialized.baseAddress == buffer.baseAddress) + let initialized = unsafe output.relinquishBorrowedMemory() + unsafe assert(initialized.baseAddress == buffer.baseAddress) return initialized.count } } diff --git a/Tests/SpanTests/OutputSpanTests.swift b/Tests/SpanTests/OutputSpanTests.swift index 883fc6c34..fcac8ce0e 100644 --- a/Tests/SpanTests/OutputSpanTests.swift +++ b/Tests/SpanTests/OutputSpanTests.swift @@ -41,7 +41,7 @@ struct Allocation: ~Copyable { count = initialized.count } catch { - outputBuffer.deinitialize() + outputBuffer.removeAll() let empty = outputBuffer.relinquishBorrowedMemory() assert(empty.baseAddress == allocation) assert(empty.count == 0) @@ -97,7 +97,7 @@ final class OutputSpanTests: XCTestCase { for i in 0...c { $0.append(i) } - let oops = $0.deinitializeLastElement() + let oops = $0.removeLast() XCTAssertEqual(oops, c) } a.withSpan { @@ -126,7 +126,7 @@ final class OutputSpanTests: XCTestCase { let c = 10 a.initialize { $0.append(repeating: c, count: c) - let oops = $0.deinitializeLastElement() + let oops = $0.removeLast() XCTAssertEqual(oops, c) XCTAssertEqual($0.count, c-1) } From 505d6d5dc4a785be53e31ae597081a43d67a6617 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 5 Mar 2025 15:00:14 -0800 Subject: [PATCH 168/195] add unsafe annotations for `Span` extensions --- Sources/Span/SpanExtensions.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Span/SpanExtensions.swift b/Sources/Span/SpanExtensions.swift index 87546beab..c08065470 100644 --- a/Sources/Span/SpanExtensions.swift +++ b/Sources/Span/SpanExtensions.swift @@ -19,18 +19,18 @@ extension Span { public static var empty: Span { @lifetime(immortal) get { - let nilBasedBuffer = UnsafeBufferPointer(start: nil, count: 0) - let span = Span(_unsafeElements: nilBasedBuffer) - return _overrideLifetime(span, borrowing: immortalThing) + let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) + let span = Span(_unsafeElements: empty) + return unsafe _overrideLifetime(span, borrowing: immortalThing) } } @available(macOS 9999, *) @lifetime(immortal) public init() { - let nilBasedBuffer = UnsafeBufferPointer(start: nil, count: 0) - let span = Span(_unsafeElements: nilBasedBuffer) - self = _overrideLifetime(span, borrowing: immortalThing) + let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) + let span = Span(_unsafeElements: empty) + self = unsafe _overrideLifetime(span, borrowing: immortalThing) } } @@ -59,7 +59,7 @@ extension Span where Element: Equatable { // return _swift_stdlib_memcmp(lhs.baseAddress, rhs.baseAddress, count) == 0 // } for o in 0..= count { return false } - if self[unchecked: offset] != otherElement { return false } + if unsafe self[unchecked: offset] != otherElement { return false } offset += 1 } return offset == count From 98b8b786379effa36cc2d04343d6ebf6ec7a9974 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 7 Mar 2025 09:14:32 -0800 Subject: [PATCH 169/195] rename two OutputSpan functions --- Sources/Span/OutputSpan.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Span/OutputSpan.swift b/Sources/Span/OutputSpan.swift index 0d0c65081..4df8661b6 100644 --- a/Sources/Span/OutputSpan.swift +++ b/Sources/Span/OutputSpan.swift @@ -184,7 +184,7 @@ extension OutputSpan where Element: ~Copyable { } @_alwaysEmitIntoClient - public mutating func deinitializeLastElement() -> Element? { + public mutating func removeLast() -> Element? { guard _initialized > 0 else { return nil } _initialized &-= 1 let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) @@ -192,7 +192,7 @@ extension OutputSpan where Element: ~Copyable { } @_alwaysEmitIntoClient - public mutating func deinitialize() { + public mutating func removeAll() { _ = unsafe _start().withMemoryRebound(to: Element.self, capacity: _initialized) { unsafe $0.deinitialize(count: _initialized) } From a26fc8e7e4c6e0d63b08951f5b7268044fb95c83 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 28 Feb 2025 13:10:09 -0800 Subject: [PATCH 170/195] complete span tests --- Tests/SpanTests/RawSpanTests.swift | 33 +++----- Tests/SpanTests/SpanTests.swift | 123 +++++++++++------------------ 2 files changed, 54 insertions(+), 102 deletions(-) diff --git a/Tests/SpanTests/RawSpanTests.swift b/Tests/SpanTests/RawSpanTests.swift index 874af7546..7f2838bca 100644 --- a/Tests/SpanTests/RawSpanTests.swift +++ b/Tests/SpanTests/RawSpanTests.swift @@ -112,7 +112,7 @@ final class RawSpanTests: XCTestCase { let sub1 = span._extracting(0..<2) let sub2 = span._extracting(..<2) let sub3 = span._extracting(...) - let sub4 = span._extracting(unchecked: 2...) + let sub4 = span._extracting(2...) XCTAssertTrue( sub1._unsafeView(as: UInt8.self)._elementsEqual(sub2._unsafeView(as: UInt8.self)) ) @@ -137,7 +137,7 @@ final class RawSpanTests: XCTestCase { } } - func testUnsafeBytes() { + func testWithUnsafeBytes() { let capacity = 4 let array = Array(0.. Date: Tue, 8 Apr 2025 13:08:18 -0700 Subject: [PATCH 171/195] Revive Future/* Contents are mostly disabled for now --- Package.swift | 32 ++- Sources/Future/Box.swift | 79 +++++ Sources/Future/CMakeLists.txt | 27 ++ Sources/Future/Cell.swift | 59 ++++ Sources/Future/Containers/Container.swift | 160 +++++++++++ Sources/Future/Containers/DynamicArray.swift | 200 +++++++++++++ Sources/Future/Containers/NewArray.swift | 120 ++++++++ Sources/Future/Containers/RigidArray.swift | 271 ++++++++++++++++++ Sources/Future/Containers/Shared.swift | 189 ++++++++++++ Sources/Future/ContiguousStorage.swift | 106 +++++++ Sources/Future/Inout.swift | 85 ++++++ Sources/Future/Span+Iterator.swift | 62 ++++ Sources/Span/CMakeLists.txt | 2 +- Tests/FutureTests/BoxTests.swift | 43 +++ Tests/FutureTests/CellTests.swift | 37 +++ .../FutureTests/ContiguousStorageTests.swift | 59 ++++ Tests/FutureTests/DynamicArrayTests.swift | 180 ++++++++++++ Tests/FutureTests/Inout.swift | 29 ++ 18 files changed, 1733 insertions(+), 7 deletions(-) create mode 100644 Sources/Future/Box.swift create mode 100644 Sources/Future/CMakeLists.txt create mode 100644 Sources/Future/Cell.swift create mode 100644 Sources/Future/Containers/Container.swift create mode 100644 Sources/Future/Containers/DynamicArray.swift create mode 100644 Sources/Future/Containers/NewArray.swift create mode 100644 Sources/Future/Containers/RigidArray.swift create mode 100644 Sources/Future/Containers/Shared.swift create mode 100644 Sources/Future/ContiguousStorage.swift create mode 100644 Sources/Future/Inout.swift create mode 100644 Sources/Future/Span+Iterator.swift create mode 100644 Tests/FutureTests/BoxTests.swift create mode 100644 Tests/FutureTests/CellTests.swift create mode 100644 Tests/FutureTests/ContiguousStorageTests.swift create mode 100644 Tests/FutureTests/DynamicArrayTests.swift create mode 100644 Tests/FutureTests/Inout.swift diff --git a/Package.swift b/Package.swift index 540dbfc3c..f34695bf6 100644 --- a/Package.swift +++ b/Package.swift @@ -52,13 +52,27 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -let _sharedSettings: [SwiftSetting] = defines.map { .define($0) } + [ +let availabilityMacros: KeyValuePairs = [ + "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", + "SwiftStdlib 6.2": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999", +] + +let extraSettings: [SwiftSetting] = [ .enableExperimentalFeature("AllowUnsafeAttribute"), .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("LifetimeDependence"), .enableExperimentalFeature("SuppressedAssociatedTypes"), + .enableUpcomingFeature("StrictMemorySafety"), ] +let _sharedSettings: [SwiftSetting] = ( + defines.map { .define($0) } + + availabilityMacros.map { name, value in + .enableExperimentalFeature("AvailabilityMacro=\(name): \(value)") + } + + extraSettings +) + let _settings: [SwiftSetting] = _sharedSettings + [] @@ -77,6 +91,7 @@ struct CustomTarget { var dependencies: [Target.Dependency] var directory: String var exclude: [String] + var sources: [String]? var settings: [SwiftSetting] } @@ -103,6 +118,7 @@ extension CustomTarget { dependencies: [Target.Dependency] = [], directory: String? = nil, exclude: [String] = [], + sources: [String]? = nil, settings: [SwiftSetting]? = nil ) -> CustomTarget { CustomTarget( @@ -111,6 +127,7 @@ extension CustomTarget { dependencies: dependencies, directory: directory ?? name, exclude: exclude, + sources: sources, settings: settings ?? (kind.isTest ? _testSettings : _settings)) } @@ -127,6 +144,7 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, + sources: sources, swiftSettings: settings, linkerSettings: linkerSettings) case .test: @@ -217,15 +235,17 @@ let targets: [CustomTarget] = [ .target( kind: .exported, - name: "Span", + name: "Future", dependencies: ["InternalCollectionsUtilities"], exclude: ["CMakeLists.txt"], settings: _sharedSettings + [.unsafeFlags(["-Xfrontend", "-strict-memory-safety"])], ), .target( kind: .test, - name: "SpanTests", - dependencies: ["Span", "_CollectionsTestSupport"]), + name: "FutureTests", + dependencies: ["Future", "_CollectionsTestSupport"], + sources: ["Tests/FutureTests", "Tests/SpanTests"] + ), .target( kind: .exported, @@ -242,7 +262,7 @@ let targets: [CustomTarget] = [ .target( kind: .exported, name: "DequeModule", - dependencies: ["InternalCollectionsUtilities", "Span"], + dependencies: ["InternalCollectionsUtilities", "Future"], exclude: ["CMakeLists.txt"]), .target( kind: .test, @@ -286,7 +306,7 @@ let targets: [CustomTarget] = [ directory: "RopeModule", exclude: ["CMakeLists.txt"], // FIXME: _modify accessors in RopeModule seem to be broken in Swift 6 mode - settings: _sharedSettings + [.swiftLanguageVersion(.v5)]), + settings: _sharedSettings + [.swiftLanguageMode(.v5)]), .target( kind: .test, name: "RopeModuleTests", diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift new file mode 100644 index 000000000..d50ec188e --- /dev/null +++ b/Sources/Future/Box.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +@frozen +public struct Box: ~Copyable { + @usableFromInline + internal let _pointer: UnsafeMutablePointer + + @_alwaysEmitIntoClient + @_transparent + public init(_ value: consuming T) { + _pointer = UnsafeMutablePointer.allocate(capacity: 1) + _pointer.initialize(to: value) + } + + @_alwaysEmitIntoClient + @_transparent + public init(_ fromInout: consuming Inout) { + _pointer = fromInout._pointer + } + + @_alwaysEmitIntoClient + @inlinable + deinit { + _pointer.deinitialize(count: 1) + _pointer.deallocate() + } +} + +extension Box where T: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public consuming func consume() -> T { + let result = _pointer.move() + _pointer.deallocate() + discard self + return result + } + + @_alwaysEmitIntoClient + @_transparent + @lifetime(immortal) + public consuming func leak() -> Inout { + let result = Inout(unsafeImmortalAddress: _pointer) + discard self + return result + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + UnsafePointer(_pointer) + } + + @_transparent + nonmutating unsafeMutableAddress { + _pointer + } + } +} + +extension Box where T: Copyable { + @_alwaysEmitIntoClient + @_transparent + public borrowing func copy() -> T { + _pointer.pointee + } +} +#endif diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt new file mode 100644 index 000000000..4d5ff5307 --- /dev/null +++ b/Sources/Future/CMakeLists.txt @@ -0,0 +1,27 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2024 - 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 +#]] + +add_library(Future + "LifetimeOverride.swift" + "MutableRawSpan.swift" + "MutableSpan.swift" + "MutableSpanSlicing.swift" + "OutputSpan.swift" + "SpanExtensions.swift" + "StdlibOutputSpanExtensions.swift" + "UnsafeBufferPointer+Additions.swift" +) + +target_link_libraries(Future PRIVATE + InternalCollectionsUtilities) +set_target_properties(Future PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +_install_target(Future) +set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS Future) diff --git a/Sources/Future/Cell.swift b/Sources/Future/Cell.swift new file mode 100644 index 000000000..a5050e68a --- /dev/null +++ b/Sources/Future/Cell.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +import Builtin + +@frozen +@_rawLayout(like: T, movesAsLike) +public struct Cell: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public var unsafeAddress: UnsafeMutablePointer { + UnsafeMutablePointer(Builtin.addressOfRawLayout(self)) + } + + @_alwaysEmitIntoClient + @_transparent + public init(_ value: consuming T) { + unsafeAddress.initialize(to: value) + } +} + +extension Cell where T: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public mutating func asInout() -> Inout { + Inout(unsafeAddress: unsafeAddress, owner: &self) + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + UnsafePointer(unsafeAddress) + } + + @_transparent + nonmutating unsafeMutableAddress { + unsafeAddress + } + } +} + +extension Cell where T: Copyable { + @_alwaysEmitIntoClient + @_transparent + public borrowing func copy() -> T { + unsafeAddress.pointee + } +} +#endif diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift new file mode 100644 index 000000000..68352669f --- /dev/null +++ b/Sources/Future/Containers/Container.swift @@ -0,0 +1,160 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +public protocol BorrowingIteratorProtocol: ~Escapable { + associatedtype Element: ~Copyable + + @lifetime(self) + mutating func nextChunk(maximumCount: Int) -> Span +} + +public protocol Container: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Escapable + where BorrowingIterator.Element == Element + + borrowing func startBorrowingIteration() -> BorrowingIterator + borrowing func startBorrowingIteration(from start: Index) -> BorrowingIterator + + associatedtype Index: Comparable + + var isEmpty: Bool { get } + var count: Int { get } + + var startIndex: Index { get } + var endIndex: Index { get } + + // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements + // FIXME: (Or rather, accessors with proper projection semantics.) + @_borrowed subscript(index: Index) -> Element { get } + + func index(after index: Index) -> Index + func formIndex(after i: inout Index) + + func index(at position: borrowing BorrowingIterator) -> Index + + func distance(from start: Index, to end: Index) -> Int + + func index(_ index: Index, offsetBy n: Int) -> Index + + func formIndex( + _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index + ) +} + +public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { + override associatedtype Element: ~Copyable + + func index(before i: Index) -> Index + func formIndex(before i: inout Index) + + @_nonoverride func index(_ i: Index, offsetBy distance: Int) -> Index + @_nonoverride func formIndex( + _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index + ) +} + +public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { + override associatedtype Element: ~Copyable +} + +extension Strideable { + @inlinable + public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { + if distance >= 0 { + guard limit >= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.min(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } else { + guard limit <= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.max(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } + } +} + +extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Self: ~Copyable { + @inlinable + public func index(after index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: 1) + } + + @inlinable + public func index(before index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: -1) + } + + @inlinable + public func formIndex(after index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: 1) + } + + @inlinable + public func formIndex(before index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: -1) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + // Note: Range checks are deferred until element access. + start.distance(to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: n) + } + + @inlinable + public func formIndex( + _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index + ) { + // Note: Range checks are deferred until element access. + index.advance(by: &distance, limitedBy: limit) + } +} +#endif + +#if false // TODO +public protocol Muterator: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + @lifetime(self) + mutating func nextChunk(maximumCount: Int) -> MutableSpan +} + +public protocol MutableContainer: Container, ~Copyable, ~Escapable { + associatedtype MutatingIterationState: ~Copyable, ~Escapable + + mutating func startMutatingIteration() -> MutatingIterationState + + // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements + @_borrowed subscript(index: Index) -> Element { get set } + +} +#endif diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift new file mode 100644 index 000000000..7f79cd42f --- /dev/null +++ b/Sources/Future/Containers/DynamicArray.swift @@ -0,0 +1,200 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false +/// A dynamically self-resizing, heap allocated, noncopyable array +/// of potentially noncopyable elements. +@frozen +public struct DynamicArray: ~Copyable { + @usableFromInline + internal var _storage: RigidArray + + @inlinable + public init() { + _storage = .init(capacity: 0) + } + + @inlinable + public init(minimumCapacity: Int) { + _storage = .init(capacity: minimumCapacity) + } + + @inlinable + public init(count: Int, initializedBy generator: (Int) -> Element) { + _storage = .init(count: count, initializedBy: generator) + } +} + +extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public var capacity: Int { _storage.capacity } +} + +extension DynamicArray where Element: ~Copyable { + public var storage: Span { + _storage.storage + } +} + +extension DynamicArray: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = RigidArray.BorrowingIterator + public typealias Index = Int + + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(for: _storage, startOffset: 0) + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(for: _storage, startOffset: start) + } + + @inlinable + public var isEmpty: Bool { _storage.isEmpty } + + @inlinable + public var count: Int { _storage.count } + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _storage.count } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + yield _storage[position] + } + @inline(__always) + _modify { + yield &_storage[position] + } + } + + @inlinable + public func index(after i: Int) -> Int { i + 1 } + + @inlinable + public func index(before i: Int) -> Int { i - 1 } + + @inlinable + public func formIndex(after index: inout Int) { + // Note: Range checks are deferred until element access. + index += 1 + } + + @inlinable + public func formIndex(before index: inout Int) { + // Note: Range checks are deferred until element access. + index -= 1 + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + // Note: Range checks are deferred until element access. + position._offset + } + + @inlinable + public func distance(from start: Int, to end: Int) -> Int { + // Note: Range checks are deferred until element access. + end - start + } + + @inlinable + public func index(_ index: Int, offsetBy n: Int) -> Int { + // Note: Range checks are deferred until element access. + index + n + } + + @inlinable + public func formIndex( + _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int + ) { + _storage.formIndex(&index, offsetBy: &distance, limitedBy: limit) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public func borrowElement ( + at index: Int, + by body: (borrowing Element) throws(E) -> R + ) throws(E) -> R { + try _storage.borrowElement(at: index, by: body) + } + + @inlinable + public mutating func updateElement ( + at index: Int, + by body: (inout Element) throws(E) -> R + ) throws(E) -> R { + try _storage.updateElement(at: index, by: body) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public mutating func reserveCapacity(_ n: Int) { + _storage.reserveCapacity(n) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + internal static func _grow(_ capacity: Int) -> Int { + 2 * capacity + } + + @inlinable + public mutating func _ensureFreeCapacity(_ minimumCapacity: Int) { + guard _storage.freeCapacity < minimumCapacity else { return } + reserveCapacity(max(count + minimumCapacity, Self._grow(capacity))) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public mutating func append(_ item: consuming Element) { + _ensureFreeCapacity(1) + _storage.append(item) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + _ensureFreeCapacity(1) + _storage.insert(item, at: index) + } +} + +extension DynamicArray { + @inlinable + public mutating func append(contentsOf items: some Sequence) { + for item in items { + append(item) + } + } +} +#endif diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift new file mode 100644 index 000000000..7362045ba --- /dev/null +++ b/Sources/Future/Containers/NewArray.swift @@ -0,0 +1,120 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false +/// What `Array` might look like if we defined it today. +@frozen +public struct NewArray { + @usableFromInline + internal var _storage: Shared> + + @inlinable + public init(minimumCapacity: Int) { + self._storage = Shared(RigidArray(capacity: minimumCapacity)) + } +} + +extension NewArray { + public var storage: Span { +#if false + // FIXME: This is what I want to write; alas, lifetimes are messed up. + return _storage.value.storage +#else + return Span(_unsafeElements: _storage.value._items) +#endif + } +} + +extension NewArray { + @inlinable + public var capacity: Int { _storage.read { $0.capacity } } + + @inlinable + internal mutating func _ensureUnique() { + _storage.ensureUnique { $0._copy() } + } + + @inlinable + internal mutating func _ensureUnique( + minimumCapacity: Int, + linear: Bool = false + ) { + if !_storage.isUnique() { + let c = Swift.max(count, minimumCapacity) + _storage = Shared(_storage.read { $0._copy(capacity: c) }) + } else if minimumCapacity > self.capacity { + _storage = Shared(_storage.update { $0._move(capacity: minimumCapacity) }) + } + } + + @inlinable + internal func _read( + _ body: (borrowing RigidArray) throws(E) -> Result + ) throws(E) -> Result { + try _storage.read(body) + } + + @inlinable + internal mutating func _update( + _ body: (inout RigidArray) throws(E) -> Result + ) throws(E) -> Result { + _ensureUnique() + return try _storage.update(body) + } + + @inlinable + internal mutating func _update( + minimumCapacity: Int, + _ body: (inout RigidArray) throws(E) -> Result + ) throws(E) -> Result { + _ensureUnique(minimumCapacity: minimumCapacity) + return try _storage.update(body) + } +} + +extension NewArray: RandomAccessCollection, MutableCollection { + public typealias Index = Int + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _storage.read { $0.count } } + + public subscript(position: Int) -> Element { + @inlinable + get { + _read { $0[position] } + } + @inlinable + @inline(__always) + _modify { + _ensureUnique() + yield &_storage.value[position] + } + } +} + +extension NewArray: RangeReplaceableCollection { + public init() { + // FIXME: Figure out if we can implement empty singletons in this setup. + self._storage = Shared(RigidArray(capacity: 0)) + } + + mutating public func replaceSubrange( + _ subrange: Range, + with newElements: some Collection + ) { + let delta = newElements.count - subrange.count + _ensureUnique(minimumCapacity: capacity + delta) + } +} +#endif diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift new file mode 100644 index 000000000..9dd374dc2 --- /dev/null +++ b/Sources/Future/Containers/RigidArray.swift @@ -0,0 +1,271 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +/// A manually resizable, heap allocated, noncopyable array of +/// potentially noncopyable elements. +@safe +@frozen +public struct RigidArray: ~Copyable { + @usableFromInline + internal var _storage: UnsafeMutableBufferPointer + + @usableFromInline + internal var _count: Int + + @inlinable + public init(capacity: Int) { + precondition(capacity >= 0) + if capacity > 0 { + unsafe _storage = .allocate(capacity: capacity) + } else { + unsafe _storage = .init(start: nil, count: 0) + } + _count = 0 + } + + @inlinable + public init(count: Int, initializedBy generator: (Int) -> Element) { + unsafe _storage = .allocate(capacity: count) + for i in 0 ..< count { + unsafe _storage.initializeElement(at: i, to: generator(i)) + } + _count = count + } + + deinit { + unsafe _storage.extracting(0 ..< count).deinitialize() + unsafe _storage.deallocate() + } +} + +extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension RigidArray where Element: ~Copyable { + @inlinable + public var capacity: Int { unsafe _storage.count } + + @inlinable + public var freeCapacity: Int { capacity - count } + + @inlinable + public var isFull: Bool { freeCapacity == 0 } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + internal var _items: UnsafeMutableBufferPointer { + unsafe _storage.extracting(Range(uncheckedBounds: (0, _count))) + } + + @inlinable + internal var _freeSpace: UnsafeMutableBufferPointer { + unsafe _storage.extracting(Range(uncheckedBounds: (_count, capacity))) + } +} + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 6.2, *) + public var span: Span { + let result = unsafe Span(_unsafeElements: _items) + return _overrideLifetime(result, borrowing: self) + } +} + +extension RigidArray where Element: ~Copyable { + public typealias Index = Int + + @inlinable + public var isEmpty: Bool { count == 0 } + + @inlinable + public var count: Int { _count } + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { count } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + precondition(position >= 0 && position < _count) + yield _storage[position] + } + @inline(__always) + _modify { + precondition(position >= 0 && position < _count) + yield &_storage[position] + } + } +} + +#if false +extension RigidArray: RandomAccessContainer where Element: ~Copyable { + public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { + @usableFromInline + internal let _items: UnsafeBufferPointer + + @usableFromInline + internal var _offset: Int + + @inlinable + internal init(for array: borrowing RigidArray, startOffset: Int) { + self._items = UnsafeBufferPointer(array._items) + self._offset = startOffset + } + + @lifetime(self) + public mutating func nextChunk( + maximumCount: Int + ) -> Span { + let end = _offset + Swift.min(maximumCount, _items.count - _offset) + defer { _offset = end } + let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) + return Span(_unsafeElements: chunk) + } + } + + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(for: self, startOffset: 0) + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(for: self, startOffset: start) + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + precondition(position._items === UnsafeBufferPointer(self._items)) + return position._offset + } +} +#endif + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func resize(to newCapacity: Int) { + precondition(newCapacity >= count) + guard newCapacity != capacity else { return } + let newStorage: UnsafeMutableBufferPointer = .allocate(capacity: newCapacity) + let i = newStorage.moveInitialize(fromContentsOf: self._items) + assert(i == count) + _storage.deallocate() + _storage = newStorage + } + + @inlinable + public mutating func reserveCapacity(_ n: Int) { + guard capacity < n else { return } + resize(to: n) + } +} + + +extension RigidArray where Element: ~Copyable { + @inlinable + public func borrowElement ( + at index: Int, + by body: (borrowing Element) throws(E) -> R + ) throws(E) -> R { + precondition(index >= 0 && index < _count) + return try body(_storage[index]) + } + + @inlinable + public mutating func updateElement ( + at index: Int, + by body: (inout Element) throws(E) -> R + ) throws(E) -> R { + precondition(index >= 0 && index < _count) + return try body(&_storage[index]) + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < count) + let old = _storage.moveElement(from: index) + let source = _storage.extracting(index + 1 ..< count) + let target = _storage.extracting(index ..< count - 1) + let i = target.moveInitialize(fromContentsOf: source) + assert(i == target.endIndex) + _count -= 1 + return old + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func append(_ item: consuming Element) { + precondition(!isFull) + _storage.initializeElement(at: _count, to: item) + _count += 1 + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + precondition(!isFull) + if index < count { + let source = _storage.extracting(index ..< count) + let target = _storage.extracting(index + 1 ..< count + 1) + let last = target.moveInitialize(fromContentsOf: source) + assert(last == target.endIndex) + } + _storage.initializeElement(at: index, to: item) + _count += 1 + } +} + +extension RigidArray { + @inlinable + public mutating func append(contentsOf items: some Sequence) { + for item in items { + append(item) + } + } +} + +extension RigidArray { + @inlinable + internal func _copy() -> Self { + _copy(capacity: capacity) + } + + @inlinable + internal func _copy(capacity: Int) -> Self { + precondition(capacity >= count) + var result = RigidArray(capacity: capacity) + let initialized = result._storage.initialize(fromContentsOf: _storage) + precondition(initialized == count) + result._count = count + return result + } + + @inlinable + internal mutating func _move(capacity: Int) -> Self { + precondition(capacity >= count) + var result = RigidArray(capacity: capacity) + let initialized = result._storage.moveInitialize(fromContentsOf: _storage) + precondition(initialized == count) + result._count = count + self._count = 0 + return result + } +} +#endif diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift new file mode 100644 index 000000000..fee5c58c3 --- /dev/null +++ b/Sources/Future/Containers/Shared.swift @@ -0,0 +1,189 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false +import Builtin // For Shared.isIdentical + +/// A utility adapter that wraps a noncopyable storage type in a copy-on-write +/// struct, enabling efficient implementation of value semantics. The type +/// allows safe borrowing and mutating access to its storage, with minimal fuss. +/// +/// Like `ManagedBufferPointer`, this type is intended to be used within the +/// internal implementation of public types. Instances of it aren't designed +/// to be exposed as public. +@frozen +public struct Shared { + @usableFromInline + internal var _box: _Box + + @inlinable + public init(_ storage: consuming Storage) { + self._box = _Box(storage) + } +} + +extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} + +#if true // FIXME: Silent error on class definition nested in noncopyable struct +@usableFromInline +internal final class _SharedBox { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + self.storage = storage + } +} + +extension Shared where Storage: ~Copyable { + @usableFromInline + internal typealias _Box = _SharedBox +} +#else +extension Shared where Storage: ~Copyable { + @usableFromInline + internal final class _Box { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + self.storage = storage + } + } +} +#endif + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public mutating func isUnique() -> Bool { + isKnownUniquelyReferenced(&_box) + } + + @inlinable + public mutating func ensureUnique( + cloner: (borrowing Storage) -> Storage + ) { + if isUnique() { return } + _box = _Box(cloner(_box.storage)) + } +} + +import struct SwiftShims.HeapObject + +extension Shared where Storage: ~Copyable { + // FIXME: Can we avoid hacks like this? If not, perhaps `_Box.storage` should be tail-allocated. + @inlinable + internal var _address: UnsafePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = ( + UnsafeRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } + + @inlinable + internal var _mutableAddress: UnsafeMutablePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = ( + UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public var value: /*FIXME: dependsOn(self)*/ Storage { + // FIXME: This implements the wrong shape. + // FIXME: Semantically it yields a borrow scoped to an access of this `value` variable, + // FIXME: not the much wider borrow of `self`, which we'd actually want. + _read { + yield _box.storage + } + _modify { + precondition(isUnique()) + yield &_box.storage + } + } + + // FIXME: This builds, but attempts to use it don't: they fail with an unexpected exclusivity violation. + @inlinable + @lifetime(self) + public subscript() -> Storage { + //@_transparent + unsafeAddress { + _address + } + + //@_transparent + unsafeMutableAddress { + precondition(isUnique()) + return _mutableAddress + } + } + + @inlinable + @inline(__always) + public func read( + _ body: (borrowing Storage) throws(E) -> R + ) throws(E) -> R { + // FIXME: This also implements the wrong shape. + // FIXME: The borrow of `Storage` isn't tied to `self` at all, and + // FIXME: it obviously cannot legally escape the `read` call. + try body(_box.storage) + } + + @inlinable + @inline(__always) + public mutating func update( + _ body: (inout Storage) throws(E) -> R + ) throws(E) -> R { + precondition(isUnique()) + return try body(&_box.storage) + } +} + +#if false // FIXME: Use it or lose it +extension Shared where Storage: ~Copyable { + // This is the actual shape we want. There is currently no way to express it. + @inlinable + @lifetime(borrow self) + public borrowing func read() -> Borrow { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage); but we have not + // (cannot) explain this to the compiler. + Borrow(unsafeAddress: _address, owner: self) + } +} +#endif + +extension Shared where Storage: ~Copyable { + @inlinable + public func isIdentical(to other: Self) -> Bool { + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + return self._box === other._box + } else { + // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions + // that are only supported in the Swift 6+ runtime. + let a = Builtin.bridgeToRawPointer(self._box) + let b = Builtin.bridgeToRawPointer(other._box) + return Bool(Builtin.cmp_eq_RawPointer(a, b)) + } + } +} +#endif diff --git a/Sources/Future/ContiguousStorage.swift b/Sources/Future/ContiguousStorage.swift new file mode 100644 index 000000000..30043f93c --- /dev/null +++ b/Sources/Future/ContiguousStorage.swift @@ -0,0 +1,106 @@ +//===--- ContiguousStorage.swift ------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +public protocol ContiguousStorage: ~Copyable, ~Escapable { + associatedtype Element/*: ~Copyable & ~Escapable*/ + + var storage: Span { borrowing get } +} + +extension Span: ContiguousStorage /*where Element: ~Copyable & ~Escapable*/ { + public var storage: Self { self } +} + +extension Array: ContiguousStorage { + public var storage: Span { + _read { + if let a = _baseAddressIfContiguous { + yield Span(_unsafeStart: a, count: count) + } + else { + let a = ContiguousArray(copy self) + #if true + let s = Span( + _unsafeStart: a._baseAddressIfContiguous!, count: a.count + ) + #else + let s = a.storage + #endif + yield s + } + } + } +} + +extension ContiguousArray: ContiguousStorage { + public var storage: Span { + borrowing get { + Span( + _unsafeStart: _baseAddressIfContiguous!, count: count + ) + } + } +} + +extension CollectionOfOne: ContiguousStorage { + public var storage: Span { + _read { +/* ideally: (with strawman syntax) + @addressable let value = self._element + yield Span( + unsafePointer: Builtin.addressable(value), count: 1 + ) +*/ + + let a = ContiguousArray(self) + yield Span( + _unsafeStart: a._baseAddressIfContiguous!, count: 1 + ) + } + } +} + +extension String.UTF8View: ContiguousStorage { + public var storage: Span { + _read { + if count < 16 { // Wrong way to know whether the String is smol +// if _guts.isSmall { +// let /*@addressable*/ rawStorage = _guts.asSmall._storage +// let span = RawSpan( +// unsafeRawPointer: UnsafeRawPointer(Builtin.adressable(rawStorage)), +// count: MemoryLayout<_SmallString.RawBitPattern>.size, +// owner: self +// ) +// yield span.view(as: UTF8.CodeUnit.self) + + let a = ContiguousArray(self) +// yield a.storage + yield Span( + _unsafeStart: a._baseAddressIfContiguous!, count: 1 + ) + } + else if let buffer = withContiguousStorageIfAvailable({ $0 }) { + // this is totally wrong, but there is a way with stdlib-internal API + yield Span(_unsafeElements: buffer) + } + else { // copy non-fast code units if we don't have eager bridging + let a = ContiguousArray(self) +// yield a.storage + yield Span( + _unsafeStart: a._baseAddressIfContiguous!, count: 1 + ) + } + } + } +} +#endif diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift new file mode 100644 index 000000000..dd2624303 --- /dev/null +++ b/Sources/Future/Inout.swift @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +import Builtin + +// FIXME: A better name for the generic argument. + +/// A safe mutable reference allowing in-place mutation to an exclusive value. +/// +/// In order to get an instance of an `Inout`, one must have exclusive access +/// to the instance of `T`. This is achieved through the 'inout' operator, '&'. +@frozen +public struct Inout: ~Copyable, ~Escapable { + @usableFromInline + internal let _pointer: UnsafeMutablePointer + + /// Initializes an instance of 'Inout' extending the exclusive access of the + /// passed instance. + /// + /// - Parameter instance: The desired instance to get a mutable reference to. + @_alwaysEmitIntoClient + @_transparent + public init(_ instance: inout T) { + _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + } + + /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' + /// as the mutable reference based on the lifetime of the given 'owner' + /// argument. + /// + /// - Parameter unsafeAddress: The address to use to mutably reference an + /// instance of type 'T'. + /// - Parameter owner: The owning instance that this 'Inout' instance's + /// lifetime is based on. + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafeMutablePointer, + owner: inout Owner + ) { + _pointer = unsafeAddress + } + + /// Unsafely initializes an instance of 'Inout' using the given + /// 'unsafeImmortalAddress' as the mutable reference acting as though its + /// lifetime is immortal. + /// + /// - Parameter unsafeImmortalAddress: The address to use to mutably reference + /// an immortal instance of type 'T'. + @_alwaysEmitIntoClient + @_transparent + @lifetime(immortal) + public init( + unsafeImmortalAddress: UnsafeMutablePointer + ) { + _pointer = unsafeImmortalAddress + } +} + +extension Inout where T: ~Copyable { + /// Dereferences the mutable reference allowing for in-place reads and writes + /// to the underlying instance. + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + UnsafePointer(_pointer) + } + + @_transparent + nonmutating unsafeMutableAddress { + _pointer + } + } +} +#endif diff --git a/Sources/Future/Span+Iterator.swift b/Sources/Future/Span+Iterator.swift new file mode 100644 index 000000000..434e6985e --- /dev/null +++ b/Sources/Future/Span+Iterator.swift @@ -0,0 +1,62 @@ +//===--- SpanIterator.swift -----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +extension Span where Element: ~Copyable { + @frozen + public struct Iterator: Copyable, ~Escapable { + var curPointer: UnsafeRawPointer? + let endPointer: UnsafeRawPointer? + + @lifetime(span) + public init(from span: consuming Span) { + curPointer = span._pointer + endPointer = span._pointer?.advanced( + by: span.count*MemoryLayout.stride + ) + } + } +} + +extension Span.Iterator where Element: Copyable { + + // This is the `IteratorProtocol` requirement, except that + // Span.Iterator does not conform to `Escapable` + public mutating func next() -> Element? { + guard curPointer != endPointer, + let cur = curPointer, cur < endPointer.unsafelyUnwrapped + else { return nil } + defer { + curPointer = cur.advanced(by: MemoryLayout.stride) + } + if _isPOD(Element.self) { + return cur.loadUnaligned(as: Element.self) + } + return cur.load(as: Element.self) + } +} + +extension Span.Iterator where Element: BitwiseCopyable { + + // This is the `IteratorProtocol` requirement, except that + // Span.Iterator does not conform to `Escapable` + public mutating func next() -> Element? { + guard curPointer != endPointer, + let cur = curPointer, cur < endPointer.unsafelyUnwrapped + else { return nil } + defer { + curPointer = cur.advanced(by: MemoryLayout.stride) + } + return cur.loadUnaligned(as: Element.self) + } +} +#endif diff --git a/Sources/Span/CMakeLists.txt b/Sources/Span/CMakeLists.txt index b85b0b5e3..4d5ff5307 100644 --- a/Sources/Span/CMakeLists.txt +++ b/Sources/Span/CMakeLists.txt @@ -1,7 +1,7 @@ #[[ This source file is part of the Swift Collections Open Source Project -Copyright (c) 2024 Apple Inc. and the Swift project authors +Copyright (c) 2024 - 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 diff --git a/Tests/FutureTests/BoxTests.swift b/Tests/FutureTests/BoxTests.swift new file mode 100644 index 000000000..9f1075e85 --- /dev/null +++ b/Tests/FutureTests/BoxTests.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class FutureBoxTests: XCTestCase { + func test_basic() { + let intOnHeap = Box(0) + + XCTAssertEqual(intOnHeap[], 0) + + intOnHeap[] = 123 + + XCTAssertEqual(intOnHeap[], 123) + + let inoutToIntOnHeap = intOnHeap.leak() + + XCTAssertEqual(inoutToIntOnHeap[], 123) + + inoutToIntOnHeap[] = 321 + + XCTAssertEqual(inoutToIntOnHeap[], 321) + + let intOnHeapAgain = Box(inoutToIntOnHeap) + + XCTAssertEqual(intOnHeapAgain[], 321) + + XCTAssertEqual(intOnHeapAgain.copy(), 321) + + let intInRegister = intOnHeapAgain.consume() + + XCTAssertEqual(intInRegister, 321) + } +} diff --git a/Tests/FutureTests/CellTests.swift b/Tests/FutureTests/CellTests.swift new file mode 100644 index 000000000..7588e1642 --- /dev/null +++ b/Tests/FutureTests/CellTests.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class FutureCellTests: XCTestCase { + struct IntOnStack: ~Copyable { + var value = Cell(0) + } + + func test_basic() { + var myInt = IntOnStack() + + XCTAssertEqual(myInt.value[], 0) + + myInt.value[] = 123 + + XCTAssertEqual(myInt.value[], 123) + + let inoutToIntOnStack = myInt.value.asInout() + + inoutToIntOnStack[] = 321 + + XCTAssertEqual(myInt.value[], 321) + + XCTAssertEqual(myInt.value.copy(), 321) + } +} diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift new file mode 100644 index 000000000..b0fa7a067 --- /dev/null +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -0,0 +1,59 @@ +import XCTest +import Future + +final class ContiguousStorageTests: XCTestCase { + + func testBorrowArrayStorage() throws { + + let capacity = 10 + var a: [Int] = [] + a = Array(0.. + + var startIndex: Int { 0 } + var endIndex: Int { span.count } + func index(after i: Int) -> Int { i+2 } + + init(_ contiguous: borrowing Span) { + span = copy contiguous + } + + subscript(_ p: Int) -> Int { span[p] } + } + + @inline(never) + @lifetime(borrow array) + private func skip( + along array: borrowing Array + ) -> Skipper { + Skipper(array.storage) + } + + func testSpanWrapper() { + let capacity = 8 + let a = Array(0..() + expectTrue(array.isEmpty) + expectEqual(array.count, 0) + expectEqual(array.capacity, 0) + expectEqual(Counted.instances, 0) + + array.append(Counted(42)) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array[0].value, 42) + expectEqual(Counted.instances, 1) + + array.append(Counted(23)) + expectFalse(array.isEmpty) + expectEqual(array.count, 2) + expectEqual(array[0].value, 42) + expectEqual(array[1].value, 23) + expectEqual(Counted.instances, 2) + + let old = array.remove(at: 0) + expectEqual(old.value, 42) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array[0].value, 23) + expectEqual(Counted.instances, 2) + _ = consume old + expectEqual(Counted.instances, 1) + + let old2 = array.remove(at: 0) + expectEqual(old2.value, 23) + expectEqual(array.count, 0) + expectTrue(array.isEmpty) + expectEqual(Counted.instances, 1) + _ = consume old2 + expectEqual(Counted.instances, 0) + } + + func test_read_access() { + let c = 100 + let array = DynamicArray(count: c) { Counted($0) } + + for i in 0 ..< c { + expectEqual(array.borrowElement(at: i) { $0.value }, i) + expectEqual(array[i].value, i) + } + } + + func test_update_access() { + let c = 100 + var array = DynamicArray(count: c) { Counted($0) } + + for i in 0 ..< c { + array.updateElement(at: i) { $0.value += 100 } + array[i].value += 100 + } + + for i in 0 ..< c { + expectEqual(array[i].value, 200 + i) + } + + expectEqual(Counted.instances, c) + _ = consume array + expectEqual(Counted.instances, 0) + } + + func test_append() { + var array = DynamicArray() + let c = 100 + for i in 0 ..< c { + array.append(Counted(100 + i)) + } + expectEqual(Counted.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + // FIXME: unexpected exclusivity violation (rdar://128441125) + //expectEqual(array.borrowElement(at: i) { $0.value }, 100 + i) + expectEqual(array[i].value, 100 + i) + } + + _ = consume array + expectEqual(Counted.instances, 0) + } + + func test_insert() { + var array = DynamicArray() + let c = 100 + for i in 0 ..< c { + array.insert(Counted(100 + i), at: 0) + } + expectEqual(Counted.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + // FIXME: unexpected exclusivity violation (rdar://128441125) + //expectEqual(array.borrowElement(at: i) { $0.value }, c + 99 - i) + expectEqual(array[i].value, c + 99 - i) + } + + _ = consume array + expectEqual(Counted.instances, 0) + } + + func test_remove() { + let c = 100 + var array = DynamicArray(count: c) { Counted(100 + $0) } + expectEqual(Counted.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + array.remove(at: 0) + expectEqual(array.count, c - 1 - i) + expectEqual(Counted.instances, c - 1 - i) + } + + expectTrue(array.isEmpty) + expectEqual(Counted.instances, 0) + } + + func test_iterate_full() { + let c = 100 + let array = DynamicArray(count: c) { Counted(100 + $0) } + + var state = array.startBorrowingIteration() + do { + let span = state.nextChunk(maximumCount: Int.max) + expectEqual(span.count, c) + for i in 0 ..< span.count { + expectEqual(span[i].value, 100 + i) + } + } + do { + let span2 = state.nextChunk(maximumCount: Int.max) + expectEqual(span2.count, 0) + } + } + + func test_iterate_stepped() { + let c = 100 + let array = DynamicArray(count: c) { Counted($0) } + + withEvery("stride", in: 1 ... c) { stride in + var state = array.startBorrowingIteration() + var i = 0 + while true { + let span = state.nextChunk(maximumCount: stride) + if span.count == 0 { break } + expectEqual(span.count, i + stride <= c ? stride : c % stride) + for j in 0 ..< span.count { + expectEqual(span[j].value, i) + i += 1 + } + } + expectEqual(i, c) + expectEqual(state.nextChunk(maximumCount: Int.max).count, 0) + } + } +} diff --git a/Tests/FutureTests/Inout.swift b/Tests/FutureTests/Inout.swift new file mode 100644 index 000000000..8ef1a1001 --- /dev/null +++ b/Tests/FutureTests/Inout.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class FutureInoutTests: XCTestCase { + func test_basic() { + var x = 0 + let y = Inout(&x) + + var v = y[] + XCTAssertEqual(v, 0) + + y[] += 10 + + v = y[] + XCTAssertEqual(v, 10) + XCTAssertEqual(x, 10) + } +} From 64eab2812be9dd8773308b58f2b944ec69d5575c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 14:26:33 -0700 Subject: [PATCH 172/195] Move Span back under Future --- Sources/{ => Future}/Span/CMakeLists.txt | 0 Sources/{ => Future}/Span/LifetimeOverride.swift | 0 Sources/{ => Future}/Span/MutableRawSpan.swift | 0 Sources/{ => Future}/Span/MutableSpan.swift | 0 Sources/{ => Future}/Span/MutableSpanSlicing.swift | 0 Sources/{ => Future}/Span/OutputSpan.swift | 0 Sources/{ => Future}/Span/SpanExtensions.swift | 0 Sources/{ => Future}/Span/StdlibOutputSpanExtensions.swift | 0 Sources/{ => Future}/Span/UnsafeBufferPointer+Additions.swift | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename Sources/{ => Future}/Span/CMakeLists.txt (100%) rename Sources/{ => Future}/Span/LifetimeOverride.swift (100%) rename Sources/{ => Future}/Span/MutableRawSpan.swift (100%) rename Sources/{ => Future}/Span/MutableSpan.swift (100%) rename Sources/{ => Future}/Span/MutableSpanSlicing.swift (100%) rename Sources/{ => Future}/Span/OutputSpan.swift (100%) rename Sources/{ => Future}/Span/SpanExtensions.swift (100%) rename Sources/{ => Future}/Span/StdlibOutputSpanExtensions.swift (100%) rename Sources/{ => Future}/Span/UnsafeBufferPointer+Additions.swift (100%) diff --git a/Sources/Span/CMakeLists.txt b/Sources/Future/Span/CMakeLists.txt similarity index 100% rename from Sources/Span/CMakeLists.txt rename to Sources/Future/Span/CMakeLists.txt diff --git a/Sources/Span/LifetimeOverride.swift b/Sources/Future/Span/LifetimeOverride.swift similarity index 100% rename from Sources/Span/LifetimeOverride.swift rename to Sources/Future/Span/LifetimeOverride.swift diff --git a/Sources/Span/MutableRawSpan.swift b/Sources/Future/Span/MutableRawSpan.swift similarity index 100% rename from Sources/Span/MutableRawSpan.swift rename to Sources/Future/Span/MutableRawSpan.swift diff --git a/Sources/Span/MutableSpan.swift b/Sources/Future/Span/MutableSpan.swift similarity index 100% rename from Sources/Span/MutableSpan.swift rename to Sources/Future/Span/MutableSpan.swift diff --git a/Sources/Span/MutableSpanSlicing.swift b/Sources/Future/Span/MutableSpanSlicing.swift similarity index 100% rename from Sources/Span/MutableSpanSlicing.swift rename to Sources/Future/Span/MutableSpanSlicing.swift diff --git a/Sources/Span/OutputSpan.swift b/Sources/Future/Span/OutputSpan.swift similarity index 100% rename from Sources/Span/OutputSpan.swift rename to Sources/Future/Span/OutputSpan.swift diff --git a/Sources/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift similarity index 100% rename from Sources/Span/SpanExtensions.swift rename to Sources/Future/Span/SpanExtensions.swift diff --git a/Sources/Span/StdlibOutputSpanExtensions.swift b/Sources/Future/Span/StdlibOutputSpanExtensions.swift similarity index 100% rename from Sources/Span/StdlibOutputSpanExtensions.swift rename to Sources/Future/Span/StdlibOutputSpanExtensions.swift diff --git a/Sources/Span/UnsafeBufferPointer+Additions.swift b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift similarity index 100% rename from Sources/Span/UnsafeBufferPointer+Additions.swift rename to Sources/Future/Span/UnsafeBufferPointer+Additions.swift From 9e2fc09c3e97dec71fdcb98eb679b09df0c54817 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 14:42:17 -0700 Subject: [PATCH 173/195] Update Span/* to build with current snapshots --- Sources/Future/Span/LifetimeOverride.swift | 2 +- Sources/Future/Span/MutableRawSpan.swift | 2 + Sources/Future/Span/MutableSpan.swift | 21 +++++++++ Sources/Future/Span/MutableSpanSlicing.swift | 18 +++++--- Sources/Future/Span/OutputSpan.swift | 46 +++++++++++-------- Sources/Future/Span/SpanExtensions.swift | 6 +-- .../Span/StdlibOutputSpanExtensions.swift | 6 +-- .../Span/UnsafeBufferPointer+Additions.swift | 10 ++-- 8 files changed, 75 insertions(+), 36 deletions(-) diff --git a/Sources/Future/Span/LifetimeOverride.swift b/Sources/Future/Span/LifetimeOverride.swift index ed416cfa9..539409898 100644 --- a/Sources/Future/Span/LifetimeOverride.swift +++ b/Sources/Future/Span/LifetimeOverride.swift @@ -35,7 +35,7 @@ internal func _overrideLifetime< @_unsafeNonescapableResult @_alwaysEmitIntoClient @_transparent -@lifetime(source) +@lifetime(copy source) internal func _overrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable >( diff --git a/Sources/Future/Span/MutableRawSpan.swift b/Sources/Future/Span/MutableRawSpan.swift index 3a2770a14..8e5cd9f8a 100644 --- a/Sources/Future/Span/MutableRawSpan.swift +++ b/Sources/Future/Span/MutableRawSpan.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#if false // FIXME: Revive // A MutableRawSpan represents a span of memory which // contains initialized `Element` instances. @safe @@ -628,3 +629,4 @@ extension MutableRawSpan { return unsafe _overrideLifetime(newSpan, mutating: &self) } } +#endif diff --git a/Sources/Future/Span/MutableSpan.swift b/Sources/Future/Span/MutableSpan.swift index d552a93f1..1b6aefafc 100644 --- a/Sources/Future/Span/MutableSpan.swift +++ b/Sources/Future/Span/MutableSpan.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#if false // FIXME: Revive import Builtin // A MutableSpan represents a span of memory which @@ -382,6 +383,7 @@ extension MutableSpan where Element: ~Copyable { //FIXME: mark closure parameter as non-escaping @_alwaysEmitIntoClient + @lifetime(self: copy self) public func withUnsafeBufferPointer( _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result ) throws(E) -> Result { @@ -390,6 +392,7 @@ extension MutableSpan where Element: ~Copyable { //FIXME: mark closure parameter as non-escaping @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func withUnsafeMutableBufferPointer< E: Error, Result: ~Copyable >( @@ -412,6 +415,7 @@ extension MutableSpan where Element: BitwiseCopyable { //FIXME: mark closure parameter as non-escaping @_alwaysEmitIntoClient + @lifetime(self: copy self) public func withUnsafeBytes( _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { @@ -420,6 +424,7 @@ extension MutableSpan where Element: BitwiseCopyable { //FIXME: mark closure parameter as non-escaping @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func withUnsafeMutableBytes( _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws(E) -> Result ) throws(E) -> Result { @@ -436,6 +441,7 @@ extension MutableSpan where Element: BitwiseCopyable { extension MutableSpan { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update(repeating repeatedValue: consuming Element) { unsafe _start().withMemoryRebound(to: Element.self, capacity: count) { unsafe $0.update(repeating: repeatedValue, count: count) @@ -443,6 +449,7 @@ extension MutableSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( from source: S ) -> (unwritten: S.Iterator, index: Index) where S.Element == Element { @@ -452,6 +459,7 @@ extension MutableSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( from elements: inout some IteratorProtocol ) -> Index { @@ -465,6 +473,7 @@ extension MutableSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( fromContentsOf source: some Collection ) -> Index { @@ -487,6 +496,7 @@ extension MutableSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update(fromContentsOf source: Span) -> Index { guard !source.isEmpty else { return 0 } precondition( @@ -504,6 +514,7 @@ extension MutableSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( fromContentsOf source: borrowing MutableSpan ) -> Index { @@ -515,6 +526,7 @@ extension MutableSpan { extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func moveUpdate( fromContentsOf source: consuming OutputSpan ) -> Index { @@ -532,6 +544,7 @@ extension MutableSpan where Element: ~Copyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func moveUpdate( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index { @@ -544,6 +557,7 @@ extension MutableSpan where Element: ~Copyable { extension MutableSpan { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func moveUpdate( fromContentsOf source: Slice> ) -> Index { @@ -555,6 +569,7 @@ extension MutableSpan { extension MutableSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( repeating repeatedValue: Element ) where Element: BitwiseCopyable { @@ -568,6 +583,7 @@ extension MutableSpan where Element: BitwiseCopyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( from source: S ) -> (unwritten: S.Iterator, index: Index) @@ -578,6 +594,7 @@ extension MutableSpan where Element: BitwiseCopyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( from elements: inout some IteratorProtocol ) -> Index { @@ -591,6 +608,7 @@ extension MutableSpan where Element: BitwiseCopyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( fromContentsOf source: some Collection ) -> Index where Element: BitwiseCopyable { @@ -613,6 +631,7 @@ extension MutableSpan where Element: BitwiseCopyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( fromContentsOf source: Span ) -> Index where Element: BitwiseCopyable { @@ -631,6 +650,7 @@ extension MutableSpan where Element: BitwiseCopyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func update( fromContentsOf source: borrowing MutableSpan ) -> Index where Element: BitwiseCopyable { @@ -858,3 +878,4 @@ extension MutableSpan where Element: ~Copyable { return unsafe _overrideLifetime(newSpan, mutating: &self) } } +#endif diff --git a/Sources/Future/Span/MutableSpanSlicing.swift b/Sources/Future/Span/MutableSpanSlicing.swift index 7db6229b9..28feaa34c 100644 --- a/Sources/Future/Span/MutableSpanSlicing.swift +++ b/Sources/Future/Span/MutableSpanSlicing.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#if false // FIXME: Revive //MARK: Option 1 extracting() function @available(macOS 9999, *) extension MutableSpan where Element: ~Copyable { @@ -21,7 +22,7 @@ extension MutableSpan where Element: ~Copyable { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - let newSpan = MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) + let newSpan = unsafe MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) return unsafe _overrideLifetime(newSpan, mutating: &self) } } @@ -36,7 +37,7 @@ extension MutableSpan where Element: ~Copyable { ) { let delta = bounds.lowerBound &* MemoryLayout.stride let newStart = unsafe pointer.advanced(by: delta) - let newSpan = Self( + let newSpan = unsafe Self( _unchecked: newStart, count: bounds.upperBound &- bounds.lowerBound ) self = unsafe _overrideLifetime(newSpan, borrowing: pointer) @@ -51,7 +52,7 @@ extension Span where Element: ~Copyable { _unchecked pointer: UnsafeMutableRawPointer, uncheckedBounds bounds: Range ) { - let mut = MutableSpan(_unchecked: pointer, uncheckedBounds: bounds) + let mut = unsafe MutableSpan(_unchecked: pointer, uncheckedBounds: bounds) let newSpan = mut.span self = unsafe _overrideLifetime(newSpan, borrowing: pointer) } @@ -69,7 +70,7 @@ extension MutableSpan where Element: ~Copyable { UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), "Index range out of bounds" ) - let newSpan = MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) + let newSpan = unsafe MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) return unsafe _overrideLifetime(newSpan, mutating: &self) } } @@ -84,6 +85,7 @@ public struct SubMutableSpan public fileprivate(set) var offset: MutableSpan.Index public /*exclusive*/ var base: MutableSpan + @lifetime(copy _base) init(_ _offset: Index, _ _base: consuming MutableSpan) { offset = _offset base = _base @@ -100,6 +102,7 @@ extension SubMutableSpan where Element: ~Copyable { precondition(offset <= index && index < base.count) return unsafe UnsafePointer(base._unsafeAddressOfElement(unchecked: index)) } + @lifetime(self: copy self) unsafeMutableAddress { precondition(offset <= index && index < base.count) return unsafe base._unsafeAddressOfElement(unchecked: index) @@ -109,7 +112,7 @@ extension SubMutableSpan where Element: ~Copyable { public var span: Span { @lifetime(borrow self) borrowing get { - let newSpan = Span( + let newSpan = unsafe Span( _unchecked: base._start(), uncheckedBounds: offset..(bounds.lowerBound, prefix) @@ -148,6 +152,7 @@ extension MutableSpan where Element: ~Copyable { @available(macOS 9999, *) extension MutableSpan where Element: Copyable { + @lifetime(self: copy self) public mutating func update( in bounds: Range, repeating repeatedValue: consuming Element @@ -163,3 +168,4 @@ extension MutableSpan where Element: Copyable { } } } +#endif diff --git a/Sources/Future/Span/OutputSpan.swift b/Sources/Future/Span/OutputSpan.swift index 4df8661b6..d1e1cb4b2 100644 --- a/Sources/Future/Span/OutputSpan.swift +++ b/Sources/Future/Span/OutputSpan.swift @@ -56,7 +56,7 @@ public struct OutputSpan: ~Copyable, ~Escapable { capacity: Int, initialized: Int ) { - _pointer = unsafe start + unsafe _pointer = start self.capacity = capacity _initialized = initialized } @@ -75,7 +75,7 @@ extension OutputSpan where Element: ~Copyable { _unchecked buffer: UnsafeMutableBufferPointer, initialized: Int ) { - _pointer = .init(buffer.baseAddress) + unsafe _pointer = .init(buffer.baseAddress) capacity = buffer.count _initialized = initialized } @@ -91,7 +91,7 @@ extension OutputSpan where Element: ~Copyable { (MemoryLayout.alignment&-1)) == 0), "baseAddress must be properly aligned to access Element" ) - self.init(_unchecked: buffer, initialized: initialized) + unsafe self.init(_unchecked: buffer, initialized: initialized) } @_alwaysEmitIntoClient @@ -103,7 +103,7 @@ extension OutputSpan where Element: ~Copyable { ) { precondition(capacity >= 0, "Capacity must be 0 or greater") let buf = unsafe UnsafeMutableBufferPointer(start: pointer, count: capacity) - let os = OutputSpan(_initializing: buf, initialized: initialized) + let os = unsafe OutputSpan(_initializing: buf, initialized: initialized) self = unsafe _overrideLifetime(os, borrowing: pointer) } } @@ -118,7 +118,7 @@ extension OutputSpan { initialized: Int = 0 ) { let rebased = unsafe UnsafeMutableBufferPointer(rebasing: buffer) - let os = OutputSpan(_initializing: rebased, initialized: 0) + let os = unsafe OutputSpan(_initializing: rebased, initialized: 0) self = unsafe unsafe _overrideLifetime(os, borrowing: buffer) } } @@ -141,7 +141,7 @@ extension OutputSpan where Element: BitwiseCopyable { let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) precondition(remainder == 0, "Span must contain a whole number of elements") let pointer = bytes.baseAddress - let os = OutputSpan( + let os = unsafe OutputSpan( _unchecked: pointer, capacity: count, initialized: initialized ) self = unsafe _overrideLifetime(os, borrowing: bytes) @@ -156,7 +156,7 @@ extension OutputSpan where Element: BitwiseCopyable { ) { precondition(capacity >= 0, "Capacity must be 0 or greater") let buf = unsafe UnsafeMutableRawBufferPointer(start: pointer, count: capacity) - let os = OutputSpan(_initializing: buf, initialized: initialized) + let os = unsafe OutputSpan(_initializing: buf, initialized: initialized) self = unsafe _overrideLifetime(os, borrowing: pointer) } @@ -167,7 +167,7 @@ extension OutputSpan where Element: BitwiseCopyable { initialized: Int = 0 ) { let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) - let os = OutputSpan(_initializing: rebased, initialized: initialized) + let os = unsafe OutputSpan(_initializing: rebased, initialized: initialized) self = unsafe _overrideLifetime(os, borrowing: buffer) } } @@ -176,6 +176,7 @@ extension OutputSpan where Element: BitwiseCopyable { extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append(_ value: consuming Element) { precondition(_initialized < capacity, "Output buffer overflow") let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) @@ -205,6 +206,7 @@ extension OutputSpan where Element: ~Copyable { extension OutputSpan { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append(repeating repeatedValue: Element, count: Int) { let available = capacity &- _initialized precondition( @@ -220,6 +222,7 @@ extension OutputSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append( from elements: S ) -> S.Iterator where S: Sequence, S.Element == Element { @@ -229,6 +232,7 @@ extension OutputSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append( from elements: inout some IteratorProtocol ) { @@ -241,11 +245,12 @@ extension OutputSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append( fromContentsOf source: some Collection ) { let void: Void? = source.withContiguousStorageIfAvailable { - append(fromContentsOf: Span(_unsafeElements: $0)) + append(fromContentsOf: unsafe Span(_unsafeElements: $0)) } if void != nil { return @@ -267,6 +272,7 @@ extension OutputSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append( fromContentsOf source: Span ) { @@ -276,7 +282,7 @@ extension OutputSpan { "destination span cannot contain every element from source." ) let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) - _ = source.withUnsafeBufferPointer { + _ = unsafe source.withUnsafeBufferPointer { unsafe tail.initializeMemory( as: Element.self, from: $0.baseAddress!, count: $0.count ) @@ -285,8 +291,9 @@ extension OutputSpan { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func append(fromContentsOf source: borrowing MutableSpan) { - source.withUnsafeBufferPointer { append(fromContentsOf: $0) } + unsafe source.withUnsafeBufferPointer { unsafe append(fromContentsOf: $0) } } } @@ -294,6 +301,7 @@ extension OutputSpan { extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func moveAppend( fromContentsOf source: consuming Self ) { @@ -312,10 +320,11 @@ extension OutputSpan where Element: ~Copyable { } @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func moveAppend( fromContentsOf source: UnsafeMutableBufferPointer ) { - let source = OutputSpan(_initializing: source, initialized: source.count) + let source = unsafe OutputSpan(_initializing: source, initialized: source.count) moveAppend(fromContentsOf: source) } } @@ -324,11 +333,12 @@ extension OutputSpan where Element: ~Copyable { extension OutputSpan { @_alwaysEmitIntoClient + @lifetime(self: copy self) public mutating func moveAppend( fromContentsOf source: Slice> ) { - moveAppend( - fromContentsOf: unsafe UnsafeMutableBufferPointer(rebasing: source) + unsafe moveAppend( + fromContentsOf: UnsafeMutableBufferPointer(rebasing: source) ) } } @@ -347,7 +357,7 @@ extension OutputSpan where Element: ~Copyable { borrowing get { let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) let buffer = unsafe UnsafeBufferPointer(start: pointer, count: _initialized) - let span = Span(_unsafeElements: buffer) + let span = unsafe Span(_unsafeElements: buffer) return unsafe _overrideLifetime(span, borrowing: self) } } @@ -360,7 +370,7 @@ extension OutputSpan where Element: ~Copyable { let buffer = unsafe UnsafeMutableBufferPointer( start: pointer, count: _initialized ) - let span = MutableSpan(_unsafeElements: buffer) + let span = unsafe MutableSpan(_unsafeElements: buffer) return unsafe _overrideLifetime(span, mutating: &self) } } @@ -373,7 +383,7 @@ extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient public consuming func relinquishBorrowedMemory( ) -> UnsafeMutableBufferPointer { - let (start, count) = (self._pointer, self._initialized) + let (start, count) = unsafe (self._pointer, self._initialized) discard self let typed = unsafe start?.bindMemory(to: Element.self, capacity: count) return unsafe UnsafeMutableBufferPointer(start: typed, count: count) @@ -387,7 +397,7 @@ extension OutputSpan where Element: BitwiseCopyable { @_alwaysEmitIntoClient public consuming func relinquishBorrowedBytes( ) -> UnsafeMutableRawBufferPointer { - let (start, count) = (self._pointer, self._initialized) + let (start, count) = unsafe (self._pointer, self._initialized) discard self let byteCount = count&*MemoryLayout.stride return unsafe UnsafeMutableRawBufferPointer(start: start, count: byteCount) diff --git a/Sources/Future/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift index c08065470..9cd081b1b 100644 --- a/Sources/Future/Span/SpanExtensions.swift +++ b/Sources/Future/Span/SpanExtensions.swift @@ -20,7 +20,7 @@ extension Span { @lifetime(immortal) get { let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) - let span = Span(_unsafeElements: empty) + let span = unsafe Span(_unsafeElements: empty) return unsafe _overrideLifetime(span, borrowing: immortalThing) } } @@ -29,7 +29,7 @@ extension Span { @lifetime(immortal) public init() { let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) - let span = Span(_unsafeElements: empty) + let span = unsafe Span(_unsafeElements: empty) self = unsafe _overrideLifetime(span, borrowing: immortalThing) } } @@ -77,7 +77,7 @@ extension Span where Element: Equatable { @_alwaysEmitIntoClient public func _elementsEqual(_ other: some Collection) -> Bool { let equal = other.withContiguousStorageIfAvailable { - _elementsEqual(Span(_unsafeElements: $0)) + _elementsEqual(unsafe Span(_unsafeElements: $0)) } if let equal { return equal } diff --git a/Sources/Future/Span/StdlibOutputSpanExtensions.swift b/Sources/Future/Span/StdlibOutputSpanExtensions.swift index fce41a049..5a81b5bc5 100644 --- a/Sources/Future/Span/StdlibOutputSpanExtensions.swift +++ b/Sources/Future/Span/StdlibOutputSpanExtensions.swift @@ -21,7 +21,7 @@ extension Array { unsafeUninitializedCapacity: capacity, initializingWith: { (buffer, count) in let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped - var output = OutputSpan( + var output = unsafe OutputSpan( _initializing: pointer, capacity: buffer.count ) try initializer(&output) @@ -47,7 +47,7 @@ extension String { unsafeUninitializedCapacity: capacity, initializingUTF8With: { buffer in let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped - var output = OutputSpan( + var output = unsafe OutputSpan( _initializing: pointer, capacity: buffer.count ) try initializer(&output) @@ -73,7 +73,7 @@ extension Data { unsafe try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in unsafe buffer.deinitialize() let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped - var output = OutputSpan( + var output = unsafe OutputSpan( _initializing: pointer, capacity: capacity ) try initializer(&output) diff --git a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift index 7b7a5e96f..60b683a9f 100644 --- a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift +++ b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 @@ -14,7 +14,7 @@ extension UnsafeBufferPointer where Element: ~Copyable { /// instances refer to the same region in memory. @inlinable @inline(__always) public static func ===(_ a: Self, _ b: Self) -> Bool { - (a.baseAddress == b.baseAddress) && (a.count == b.count) + unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) } } @@ -24,7 +24,7 @@ extension UnsafeMutableBufferPointer where Element: ~Copyable { /// memory. @inlinable @inline(__always) public static func ===(_ a: Self, _ b: Self) -> Bool { - (a.baseAddress == b.baseAddress) && (a.count == b.count) + unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) } } @@ -33,7 +33,7 @@ extension UnsafeRawBufferPointer { /// instances refer to the same region in memory. @inlinable @inline(__always) public static func ===(_ a: Self, _ b: Self) -> Bool { - (a.baseAddress == b.baseAddress) && (a.count == b.count) + unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) } } @@ -43,6 +43,6 @@ extension UnsafeMutableRawBufferPointer { /// memory. @inlinable @inline(__always) public static func ===(_ a: Self, _ b: Self) -> Bool { - (a.baseAddress == b.baseAddress) && (a.count == b.count) + unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) } } From 800dca761785b8ee4b7a3ebb9ee6116b6a4e7f49 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 14:52:31 -0700 Subject: [PATCH 174/195] Make CMake config more plausible --- Sources/Future/CMakeLists.txt | 26 ++++++++++++++++++-------- Sources/Future/Span/CMakeLists.txt | 27 --------------------------- 2 files changed, 18 insertions(+), 35 deletions(-) delete mode 100644 Sources/Future/Span/CMakeLists.txt diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 4d5ff5307..1f8641461 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -8,14 +8,24 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(Future - "LifetimeOverride.swift" - "MutableRawSpan.swift" - "MutableSpan.swift" - "MutableSpanSlicing.swift" - "OutputSpan.swift" - "SpanExtensions.swift" - "StdlibOutputSpanExtensions.swift" - "UnsafeBufferPointer+Additions.swift" + "Span/LifetimeOverride.swift" + "Span/MutableRawSpan.swift" + "Span/MutableSpan.swift" + "Span/MutableSpanSlicing.swift" + "Span/OutputSpan.swift" + "Span/SpanExtensions.swift" + "Span/StdlibOutputSpanExtensions.swift" + "Span/UnsafeBufferPointer+Additions.swift" + "Containers/Container.swift" + "Containers/DynamicArray.swift" + "Containers/NewArray.swift" + "Containers/RigidArray.swift" + "Containers/Shared.swift" + "Box.swift" + "Cell.swift" + "ContiguousStorage.swift" + "Inout.swift" + "Span+Iterator.swift" ) target_link_libraries(Future PRIVATE diff --git a/Sources/Future/Span/CMakeLists.txt b/Sources/Future/Span/CMakeLists.txt deleted file mode 100644 index 4d5ff5307..000000000 --- a/Sources/Future/Span/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -#[[ -This source file is part of the Swift Collections Open Source Project - -Copyright (c) 2024 - 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 -#]] - -add_library(Future - "LifetimeOverride.swift" - "MutableRawSpan.swift" - "MutableSpan.swift" - "MutableSpanSlicing.swift" - "OutputSpan.swift" - "SpanExtensions.swift" - "StdlibOutputSpanExtensions.swift" - "UnsafeBufferPointer+Additions.swift" -) - -target_link_libraries(Future PRIVATE - InternalCollectionsUtilities) -set_target_properties(Future PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) - -_install_target(Future) -set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS Future) From 9eaf5260ed8f479a4b3b641b643c9bd992ebf9e1 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 15:58:16 -0700 Subject: [PATCH 175/195] Remove Mutable[Raw]Span These types have landed in the stdlib now. --- Sources/Future/Span/MutableRawSpan.swift | 632 ------------- Sources/Future/Span/MutableSpan.swift | 881 ------------------- Sources/Future/Span/MutableSpanSlicing.swift | 171 ---- 3 files changed, 1684 deletions(-) delete mode 100644 Sources/Future/Span/MutableRawSpan.swift delete mode 100644 Sources/Future/Span/MutableSpan.swift delete mode 100644 Sources/Future/Span/MutableSpanSlicing.swift diff --git a/Sources/Future/Span/MutableRawSpan.swift b/Sources/Future/Span/MutableRawSpan.swift deleted file mode 100644 index 8e5cd9f8a..000000000 --- a/Sources/Future/Span/MutableRawSpan.swift +++ /dev/null @@ -1,632 +0,0 @@ -//===--- MutableRawSpan.swift ---------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -#if false // FIXME: Revive -// A MutableRawSpan represents a span of memory which -// contains initialized `Element` instances. -@safe -@frozen -@available(macOS 9999, *) -public struct MutableRawSpan: ~Copyable & ~Escapable { - @usableFromInline - internal let _pointer: UnsafeMutableRawPointer? - - @usableFromInline - internal let _count: Int - - @_alwaysEmitIntoClient - internal func _start() -> UnsafeMutableRawPointer { - unsafe _pointer.unsafelyUnwrapped - } - - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - internal init( - _unchecked pointer: UnsafeMutableRawPointer?, - byteCount: Int - ) { - _pointer = unsafe pointer - _count = byteCount - } -} - -@available(macOS 9999, *) -extension MutableRawSpan: @unchecked Sendable {} - -@available(macOS 9999, *) -extension MutableRawSpan { - - @_alwaysEmitIntoClient - @lifetime(borrow bytes) - public init( - _unsafeBytes bytes: UnsafeMutableRawBufferPointer - ) { - let baseAddress = bytes.baseAddress - let span = MutableRawSpan(_unchecked: baseAddress, byteCount: bytes.count) - self = unsafe _overrideLifetime(span, borrowing: bytes) - } - - @_alwaysEmitIntoClient - @lifetime(borrow bytes) - public init( - _unsafeBytes bytes: borrowing Slice - ) { - let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: bytes) - let span = MutableRawSpan(_unsafeBytes: rebased) - self = unsafe _overrideLifetime(span, borrowing: bytes) - } - - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - public init( - _unsafeStart pointer: UnsafeMutableRawPointer, - byteCount: Int - ) { - precondition(byteCount >= 0, "Count must not be negative") - self.init(_unchecked: pointer, byteCount: byteCount) - } - - @_alwaysEmitIntoClient - @lifetime(borrow elements) - public init( - _unsafeElements elements: UnsafeMutableBufferPointer - ) { - let bytes = UnsafeMutableRawBufferPointer(elements) - let span = MutableRawSpan(_unsafeBytes: bytes) - self = unsafe _overrideLifetime(span, borrowing: elements) - } - - @_alwaysEmitIntoClient - @lifetime(borrow elements) - public init( - _unsafeElements elements: borrowing Slice> - ) { - let rebased = unsafe UnsafeMutableBufferPointer(rebasing: elements) - let span = MutableRawSpan(_unsafeElements: rebased) - self = unsafe _overrideLifetime(span, borrowing: elements) - } - - @_alwaysEmitIntoClient - @lifetime(elements) - public init( - _elements elements: consuming MutableSpan - ) { - let bytes = unsafe UnsafeMutableRawBufferPointer( - start: elements._pointer, - count: elements.count &* MemoryLayout.stride - ) - let span = MutableRawSpan(_unsafeBytes: bytes) - self = unsafe _overrideLifetime(span, copying: elements) - } -} - -@available(macOS 9999, *) -extension MutableRawSpan { - @_alwaysEmitIntoClient - public var byteCount: Int { _count } - - @_alwaysEmitIntoClient - public var isEmpty: Bool { byteCount == 0 } - - @_alwaysEmitIntoClient - public var byteOffsets: Range { - unsafe Range(uncheckedBounds: (0, byteCount)) - } -} - -@available(macOS 9999, *) -extension MutableRawSpan { - - @_alwaysEmitIntoClient - public func withUnsafeBytes( - _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - guard let pointer = _pointer, _count > 0 else { - return try unsafe body(.init(start: nil, count: 0)) - } - return try unsafe body(.init(start: pointer, count: _count)) - } - - @_alwaysEmitIntoClient - public mutating func withUnsafeMutableBytes( - _ body: (UnsafeMutableRawBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - guard let pointer = _pointer, _count > 0 else { - return try unsafe body(.init(start: nil, count: 0)) - } - return try unsafe body(.init(start: pointer, count: _count)) - } -} - -@available(macOS 9999, *) -extension RawSpan { - - @_alwaysEmitIntoClient - @lifetime(borrow mutableSpan) - public init(_unsafeMutableRawSpan mutableSpan: borrowing MutableRawSpan) { - let start = mutableSpan._start() - let span = RawSpan(_unsafeStart: start, byteCount: mutableSpan.byteCount) - self = unsafe _overrideLifetime(span, borrowing: mutableSpan) - } -} - -@available(macOS 9999, *) -extension MutableRawSpan { - - public var bytes: RawSpan { - @_alwaysEmitIntoClient - @lifetime(borrow self) - borrowing get { - return RawSpan(_unsafeMutableRawSpan: self) - } - } - - @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - public borrowing func _unsafeView( - as type: T.Type - ) -> Span { - let bytes = unsafe UnsafeRawBufferPointer(start: _pointer, count: _count) - let span = Span(_unsafeBytes: bytes) - return unsafe _overrideLifetime(span, borrowing: self) - } - - @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - public mutating func _unsafeMutableView( - as type: T.Type - ) -> MutableSpan { - let bytes = unsafe UnsafeMutableRawBufferPointer( - start: _pointer, count: _count - ) - let span = MutableSpan(_unsafeBytes: bytes) - return unsafe _overrideLifetime(span, mutating: &self) - } -} - -@available(macOS 9999, *) -extension MutableRawSpan { - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be properly aligned for - /// accessing `T` and initialized to `T` or another type that is layout - /// compatible with `T`. - /// - /// This is an unsafe operation. Failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance is memory-managed and unassociated - /// with the value in the memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoad( - fromByteOffset offset: Int = 0, as: T.Type - ) -> T { - precondition( - UInt(bitPattern: offset) <= UInt(bitPattern: _count) && - MemoryLayout.size <= (_count &- offset), - "Byte offset range out of bounds" - ) - return unsafe unsafeLoad(fromUncheckedByteOffset: offset, as: T.self) - } - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be properly aligned for - /// accessing `T` and initialized to `T` or another type that is layout - /// compatible with `T`. - /// - /// This is an unsafe operation. This function does not validate the bounds - /// of the memory access, and failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance is memory-managed and unassociated - /// with the value in the memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoad( - fromUncheckedByteOffset offset: Int, as: T.Type - ) -> T { - unsafe _start().load(fromByteOffset: offset, as: T.self) - } - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be initialized to `T` - /// or another type that is layout compatible with `T`. - /// - /// This is an unsafe operation. Failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance isn't associated - /// with the value in the range of memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoadUnaligned( - fromByteOffset offset: Int = 0, as: T.Type - ) -> T { - precondition( - UInt(bitPattern: offset) <= UInt(bitPattern: _count) && - MemoryLayout.size <= (_count &- offset), - "Byte offset range out of bounds" - ) - return unsafe unsafeLoadUnaligned(fromUncheckedByteOffset: offset, as: T.self) - } - - /// Returns a new instance of the given type, constructed from the raw memory - /// at the specified offset. - /// - /// The memory at this pointer plus `offset` must be initialized to `T` - /// or another type that is layout compatible with `T`. - /// - /// This is an unsafe operation. This function does not validate the bounds - /// of the memory access, and failure to meet the preconditions - /// above may produce an invalid value of `T`. - /// - /// - Parameters: - /// - offset: The offset from this pointer, in bytes. `offset` must be - /// nonnegative. The default is zero. - /// - type: The type of the instance to create. - /// - Returns: A new instance of type `T`, read from the raw bytes at - /// `offset`. The returned instance isn't associated - /// with the value in the range of memory referenced by this pointer. - @unsafe - @_alwaysEmitIntoClient - public func unsafeLoadUnaligned( - fromUncheckedByteOffset offset: Int, as: T.Type - ) -> T { - unsafe _start().loadUnaligned(fromByteOffset: offset, as: T.self) - } - - @_alwaysEmitIntoClient - public mutating func storeBytes( - of value: T, toByteOffset offset: Int = 0, as type: T.Type - ) { - precondition( - UInt(bitPattern: offset) <= UInt(bitPattern: _count) && - MemoryLayout.size <= (_count &- offset), - "Byte offset range out of bounds" - ) - unsafe storeBytes(of: value, toUncheckedByteOffset: offset, as: type) - } - - @unsafe - @_alwaysEmitIntoClient - public mutating func storeBytes( - of value: T, toUncheckedByteOffset offset: Int, as type: T.Type - ) { - unsafe _start().storeBytes(of: value, toByteOffset: offset, as: type) - } -} - -//MARK: copyMemory -@available(macOS 9999, *) -extension MutableRawSpan { - - @_alwaysEmitIntoClient - public mutating func update( - from source: S - ) -> (unwritten: S.Iterator, byteOffset: Int) where S.Element: BitwiseCopyable { - var iterator = source.makeIterator() - let offset = update(from: &iterator) - return (iterator, offset) - } - - @_alwaysEmitIntoClient - public mutating func update( - from elements: inout some IteratorProtocol - ) -> Int { - var offset = 0 - while offset + MemoryLayout.stride <= _count { - guard let element = elements.next() else { break } - unsafe storeBytes( - of: element, toUncheckedByteOffset: offset, as: Element.self - ) - offset &+= MemoryLayout.stride - } - return offset - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: C - ) -> Int where C.Element: BitwiseCopyable { - let newOffset = source.withContiguousStorageIfAvailable { - self.update(fromContentsOf: RawSpan(_unsafeElements: $0)) - } - if let newOffset { return newOffset } - - var elements = source.makeIterator() - let lastOffset = update(from: &elements) - precondition( - elements.next() == nil, - "destination span cannot contain every element from source." - ) - return lastOffset - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: Span - ) -> Int { -// update(from: source.bytes) - source.withUnsafeBytes { - update(fromContentsOf: $0) - } - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: borrowing MutableSpan - ) -> Int { -// update(from: source.span.bytes) - source.withUnsafeBytes { - update(fromContentsOf: $0) - } - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: RawSpan - ) -> Int { - if source.byteCount == 0 { return 0 } - source.withUnsafeBytes { - unsafe _start().copyMemory(from: $0.baseAddress!, byteCount: $0.count) - } - return source.byteCount - } - - @_alwaysEmitIntoClient - public mutating func update( - fromContentsOf source: borrowing MutableRawSpan - ) -> Int { - update(fromContentsOf: source.bytes) - } -} - -// MARK: sub-spans -@available(macOS 9999, *) -extension MutableRawSpan { - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(_ bounds: Range) -> Self { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - return unsafe _extracting(unchecked: bounds) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(unchecked bounds: Range) -> Self { - let newStart = unsafe _pointer?.advanced(by: bounds.lowerBound) - let newSpan = Self(_unchecked: newStart, byteCount: bounds.count) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting( - _ bounds: some RangeExpression - ) -> Self { - _extracting(bounds.relative(to: byteOffsets)) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(unchecked bounds: ClosedRange) -> Self { - let range = unsafe Range( - uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1) - ) - return unsafe _extracting(unchecked: range) - } - - /// Constructs a new span over all the items of this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Returns: A `MutableSpan` over all the items of this span. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(_: UnboundedRange) -> Self { - let newSpan = Self(_unchecked: _start(), byteCount: _count) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } -} - -// MARK: prefixes and suffixes -@available(macOS 9999, *) -extension MutableRawSpan { - - /// Returns a span containing the initial elements of this span, - /// up to the specified maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the elements. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of elements to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(first maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a prefix of negative length") - let newCount = min(maxLength, byteCount) - let newSpan = Self(_unchecked: _pointer, byteCount: newCount) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Returns a span over all but the given number of trailing elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of elements to drop off the end of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span leaving off the specified number of elements at the end. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(droppingLast k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let dropped = min(k, byteCount) - let newSpan = Self(_unchecked: _pointer, byteCount: byteCount &- dropped) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Returns a span containing the final elements of the span, - /// up to the given maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the elements. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of elements to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(last maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a suffix of negative length") - let newCount = min(maxLength, byteCount) - let newStart = unsafe _pointer?.advanced(by: byteCount &- newCount) - let newSpan = Self(_unchecked: newStart, byteCount: newCount) - return unsafe _overrideLifetime(newSpan, copying: self) - } - - /// Returns a span over all but the given number of initial elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of elements to drop from the beginning of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span starting after the specified number of elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(droppingFirst k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of bytes") - let dropped = min(k, byteCount) - let newStart = unsafe _pointer?.advanced(by: dropped) - let newSpan = Self(_unchecked: newStart, byteCount: byteCount &- dropped) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } -} -#endif diff --git a/Sources/Future/Span/MutableSpan.swift b/Sources/Future/Span/MutableSpan.swift deleted file mode 100644 index 1b6aefafc..000000000 --- a/Sources/Future/Span/MutableSpan.swift +++ /dev/null @@ -1,881 +0,0 @@ -//===--- MutableSpan.swift ------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -#if false // FIXME: Revive -import Builtin - -// A MutableSpan represents a span of memory which -// contains initialized `Element` instances. -@safe -@frozen -@available(macOS 9999, *) -public struct MutableSpan -: ~Copyable, ~Escapable { - @usableFromInline - internal let _pointer: UnsafeMutableRawPointer? - - @usableFromInline - internal let _count: Int - - @_alwaysEmitIntoClient - internal func _start() -> UnsafeMutableRawPointer { - unsafe _pointer.unsafelyUnwrapped - } - - @_alwaysEmitIntoClient - @lifetime(borrow start) - internal init( - _unchecked start: UnsafeMutableRawPointer?, - count: Int - ) { - _pointer = unsafe start - _count = count - } -} - -@available(macOS 9999, *) -extension MutableSpan: @unchecked Sendable where Element: Sendable {} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - @usableFromInline - @lifetime(borrow elements) - internal init( - _unchecked elements: UnsafeMutableBufferPointer - ) { - _pointer = .init(elements.baseAddress) - _count = elements.count - } - - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeElements buffer: UnsafeMutableBufferPointer - ) { - precondition( - ((Int(bitPattern: buffer.baseAddress) & - (MemoryLayout.alignment &- 1)) == 0), - "baseAddress must be properly aligned to access Element" - ) - let ms = MutableSpan(_unchecked: buffer) - self = unsafe _overrideLifetime(ms, borrowing: buffer) - } - - @_alwaysEmitIntoClient - @lifetime(borrow start) - public init( - _unsafeStart start: UnsafeMutablePointer, - count: Int - ) { - precondition(count >= 0, "Count must not be negative") - let buffer = unsafe UnsafeMutableBufferPointer(start: start, count: count) - let ms = MutableSpan(_unsafeElements: buffer) - self = unsafe _overrideLifetime(ms, borrowing: start) - } -} - -@available(macOS 9999, *) -extension MutableSpan { - - @_alwaysEmitIntoClient - @lifetime(borrow elements) - public init( - _unsafeElements elements: borrowing Slice> - ) { - let rb = unsafe UnsafeMutableBufferPointer(rebasing: elements) - let ms = MutableSpan(_unsafeElements: rb) - self = unsafe _overrideLifetime(ms, borrowing: elements) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: BitwiseCopyable { - - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: UnsafeMutableRawBufferPointer - ) { - precondition( - ((Int(bitPattern: buffer.baseAddress) & - (MemoryLayout.alignment &- 1)) == 0), - "baseAddress must be properly aligned to access Element" - ) - let (byteCount, stride) = (buffer.count, MemoryLayout.stride) - let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) - precondition(remainder == 0, "Span must contain a whole number of elements") - let elements = unsafe UnsafeMutableBufferPointer( - start: buffer.baseAddress?.assumingMemoryBound(to: Element.self), - count: count - ) - let ms = MutableSpan(_unsafeElements: elements) - self = unsafe _overrideLifetime(ms, borrowing: buffer) - } - - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - public init( - _unsafeStart pointer: UnsafeMutableRawPointer, - byteCount: Int - ) { - precondition(byteCount >= 0, "Count must not be negative") - let bytes = unsafe UnsafeMutableRawBufferPointer( - start: pointer, count: byteCount - ) - let ms = MutableSpan(_unsafeBytes: bytes) - self = unsafe _overrideLifetime(ms, borrowing: pointer) - } - - @_alwaysEmitIntoClient - @lifetime(borrow buffer) - public init( - _unsafeBytes buffer: borrowing Slice - ) { - let bytes = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) - let ms = MutableSpan(_unsafeBytes: bytes) - self = unsafe _overrideLifetime(ms, borrowing: buffer) - } -} - -@available(macOS 9999, *) -extension Span where Element: ~Copyable { - - @_alwaysEmitIntoClient - @lifetime(borrow mutableSpan) - public init(_unsafeMutableSpan mutableSpan: borrowing MutableSpan) { - let pointer = - unsafe mutableSpan._pointer?.assumingMemoryBound(to: Element.self) - let buffer = unsafe UnsafeBufferPointer( - start: pointer, count: mutableSpan.count - ) - let span = Span(_unsafeElements: buffer) - self = unsafe _overrideLifetime(span, borrowing: mutableSpan) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - @_alwaysEmitIntoClient - public var span: Span { - @lifetime(borrow self) - borrowing get { - Span(_unsafeMutableSpan: self) - } - } -} - -@available(macOS 9999, *) -extension RawSpan { - - @_alwaysEmitIntoClient - public init( - _unsafeMutableSpan mutableSpan: borrowing MutableSpan - ) { - let pointer = mutableSpan._pointer - let byteCount = mutableSpan.count &* MemoryLayout.stride - let buffer = unsafe UnsafeRawBufferPointer(start: pointer, count: byteCount) - let rawSpan = RawSpan(_unsafeBytes: buffer) - self = unsafe _overrideLifetime(rawSpan, borrowing: mutableSpan) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: Equatable { - - @_alwaysEmitIntoClient - public func _elementsEqual(_ other: borrowing Self) -> Bool { - _elementsEqual(Span(_unsafeMutableSpan: other)) - } - - @_alwaysEmitIntoClient - public func _elementsEqual(_ other: Span) -> Bool { - Span(_unsafeMutableSpan: self)._elementsEqual(other) - } - - @_alwaysEmitIntoClient - public func _elementsEqual(_ other: some Collection) -> Bool { - Span(_unsafeMutableSpan: self)._elementsEqual(other) - } - - @_alwaysEmitIntoClient - public func _elementsEqual(_ other: some Sequence) -> Bool { - Span(_unsafeMutableSpan: self)._elementsEqual(other) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - @_alwaysEmitIntoClient - public var _description: String { - let addr = String( - UInt(bitPattern: _pointer), radix: 16, uppercase: false - ) - return "(0x\(addr), \(_count))" - } -} - -//MARK: Collection, RandomAccessCollection -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable & ~Escapable { - - @_alwaysEmitIntoClient - public var count: Int { _count } - - @_alwaysEmitIntoClient - public var isEmpty: Bool { _count == 0 } - - public typealias Index = Int - - @_alwaysEmitIntoClient - public var indices: Range { - unsafe Range(uncheckedBounds: (0, _count)) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: BitwiseCopyable { - - /// Construct a RawSpan over the memory represented by this span - /// - /// - Returns: a RawSpan over the memory represented by this span - @_alwaysEmitIntoClient - public var bytes: RawSpan { - @lifetime(borrow self) - borrowing get { - RawSpan(_unsafeMutableSpan: self) - } - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - /// Accesses the element at the specified position in the `Span`. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public subscript(_ position: Index) -> Element { - unsafeAddress { - precondition(indices.contains(position), "index out of bounds") - return unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: position)) - } - unsafeMutableAddress { - precondition(indices.contains(position), "index out of bounds") - return unsafe _unsafeAddressOfElement(unchecked: position) - } - } - - /// Accesses the element at the specified position in the `Span`. - /// - /// This subscript does not validate `position`; this is an unsafe operation. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - public subscript(unchecked position: Index) -> Element { - unsafeAddress { - unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: position)) - } - unsafeMutableAddress { - unsafe _unsafeAddressOfElement(unchecked: position) - } - } - - @unsafe - @_alwaysEmitIntoClient - internal func _unsafeAddressOfElement( - unchecked position: Index - ) -> UnsafeMutablePointer { - let elementOffset = position &* MemoryLayout.stride - let address = unsafe _start().advanced(by: elementOffset) - return unsafe address.assumingMemoryBound(to: Element.self) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - @_alwaysEmitIntoClient - public mutating func swapAt(_ i: Index, _ j: Index) { - precondition(indices.contains(Index(i))) - precondition(indices.contains(Index(j))) - unsafe swapAt(unchecked: i, unchecked: j) - } - - @unsafe - @_alwaysEmitIntoClient - public mutating func swapAt(unchecked i: Index, unchecked j: Index) { - let pi = unsafe _unsafeAddressOfElement(unchecked: i) - let pj = unsafe _unsafeAddressOfElement(unchecked: j) - let temporary = unsafe pi.move() - unsafe pi.initialize(to: pj.move()) - unsafe pj.initialize(to: consume temporary) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: BitwiseCopyable { - - /// Accesses the element at the specified position in the `Span`. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - public subscript(_ position: Index) -> Element { - get { - precondition(indices.contains(position), "index out of bounds") - return unsafe self[unchecked: position] - } - set { - precondition(indices.contains(position), "index out of bounds") - unsafe self[unchecked: position] = newValue - } - } - - /// Accesses the element at the specified position in the `Span`. - /// - /// This subscript does not validate `position`; this is an unsafe operation. - /// - /// - Parameter position: The offset of the element to access. `position` - /// must be greater or equal to zero, and less than `count`. - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - public subscript(unchecked position: Index) -> Element { - get { - let offset = position&*MemoryLayout.stride - return unsafe _start().loadUnaligned( - fromByteOffset: offset, as: Element.self - ) - } - set { - let offset = position&*MemoryLayout.stride - unsafe _start().storeBytes( - of: newValue, toByteOffset: offset, as: Element.self - ) - } - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - //FIXME: mark closure parameter as non-escaping - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public func withUnsafeBufferPointer( - _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - try Span(_unsafeMutableSpan: self).withUnsafeBufferPointer(body) - } - - //FIXME: mark closure parameter as non-escaping - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func withUnsafeMutableBufferPointer< - E: Error, Result: ~Copyable - >( - _ body: (UnsafeMutableBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - guard let pointer = _pointer, count > 0 else { - return try unsafe body(.init(start: nil, count: 0)) - } - // bind memory by hand to sidestep alignment concerns - let binding = Builtin.bindMemory( - pointer._rawValue, count._builtinWordValue, Element.self - ) - defer { Builtin.rebindMemory(pointer._rawValue, binding) } - return try unsafe body(.init(start: .init(pointer._rawValue), count: count)) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: BitwiseCopyable { - - //FIXME: mark closure parameter as non-escaping - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public func withUnsafeBytes( - _ body: (_ buffer: UnsafeRawBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - try RawSpan(_unsafeMutableSpan: self).withUnsafeBytes(body) - } - - //FIXME: mark closure parameter as non-escaping - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func withUnsafeMutableBytes( - _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws(E) -> Result - ) throws(E) -> Result { - let bytes = unsafe UnsafeMutableRawBufferPointer( - start: (_count == 0) ? nil : _start(), - count: _count &* MemoryLayout.stride - ) - return try unsafe body(bytes) - } -} - -//MARK: bulk-update functions -@available(macOS 9999, *) -extension MutableSpan { - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update(repeating repeatedValue: consuming Element) { - unsafe _start().withMemoryRebound(to: Element.self, capacity: count) { - unsafe $0.update(repeating: repeatedValue, count: count) - } - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - from source: S - ) -> (unwritten: S.Iterator, index: Index) where S.Element == Element { - var iterator = source.makeIterator() - let index = update(from: &iterator) - return (iterator, index) - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - from elements: inout some IteratorProtocol - ) -> Index { - var index = 0 - while index < _count { - guard let element = elements.next() else { break } - unsafe self[unchecked: index] = element - index &+= 1 - } - return index - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: some Collection - ) -> Index { - let updated = source.withContiguousStorageIfAvailable { - self.update(fromContentsOf: Span(_unsafeElements: $0)) - } - if let updated { - return updated - } - - //TODO: use _copyContents here - - var iterator = source.makeIterator() - let index = update(from: &iterator) - precondition( - iterator.next() == nil, - "destination buffer view cannot contain every element from source." - ) - return index - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update(fromContentsOf source: Span) -> Index { - guard !source.isEmpty else { return 0 } - precondition( - source.count <= self.count, - "destination span cannot contain every element from source." - ) - unsafe _start().withMemoryRebound( - to: Element.self, capacity: source.count - ) { dest in - source.withUnsafeBufferPointer { - unsafe dest.update(from: $0.baseAddress!, count: $0.count) - } - } - return source.count - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: borrowing MutableSpan - ) -> Index { - update(fromContentsOf: source.span) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func moveUpdate( - fromContentsOf source: consuming OutputSpan - ) -> Index { - guard !source.isEmpty else { return 0 } - precondition( - source.count <= self.count, - "destination span cannot contain every element from source." - ) - let buffer = unsafe source.relinquishBorrowedMemory() - // we must now deinitialize the returned UMBP - unsafe _start().moveInitializeMemory( - as: Element.self, from: buffer.baseAddress!, count: buffer.count - ) - return buffer.count - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func moveUpdate( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index { - let s = OutputSpan(_initializing: source, initialized: source.count) - return self.moveUpdate(fromContentsOf: s) - } -} - -@available(macOS 9999, *) -extension MutableSpan { - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func moveUpdate( - fromContentsOf source: Slice> - ) -> Index { - self.moveUpdate(fromContentsOf: unsafe .init(rebasing: source)) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: BitwiseCopyable { - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - repeating repeatedValue: Element - ) where Element: BitwiseCopyable { - guard count > 0 else { return } - // rebind _start manually in order to avoid assumptions about alignment. - let rp = _start()._rawValue - let binding = Builtin.bindMemory(rp, count._builtinWordValue, Element.self) - let rebound = unsafe UnsafeMutablePointer(rp) - unsafe rebound.update(repeating: repeatedValue, count: count) - Builtin.rebindMemory(rp, binding) - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - from source: S - ) -> (unwritten: S.Iterator, index: Index) - where S.Element == Element, Element: BitwiseCopyable { - var iterator = source.makeIterator() - let index = update(from: &iterator) - return (iterator, index) - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - from elements: inout some IteratorProtocol - ) -> Index { - var index = 0 - while index < _count { - guard let element = elements.next() else { break } - unsafe self[unchecked: index] = element - index &+= 1 - } - return index - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: some Collection - ) -> Index where Element: BitwiseCopyable { - let updated = source.withContiguousStorageIfAvailable { - self.update(fromContentsOf: Span(_unsafeElements: $0)) - } - if let updated { - return updated - } - - //TODO: use _copyContents here - - var iterator = source.makeIterator() - let index = update(from: &iterator) - precondition( - iterator.next() == nil, - "destination buffer view cannot contain every element from source." - ) - return index - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: Span - ) -> Index where Element: BitwiseCopyable { - guard !source.isEmpty else { return 0 } - precondition( - source.count <= self.count, - "destination span cannot contain every element from source." - ) - source.withUnsafeBufferPointer { - unsafe _start().copyMemory( - from: $0.baseAddress!, - byteCount: $0.count &* MemoryLayout.stride - ) - } - return source.count - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: borrowing MutableSpan - ) -> Index where Element: BitwiseCopyable { - update(fromContentsOf: source.span) - } -} - -// MARK: sub-spans -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(_ bounds: Range) -> Self { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - return unsafe _extracting(unchecked: bounds) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(unchecked bounds: Range) -> Self { - let delta = bounds.lowerBound &* MemoryLayout.stride - let newStart = unsafe _pointer?.advanced(by: delta) - let newSpan = Self(_unchecked: newStart, count: bounds.count) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting( - _ bounds: some RangeExpression - ) -> Self { - _extracting(bounds.relative(to: indices)) - } - - /// Constructs a new span over the items within the supplied range of - /// positions within this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// This function does not validate `bounds`; this is an unsafe operation. - /// - /// - Parameter bounds: A valid range of positions. Every position in - /// this range must be within the bounds of this `MutableSpan`. - /// - /// - Returns: A `MutableSpan` over the items within `bounds` - /// - /// - Complexity: O(1) - @unsafe - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting( - unchecked bounds: ClosedRange - ) -> Self { - let range = unsafe Range( - uncheckedBounds: (bounds.lowerBound, bounds.upperBound&+1) - ) - return unsafe _extracting(unchecked: range) - } - - /// Constructs a new span over all the items of this span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Returns: A `MutableSpan` over all the items of this span. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(_: UnboundedRange) -> Self { - let newSpan = Self(_unchecked: _start(), count: _count) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } -} - -// MARK: prefixes and suffixes -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - /// Returns a span containing the initial elements of this span, - /// up to the specified maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the elements. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of elements to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(first maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a prefix of negative length") - let newCount = min(maxLength, count) - let newSpan = Self(_unchecked: _pointer, count: newCount) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Returns a span over all but the given number of trailing elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of elements to drop off the end of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span leaving off the specified number of elements at the end. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(droppingLast k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let droppedCount = min(k, count) - let newSpan = Self(_unchecked: _pointer, count: count &- droppedCount) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Returns a span containing the final elements of the span, - /// up to the given maximum length. - /// - /// If the maximum length exceeds the length of this span, - /// the result contains all the elements. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter maxLength: The maximum number of elements to return. - /// `maxLength` must be greater than or equal to zero. - /// - Returns: A span with at most `maxLength` elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(last maxLength: Int) -> Self { - precondition(maxLength >= 0, "Can't have a suffix of negative length") - let newCount = min(maxLength, count) - let offset = (count &- newCount) * MemoryLayout.stride - let newStart = unsafe _pointer?.advanced(by: offset) - let newSpan = Self(_unchecked: newStart, count: newCount) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - - /// Returns a span over all but the given number of initial elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the span, the result is an empty span. - /// - /// The returned span's first item is always at offset 0; unlike buffer - /// slices, extracted spans do not share their indices with the - /// span from which they are extracted. - /// - /// - Parameter k: The number of elements to drop from the beginning of - /// the span. `k` must be greater than or equal to zero. - /// - Returns: A span starting after the specified number of elements. - /// - /// - Complexity: O(1) - @_alwaysEmitIntoClient - @lifetime(borrow self) - mutating public func _extracting(droppingFirst k: Int) -> Self { - precondition(k >= 0, "Can't drop a negative number of elements") - let droppedCount = min(k, count) - let offset = droppedCount * MemoryLayout.stride - let newStart = unsafe _pointer?.advanced(by: offset) - let newSpan = Self(_unchecked: newStart, count: count &- droppedCount) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } -} -#endif diff --git a/Sources/Future/Span/MutableSpanSlicing.swift b/Sources/Future/Span/MutableSpanSlicing.swift deleted file mode 100644 index 28feaa34c..000000000 --- a/Sources/Future/Span/MutableSpanSlicing.swift +++ /dev/null @@ -1,171 +0,0 @@ -//===--- MutableSpanSlicing.swift -----------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#if false // FIXME: Revive -//MARK: Option 1 extracting() function -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - @lifetime(borrow self) - public mutating func extracting(_ bounds: Range) -> Self { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - let newSpan = unsafe MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } -} - -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - fileprivate init( - _unchecked pointer: UnsafeMutableRawPointer, - uncheckedBounds bounds: Range - ) { - let delta = bounds.lowerBound &* MemoryLayout.stride - let newStart = unsafe pointer.advanced(by: delta) - let newSpan = unsafe Self( - _unchecked: newStart, count: bounds.upperBound &- bounds.lowerBound - ) - self = unsafe _overrideLifetime(newSpan, borrowing: pointer) - } -} - -@available(macOS 9999, *) -extension Span where Element: ~Copyable { - @_alwaysEmitIntoClient - @lifetime(borrow pointer) - fileprivate init( - _unchecked pointer: UnsafeMutableRawPointer, - uncheckedBounds bounds: Range - ) { - let mut = unsafe MutableSpan(_unchecked: pointer, uncheckedBounds: bounds) - let newSpan = mut.span - self = unsafe _overrideLifetime(newSpan, borrowing: pointer) - } -} - -//MARK: Option 2 extracting subscript -@available(macOS 9999, *) -extension MutableSpan where Element: ~Copyable { - - public subscript(extracting bounds: Range) -> Self { - @lifetime(borrow self) - mutating get { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - let newSpan = unsafe MutableSpan(_unchecked: _start(), uncheckedBounds: bounds) - return unsafe _overrideLifetime(newSpan, mutating: &self) - } - } -} - -//MARK: Option 3 specific slicing wrapper type -@available(macOS 9999, *) -public struct SubMutableSpan -: ~Copyable, ~Escapable { - public typealias Index = MutableSpan.Index - - public fileprivate(set) var offset: MutableSpan.Index - public /*exclusive*/ var base: MutableSpan - - @lifetime(copy _base) - init(_ _offset: Index, _ _base: consuming MutableSpan) { - offset = _offset - base = _base - } - - public var count: Int { base.count - offset } -} - -@available(macOS 9999, *) -extension SubMutableSpan where Element: ~Copyable { - - public subscript (index: Index) -> Element { - unsafeAddress { - precondition(offset <= index && index < base.count) - return unsafe UnsafePointer(base._unsafeAddressOfElement(unchecked: index)) - } - @lifetime(self: copy self) - unsafeMutableAddress { - precondition(offset <= index && index < base.count) - return unsafe base._unsafeAddressOfElement(unchecked: index) - } - } - - public var span: Span { - @lifetime(borrow self) - borrowing get { - let newSpan = unsafe Span( - _unchecked: base._start(), uncheckedBounds: offset..) -> SubMutableSpan { - @lifetime(borrow self) - mutating get { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - let prefix = unsafe Self( - _unchecked: _start(), uncheckedBounds: 0..(bounds.lowerBound, prefix) - return unsafe _overrideLifetime(subSpan, mutating: &self) - } - } -} - -//MARK: Option 4 range parameters to bulk mutation functions -@available(macOS 9999, *) -extension MutableSpan where Element: Copyable { - - @lifetime(self: copy self) - public mutating func update( - in bounds: Range, - repeating repeatedValue: consuming Element - ) { - precondition( - UInt(bitPattern: bounds.lowerBound) <= UInt(bitPattern: _count) && - UInt(bitPattern: bounds.upperBound) <= UInt(bitPattern: _count), - "Index range out of bounds" - ) - let start = unsafe _start() + bounds.lowerBound &* MemoryLayout.stride - unsafe start.withMemoryRebound(to: Element.self, capacity: bounds.count) { - unsafe $0.update(repeating: repeatedValue, count: bounds.count) - } - } -} -#endif From fd56892438bf2294ff54f8e39a1912c3f1c95f27 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 17:27:19 -0700 Subject: [PATCH 176/195] Revive RigidArray and DynamicArray --- Sources/Future/Containers/DynamicArray.swift | 75 ++++++------- Sources/Future/Containers/RigidArray.swift | 106 ++++++++++--------- 2 files changed, 86 insertions(+), 95 deletions(-) diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift index 7f79cd42f..20f9bdb75 100644 --- a/Sources/Future/Containers/DynamicArray.swift +++ b/Sources/Future/Containers/DynamicArray.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -#if false /// A dynamically self-resizing, heap allocated, noncopyable array /// of potentially noncopyable elements. @frozen @@ -40,24 +39,14 @@ extension DynamicArray where Element: ~Copyable { public var capacity: Int { _storage.capacity } } +@available(SwiftStdlib 6.2, *) extension DynamicArray where Element: ~Copyable { - public var storage: Span { - _storage.storage + public var span: Span { + _storage.span } } -extension DynamicArray: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = RigidArray.BorrowingIterator - public typealias Index = Int - - public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(for: _storage, startOffset: 0) - } - - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(for: _storage, startOffset: start) - } - +extension DynamicArray where Element: ~Copyable { @inlinable public var isEmpty: Bool { _storage.isEmpty } @@ -73,12 +62,12 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { @inlinable public subscript(position: Int) -> Element { @inline(__always) - _read { - yield _storage[position] + unsafeAddress { + unsafe _storage._ptr(at: position) } @inline(__always) - _modify { - yield &_storage[position] + unsafeMutableAddress { + unsafe _storage._mutablePtr(at: position) } } @@ -99,13 +88,7 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { // Note: Range checks are deferred until element access. index -= 1 } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - // Note: Range checks are deferred until element access. - position._offset - } - + @inlinable public func distance(from start: Int, to end: Int) -> Int { // Note: Range checks are deferred until element access. @@ -117,7 +100,27 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { // Note: Range checks are deferred until element access. index + n } +} +#if false +extension DynamicArray: RandomAccessContainer where Element: ~Copyable { + public typealias BorrowingIterator = RigidArray.BorrowingIterator + public typealias Index = Int + + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(for: _storage, startOffset: 0) + } + + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(for: _storage, startOffset: start) + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + // Note: Range checks are deferred until element access. + position._offset + } + @inlinable public func formIndex( _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int @@ -125,24 +128,7 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { _storage.formIndex(&index, offsetBy: &distance, limitedBy: limit) } } - -extension DynamicArray where Element: ~Copyable { - @inlinable - public func borrowElement ( - at index: Int, - by body: (borrowing Element) throws(E) -> R - ) throws(E) -> R { - try _storage.borrowElement(at: index, by: body) - } - - @inlinable - public mutating func updateElement ( - at index: Int, - by body: (inout Element) throws(E) -> R - ) throws(E) -> R { - try _storage.updateElement(at: index, by: body) - } -} +#endif extension DynamicArray where Element: ~Copyable { @inlinable @@ -197,4 +183,3 @@ extension DynamicArray { } } } -#endif diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index 9dd374dc2..1b1827ccf 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -#if false // FIXME: Revive /// A manually resizable, heap allocated, noncopyable array of /// potentially noncopyable elements. @safe @@ -21,6 +20,11 @@ public struct RigidArray: ~Copyable { @usableFromInline internal var _count: Int + deinit { + unsafe _storage.extracting(0 ..< count).deinitialize() + unsafe _storage.deallocate() + } + @inlinable public init(capacity: Int) { precondition(capacity >= 0) @@ -40,23 +44,21 @@ public struct RigidArray: ~Copyable { } _count = count } - - deinit { - unsafe _storage.extracting(0 ..< count).deinitialize() - unsafe _storage.deallocate() - } } extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} extension RigidArray where Element: ~Copyable { @inlinable + @inline(__always) public var capacity: Int { unsafe _storage.count } @inlinable + @inline(__always) public var freeCapacity: Int { capacity - count } @inlinable + @inline(__always) public var isFull: Bool { freeCapacity == 0 } } @@ -75,8 +77,11 @@ extension RigidArray where Element: ~Copyable { extension RigidArray where Element: ~Copyable { @available(SwiftStdlib 6.2, *) public var span: Span { - let result = unsafe Span(_unsafeElements: _items) - return _overrideLifetime(result, borrowing: self) + @lifetime(borrow self) + get { + let result = unsafe Span(_unsafeElements: _items) + return unsafe _overrideLifetime(result, borrowing: self) + } } } @@ -95,17 +100,40 @@ extension RigidArray where Element: ~Copyable { @inlinable public var endIndex: Int { count } + @inlinable + @_transparent + internal func _uncheckedMutablePtr(at index: Int) -> UnsafeMutablePointer { + unsafe _storage.baseAddress.unsafelyUnwrapped.advanced(by: index) + } + + @inlinable + @_transparent + internal func _uncheckedPtr(at index: Int) -> UnsafePointer { + unsafe UnsafePointer(_uncheckedMutablePtr(at: index)) + } + + @inlinable + @_transparent + internal func _mutablePtr(at index: Int) -> UnsafeMutablePointer { + precondition(index >= 0 && index < _count) + return unsafe _uncheckedMutablePtr(at: index) + } + + @inlinable + @_transparent + internal func _ptr(at index: Int) -> UnsafePointer { + unsafe UnsafePointer(_mutablePtr(at: index)) + } + @inlinable public subscript(position: Int) -> Element { @inline(__always) - _read { - precondition(position >= 0 && position < _count) - yield _storage[position] + unsafeAddress { + unsafe _ptr(at: position) } @inline(__always) - _modify { - precondition(position >= 0 && position < _count) - yield &_storage[position] + unsafeMutableAddress { + unsafe _mutablePtr(at: position) } } } @@ -158,10 +186,10 @@ extension RigidArray where Element: ~Copyable { precondition(newCapacity >= count) guard newCapacity != capacity else { return } let newStorage: UnsafeMutableBufferPointer = .allocate(capacity: newCapacity) - let i = newStorage.moveInitialize(fromContentsOf: self._items) + let i = unsafe newStorage.moveInitialize(fromContentsOf: self._items) assert(i == count) - _storage.deallocate() - _storage = newStorage + unsafe _storage.deallocate() + unsafe _storage = newStorage } @inlinable @@ -171,36 +199,15 @@ extension RigidArray where Element: ~Copyable { } } - -extension RigidArray where Element: ~Copyable { - @inlinable - public func borrowElement ( - at index: Int, - by body: (borrowing Element) throws(E) -> R - ) throws(E) -> R { - precondition(index >= 0 && index < _count) - return try body(_storage[index]) - } - - @inlinable - public mutating func updateElement ( - at index: Int, - by body: (inout Element) throws(E) -> R - ) throws(E) -> R { - precondition(index >= 0 && index < _count) - return try body(&_storage[index]) - } -} - extension RigidArray where Element: ~Copyable { @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { precondition(index >= 0 && index < count) - let old = _storage.moveElement(from: index) - let source = _storage.extracting(index + 1 ..< count) - let target = _storage.extracting(index ..< count - 1) - let i = target.moveInitialize(fromContentsOf: source) + let old = unsafe _storage.moveElement(from: index) + let source = unsafe _storage.extracting(index + 1 ..< count) + let target = unsafe _storage.extracting(index ..< count - 1) + let i = unsafe target.moveInitialize(fromContentsOf: source) assert(i == target.endIndex) _count -= 1 return old @@ -211,7 +218,7 @@ extension RigidArray where Element: ~Copyable { @inlinable public mutating func append(_ item: consuming Element) { precondition(!isFull) - _storage.initializeElement(at: _count, to: item) + unsafe _storage.initializeElement(at: _count, to: item) _count += 1 } } @@ -222,12 +229,12 @@ extension RigidArray where Element: ~Copyable { precondition(index >= 0 && index <= count) precondition(!isFull) if index < count { - let source = _storage.extracting(index ..< count) - let target = _storage.extracting(index + 1 ..< count + 1) - let last = target.moveInitialize(fromContentsOf: source) + let source = unsafe _storage.extracting(index ..< count) + let target = unsafe _storage.extracting(index + 1 ..< count + 1) + let last = unsafe target.moveInitialize(fromContentsOf: source) assert(last == target.endIndex) } - _storage.initializeElement(at: index, to: item) + unsafe _storage.initializeElement(at: index, to: item) _count += 1 } } @@ -251,7 +258,7 @@ extension RigidArray { internal func _copy(capacity: Int) -> Self { precondition(capacity >= count) var result = RigidArray(capacity: capacity) - let initialized = result._storage.initialize(fromContentsOf: _storage) + let initialized = unsafe result._storage.initialize(fromContentsOf: _storage) precondition(initialized == count) result._count = count return result @@ -261,11 +268,10 @@ extension RigidArray { internal mutating func _move(capacity: Int) -> Self { precondition(capacity >= count) var result = RigidArray(capacity: capacity) - let initialized = result._storage.moveInitialize(fromContentsOf: _storage) + let initialized = unsafe result._storage.moveInitialize(fromContentsOf: _storage) precondition(initialized == count) result._count = count self._count = 0 return result } } -#endif From ea2887fe391469bdb51c1aa151f8743abf5cf1e8 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 17:52:18 -0700 Subject: [PATCH 177/195] Add (nonfunctional) mutableSpan properties --- Sources/Future/Containers/DynamicArray.swift | 18 ++++++++++++++++-- Sources/Future/Containers/RigidArray.swift | 13 +++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/DynamicArray.swift index 20f9bdb75..3685afc37 100644 --- a/Sources/Future/Containers/DynamicArray.swift +++ b/Sources/Future/Containers/DynamicArray.swift @@ -39,11 +39,25 @@ extension DynamicArray where Element: ~Copyable { public var capacity: Int { _storage.capacity } } -@available(SwiftStdlib 6.2, *) extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 6.2, *) public var span: Span { - _storage.span + @lifetime(borrow self) + get { + _storage.span + } } + +#if compiler(>=6.3) // FIXME: Turn this on once we have a new enough toolchain + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(&self) + @inlinable + mutating get { + _storage.mutableSpan + } + } +#endif } extension DynamicArray where Element: ~Copyable { diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/RigidArray.swift index 1b1827ccf..95f14a30c 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/RigidArray.swift @@ -78,11 +78,24 @@ extension RigidArray where Element: ~Copyable { @available(SwiftStdlib 6.2, *) public var span: Span { @lifetime(borrow self) + @inlinable get { let result = unsafe Span(_unsafeElements: _items) return unsafe _overrideLifetime(result, borrowing: self) } } + + #if compiler(>=6.3) // FIXME: Turn this on once we have a new enough toolchain + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(&self) + @inlinable + mutating get { + let result = unsafe MutableSpan(_unsafeElements: _items) + return unsafe _overrideLifetime(result, mutating: self) + } + } + #endif } extension RigidArray where Element: ~Copyable { From f5b053da579ccc61a243d25ca5aa2bddd177d500 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 18:09:22 -0700 Subject: [PATCH 178/195] Revive Shared and NewArray MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that unsafeAddress accessors have the expected semantics, `struct Shared` now appears to be functional! The expected syntax builds without issues, and appears to have the right semantics, although I haven’t tried running anything yet --- Package.swift | 1 + Sources/Future/Containers/NewArray.swift | 50 ++++++---------- Sources/Future/Containers/Shared.swift | 76 ++++++------------------ 3 files changed, 37 insertions(+), 90 deletions(-) diff --git a/Package.swift b/Package.swift index f34695bf6..e3b33722c 100644 --- a/Package.swift +++ b/Package.swift @@ -53,6 +53,7 @@ var defines: [String] = [ ] let availabilityMacros: KeyValuePairs = [ + "SwiftStdlib 6.0": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", "SwiftStdlib 6.2": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999", ] diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/NewArray.swift index 7362045ba..420f56e91 100644 --- a/Sources/Future/Containers/NewArray.swift +++ b/Sources/Future/Containers/NewArray.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -#if false /// What `Array` might look like if we defined it today. @frozen public struct NewArray { @@ -23,19 +22,18 @@ public struct NewArray { } extension NewArray { - public var storage: Span { -#if false - // FIXME: This is what I want to write; alas, lifetimes are messed up. - return _storage.value.storage -#else - return Span(_unsafeElements: _storage.value._items) -#endif + @available(SwiftStdlib 6.2, *) + public var span: Span { + @lifetime(borrow self) + get { + _storage.value.span + } } } extension NewArray { @inlinable - public var capacity: Int { _storage.read { $0.capacity } } + public var capacity: Int { _storage.value.capacity } @inlinable internal mutating func _ensureUnique() { @@ -47,36 +45,22 @@ extension NewArray { minimumCapacity: Int, linear: Bool = false ) { + // FIXME: Handle resizing if !_storage.isUnique() { let c = Swift.max(count, minimumCapacity) - _storage = Shared(_storage.read { $0._copy(capacity: c) }) + _storage = Shared(_storage.value._copy(capacity: c)) } else if minimumCapacity > self.capacity { - _storage = Shared(_storage.update { $0._move(capacity: minimumCapacity) }) + _storage = Shared(_storage.value._move(capacity: minimumCapacity)) } } - @inlinable - internal func _read( - _ body: (borrowing RigidArray) throws(E) -> Result - ) throws(E) -> Result { - try _storage.read(body) - } - - @inlinable - internal mutating func _update( - _ body: (inout RigidArray) throws(E) -> Result - ) throws(E) -> Result { - _ensureUnique() - return try _storage.update(body) - } - @inlinable internal mutating func _update( minimumCapacity: Int, _ body: (inout RigidArray) throws(E) -> Result ) throws(E) -> Result { _ensureUnique(minimumCapacity: minimumCapacity) - return try _storage.update(body) + return try body(&_storage.value) } } @@ -87,22 +71,22 @@ extension NewArray: RandomAccessCollection, MutableCollection { public var startIndex: Int { 0 } @inlinable - public var endIndex: Int { _storage.read { $0.count } } + public var endIndex: Int { _storage.value.count } public subscript(position: Int) -> Element { @inlinable - get { - _read { $0[position] } + unsafeAddress { + unsafe _storage.value._ptr(at: position) } @inlinable - @inline(__always) - _modify { + unsafeMutableAddress { _ensureUnique() - yield &_storage.value[position] + return unsafe _storage.value._mutablePtr(at: position) } } } +#if false // FIXME: Implement extension NewArray: RangeReplaceableCollection { public init() { // FIXME: Figure out if we can implement empty singletons in this setup. diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift index fee5c58c3..35708f90f 100644 --- a/Sources/Future/Containers/Shared.swift +++ b/Sources/Future/Containers/Shared.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -#if false import Builtin // For Shared.isIdentical /// A utility adapter that wraps a noncopyable storage type in a copy-on-write @@ -19,6 +18,7 @@ import Builtin // For Shared.isIdentical /// Like `ManagedBufferPointer`, this type is intended to be used within the /// internal implementation of public types. Instances of it aren't designed /// to be exposed as public. +@safe @frozen public struct Shared { @usableFromInline @@ -26,13 +26,14 @@ public struct Shared { @inlinable public init(_ storage: consuming Storage) { - self._box = _Box(storage) + unsafe self._box = _Box(storage) } } extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} -#if true // FIXME: Silent error on class definition nested in noncopyable struct +#if false // FIXME: Silent error on class definition nested in noncopyable struct +@unsafe @usableFromInline internal final class _SharedBox { @exclusivity(unchecked) @@ -41,7 +42,7 @@ internal final class _SharedBox { @inlinable internal init(_ storage: consuming Storage) { - self.storage = storage + unsafe self.storage = storage } } @@ -51,6 +52,7 @@ extension Shared where Storage: ~Copyable { } #else extension Shared where Storage: ~Copyable { + @unsafe @usableFromInline internal final class _Box { @exclusivity(unchecked) @@ -59,7 +61,7 @@ extension Shared where Storage: ~Copyable { @inlinable internal init(_ storage: consuming Storage) { - self.storage = storage + unsafe self.storage = storage } } } @@ -69,7 +71,7 @@ extension Shared where Storage: ~Copyable { @inlinable @inline(__always) public mutating func isUnique() -> Bool { - isKnownUniquelyReferenced(&_box) + unsafe isKnownUniquelyReferenced(&_box) } @inlinable @@ -77,7 +79,7 @@ extension Shared where Storage: ~Copyable { cloner: (borrowing Storage) -> Storage ) { if isUnique() { return } - _box = _Box(cloner(_box.storage)) + unsafe _box = _Box(cloner(_box.storage)) } } @@ -88,73 +90,34 @@ extension Shared where Storage: ~Copyable { @inlinable internal var _address: UnsafePointer { // Adapted from _getUnsafePointerToStoredProperties - let p = ( + let p = unsafe ( UnsafeRawPointer(Builtin.bridgeToRawPointer(_box)) + MemoryLayout.size) - return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + return unsafe p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) } @inlinable internal var _mutableAddress: UnsafeMutablePointer { // Adapted from _getUnsafePointerToStoredProperties - let p = ( + let p = unsafe ( UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_box)) + MemoryLayout.size) - return p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + return unsafe p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) } } extension Shared where Storage: ~Copyable { @inlinable @inline(__always) - public var value: /*FIXME: dependsOn(self)*/ Storage { - // FIXME: This implements the wrong shape. - // FIXME: Semantically it yields a borrow scoped to an access of this `value` variable, - // FIXME: not the much wider borrow of `self`, which we'd actually want. - _read { - yield _box.storage - } - _modify { - precondition(isUnique()) - yield &_box.storage - } - } - - // FIXME: This builds, but attempts to use it don't: they fail with an unexpected exclusivity violation. - @inlinable - @lifetime(self) - public subscript() -> Storage { - //@_transparent + public var value: Storage { unsafeAddress { - _address + unsafe _address } - - //@_transparent unsafeMutableAddress { precondition(isUnique()) - return _mutableAddress + return unsafe _mutableAddress } } - - @inlinable - @inline(__always) - public func read( - _ body: (borrowing Storage) throws(E) -> R - ) throws(E) -> R { - // FIXME: This also implements the wrong shape. - // FIXME: The borrow of `Storage` isn't tied to `self` at all, and - // FIXME: it obviously cannot legally escape the `read` call. - try body(_box.storage) - } - - @inlinable - @inline(__always) - public mutating func update( - _ body: (inout Storage) throws(E) -> R - ) throws(E) -> R { - precondition(isUnique()) - return try body(&_box.storage) - } } #if false // FIXME: Use it or lose it @@ -176,14 +139,13 @@ extension Shared where Storage: ~Copyable { @inlinable public func isIdentical(to other: Self) -> Bool { if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { - return self._box === other._box + return unsafe self._box === other._box } else { // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions // that are only supported in the Swift 6+ runtime. - let a = Builtin.bridgeToRawPointer(self._box) - let b = Builtin.bridgeToRawPointer(other._box) + let a = unsafe Builtin.bridgeToRawPointer(self._box) + let b = unsafe Builtin.bridgeToRawPointer(other._box) return Bool(Builtin.cmp_eq_RawPointer(a, b)) } } } -#endif From 9fc69b4f567058c684f71e7f26f4cdaaa60a036a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Apr 2025 19:25:35 -0700 Subject: [PATCH 179/195] Revive most Container protocols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We cannot express the right subscript requirements yet; make do by requiring hidden members that return (unsafe, escaping) pointers, and provide a default subscript that’s based on unsafe addressors. This is very unsatisfying, but currently there is no way for a protocol to require subscripts with the right semantics. --- .../{ => Arrays}/DynamicArray.swift | 4 +- .../Containers/{ => Arrays}/NewArray.swift | 4 +- .../Arrays/RigidArray+Iteration.swift | 62 +++++++++ .../Containers/{ => Arrays}/RigidArray.swift | 81 +++-------- .../Containers/BidirectionalContainer.swift | 23 ++++ Sources/Future/Containers/Container.swift | 127 +++--------------- .../Future/Containers/MutableContainer.swift | 49 +++++++ .../Containers/RandomAccessContainer.swift | 88 ++++++++++++ Sources/Future/Span/SpanExtensions.swift | 59 +++++++- 9 files changed, 315 insertions(+), 182 deletions(-) rename Sources/Future/Containers/{ => Arrays}/DynamicArray.swift (97%) rename Sources/Future/Containers/{ => Arrays}/NewArray.swift (94%) create mode 100644 Sources/Future/Containers/Arrays/RigidArray+Iteration.swift rename Sources/Future/Containers/{ => Arrays}/RigidArray.swift (76%) create mode 100644 Sources/Future/Containers/BidirectionalContainer.swift create mode 100644 Sources/Future/Containers/MutableContainer.swift create mode 100644 Sources/Future/Containers/RandomAccessContainer.swift diff --git a/Sources/Future/Containers/DynamicArray.swift b/Sources/Future/Containers/Arrays/DynamicArray.swift similarity index 97% rename from Sources/Future/Containers/DynamicArray.swift rename to Sources/Future/Containers/Arrays/DynamicArray.swift index 3685afc37..fe36ce31c 100644 --- a/Sources/Future/Containers/DynamicArray.swift +++ b/Sources/Future/Containers/Arrays/DynamicArray.swift @@ -77,11 +77,11 @@ extension DynamicArray where Element: ~Copyable { public subscript(position: Int) -> Element { @inline(__always) unsafeAddress { - unsafe _storage._ptr(at: position) + unsafe _storage._unsafeAddressOfElement(at: position) } @inline(__always) unsafeMutableAddress { - unsafe _storage._mutablePtr(at: position) + unsafe _storage._unsafeMutableAddressOfElement(at: position) } } diff --git a/Sources/Future/Containers/NewArray.swift b/Sources/Future/Containers/Arrays/NewArray.swift similarity index 94% rename from Sources/Future/Containers/NewArray.swift rename to Sources/Future/Containers/Arrays/NewArray.swift index 420f56e91..5121a98dc 100644 --- a/Sources/Future/Containers/NewArray.swift +++ b/Sources/Future/Containers/Arrays/NewArray.swift @@ -76,12 +76,12 @@ extension NewArray: RandomAccessCollection, MutableCollection { public subscript(position: Int) -> Element { @inlinable unsafeAddress { - unsafe _storage.value._ptr(at: position) + unsafe _storage.value._unsafeAddressOfElement(at: position) } @inlinable unsafeMutableAddress { _ensureUnique() - return unsafe _storage.value._mutablePtr(at: position) + return unsafe _storage.value._unsafeMutableAddressOfElement(at: position) } } } diff --git a/Sources/Future/Containers/Arrays/RigidArray+Iteration.swift b/Sources/Future/Containers/Arrays/RigidArray+Iteration.swift new file mode 100644 index 000000000..f7eb6e613 --- /dev/null +++ b/Sources/Future/Containers/Arrays/RigidArray+Iteration.swift @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) // For Span +extension RigidArray: RandomAccessContainer where Element: ~Copyable { + @safe + public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { + @usableFromInline + internal let _items: UnsafeBufferPointer + + @usableFromInline + internal var _offset: Int + + @inlinable + @lifetime(borrow array) + internal init(for array: borrowing RigidArray, startOffset: Int) { + unsafe self._items = UnsafeBufferPointer(array._items) + self._offset = startOffset + } + + @inlinable + internal var _span: Span { + @lifetime(copy self) + get { + unsafe Span(_unsafeElements: _items) + } + } + + @lifetime(copy self) + public mutating func nextChunk( + maximumCount: Int + ) -> Span { + let end = unsafe _offset + Swift.min(maximumCount, _items.count - _offset) + defer { _offset = end } + return unsafe _span._extracting(Range(uncheckedBounds: (_offset, end))) + } + } + + @lifetime(borrow self) + public func startBorrowingIteration() -> BorrowingIterator { + BorrowingIterator(for: self, startOffset: 0) + } + + @lifetime(borrow self) + public func startBorrowingIteration(from start: Int) -> BorrowingIterator { + BorrowingIterator(for: self, startOffset: start) + } + + @inlinable + public func index(at position: borrowing BorrowingIterator) -> Int { + precondition(unsafe position._items === UnsafeBufferPointer(self._items)) + return position._offset + } +} diff --git a/Sources/Future/Containers/RigidArray.swift b/Sources/Future/Containers/Arrays/RigidArray.swift similarity index 76% rename from Sources/Future/Containers/RigidArray.swift rename to Sources/Future/Containers/Arrays/RigidArray.swift index 95f14a30c..5cd98820d 100644 --- a/Sources/Future/Containers/RigidArray.swift +++ b/Sources/Future/Containers/Arrays/RigidArray.swift @@ -100,98 +100,51 @@ extension RigidArray where Element: ~Copyable { extension RigidArray where Element: ~Copyable { public typealias Index = Int - + @inlinable public var isEmpty: Bool { count == 0 } - + @inlinable public var count: Int { _count } - + @inlinable public var startIndex: Int { 0 } - + @inlinable public var endIndex: Int { count } +} +extension RigidArray where Element: ~Copyable { + // FIXME: This is wildly unsafe. Remove it once the subscript can have proper accessors. @inlinable @_transparent - internal func _uncheckedMutablePtr(at index: Int) -> UnsafeMutablePointer { - unsafe _storage.baseAddress.unsafelyUnwrapped.advanced(by: index) - } - - @inlinable - @_transparent - internal func _uncheckedPtr(at index: Int) -> UnsafePointer { - unsafe UnsafePointer(_uncheckedMutablePtr(at: index)) - } - - @inlinable - @_transparent - internal func _mutablePtr(at index: Int) -> UnsafeMutablePointer { + internal mutating func _unsafeMutableAddressOfElement( + at index: Int + ) -> UnsafeMutablePointer { precondition(index >= 0 && index < _count) - return unsafe _uncheckedMutablePtr(at: index) + return unsafe _storage.baseAddress.unsafelyUnwrapped.advanced(by: index) } + // FIXME: This is wildly unsafe. Remove it once the subscript can have proper accessors. @inlinable @_transparent - internal func _ptr(at index: Int) -> UnsafePointer { - unsafe UnsafePointer(_mutablePtr(at: index)) + public func _unsafeAddressOfElement(at index: Int) -> UnsafePointer { + precondition(index >= 0 && index < _count) + return unsafe UnsafePointer(_storage.baseAddress.unsafelyUnwrapped.advanced(by: index)) } @inlinable public subscript(position: Int) -> Element { @inline(__always) unsafeAddress { - unsafe _ptr(at: position) + unsafe _unsafeAddressOfElement(at: position) } @inline(__always) unsafeMutableAddress { - unsafe _mutablePtr(at: position) - } - } -} - -#if false -extension RigidArray: RandomAccessContainer where Element: ~Copyable { - public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { - @usableFromInline - internal let _items: UnsafeBufferPointer - - @usableFromInline - internal var _offset: Int - - @inlinable - internal init(for array: borrowing RigidArray, startOffset: Int) { - self._items = UnsafeBufferPointer(array._items) - self._offset = startOffset + unsafe _unsafeMutableAddressOfElement(at: position) } - - @lifetime(self) - public mutating func nextChunk( - maximumCount: Int - ) -> Span { - let end = _offset + Swift.min(maximumCount, _items.count - _offset) - defer { _offset = end } - let chunk = _items.extracting(Range(uncheckedBounds: (_offset, end))) - return Span(_unsafeElements: chunk) - } - } - - public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(for: self, startOffset: 0) - } - - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(for: self, startOffset: start) - } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - precondition(position._items === UnsafeBufferPointer(self._items)) - return position._offset } } -#endif extension RigidArray where Element: ~Copyable { @inlinable diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift new file mode 100644 index 000000000..fa7825aee --- /dev/null +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) // For Span +public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { + override associatedtype Element: ~Copyable + + func index(before i: Index) -> Index + func formIndex(before i: inout Index) + + @_nonoverride func index(_ i: Index, offsetBy distance: Int) -> Index + @_nonoverride func formIndex( + _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index + ) +} diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 68352669f..5ce30e0db 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -9,21 +9,25 @@ // //===----------------------------------------------------------------------===// -#if false // FIXME: Revive +@available(SwiftStdlib 6.2, *) // For Span public protocol BorrowingIteratorProtocol: ~Escapable { associatedtype Element: ~Copyable - @lifetime(self) + @lifetime(copy self) mutating func nextChunk(maximumCount: Int) -> Span } +@available(SwiftStdlib 6.2, *) // For Span public protocol Container: ~Copyable, ~Escapable { associatedtype Element: ~Copyable associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Escapable where BorrowingIterator.Element == Element + @lifetime(copy self) borrowing func startBorrowingIteration() -> BorrowingIterator + + @lifetime(copy self) borrowing func startBorrowingIteration(from start: Index) -> BorrowingIterator associatedtype Index: Comparable @@ -34,10 +38,15 @@ public protocol Container: ~Copyable, ~Escapable { var startIndex: Index { get } var endIndex: Index { get } - // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements - // FIXME: (Or rather, accessors with proper projection semantics.) - @_borrowed subscript(index: Index) -> Element { get } - + /// Return a pointer addressing the element at the given index. + /// This is wildly unsafe; please do not use this outside of `unsafeAddress` accessors. + /// + /// This is a temporary stand-in for the subscript requirement that we actually want: + /// + /// subscript(index: Index) -> Element { borrow } + @unsafe + func _unsafeAddressOfElement(at index: Index) -> UnsafePointer + func index(after index: Index) -> Index func formIndex(after i: inout Index) @@ -52,109 +61,13 @@ public protocol Container: ~Copyable, ~Escapable { ) } -public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { - override associatedtype Element: ~Copyable - - func index(before i: Index) -> Index - func formIndex(before i: inout Index) - - @_nonoverride func index(_ i: Index, offsetBy distance: Int) -> Index - @_nonoverride func formIndex( - _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index - ) -} - -public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { - override associatedtype Element: ~Copyable -} - -extension Strideable { +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { @inlinable - public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { - if distance >= 0 { - guard limit >= self else { - self = self.advanced(by: distance) - distance = 0 - return - } - let d = Swift.min(distance, self.distance(to: limit)) - self = self.advanced(by: d) - distance -= d - } else { - guard limit <= self else { - self = self.advanced(by: distance) - distance = 0 - return - } - let d = Swift.max(distance, self.distance(to: limit)) - self = self.advanced(by: d) - distance -= d + public subscript(index: Index) -> Element { + unsafeAddress { + unsafe _unsafeAddressOfElement(at: index) } } } -extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Self: ~Copyable { - @inlinable - public func index(after index: Index) -> Index { - // Note: Range checks are deferred until element access. - index.advanced(by: 1) - } - - @inlinable - public func index(before index: Index) -> Index { - // Note: Range checks are deferred until element access. - index.advanced(by: -1) - } - - @inlinable - public func formIndex(after index: inout Index) { - // Note: Range checks are deferred until element access. - index = index.advanced(by: 1) - } - - @inlinable - public func formIndex(before index: inout Index) { - // Note: Range checks are deferred until element access. - index = index.advanced(by: -1) - } - - @inlinable - public func distance(from start: Index, to end: Index) -> Int { - // Note: Range checks are deferred until element access. - start.distance(to: end) - } - - @inlinable - public func index(_ index: Index, offsetBy n: Int) -> Index { - // Note: Range checks are deferred until element access. - index.advanced(by: n) - } - - @inlinable - public func formIndex( - _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index - ) { - // Note: Range checks are deferred until element access. - index.advance(by: &distance, limitedBy: limit) - } -} -#endif - -#if false // TODO -public protocol Muterator: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable - - @lifetime(self) - mutating func nextChunk(maximumCount: Int) -> MutableSpan -} - -public protocol MutableContainer: Container, ~Copyable, ~Escapable { - associatedtype MutatingIterationState: ~Copyable, ~Escapable - - mutating func startMutatingIteration() -> MutatingIterationState - - // FIXME: Replace `@_borrowed` with proper `read`/`modify` accessor requirements - @_borrowed subscript(index: Index) -> Element { get set } - -} -#endif diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift new file mode 100644 index 000000000..78e8c9031 --- /dev/null +++ b/Sources/Future/Containers/MutableContainer.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.3) // FIXME: Turn this on once we have a new enough toolchain +@available(SwiftStdlib 6.2, *) // for MutableSpan +public protocol Muterator: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + + @lifetime(copy self) + mutating func nextChunk(maximumCount: Int) -> MutableSpan +} + +@available(SwiftStdlib 6.2, *) // for MutableSpan +public protocol MutableContainer: Container, ~Copyable, ~Escapable { + associatedtype MutatingIterationState: ~Copyable, ~Escapable + + @lifetime(&self) + mutating func startMutatingIteration() -> MutatingIterationState + + /// Return a pointer addressing the element at the given index. + /// This is wildly unsafe; please do not use this outside of `unsafeAddress` accessors. + /// + /// This is a temporary stand-in for the subscript requirement that we actually want: + /// + /// subscript(index: Index) -> Element { borrow mutate } + @unsafe + mutating func _unsafeMutableAddressOfElement(at index: Index) -> UnsafePointer +} + +extension MutableContainer where Self: ~Copyable & ~Escapable { + @inlinable + public subscript(index: Index) -> Element { + unsafeAddress { + unsafe _unsafeAddressOfElement(at: index) + } + unsafeMutableAddress { + unsafe _unsafeMutableAddressOfElement(at: index) + } + } +} +#endif diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift new file mode 100644 index 000000000..4a8752dd4 --- /dev/null +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) // For Span +public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { + override associatedtype Element: ~Copyable +} + +extension Strideable { + @inlinable + public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { + if distance >= 0 { + guard limit >= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.min(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } else { + guard limit <= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.max(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } + } +} + +@available(SwiftStdlib 6.2, *) // For Span +extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Self: ~Copyable { + @inlinable + public func index(after index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: 1) + } + + @inlinable + public func index(before index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: -1) + } + + @inlinable + public func formIndex(after index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: 1) + } + + @inlinable + public func formIndex(before index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: -1) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + // Note: Range checks are deferred until element access. + start.distance(to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: n) + } + + @inlinable + public func formIndex( + _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index + ) { + // Note: Range checks are deferred until element access. + index.advance(by: &distance, limitedBy: limit) + } +} + diff --git a/Sources/Future/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift index 9cd081b1b..414fb1db1 100644 --- a/Sources/Future/Span/SpanExtensions.swift +++ b/Sources/Future/Span/SpanExtensions.swift @@ -10,12 +10,13 @@ // //===----------------------------------------------------------------------===// -private let immortalThing: [Void] = [] +import Builtin -@available(macOS 9999, *) -extension Span { +@usableFromInline +internal let immortalThing: [Void] = [] - @available(macOS 9999, *) +@available(SwiftStdlib 6.2, *) +extension Span where Element: ~Copyable { public static var empty: Span { @lifetime(immortal) get { @@ -25,7 +26,6 @@ extension Span { } } - @available(macOS 9999, *) @lifetime(immortal) public init() { let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) @@ -34,9 +34,54 @@ extension Span { } } -@available(macOS 9999, *) -extension Span where Element: Equatable { +#if false // Use it or lose it +@available(SwiftStdlib 6.2, *) +extension Span where Element: ~Copyable { + @usableFromInline + typealias _Components = (pointer: UnsafeRawPointer?, count: Int) + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + @_transparent + @lifetime(copy self) + internal func _unsafelyExplode() -> _Components { + unsafe assert( + MemoryLayout.size == MemoryLayout<_Components>.size, + "Unexpected Span layout") + let immortal = unsafe _overrideLifetime(self, borrowing: immortalThing) + return unsafe Builtin.reinterpretCast(immortal) + } + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + unchecked position: Index + ) -> UnsafePointer { + let (start, _) = unsafe _unsafelyExplode() + let elementOffset = position &* MemoryLayout.stride + let address = unsafe start.unsafelyUnwrapped.advanced(by: elementOffset) + return unsafe address.assumingMemoryBound(to: Element.self) + } + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + _ position: Index + ) -> UnsafePointer { + let (start, count) = unsafe _unsafelyExplode() + precondition(position >= 0 && position < count, "Index out of bounds") + let elementOffset = position &* MemoryLayout.stride + let address = unsafe start.unsafelyUnwrapped.advanced(by: elementOffset) + return unsafe address.assumingMemoryBound(to: Element.self) + } +} +#endif +@available(SwiftStdlib 6.2, *) +extension Span where Element: Equatable { /// Returns a Boolean value indicating whether this and another span /// contain equal elements in the same order. /// From 35da38de54fa5be102214cf125df1d0757a802de Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 15 Apr 2025 15:57:18 -0700 Subject: [PATCH 180/195] Introduce Borrow and use it in Container --- Package.swift | 3 + Sources/Future/Borrow.swift | 44 ++++++++++++++ Sources/Future/Box.swift | 33 ++++------- Sources/Future/CMakeLists.txt | 3 +- Sources/Future/Cell.swift | 59 ------------------- .../Containers/Arrays/DynamicArray.swift | 22 +++++-- .../Future/Containers/Arrays/NewArray.swift | 4 +- .../Future/Containers/Arrays/RigidArray.swift | 34 +++++------ Sources/Future/Containers/Container.swift | 11 ++-- .../Future/Containers/MutableContainer.swift | 20 +++---- Sources/Future/Containers/Shared.swift | 19 +++--- Sources/Future/Inout.swift | 21 ++++--- Sources/Future/Span/LifetimeOverride.swift | 2 +- Sources/Future/Span/OutputSpan.swift | 2 +- Sources/Future/StdlibAdditions.swift | 47 +++++++++++++++ Tests/FutureTests/BorrowTests.swift | 25 ++++++++ Tests/FutureTests/BoxTests.swift | 18 ++---- Tests/FutureTests/CellTests.swift | 37 ------------ .../FutureTests/ContiguousStorageTests.swift | 8 ++- Tests/FutureTests/DynamicArrayTests.swift | 14 +++-- Tests/FutureTests/Inout.swift | 4 +- 21 files changed, 229 insertions(+), 201 deletions(-) create mode 100644 Sources/Future/Borrow.swift delete mode 100644 Sources/Future/Cell.swift create mode 100644 Sources/Future/StdlibAdditions.swift create mode 100644 Tests/FutureTests/BorrowTests.swift delete mode 100644 Tests/FutureTests/CellTests.swift diff --git a/Package.swift b/Package.swift index e3b33722c..3bb3b34e3 100644 --- a/Package.swift +++ b/Package.swift @@ -64,6 +64,9 @@ let extraSettings: [SwiftSetting] = [ .enableExperimentalFeature("LifetimeDependence"), .enableExperimentalFeature("SuppressedAssociatedTypes"), .enableUpcomingFeature("StrictMemorySafety"), + .enableExperimentalFeature("InoutLifetimeDependence"), + .enableExperimentalFeature("AddressableParameters"), + .enableExperimentalFeature("AddressableTypes") ] let _sharedSettings: [SwiftSetting] = ( diff --git a/Sources/Future/Borrow.swift b/Sources/Future/Borrow.swift new file mode 100644 index 000000000..2de2028c2 --- /dev/null +++ b/Sources/Future/Borrow.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@frozen +@safe +public struct Borrow: Copyable, ~Escapable { + @usableFromInline + let _pointer: UnsafePointer + + @lifetime(borrow value) + @_alwaysEmitIntoClient + @_transparent + public init(_ value: borrowing @_addressable T) { + unsafe _pointer = UnsafePointer(Builtin.unprotectedAddressOfBorrow(value)) + } + + @lifetime(copy owner) + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafePointer, + owner: borrowing Owner + ) { + unsafe _pointer = unsafeAddress + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe _pointer + } + } +} diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index d50ec188e..cc942fbbb 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -#if false // FIXME: Revive @frozen +@safe public struct Box: ~Copyable { @usableFromInline internal let _pointer: UnsafeMutablePointer @@ -18,21 +18,15 @@ public struct Box: ~Copyable { @_alwaysEmitIntoClient @_transparent public init(_ value: consuming T) { - _pointer = UnsafeMutablePointer.allocate(capacity: 1) - _pointer.initialize(to: value) - } - - @_alwaysEmitIntoClient - @_transparent - public init(_ fromInout: consuming Inout) { - _pointer = fromInout._pointer + unsafe _pointer = UnsafeMutablePointer.allocate(capacity: 1) + unsafe _pointer.initialize(to: value) } @_alwaysEmitIntoClient @inlinable deinit { - _pointer.deinitialize(count: 1) - _pointer.deallocate() + unsafe _pointer.deinitialize(count: 1) + unsafe _pointer.deallocate() } } @@ -40,17 +34,17 @@ extension Box where T: ~Copyable { @_alwaysEmitIntoClient @_transparent public consuming func consume() -> T { - let result = _pointer.move() - _pointer.deallocate() + let result = unsafe _pointer.move() + unsafe _pointer.deallocate() discard self return result } + @lifetime(immortal) @_alwaysEmitIntoClient @_transparent - @lifetime(immortal) public consuming func leak() -> Inout { - let result = Inout(unsafeImmortalAddress: _pointer) + let result = unsafe Inout(unsafeImmortalAddress: _pointer) discard self return result } @@ -59,12 +53,12 @@ extension Box where T: ~Copyable { public subscript() -> T { @_transparent unsafeAddress { - UnsafePointer(_pointer) + unsafe UnsafePointer(_pointer) } @_transparent - nonmutating unsafeMutableAddress { - _pointer + unsafeMutableAddress { + unsafe _pointer } } } @@ -73,7 +67,6 @@ extension Box where T: Copyable { @_alwaysEmitIntoClient @_transparent public borrowing func copy() -> T { - _pointer.pointee + unsafe _pointer.pointee } } -#endif diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt index 1f8641461..bf7e94ec7 100644 --- a/Sources/Future/CMakeLists.txt +++ b/Sources/Future/CMakeLists.txt @@ -21,11 +21,12 @@ add_library(Future "Containers/NewArray.swift" "Containers/RigidArray.swift" "Containers/Shared.swift" + "Borrow.swift" "Box.swift" - "Cell.swift" "ContiguousStorage.swift" "Inout.swift" "Span+Iterator.swift" + "StdlibAdditions.swift" ) target_link_libraries(Future PRIVATE diff --git a/Sources/Future/Cell.swift b/Sources/Future/Cell.swift deleted file mode 100644 index a5050e68a..000000000 --- a/Sources/Future/Cell.swift +++ /dev/null @@ -1,59 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -#if false // FIXME: Revive -import Builtin - -@frozen -@_rawLayout(like: T, movesAsLike) -public struct Cell: ~Copyable { - @_alwaysEmitIntoClient - @_transparent - public var unsafeAddress: UnsafeMutablePointer { - UnsafeMutablePointer(Builtin.addressOfRawLayout(self)) - } - - @_alwaysEmitIntoClient - @_transparent - public init(_ value: consuming T) { - unsafeAddress.initialize(to: value) - } -} - -extension Cell where T: ~Copyable { - @_alwaysEmitIntoClient - @_transparent - public mutating func asInout() -> Inout { - Inout(unsafeAddress: unsafeAddress, owner: &self) - } - - @_alwaysEmitIntoClient - public subscript() -> T { - @_transparent - unsafeAddress { - UnsafePointer(unsafeAddress) - } - - @_transparent - nonmutating unsafeMutableAddress { - unsafeAddress - } - } -} - -extension Cell where T: Copyable { - @_alwaysEmitIntoClient - @_transparent - public borrowing func copy() -> T { - unsafeAddress.pointee - } -} -#endif diff --git a/Sources/Future/Containers/Arrays/DynamicArray.swift b/Sources/Future/Containers/Arrays/DynamicArray.swift index fe36ce31c..bcac2e712 100644 --- a/Sources/Future/Containers/Arrays/DynamicArray.swift +++ b/Sources/Future/Containers/Arrays/DynamicArray.swift @@ -48,7 +48,6 @@ extension DynamicArray where Element: ~Copyable { } } -#if compiler(>=6.3) // FIXME: Turn this on once we have a new enough toolchain @available(SwiftStdlib 6.2, *) public var mutableSpan: MutableSpan { @lifetime(&self) @@ -57,7 +56,6 @@ extension DynamicArray where Element: ~Copyable { _storage.mutableSpan } } -#endif } extension DynamicArray where Element: ~Copyable { @@ -73,15 +71,27 @@ extension DynamicArray where Element: ~Copyable { @inlinable public var endIndex: Int { _storage.count } + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.borrowElement(at: index) + } + + @inlinable + @lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + _storage.mutateElement(at: index) + } + @inlinable public subscript(position: Int) -> Element { @inline(__always) unsafeAddress { - unsafe _storage._unsafeAddressOfElement(at: position) + unsafe borrowElement(at: position)._pointer } @inline(__always) unsafeMutableAddress { - unsafe _storage._unsafeMutableAddressOfElement(at: position) + unsafe mutateElement(at: position)._pointer } } @@ -116,7 +126,7 @@ extension DynamicArray where Element: ~Copyable { } } -#if false +@available(SwiftStdlib 6.2, *) // For Span extension DynamicArray: RandomAccessContainer where Element: ~Copyable { public typealias BorrowingIterator = RigidArray.BorrowingIterator public typealias Index = Int @@ -125,6 +135,7 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { BorrowingIterator(for: _storage, startOffset: 0) } + @lifetime(borrow self) public func startBorrowingIteration(from start: Int) -> BorrowingIterator { BorrowingIterator(for: _storage, startOffset: start) } @@ -142,7 +153,6 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { _storage.formIndex(&index, offsetBy: &distance, limitedBy: limit) } } -#endif extension DynamicArray where Element: ~Copyable { @inlinable diff --git a/Sources/Future/Containers/Arrays/NewArray.swift b/Sources/Future/Containers/Arrays/NewArray.swift index 5121a98dc..bb6a89464 100644 --- a/Sources/Future/Containers/Arrays/NewArray.swift +++ b/Sources/Future/Containers/Arrays/NewArray.swift @@ -76,12 +76,12 @@ extension NewArray: RandomAccessCollection, MutableCollection { public subscript(position: Int) -> Element { @inlinable unsafeAddress { - unsafe _storage.value._unsafeAddressOfElement(at: position) + unsafe _storage.value.borrowElement(at: position)._pointer } @inlinable unsafeMutableAddress { _ensureUnique() - return unsafe _storage.value._unsafeMutableAddressOfElement(at: position) + return unsafe _storage.value.mutateElement(at: position)._pointer } } } diff --git a/Sources/Future/Containers/Arrays/RigidArray.swift b/Sources/Future/Containers/Arrays/RigidArray.swift index 5cd98820d..9e01e0477 100644 --- a/Sources/Future/Containers/Arrays/RigidArray.swift +++ b/Sources/Future/Containers/Arrays/RigidArray.swift @@ -85,17 +85,15 @@ extension RigidArray where Element: ~Copyable { } } - #if compiler(>=6.3) // FIXME: Turn this on once we have a new enough toolchain @available(SwiftStdlib 6.2, *) public var mutableSpan: MutableSpan { @lifetime(&self) @inlinable mutating get { let result = unsafe MutableSpan(_unsafeElements: _items) - return unsafe _overrideLifetime(result, mutating: self) + return unsafe _overrideLifetime(result, mutating: &self) } } - #endif } extension RigidArray where Element: ~Copyable { @@ -115,33 +113,35 @@ extension RigidArray where Element: ~Copyable { } extension RigidArray where Element: ~Copyable { - // FIXME: This is wildly unsafe. Remove it once the subscript can have proper accessors. @inlinable - @_transparent - internal mutating func _unsafeMutableAddressOfElement( - at index: Int - ) -> UnsafeMutablePointer { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { precondition(index >= 0 && index < _count) - return unsafe _storage.baseAddress.unsafelyUnwrapped.advanced(by: index) + return unsafe Borrow( + unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), + owner: self + ) } - - // FIXME: This is wildly unsafe. Remove it once the subscript can have proper accessors. + @inlinable - @_transparent - public func _unsafeAddressOfElement(at index: Int) -> UnsafePointer { + @lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { precondition(index >= 0 && index < _count) - return unsafe UnsafePointer(_storage.baseAddress.unsafelyUnwrapped.advanced(by: index)) + return unsafe Inout( + unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), + owner: &self + ) } - + @inlinable public subscript(position: Int) -> Element { @inline(__always) unsafeAddress { - unsafe _unsafeAddressOfElement(at: position) + unsafe borrowElement(at: position)._pointer } @inline(__always) unsafeMutableAddress { - unsafe _unsafeMutableAddressOfElement(at: position) + unsafe mutateElement(at: position)._pointer } } } diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 5ce30e0db..f9cff7031 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -37,15 +37,12 @@ public protocol Container: ~Copyable, ~Escapable { var startIndex: Index { get } var endIndex: Index { get } - - /// Return a pointer addressing the element at the given index. - /// This is wildly unsafe; please do not use this outside of `unsafeAddress` accessors. - /// + /// This is a temporary stand-in for the subscript requirement that we actually want: /// /// subscript(index: Index) -> Element { borrow } - @unsafe - func _unsafeAddressOfElement(at index: Index) -> UnsafePointer + @lifetime(copy self) + func borrowElement(at index: Index) -> Borrow func index(after index: Index) -> Index func formIndex(after i: inout Index) @@ -66,7 +63,7 @@ extension Container where Self: ~Copyable & ~Escapable { @inlinable public subscript(index: Index) -> Element { unsafeAddress { - unsafe _unsafeAddressOfElement(at: index) + unsafe borrowElement(at: index)._pointer } } } diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index 78e8c9031..29d719128 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -9,12 +9,11 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=6.3) // FIXME: Turn this on once we have a new enough toolchain @available(SwiftStdlib 6.2, *) // for MutableSpan public protocol Muterator: ~Copyable, ~Escapable { associatedtype Element: ~Copyable - @lifetime(copy self) + @lifetime(&self) mutating func nextChunk(maximumCount: Int) -> MutableSpan } @@ -24,26 +23,25 @@ public protocol MutableContainer: Container, ~Copyable, ~Escapable { @lifetime(&self) mutating func startMutatingIteration() -> MutatingIterationState - - /// Return a pointer addressing the element at the given index. - /// This is wildly unsafe; please do not use this outside of `unsafeAddress` accessors. - /// + /// This is a temporary stand-in for the subscript requirement that we actually want: /// /// subscript(index: Index) -> Element { borrow mutate } - @unsafe - mutating func _unsafeMutableAddressOfElement(at index: Index) -> UnsafePointer + @lifetime(&self) + mutating func mutateElement(at index: Index) -> Inout } +@available(SwiftStdlib 6.2, *) // for MutableSpan extension MutableContainer where Self: ~Copyable & ~Escapable { @inlinable public subscript(index: Index) -> Element { unsafeAddress { - unsafe _unsafeAddressOfElement(at: index) + unsafe borrowElement(at: index)._pointer } + + @lifetime(&self) unsafeMutableAddress { - unsafe _unsafeMutableAddressOfElement(at: index) + unsafe mutateElement(at: index)._pointer } } } -#endif diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift index 35708f90f..3f69347c2 100644 --- a/Sources/Future/Containers/Shared.swift +++ b/Sources/Future/Containers/Shared.swift @@ -111,7 +111,7 @@ extension Shared where Storage: ~Copyable { @inline(__always) public var value: Storage { unsafeAddress { - unsafe _address + unsafe read()._pointer } unsafeMutableAddress { precondition(isUnique()) @@ -120,20 +120,25 @@ extension Shared where Storage: ~Copyable { } } -#if false // FIXME: Use it or lose it extension Shared where Storage: ~Copyable { - // This is the actual shape we want. There is currently no way to express it. @inlinable @lifetime(borrow self) public borrowing func read() -> Borrow { // This is gloriously (and very explicitly) unsafe, as it should be. // `Shared` is carefully constructed to guarantee that - // lifetime(self) == lifetime(_box.storage); but we have not - // (cannot) explain this to the compiler. - Borrow(unsafeAddress: _address, owner: self) + // lifetime(self) == lifetime(_box.storage). + unsafe Borrow(unsafeAddress: _address, owner: self) + } + + @inlinable + @lifetime(&self) + public mutating func mutate() -> Inout { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage). + unsafe Inout(unsafeAddress: _mutableAddress, owner: &self) } } -#endif extension Shared where Storage: ~Copyable { @inlinable diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift index dd2624303..cc33ec82f 100644 --- a/Sources/Future/Inout.swift +++ b/Sources/Future/Inout.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -#if false // FIXME: Revive import Builtin // FIXME: A better name for the generic argument. @@ -19,6 +18,7 @@ import Builtin /// In order to get an instance of an `Inout`, one must have exclusive access /// to the instance of `T`. This is achieved through the 'inout' operator, '&'. @frozen +@safe public struct Inout: ~Copyable, ~Escapable { @usableFromInline internal let _pointer: UnsafeMutablePointer @@ -30,7 +30,7 @@ public struct Inout: ~Copyable, ~Escapable { @_alwaysEmitIntoClient @_transparent public init(_ instance: inout T) { - _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + unsafe _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) } /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' @@ -41,13 +41,15 @@ public struct Inout: ~Copyable, ~Escapable { /// instance of type 'T'. /// - Parameter owner: The owning instance that this 'Inout' instance's /// lifetime is based on. + @lifetime(&owner) + @unsafe @_alwaysEmitIntoClient @_transparent public init( unsafeAddress: UnsafeMutablePointer, owner: inout Owner ) { - _pointer = unsafeAddress + unsafe _pointer = unsafeAddress } /// Unsafely initializes an instance of 'Inout' using the given @@ -56,13 +58,14 @@ public struct Inout: ~Copyable, ~Escapable { /// /// - Parameter unsafeImmortalAddress: The address to use to mutably reference /// an immortal instance of type 'T'. + @lifetime(immortal) + @unsafe @_alwaysEmitIntoClient @_transparent - @lifetime(immortal) public init( unsafeImmortalAddress: UnsafeMutablePointer ) { - _pointer = unsafeImmortalAddress + unsafe _pointer = unsafeImmortalAddress } } @@ -73,13 +76,13 @@ extension Inout where T: ~Copyable { public subscript() -> T { @_transparent unsafeAddress { - UnsafePointer(_pointer) + unsafe UnsafePointer(_pointer) } + @lifetime(copy self) @_transparent - nonmutating unsafeMutableAddress { - _pointer + unsafeMutableAddress { + unsafe _pointer } } } -#endif diff --git a/Sources/Future/Span/LifetimeOverride.swift b/Sources/Future/Span/LifetimeOverride.swift index 539409898..d5661a93f 100644 --- a/Sources/Future/Span/LifetimeOverride.swift +++ b/Sources/Future/Span/LifetimeOverride.swift @@ -53,7 +53,7 @@ internal func _overrideLifetime< @_unsafeNonescapableResult @_alwaysEmitIntoClient @_transparent -@lifetime(borrow source) +@lifetime(&source) public func _overrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable diff --git a/Sources/Future/Span/OutputSpan.swift b/Sources/Future/Span/OutputSpan.swift index d1e1cb4b2..9e3b5c0e5 100644 --- a/Sources/Future/Span/OutputSpan.swift +++ b/Sources/Future/Span/OutputSpan.swift @@ -364,7 +364,7 @@ extension OutputSpan where Element: ~Copyable { @_alwaysEmitIntoClient public var mutableSpan: MutableSpan { - @lifetime(borrow self) + @lifetime(&self) mutating get { let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) let buffer = unsafe UnsafeMutableBufferPointer( diff --git a/Sources/Future/StdlibAdditions.swift b/Sources/Future/StdlibAdditions.swift new file mode 100644 index 000000000..3a867cadd --- /dev/null +++ b/Sources/Future/StdlibAdditions.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +extension Optional where Wrapped: ~Copyable { + @lifetime(borrow self) + @_addressableSelf + public func borrow() -> Borrow? { + switch self { + case .some: + let pointer = unsafe UnsafePointer( + Builtin.unprotectedAddressOfBorrow(self) + ) + + return unsafe Borrow(unsafeAddress: pointer, owner: self) + + case .none: + return nil + } + } + + #if false // Compiler bug preventing this + @lifetime(&self) + public mutating func mutate() -> Inout? { + switch self { + case .some: + let pointer = unsafe UnsafeMutablePointer( + Builtin.unprotectedAddressOf(&self) + ) + + return unsafe Inout(unsafeAddress: pointer, owner: &self) + + case .none: + return nil + } + } + #endif +} diff --git a/Tests/FutureTests/BorrowTests.swift b/Tests/FutureTests/BorrowTests.swift new file mode 100644 index 000000000..2b1414078 --- /dev/null +++ b/Tests/FutureTests/BorrowTests.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future +import Synchronization + +final class BorrowTests: XCTestCase { + @available(SwiftStdlib 6.2, *) + func test_basic() { + let x: Atomic? = Atomic(0) + + if let y = x.borrow() { + XCTAssertEqual(y[].load(ordering: .relaxed), 0) + } + } +} diff --git a/Tests/FutureTests/BoxTests.swift b/Tests/FutureTests/BoxTests.swift index 9f1075e85..5230dafdc 100644 --- a/Tests/FutureTests/BoxTests.swift +++ b/Tests/FutureTests/BoxTests.swift @@ -12,9 +12,9 @@ import XCTest import Future -final class FutureBoxTests: XCTestCase { +final class BoxTests: XCTestCase { func test_basic() { - let intOnHeap = Box(0) + var intOnHeap = Box(0) XCTAssertEqual(intOnHeap[], 0) @@ -22,22 +22,14 @@ final class FutureBoxTests: XCTestCase { XCTAssertEqual(intOnHeap[], 123) - let inoutToIntOnHeap = intOnHeap.leak() + XCTAssertEqual(intOnHeap.copy(), 123) + + var inoutToIntOnHeap = intOnHeap.leak() XCTAssertEqual(inoutToIntOnHeap[], 123) inoutToIntOnHeap[] = 321 XCTAssertEqual(inoutToIntOnHeap[], 321) - - let intOnHeapAgain = Box(inoutToIntOnHeap) - - XCTAssertEqual(intOnHeapAgain[], 321) - - XCTAssertEqual(intOnHeapAgain.copy(), 321) - - let intInRegister = intOnHeapAgain.consume() - - XCTAssertEqual(intInRegister, 321) } } diff --git a/Tests/FutureTests/CellTests.swift b/Tests/FutureTests/CellTests.swift deleted file mode 100644 index 7588e1642..000000000 --- a/Tests/FutureTests/CellTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -import XCTest -import Future - -final class FutureCellTests: XCTestCase { - struct IntOnStack: ~Copyable { - var value = Cell(0) - } - - func test_basic() { - var myInt = IntOnStack() - - XCTAssertEqual(myInt.value[], 0) - - myInt.value[] = 123 - - XCTAssertEqual(myInt.value[], 123) - - let inoutToIntOnStack = myInt.value.asInout() - - inoutToIntOnStack[] = 321 - - XCTAssertEqual(myInt.value[], 321) - - XCTAssertEqual(myInt.value.copy(), 321) - } -} diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift index b0fa7a067..a4cbdce02 100644 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -3,13 +3,14 @@ import Future final class ContiguousStorageTests: XCTestCase { + @available(SwiftStdlib 6.2, *) func testBorrowArrayStorage() throws { let capacity = 10 var a: [Int] = [] a = Array(0.. Int { i+2 } + @lifetime(copy contiguous) init(_ contiguous: borrowing Span) { span = copy contiguous } @@ -35,14 +37,16 @@ final class ContiguousStorageTests: XCTestCase { subscript(_ p: Int) -> Int { span[p] } } + @available(SwiftStdlib 6.2, *) @inline(never) @lifetime(borrow array) private func skip( along array: borrowing Array ) -> Skipper { - Skipper(array.storage) + Skipper(array.span) } + @available(SwiftStdlib 6.2, *) func testSpanWrapper() { let capacity = 8 let a = Array(0..(count: c) { Counted($0) } for i in 0 ..< c { - expectEqual(array.borrowElement(at: i) { $0.value }, i) + expectEqual(array.borrowElement(at: i)[].value, i) expectEqual(array[i].value, i) } } @@ -72,7 +72,9 @@ class DynamicArrayTests: CollectionTestCase { var array = DynamicArray(count: c) { Counted($0) } for i in 0 ..< c { - array.updateElement(at: i) { $0.value += 100 } + // FIXME: 'exclusive' or something instead of mutating subscript + var me = array.mutateElement(at: i) + me[].value += 100 array[i].value += 100 } @@ -95,8 +97,7 @@ class DynamicArrayTests: CollectionTestCase { expectEqual(array.count, c) for i in 0 ..< c { - // FIXME: unexpected exclusivity violation (rdar://128441125) - //expectEqual(array.borrowElement(at: i) { $0.value }, 100 + i) + expectEqual(array.borrowElement(at: i)[].value, 100 + i) expectEqual(array[i].value, 100 + i) } @@ -114,8 +115,7 @@ class DynamicArrayTests: CollectionTestCase { expectEqual(array.count, c) for i in 0 ..< c { - // FIXME: unexpected exclusivity violation (rdar://128441125) - //expectEqual(array.borrowElement(at: i) { $0.value }, c + 99 - i) + expectEqual(array.borrowElement(at: i)[].value, c + 99 - i) expectEqual(array[i].value, c + 99 - i) } @@ -139,6 +139,7 @@ class DynamicArrayTests: CollectionTestCase { expectEqual(Counted.instances, 0) } + @available(SwiftStdlib 6.2, *) // For Span func test_iterate_full() { let c = 100 let array = DynamicArray(count: c) { Counted(100 + $0) } @@ -157,6 +158,7 @@ class DynamicArrayTests: CollectionTestCase { } } + @available(SwiftStdlib 6.2, *) // For Span func test_iterate_stepped() { let c = 100 let array = DynamicArray(count: c) { Counted($0) } diff --git a/Tests/FutureTests/Inout.swift b/Tests/FutureTests/Inout.swift index 8ef1a1001..f2d0ca017 100644 --- a/Tests/FutureTests/Inout.swift +++ b/Tests/FutureTests/Inout.swift @@ -12,10 +12,10 @@ import XCTest import Future -final class FutureInoutTests: XCTestCase { +final class InoutTests: XCTestCase { func test_basic() { var x = 0 - let y = Inout(&x) + var y = Inout(&x) var v = y[] XCTAssertEqual(v, 0) From c42b4d1ebc5d198401f3efbcda36874b987e29d4 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 16 Apr 2025 09:50:00 -0700 Subject: [PATCH 181/195] some more stuff --- Sources/Future/Borrow.swift | 2 +- Sources/Future/Box.swift | 14 +++++++++ Sources/Future/StdlibAdditions.swift | 45 +++++++++++++--------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Sources/Future/Borrow.swift b/Sources/Future/Borrow.swift index 2de2028c2..e4e8ae488 100644 --- a/Sources/Future/Borrow.swift +++ b/Sources/Future/Borrow.swift @@ -24,7 +24,7 @@ public struct Borrow: Copyable, ~Escapable { unsafe _pointer = UnsafePointer(Builtin.unprotectedAddressOfBorrow(value)) } - @lifetime(copy owner) + @lifetime(borrow owner) @_alwaysEmitIntoClient @_transparent public init( diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index cc942fbbb..b76205fdf 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -61,6 +61,20 @@ extension Box where T: ~Copyable { unsafe _pointer } } + + @lifetime(borrow self) + @_alwaysEmitIntoClient + @_transparent + public func borrow() -> Borrow { + unsafe Borrow(unsafeAddress: UnsafePointer(_pointer), owner: self) + } + + @lifetime(&self) + @_alwaysEmitIntoClient + @_transparent + public mutating func mutate() -> Inout { + unsafe Inout(unsafeAddress: _pointer, owner: &self) + } } extension Box where T: Copyable { diff --git a/Sources/Future/StdlibAdditions.swift b/Sources/Future/StdlibAdditions.swift index 3a867cadd..3ed48266a 100644 --- a/Sources/Future/StdlibAdditions.swift +++ b/Sources/Future/StdlibAdditions.swift @@ -15,33 +15,28 @@ extension Optional where Wrapped: ~Copyable { @lifetime(borrow self) @_addressableSelf public func borrow() -> Borrow? { - switch self { - case .some: - let pointer = unsafe UnsafePointer( - Builtin.unprotectedAddressOfBorrow(self) - ) - - return unsafe Borrow(unsafeAddress: pointer, owner: self) - - case .none: + if self == nil { return nil } + + let pointer = unsafe UnsafePointer( + Builtin.unprotectedAddressOfBorrow(self) + ) + + return unsafe Borrow(unsafeAddress: pointer, owner: self) } - #if false // Compiler bug preventing this - @lifetime(&self) - public mutating func mutate() -> Inout? { - switch self { - case .some: - let pointer = unsafe UnsafeMutablePointer( - Builtin.unprotectedAddressOf(&self) - ) - - return unsafe Inout(unsafeAddress: pointer, owner: &self) - - case .none: - return nil - } - } - #endif +// #if false // Compiler bug preventing this +// @lifetime(&self) +// public mutating func mutate() -> Inout? { +// if self == nil { +// return nil +// } +// +// let pointer = unsafe UnsafeMutablePointer( +// Builtin.unprotectedAddressOf(&self) +// ) +// +// return unsafe Inout(unsafeAddress: pointer, owner: &self) +// } } From 3d4c79a0a5baa408f5ffa3d134190089adc3f29e Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 17 Apr 2025 00:40:27 -0700 Subject: [PATCH 182/195] Overhaul Container protocols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Container, MutableContainer: Replace iterator protocols with direct methods that return the next span following a particular index. This is a radical simplification, but it reintroduces some index validation during iteration, and may have undesirable overhead for complex container types. - RigidArray, DynamicArray, NewArray: Update to reflect changes - Span: Conform to RandomAccessContainer - Borrow, Inout: Normalize labels of “owner” parameters. Expose pointers within the package. - Shared: Normalize read() into borrow() --- Package.swift | 5 +- Sources/Future/Borrow.swift | 16 ++- Sources/Future/Box.swift | 4 +- .../Containers/Arrays/DynamicArray.swift | 102 +++++------------ .../Future/Containers/Arrays/NewArray.swift | 27 +++++ .../Arrays/RigidArray+Iteration.swift | 62 ----------- .../Future/Containers/Arrays/RigidArray.swift | 95 +++++++++++----- .../Containers/BidirectionalContainer.swift | 2 +- Sources/Future/Containers/Container.swift | 41 ++----- .../Future/Containers/MutableContainer.swift | 28 ++--- .../Containers/RandomAccessContainer.swift | 105 +++++++++++++----- Sources/Future/Containers/Shared.swift | 10 +- Sources/Future/Inout.swift | 4 +- Sources/Future/Span/LifetimeOverride.swift | 4 +- Sources/Future/Span/OutputSpan.swift | 2 + Sources/Future/Span/Span+Container.swift | 76 +++++++++++++ Sources/Future/Span/SpanExtensions.swift | 48 -------- .../Span/StdlibOutputSpanExtensions.swift | 2 + .../Span/UnsafeBufferPointer+Additions.swift | 16 +-- Sources/Future/StdlibAdditions.swift | 2 +- Tests/FutureTests/DynamicArrayTests.swift | 21 ++-- .../SpanTests/MutableRawSpanTests.swift | 4 +- .../SpanTests/MutableSpanSlicing.swift | 4 +- .../SpanTests/MutableSpanTests.swift | 4 +- .../SpanTests/OutputSpanTests.swift | 2 +- .../SpanTests/RawSpanTests.swift | 2 +- .../SpanTests/SpanTests.swift | 2 +- .../StdlibOutputSpanExtensionTests.swift | 2 +- 28 files changed, 365 insertions(+), 327 deletions(-) delete mode 100644 Sources/Future/Containers/Arrays/RigidArray+Iteration.swift create mode 100644 Sources/Future/Span/Span+Container.swift rename Tests/{ => FutureTests}/SpanTests/MutableRawSpanTests.swift (99%) rename Tests/{ => FutureTests}/SpanTests/MutableSpanSlicing.swift (99%) rename Tests/{ => FutureTests}/SpanTests/MutableSpanTests.swift (99%) rename Tests/{ => FutureTests}/SpanTests/OutputSpanTests.swift (99%) rename Tests/{ => FutureTests}/SpanTests/RawSpanTests.swift (99%) rename Tests/{ => FutureTests}/SpanTests/SpanTests.swift (99%) rename Tests/{ => FutureTests}/SpanTests/StdlibOutputSpanExtensionTests.swift (99%) diff --git a/Package.swift b/Package.swift index 3bb3b34e3..58dd74f65 100644 --- a/Package.swift +++ b/Package.swift @@ -56,6 +56,8 @@ let availabilityMacros: KeyValuePairs = [ "SwiftStdlib 6.0": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", "SwiftStdlib 6.2": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999", + "SwiftCompatibilitySpan 5.0": "macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.1", + "SwiftCompatibilitySpan 6.2": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", ] let extraSettings: [SwiftSetting] = [ @@ -247,8 +249,7 @@ let targets: [CustomTarget] = [ .target( kind: .test, name: "FutureTests", - dependencies: ["Future", "_CollectionsTestSupport"], - sources: ["Tests/FutureTests", "Tests/SpanTests"] + dependencies: ["Future", "_CollectionsTestSupport"] ), .target( diff --git a/Sources/Future/Borrow.swift b/Sources/Future/Borrow.swift index e4e8ae488..7ba3d03da 100644 --- a/Sources/Future/Borrow.swift +++ b/Sources/Future/Borrow.swift @@ -15,7 +15,7 @@ import Builtin @safe public struct Borrow: Copyable, ~Escapable { @usableFromInline - let _pointer: UnsafePointer + package let _pointer: UnsafePointer @lifetime(borrow value) @_alwaysEmitIntoClient @@ -29,11 +29,21 @@ public struct Borrow: Copyable, ~Escapable { @_transparent public init( unsafeAddress: UnsafePointer, - owner: borrowing Owner + borrowing owner: borrowing Owner ) { unsafe _pointer = unsafeAddress } - + + @lifetime(copy owner) + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafePointer, + copying owner: borrowing Owner + ) { + unsafe _pointer = unsafeAddress + } + @_alwaysEmitIntoClient public subscript() -> T { @_transparent diff --git a/Sources/Future/Box.swift b/Sources/Future/Box.swift index b76205fdf..c7f3a7030 100644 --- a/Sources/Future/Box.swift +++ b/Sources/Future/Box.swift @@ -66,14 +66,14 @@ extension Box where T: ~Copyable { @_alwaysEmitIntoClient @_transparent public func borrow() -> Borrow { - unsafe Borrow(unsafeAddress: UnsafePointer(_pointer), owner: self) + unsafe Borrow(unsafeAddress: UnsafePointer(_pointer), borrowing: self) } @lifetime(&self) @_alwaysEmitIntoClient @_transparent public mutating func mutate() -> Inout { - unsafe Inout(unsafeAddress: _pointer, owner: &self) + unsafe Inout(unsafeAddress: _pointer, mutating: &self) } } diff --git a/Sources/Future/Containers/Arrays/DynamicArray.swift b/Sources/Future/Containers/Arrays/DynamicArray.swift index bcac2e712..01bcf0fa8 100644 --- a/Sources/Future/Containers/Arrays/DynamicArray.swift +++ b/Sources/Future/Containers/Arrays/DynamicArray.swift @@ -40,7 +40,7 @@ extension DynamicArray where Element: ~Copyable { } extension DynamicArray where Element: ~Copyable { - @available(SwiftStdlib 6.2, *) + @available(SwiftCompatibilitySpan 5.0, *) public var span: Span { @lifetime(borrow self) get { @@ -48,7 +48,7 @@ extension DynamicArray where Element: ~Copyable { } } - @available(SwiftStdlib 6.2, *) + @available(SwiftCompatibilitySpan 5.0, *) public var mutableSpan: MutableSpan { @lifetime(&self) @inlinable @@ -58,7 +58,19 @@ extension DynamicArray where Element: ~Copyable { } } +//MARK: RandomAccessContainer conformance + +@available(SwiftCompatibilitySpan 5.0, *) +extension DynamicArray: RandomAccessContainer where Element: ~Copyable { + @lifetime(borrow self) + public func span(following index: inout Int, maximumCount: Int) -> Span { + _storage.span(following: &index, maximumCount: maximumCount) + } +} + extension DynamicArray where Element: ~Copyable { + public typealias Index = Int + @inlinable public var isEmpty: Bool { _storage.isEmpty } @@ -76,83 +88,27 @@ extension DynamicArray where Element: ~Copyable { public func borrowElement(at index: Int) -> Borrow { _storage.borrowElement(at: index) } - +} + +// MARK: - MutableContainer conformance + +extension DynamicArray: MutableContainer where Element: ~Copyable { @inlinable @lifetime(&self) public mutating func mutateElement(at index: Int) -> Inout { _storage.mutateElement(at: index) } - - @inlinable - public subscript(position: Int) -> Element { - @inline(__always) - unsafeAddress { - unsafe borrowElement(at: position)._pointer - } - @inline(__always) - unsafeMutableAddress { - unsafe mutateElement(at: position)._pointer - } - } - @inlinable - public func index(after i: Int) -> Int { i + 1 } - - @inlinable - public func index(before i: Int) -> Int { i - 1 } - - @inlinable - public func formIndex(after index: inout Int) { - // Note: Range checks are deferred until element access. - index += 1 - } - - @inlinable - public func formIndex(before index: inout Int) { - // Note: Range checks are deferred until element access. - index -= 1 - } - - @inlinable - public func distance(from start: Int, to end: Int) -> Int { - // Note: Range checks are deferred until element access. - end - start - } - - @inlinable - public func index(_ index: Int, offsetBy n: Int) -> Int { - // Note: Range checks are deferred until element access. - index + n + @available(SwiftCompatibilitySpan 5.0, *) + @lifetime(&self) + public mutating func mutableSpan( + following index: inout Int, maximumCount: Int + ) -> MutableSpan { + _storage.mutableSpan(following: &index, maximumCount: maximumCount) } } -@available(SwiftStdlib 6.2, *) // For Span -extension DynamicArray: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = RigidArray.BorrowingIterator - public typealias Index = Int - - public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(for: _storage, startOffset: 0) - } - - @lifetime(borrow self) - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(for: _storage, startOffset: start) - } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - // Note: Range checks are deferred until element access. - position._offset - } - - @inlinable - public func formIndex( - _ index: inout Int, offsetBy distance: inout Int, limitedBy limit: Int - ) { - _storage.formIndex(&index, offsetBy: &distance, limitedBy: limit) - } -} +// MARK: - Range replacement operations extension DynamicArray where Element: ~Copyable { @inlinable @@ -167,9 +123,7 @@ extension DynamicArray where Element: ~Copyable { public mutating func reserveCapacity(_ n: Int) { _storage.reserveCapacity(n) } -} -extension DynamicArray where Element: ~Copyable { @inlinable internal static func _grow(_ capacity: Int) -> Int { 2 * capacity @@ -188,18 +142,14 @@ extension DynamicArray where Element: ~Copyable { _ensureFreeCapacity(1) _storage.append(item) } -} -extension DynamicArray where Element: ~Copyable { @inlinable public mutating func insert(_ item: consuming Element, at index: Int) { precondition(index >= 0 && index <= count) _ensureFreeCapacity(1) _storage.insert(item, at: index) } -} -extension DynamicArray { @inlinable public mutating func append(contentsOf items: some Sequence) { for item in items { diff --git a/Sources/Future/Containers/Arrays/NewArray.swift b/Sources/Future/Containers/Arrays/NewArray.swift index bb6a89464..e9fae9528 100644 --- a/Sources/Future/Containers/Arrays/NewArray.swift +++ b/Sources/Future/Containers/Arrays/NewArray.swift @@ -66,6 +66,7 @@ extension NewArray { extension NewArray: RandomAccessCollection, MutableCollection { public typealias Index = Int + public typealias Indices = Range @inlinable public var startIndex: Int { 0 } @@ -86,6 +87,32 @@ extension NewArray: RandomAccessCollection, MutableCollection { } } +@available(SwiftCompatibilitySpan 5.0, *) +extension NewArray: RandomAccessContainer, MutableContainer { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.value.borrowElement(at: index) + } + + @lifetime(borrow self) + public func span(following index: inout Int, maximumCount: Int) -> Span { + _storage.value.span(following: &index, maximumCount: maximumCount) + } + + @lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + _storage.value.mutateElement(at: index) + } + + @lifetime(&self) + public mutating func mutableSpan( + following index: inout Int, maximumCount: Int + ) -> MutableSpan { + _storage.value.mutableSpan(following: &index, maximumCount: maximumCount) + } +} + + #if false // FIXME: Implement extension NewArray: RangeReplaceableCollection { public init() { diff --git a/Sources/Future/Containers/Arrays/RigidArray+Iteration.swift b/Sources/Future/Containers/Arrays/RigidArray+Iteration.swift deleted file mode 100644 index f7eb6e613..000000000 --- a/Sources/Future/Containers/Arrays/RigidArray+Iteration.swift +++ /dev/null @@ -1,62 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -@available(SwiftStdlib 6.2, *) // For Span -extension RigidArray: RandomAccessContainer where Element: ~Copyable { - @safe - public struct BorrowingIterator: BorrowingIteratorProtocol, ~Escapable { - @usableFromInline - internal let _items: UnsafeBufferPointer - - @usableFromInline - internal var _offset: Int - - @inlinable - @lifetime(borrow array) - internal init(for array: borrowing RigidArray, startOffset: Int) { - unsafe self._items = UnsafeBufferPointer(array._items) - self._offset = startOffset - } - - @inlinable - internal var _span: Span { - @lifetime(copy self) - get { - unsafe Span(_unsafeElements: _items) - } - } - - @lifetime(copy self) - public mutating func nextChunk( - maximumCount: Int - ) -> Span { - let end = unsafe _offset + Swift.min(maximumCount, _items.count - _offset) - defer { _offset = end } - return unsafe _span._extracting(Range(uncheckedBounds: (_offset, end))) - } - } - - @lifetime(borrow self) - public func startBorrowingIteration() -> BorrowingIterator { - BorrowingIterator(for: self, startOffset: 0) - } - - @lifetime(borrow self) - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - BorrowingIterator(for: self, startOffset: start) - } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - precondition(unsafe position._items === UnsafeBufferPointer(self._items)) - return position._offset - } -} diff --git a/Sources/Future/Containers/Arrays/RigidArray.swift b/Sources/Future/Containers/Arrays/RigidArray.swift index 9e01e0477..acc4732f2 100644 --- a/Sources/Future/Containers/Arrays/RigidArray.swift +++ b/Sources/Future/Containers/Arrays/RigidArray.swift @@ -74,8 +74,10 @@ extension RigidArray where Element: ~Copyable { } } +//MARK: - Span creation + extension RigidArray where Element: ~Copyable { - @available(SwiftStdlib 6.2, *) + @available(SwiftCompatibilitySpan 5.0, *) public var span: Span { @lifetime(borrow self) @inlinable @@ -84,8 +86,8 @@ extension RigidArray where Element: ~Copyable { return unsafe _overrideLifetime(result, borrowing: self) } } - - @available(SwiftStdlib 6.2, *) + + @available(SwiftCompatibilitySpan 5.0, *) public var mutableSpan: MutableSpan { @lifetime(&self) @inlinable @@ -94,58 +96,97 @@ extension RigidArray where Element: ~Copyable { return unsafe _overrideLifetime(result, mutating: &self) } } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + @lifetime(borrow self) + internal func _span(in range: Range) -> Span { + let result = unsafe Span(_unsafeElements: _items.extracting(range)) + return unsafe _overrideLifetime(result, borrowing: self) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + @lifetime(&self) + internal mutating func _mutableSpan(in range: Range) -> MutableSpan { + let result = unsafe MutableSpan(_unsafeElements: _items.extracting(range)) + return unsafe _overrideLifetime(result, mutating: &self) + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + internal func _subrange(following index: inout Int, maximumCount: Int) -> Range { + precondition(index >= 0 && index <= _count, "Index out of bounds") + let end = index + Swift.min(_count - index, maximumCount) + defer { index = end } + return unsafe Range(uncheckedBounds: (index, end)) + } +} + +//MARK: - RandomAccessContainer conformance + +@available(SwiftCompatibilitySpan 5.0, *) +extension RigidArray: RandomAccessContainer where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + public func span(following index: inout Int, maximumCount: Int) -> Span { + _span(in: _subrange(following: &index, maximumCount: maximumCount)) + } } extension RigidArray where Element: ~Copyable { public typealias Index = Int - + @inlinable public var isEmpty: Bool { count == 0 } - + @inlinable public var count: Int { _count } - + @inlinable public var startIndex: Int { 0 } - + @inlinable public var endIndex: Int { count } -} -extension RigidArray where Element: ~Copyable { @inlinable @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { - precondition(index >= 0 && index < _count) + precondition(index >= 0 && index < _count, "Index out of bounds") return unsafe Borrow( unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), - owner: self + borrowing: self ) } - +} + +//MARK: - MutableContainer conformance + +@available(SwiftCompatibilitySpan 5.0, *) +extension RigidArray: MutableContainer where Element: ~Copyable { + @lifetime(&self) + public mutating func mutableSpan( + following index: inout Int, maximumCount: Int + ) -> MutableSpan { + _mutableSpan(in: _subrange(following: &index, maximumCount: maximumCount)) + } +} + +extension RigidArray where Element: ~Copyable { @inlinable @lifetime(&self) public mutating func mutateElement(at index: Int) -> Inout { precondition(index >= 0 && index < _count) return unsafe Inout( unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), - owner: &self + mutating: &self ) } - - @inlinable - public subscript(position: Int) -> Element { - @inline(__always) - unsafeAddress { - unsafe borrowElement(at: position)._pointer - } - @inline(__always) - unsafeMutableAddress { - unsafe mutateElement(at: position)._pointer - } - } } +//MARK: - Resizing + extension RigidArray where Element: ~Copyable { @inlinable public mutating func resize(to newCapacity: Int) { @@ -165,6 +206,8 @@ extension RigidArray where Element: ~Copyable { } } +//MARK: Range replacement operations + extension RigidArray where Element: ~Copyable { @inlinable @discardableResult @@ -214,6 +257,8 @@ extension RigidArray { } } +//MARK: - Copying and moving helpers + extension RigidArray { @inlinable internal func _copy() -> Self { diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift index fa7825aee..84faaf3d1 100644 --- a/Sources/Future/Containers/BidirectionalContainer.swift +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(SwiftStdlib 6.2, *) // For Span +@available(SwiftCompatibilitySpan 5.0, *) public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { override associatedtype Element: ~Copyable diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index f9cff7031..02ba067a7 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -9,27 +9,10 @@ // //===----------------------------------------------------------------------===// -@available(SwiftStdlib 6.2, *) // For Span -public protocol BorrowingIteratorProtocol: ~Escapable { - associatedtype Element: ~Copyable - - @lifetime(copy self) - mutating func nextChunk(maximumCount: Int) -> Span -} - -@available(SwiftStdlib 6.2, *) // For Span +@available(SwiftCompatibilitySpan 5.0, *) public protocol Container: ~Copyable, ~Escapable { associatedtype Element: ~Copyable - associatedtype BorrowingIterator: BorrowingIteratorProtocol, ~Escapable - where BorrowingIterator.Element == Element - - @lifetime(copy self) - borrowing func startBorrowingIteration() -> BorrowingIterator - - @lifetime(copy self) - borrowing func startBorrowingIteration(from start: Index) -> BorrowingIterator - associatedtype Index: Comparable var isEmpty: Bool { get } @@ -37,31 +20,31 @@ public protocol Container: ~Copyable, ~Escapable { var startIndex: Index { get } var endIndex: Index { get } - - /// This is a temporary stand-in for the subscript requirement that we actually want: - /// - /// subscript(index: Index) -> Element { borrow } + + #if compiler(>=9999) // We want this but we can't do it yet + subscript(index: Index) -> Element { borrow } + #else @lifetime(copy self) func borrowElement(at index: Index) -> Borrow - - func index(after index: Index) -> Index - func formIndex(after i: inout Index) + #endif - func index(at position: borrowing BorrowingIterator) -> Index + @lifetime(copy self) + func span(following index: inout Index, maximumCount: Int) -> Span + func index(after index: Index) -> Index + func formIndex(after i: inout Index) func distance(from start: Index, to end: Index) -> Int - func index(_ index: Index, offsetBy n: Int) -> Index - func formIndex( _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index ) } -@available(SwiftStdlib 6.2, *) +@available(SwiftCompatibilitySpan 5.0, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable public subscript(index: Index) -> Element { + @lifetime(copy self) unsafeAddress { unsafe borrowElement(at: index)._pointer } diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index 29d719128..67c8f4f0a 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -9,32 +9,24 @@ // //===----------------------------------------------------------------------===// -@available(SwiftStdlib 6.2, *) // for MutableSpan -public protocol Muterator: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable - - @lifetime(&self) - mutating func nextChunk(maximumCount: Int) -> MutableSpan -} - -@available(SwiftStdlib 6.2, *) // for MutableSpan +@available(SwiftCompatibilitySpan 5.0, *) public protocol MutableContainer: Container, ~Copyable, ~Escapable { - associatedtype MutatingIterationState: ~Copyable, ~Escapable - - @lifetime(&self) - mutating func startMutatingIteration() -> MutatingIterationState - - /// This is a temporary stand-in for the subscript requirement that we actually want: - /// - /// subscript(index: Index) -> Element { borrow mutate } +#if compiler(>=9999) // We want this but we can't do it yet + subscript(index: Index) -> Element { borrow mutate } +#else @lifetime(&self) mutating func mutateElement(at index: Index) -> Inout +#endif + + @lifetime(&self) + mutating func mutableSpan(following index: inout Index, maximumCount: Int) -> MutableSpan } -@available(SwiftStdlib 6.2, *) // for MutableSpan +@available(SwiftCompatibilitySpan 5.0, *) extension MutableContainer where Self: ~Copyable & ~Escapable { @inlinable public subscript(index: Index) -> Element { + @lifetime(borrow self) unsafeAddress { unsafe borrowElement(at: index)._pointer } diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift index 4a8752dd4..a3b61ea7b 100644 --- a/Sources/Future/Containers/RandomAccessContainer.swift +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -9,38 +9,15 @@ // //===----------------------------------------------------------------------===// -@available(SwiftStdlib 6.2, *) // For Span +@available(SwiftCompatibilitySpan 5.0, *) public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { override associatedtype Element: ~Copyable } -extension Strideable { - @inlinable - public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { - if distance >= 0 { - guard limit >= self else { - self = self.advanced(by: distance) - distance = 0 - return - } - let d = Swift.min(distance, self.distance(to: limit)) - self = self.advanced(by: d) - distance -= d - } else { - guard limit <= self else { - self = self.advanced(by: distance) - distance = 0 - return - } - let d = Swift.max(distance, self.distance(to: limit)) - self = self.advanced(by: d) - distance -= d - } - } -} - -@available(SwiftStdlib 6.2, *) // For Span -extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Self: ~Copyable { +@available(SwiftCompatibilitySpan 5.0, *) +extension RandomAccessContainer +where Self: ~Copyable & ~Escapable, Index: Strideable, Index.Stride == Int +{ @inlinable public func index(after index: Index) -> Index { // Note: Range checks are deferred until element access. @@ -86,3 +63,75 @@ extension RandomAccessContainer where Index: Strideable, Index.Stride == Int, Se } } +// Disambiguate parallel extensions on RandomAccessContainer and RandomAccessCollection + +@available(SwiftCompatibilitySpan 5.0, *) +extension RandomAccessCollection +where + Self: RandomAccessContainer, + Index: Strideable, + Index.Stride == Int, + Indices == Range +{ + @inlinable + public func index(after index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: 1) + } + + @inlinable + public func index(before index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: -1) + } + + @inlinable + public func formIndex(after index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: 1) + } + + @inlinable + public func formIndex(before index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: -1) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + // Note: Range checks are deferred until element access. + start.distance(to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: n) + } +} + +extension Strideable { + @inlinable + public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { + if distance >= 0 { + guard limit >= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.min(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } else { + guard limit <= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.max(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } + } +} + diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Containers/Shared.swift index 3f69347c2..f5de3d1bb 100644 --- a/Sources/Future/Containers/Shared.swift +++ b/Sources/Future/Containers/Shared.swift @@ -110,9 +110,11 @@ extension Shared where Storage: ~Copyable { @inlinable @inline(__always) public var value: Storage { + @lifetime(borrow self) unsafeAddress { - unsafe read()._pointer + unsafe _address } + @lifetime(&self) unsafeMutableAddress { precondition(isUnique()) return unsafe _mutableAddress @@ -123,11 +125,11 @@ extension Shared where Storage: ~Copyable { extension Shared where Storage: ~Copyable { @inlinable @lifetime(borrow self) - public borrowing func read() -> Borrow { + public borrowing func borrow() -> Borrow { // This is gloriously (and very explicitly) unsafe, as it should be. // `Shared` is carefully constructed to guarantee that // lifetime(self) == lifetime(_box.storage). - unsafe Borrow(unsafeAddress: _address, owner: self) + unsafe Borrow(unsafeAddress: _address, borrowing: self) } @inlinable @@ -136,7 +138,7 @@ extension Shared where Storage: ~Copyable { // This is gloriously (and very explicitly) unsafe, as it should be. // `Shared` is carefully constructed to guarantee that // lifetime(self) == lifetime(_box.storage). - unsafe Inout(unsafeAddress: _mutableAddress, owner: &self) + unsafe Inout(unsafeAddress: _mutableAddress, mutating: &self) } } diff --git a/Sources/Future/Inout.swift b/Sources/Future/Inout.swift index cc33ec82f..b4e7a5789 100644 --- a/Sources/Future/Inout.swift +++ b/Sources/Future/Inout.swift @@ -21,7 +21,7 @@ import Builtin @safe public struct Inout: ~Copyable, ~Escapable { @usableFromInline - internal let _pointer: UnsafeMutablePointer + package let _pointer: UnsafeMutablePointer /// Initializes an instance of 'Inout' extending the exclusive access of the /// passed instance. @@ -47,7 +47,7 @@ public struct Inout: ~Copyable, ~Escapable { @_transparent public init( unsafeAddress: UnsafeMutablePointer, - owner: inout Owner + mutating owner: inout Owner ) { unsafe _pointer = unsafeAddress } diff --git a/Sources/Future/Span/LifetimeOverride.swift b/Sources/Future/Span/LifetimeOverride.swift index d5661a93f..e6fc96abe 100644 --- a/Sources/Future/Span/LifetimeOverride.swift +++ b/Sources/Future/Span/LifetimeOverride.swift @@ -18,7 +18,7 @@ @_alwaysEmitIntoClient @_transparent @lifetime(borrow source) -internal func _overrideLifetime< +public func _overrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable >( _ dependent: consuming T, borrowing source: borrowing U @@ -36,7 +36,7 @@ internal func _overrideLifetime< @_alwaysEmitIntoClient @_transparent @lifetime(copy source) -internal func _overrideLifetime< +public func _overrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable >( _ dependent: consuming T, copying source: borrowing U diff --git a/Sources/Future/Span/OutputSpan.swift b/Sources/Future/Span/OutputSpan.swift index 9e3b5c0e5..b54ea24f9 100644 --- a/Sources/Future/Span/OutputSpan.swift +++ b/Sources/Future/Span/OutputSpan.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#if false // FIXME: Revive // OutputSpan represents a span of memory which contains // a variable number of `Element` instances, followed by uninitialized memory. @safe @@ -403,3 +404,4 @@ extension OutputSpan where Element: BitwiseCopyable { return unsafe UnsafeMutableRawBufferPointer(start: start, count: byteCount) } } +#endif diff --git a/Sources/Future/Span/Span+Container.swift b/Sources/Future/Span/Span+Container.swift new file mode 100644 index 000000000..ef23c7096 --- /dev/null +++ b/Sources/Future/Span/Span+Container.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@available(SwiftStdlib 6.2, *) +extension Span: RandomAccessContainer where Element: ~Copyable { + @inlinable + public var startIndex: Int { 0 } + @inlinable + public var endIndex: Int { count } + + @inlinable + @lifetime(copy self) + public func borrowElement(at index: Int) -> Borrow { + unsafe Borrow(unsafeAddress: _unsafeAddressOfElement(index), copying: self) + } + + @lifetime(copy self) + public func span(following index: inout Index, maximumCount: Int) -> Span { + precondition(index >= 0 && index <= count, "Invalid index") + let end = index + Swift.min(count - index, maximumCount) + defer { index = end } + return _extracting(unsafe Range(uncheckedBounds: (index, end))) + } +} + +@available(SwiftStdlib 6.2, *) +extension Span where Element: ~Copyable { + @usableFromInline + typealias _Components = (pointer: UnsafeRawPointer?, count: Int) + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + @_transparent + @lifetime(copy self) + internal func _unsafelyExplode() -> _Components { + unsafe assert( + MemoryLayout.size == MemoryLayout<_Components>.size, + "Unexpected Span layout") + let immortal = unsafe _overrideLifetime(self, borrowing: immortalThing) + return unsafe Builtin.reinterpretCast(immortal) + } + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + unchecked position: Index + ) -> UnsafePointer { + let (start, _) = unsafe _unsafelyExplode() + let elementOffset = position &* MemoryLayout.stride + let address = unsafe start.unsafelyUnwrapped.advanced(by: elementOffset) + return unsafe address.assumingMemoryBound(to: Element.self) + } + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + _ position: Index + ) -> UnsafePointer { + precondition(position >= 0 && position < count, "Index out of bounds") + return unsafe _unsafeAddressOfElement(unchecked: position) + } +} + diff --git a/Sources/Future/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift index 414fb1db1..a1af10451 100644 --- a/Sources/Future/Span/SpanExtensions.swift +++ b/Sources/Future/Span/SpanExtensions.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import Builtin - @usableFromInline internal let immortalThing: [Void] = [] @@ -34,52 +32,6 @@ extension Span where Element: ~Copyable { } } -#if false // Use it or lose it -@available(SwiftStdlib 6.2, *) -extension Span where Element: ~Copyable { - @usableFromInline - typealias _Components = (pointer: UnsafeRawPointer?, count: Int) - - // FIXME: This is *wildly* unsafe; remove it. - @unsafe - @_alwaysEmitIntoClient - @_transparent - @lifetime(copy self) - internal func _unsafelyExplode() -> _Components { - unsafe assert( - MemoryLayout.size == MemoryLayout<_Components>.size, - "Unexpected Span layout") - let immortal = unsafe _overrideLifetime(self, borrowing: immortalThing) - return unsafe Builtin.reinterpretCast(immortal) - } - - // FIXME: This is *wildly* unsafe; remove it. - @unsafe - @_alwaysEmitIntoClient - internal func _unsafeAddressOfElement( - unchecked position: Index - ) -> UnsafePointer { - let (start, _) = unsafe _unsafelyExplode() - let elementOffset = position &* MemoryLayout.stride - let address = unsafe start.unsafelyUnwrapped.advanced(by: elementOffset) - return unsafe address.assumingMemoryBound(to: Element.self) - } - - // FIXME: This is *wildly* unsafe; remove it. - @unsafe - @_alwaysEmitIntoClient - internal func _unsafeAddressOfElement( - _ position: Index - ) -> UnsafePointer { - let (start, count) = unsafe _unsafelyExplode() - precondition(position >= 0 && position < count, "Index out of bounds") - let elementOffset = position &* MemoryLayout.stride - let address = unsafe start.unsafelyUnwrapped.advanced(by: elementOffset) - return unsafe address.assumingMemoryBound(to: Element.self) - } -} -#endif - @available(SwiftStdlib 6.2, *) extension Span where Element: Equatable { /// Returns a Boolean value indicating whether this and another span diff --git a/Sources/Future/Span/StdlibOutputSpanExtensions.swift b/Sources/Future/Span/StdlibOutputSpanExtensions.swift index 5a81b5bc5..624bca8a5 100644 --- a/Sources/Future/Span/StdlibOutputSpanExtensions.swift +++ b/Sources/Future/Span/StdlibOutputSpanExtensions.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#if false // FIXME: Revive extension Array { @available(macOS 9999, *) @@ -86,3 +87,4 @@ extension Data { self.replaceSubrange(count.. Bool { - unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) } } @@ -23,8 +23,8 @@ extension UnsafeMutableBufferPointer where Element: ~Copyable { /// `UnsafeMutableBufferPointer` instances refer to the same region in /// memory. @inlinable @inline(__always) - public static func ===(_ a: Self, _ b: Self) -> Bool { - unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) } } @@ -32,8 +32,8 @@ extension UnsafeRawBufferPointer { /// Returns a Boolean value indicating whether two `UnsafeRawBufferPointer` /// instances refer to the same region in memory. @inlinable @inline(__always) - public static func ===(_ a: Self, _ b: Self) -> Bool { - unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) } } @@ -42,7 +42,7 @@ extension UnsafeMutableRawBufferPointer { /// `UnsafeMutableRawBufferPointer` instances refer to the same region in /// memory. @inlinable @inline(__always) - public static func ===(_ a: Self, _ b: Self) -> Bool { - unsafe (a.baseAddress == b.baseAddress) && (a.count == b.count) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) } } diff --git a/Sources/Future/StdlibAdditions.swift b/Sources/Future/StdlibAdditions.swift index 3ed48266a..31dec320f 100644 --- a/Sources/Future/StdlibAdditions.swift +++ b/Sources/Future/StdlibAdditions.swift @@ -23,7 +23,7 @@ extension Optional where Wrapped: ~Copyable { Builtin.unprotectedAddressOfBorrow(self) ) - return unsafe Borrow(unsafeAddress: pointer, owner: self) + return unsafe Borrow(unsafeAddress: pointer, borrowing: self) } // #if false // Compiler bug preventing this diff --git a/Tests/FutureTests/DynamicArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift index fbb0e5983..18d25bd6f 100644 --- a/Tests/FutureTests/DynamicArrayTests.swift +++ b/Tests/FutureTests/DynamicArrayTests.swift @@ -139,36 +139,38 @@ class DynamicArrayTests: CollectionTestCase { expectEqual(Counted.instances, 0) } - @available(SwiftStdlib 6.2, *) // For Span + @available(SwiftCompatibilitySpan 5.0, *) func test_iterate_full() { let c = 100 let array = DynamicArray(count: c) { Counted(100 + $0) } - var state = array.startBorrowingIteration() + var index = 0 do { - let span = state.nextChunk(maximumCount: Int.max) + let span = array.span(following: &index, maximumCount: Int.max) expectEqual(span.count, c) for i in 0 ..< span.count { expectEqual(span[i].value, 100 + i) } } do { - let span2 = state.nextChunk(maximumCount: Int.max) + let span2 = array.span(following: &index, maximumCount: Int.max) expectEqual(span2.count, 0) } } - @available(SwiftStdlib 6.2, *) // For Span + @available(SwiftCompatibilitySpan 5.0, *) func test_iterate_stepped() { let c = 100 let array = DynamicArray(count: c) { Counted($0) } withEvery("stride", in: 1 ... c) { stride in - var state = array.startBorrowingIteration() + var index = 0 var i = 0 while true { - let span = state.nextChunk(maximumCount: stride) - if span.count == 0 { break } + expectEqual(index, i) + let span = array.span(following: &index, maximumCount: stride) + expectEqual(index, i + span.count) + if span.isEmpty { break } expectEqual(span.count, i + stride <= c ? stride : c % stride) for j in 0 ..< span.count { expectEqual(span[j].value, i) @@ -176,7 +178,8 @@ class DynamicArrayTests: CollectionTestCase { } } expectEqual(i, c) - expectEqual(state.nextChunk(maximumCount: Int.max).count, 0) + expectEqual(array.span(following: &index, maximumCount: Int.max).count, 0) + expectEqual(index, i) } } } diff --git a/Tests/SpanTests/MutableRawSpanTests.swift b/Tests/FutureTests/SpanTests/MutableRawSpanTests.swift similarity index 99% rename from Tests/SpanTests/MutableRawSpanTests.swift rename to Tests/FutureTests/SpanTests/MutableRawSpanTests.swift index 2dd1a4367..a3956c1d7 100644 --- a/Tests/SpanTests/MutableRawSpanTests.swift +++ b/Tests/FutureTests/SpanTests/MutableRawSpanTests.swift @@ -11,8 +11,9 @@ //===----------------------------------------------------------------------===// import XCTest -import Span +import Future +#if false @available(macOS 9999, *) final class MutableRawSpanTests: XCTestCase { @@ -354,3 +355,4 @@ final class MutableRawSpanTests: XCTestCase { } } } +#endif diff --git a/Tests/SpanTests/MutableSpanSlicing.swift b/Tests/FutureTests/SpanTests/MutableSpanSlicing.swift similarity index 99% rename from Tests/SpanTests/MutableSpanSlicing.swift rename to Tests/FutureTests/SpanTests/MutableSpanSlicing.swift index 60cfe4bdb..1d8ab8bde 100644 --- a/Tests/SpanTests/MutableSpanSlicing.swift +++ b/Tests/FutureTests/SpanTests/MutableSpanSlicing.swift @@ -11,8 +11,9 @@ //===----------------------------------------------------------------------===// import XCTest -import Span +import Future +#if false //MARK: slicing tests and examples @available(macOS 9999, *) extension MutableSpanTests { @@ -142,3 +143,4 @@ extension MutableSpanTests { XCTAssertTrue(suffix._elementsEqual(repeated)) } } +#endif diff --git a/Tests/SpanTests/MutableSpanTests.swift b/Tests/FutureTests/SpanTests/MutableSpanTests.swift similarity index 99% rename from Tests/SpanTests/MutableSpanTests.swift rename to Tests/FutureTests/SpanTests/MutableSpanTests.swift index a830fcdd0..6134adba1 100644 --- a/Tests/SpanTests/MutableSpanTests.swift +++ b/Tests/FutureTests/SpanTests/MutableSpanTests.swift @@ -11,8 +11,9 @@ //===----------------------------------------------------------------------===// import XCTest -import Span +import Future +#if false class ID { let id: Int init(id: Int) { @@ -591,3 +592,4 @@ final class MutableSpanTests: XCTestCase { } } } +#endif diff --git a/Tests/SpanTests/OutputSpanTests.swift b/Tests/FutureTests/SpanTests/OutputSpanTests.swift similarity index 99% rename from Tests/SpanTests/OutputSpanTests.swift rename to Tests/FutureTests/SpanTests/OutputSpanTests.swift index fcac8ce0e..486a239a3 100644 --- a/Tests/SpanTests/OutputSpanTests.swift +++ b/Tests/FutureTests/SpanTests/OutputSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Span +import Future @available(macOS 9999, *) struct Allocation: ~Copyable { diff --git a/Tests/SpanTests/RawSpanTests.swift b/Tests/FutureTests/SpanTests/RawSpanTests.swift similarity index 99% rename from Tests/SpanTests/RawSpanTests.swift rename to Tests/FutureTests/SpanTests/RawSpanTests.swift index 7f2838bca..0abee0d82 100644 --- a/Tests/SpanTests/RawSpanTests.swift +++ b/Tests/FutureTests/SpanTests/RawSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import Span +@testable import Future @available(macOS 9999, *) final class RawSpanTests: XCTestCase { diff --git a/Tests/SpanTests/SpanTests.swift b/Tests/FutureTests/SpanTests/SpanTests.swift similarity index 99% rename from Tests/SpanTests/SpanTests.swift rename to Tests/FutureTests/SpanTests/SpanTests.swift index 596547111..00b333ff0 100644 --- a/Tests/SpanTests/SpanTests.swift +++ b/Tests/FutureTests/SpanTests/SpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Span +import Future @available(macOS 9999, *) final class SpanTests: XCTestCase { diff --git a/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift b/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift similarity index 99% rename from Tests/SpanTests/StdlibOutputSpanExtensionTests.swift rename to Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift index a8f883226..bc24c3260 100644 --- a/Tests/SpanTests/StdlibOutputSpanExtensionTests.swift +++ b/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift @@ -12,7 +12,7 @@ import XCTest import Foundation -import Span +import Future @available(macOS 9999, *) final class StdlibOutputSpanExtensionTests: XCTestCase { From 8a73c1be08069b9aba3712bc38e2539c4d2aa47a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 17 Apr 2025 00:41:05 -0700 Subject: [PATCH 183/195] Revive DequeModule Lots to do still, but the thing passes tests now --- Sources/DequeModule/Deque+Collection.swift | 44 ++------- Sources/DequeModule/Deque+Container.swift | 37 ++------ Sources/DequeModule/Deque+Equatable.swift | 2 +- Sources/DequeModule/Deque+Testing.swift | 2 +- Sources/DequeModule/Deque.swift | 20 ++-- Sources/DequeModule/DynamicDeque.swift | 34 +++---- Sources/DequeModule/RigidDeque.swift | 94 ++++++++----------- Sources/DequeModule/_UnsafeDequeHandle.swift | 25 +++-- .../DequeModule/_UnsafeDequeSegments.swift | 6 +- 9 files changed, 101 insertions(+), 163 deletions(-) diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index 0e308724d..f8198a5d6 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -11,10 +11,9 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities +import Future #endif -import Span - extension Deque { // Implementation note: we could also use the default `IndexingIterator` here. // This custom implementation performs direct storage access to eliminate any @@ -183,7 +182,7 @@ extension Deque: RandomAccessCollection { /// - Complexity: O(1) @inlinable @inline(__always) - public var count: Int { _storage.read { $0.count } } + public var count: Int { _storage.value.count } /// The position of the first element in a nonempty deque. /// @@ -360,39 +359,14 @@ extension Deque: RandomAccessCollection { /// writing is O(`count`). @inlinable public subscript(index: Int) -> Element { - get { - return _storage.read { $0[index] } - } - set { - _storage.update { $0[index] = newValue } - } - @inline(__always) // https://github.com/apple/swift-collections/issues/164 - _modify { - precondition(index >= 0 && index < count, "Index out of bounds") - var (slot, value) = _prepareForModify(at: index) - defer { - _finalizeModify(slot, value) - } - yield &value - } - } - - @inlinable - internal mutating func _prepareForModify(at index: Int) -> (_Slot, Element) { - _ensureUnique() - // We technically aren't supposed to escape storage pointers out of a - // managed buffer, so we escape a `(slot, value)` pair instead, leaving - // the corresponding slot temporarily uninitialized. - return _update { handle in - let slot = handle.slot(forOffset: index) - return (slot, handle.mutablePtr(at: slot).move()) + @lifetime(borrow self) + unsafeAddress { + _storage.value.borrowElement(at: index)._pointer } - } - - @inlinable - internal mutating func _finalizeModify(_ slot: _Slot, _ value: __owned Element) { - _update { handle in - handle.mutablePtr(at: slot).initialize(to: value) + @lifetime(&self) + unsafeMutableAddress { + _ensureUnique() + return _storage.value.mutateElement(at: index)._pointer } } diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index 0b714a24d..e28e265b8 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -10,39 +10,18 @@ //===----------------------------------------------------------------------===// #if !COLLECTIONS_SINGLE_MODULE -import Span +import Future #endif extension Deque: RandomAccessContainer { - public typealias BorrowingIterator = RigidDeque.BorrowingIterator - - @inlinable - public func startBorrowingIteration() -> BorrowingIterator { - self.startBorrowingIteration(from: 0) - } - - @inlinable - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - // FIXME: This is unacceptably unsafe. We want to access `_storage.value` - // FIXME: as if it was a structural part of `self`, but there is no way - // FIXME: to express this in Swift. - BorrowingIterator( - _unsafeSegments: _storage.value._handle.segments(), - startOffset: start, - owner: self) - } - - @inlinable - public func index(at position: borrowing BorrowingIterator) -> Int { - precondition(_read { $0.segments().isIdentical(to: position._segments) }) - return position._offset + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.value.borrowElement(at: index) } - @inlinable - public func formIndex( - _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index - ) { - // Note: Range checks are deferred until element access. - index.advance(by: &distance, limitedBy: limit) + @available(SwiftCompatibilitySpan 5.0, *) + @lifetime(borrow self) + public func span(following index: inout Int, maximumCount: Int) -> Span { + _storage.value.span(following: &index, maximumCount: maximumCount) } } diff --git a/Sources/DequeModule/Deque+Equatable.swift b/Sources/DequeModule/Deque+Equatable.swift index 0c64445e3..eb0f4709e 100644 --- a/Sources/DequeModule/Deque+Equatable.swift +++ b/Sources/DequeModule/Deque+Equatable.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import Span +import Future extension Deque: Equatable where Element: Equatable { /// Returns a Boolean value indicating whether two values are equal. Two diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index ce4dbea65..0d8a1d7f4 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -49,7 +49,7 @@ extension Deque { /// target. @_spi(Testing) public var _unstableStartSlot: Int { - _storage.read { $0._handle.startSlot.position } + _storage.value._handle.startSlot.position } /// Constructs a deque instance of the specified contents and layout. Exposed diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index c52bbc0a6..f3b8fd1d1 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import Span +import Future /// A collection implementing a double-ended queue. `Deque` (pronounced "deck") /// implements an ordered random-access collection that supports efficient @@ -117,9 +117,7 @@ extension Deque { internal func _read( _ body: (borrowing _UnsafeHandle) throws(E) -> R ) throws(E) -> R { - try _storage.read { rigid throws(E) in - try body(rigid._handle) - } + return try body(_storage.value._handle) } @inlinable @@ -128,9 +126,9 @@ extension Deque { _ body: (inout _UnsafeHandle) throws(E) -> R ) throws(E) -> R { _ensureUnique() - return try _storage.update { v throws(E) in - try body(&v._handle) - } + var rigid = _storage.mutate() + defer { extendLifetime(rigid) } + return try body(&rigid[]._handle) } } @@ -156,18 +154,18 @@ extension Deque { @inlinable @inline(never) internal func _makeUniqueCopy() -> Self { - Deque(_storage: _storage.read { $0._copy() }) + Deque(_storage: _storage.value._copy()) } @inlinable @inline(never) internal func _makeUniqueCopy(capacity: Int) -> Self { - Deque(_storage: _storage.read { $0._copy(capacity: capacity) }) + Deque(_storage: _storage.value._copy(capacity: capacity)) } @inlinable internal var _capacity: Int { - _storage.read { $0.capacity } + _storage.value.capacity } @usableFromInline @@ -221,7 +219,7 @@ extension Deque { let c = _growCapacity(to: minimumCapacity, linearly: linearGrowth) if isUnique { - self._storage.update { $0.resize(to: c) } + self._storage.value.resize(to: c) } else { self = self._makeUniqueCopy(capacity: c) } diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift index 58eb43039..ab361388d 100644 --- a/Sources/DequeModule/DynamicDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -11,7 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities -import Span +import Future #endif @frozen @@ -33,16 +33,15 @@ public struct DynamicDeque: ~Copyable { extension DynamicDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = RigidDeque.BorrowingIterator - public typealias Index = Int - - public func startBorrowingIteration() -> BorrowingIterator { - _storage.startBorrowingIteration() + @available(SwiftCompatibilitySpan 5.0, *) + @lifetime(borrow self) + public func span(following index: inout Int, maximumCount: Int) -> Span { + _storage.span(following: &index, maximumCount: maximumCount) } +} - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - _storage.startBorrowingIteration(from: start) - } +extension DynamicDeque where Element: ~Copyable { + public typealias Index = Int @inlinable public var isEmpty: Bool { _storage.isEmpty } @@ -56,20 +55,9 @@ extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { @inlinable public var endIndex: Int { _storage.endIndex } - @inlinable - public subscript(position: Int) -> Element { - @inline(__always) - _read { - yield _storage[position] - } - @inline(__always) - _modify { - yield &_storage[position] - } - } - - public func index(at position: borrowing BorrowingIterator) -> Int { - _storage.index(at: position) + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Future.Borrow { + _storage.borrowElement(at: index) } } diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index cdf3f3f43..83a0b5d5f 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -11,7 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities -import Span +import Future #endif @frozen @@ -61,64 +61,43 @@ extension RigidDeque where Element: ~Copyable { } } -public struct _DequeBorrowingIterator: BorrowingIteratorProtocol, ~Escapable { - @usableFromInline - internal typealias _UnsafeHandle = _UnsafeDequeHandle - - @usableFromInline - internal let _segments: _UnsafeDequeSegments - - @usableFromInline - internal var _offset: Int - - @inlinable - internal init( - _unsafeSegments segments: _UnsafeDequeSegments, - startOffset: Int, - owner: borrowing T - ) { - self._segments = segments - self._offset = startOffset - } - +@available(SwiftCompatibilitySpan 5.0, *) +extension RigidDeque where Element: ~Copyable { @inlinable - internal init(_for handle: borrowing _UnsafeHandle, startOffset: Int) { - self.init(_unsafeSegments: handle.segments(), startOffset: startOffset, owner: handle) + @lifetime(borrow self) + internal func _span(over slots: Range<_DequeSlot>) -> Span { + let span = Span(_unsafeElements: _handle.buffer(for: slots)) + return _overrideLifetime(span, borrowing: self) } @inlinable - public mutating func nextChunk( - maximumCount: Int - ) -> dependsOn(scoped self) Span { - precondition(maximumCount > 0) - if _offset < _segments.first.count { - let d = Swift.min(maximumCount, _segments.first.count - _offset) - let slice = _segments.first.extracting(_offset ..< _offset + d) - _offset += d - return Span(_unsafeElements: slice) - } - guard let second = _segments.second else { - return Span(_unsafeElements: UnsafeBufferPointer._empty) - } - let o = _offset - _segments.first.count - let d = Swift.min(maximumCount, second.count - o) - let slice = second.extracting(o ..< o + d) - _offset += d - return Span(_unsafeElements: slice) + @lifetime(&self) + internal mutating func _mutableSpan(over slots: Range<_DequeSlot>) -> MutableSpan { + let span = MutableSpan(_unsafeElements: _handle.mutableBuffer(for: slots)) + return _overrideLifetime(span, mutating: &self) } } -extension RigidDeque: RandomAccessContainer where Element: ~Copyable { - public typealias BorrowingIterator = _DequeBorrowingIterator - - public func startBorrowingIteration() -> BorrowingIterator { - _handle.startBorrowingIteration() +@available(SwiftCompatibilitySpan 5.0, *) +extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + public func span(following index: inout Int, maximumCount: Int) -> Span { + let slots = _handle.slotRange(following: &index, maximumCount: maximumCount) + return _span(over: slots) } - public func startBorrowingIteration(from start: Int) -> BorrowingIterator { - _handle.startBorrowingIteration(from: start) + @inlinable + @lifetime(&self) + public mutating func mutableSpan( + following index: inout Int, maximumCount: Int + ) -> MutableSpan { + let slots = _handle.slotRange(following: &index, maximumCount: maximumCount) + return _mutableSpan(over: slots) } +} +extension RigidDeque where Element: ~Copyable { public typealias Index = Int @inlinable @@ -133,6 +112,20 @@ extension RigidDeque: RandomAccessContainer where Element: ~Copyable { @inlinable public var endIndex: Int { _handle.count } + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < count, "Index out of bounds") + let slot = _handle.slot(forOffset: index) + return Borrow(unsafeAddress: _handle.ptr(at: slot), borrowing: self) + } + + @lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + precondition(index >= 0 && index < count, "Index out of bounds") + let slot = _handle.slot(forOffset: index) + return Inout(unsafeAddress: _handle.mutablePtr(at: slot), mutating: &self) + } + @inlinable public subscript(position: Int) -> Element { @inline(__always) @@ -144,11 +137,6 @@ extension RigidDeque: RandomAccessContainer where Element: ~Copyable { yield &_handle[offset: position] } } - - public func index(at position: borrowing BorrowingIterator) -> Int { - precondition(_handle.segments().isIdentical(to: position._segments)) - return position._offset - } } extension RigidDeque where Element: ~Copyable { diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index 25d5b1bfe..b6e2d2b11 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -386,14 +386,25 @@ extension _UnsafeDequeHandle where Element: ~Copyable { extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func startBorrowingIteration() -> _DequeBorrowingIterator { - .init(_for: self, startOffset: 0) - } + internal func slotRange(following offset: inout Int, maximumCount: Int) -> Range { + precondition(offset >= 0 && offset <= count, "Invalid index") + precondition(maximumCount > 0, "maximumCount must be positive") + guard _buffer.baseAddress != nil else { + return Range(uncheckedBounds: (Slot.zero, Slot.zero)) + } + let wrapOffset = Swift.min(capacity - startSlot.position, count) - @inlinable - internal func startBorrowingIteration(from start: Int) -> _DequeBorrowingIterator { - precondition(start >= 0 && start <= count) - return .init(_for: self, startOffset: start) + if offset < wrapOffset { + let endOffset = offset + Swift.min(wrapOffset - offset, maximumCount) + defer { offset += endOffset - offset } + return Range( + uncheckedBounds: (startSlot.advanced(by: offset), startSlot.advanced(by: endOffset))) + } + let endOffset = offset + Swift.min(count - offset, maximumCount) + let lowerSlot = Slot.zero.advanced(by: offset - wrapOffset) + let upperSlot = lowerSlot.advanced(by: endOffset - wrapOffset) + defer { offset += endOffset - offset } + return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) } } diff --git a/Sources/DequeModule/_UnsafeDequeSegments.swift b/Sources/DequeModule/_UnsafeDequeSegments.swift index 49d5592f9..4e5b8eb60 100644 --- a/Sources/DequeModule/_UnsafeDequeSegments.swift +++ b/Sources/DequeModule/_UnsafeDequeSegments.swift @@ -11,7 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities -import Span +import Future #endif @frozen @@ -58,10 +58,10 @@ internal struct _UnsafeDequeSegments { @inlinable internal func isIdentical(to other: Self) -> Bool { - guard self.first === other.first else { return false } + guard self.first.isIdentical(to: other.first) else { return false } switch (self.second, other.second) { case (nil, nil): return true - case let (a?, b?): return a === b + case let (a?, b?): return a.isIdentical(to: b) default: return false } } From d7768235ef5f705d05f91f1d00b1ca8047499cfd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 17 Apr 2025 03:02:33 -0700 Subject: [PATCH 184/195] More adjustments --- Sources/DequeModule/Deque+Container.swift | 4 +- Sources/DequeModule/DynamicDeque.swift | 4 +- Sources/DequeModule/RigidDeque.swift | 6 +- .../Arrays/DynamicArray.swift | 10 +- Sources/Future/Arrays/NewArray.swift | 244 ++++++++++++++++++ .../{Containers => }/Arrays/RigidArray.swift | 39 ++- .../Future/Containers/Arrays/NewArray.swift | 131 ---------- .../Containers/BidirectionalContainer.swift | 9 +- Sources/Future/Containers/Container.swift | 27 +- .../Containers/ContiguousContainer.swift | 38 +++ .../Future/Containers/MutableContainer.swift | 6 +- .../Containers/RandomAccessContainer.swift | 5 +- Sources/Future/ContiguousStorage.swift | 106 -------- Sources/Future/Span+Iterator.swift | 62 ----- Sources/Future/Span/Span+Container.swift | 2 +- Sources/Future/{ => Tools}/Borrow.swift | 0 Sources/Future/{ => Tools}/Box.swift | 0 Sources/Future/{ => Tools}/Inout.swift | 0 .../Future/{Containers => Tools}/Shared.swift | 57 ++-- Tests/FutureTests/DynamicArrayTests.swift | 8 +- .../SpanTests/OutputSpanTests.swift | 2 + .../StdlibOutputSpanExtensionTests.swift | 2 + 22 files changed, 383 insertions(+), 379 deletions(-) rename Sources/Future/{Containers => }/Arrays/DynamicArray.swift (93%) create mode 100644 Sources/Future/Arrays/NewArray.swift rename Sources/Future/{Containers => }/Arrays/RigidArray.swift (88%) delete mode 100644 Sources/Future/Containers/Arrays/NewArray.swift create mode 100644 Sources/Future/Containers/ContiguousContainer.swift delete mode 100644 Sources/Future/ContiguousStorage.swift delete mode 100644 Sources/Future/Span+Iterator.swift rename Sources/Future/{ => Tools}/Borrow.swift (100%) rename Sources/Future/{ => Tools}/Box.swift (100%) rename Sources/Future/{ => Tools}/Inout.swift (100%) rename Sources/Future/{Containers => Tools}/Shared.swift (79%) diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index e28e265b8..a1baa3a9d 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -21,7 +21,7 @@ extension Deque: RandomAccessContainer { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(borrow self) - public func span(following index: inout Int, maximumCount: Int) -> Span { - _storage.value.span(following: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { + _storage.value.nextSpan(after: &index, maximumCount: maximumCount) } } diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift index ab361388d..084e834fc 100644 --- a/Sources/DequeModule/DynamicDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -35,8 +35,8 @@ extension DynamicDeque: @unchecked Sendable where Element: Sendable & ~Copyable extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(borrow self) - public func span(following index: inout Int, maximumCount: Int) -> Span { - _storage.span(following: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { + _storage.nextSpan(after: &index, maximumCount: maximumCount) } } diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index 83a0b5d5f..9164d0ba8 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -82,15 +82,15 @@ extension RigidDeque where Element: ~Copyable { extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) - public func span(following index: inout Int, maximumCount: Int) -> Span { + public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { let slots = _handle.slotRange(following: &index, maximumCount: maximumCount) return _span(over: slots) } @inlinable @lifetime(&self) - public mutating func mutableSpan( - following index: inout Int, maximumCount: Int + public mutating func nextMutableSpan( + after index: inout Int, maximumCount: Int ) -> MutableSpan { let slots = _handle.slotRange(following: &index, maximumCount: maximumCount) return _mutableSpan(over: slots) diff --git a/Sources/Future/Containers/Arrays/DynamicArray.swift b/Sources/Future/Arrays/DynamicArray.swift similarity index 93% rename from Sources/Future/Containers/Arrays/DynamicArray.swift rename to Sources/Future/Arrays/DynamicArray.swift index 01bcf0fa8..ef8900aac 100644 --- a/Sources/Future/Containers/Arrays/DynamicArray.swift +++ b/Sources/Future/Arrays/DynamicArray.swift @@ -63,8 +63,8 @@ extension DynamicArray where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) extension DynamicArray: RandomAccessContainer where Element: ~Copyable { @lifetime(borrow self) - public func span(following index: inout Int, maximumCount: Int) -> Span { - _storage.span(following: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { + _storage.nextSpan(after: &index, maximumCount: maximumCount) } } @@ -101,10 +101,10 @@ extension DynamicArray: MutableContainer where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(&self) - public mutating func mutableSpan( - following index: inout Int, maximumCount: Int + public mutating func nextMutableSpan( + after index: inout Int, maximumCount: Int ) -> MutableSpan { - _storage.mutableSpan(following: &index, maximumCount: maximumCount) + _storage.nextMutableSpan(after: &index, maximumCount: maximumCount) } } diff --git a/Sources/Future/Arrays/NewArray.swift b/Sources/Future/Arrays/NewArray.swift new file mode 100644 index 000000000..2e762aaab --- /dev/null +++ b/Sources/Future/Arrays/NewArray.swift @@ -0,0 +1,244 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 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 +// +//===----------------------------------------------------------------------===// + +/// What `Array` might look like if we defined it today. +@frozen +public struct NewArray { + @usableFromInline + internal var _storage: Shared> + + @inlinable + public init(minimumCapacity: Int) { + self._storage = Shared(RigidArray(capacity: minimumCapacity)) + } +} + +extension NewArray { + @available(SwiftStdlib 6.2, *) + public var span: Span { + @lifetime(borrow self) + get { + _storage.value.span + } + } +} + +extension NewArray { + @inlinable + public var capacity: Int { _storage.value.capacity } +} + +extension NewArray: RandomAccessCollection, MutableCollection { + public typealias Index = Int + public typealias Indices = Range + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _storage.value.count } + + public subscript(position: Int) -> Element { + @inlinable + unsafeAddress { + unsafe _storage.value.borrowElement(at: position)._pointer + } + @inlinable + unsafeMutableAddress { + _ensureUnique() + return unsafe _storage.value.mutateElement(at: position)._pointer + } + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension NewArray: RandomAccessContainer, MutableContainer { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.value.borrowElement(at: index) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { + _storage.value.nextSpan(after: &index, maximumCount: maximumCount) + } + + @lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + _ensureUnique() + return _storage.value.mutateElement(at: index) + } + + @lifetime(&self) + public mutating func nextMutableSpan( + after index: inout Int, maximumCount: Int + ) -> MutableSpan { + _ensureUnique() + return _storage.value.nextMutableSpan(after: &index, maximumCount: maximumCount) + } +} + +extension NewArray { + @inlinable + public mutating func reserveCapacity(_ n: Int) { + if _storage.isUnique() { + _storage.value.reserveCapacity(n) + } else { + _storage.replace { $0._copy(capacity: Swift.max($0.capacity, n)) } + } + } + + @inlinable + internal static func _grow(_ capacity: Int) -> Int { + 2 * capacity + } + + @inlinable + public mutating func _ensureFreeCapacity(_ minimumCapacity: Int) { + guard _storage.value.freeCapacity < minimumCapacity else { return } + let newCapacity = Swift.max(count + minimumCapacity, Self._grow(capacity)) + _storage.value.reserveCapacity(newCapacity) + } + + @inlinable + internal mutating func _ensureUnique() { + _storage.ensureUnique { $0._copy() } + } + + @inlinable + internal mutating func _ensureUnique( + minimumCapacity: Int, + linear: Bool + ) { + if minimumCapacity <= self.capacity { + _ensureUnique() + return + } + let c = Swift.max(minimumCapacity, Self._grow(capacity)) + _storage.edit( + shared: { $0._copy(capacity: c) }, + unique: { $0 = $0._move(capacity: c) } + ) + } + + @inlinable + public mutating func _edit( + ensuringMinimumCapacity minimumCapacity: Int, + shared cloner: (borrowing RigidArray, Int) -> RigidArray, + resize resizer: (inout RigidArray, Int) -> RigidArray, + direct updater: (inout RigidArray) -> Void + ) { + var c = capacity + let isUnique = _storage.isUnique() + let hasEnoughCapacity = minimumCapacity <= c + if !hasEnoughCapacity { + c = Swift.max(minimumCapacity, Self._grow(c)) + } + switch (isUnique, hasEnoughCapacity) { + case (true, true): + updater(&_storage.value) + case (true, false): + _modify(&_storage.value, by: { resizer(&$0, c) }) + case (false, _): + _storage.replace(using: { cloner($0, c) }) + } + } +} + +@inlinable +@_transparent +internal func _modify( + _ value: inout T, + by body: (inout T) -> T +) { + value = body(&value) +} + +extension NewArray { + @inlinable + public mutating func append(_ newElement: Element) { + self._ensureFreeCapacity(1) + self._storage.value.append(newElement) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + var item: Element? = item + _edit( + ensuringMinimumCapacity: count + 1, + shared: { src, capacity in + var new = RigidArray(capacity: capacity) + new.append(contentsOf: src._span(in: 0 ..< index)) + new.append(item.take()!) + new.append(contentsOf: src._span(in: index ..< src.count)) + return new + }, + resize: { src, capacity in + var dst = RigidArray(capacity: capacity) + unsafe src.withUnsafeMutableBufferPointer { srcBuffer, srcCount in + unsafe dst.withUnsafeMutableBufferPointer { dstBuffer, dstCount in + assert(dstCount == 0) + assert(dstBuffer.count >= srcCount + 1) + _ = unsafe dstBuffer.moveInitialize(fromContentsOf: srcBuffer.extracting(.. Element { + precondition(count > 0, "Cannot remove last element from empty array") + if _storage.isUnique() { + return _storage.value.removeLast() + } else { + let old = self[count - 1] + _storage.replace { + var new = RigidArray(capacity: $0.capacity) + new.append(contentsOf: $0.span._extracting(droppingLast: 1)) + return new + } + return old + } + } +} + +#if false // FIXME: Implement +extension NewArray: RangeReplaceableCollection { + public init() { + // FIXME: Figure out if we can implement empty singletons in this setup. + self._storage = Shared(RigidArray(capacity: 0)) + } + + mutating public func replaceSubrange( + _ subrange: Range, + with newElements: some Collection + ) { + let delta = newElements.count - subrange.count + _ensureUnique(minimumCapacity: capacity + delta) + } +} +#endif diff --git a/Sources/Future/Containers/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift similarity index 88% rename from Sources/Future/Containers/Arrays/RigidArray.swift rename to Sources/Future/Arrays/RigidArray.swift index acc4732f2..f84a3adb5 100644 --- a/Sources/Future/Containers/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -101,8 +101,7 @@ extension RigidArray where Element: ~Copyable { @inlinable @lifetime(borrow self) internal func _span(in range: Range) -> Span { - let result = unsafe Span(_unsafeElements: _items.extracting(range)) - return unsafe _overrideLifetime(result, borrowing: self) + span._extracting(range) } @available(SwiftCompatibilitySpan 5.0, *) @@ -130,7 +129,7 @@ extension RigidArray where Element: ~Copyable { extension RigidArray: RandomAccessContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) - public func span(following index: inout Int, maximumCount: Int) -> Span { + public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { _span(in: _subrange(following: &index, maximumCount: maximumCount)) } } @@ -166,8 +165,8 @@ extension RigidArray where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) extension RigidArray: MutableContainer where Element: ~Copyable { @lifetime(&self) - public mutating func mutableSpan( - following index: inout Int, maximumCount: Int + public mutating func nextMutableSpan( + after index: inout Int, maximumCount: Int ) -> MutableSpan { _mutableSpan(in: _subrange(following: &index, maximumCount: maximumCount)) } @@ -185,6 +184,18 @@ extension RigidArray where Element: ~Copyable { } } +//MARK: Unsafe access + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func withUnsafeMutableBufferPointer( + _ body: (UnsafeMutableBufferPointer, inout Int) throws(E) -> R + ) throws(E) -> R { + defer { precondition(_count >= 0 && _count <= capacity) } + return unsafe try body(_items, &_count) + } +} + //MARK: - Resizing extension RigidArray where Element: ~Copyable { @@ -209,6 +220,15 @@ extension RigidArray where Element: ~Copyable { //MARK: Range replacement operations extension RigidArray where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty) + let old = unsafe _storage.moveElement(from: _count - 1) + _count -= 1 + return old + } + @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { @@ -255,6 +275,15 @@ extension RigidArray { append(item) } } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + public mutating func append(contentsOf items: Span) { + precondition(items.count <= freeCapacity) + unsafe items.withUnsafeBufferPointer { + _ = unsafe _freeSpace.initialize(fromContentsOf: $0) + } + } } //MARK: - Copying and moving helpers diff --git a/Sources/Future/Containers/Arrays/NewArray.swift b/Sources/Future/Containers/Arrays/NewArray.swift deleted file mode 100644 index e9fae9528..000000000 --- a/Sources/Future/Containers/Arrays/NewArray.swift +++ /dev/null @@ -1,131 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -/// What `Array` might look like if we defined it today. -@frozen -public struct NewArray { - @usableFromInline - internal var _storage: Shared> - - @inlinable - public init(minimumCapacity: Int) { - self._storage = Shared(RigidArray(capacity: minimumCapacity)) - } -} - -extension NewArray { - @available(SwiftStdlib 6.2, *) - public var span: Span { - @lifetime(borrow self) - get { - _storage.value.span - } - } -} - -extension NewArray { - @inlinable - public var capacity: Int { _storage.value.capacity } - - @inlinable - internal mutating func _ensureUnique() { - _storage.ensureUnique { $0._copy() } - } - - @inlinable - internal mutating func _ensureUnique( - minimumCapacity: Int, - linear: Bool = false - ) { - // FIXME: Handle resizing - if !_storage.isUnique() { - let c = Swift.max(count, minimumCapacity) - _storage = Shared(_storage.value._copy(capacity: c)) - } else if minimumCapacity > self.capacity { - _storage = Shared(_storage.value._move(capacity: minimumCapacity)) - } - } - - @inlinable - internal mutating func _update( - minimumCapacity: Int, - _ body: (inout RigidArray) throws(E) -> Result - ) throws(E) -> Result { - _ensureUnique(minimumCapacity: minimumCapacity) - return try body(&_storage.value) - } -} - -extension NewArray: RandomAccessCollection, MutableCollection { - public typealias Index = Int - public typealias Indices = Range - - @inlinable - public var startIndex: Int { 0 } - - @inlinable - public var endIndex: Int { _storage.value.count } - - public subscript(position: Int) -> Element { - @inlinable - unsafeAddress { - unsafe _storage.value.borrowElement(at: position)._pointer - } - @inlinable - unsafeMutableAddress { - _ensureUnique() - return unsafe _storage.value.mutateElement(at: position)._pointer - } - } -} - -@available(SwiftCompatibilitySpan 5.0, *) -extension NewArray: RandomAccessContainer, MutableContainer { - @lifetime(borrow self) - public func borrowElement(at index: Int) -> Borrow { - _storage.value.borrowElement(at: index) - } - - @lifetime(borrow self) - public func span(following index: inout Int, maximumCount: Int) -> Span { - _storage.value.span(following: &index, maximumCount: maximumCount) - } - - @lifetime(&self) - public mutating func mutateElement(at index: Int) -> Inout { - _storage.value.mutateElement(at: index) - } - - @lifetime(&self) - public mutating func mutableSpan( - following index: inout Int, maximumCount: Int - ) -> MutableSpan { - _storage.value.mutableSpan(following: &index, maximumCount: maximumCount) - } -} - - -#if false // FIXME: Implement -extension NewArray: RangeReplaceableCollection { - public init() { - // FIXME: Figure out if we can implement empty singletons in this setup. - self._storage = Shared(RigidArray(capacity: 0)) - } - - mutating public func replaceSubrange( - _ subrange: Range, - with newElements: some Collection - ) { - let delta = newElements.count - subrange.count - _ensureUnique(minimumCapacity: capacity + delta) - } -} -#endif diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift index 84faaf3d1..8b7db81fe 100644 --- a/Sources/Future/Containers/BidirectionalContainer.swift +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -10,14 +10,7 @@ //===----------------------------------------------------------------------===// @available(SwiftCompatibilitySpan 5.0, *) -public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { - override associatedtype Element: ~Copyable - +public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { func index(before i: Index) -> Index func formIndex(before i: inout Index) - - @_nonoverride func index(_ i: Index, offsetBy distance: Int) -> Index - @_nonoverride func formIndex( - _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index - ) } diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 02ba067a7..37bcb9cf8 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -10,9 +10,8 @@ //===----------------------------------------------------------------------===// @available(SwiftCompatibilitySpan 5.0, *) -public protocol Container: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable - +public protocol Container: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable/* & ~Escapable*/ associatedtype Index: Comparable var isEmpty: Bool { get } @@ -21,7 +20,17 @@ public protocol Container: ~Copyable, ~Escapable { var startIndex: Index { get } var endIndex: Index { get } - #if compiler(>=9999) // We want this but we can't do it yet + func index(after index: Index) -> Index + func formIndex(after i: inout Index) + func distance(from start: Index, to end: Index) -> Int + func index(_ index: Index, offsetBy n: Int) -> Index + func formIndex( + _ i: inout Index, + offsetBy distance: inout Int, + limitedBy limit: Index + ) + + #if compiler(>=9999) // FIXME: We can't do this yet subscript(index: Index) -> Element { borrow } #else @lifetime(copy self) @@ -29,15 +38,7 @@ public protocol Container: ~Copyable, ~Escapable { #endif @lifetime(copy self) - func span(following index: inout Index, maximumCount: Int) -> Span - - func index(after index: Index) -> Index - func formIndex(after i: inout Index) - func distance(from start: Index, to end: Index) -> Int - func index(_ index: Index, offsetBy n: Int) -> Index - func formIndex( - _ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index - ) + func nextSpan(after index: inout Index, maximumCount: Int) -> Span } @available(SwiftCompatibilitySpan 5.0, *) diff --git a/Sources/Future/Containers/ContiguousContainer.swift b/Sources/Future/Containers/ContiguousContainer.swift new file mode 100644 index 000000000..3e9a92ef6 --- /dev/null +++ b/Sources/Future/Containers/ContiguousContainer.swift @@ -0,0 +1,38 @@ +//===---------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 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(SwiftCompatibilitySpan 5.0, *) +public protocol ContiguousContainer: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable/* & ~Escapable*/ + + var span: Span { @lifetime(copy self) get } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Span: ContiguousContainer where Element: ~Copyable { + public var span: Self { + @lifetime(copy self) + get { self } + } +} + +@available(SwiftStdlib 6.2, *) +extension Array: ContiguousContainer {} + +@available(SwiftStdlib 6.2, *) +extension ContiguousArray: ContiguousContainer {} + +@available(SwiftStdlib 6.2, *) +extension CollectionOfOne: ContiguousContainer {} + +@available(SwiftStdlib 6.2, *) +extension String.UTF8View: ContiguousContainer {} diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index 67c8f4f0a..6d69d4534 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -10,8 +10,8 @@ //===----------------------------------------------------------------------===// @available(SwiftCompatibilitySpan 5.0, *) -public protocol MutableContainer: Container, ~Copyable, ~Escapable { -#if compiler(>=9999) // We want this but we can't do it yet +public protocol MutableContainer: Container, ~Copyable, ~Escapable { +#if compiler(>=9999) // FIXME: We can't do this yet subscript(index: Index) -> Element { borrow mutate } #else @lifetime(&self) @@ -19,7 +19,7 @@ public protocol MutableContainer: Container, ~Copyable, ~Escapable { #endif @lifetime(&self) - mutating func mutableSpan(following index: inout Index, maximumCount: Int) -> MutableSpan + mutating func nextMutableSpan(after index: inout Index, maximumCount: Int) -> MutableSpan } @available(SwiftCompatibilitySpan 5.0, *) diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift index a3b61ea7b..742c76c44 100644 --- a/Sources/Future/Containers/RandomAccessContainer.swift +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -10,9 +10,8 @@ //===----------------------------------------------------------------------===// @available(SwiftCompatibilitySpan 5.0, *) -public protocol RandomAccessContainer: BidirectionalContainer, ~Copyable, ~Escapable { - override associatedtype Element: ~Copyable -} +public protocol RandomAccessContainer +: BidirectionalContainer, ~Copyable, ~Escapable {} @available(SwiftCompatibilitySpan 5.0, *) extension RandomAccessContainer diff --git a/Sources/Future/ContiguousStorage.swift b/Sources/Future/ContiguousStorage.swift deleted file mode 100644 index 30043f93c..000000000 --- a/Sources/Future/ContiguousStorage.swift +++ /dev/null @@ -1,106 +0,0 @@ -//===--- ContiguousStorage.swift ------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -#if false // FIXME: Revive -public protocol ContiguousStorage: ~Copyable, ~Escapable { - associatedtype Element/*: ~Copyable & ~Escapable*/ - - var storage: Span { borrowing get } -} - -extension Span: ContiguousStorage /*where Element: ~Copyable & ~Escapable*/ { - public var storage: Self { self } -} - -extension Array: ContiguousStorage { - public var storage: Span { - _read { - if let a = _baseAddressIfContiguous { - yield Span(_unsafeStart: a, count: count) - } - else { - let a = ContiguousArray(copy self) - #if true - let s = Span( - _unsafeStart: a._baseAddressIfContiguous!, count: a.count - ) - #else - let s = a.storage - #endif - yield s - } - } - } -} - -extension ContiguousArray: ContiguousStorage { - public var storage: Span { - borrowing get { - Span( - _unsafeStart: _baseAddressIfContiguous!, count: count - ) - } - } -} - -extension CollectionOfOne: ContiguousStorage { - public var storage: Span { - _read { -/* ideally: (with strawman syntax) - @addressable let value = self._element - yield Span( - unsafePointer: Builtin.addressable(value), count: 1 - ) -*/ - - let a = ContiguousArray(self) - yield Span( - _unsafeStart: a._baseAddressIfContiguous!, count: 1 - ) - } - } -} - -extension String.UTF8View: ContiguousStorage { - public var storage: Span { - _read { - if count < 16 { // Wrong way to know whether the String is smol -// if _guts.isSmall { -// let /*@addressable*/ rawStorage = _guts.asSmall._storage -// let span = RawSpan( -// unsafeRawPointer: UnsafeRawPointer(Builtin.adressable(rawStorage)), -// count: MemoryLayout<_SmallString.RawBitPattern>.size, -// owner: self -// ) -// yield span.view(as: UTF8.CodeUnit.self) - - let a = ContiguousArray(self) -// yield a.storage - yield Span( - _unsafeStart: a._baseAddressIfContiguous!, count: 1 - ) - } - else if let buffer = withContiguousStorageIfAvailable({ $0 }) { - // this is totally wrong, but there is a way with stdlib-internal API - yield Span(_unsafeElements: buffer) - } - else { // copy non-fast code units if we don't have eager bridging - let a = ContiguousArray(self) -// yield a.storage - yield Span( - _unsafeStart: a._baseAddressIfContiguous!, count: 1 - ) - } - } - } -} -#endif diff --git a/Sources/Future/Span+Iterator.swift b/Sources/Future/Span+Iterator.swift deleted file mode 100644 index 434e6985e..000000000 --- a/Sources/Future/Span+Iterator.swift +++ /dev/null @@ -1,62 +0,0 @@ -//===--- SpanIterator.swift -----------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 - 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 -// -//===----------------------------------------------------------------------===// - -#if false // FIXME: Revive -extension Span where Element: ~Copyable { - @frozen - public struct Iterator: Copyable, ~Escapable { - var curPointer: UnsafeRawPointer? - let endPointer: UnsafeRawPointer? - - @lifetime(span) - public init(from span: consuming Span) { - curPointer = span._pointer - endPointer = span._pointer?.advanced( - by: span.count*MemoryLayout.stride - ) - } - } -} - -extension Span.Iterator where Element: Copyable { - - // This is the `IteratorProtocol` requirement, except that - // Span.Iterator does not conform to `Escapable` - public mutating func next() -> Element? { - guard curPointer != endPointer, - let cur = curPointer, cur < endPointer.unsafelyUnwrapped - else { return nil } - defer { - curPointer = cur.advanced(by: MemoryLayout.stride) - } - if _isPOD(Element.self) { - return cur.loadUnaligned(as: Element.self) - } - return cur.load(as: Element.self) - } -} - -extension Span.Iterator where Element: BitwiseCopyable { - - // This is the `IteratorProtocol` requirement, except that - // Span.Iterator does not conform to `Escapable` - public mutating func next() -> Element? { - guard curPointer != endPointer, - let cur = curPointer, cur < endPointer.unsafelyUnwrapped - else { return nil } - defer { - curPointer = cur.advanced(by: MemoryLayout.stride) - } - return cur.loadUnaligned(as: Element.self) - } -} -#endif diff --git a/Sources/Future/Span/Span+Container.swift b/Sources/Future/Span/Span+Container.swift index ef23c7096..fbe56dba1 100644 --- a/Sources/Future/Span/Span+Container.swift +++ b/Sources/Future/Span/Span+Container.swift @@ -25,7 +25,7 @@ extension Span: RandomAccessContainer where Element: ~Copyable { } @lifetime(copy self) - public func span(following index: inout Index, maximumCount: Int) -> Span { + public func nextSpan(after index: inout Index, maximumCount: Int) -> Span { precondition(index >= 0 && index <= count, "Invalid index") let end = index + Swift.min(count - index, maximumCount) defer { index = end } diff --git a/Sources/Future/Borrow.swift b/Sources/Future/Tools/Borrow.swift similarity index 100% rename from Sources/Future/Borrow.swift rename to Sources/Future/Tools/Borrow.swift diff --git a/Sources/Future/Box.swift b/Sources/Future/Tools/Box.swift similarity index 100% rename from Sources/Future/Box.swift rename to Sources/Future/Tools/Box.swift diff --git a/Sources/Future/Inout.swift b/Sources/Future/Tools/Inout.swift similarity index 100% rename from Sources/Future/Inout.swift rename to Sources/Future/Tools/Inout.swift diff --git a/Sources/Future/Containers/Shared.swift b/Sources/Future/Tools/Shared.swift similarity index 79% rename from Sources/Future/Containers/Shared.swift rename to Sources/Future/Tools/Shared.swift index f5de3d1bb..1bb47b20b 100644 --- a/Sources/Future/Containers/Shared.swift +++ b/Sources/Future/Tools/Shared.swift @@ -32,25 +32,6 @@ public struct Shared { extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} -#if false // FIXME: Silent error on class definition nested in noncopyable struct -@unsafe -@usableFromInline -internal final class _SharedBox { - @exclusivity(unchecked) - @usableFromInline - internal var storage: Storage - - @inlinable - internal init(_ storage: consuming Storage) { - unsafe self.storage = storage - } -} - -extension Shared where Storage: ~Copyable { - @usableFromInline - internal typealias _Box = _SharedBox -} -#else extension Shared where Storage: ~Copyable { @unsafe @usableFromInline @@ -65,7 +46,6 @@ extension Shared where Storage: ~Copyable { } } } -#endif extension Shared where Storage: ~Copyable { @inlinable @@ -74,12 +54,34 @@ extension Shared where Storage: ~Copyable { unsafe isKnownUniquelyReferenced(&_box) } + /// - Returns true if this instance was already uniquely referenced. + @discardableResult @inlinable public mutating func ensureUnique( cloner: (borrowing Storage) -> Storage + ) -> Bool { + if isUnique() { return true } + replace(using: cloner) + return false + } + + @inlinable + public mutating func replace( + using body: (borrowing Storage) -> Storage + ) { + unsafe _box = _Box(body(_box.storage)) + } + + @inlinable + public mutating func edit( + shared cloner: (borrowing Storage) -> Storage, + unique updater: (inout Storage) -> Void ) { - if isUnique() { return } - unsafe _box = _Box(cloner(_box.storage)) + if isUnique() { + unsafe updater(&_box.storage) + } else { + unsafe _box = _Box(cloner(_box.storage)) + } } } @@ -143,16 +145,9 @@ extension Shared where Storage: ~Copyable { } extension Shared where Storage: ~Copyable { + @available(SwiftStdlib 6.0, *) // for === @inlinable public func isIdentical(to other: Self) -> Bool { - if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { - return unsafe self._box === other._box - } else { - // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions - // that are only supported in the Swift 6+ runtime. - let a = unsafe Builtin.bridgeToRawPointer(self._box) - let b = unsafe Builtin.bridgeToRawPointer(other._box) - return Bool(Builtin.cmp_eq_RawPointer(a, b)) - } + unsafe self._box === other._box } } diff --git a/Tests/FutureTests/DynamicArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift index 18d25bd6f..980545dbc 100644 --- a/Tests/FutureTests/DynamicArrayTests.swift +++ b/Tests/FutureTests/DynamicArrayTests.swift @@ -146,14 +146,14 @@ class DynamicArrayTests: CollectionTestCase { var index = 0 do { - let span = array.span(following: &index, maximumCount: Int.max) + let span = array.nextSpan(after: &index, maximumCount: Int.max) expectEqual(span.count, c) for i in 0 ..< span.count { expectEqual(span[i].value, 100 + i) } } do { - let span2 = array.span(following: &index, maximumCount: Int.max) + let span2 = array.nextSpan(after: &index, maximumCount: Int.max) expectEqual(span2.count, 0) } } @@ -168,7 +168,7 @@ class DynamicArrayTests: CollectionTestCase { var i = 0 while true { expectEqual(index, i) - let span = array.span(following: &index, maximumCount: stride) + let span = array.nextSpan(after: &index, maximumCount: stride) expectEqual(index, i + span.count) if span.isEmpty { break } expectEqual(span.count, i + stride <= c ? stride : c % stride) @@ -178,7 +178,7 @@ class DynamicArrayTests: CollectionTestCase { } } expectEqual(i, c) - expectEqual(array.span(following: &index, maximumCount: Int.max).count, 0) + expectEqual(array.nextSpan(after: &index, maximumCount: Int.max).count, 0) expectEqual(index, i) } } diff --git a/Tests/FutureTests/SpanTests/OutputSpanTests.swift b/Tests/FutureTests/SpanTests/OutputSpanTests.swift index 486a239a3..3ac9142ee 100644 --- a/Tests/FutureTests/SpanTests/OutputSpanTests.swift +++ b/Tests/FutureTests/SpanTests/OutputSpanTests.swift @@ -13,6 +13,7 @@ import XCTest import Future +#if false @available(macOS 9999, *) struct Allocation: ~Copyable { let allocation: UnsafeMutablePointer @@ -264,3 +265,4 @@ final class OutputSpanTests: XCTestCase { r.deinitialize() } } +#endif diff --git a/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift b/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift index bc24c3260..ed5707b12 100644 --- a/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift +++ b/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift @@ -14,6 +14,7 @@ import XCTest import Foundation import Future +#if false @available(macOS 9999, *) final class StdlibOutputSpanExtensionTests: XCTestCase { @@ -52,3 +53,4 @@ final class StdlibOutputSpanExtensionTests: XCTestCase { XCTAssert(string.utf8.elementsEqual(c..<(c+16))) } } +#endif From 204531b09050b374339d2db1fc643c1a4f23a8fa Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 18 Apr 2025 15:33:14 -0700 Subject: [PATCH 185/195] Simplify containers even more --- Sources/DequeModule/Deque+Container.swift | 11 ++- Sources/DequeModule/DynamicDeque.swift | 10 ++- Sources/DequeModule/RigidDeque.swift | 15 +++- Sources/DequeModule/_UnsafeDequeHandle.swift | 34 ++++++--- Sources/Future/Arrays/DynamicArray.swift | 15 ++-- Sources/Future/Arrays/NewArray.swift | 17 +++-- Sources/Future/Arrays/RigidArray.swift | 30 +++++--- .../Containers/BidirectionalContainer.swift | 17 +++++ Sources/Future/Containers/Container.swift | 73 ++++++++++++++++++- .../Containers/ContiguousContainer.swift | 2 +- .../Future/Containers/MutableContainer.swift | 20 ++++- Sources/Future/Span/Span+Container.swift | 16 ++-- Sources/Future/Tools/Shared.swift | 11 ++- Tests/FutureTests/DynamicArrayTests.swift | 11 ++- 14 files changed, 228 insertions(+), 54 deletions(-) diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index a1baa3a9d..b523c9730 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -21,7 +21,14 @@ extension Deque: RandomAccessContainer { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(borrow self) - public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { - _storage.value.nextSpan(after: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int) -> Span { + _storage.value.nextSpan(after: &index) } + + @available(SwiftCompatibilitySpan 5.0, *) + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.value.previousSpan(before: &index) + } + } diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift index 084e834fc..b20719ae3 100644 --- a/Sources/DequeModule/DynamicDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -35,8 +35,14 @@ extension DynamicDeque: @unchecked Sendable where Element: Sendable & ~Copyable extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(borrow self) - public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { - _storage.nextSpan(after: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int) -> Span { + _storage.nextSpan(after: &index) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.previousSpan(before: &index) } } diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index 9164d0ba8..1c8a5d8bb 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -82,17 +82,24 @@ extension RigidDeque where Element: ~Copyable { extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) - public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { - let slots = _handle.slotRange(following: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int) -> Span { + let slots = _handle.slotRange(following: &index) + return _span(over: slots) + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + let slots = _handle.slotRange(preceding: &index) return _span(over: slots) } @inlinable @lifetime(&self) public mutating func nextMutableSpan( - after index: inout Int, maximumCount: Int + after index: inout Int ) -> MutableSpan { - let slots = _handle.slotRange(following: &index, maximumCount: maximumCount) + let slots = _handle.slotRange(following: &index) return _mutableSpan(over: slots) } } diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index b6e2d2b11..a36939c17 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -386,24 +386,40 @@ extension _UnsafeDequeHandle where Element: ~Copyable { extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func slotRange(following offset: inout Int, maximumCount: Int) -> Range { - precondition(offset >= 0 && offset <= count, "Invalid index") - precondition(maximumCount > 0, "maximumCount must be positive") + internal func slotRange(following offset: inout Int) -> Range { + precondition(offset >= 0 && offset <= count, "Index out of bounds") guard _buffer.baseAddress != nil else { return Range(uncheckedBounds: (Slot.zero, Slot.zero)) } let wrapOffset = Swift.min(capacity - startSlot.position, count) if offset < wrapOffset { - let endOffset = offset + Swift.min(wrapOffset - offset, maximumCount) - defer { offset += endOffset - offset } + defer { offset += wrapOffset - offset } return Range( - uncheckedBounds: (startSlot.advanced(by: offset), startSlot.advanced(by: endOffset))) + uncheckedBounds: (startSlot.advanced(by: offset), startSlot.advanced(by: wrapOffset))) } - let endOffset = offset + Swift.min(count - offset, maximumCount) let lowerSlot = Slot.zero.advanced(by: offset - wrapOffset) - let upperSlot = lowerSlot.advanced(by: endOffset - wrapOffset) - defer { offset += endOffset - offset } + let upperSlot = lowerSlot.advanced(by: count - wrapOffset) + defer { offset += count - offset } + return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) + } + + @inlinable + internal func slotRange(preceding offset: inout Int) -> Range { + precondition(offset >= 0 && offset <= count, "Index out of bounds") + guard _buffer.baseAddress != nil else { + return Range(uncheckedBounds: (Slot.zero, Slot.zero)) + } + let wrapOffset = Swift.min(capacity - startSlot.position, count) + + if offset <= wrapOffset { + defer { offset = 0 } + return Range( + uncheckedBounds: (startSlot, startSlot.advanced(by: offset))) + } + let lowerSlot = Slot.zero + let upperSlot = lowerSlot.advanced(by: offset - wrapOffset) + defer { offset = wrapOffset } return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) } } diff --git a/Sources/Future/Arrays/DynamicArray.swift b/Sources/Future/Arrays/DynamicArray.swift index ef8900aac..92f607d81 100644 --- a/Sources/Future/Arrays/DynamicArray.swift +++ b/Sources/Future/Arrays/DynamicArray.swift @@ -63,8 +63,13 @@ extension DynamicArray where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) extension DynamicArray: RandomAccessContainer where Element: ~Copyable { @lifetime(borrow self) - public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { - _storage.nextSpan(after: &index, maximumCount: maximumCount) + public func nextSpan(after index: inout Int) -> Span { + _storage.nextSpan(after: &index) + } + + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.previousSpan(before: &index) } } @@ -101,10 +106,8 @@ extension DynamicArray: MutableContainer where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(&self) - public mutating func nextMutableSpan( - after index: inout Int, maximumCount: Int - ) -> MutableSpan { - _storage.nextMutableSpan(after: &index, maximumCount: maximumCount) + public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { + _storage.nextMutableSpan(after: &index) } } diff --git a/Sources/Future/Arrays/NewArray.swift b/Sources/Future/Arrays/NewArray.swift index 2e762aaab..cbce7f2dc 100644 --- a/Sources/Future/Arrays/NewArray.swift +++ b/Sources/Future/Arrays/NewArray.swift @@ -65,10 +65,15 @@ extension NewArray: RandomAccessContainer, MutableContainer { public func borrowElement(at index: Int) -> Borrow { _storage.value.borrowElement(at: index) } - + + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _storage.value.nextSpan(after: &index) + } + @lifetime(borrow self) - public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { - _storage.value.nextSpan(after: &index, maximumCount: maximumCount) + public func previousSpan(before index: inout Int) -> Span { + return _storage.value.previousSpan(before: &index) } @lifetime(&self) @@ -78,11 +83,9 @@ extension NewArray: RandomAccessContainer, MutableContainer { } @lifetime(&self) - public mutating func nextMutableSpan( - after index: inout Int, maximumCount: Int - ) -> MutableSpan { + public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { _ensureUnique() - return _storage.value.nextMutableSpan(after: &index, maximumCount: maximumCount) + return _storage.value.nextMutableSpan(after: &index) } } diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift index f84a3adb5..71e1fc234 100644 --- a/Sources/Future/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -115,11 +115,17 @@ extension RigidArray where Element: ~Copyable { extension RigidArray where Element: ~Copyable { @inlinable - internal func _subrange(following index: inout Int, maximumCount: Int) -> Range { + internal func _contiguousSubrange(following index: inout Int) -> Range { precondition(index >= 0 && index <= _count, "Index out of bounds") - let end = index + Swift.min(_count - index, maximumCount) - defer { index = end } - return unsafe Range(uncheckedBounds: (index, end)) + defer { index = _count } + return unsafe Range(uncheckedBounds: (index, _count)) + } + + @inlinable + internal func _contiguousSubrange(preceding index: inout Int) -> Range { + precondition(index >= 0 && index <= _count, "Index out of bounds") + defer { index = 0 } + return unsafe Range(uncheckedBounds: (0, index)) } } @@ -129,8 +135,14 @@ extension RigidArray where Element: ~Copyable { extension RigidArray: RandomAccessContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) - public func nextSpan(after index: inout Int, maximumCount: Int) -> Span { - _span(in: _subrange(following: &index, maximumCount: maximumCount)) + public func nextSpan(after index: inout Int) -> Span { + _span(in: _contiguousSubrange(following: &index)) + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _span(in: _contiguousSubrange(preceding: &index)) } } @@ -165,10 +177,8 @@ extension RigidArray where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) extension RigidArray: MutableContainer where Element: ~Copyable { @lifetime(&self) - public mutating func nextMutableSpan( - after index: inout Int, maximumCount: Int - ) -> MutableSpan { - _mutableSpan(in: _subrange(following: &index, maximumCount: maximumCount)) + public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { + _mutableSpan(in: _contiguousSubrange(following: &index)) } } diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift index 8b7db81fe..6ac9873dd 100644 --- a/Sources/Future/Containers/BidirectionalContainer.swift +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -13,4 +13,21 @@ public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { func index(before i: Index) -> Index func formIndex(before i: inout Index) + + @lifetime(borrow self) + func previousSpan(before index: inout Index) -> Span +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension BidirectionalContainer where Self: ~Copyable & ~Escapable { + @lifetime(borrow self) + func previousSpan(before index: inout Index, maximumCount: Int) -> Span { + var span = previousSpan(before: &index) + if span.count > maximumCount { + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(index, offsetBy: span.count - maximumCount) + span = span._extracting(last: maximumCount) + } + return span + } } diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 37bcb9cf8..3e913cc97 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -30,19 +30,48 @@ public protocol Container: ~Copyable, ~Escapable { limitedBy limit: Index ) + // FIXME: Do we want these as standard requirements? + func index(alignedDown index: Index) -> Index + func index(alignedUp index: Index) -> Index + #if compiler(>=9999) // FIXME: We can't do this yet subscript(index: Index) -> Element { borrow } #else - @lifetime(copy self) + @lifetime(borrow self) func borrowElement(at index: Index) -> Borrow #endif - @lifetime(copy self) - func nextSpan(after index: inout Index, maximumCount: Int) -> Span + // See if index rounding results need to get returned somewhere + // maximumCount: see if it has any real reason to exist yet + @lifetime(borrow self) + func nextSpan(after index: inout Index) -> Span + + // Try a version where nextSpan takes an index range + + // See if it makes sense to have a ~Escapable ValidatedIndex type, as a sort of non-self-driving iterator substitute } @available(SwiftCompatibilitySpan 5.0, *) extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public func index(alignedDown index: Index) -> Index { index } + + @inlinable + public func index(alignedUp index: Index) -> Index { index } + + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Index, maximumCount: Int) -> Span { + let original = index + var span = nextSpan(after: &index) + if span.count > maximumCount { + span = span._extracting(first: maximumCount) + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(original, offsetBy: maximumCount) + } + return span + } + @inlinable public subscript(index: Index) -> Element { @lifetime(copy self) @@ -52,3 +81,41 @@ extension Container where Self: ~Copyable & ~Escapable { } } + +#if false // DEMO +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { + // This is just to demo the bulk iteration model + func forEachSpan(_ body: (Span) throws(E) -> Void) throws(E) { + var it = self.startIndex + while true { + let span = self.nextSpan(after: &it) + if span.isEmpty { break } + try body(span) + } + } + + // This is just to demo the bulk iteration model + func forEach(_ body: (borrowing Element) throws(E) -> Void) throws(E) { + var it = self.startIndex + while true { + let span = self.nextSpan(after: &it) + if span.isEmpty { break } + var i = 0 + while i < span.count { + unsafe try body(span[unchecked: i]) + i &+= 1 + } + } + } + + borrowing func borrowingMap( + _ transform: (borrowing Element) throws(E) -> U + ) throws(E) -> [U] { + var result: [U] = [] + result.reserveCapacity(count) + try self.forEach { value throws(E) in result.append(try transform(value)) } + return result + } +} +#endif diff --git a/Sources/Future/Containers/ContiguousContainer.swift b/Sources/Future/Containers/ContiguousContainer.swift index 3e9a92ef6..853e74627 100644 --- a/Sources/Future/Containers/ContiguousContainer.swift +++ b/Sources/Future/Containers/ContiguousContainer.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// @available(SwiftCompatibilitySpan 5.0, *) -public protocol ContiguousContainer: ~Copyable, ~Escapable { +public protocol ContiguousContainer: /*RandomAccessContainer*/ ~Copyable, ~Escapable { associatedtype Element: ~Copyable/* & ~Escapable*/ var span: Span { @lifetime(copy self) get } diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index 6d69d4534..d9c20d2e7 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -19,11 +19,29 @@ public protocol MutableContainer: Container, ~Copyable, ~Escapable { #endif @lifetime(&self) - mutating func nextMutableSpan(after index: inout Index, maximumCount: Int) -> MutableSpan + mutating func nextMutableSpan(after index: inout Index) -> MutableSpan + + // FIXME: What about previousMutableSpan? } @available(SwiftCompatibilitySpan 5.0, *) extension MutableContainer where Self: ~Copyable & ~Escapable { + #if false // Hm... + @lifetime(&self) + mutating func nextMutableSpan( + after index: inout Index, maximumCount: Int + ) -> MutableSpan { + let original = index + var span = self.nextMutableSpan(after: &index) + if span.count > maximumCount { + span = span.extracting(first: maximumCount) + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(original, offsetBy: maximumCount) + } + return span + } + #endif + @inlinable public subscript(index: Index) -> Element { @lifetime(borrow self) diff --git a/Sources/Future/Span/Span+Container.swift b/Sources/Future/Span/Span+Container.swift index fbe56dba1..5f032241e 100644 --- a/Sources/Future/Span/Span+Container.swift +++ b/Sources/Future/Span/Span+Container.swift @@ -25,11 +25,17 @@ extension Span: RandomAccessContainer where Element: ~Copyable { } @lifetime(copy self) - public func nextSpan(after index: inout Index, maximumCount: Int) -> Span { - precondition(index >= 0 && index <= count, "Invalid index") - let end = index + Swift.min(count - index, maximumCount) - defer { index = end } - return _extracting(unsafe Range(uncheckedBounds: (index, end))) + public func nextSpan(after index: inout Index) -> Span { + precondition(index >= 0 && index <= count, "Index out of bounds") + defer { index = count } + return _extracting(unsafe Range(uncheckedBounds: (index, count))) + } + + @lifetime(copy self) + public func previousSpan(before index: inout Int) -> Span { + precondition(index >= 0 && index <= count, "Index out of bounds") + defer { index = 0 } + return _extracting(unsafe Range(uncheckedBounds: (0, index))) } } diff --git a/Sources/Future/Tools/Shared.swift b/Sources/Future/Tools/Shared.swift index 1bb47b20b..7eaaee1ce 100644 --- a/Sources/Future/Tools/Shared.swift +++ b/Sources/Future/Tools/Shared.swift @@ -145,9 +145,16 @@ extension Shared where Storage: ~Copyable { } extension Shared where Storage: ~Copyable { - @available(SwiftStdlib 6.0, *) // for === @inlinable public func isIdentical(to other: Self) -> Bool { - unsafe self._box === other._box + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + return unsafe self._box === other._box + } else { + // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions + // that are only supported in the Swift 6+ runtime. + let a = unsafe Builtin.bridgeToRawPointer(self._box) + let b = unsafe Builtin.bridgeToRawPointer(other._box) + return Bool(Builtin.cmp_eq_RawPointer(a, b)) + } } } diff --git a/Tests/FutureTests/DynamicArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift index 980545dbc..8a1ba8e7a 100644 --- a/Tests/FutureTests/DynamicArrayTests.swift +++ b/Tests/FutureTests/DynamicArrayTests.swift @@ -146,14 +146,14 @@ class DynamicArrayTests: CollectionTestCase { var index = 0 do { - let span = array.nextSpan(after: &index, maximumCount: Int.max) + let span = array.nextSpan(after: &index) expectEqual(span.count, c) for i in 0 ..< span.count { expectEqual(span[i].value, 100 + i) } } do { - let span2 = array.nextSpan(after: &index, maximumCount: Int.max) + let span2 = array.nextSpan(after: &index) expectEqual(span2.count, 0) } } @@ -182,4 +182,11 @@ class DynamicArrayTests: CollectionTestCase { expectEqual(index, i) } } + + func iterationDemo() { + let c = 100_000 + let stride = 7 + let array = DynamicArray(count: c) { Counted($0) } + + } } From fd9ee69e0ba4e389a5ecdd85822a0932b0496e3c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 21 Apr 2025 11:48:14 -0700 Subject: [PATCH 186/195] Make BidirectionalContainer.previousSpan(before:maximumCount:) public --- Sources/Future/Containers/BidirectionalContainer.swift | 3 ++- Sources/Future/Containers/Container.swift | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift index 6ac9873dd..f22fbefa6 100644 --- a/Sources/Future/Containers/BidirectionalContainer.swift +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -20,8 +20,9 @@ public protocol BidirectionalContainer: Container, ~Copyable, ~Escapabl @available(SwiftCompatibilitySpan 5.0, *) extension BidirectionalContainer where Self: ~Copyable & ~Escapable { + @inlinable @lifetime(borrow self) - func previousSpan(before index: inout Index, maximumCount: Int) -> Span { + public func previousSpan(before index: inout Index, maximumCount: Int) -> Span { var span = previousSpan(before: &index) if span.count > maximumCount { // Index remains within the same span, so offseting it is expected to be quick diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 3e913cc97..ddbcf949f 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -42,7 +42,6 @@ public protocol Container: ~Copyable, ~Escapable { #endif // See if index rounding results need to get returned somewhere - // maximumCount: see if it has any real reason to exist yet @lifetime(borrow self) func nextSpan(after index: inout Index) -> Span From 4540631f750c887464fb5eab39be0f163cf8b8d1 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 21 Apr 2025 18:47:34 -0700 Subject: [PATCH 187/195] Add first cut of Container conformance validator Plus, fix issues that came to light as part of this work. --- Package.swift | 9 +- Sources/DequeModule/Deque+Container.swift | 3 +- Sources/DequeModule/Deque+Testing.swift | 26 +- Sources/DequeModule/RigidDeque+Testing.swift | 51 +++ Sources/DequeModule/_UnsafeDequeHandle.swift | 33 ++ Sources/Future/Containers/Container.swift | 36 +- .../Containers/RandomAccessContainer.swift | 12 + .../Containers/RepeatingContainer.swift | 115 +++++++ Sources/Future/Span/SpanExtensions.swift | 4 +- Sources/Future/Tools/Box.swift | 20 ++ Tests/DequeTests/DequeInternals.swift | 23 +- Tests/DequeTests/RigidDequeTests.swift | 38 +++ Tests/FutureTests/DynamicArrayTests.swift | 308 ++++++++++-------- .../FutureTests/RepeatingContainerTests.swift | 23 ++ Tests/FutureTests/RigidArrayTests.swift | 23 ++ .../CheckBidirectionalCollection.swift | 13 +- .../ConformanceCheckers/CheckCollection.swift | 17 +- .../ConformanceCheckers/CheckComparable.swift | 9 +- .../ConformanceCheckers/CheckContainer.swift | 229 +++++++++++++ .../ConformanceCheckers/CheckEquatable.swift | 6 +- .../ConformanceCheckers/CheckHashable.swift | 9 +- .../ConformanceCheckers/CheckSequence.swift | 4 +- .../Utilities/LifetimeTrackedStruct.swift | 44 +++ .../Utilities/LifetimeTracker.swift | 6 + 24 files changed, 871 insertions(+), 190 deletions(-) create mode 100644 Sources/DequeModule/RigidDeque+Testing.swift create mode 100644 Sources/Future/Containers/RepeatingContainer.swift create mode 100644 Tests/DequeTests/RigidDequeTests.swift create mode 100644 Tests/FutureTests/RepeatingContainerTests.swift create mode 100644 Tests/FutureTests/RigidArrayTests.swift create mode 100644 Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift create mode 100644 Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift diff --git a/Package.swift b/Package.swift index 58dd74f65..f1690e25e 100644 --- a/Package.swift +++ b/Package.swift @@ -68,7 +68,8 @@ let extraSettings: [SwiftSetting] = [ .enableUpcomingFeature("StrictMemorySafety"), .enableExperimentalFeature("InoutLifetimeDependence"), .enableExperimentalFeature("AddressableParameters"), - .enableExperimentalFeature("AddressableTypes") + .enableExperimentalFeature("AddressableTypes"), + //.unsafeFlags(["-Xfrontend", "-strict-memory-safety"]), ] let _sharedSettings: [SwiftSetting] = ( @@ -215,7 +216,7 @@ let targets: [CustomTarget] = [ .target( kind: .testSupport, name: "_CollectionsTestSupport", - dependencies: ["InternalCollectionsUtilities"]), + dependencies: ["InternalCollectionsUtilities", "Future"]), .target( kind: .test, name: "CollectionsTestSupportTests", @@ -244,7 +245,7 @@ let targets: [CustomTarget] = [ name: "Future", dependencies: ["InternalCollectionsUtilities"], exclude: ["CMakeLists.txt"], - settings: _sharedSettings + [.unsafeFlags(["-Xfrontend", "-strict-memory-safety"])], + settings: _sharedSettings, ), .target( kind: .test, @@ -339,7 +340,7 @@ let targets: [CustomTarget] = [ "_RopeModule", "SortedCollections", ], - exclude: ["CMakeLists.txt"]) + exclude: ["CMakeLists.txt"]), ] var _products: [Product] = [] diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index b523c9730..cad282c29 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -13,6 +13,7 @@ import Future #endif +#if false extension Deque: RandomAccessContainer { @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { @@ -30,5 +31,5 @@ extension Deque: RandomAccessContainer { public func previousSpan(before index: inout Int) -> Span { _storage.value.previousSpan(before: &index) } - } +#endif diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index 0d8a1d7f4..bda956b93 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -62,28 +62,14 @@ extension Deque { contents: some Sequence ) { let contents = ContiguousArray(contents) - precondition(capacity >= 0) - precondition(startSlot >= 0 && (startSlot < capacity || (capacity == 0 && startSlot == 0))) - precondition(contents.count <= capacity) - - let startSlot = _Slot(at: startSlot) - - var d = RigidDeque(capacity: capacity) - d._handle.count = contents.count - d._handle.startSlot = startSlot - if d._handle.count > 0 { - contents.withUnsafeBufferPointer { source in - let segments = d._handle.mutableSegments() - let c = segments.first.count - segments.first.initializeAll(fromContentsOf: source.prefix(c)) - if let second = segments.second { - second.initializeAll(fromContentsOf: source.dropFirst(c)) - } - } - } + let d = RigidDeque( + _capacity: capacity, + startSlot: startSlot, + count: contents.count + ) { contents[$0] } self.init(_storage: d) assert(self._unstableCapacity == capacity) - assert(self._unstableStartSlot == startSlot.position) + assert(self._unstableStartSlot == startSlot) assert(self.count == contents.count) } } diff --git a/Sources/DequeModule/RigidDeque+Testing.swift b/Sources/DequeModule/RigidDeque+Testing.swift new file mode 100644 index 000000000..027cc2d2f --- /dev/null +++ b/Sources/DequeModule/RigidDeque+Testing.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +extension RigidDeque where Element: ~Copyable { + /// True if consistency checking is enabled in the implementation of this + /// type, false otherwise. + /// + /// Documented performance promises are null and void when this property + /// returns true -- for example, operations that are documented to take + /// O(1) time might take O(*n*) time, or worse. + public static var _isConsistencyCheckingEnabled: Bool { + _isCollectionsInternalCheckingEnabled + } + + /// The number of the storage slot in this deque that holds the first element. + /// (Or would hold it after an insertion in case the deque is currently + /// empty.) + /// + /// This property isn't intended to be used outside of `RigidDeque`'s own test + /// target. + package var _unstableStartSlot: Int { + _handle.startSlot.position + } + + /// Constructs a deque instance of the specified contents and layout. Exposed + /// as public to allow exhaustive input/output tests for `RigidDeque`'s members. + /// This isn't intended to be used outside of `RigidDeque`'s own test target. + package init( + _capacity capacity: Int, + startSlot: Int, + count: Int, + generator: (Int) -> Element + ) { + let startSlot = _Slot(at: startSlot) + self.init(_handle: .allocate(capacity: capacity, startSlot: startSlot, count: count, generator: generator)) + assert(self._unstableStartSlot == startSlot.position) + assert(self.count == count) + } +} diff --git a/Sources/DequeModule/_UnsafeDequeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift index a36939c17..b63631e27 100644 --- a/Sources/DequeModule/_UnsafeDequeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -97,6 +97,39 @@ extension _UnsafeDequeHandle where Element: ~Copyable { } } +// MARK: Test initializer + +extension _UnsafeDequeHandle where Element: ~Copyable { + internal static func allocate( + capacity: Int, + startSlot: Slot, + count: Int, + generator: (Int) -> Element + ) -> Self { + precondition(capacity >= 0) + precondition( + startSlot.position >= 0 && + (startSlot.position < capacity || (capacity == 0 && startSlot.position == 0))) + precondition(count <= capacity) + + var h = Self.allocate(capacity: capacity) + h.count = count + h.startSlot = startSlot + if h.count > 0 { + let segments = h.mutableSegments() + let c = segments.first.count + for i in 0 ..< c { + segments.first.initializeElement(at: i, to: generator(i)) + } + if let second = segments.second { + for i in c ..< h.count { + second.initializeElement(at: i - c, to: generator(i)) + } + } + } + return h + } +} // MARK: Slots diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index ddbcf949f..27705c93d 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -30,7 +30,7 @@ public protocol Container: ~Copyable, ~Escapable { limitedBy limit: Index ) - // FIXME: Do we want these as standard requirements? + // FIXME: Do we want these as standard requirements this time? func index(alignedDown index: Index) -> Index func index(alignedUp index: Index) -> Index @@ -41,13 +41,41 @@ public protocol Container: ~Copyable, ~Escapable { func borrowElement(at index: Index) -> Borrow #endif - // See if index rounding results need to get returned somewhere + /// Return a span over the container's storage that begins with the element at the given index, + /// and extends to the end of the contiguous storage chunk that contains it. On return, the index + /// is updated to address the next item following the end of the returned span. + /// + /// 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 index = items.startIndex + /// while true { + /// let span = items.nextSpan(after: &index) + /// if span.isEmpty { break } + /// // Process items in `span` + /// } + /// + /// Note: The spans returned by this method are not guaranteed to be disjunct. Some containers + /// may use the same storage chunk (or parts of a storage chunk) multiple times, to repeat their + /// contents. + /// + /// Note: Repeated invocations of `nextSpan` on the same container and index are not guaranteed + /// to return identical results. (This is particularly the case with containers that can store + /// contents in their "inline" representation. Such containers may not always have + /// a unique address in memory; the locations of the spans exposed by this method may vary + /// between different borrows of the same container.) + /// + /// - Parameter index: A valid index in the container, including the end index. On return, this + /// index is advanced by the count of the resulting span, to simplify iteration. + /// - Returns: A span over contiguous storage that starts at the given index. If the input index + /// is the end index, then this returns an empty span. Otherwise the result is non-empty, + /// with its first element matching the element at the input index. @lifetime(borrow self) func nextSpan(after index: inout Index) -> Span - // Try a version where nextSpan takes an index range + // FIXME: Try a version where nextSpan takes an index range - // See if it makes sense to have a ~Escapable ValidatedIndex type, as a sort of non-self-driving iterator substitute + // FIXME: See if it makes sense to have a ~Escapable ValidatedIndex type, as a sort of non-self-driving iterator substitute } @available(SwiftCompatibilitySpan 5.0, *) diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift index 742c76c44..f2bfe9218 100644 --- a/Sources/Future/Containers/RandomAccessContainer.swift +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -17,6 +17,12 @@ public protocol RandomAccessContainer extension RandomAccessContainer where Self: ~Copyable & ~Escapable, Index: Strideable, Index.Stride == Int { + @inlinable + public var isEmpty: Bool { startIndex == endIndex } + + @inlinable + public var count: Int { startIndex.distance(to: endIndex) } + @inlinable public func index(after index: Index) -> Index { // Note: Range checks are deferred until element access. @@ -72,6 +78,12 @@ where Index.Stride == Int, Indices == Range { + @inlinable + public var isEmpty: Bool { startIndex == endIndex } + + @inlinable + public var count: Int { endIndex - startIndex } + @inlinable public func index(after index: Index) -> Index { // Note: Range checks are deferred until element access. diff --git a/Sources/Future/Containers/RepeatingContainer.swift b/Sources/Future/Containers/RepeatingContainer.swift new file mode 100644 index 000000000..652cb6cff --- /dev/null +++ b/Sources/Future/Containers/RepeatingContainer.swift @@ -0,0 +1,115 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +#if false + +#if false // FIXME: This is what we'll want +import Builtin + +@frozen +@_addressableForDependencies +public struct RepeatingContainer: ~Copyable { + @usableFromInline + internal var _count: Int + + @usableFromInline + internal var _item: Element + + public init(repeating item: consuming Element, count: Int) { + self._item = item + self._count = count + } +} +#else +@frozen +public struct RepeatingContainer: ~Copyable { + @usableFromInline + internal var _count: Int + + @usableFromInline + internal var _contents: Box + + public init(repeating item: consuming Element, count: Int) { + self._count = count + self._contents = Box(item) + } +} +#endif + +extension RepeatingContainer where Element: ~Copyable { + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _count } +} + +#if false // FIXME: This is what we'll want +@available(SwiftCompatibilitySpan 5.0, *) +extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + Borrow(self._item) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index < c else { return .empty } + index += 1 + // FIXME: Oh my + let pointer = unsafe UnsafePointer(Builtin.unprotectedAddressOfBorrow(self._item)) + let span = unsafe Span(_unsafeStart: pointer, count: 1) + return unsafe _overrideLifetime(span, borrowing: self) + } + + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index > 0 else { return .empty } + index -= 1 + // FIXME: Oh my + let pointer = unsafe UnsafePointer(Builtin.addressOfBorrow(self._item)) + let span = unsafe Span(_unsafeStart: pointer, count: 1) + return unsafe _overrideLifetime(span, borrowing: self) + } +} +#else +@available(SwiftCompatibilitySpan 5.0, *) +extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _contents.borrow() + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index < c else { return .empty } + index += 1 + return _contents.span + } + + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index > 0 else { return .empty } + index -= 1 + return _contents.span + } +} +#endif + +#endif diff --git a/Sources/Future/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift index a1af10451..f8ea9fb5b 100644 --- a/Sources/Future/Span/SpanExtensions.swift +++ b/Sources/Future/Span/SpanExtensions.swift @@ -13,7 +13,7 @@ @usableFromInline internal let immortalThing: [Void] = [] -@available(SwiftStdlib 6.2, *) +@available(SwiftCompatibilitySpan 5.0, *) extension Span where Element: ~Copyable { public static var empty: Span { @lifetime(immortal) @@ -32,7 +32,7 @@ extension Span where Element: ~Copyable { } } -@available(SwiftStdlib 6.2, *) +@available(SwiftCompatibilitySpan 5.0, *) extension Span where Element: Equatable { /// Returns a Boolean value indicating whether this and another span /// contain equal elements in the same order. diff --git a/Sources/Future/Tools/Box.swift b/Sources/Future/Tools/Box.swift index c7f3a7030..5f636c86c 100644 --- a/Sources/Future/Tools/Box.swift +++ b/Sources/Future/Tools/Box.swift @@ -84,3 +84,23 @@ extension Box where T: Copyable { unsafe _pointer.pointee } } + +extension Box where T: ~Copyable { + @available(SwiftCompatibilitySpan 5.0, *) + public var span: Span { + @_alwaysEmitIntoClient + @lifetime(borrow self) + get { + unsafe Span(_unsafeStart: _pointer, count: 1) + } + } + + @available(SwiftCompatibilitySpan 5.0, *) + public var mutableSpan: MutableSpan { + @_alwaysEmitIntoClient + @lifetime(&self) + mutating get { + unsafe MutableSpan(_unsafeStart: _pointer, count: 1) + } + } +} diff --git a/Tests/DequeTests/DequeInternals.swift b/Tests/DequeTests/DequeInternals.swift index c34a3f18f..1ba5ad531 100644 --- a/Tests/DequeTests/DequeInternals.swift +++ b/Tests/DequeTests/DequeInternals.swift @@ -52,6 +52,18 @@ extension Deque { } } +extension RigidDeque { + init>(layout: DequeLayout, contents: C) { + precondition(contents.count == layout.count) + let contents = ContiguousArray(contents) + self.init( + _capacity: layout.capacity, startSlot: layout.startSlot, count: contents.count + ) { + contents[$0] + } + } +} + extension LifetimeTracker { func deque( with layout: DequeLayout @@ -60,15 +72,20 @@ extension LifetimeTracker { let deque = Deque(layout: layout, contents: contents) return (deque, contents) } + + func rigidDeque(with layout: DequeLayout) -> RigidDeque> { + let contents = self.instances(for: layout.valueRange) + return RigidDeque(layout: layout, contents: contents) + } } -func withEveryDeque( +func withEveryDeque( _ label: String, - ofCapacities capacities: C, + ofCapacities capacities: some Collection, startValue: Int = 0, file: StaticString = #file, line: UInt = #line, _ body: (DequeLayout) throws -> Void -) rethrows -> Void where C.Element == Int { +) rethrows -> Void { // Exhaustive tests for all deque layouts of various capacities for capacity in capacities { for startSlot in 0 ..< capacity { diff --git a/Tests/DequeTests/RigidDequeTests.swift b/Tests/DequeTests/RigidDequeTests.swift new file mode 100644 index 000000000..d0057ee67 --- /dev/null +++ b/Tests/DequeTests/RigidDequeTests.swift @@ -0,0 +1,38 @@ +// +// RepeatingContainerTests.swift +// swift-collections +// +// Created by Karoy Lorentey on 2025-04-21. +// + +import XCTest + +#if COLLECTIONS_SINGLE_MODULE +@_spi(Testing) import Collections +#else +import _CollectionsTestSupport +@_spi(Testing) import DequeModule +import Future +#endif + +#if false // FIXME: Debug compiler crash +@available(SwiftStdlib 6.0, *) +class RigidDequeTests: CollectionTestCase { + func test_basic() { + var deque = RigidDeque(capacity: 100) + expectEqual(deque.count, 0) + } + + func test_validate_Container() { + let c = 100 + + withEveryDeque("layout", ofCapacities: [c]) { layout in + withLifetimeTracking { tracker in + let deque = tracker.rigidDeque(with: layout) + let contents = tracker.instances(for: 0 ..< layout.count) + checkContainer(deque, expectedContents: contents) + } + } + } +} +#endif diff --git a/Tests/FutureTests/DynamicArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift index 8a1ba8e7a..109c7fd60 100644 --- a/Tests/FutureTests/DynamicArrayTests.swift +++ b/Tests/FutureTests/DynamicArrayTests.swift @@ -1,192 +1,212 @@ import XCTest import _CollectionsTestSupport import Future +import Synchronization -struct Counted: ~Copyable { - var value: Int - nonisolated(unsafe) static var instances: Int = 0 - - init(_ value: Int) { - self.value = value - Counted.instances += 1 - } +class DynamicArrayTests: CollectionTestCase { + func test_validate_Container() { + let c = 100 - deinit { - Counted.instances -= 1 - expectGreaterThanOrEqual(Counted.instances, 0) + withLifetimeTracking { tracker in + let expected = (0 ..< c).map { tracker.instance(for: $0) } + let items = DynamicArray(count: c, initializedBy: { expected[$0] }) + checkContainer(items, expectedContents: expected) + } } -} - -class DynamicArrayTests: CollectionTestCase { func test_basics() { - var array = DynamicArray() - expectTrue(array.isEmpty) - expectEqual(array.count, 0) - expectEqual(array.capacity, 0) - expectEqual(Counted.instances, 0) - - array.append(Counted(42)) - expectFalse(array.isEmpty) - expectEqual(array.count, 1) - expectEqual(array[0].value, 42) - expectEqual(Counted.instances, 1) - - array.append(Counted(23)) - expectFalse(array.isEmpty) - expectEqual(array.count, 2) - expectEqual(array[0].value, 42) - expectEqual(array[1].value, 23) - expectEqual(Counted.instances, 2) - - let old = array.remove(at: 0) - expectEqual(old.value, 42) - expectFalse(array.isEmpty) - expectEqual(array.count, 1) - expectEqual(array[0].value, 23) - expectEqual(Counted.instances, 2) - _ = consume old - expectEqual(Counted.instances, 1) - - let old2 = array.remove(at: 0) - expectEqual(old2.value, 23) - expectEqual(array.count, 0) - expectTrue(array.isEmpty) - expectEqual(Counted.instances, 1) - _ = consume old2 - expectEqual(Counted.instances, 0) + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + var array = DynamicArray() + expectTrue(array.isEmpty) + expectEqual(array.count, 0) + expectEqual(array.capacity, 0) + expectEqual(tracker.instances, 0) + + array.append(tracker.structInstance(for: 42)) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array[0].payload, 42) + expectEqual(tracker.instances, 1) + + array.append(tracker.structInstance(for: 23)) + expectFalse(array.isEmpty) + expectEqual(array.count, 2) + expectEqual(array[0].payload, 42) + expectEqual(array[1].payload, 23) + expectEqual(tracker.instances, 2) + + let old = array.remove(at: 0) + expectEqual(old.payload, 42) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array[0].payload, 23) + expectEqual(tracker.instances, 2) + _ = consume old + expectEqual(tracker.instances, 1) + + let old2 = array.remove(at: 0) + expectEqual(old2.payload, 23) + expectEqual(array.count, 0) + expectTrue(array.isEmpty) + expectEqual(tracker.instances, 1) + _ = consume old2 + expectEqual(tracker.instances, 0) + } } func test_read_access() { - let c = 100 - let array = DynamicArray(count: c) { Counted($0) } + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i)[].value, i) - expectEqual(array[i].value, i) + let c = 100 + let array = DynamicArray(count: c) { tracker.structInstance(for: $0) } + + for i in 0 ..< c { + expectEqual(array.borrowElement(at: i)[].payload, i) + expectEqual(array[i].payload, i) + } } } func test_update_access() { - let c = 100 - var array = DynamicArray(count: c) { Counted($0) } + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct - for i in 0 ..< c { - // FIXME: 'exclusive' or something instead of mutating subscript - var me = array.mutateElement(at: i) - me[].value += 100 - array[i].value += 100 - } + let c = 100 + var array = DynamicArray(count: c) { tracker.structInstance(for: $0) } - for i in 0 ..< c { - expectEqual(array[i].value, 200 + i) - } + for i in 0 ..< c { + // FIXME: 'exclusive' or something instead of mutating subscript + var me = array.mutateElement(at: i) + me[].payload += 100 + array[i].payload += 100 + } - expectEqual(Counted.instances, c) - _ = consume array - expectEqual(Counted.instances, 0) + for i in 0 ..< c { + expectEqual(array[i].payload, 200 + i) + } + + expectEqual(tracker.instances, c) + _ = consume array + expectEqual(tracker.instances, 0) + } } func test_append() { - var array = DynamicArray() - let c = 100 - for i in 0 ..< c { - array.append(Counted(100 + i)) - } - expectEqual(Counted.instances, c) - expectEqual(array.count, c) + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i)[].value, 100 + i) - expectEqual(array[i].value, 100 + i) - } + var array = DynamicArray() + let c = 100 + for i in 0 ..< c { + array.append(tracker.structInstance(for: 100 + i)) + } + expectEqual(tracker.instances, c) + expectEqual(array.count, c) - _ = consume array - expectEqual(Counted.instances, 0) + for i in 0 ..< c { + expectEqual(array.borrowElement(at: i)[].payload, 100 + i) + expectEqual(array[i].payload, 100 + i) + } + + _ = consume array + expectEqual(tracker.instances, 0) + } } func test_insert() { - var array = DynamicArray() - let c = 100 - for i in 0 ..< c { - array.insert(Counted(100 + i), at: 0) - } - expectEqual(Counted.instances, c) - expectEqual(array.count, c) + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i)[].value, c + 99 - i) - expectEqual(array[i].value, c + 99 - i) - } + var array = DynamicArray() + let c = 100 + for i in 0 ..< c { + array.insert(tracker.structInstance(for: 100 + i), at: 0) + } + expectEqual(tracker.instances, c) + expectEqual(array.count, c) - _ = consume array - expectEqual(Counted.instances, 0) + for i in 0 ..< c { + expectEqual(array.borrowElement(at: i)[].payload, c + 99 - i) + expectEqual(array[i].payload, c + 99 - i) + } + + _ = consume array + expectEqual(tracker.instances, 0) + } } func test_remove() { - let c = 100 - var array = DynamicArray(count: c) { Counted(100 + $0) } - expectEqual(Counted.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - array.remove(at: 0) - expectEqual(array.count, c - 1 - i) - expectEqual(Counted.instances, c - 1 - i) - } + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + let c = 100 + var array = DynamicArray(count: c) { tracker.structInstance(for: 100 + $0) } + expectEqual(tracker.instances, c) + expectEqual(array.count, c) + + for i in 0 ..< c { + array.remove(at: 0) + expectEqual(array.count, c - 1 - i) + expectEqual(tracker.instances, c - 1 - i) + } - expectTrue(array.isEmpty) - expectEqual(Counted.instances, 0) + expectTrue(array.isEmpty) + expectEqual(tracker.instances, 0) + } } @available(SwiftCompatibilitySpan 5.0, *) func test_iterate_full() { - let c = 100 - let array = DynamicArray(count: c) { Counted(100 + $0) } - - var index = 0 - do { - let span = array.nextSpan(after: &index) - expectEqual(span.count, c) - for i in 0 ..< span.count { - expectEqual(span[i].value, 100 + i) + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + let c = 100 + let array = DynamicArray(count: c) { tracker.structInstance(for: 100 + $0) } + + var index = 0 + do { + let span = array.nextSpan(after: &index) + expectEqual(span.count, c) + for i in 0 ..< span.count { + expectEqual(span[i].payload, 100 + i) + } + } + do { + let span2 = array.nextSpan(after: &index) + expectEqual(span2.count, 0) } - } - do { - let span2 = array.nextSpan(after: &index) - expectEqual(span2.count, 0) } } @available(SwiftCompatibilitySpan 5.0, *) func test_iterate_stepped() { - let c = 100 - let array = DynamicArray(count: c) { Counted($0) } - - withEvery("stride", in: 1 ... c) { stride in - var index = 0 - var i = 0 - while true { - expectEqual(index, i) - let span = array.nextSpan(after: &index, maximumCount: stride) - expectEqual(index, i + span.count) - if span.isEmpty { break } - expectEqual(span.count, i + stride <= c ? stride : c % stride) - for j in 0 ..< span.count { - expectEqual(span[j].value, i) - i += 1 + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + let c = 100 + let array = DynamicArray(count: c) { tracker.structInstance(for: $0) } + + withEvery("stride", in: 1 ... c) { stride in + var index = 0 + var i = 0 + while true { + expectEqual(index, i) + let span = array.nextSpan(after: &index, maximumCount: stride) + expectEqual(index, i + span.count) + if span.isEmpty { break } + expectEqual(span.count, i + stride <= c ? stride : c % stride) + for j in 0 ..< span.count { + expectEqual(span[j].payload, i) + i += 1 + } } + expectEqual(i, c) + expectEqual(array.nextSpan(after: &index, maximumCount: Int.max).count, 0) + expectEqual(index, i) } - expectEqual(i, c) - expectEqual(array.nextSpan(after: &index, maximumCount: Int.max).count, 0) - expectEqual(index, i) } } - - func iterationDemo() { - let c = 100_000 - let stride = 7 - let array = DynamicArray(count: c) { Counted($0) } - - } } diff --git a/Tests/FutureTests/RepeatingContainerTests.swift b/Tests/FutureTests/RepeatingContainerTests.swift new file mode 100644 index 000000000..b8de34219 --- /dev/null +++ b/Tests/FutureTests/RepeatingContainerTests.swift @@ -0,0 +1,23 @@ +// +// RepeatingContainerTests.swift +// swift-collections +// +// Created by Karoy Lorentey on 2025-04-21. +// + +import XCTest +import _CollectionsTestSupport +import Future + +@available(SwiftStdlib 6.0, *) +class RepeatingContainerTests: CollectionTestCase { + func test_10() { + withLifetimeTracking { tracker in + let a = tracker.instance(for: 42) + + let items = RepeatingContainer(repeating: a, count: 10) + let expected = [a, a, a, a, a, a, a, a, a, a] + checkContainer(items, expectedContents: expected) + } + } +} diff --git a/Tests/FutureTests/RigidArrayTests.swift b/Tests/FutureTests/RigidArrayTests.swift new file mode 100644 index 000000000..32dfb743b --- /dev/null +++ b/Tests/FutureTests/RigidArrayTests.swift @@ -0,0 +1,23 @@ +// +// RepeatingContainerTests.swift +// swift-collections +// +// Created by Karoy Lorentey on 2025-04-21. +// + +import XCTest +import _CollectionsTestSupport +import Future + +@available(SwiftStdlib 6.0, *) +class RigidArrayTests: CollectionTestCase { + func test_validate_Container() { + let c = 100 + + withLifetimeTracking { tracker in + let expected = (0 ..< c).map { tracker.instance(for: $0) } + let items = RigidArray(count: c, initializedBy: { expected[$0] }) + checkContainer(items, expectedContents: expected) + } + } +} diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift index d7c20c106..4a992a34b 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -17,6 +17,7 @@ import XCTest extension BidirectionalCollection { + @inlinable func _indicesByIndexBefore() -> [Index] { var result: [Index] = [] var i = endIndex @@ -28,6 +29,7 @@ extension BidirectionalCollection { return result } + @inlinable func _indicesByFormIndexBefore() -> [Index] { var result: [Index] = [] var i = endIndex @@ -40,7 +42,11 @@ extension BidirectionalCollection { } } -public func checkBidirectionalCollection( +@inlinable +public func checkBidirectionalCollection< + C: BidirectionalCollection, + S: Sequence +>( _ collection: C, expectedContents: S, maxSamples: Int? = nil, @@ -56,6 +62,7 @@ public func checkBidirectionalCollection( _ collection: C, expectedContents: S, @@ -80,6 +87,7 @@ public func checkBidirectionalCollection( @@ -135,6 +143,7 @@ public func _checkBidirectionalCollection_indexOffsetBy< } } +@inlinable public func _checkBidirectionalCollection( _ collection: C, expectedContents: S, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift index 80e95d012..6a2610b81 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -19,7 +19,8 @@ import XCTest // FIXME: Port the collection validation tests from the Swift compiler codebase. extension Sequence { - func _contentsByIterator() -> [Element] { + @inlinable + internal func _contentsByIterator() -> [Element] { var result: [Element] = [] var it = makeIterator() while let item = it.next() { @@ -28,7 +29,8 @@ extension Sequence { return result } - func _contentsByCopyContents(_ count: Int? = nil) -> [Element] { + @inlinable + internal func _contentsByCopyContents(_ count: Int? = nil) -> [Element] { var it: Iterator? var result = Array( unsafeUninitializedCapacity: count ?? self.underestimatedCount @@ -43,7 +45,8 @@ extension Sequence { } extension Collection { - func _indicesByIndexAfter() -> [Index] { + @inlinable + internal func _indicesByIndexAfter() -> [Index] { var result: [Index] = [] var i = startIndex while i != endIndex { @@ -53,7 +56,8 @@ extension Collection { return result } - func _indicesByFormIndexAfter() -> [Index] { + @inlinable + internal func _indicesByFormIndexAfter() -> [Index] { var result: [Index] = [] var i = startIndex while i != endIndex { @@ -64,6 +68,7 @@ extension Collection { } } +@inlinable public func checkCollection( _ collection: C, expectedContents: Expected, @@ -99,6 +104,7 @@ public func checkCollection( } } +@inlinable public func checkCollection( _ collection: C, expectedContents: Expected, @@ -120,6 +126,7 @@ public func checkCollection( file: file, line: line) } +@inlinable public func _checkCollection( _ collection: C, expectedContents: Expected, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift index 8bde8a7de..b5486a39a 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -12,9 +12,11 @@ // Loosely adapted from https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest +@frozen public enum ExpectedComparisonResult: Hashable { case lt, eq, gt + @inlinable public func flip() -> ExpectedComparisonResult { switch self { case .lt: @@ -26,6 +28,7 @@ public enum ExpectedComparisonResult: Hashable { } } + @inlinable public static func comparing(_ left: C, _ right: C) -> Self { left < right ? .lt : left > right ? .gt @@ -46,6 +49,7 @@ extension ExpectedComparisonResult: CustomStringConvertible { } } +@inlinable public func checkComparable( sortedEquivalenceClasses: [[Instance]], maxSamples: Int? = nil, @@ -68,6 +72,7 @@ public func checkComparable( /// Test that the elements of `instances` satisfy the semantic /// requirements of `Comparable`, using `oracle` to generate comparison /// expectations from pairs of positions in `instances`. +@inlinable public func checkComparable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, @@ -86,6 +91,7 @@ public func checkComparable( file: file, line: line) } +@inlinable public func checkComparable( expected: ExpectedComparisonResult, _ lhs: T, _ rhs: T, @@ -99,6 +105,7 @@ public func checkComparable( /// Same as `checkComparable(_:oracle:file:line:)` but doesn't check /// `Equatable` conformance. Useful for preventing duplicate testing. +@inlinable public func _checkComparable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift new file mode 100644 index 000000000..cc2453778 --- /dev/null +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift @@ -0,0 +1,229 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _indicesByIndexAfter() -> [Index] { + var result: [Index] = [] + var i = startIndex + while i != endIndex { + result.append(i) + i = index(after: i) + } + return result + } + + @inlinable + internal func _indicesByFormIndexAfter() -> [Index] { + var result: [Index] = [] + var i = startIndex + while i != endIndex { + result.append(i) + formIndex(after: &i) + } + return result + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +public func checkContainer< + C: Container & ~Copyable & ~Escapable, + Expected: Sequence +>( + _ container: borrowing C, + expectedContents: Expected, + file: StaticString = #file, + line: UInt = #line +) where C.Element: Equatable { + checkContainer( + container, + expectedContents: expectedContents, + by: ==, + file: file, line: line) +} + +@available(SwiftCompatibilitySpan 5.0, *) +public func checkContainer< + C: Container & ~Copyable & ~Escapable, + Expected: Sequence +>( + _ container: borrowing C, + expectedContents: Expected, + by areEquivalent: (C.Element, C.Element) -> Bool, + file: StaticString = #file, + line: UInt = #line +) where C.Element: Equatable { + let entry = TestContext.current.push("checkContainer", file: file, line: line) + defer { TestContext.current.pop(entry) } + + let expectedContents = Array(expectedContents) + expectEqual(container.isEmpty, expectedContents.isEmpty) + let actualCount = container.count + expectEqual(actualCount, expectedContents.count) + + let validIndices = container._indicesByIndexAfter() + + expectEqual( + container._indicesByIndexAfter(), validIndices, + "Container does not have stable indices") + + // Check that `index(after:)` produces the same results as `formIndex(after:)` + do { + let indicesByFormIndexAfter = container._indicesByFormIndexAfter() + expectEqual(indicesByFormIndexAfter, validIndices) + } + + expectEqual(container.index(container.startIndex, offsetBy: container.count), container.endIndex) + + // Check contents using indexing. + let actualContents = validIndices.map { container[$0] } + expectEquivalentElements(actualContents, expectedContents, by: areEquivalent) + + let allIndices = validIndices + [container.endIndex] + do { + var last: C.Index? = nil + for index in allIndices { + if let last { + // The indices must be monotonically increasing. + expectGreaterThan( + index, last, + "Index \(index) is not greater than immediately preceding index \(last)") + + // Aligning a valid index must not change it. + let nearestDown = container.index(alignedDown: index) + expectEqual(nearestDown, index, "Aligning a valid index down must not change its position") + let nearestUp = container.index(alignedUp: index) + expectEqual(nearestUp, index, "Aligning a valid index up must not change its position") + } + last = index + } + } + + // Check the `Comparable` conformance of the Index type. + if C.Index.self != Int.self { + checkComparable(allIndices, oracle: { .comparing($0, $1) }) + } + + withEveryRange("range", in: 0 ..< allIndices.count - 1) { range in + let i = range.lowerBound + let j = range.upperBound + + // Check `index(_,offsetBy:)` + let e = container.index(allIndices[i], offsetBy: j - i) + expectEqual(e, allIndices[j]) + if j < expectedContents.count { + expectEquivalent(container[e], expectedContents[j], by: areEquivalent) + } + + // Check `distance(from:to:)` + let d = container.distance(from: allIndices[i], to: allIndices[j]) + expectEqual(d, j - i) + } + + // Check `formIndex(_,offsetBy:limitedBy:)` + let limits = + Set([0, allIndices.count - 1, allIndices.count / 2]) + .sorted() + withEvery("limit", in: limits) { limit in + withEvery("i", in: 0 ..< allIndices.count) { i in + let max = allIndices.count - i + (limit >= i ? 2 : 0) + withEvery("delta", in: 0 ..< max) { delta in + let target = i + delta + var index = allIndices[i] + var d = delta + container.formIndex(&index, offsetBy: &d, limitedBy: allIndices[limit]) + if i > limit { + expectEqual(d, 0, "Nonzero remainder after jump opposite limit") + expectEqual(index, allIndices[target], "Jump opposite limit landed in wrong position") + } else if target <= limit { + expectEqual(d, 0, "Nonzero remainder after jump within limit") + expectEqual(index, allIndices[target], "Jump within limit landed in wrong position") + } else { + expectEqual(d, target - limit, "Unexpected remainder after jump beyond limit") + expectEqual(index, allIndices[limit], "Jump beyond limit landed in unexpected position") + } + } + } + } + + // Check that the spans seem plausibly sized and that the indices are monotonic. + let spanShapes: [(offsetRange: Range, indexRange: Range)] = { + var r: [(offsetRange: Range, indexRange: Range)] = [] + var pos = 0 + var index = container.startIndex + while true { + let origIndex = index + let origPos = pos + let span = container.nextSpan(after: &index) + pos += span.count + if span.isEmpty { + expectEqual(origIndex, container.endIndex) + expectEqual(index, origIndex, "nextCount is not expected to move the end index") + break + } + expectGreaterThan( + index, origIndex, "nextCount does not monotonically increase the index") + expectEqual( + index, allIndices[pos], "nextCount does not increase the index by the size of the span") + r.append((origPos ..< pos, origIndex ..< index)) + } + return r + }() + expectEqual( + spanShapes.reduce(into: 0, { $0 += $1.offsetRange.count }), actualCount, + "Container's count does not match the sum of its spans") + + + // Check that the spans have stable sizes and the expected contents. + do { + var pos = 0 + var index = container.startIndex + var spanIndex = 0 + while true { + let span = container.nextSpan(after: &index) + if span.isEmpty { break } + expectEqual( + span.count, spanShapes[spanIndex].offsetRange.count, + "Container has nondeterministic span sizes") + expectEqual( + index, spanShapes[spanIndex].indexRange.upperBound, + "Container has nondeterministic span boundaries") + for i in 0 ..< span.count { + expectEqual(span[i], expectedContents[pos]) + pos += 1 + } + spanIndex += 1 + } + expectEqual(spanIndex, spanShapes.endIndex) + expectEqual(pos, expectedContents.count) + } + + // Check that we can get a span beginning at every index, and that it extends as much as possible. + do { + for spanIndex in spanShapes.indices { + let (offsetRange, indexRange) = spanShapes[spanIndex] + for pos in offsetRange { + let start = validIndices[pos] + var i = start + let span = container.nextSpan(after: &i) + + expectEqual(span.count, offsetRange.upperBound - pos, + "Unexpected span size at offset \(pos), index \(start)") + expectEqual(i, indexRange.upperBound, + "Unexpected span upper bound at offset \(pos), index \(start)") + } + } + } +} diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift index 4bc8586fc..5002b931f 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -12,6 +12,7 @@ // Loosely adapted from https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest +@inlinable public func checkEquatable( equivalenceClasses: [[Instance]], maxSamples: Int? = nil, @@ -23,6 +24,7 @@ public func checkEquatable( checkEquatable(instances, oracle: { oracle[$0] == oracle[$1] }, file: file, line: line) } +@inlinable public func checkEquatable( _ instances: C, oracle: (C.Index, C.Index) -> Bool, @@ -37,6 +39,7 @@ public func checkEquatable( file: file, line: line) } +@inlinable public func checkEquatable( expectedEqual: Bool, _ lhs: T, _ rhs: T, file: StaticString = #file, line: UInt = #line @@ -47,6 +50,7 @@ public func checkEquatable( file: file, line: line) } +@inlinable public func checkEquatable( _ instances: [Instance], oracle: (Int, Int) -> Bool, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift index 91c329a58..d8496912e 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -16,7 +16,8 @@ /// `Hasher`. This is always done by calling the `hash(into:)` method. /// If a non-nil `seed` is given, it is used to perturb the hasher state; /// this is useful for resolving accidental hash collisions. -private func _hash(_ value: H, seed: Int? = nil) -> Int { +@inlinable +internal func _hash(_ value: H, seed: Int? = nil) -> Int { var hasher = Hasher() if let seed = seed { hasher.combine(seed) @@ -28,6 +29,7 @@ private func _hash(_ value: H, seed: Int? = nil) -> Int { /// Test that the elements of `equivalenceClasses` consist of instances that /// satisfy the semantic requirements of `Hashable`, with each group defining /// a distinct equivalence class under `==`. +@inlinable public func checkHashable( equivalenceClasses: [[Instance]], file: StaticString = #file, line: UInt = #line @@ -41,6 +43,7 @@ public func checkHashable( file: file, line: line) } +@inlinable public func checkHashable( expectedEqual: Bool, _ lhs: T, _ rhs: T, file: StaticString = #file, line: UInt = #line @@ -52,6 +55,7 @@ public func checkHashable( /// Test that the elements of `instances` satisfy the semantic requirements of /// `Hashable`, using `equalityOracle` to generate equality and hashing /// expectations from pairs of positions in `instances`. +@inlinable public func checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, @@ -63,6 +67,7 @@ public func checkHashable( /// Same as `checkHashable(_:equalityOracle:file:line:)` but doesn't check /// `Equatable` conformance. Useful for preventing duplicate testing. +@inlinable public func _checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift index da6869502..714ad9cad 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -18,6 +18,7 @@ import XCTest // FIXME: Port the collection validation tests from the Swift compiler codebase. +@inlinable public func checkSequence( _ sequenceGenerator: () -> S, expectedContents: Expected, @@ -32,6 +33,7 @@ public func checkSequence( line: line) } +@inlinable public func checkSequence( _ sequenceGenerator: () -> S, expectedContents: Expected, diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift new file mode 100644 index 000000000..b1b32aae8 --- /dev/null +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// A type that tracks the number of live instances. +/// +/// `LifetimeTracked` is useful to check for leaks in algorithms and data +/// structures. The easiest way to produce instances is to use the +/// `withLifetimeTracking` function: +/// +/// class FooTests: XCTestCase { +/// func testFoo() { +/// withLifetimeTracking([1, 2, 3]) { instances in +/// _ = instances.sorted(by: >) +/// } +/// } +/// } +public struct LifetimeTrackedStruct: ~Copyable { + public let tracker: LifetimeTracker + internal var serialNumber: Int = 0 + public var payload: Payload + + public init(_ payload: consuming Payload, for tracker: LifetimeTracker) { + tracker.instances += 1 + tracker._nextSerialNumber += 1 + self.tracker = tracker + self.serialNumber = tracker._nextSerialNumber + self.payload = payload + } + + deinit { + precondition(serialNumber != 0, "Double deinit") + tracker.instances -= 1 + } +} + diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift index 1728cff69..a12fc4f7e 100644 --- a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift @@ -37,6 +37,12 @@ public class LifetimeTracker { LifetimeTracked(payload, for: self) } + public func structInstance( + for payload: consuming Payload + ) -> LifetimeTrackedStruct { + LifetimeTrackedStruct(payload, for: self) + } + public func instances(for items: S) -> [LifetimeTracked] { return items.map { LifetimeTracked($0, for: self) } } From 5e963e05428612478435cf9c3b8b90aedb230f36 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 22 Apr 2025 17:57:13 -0700 Subject: [PATCH 188/195] Enable MemberImportVisibility feature & solve issues uncovered --- Package.swift | 1 + .../BitCollections/BitArray/BitArray+BitwiseOperations.swift | 2 ++ Sources/BitCollections/BitArray/BitArray+Codable.swift | 2 ++ Sources/BitCollections/BitArray/BitArray+Initializers.swift | 2 ++ Sources/BitCollections/BitArray/BitArray+RandomBits.swift | 2 ++ Sources/BitCollections/BitArray/BitArray+Testing.swift | 2 ++ .../BitCollections/BitSet/BitSet+BidirectionalCollection.swift | 2 ++ Sources/BitCollections/BitSet/BitSet+Random.swift | 2 ++ Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift | 2 ++ Sources/DequeModule/Deque+Testing.swift | 1 + 10 files changed, 18 insertions(+) diff --git a/Package.swift b/Package.swift index f1690e25e..7b65673d8 100644 --- a/Package.swift +++ b/Package.swift @@ -69,6 +69,7 @@ let extraSettings: [SwiftSetting] = [ .enableExperimentalFeature("InoutLifetimeDependence"), .enableExperimentalFeature("AddressableParameters"), .enableExperimentalFeature("AddressableTypes"), + .enableUpcomingFeature("MemberImportVisibility"), //.unsafeFlags(["-Xfrontend", "-strict-memory-safety"]), ] diff --git a/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift b/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift index 2db5d9615..aab2137c4 100644 --- a/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift +++ b/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + #if false // FIXME: Bitwise operators disabled for now. I have two concerns: // 1. We need to support bitwise operations over slices of bit arrays, not just diff --git a/Sources/BitCollections/BitArray/BitArray+Codable.swift b/Sources/BitCollections/BitArray/BitArray+Codable.swift index 18c765ede..a7b5d9d0a 100644 --- a/Sources/BitCollections/BitArray/BitArray+Codable.swift +++ b/Sources/BitCollections/BitArray/BitArray+Codable.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray: Codable { /// Encodes this bit array into the given encoder. /// diff --git a/Sources/BitCollections/BitArray/BitArray+Initializers.swift b/Sources/BitCollections/BitArray/BitArray+Initializers.swift index 4bf604b41..71a2be785 100644 --- a/Sources/BitCollections/BitArray/BitArray+Initializers.swift +++ b/Sources/BitCollections/BitArray/BitArray+Initializers.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray { /// Initialize a bit array from a bit set. /// diff --git a/Sources/BitCollections/BitArray/BitArray+RandomBits.swift b/Sources/BitCollections/BitArray/BitArray+RandomBits.swift index f59ef36aa..eaf5d2837 100644 --- a/Sources/BitCollections/BitArray/BitArray+RandomBits.swift +++ b/Sources/BitCollections/BitArray/BitArray+RandomBits.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray { /// Create and return a new bit array consisting of `count` random bits, /// using the system random number generator. diff --git a/Sources/BitCollections/BitArray/BitArray+Testing.swift b/Sources/BitCollections/BitArray/BitArray+Testing.swift index bf0bcbf5b..73e4766e7 100644 --- a/Sources/BitCollections/BitArray/BitArray+Testing.swift +++ b/Sources/BitCollections/BitArray/BitArray+Testing.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray { @_spi(Testing) public var _capacity: Int { diff --git a/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift b/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift index 30a57cceb..df7c69dd2 100644 --- a/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift +++ b/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitSet: Sequence { /// The type representing the bit set's elements. /// Bit sets are collections of nonnegative integers. diff --git a/Sources/BitCollections/BitSet/BitSet+Random.swift b/Sources/BitCollections/BitSet/BitSet+Random.swift index 5e7977920..d76c835e2 100644 --- a/Sources/BitCollections/BitSet/BitSet+Random.swift +++ b/Sources/BitCollections/BitSet/BitSet+Random.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitSet { public static func random(upTo limit: Int) -> BitSet { var rng = SystemRandomNumberGenerator() diff --git a/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift b/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift index a7a0f344d..fbe6461c9 100644 --- a/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift +++ b/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitSet { /// Returns a Boolean value that indicates whether the given element exists /// in the set. diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index bda956b93..e2b8ed440 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -11,6 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities +import Future #endif // This file contains exported but non-public entry points to support clear box From 769bc7b07c3b986d928540d74d53bdd3d73f4374 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 22 Apr 2025 17:57:36 -0700 Subject: [PATCH 189/195] =?UTF-8?q?Disable=20Deque=E2=80=99s=20Container?= =?UTF-8?q?=20conformance=20to=20avoid=20a=20deserialization=20crash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/DequeModule/Deque+Container.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index cad282c29..3fa7eba18 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -13,7 +13,7 @@ import Future #endif -#if false +#if false // FIXME: Follow up on compiler crash when deserializing this conformance extension Deque: RandomAccessContainer { @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { From 75837a77174c0dd3243a599fae8c1675c64860d1 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 24 Apr 2025 01:14:09 -0700 Subject: [PATCH 190/195] Flesh out RigidArray --- Sources/DequeModule/RigidDeque.swift | 12 +- Sources/Future/Arrays/DynamicArray.swift | 10 +- Sources/Future/Arrays/NewArray.swift | 10 +- Sources/Future/Arrays/RigidArray.swift | 359 +++++++++- Sources/Future/Containers/Container.swift | 52 +- .../Containers/ContainerAlgorithms.swift | 418 ++++++++++++ .../Future/Containers/MutableContainer.swift | 31 + .../Containers/RepeatingContainer.swift | 8 +- Tests/BitCollectionsTests/BitArrayTests.swift | 12 +- .../MinimalTypeConformances.swift | 11 + Tests/DequeTests/DequeTests.swift | 4 +- Tests/FutureTests/DynamicArrayTests.swift | 2 +- Tests/FutureTests/RigidArrayTests.swift | 618 +++++++++++++++++- .../TreeDictionary Tests.swift | 8 +- .../TreeSet Tests.swift | 8 +- .../OrderedDictionary Tests.swift | 4 +- .../OrderedSet/OrderedSetTests.swift | 4 +- .../SortedSet/SortedSet Tests.swift | 4 +- .../Assertions+Containers.swift | 131 ++++ .../AssertionContexts/Assertions.swift | 8 +- .../MinimalTypes/TestContainer.swift | 102 +++ .../Utilities/LifetimeTracker.swift | 6 +- 22 files changed, 1740 insertions(+), 82 deletions(-) create mode 100644 Sources/Future/Containers/ContainerAlgorithms.swift create mode 100644 Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift create mode 100644 Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index 1c8a5d8bb..f342ff77c 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -86,14 +86,14 @@ extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Co let slots = _handle.slotRange(following: &index) return _span(over: slots) } - + @inlinable @lifetime(borrow self) public func previousSpan(before index: inout Int) -> Span { let slots = _handle.slotRange(preceding: &index) return _span(over: slots) } - + @inlinable @lifetime(&self) public mutating func nextMutableSpan( @@ -102,6 +102,14 @@ extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Co let slots = _handle.slotRange(following: &index) return _mutableSpan(over: slots) } + + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition(i >= 0 && i < count, "Index out of bounds") + precondition(j >= 0 && j < count, "Index out of bounds") + let slot1 = _handle.slot(forOffset: i) + let slot2 = _handle.slot(forOffset: j) + unsafe _handle.mutableBuffer.swapAt(slot1.position, slot2.position) + } } extension RigidDeque where Element: ~Copyable { diff --git a/Sources/Future/Arrays/DynamicArray.swift b/Sources/Future/Arrays/DynamicArray.swift index 92f607d81..2c3b03a79 100644 --- a/Sources/Future/Arrays/DynamicArray.swift +++ b/Sources/Future/Arrays/DynamicArray.swift @@ -27,8 +27,8 @@ public struct DynamicArray: ~Copyable { } @inlinable - public init(count: Int, initializedBy generator: (Int) -> Element) { - _storage = .init(count: count, initializedBy: generator) + public init(count: Int, initializedWith generator: (Int) -> Element) { + _storage = .init(count: count, initializedWith: generator) } } @@ -109,6 +109,12 @@ extension DynamicArray: MutableContainer where Element: ~Copyable { public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { _storage.nextMutableSpan(after: &index) } + + @inlinable + @lifetime(&self) + public mutating func swapAt(_ i: Int, _ j: Int) { + _storage.swapAt(i, j) + } } // MARK: - Range replacement operations diff --git a/Sources/Future/Arrays/NewArray.swift b/Sources/Future/Arrays/NewArray.swift index cbce7f2dc..caae232e7 100644 --- a/Sources/Future/Arrays/NewArray.swift +++ b/Sources/Future/Arrays/NewArray.swift @@ -181,9 +181,9 @@ extension NewArray { ensuringMinimumCapacity: count + 1, shared: { src, capacity in var new = RigidArray(capacity: capacity) - new.append(contentsOf: src._span(in: 0 ..< index)) + new.append(copying: src._span(in: 0 ..< index)) new.append(item.take()!) - new.append(contentsOf: src._span(in: index ..< src.count)) + new.append(copying: src._span(in: index ..< src.count)) return new }, resize: { src, capacity in @@ -199,9 +199,9 @@ extension NewArray { srcCount = 0 } } - dst.append(contentsOf: src._span(in: 0 ..< index)) + dst.append(copying: src._span(in: 0 ..< index)) dst.append(item.take()!) - dst.append(contentsOf: src._span(in: index ..< src.count)) + dst.append(copying: src._span(in: index ..< src.count)) return dst }, direct: { target in @@ -221,7 +221,7 @@ extension NewArray { let old = self[count - 1] _storage.replace { var new = RigidArray(capacity: $0.capacity) - new.append(contentsOf: $0.span._extracting(droppingLast: 1)) + new.append(copying: $0.span._extracting(droppingLast: 1)) return new } return old diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift index 71e1fc234..160b48dae 100644 --- a/Sources/Future/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -27,7 +27,7 @@ public struct RigidArray: ~Copyable { @inlinable public init(capacity: Int) { - precondition(capacity >= 0) + precondition(capacity >= 0, "Array capacity must be nonnegative") if capacity > 0 { unsafe _storage = .allocate(capacity: capacity) } else { @@ -37,7 +37,7 @@ public struct RigidArray: ~Copyable { } @inlinable - public init(count: Int, initializedBy generator: (Int) -> Element) { + public init(count: Int, initializedWith generator: (Int) -> Element) { unsafe _storage = .allocate(capacity: count) for i in 0 ..< count { unsafe _storage.initializeElement(at: i, to: generator(i)) @@ -48,6 +48,21 @@ public struct RigidArray: ~Copyable { extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} +extension RigidArray /*where Element: Copyable*/ { + /// Creates a new array containing the specified number of a single, + /// repeated value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the value passed in the + /// `repeating` parameter. `count` must be zero or greater. + public init(repeating repeatedValue: Element, count: Int) { + self.init(capacity: count) + unsafe _freeSpace.initialize(repeating: repeatedValue) + _count = count + } +} + extension RigidArray where Element: ~Copyable { @inlinable @inline(__always) @@ -161,6 +176,9 @@ extension RigidArray where Element: ~Copyable { @inlinable public var endIndex: Int { count } + @inlinable + public var indices: Range { unsafe Range(uncheckedBounds: (0, count)) } + @inlinable @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { @@ -170,6 +188,12 @@ extension RigidArray where Element: ~Copyable { borrowing: self ) } + + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition(i >= 0 && i < _count && j >= 0 && j < _count, "Index out of bounds") + unsafe _items.swapAt(i, j) + } } //MARK: - MutableContainer conformance @@ -197,12 +221,13 @@ extension RigidArray where Element: ~Copyable { //MARK: Unsafe access extension RigidArray where Element: ~Copyable { + // FIXME: Replace this with an OutputSpan-based mutator @inlinable public mutating func withUnsafeMutableBufferPointer( _ body: (UnsafeMutableBufferPointer, inout Int) throws(E) -> R ) throws(E) -> R { defer { precondition(_count >= 0 && _count <= capacity) } - return unsafe try body(_items, &_count) + return unsafe try body(_storage, &_count) } } @@ -227,33 +252,175 @@ extension RigidArray where Element: ~Copyable { } } -//MARK: Range replacement operations +//MARK: - Opening and closing gaps + +extension RigidArray where Element: ~Copyable { + @inlinable + internal mutating func _closeGap( + at index: Int, count: Int + ) { + guard count > 0 else { return } + let source = unsafe _storage.extracting(Range(uncheckedBounds: (index + count, _count))) + let target = unsafe _storage.extracting(Range(uncheckedBounds: (index, index + source.count))) + let i = unsafe target.moveInitialize(fromContentsOf: source) + assert(i == target.endIndex) + } + + @inlinable + internal mutating func _openGap( + at index: Int, count: Int + ) -> UnsafeMutableBufferPointer { + assert(index >= 0 && index <= _count) + assert(count <= freeCapacity) + guard count > 0 else { return unsafe _storage.extracting(index ..< index) } + let source = unsafe _storage.extracting(Range(uncheckedBounds: (index, _count))) + let target = unsafe _storage.extracting(Range(uncheckedBounds: (index + count, _count + count))) + let i = unsafe target.moveInitialize(fromContentsOf: source) + assert(i == target.count) + return unsafe _storage.extracting(Range(uncheckedBounds: (index, index + count))) + } +} + +//MARK: - Removal operations extension RigidArray where Element: ~Copyable { + /// Removes all elements from the array, preserving its allocated capacity. + /// + /// - Complexity: O(*n*), where *n* is the original count of the array. + @inlinable + public mutating func removeAll() { + unsafe _items.deinitialize() + _count = 0 + } + + /// Removes and returns the last element of the array. + /// + /// The array must not be empty. + /// + /// - Returns: The last element of the original array. + /// + /// - Complexity: O(1) @inlinable @discardableResult public mutating func removeLast() -> Element { - precondition(!isEmpty) + precondition(!isEmpty, "Cannot remove last element from an empty array") let old = unsafe _storage.moveElement(from: _count - 1) _count -= 1 return old } + /// Removes the specified number of elements from the end of the array. + /// + /// Attempting to remove more elements than exist in the array triggers a runtime error. + /// + /// - Parameter k: The number of elements to remove from the array. + /// `k` must be greater than or equal to zero and must not exceed the count of the array. + /// + /// - Complexity: O(`k`) + @inlinable + public mutating func removeLast(_ k: Int) { + if k == 0 { return } + precondition(k >= 0 && k <= _count, "Count of elements to remove is out of bounds") + unsafe _storage.extracting(Range(uncheckedBounds: (_count - k, _count))).deinitialize() + _count &-= k + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// gap. + /// + /// - Parameter i: The position of the element to remove. `index` must be + /// a valid index of the array that is not equal to the end index. + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) @inlinable @discardableResult public mutating func remove(at index: Int) -> Element { - precondition(index >= 0 && index < count) + precondition(index >= 0 && index < _count, "Index out of bounds") let old = unsafe _storage.moveElement(from: index) - let source = unsafe _storage.extracting(index + 1 ..< count) - let target = unsafe _storage.extracting(index ..< count - 1) - let i = unsafe target.moveInitialize(fromContentsOf: source) - assert(i == target.endIndex) + _closeGap(at: index, count: 1) _count -= 1 return old } + + /// Removes the specified subrange of elements from the array. + /// + /// All the elements following the specified subrange are moved to close the resulting + /// gap. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + precondition( + bounds.lowerBound >= 0 && bounds.upperBound <= _count, + "Subrange out of bounds") + guard !bounds.isEmpty else { return } + unsafe _storage.extracting(bounds).deinitialize() + _closeGap(at: bounds.lowerBound, count: bounds.count) + _count -= bounds.count + } } extension RigidArray where Element: ~Copyable { + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a container that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the array. + /// + /// - Complexity: O(`count`) + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func removeAll( + where shouldBeRemoved: (borrowing Element) throws(E) -> Bool + ) throws(E) { + let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) + removeSubrange(suffixStart...) + } + + /// Removes and returns the last element of the array. + /// + /// - Returns: The last element of the array if the array is not empty; otherwise, `nil`. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public mutating func popLast() -> Element? { + if isEmpty { return nil } + return removeLast() + } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + removeSubrange(bounds.relative(to: indices)) + } +} + +//MARK: - Insertion operations + + +extension RigidArray where Element: ~Copyable { + /// Adds an element to the end of the array. + /// + /// If the array does not have sufficient capacity to hold any more elements, then this + /// triggers a runtime error. + /// + /// - Parameter item: The element to append to the collection. + /// + /// - Complexity: O(1) @inlinable public mutating func append(_ item: consuming Element) { precondition(!isFull) @@ -262,11 +429,69 @@ extension RigidArray where Element: ~Copyable { } } +extension RigidArray { + @_alwaysEmitIntoClient + public mutating func append(contentsOf items: some Sequence) { + var (it, c) = unsafe items._copyContents(initializing: _freeSpace) + precondition(it.next() == nil, "RigidArray capacity overflow") + _count += c + } + + // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func append(copying items: Span) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + unsafe items.withUnsafeBufferPointer { source in + let c = unsafe source._copyContents(initializing: _freeSpace).1 + _count &+= c + } + } + + // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func append & ~Copyable & ~Escapable>( + copying items: borrowing C + ) { + var i = items.startIndex + var target = unsafe _freeSpace + while true { + let span = items.nextSpan(after: &i) + if span.isEmpty { break } + unsafe span.withUnsafeBufferPointer { source in + precondition(source.count <= target.count, "RigidArray capacity overflow") + unsafe target.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + _count += source.count + unsafe target = target.extracting(source.count...) + } + } + } +} + extension RigidArray where Element: ~Copyable { + /// Inserts a new element into the array at the specified position. + /// + /// If the array does not have sufficient capacity to hold any more elements, then this + /// triggers a runtime error. + /// + /// The new element is inserted before the element currently at the specified index. If you pass + /// the array's `endIndex` as the `index` parameter, then the new element is appended to the + /// collection. + /// + /// All existing elements at or following the specified position are moved to make room for the + /// new item. + /// + /// - Parameter item: The new element to insert into the array. + /// - Parameter i: The position at which to insert the new element. + /// `index` must be a valid index in the array. + /// + /// - Complexity: O(`count`) @inlinable public mutating func insert(_ item: consuming Element, at index: Int) { - precondition(index >= 0 && index <= count) - precondition(!isFull) + precondition(index >= 0 && index <= count, "Index out of bounds") + precondition(!isFull, "RigidArray capacity overflow") if index < count { let source = unsafe _storage.extracting(index ..< count) let target = unsafe _storage.extracting(index + 1 ..< count + 1) @@ -280,25 +505,117 @@ extension RigidArray where Element: ~Copyable { extension RigidArray { @inlinable - public mutating func append(contentsOf items: some Sequence) { - for item in items { - append(item) - } + public mutating func insert(contentsOf items: some Collection, at index: Int) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + let c = items.count + precondition(c <= freeCapacity, "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: c) + var (it, copied) = unsafe items._copyContents(initializing: gap) + precondition(it.next() == nil && copied == c, "Broken Collection: count doesn't match contents") + _count += c + } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + internal mutating func _insert(copying items: UnsafeBufferPointer, at index: Int) { + guard items.count > 0 else { return } + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: items.count) + unsafe gap.baseAddress.unsafelyUnwrapped.initialize( + from: items.baseAddress.unsafelyUnwrapped, count: items.count) + _count += items.count } + // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. @available(SwiftCompatibilitySpan 5.0, *) @inlinable - public mutating func append(contentsOf items: Span) { - precondition(items.count <= freeCapacity) - unsafe items.withUnsafeBufferPointer { - _ = unsafe _freeSpace.initialize(fromContentsOf: $0) + public mutating func insert(copying items: Span, at index: Int) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + unsafe items.withUnsafeBufferPointer { unsafe self._insert(copying: $0, at: index) } + } + + // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func insert & ~Copyable & ~Escapable>( + copying items: borrowing C, at index: Int + ) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + let c = items.count + precondition(c <= freeCapacity, "RigidArray capacity overflow") + var i = items.startIndex + var target = unsafe _openGap(at: index, count: c) + while true { + let span = items.nextSpan(after: &i) + if span.isEmpty { break } + unsafe span.withUnsafeBufferPointer { source in + precondition(source.count <= target.count, "Broken Container: count doesn't match contents") + unsafe target.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + _count += source.count + unsafe target = target.extracting(source.count...) + } + } + precondition(target.count == 0, "Broken Container: count doesn't match contents") + } +} + +//MARK: - Range replacement + +extension RigidArray { + /// Replaces the specified subrange of elements by copying the elements of the given collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + with newElements: __owned some Collection + ) { + precondition( + subrange.lowerBound >= 0 && subrange.upperBound <= _count, + "Index range out of bounds") + let c = newElements.count + precondition(c - subrange.count <= freeCapacity, "RigidArray capacity overflow") + unsafe _items.extracting(subrange).deinitialize() + if c > subrange.count { + _ = unsafe _openGap(at: subrange.upperBound, count: c - subrange.count) + } else if c < subrange.count { + _closeGap(at: subrange.lowerBound + c, count: subrange.count - c) } + let gap = unsafe _storage.extracting(subrange.lowerBound ..< subrange.lowerBound + c) + var (it, copied) = unsafe newElements._copyContents(initializing: gap) + precondition(it.next() == nil && copied == c, "Broken Collection: count doesn't match contents") + _count += c - subrange.count } + + // FIXME: Add replaceSubrange(_:copying:) variants taking Span & Container } //MARK: - Copying and moving helpers extension RigidArray { + // FIXME: Make these public, perhaps as initializers. + @inlinable internal func _copy() -> Self { _copy(capacity: capacity) @@ -313,7 +630,9 @@ extension RigidArray { result._count = count return result } +} +extension RigidArray where Element: ~Copyable { @inlinable internal mutating func _move(capacity: Int) -> Self { precondition(capacity >= count) diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 27705c93d..7af024a26 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -30,10 +30,6 @@ public protocol Container: ~Copyable, ~Escapable { limitedBy limit: Index ) - // FIXME: Do we want these as standard requirements this time? - func index(alignedDown index: Index) -> Index - func index(alignedUp index: Index) -> Index - #if compiler(>=9999) // FIXME: We can't do this yet subscript(index: Index) -> Element { borrow } #else @@ -76,6 +72,15 @@ public protocol Container: ~Copyable, ~Escapable { // FIXME: Try a version where nextSpan takes an index range // FIXME: See if it makes sense to have a ~Escapable ValidatedIndex type, as a sort of non-self-driving iterator substitute + + // *** Requirements with default implementations *** + + // FIXME: Do we want these as standard requirements this time? + func index(alignedDown index: Index) -> Index + func index(alignedUp index: Index) -> Index + + func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? + func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? } @available(SwiftCompatibilitySpan 5.0, *) @@ -86,6 +91,37 @@ extension Container where Self: ~Copyable & ~Escapable { @inlinable public func index(alignedUp index: Index) -> Index { index } + @inlinable + public func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } + + @inlinable + public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: Collection { + @inlinable + public func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } + + @inlinable + public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public subscript(index: Index) -> Element { + // This stopgap definition is standing in for the eventual subscript requirement with a + // borrowing accessor. + @lifetime(copy self) + unsafeAddress { + unsafe borrowElement(at: index)._pointer + } + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { @inlinable @lifetime(borrow self) public func nextSpan(after index: inout Index, maximumCount: Int) -> Span { @@ -98,14 +134,6 @@ extension Container where Self: ~Copyable & ~Escapable { } return span } - - @inlinable - public subscript(index: Index) -> Element { - @lifetime(copy self) - unsafeAddress { - unsafe borrowElement(at: index)._pointer - } - } } diff --git a/Sources/Future/Containers/ContainerAlgorithms.swift b/Sources/Future/Containers/ContainerAlgorithms.swift new file mode 100644 index 000000000..14543889e --- /dev/null +++ b/Sources/Future/Containers/ContainerAlgorithms.swift @@ -0,0 +1,418 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2014 - 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 +// +//===----------------------------------------------------------------------===// + +//MARK: - firstIndex(where:) + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _containerFirstIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + var i = self.startIndex + while true { + let spanStart = i + let span = self.nextSpan(after: &i) + if span.isEmpty { break } + for offset in span.indices { + if try predicate(span[offset]) { + return self.index(spanStart, offsetBy: offset) + } + } + } + return nil + } + + /// Returns the first index that addresses an element of the container that + /// satisfies the given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the container satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerFirstIndex(where: predicate) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: Collection { + /// Returns the first index that addresses an element of the container that + /// satisfies the given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the container satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerFirstIndex(where: predicate) + } +} + +//MARK: - firstIndex(of:) + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Element: Equatable { + @inlinable + internal func _containerFirstIndex(of element: Element) -> Index? { + if let result = _customIndexOfEquatableElement(element) { + return result + } + return firstIndex(where: { $0 == element }) + } + + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex(of element: Element) -> Index? { + _containerFirstIndex(of: element) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: Collection, Element: Equatable { + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex(of element: Element) -> Index? { + _containerFirstIndex(of: element) + } +} + +//MARK: - lastIndex(where:) + +@available(SwiftCompatibilitySpan 5.0, *) +extension BidirectionalContainer where Self: ~Copyable & ~Escapable { + @inlinable + internal func _containerLastIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + var i = self.endIndex + while true { + let span = self.previousSpan(before: &i) + if span.isEmpty { break } + var offset = span.count - 1 + while offset >= 0 { + if try predicate(span[offset]) { + return self.index(i, offsetBy: offset) + } + offset &-= 1 + } + } + return nil + } + + /// Returns the index of the last element in the container that matches the + /// given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the last element in the container that matches + /// `predicate`, or `nil` if no elements match. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerLastIndex(where: predicate) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension BidirectionalContainer where Self: BidirectionalCollection { + /// Returns the first index that addresses an element of the container that + /// satisfies the given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the container satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerLastIndex(where: predicate) + } +} + +//MARK: - lastIndex(of:) + +@available(SwiftCompatibilitySpan 5.0, *) +extension BidirectionalContainer where Element: Equatable { + @inlinable + internal func _containerLastIndex(of element: Element) -> Index? { + if let result = _customLastIndexOfEquatableElement(element) { + return result + } + return self._containerLastIndex(where: { $0 == element }) + } + + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex(of element: Element) -> Index? { + _containerLastIndex(of: element) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension BidirectionalContainer where Self: BidirectionalCollection, Element: Equatable { + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex(of element: Element) -> Index? { + _containerLastIndex(of: element) + } +} + +//MARK: - elementsEqual(_:,by:) + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _spanwiseZip( + state: inout State, + with other: borrowing Other, + by process: (inout State, Span, Span) throws(E) -> Bool + ) throws(E) -> (Index, Other.Index) { + var i = self.startIndex // End index of the current span in `self` + var j = other.startIndex // End index of the current span in `other` + var a: Span = .empty + var b: Span = .empty + var pi = i // Start index of the original span we're currently processing in `self` + var pj = j // Start index of the original span we're currently processing in `other` + var ca = 0 // Count of the original span we're currently processing in `self` + var cb = 0 // Count of the original span we're currently processing in `other` + loop: + while true { + if a.isEmpty { + pi = i + a = self.nextSpan(after: &i) + ca = a.count + } + if b.isEmpty { + pj = j + b = other.nextSpan(after: &j) + cb = b.count + } + if a.isEmpty || b.isEmpty { + return ( + (a.isEmpty ? i : self.index(pi, offsetBy: ca - a.count)), + (b.isEmpty ? j : other.index(pj, offsetBy: cb - b.count))) + } + + let c = Swift.min(a.count, b.count) + guard try process(&state, a._extracting(first: c), b._extracting(first: c)) else { + return ( + (c == a.count ? i : self.index(pi, offsetBy: c)), + (c == b.count ? j : other.index(pj, offsetBy: c))) + } + a = a._extracting(droppingFirst: c) + b = b._extracting(droppingFirst: c) + } + } + + @inlinable + internal func _containerElementsEqual( + _ other: borrowing Other, + by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool + ) throws(E) -> Bool { + var result = true + let (i, j) = try _spanwiseZip(state: &result, with: other) { state, a, b throws(E) in + assert(a.count == b.count) + for i in a.indices { + guard try areEquivalent(a[i], b[i]) else { + state = false + return false + } + } + return true + } + return result && i == self.endIndex && j == other.endIndex + } + + /// Returns a Boolean value indicating whether this container and another + /// container contain equivalent elements in the same order, using the given + /// predicate as the equivalence test. + /// + /// The predicate must form an *equivalence relation* over the elements. That + /// is, for any elements `a`, `b`, and `c`, the following conditions must + /// hold: + /// + /// - `areEquivalent(a, a)` is always `true`. (Reflexivity) + /// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry) + /// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then + /// `areEquivalent(a, c)` is also `true`. (Transitivity) + /// + /// - Parameters: + /// - other: A container to compare to this container. + /// - areEquivalent: A predicate that returns `true` if its two arguments + /// are equivalent; otherwise, `false`. + /// - Returns: `true` if this container and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + public func elementsEqual( + _ other: borrowing Other, + by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool + ) throws(E) -> Bool { + try _containerElementsEqual(other, by: areEquivalent) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: Sequence { + /// Returns a Boolean value indicating whether this container and another + /// container contain equivalent elements in the same order, using the given + /// predicate as the equivalence test. + /// + /// The predicate must form an *equivalence relation* over the elements. That + /// is, for any elements `a`, `b`, and `c`, the following conditions must + /// hold: + /// + /// - `areEquivalent(a, a)` is always `true`. (Reflexivity) + /// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry) + /// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then + /// `areEquivalent(a, c)` is also `true`. (Transitivity) + /// + /// - Parameters: + /// - other: A container to compare to this container. + /// - areEquivalent: A predicate that returns `true` if its two arguments + /// are equivalent; otherwise, `false`. + /// - Returns: `true` if this container and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + public func elementsEqual & Sequence, E: Error>( + _ other: Other, + by areEquivalent: (borrowing Element, borrowing Element) throws(E) -> Bool + ) throws(E) -> Bool { + try _containerElementsEqual(other, by: areEquivalent) + } +} +//MARK: - elementsEqual(_:) + + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable, Element: Equatable { + /// Returns a Boolean value indicating whether this container and another + /// container contain the same elements in the same order. + /// + /// - Parameter other: A container whose contents to compare to this container. + /// - Returns: `true` if this container and `other` contain the same elements + /// in the same order. + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + @inline(__always) + public func elementsEqual & ~Copyable & ~Escapable>( + _ other: borrowing Other + ) -> Bool { + self._containerElementsEqual(other, by: { $0 == $1 }) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: Sequence, Element: Equatable { + /// Returns a Boolean value indicating whether this container and another + /// container contain the same elements in the same order. + /// + /// - Parameter other: A container whose contents to compare to this container. + /// - Returns: `true` if this container and `other` contain the same elements + /// in the same order. + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + @inline(__always) + public func elementsEqual & Container>( + _ other: Other + ) -> Bool { + self._containerElementsEqual(other, by: { $0 == $1 }) + } +} diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index d9c20d2e7..05197d4b0 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -15,13 +15,18 @@ public protocol MutableContainer: Container, ~Copyable, ~Escapable { subscript(index: Index) -> Element { borrow mutate } #else @lifetime(&self) + @lifetime(self: copy self) mutating func mutateElement(at index: Index) -> Inout #endif @lifetime(&self) + @lifetime(self: copy self) mutating func nextMutableSpan(after index: inout Index) -> MutableSpan // FIXME: What about previousMutableSpan? + + @lifetime(self: copy self) + mutating func swapAt(_ i: Index, _ j: Index) } @available(SwiftCompatibilitySpan 5.0, *) @@ -55,3 +60,29 @@ extension MutableContainer where Self: ~Copyable & ~Escapable { } } } + +@available(SwiftCompatibilitySpan 5.0, *) +extension MutableContainer where Self: ~Copyable & ~Escapable { + /// Moves all elements satisfying `isSuffixElement` into a suffix of the + /// container, returning the start position of the resulting suffix. + /// + /// - Complexity: O(*n*) where n is the count of the container. + @inlinable + @lifetime(self: copy self) + internal mutating func _halfStablePartition( + isSuffixElement: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index { + guard var i = try firstIndex(where: isSuffixElement) + else { return endIndex } + + var j = index(after: i) + while j != endIndex { + if try !isSuffixElement(self[j]) { + swapAt(i, j) + formIndex(after: &i) + } + formIndex(after: &j) + } + return i + } +} diff --git a/Sources/Future/Containers/RepeatingContainer.swift b/Sources/Future/Containers/RepeatingContainer.swift index 652cb6cff..11de12fca 100644 --- a/Sources/Future/Containers/RepeatingContainer.swift +++ b/Sources/Future/Containers/RepeatingContainer.swift @@ -9,9 +9,7 @@ // //===----------------------------------------------------------------------===// -#if false - -#if false // FIXME: This is what we'll want +#if false // FIXME: This is what we'd want import Builtin @frozen @@ -52,7 +50,7 @@ extension RepeatingContainer where Element: ~Copyable { public var endIndex: Int { _count } } -#if false // FIXME: This is what we'll want +#if false // FIXME: This is what we'd want @available(SwiftCompatibilitySpan 5.0, *) extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { @lifetime(borrow self) @@ -111,5 +109,3 @@ extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { } } #endif - -#endif diff --git a/Tests/BitCollectionsTests/BitArrayTests.swift b/Tests/BitCollectionsTests/BitArrayTests.swift index a1dd95468..1e31a1d32 100644 --- a/Tests/BitCollectionsTests/BitArrayTests.swift +++ b/Tests/BitCollectionsTests/BitArrayTests.swift @@ -485,13 +485,13 @@ final class BitArrayTests: CollectionTestCase { expectEqual(try MinimalDecoder.decode(v4, as: BitArray.self), b4) let v5: MinimalEncoder.Value = .uint64(42) - expectThrows(try MinimalDecoder.decode(v5, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v5, as: BitArray.self) } let v6: MinimalEncoder.Value = .array([]) - expectThrows(try MinimalDecoder.decode(v6, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v6, as: BitArray.self) } let v7: MinimalEncoder.Value = .array([.uint64(1)]) - expectThrows(try MinimalDecoder.decode(v7, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v7, as: BitArray.self) } let v8: MinimalEncoder.Value = .array([ .uint64(1), @@ -499,13 +499,13 @@ final class BitArrayTests: CollectionTestCase { .uint64(0), .uint64(0) ]) - expectThrows(try MinimalDecoder.decode(v8, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v8, as: BitArray.self) } let v9: MinimalEncoder.Value = .array([.uint64(100), .uint64(0)]) - expectThrows(try MinimalDecoder.decode(v9, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v9, as: BitArray.self) } let v10: MinimalEncoder.Value = .array([.uint64(16), .uint64(UInt64.max)]) - expectThrows(try MinimalDecoder.decode(v10, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v10, as: BitArray.self) } } func test_replaceSubrange_Array() { diff --git a/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift b/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift index 786e69f30..f088532b7 100644 --- a/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift +++ b/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift @@ -12,6 +12,7 @@ import XCTest #if !COLLECTIONS_SINGLE_MODULE import _CollectionsTestSupport +import Future #endif final class MinimalTypeTests: CollectionTestCase { @@ -43,4 +44,14 @@ final class MinimalTypeTests: CollectionTestCase { func testMinimalBidirectionalCollection() { checkBidirectionalCollection(MinimalBidirectionalCollection(0 ..< 50), expectedContents: 0 ..< 50) } + + #if false // FIXME: Track down compiler crash + func testTestContainer() { + guard #available(SwiftCompatibilitySpan 5.0, *) else { return } + let a = TestContainer( + contents: RigidArray(count: 100, initializedWith: { $0 }), + spanCounts: [3, 1, 7]) + checkContainer(a, expectedContents: 0 ..< 100) + } + #endif } diff --git a/Tests/DequeTests/DequeTests.swift b/Tests/DequeTests/DequeTests.swift index 310832567..dea19583a 100644 --- a/Tests/DequeTests/DequeTests.swift +++ b/Tests/DequeTests/DequeTests.swift @@ -209,7 +209,7 @@ final class DequeTests: CollectionTestCase { func workaroundSR14134(cap: Int, count: Int, tracker: LifetimeTracker) { // This function works around https://bugs.swift.org/browse/SR-14134 let contents = tracker.instances(for: 0 ..< count) - expectThrows( + expectThrows({ try Deque>( unsafeUninitializedCapacity: cap, initializingWith: { target, c in @@ -227,7 +227,7 @@ final class DequeTests: CollectionTestCase { } throw TestError(count) }) - ) { error in + }) { error in expectEqual(error as? TestError, TestError(count)) } } diff --git a/Tests/FutureTests/DynamicArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift index 109c7fd60..e066a592a 100644 --- a/Tests/FutureTests/DynamicArrayTests.swift +++ b/Tests/FutureTests/DynamicArrayTests.swift @@ -9,7 +9,7 @@ class DynamicArrayTests: CollectionTestCase { withLifetimeTracking { tracker in let expected = (0 ..< c).map { tracker.instance(for: $0) } - let items = DynamicArray(count: c, initializedBy: { expected[$0] }) + let items = DynamicArray(count: c, initializedWith: { expected[$0] }) checkContainer(items, expectedContents: expected) } } diff --git a/Tests/FutureTests/RigidArrayTests.swift b/Tests/FutureTests/RigidArrayTests.swift index 32dfb743b..04b39f330 100644 --- a/Tests/FutureTests/RigidArrayTests.swift +++ b/Tests/FutureTests/RigidArrayTests.swift @@ -9,15 +9,623 @@ import XCTest import _CollectionsTestSupport import Future +struct ArrayLayout { + var capacity: Int + var count: Int + + init(capacity: Int, count: Int) { + precondition(count >= 0 && count <= capacity) + self.capacity = capacity + self.count = count + } +} + +extension RigidArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init(capacity: layout.capacity) + for i in 0 ..< layout.count { + self.append(generator(i)) + } + } +} + +extension LifetimeTracker { + func rigidArray(layout: ArrayLayout) -> RigidArray> { + rigidArray(layout: layout, using: { $0 }) + } + + func rigidArray( + layout: ArrayLayout, + using generator: (Int) -> Element + ) -> RigidArray> { + RigidArray(layout: layout, using: { self.instance(for: generator($0)) }) + } +} + +func withSomeArrayLayouts( + _ label: String, + ofCapacities capacities: some Sequence, + file: StaticString = #file, + line: UInt = #line, + run body: (ArrayLayout) throws(E) -> Void +) throws(E) { + let context = TestContext.current + for capacity in capacities { + var counts: Set = [] + counts.insert(0) + counts.insert(capacity) + counts.insert(capacity / 2) + if capacity >= 1 { + counts.insert(1) + counts.insert(capacity - 1) + } + if capacity >= 2 { + counts.insert(2) + counts.insert(capacity - 2) + } + for count in counts { + let layout = ArrayLayout(capacity: capacity, count: count) + let entry = context.push("\(label): \(layout)", file: file, line: line) + + var done = false + defer { + context.pop(entry) + if !done { + print(context.currentTrace(title: "Throwing trace")) + } + } + try body(layout) + done = true + } + } +} + @available(SwiftStdlib 6.0, *) class RigidArrayTests: CollectionTestCase { func test_validate_Container() { - let c = 100 + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let items = tracker.rigidArray(layout: layout) + let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } + expectEqual(tracker.instances, 2 * layout.count) + checkContainer(items, expectedContents: expected) + } + } + } + + func test_init_capacity() { + do { + let a = RigidArray(capacity: 0) + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + expectTrue(a.isFull) + } + + do { + let a = RigidArray(capacity: 10) + expectEqual(a.capacity, 10) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 10) + expectTrue(a.isEmpty) + expectFalse(a.isFull) + } + } + + func test_init_generator() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectEqual(a.isFull, layout.count == layout.capacity) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_init_repeating() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let value = tracker.instance(for: 0) + let a = RigidArray(repeating: value, count: c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + expectTrue(a.isFull) + for i in 0 ..< c { + expectIdentical(a[i], value) + } + } + } + } + + func test_span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let span = a.span + expectEqual(span.count, layout.count) + for i in 0 ..< span.count { + expectEqual(span[i].payload, i) + } + } + } + } + + func test_mutableSpan() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + var span = a.mutableSpan + expectEqual(span.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(span[i].payload, i) + span[i] = tracker.instance(for: -i) + } + for i in 0 ..< layout.count { + expectEqual(span[i].payload, -i) + } + for i in 0 ..< layout.count { + expectEqual(a[i].payload, -i) + } + } + } + } + + func test_nextSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let whole = a.span + var i = 0 + let first = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(first.isIdentical(to: whole)) + let second = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(second.isEmpty) + } + } + } + + func test_previousSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let whole = a.span + var i = layout.count + let first = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(first.isIdentical(to: whole)) + let second = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(second.isEmpty) + } + } + } + + func test_nextMutableSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + var i = 0 + var span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectEqual(span.count, layout.count) + span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(span.isEmpty) + } + } + } + + + func test_index_properties() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + let a = RigidArray(layout: layout, using: { $0 }) + expectEqual(a.startIndex, 0) + expectEqual(a.endIndex, layout.count) + expectEqual(a.indices, 0 ..< layout.count) + } + } + + func test_swapAt() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + withEvery("i", in: 0 ..< layout.count / 2) { i in + a.swapAt(i, layout.count - 1 - i) + } + let expected = (0 ..< layout.count).reversed() + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_borrowElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + let item = a.borrowElement(at: i) + expectEqual(item[].payload, i) + } + } + } + } + + func test_mutateElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + var item = a.mutateElement(at: i) + expectEqual(item[].payload, i) + item[] = tracker.instance(for: -i) + expectEqual(tracker.instances, layout.count) + } + } + } + } + + func test_withUnsafeMutableBufferPointer() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, layout.count) + unsafe buffer.extracting(0 ..< count).deinitialize() + expectEqual(tracker.instances, 0) + count = 0 + } + expectEqual(a.count, 0) + + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, 0) + for i in 0 ..< buffer.count { + unsafe buffer.initializeElement(at: i, to: tracker.instance(for: -i)) + count += 1 + } + expectEqual(tracker.instances, layout.capacity) + } + expectEqual(a.count, layout.capacity) + + struct TestError: Error {} + + expectThrows({ + unsafe try a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(tracker.instances, layout.capacity) + while count > 0 { + if count == layout.count { break } + unsafe buffer.deinitializeElement(at: count - 1) + count -= 1 + } + throw TestError() + } + }) { error in + expectTrue(error is TestError) + } + expectContainerContents( + a, + equivalentTo: (0 ..< layout.count).map { -$0 }, + by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_resize_to() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [layout.capacity, layout.count, layout.count + 1, layout.capacity + 1] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.resize(to: newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_reserveCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [layout.capacity, layout.count, layout.count + 1, layout.capacity + 1] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.reserveCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.removeAll() + expectTrue(a.isEmpty) + expectEqual(a.capacity, layout.capacity) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + withEvery("i", in: 0 ..< layout.count) { i in + a.removeLast() + expectEqual(a.count, layout.count - 1 - i) + expectEqual(a.capacity, layout.capacity) + } + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast_k() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("k", in: 0 ..< layout.count) { k in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeLast(k) + + var a = tracker.rigidArray(layout: layout) + expectEqual(tracker.instances, layout.count) + a.removeLast(k) + expectEqual(tracker.instances, layout.count - k) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_remove_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ..< layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.remove(at: i) + + var a = tracker.rigidArray(layout: layout) + a.remove(at: i) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeSubrange() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeSubrange(range) + + var a = tracker.rigidArray(layout: layout) + a.removeSubrange(range) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll_where() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeAll(where: { $0.isMultiple(of: 2) }) + + var a = tracker.rigidArray(layout: layout) + a.removeAll(where: { $0.payload.isMultiple(of: 2) }) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_popLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let expectedItem = expected.popLast() + + var a = tracker.rigidArray(layout: layout) + let item = a.popLast() + + expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_append() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in layout.count ..< layout.capacity { + a.append(tracker.instance(for: i)) + expectEqual(a.count, i + 1) + expectContainerContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) + } + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_append_contentsOf() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.append(contentsOf: MinimalSequence( + elements: (layout.count ..< layout.capacity).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: false)) + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_append_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + let b = tracker.rigidArray( + layout: ArrayLayout( + capacity: layout.capacity - layout.count, + count: layout.capacity - layout.count), + using: { layout.count + $0 }) + a.append(copying: b.span) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_append_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + let b = tracker.rigidArray( // FIXME: Use TestContainer with various span counts + layout: ArrayLayout( + capacity: layout.capacity - layout.count, + count: layout.capacity - layout.count), + using: { layout.count + $0 }) + a.append(copying: b) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_insert_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + guard layout.count < layout.capacity else { return } + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.insert(-1, at: i) + + var a = tracker.rigidArray(layout: layout) + a.insert(tracker.instance(for: -1), at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + 1) + } + } + } + } + + func test_insert_contentsOf() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.capacity) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + a.insert(contentsOf: addition.map { tracker.instance(for: $0) }, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_insert_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = Array(layout.count ..< layout.capacity) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + let rigidAddition = RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + } + a.insert(copying: rigidAddition.span, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_insert_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.capacity) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + let rigidAddition = RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + } + // FIXME: Use TestContainer with various span counts + a.insert(copying: rigidAddition, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_replaceSubrange_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = addition.map { tracker.instance(for: $0) } + // FIXME: Use TestContainer with various span counts + a.replaceSubrange(range, with: trackedAddition) - withLifetimeTracking { tracker in - let expected = (0 ..< c).map { tracker.instance(for: $0) } - let items = RigidArray(count: c, initializedBy: { expected[$0] }) - checkContainer(items, expectedContents: expected) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } } } } diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift index 247deb70a..28ec8ec5c 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift @@ -386,7 +386,7 @@ class TreeDictionaryTests: CollectionTestCase { let v5: MinimalEncoder.Value = .dictionary([ "This is not a number": .string("bus"), "42": .string("train")]) - expectThrows(try MinimalDecoder.decode(v5, as: PD.self)) { + expectThrows({ try MinimalDecoder.decode(v5, as: PD.self) }) { expectTrue($0 is DecodingError) } @@ -394,10 +394,10 @@ class TreeDictionaryTests: CollectionTestCase { let v6: MinimalEncoder.Value = .dictionary([ "This is not a number": .string("bus"), "42": .string("train")]) - expectThrows( + expectThrows({ try MinimalDecoder.decode( v6, as: PD.self) - ) { + }) { expectTrue($0 is DecodingError) } @@ -416,7 +416,7 @@ class TreeDictionaryTests: CollectionTestCase { let v8: MinimalEncoder.Value = .array([ .int32(1), .string("bike"), .int32(2), ]) - expectThrows(try MinimalDecoder.decode(v8, as: PD.self)) + expectThrows({ try MinimalDecoder.decode(v8, as: PD.self) }) } func test_CustomReflectable() { diff --git a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift index ce018b6e1..fb2274232 100644 --- a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift @@ -752,9 +752,9 @@ class TreeSetTests: CollectionTestCase { expectEqual(try MinimalDecoder.decode(v4, as: TreeSet.self), s4) let v5: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0)]) - expectThrows( + expectThrows({ try MinimalDecoder.decode(v5, as: TreeSet.self) - ) { error in + }) { error in expectNotNil(error as? DecodingError) { error in guard case .dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") @@ -766,9 +766,9 @@ class TreeSetTests: CollectionTestCase { } let v6: MinimalEncoder.Value = .array([.int16(42)]) - expectThrows( + expectThrows({ try MinimalDecoder.decode(v6, as: TreeSet.self) - ) { error in + }) { error in expectNotNil(error as? DecodingError) { error in guard case .typeMismatch(_, _) = error else { expectFailure("Unexpected error \(error)") diff --git a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift index eacd60980..b6633174c 100644 --- a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift @@ -952,7 +952,7 @@ class OrderedDictionaryTests: CollectionTestCase { expectEqual(try MinimalDecoder.decode(v4, as: OD.self), d4) let v5: MinimalEncoder.Value = .array([.int(0), .int(1), .int(2)]) - expectThrows(try MinimalDecoder.decode(v5, as: OD.self)) { error in + expectThrows({ try MinimalDecoder.decode(v5, as: OD.self) }) { error in guard case DecodingError.dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") return @@ -963,7 +963,7 @@ class OrderedDictionaryTests: CollectionTestCase { } let v6: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0), .int(2)]) - expectThrows(try MinimalDecoder.decode(v6, as: OD.self)) { error in + expectThrows({ try MinimalDecoder.decode(v6, as: OD.self) }) { error in guard case DecodingError.dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") return diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift index 53e5cd77d..bd122a1ee 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift @@ -277,10 +277,10 @@ class OrderedSetTests: CollectionTestCase { let v3: MinimalEncoder.Value = .array((0 ..< 100).map { .int($0) }) expectEqual(try MinimalDecoder.decode(v3, as: OrderedSet.self), s3) - expectThrows(try MinimalDecoder.decode(.int(0), as: OrderedSet.self)) + expectThrows({ try MinimalDecoder.decode(.int(0), as: OrderedSet.self) }) let v4: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0)]) - expectThrows(try MinimalDecoder.decode(v4, as: OrderedSet.self)) { error in + expectThrows({ try MinimalDecoder.decode(v4, as: OrderedSet.self) }) { error in expectNotNil(error as? DecodingError) { error in guard case .dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") diff --git a/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift b/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift index 378479e5f..a4a4fc3b7 100644 --- a/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift +++ b/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift @@ -185,10 +185,10 @@ class SortedSetTests: CollectionTestCase { let v3: MinimalEncoder.Value = .array((0 ..< 100).map { .int($0) }) expectEqual(try MinimalDecoder.decode(v3, as: SortedSet.self), s3) - expectThrows(try MinimalDecoder.decode(.int(0), as: SortedSet.self)) + expectThrows({ try MinimalDecoder.decode(.int(0), as: SortedSet.self) }) let v4: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0)]) - expectThrows(try MinimalDecoder.decode(v4, as: SortedSet.self)) { error in + expectThrows({ try MinimalDecoder.decode(v4, as: SortedSet.self) }) { error in expectNotNil(error as? DecodingError) { error in guard case .dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift new file mode 100644 index 000000000..6b66bac87 --- /dev/null +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 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 +// +//===----------------------------------------------------------------------===// + +import Future + +@available(SwiftCompatibilitySpan 5.0, *) +public func expectContainersWithEquivalentElements< + C1: Container & ~Copyable & ~Escapable, + C2: Container & ~Copyable & ~Escapable +>( + _ left: borrowing C1, + _ right: borrowing C2, + by areEquivalent: (borrowing C1.Element, borrowing C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + if left.elementsEqual(right, by: areEquivalent) { return } + _expectFailure( + "Containers do not have equivalent elements", + message, trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftCompatibilitySpan 5.0, *) +public func expectContainersWithEqualElements< + Element: Equatable, + C1: Container & ~Copyable & ~Escapable, + C2: Container & ~Copyable & ~Escapable, +>( + _ left: borrowing C1, + _ right: borrowing C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + if left.elementsEqual(right) { return } + _expectFailure( + "Containers do not have equal elements", + message, trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftCompatibilitySpan 5.0, *) +public func expectContainerContents< + Element: Equatable, + C1: Container & ~Copyable & ~Escapable, + C2: Collection, +>( + _ left: borrowing C1, + equalTo right: C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = left.startIndex + var it = right.makeIterator() + while i < left.endIndex { + let a = left[i] + guard let b = it.next() else { + _expectFailure( + "Container is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard a == b else { + _expectFailure( + "'\(a)' at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + left.formIndex(after: &i) + } + guard it.next() == nil else { + _expectFailure( + "Container is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftCompatibilitySpan 5.0, *) +public func expectContainerContents< + C1: Container & ~Copyable & ~Escapable, + C2: Collection, +>( + _ left: borrowing C1, + equivalentTo right: C2, + by areEquivalent: (borrowing C1.Element, C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = left.startIndex + var it = right.makeIterator() + while i < left.endIndex { + guard let b = it.next() else { + _expectFailure( + "Container is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard areEquivalent(left[i], b) else { + _expectFailure( + "Element at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + left.formIndex(after: &i) + } + guard it.next() == nil else { + _expectFailure( + "Container is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift index 554a4c935..1a7955e06 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift @@ -384,18 +384,18 @@ public func expectStrictlyMonotonicallyIncreasing( } } -public func expectThrows( - _ expression: @autoclosure () throws -> T, +public func expectThrows( + _ expression: () throws(E) -> T, _ message: @autoclosure () -> String = "", trapping: Bool = false, file: StaticString = #file, line: UInt = #line, - _ errorHandler: (Error) -> Void = { _ in } + handler errorHandler: (E) -> Void = { _ in } ) { do { let result = try expression() expectFailure("Expression did not throw" - + (T.self == Void.self ? "" : " (returned '\(result)' instead)"), + + (T.self == Void.self ? "" : " (returned '\(result)' instead)"), trapping: trapping, file: file, line: line) } catch { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift b/Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift new file mode 100644 index 000000000..825e7f9a7 --- /dev/null +++ b/Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 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 +// +//===----------------------------------------------------------------------===// + +import Future + +public struct TestContainer: ~Copyable { + internal let _contents: RigidArray + internal let _spanCounts: [Int] + + public init(contents: consuming RigidArray, spanCounts: [Int]) { + precondition(spanCounts.allSatisfy { $0 > 0 }) + self._contents = contents + self._spanCounts = spanCounts + } +} + +extension TestContainer where Element: ~Copyable { + public struct Index: Comparable { + internal var _offset: Int + + internal init(_offset: Int) { + self._offset = _offset + } + + public static func == (left: Self, right: Self) -> Bool { left._offset == right._offset } + public static func < (left: Self, right: Self) -> Bool { left._offset < right._offset } + } + + func _spanCoordinates(atOffset offset: Int) -> (spanIndex: Int, spanOffset: Int) { + precondition(offset >= 0 && offset <= _contents.count) + let modulus = _spanCounts.reduce(into: 0, { $0 += $1 }) + var remainder = offset % modulus + for i in 0 ..< _spanCounts.count { + let c = _spanCounts[i] + if remainder < c { + return (i, remainder) + } + remainder -= c + } + return (_spanCounts.count, 0) + } +} + +extension TestContainer where Element: ~Copyable { + public var isEmpty: Bool { _contents.isEmpty } + public var count: Int { _contents.count } + public var startIndex: Index { Index(_offset: 0) } + public var endIndex: Index { Index(_offset: _contents.count) } + public func index(after index: Index) -> Index { + precondition(index >= startIndex && index < endIndex) + return Index(_offset: index._offset + 1) + } + public func formIndex(after i: inout Index) { + i = index(after: i) + } + public func index(_ index: Index, offsetBy n: Int) -> Index { + precondition(index._offset >= 0 && index._offset <= _contents.count) + let offset = index._offset + n + precondition(offset >= 0 && offset <= _contents.count) + return Index(_offset: offset) + } + + public func distance(from start: Index, to end: Index) -> Int { + precondition(start >= startIndex && start <= endIndex) + precondition(end >= startIndex && end <= endIndex) + return end._offset - start._offset + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension TestContainer: Container where Element: ~Copyable { + public func formIndex(_ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index) { + precondition(i >= startIndex && i <= endIndex) + _contents.formIndex(&i._offset, offsetBy: &distance, limitedBy: limit._offset) + } + + @lifetime(borrow self) + public func borrowElement(at index: Index) -> Future.Borrow { + precondition(index >= startIndex && index < endIndex) + return _contents.borrowElement(at: index._offset) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Index) -> Span { + precondition(index >= startIndex && index <= endIndex) + let (spanIndex, spanOffset) = _spanCoordinates(atOffset: index._offset) + let span = _contents.span + guard spanIndex < _spanCounts.count else { return span._extracting(last: 0) } + let c = _spanCounts[spanIndex] - spanOffset + index._offset += c + return span._extracting(index._offset ..< index._offset + c) + } +} diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift index a12fc4f7e..0321f8dfb 100644 --- a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift @@ -55,11 +55,11 @@ public class LifetimeTracker { } @inlinable -public func withLifetimeTracking( +public func withLifetimeTracking( file: StaticString = #file, line: UInt = #line, - _ body: (LifetimeTracker) throws -> R -) rethrows -> R { + _ body: (LifetimeTracker) throws(E) -> R +) throws(E) -> R { let tracker = LifetimeTracker() defer { tracker.check(file: file, line: line) } return try body(tracker) From 3fffe2ad2062eabe93dddb4d2e37bffe9bf278dc Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 28 Apr 2025 21:55:28 -0700 Subject: [PATCH 191/195] Flesh out RigidArray even more; add complete test coverage --- Sources/DequeModule/Deque.swift | 2 + Sources/Future/Arrays/NewArray.swift | 8 +- Sources/Future/Arrays/RigidArray.swift | 269 +++++++++++++----- .../Containers/RepeatingContainer.swift | 4 + .../Span/UnsafeBufferPointer+Additions.swift | 69 +++++ .../StaccatoContainerTests.swift | 110 +++++++ .../FutureTests/RepeatingContainerTests.swift | 2 + Tests/FutureTests/RigidArrayTests.swift | 180 ++++++++++-- .../FutureTests/SpanTests/RawSpanTests.swift | 2 +- .../ConformanceCheckers/CheckContainer.swift | 14 +- ...ontainer.swift => StaccatoContainer.swift} | 61 ++-- 11 files changed, 581 insertions(+), 140 deletions(-) create mode 100644 Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift rename Tests/_CollectionsTestSupport/MinimalTypes/{TestContainer.swift => StaccatoContainer.swift} (61%) diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index f3b8fd1d1..3d33c77c2 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -9,7 +9,9 @@ // //===----------------------------------------------------------------------===// +#if !COLLECTIONS_SINGLE_MODULE import Future +#endif /// A collection implementing a double-ended queue. `Deque` (pronounced "deck") /// implements an ordered random-access collection that supports efficient diff --git a/Sources/Future/Arrays/NewArray.swift b/Sources/Future/Arrays/NewArray.swift index caae232e7..fbbc7791f 100644 --- a/Sources/Future/Arrays/NewArray.swift +++ b/Sources/Future/Arrays/NewArray.swift @@ -95,7 +95,7 @@ extension NewArray { if _storage.isUnique() { _storage.value.reserveCapacity(n) } else { - _storage.replace { $0._copy(capacity: Swift.max($0.capacity, n)) } + _storage.replace { $0.copy(capacity: Swift.max($0.capacity, n)) } } } @@ -113,7 +113,7 @@ extension NewArray { @inlinable internal mutating func _ensureUnique() { - _storage.ensureUnique { $0._copy() } + _storage.ensureUnique { $0.copy() } } @inlinable @@ -127,8 +127,8 @@ extension NewArray { } let c = Swift.max(minimumCapacity, Self._grow(capacity)) _storage.edit( - shared: { $0._copy(capacity: c) }, - unique: { $0 = $0._move(capacity: c) } + shared: { $0.copy(capacity: c) }, + unique: { $0.reserveCapacity(c) } ) } diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift index 160b48dae..7a505fc26 100644 --- a/Sources/Future/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -9,6 +9,10 @@ // //===----------------------------------------------------------------------===// +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + /// A manually resizable, heap allocated, noncopyable array of /// potentially noncopyable elements. @safe @@ -35,7 +39,12 @@ public struct RigidArray: ~Copyable { } _count = 0 } +} +extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} +//MARK: - Initializers + +extension RigidArray where Element: ~Copyable { @inlinable public init(count: Int, initializedWith generator: (Int) -> Element) { unsafe _storage = .allocate(capacity: count) @@ -46,8 +55,6 @@ public struct RigidArray: ~Copyable { } } -extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} - extension RigidArray /*where Element: Copyable*/ { /// Creates a new array containing the specified number of a single, /// repeated value. @@ -63,6 +70,39 @@ extension RigidArray /*where Element: Copyable*/ { } } +extension RigidArray /*where Element: Copyable*/ { + @_alwaysEmitIntoClient + @inline(__always) + public init(capacity: Int? = nil, copying contents: some Collection) { + self.init(capacity: capacity ?? contents.count) + self.append(contentsOf: contents) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & ~Copyable & ~Escapable>( + capacity: Int? = nil, + copying contents: borrowing C + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Collection>( + capacity: Int? = nil, + copying contents: C + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } +} + +//MARK: - Basics + extension RigidArray where Element: ~Copyable { @inlinable @inline(__always) @@ -235,8 +275,8 @@ extension RigidArray where Element: ~Copyable { extension RigidArray where Element: ~Copyable { @inlinable - public mutating func resize(to newCapacity: Int) { - precondition(newCapacity >= count) + public mutating func setCapacity(_ newCapacity: Int) { + precondition(newCapacity >= count, "RigidArray capacity overflow") guard newCapacity != capacity else { return } let newStorage: UnsafeMutableBufferPointer = .allocate(capacity: newCapacity) let i = unsafe newStorage.moveInitialize(fromContentsOf: self._items) @@ -248,10 +288,30 @@ extension RigidArray where Element: ~Copyable { @inlinable public mutating func reserveCapacity(_ n: Int) { guard capacity < n else { return } - resize(to: n) + setCapacity(n) + } +} + +//MARK: - Copying helpers + +extension RigidArray { + @inlinable + public func copy() -> Self { + copy(capacity: capacity) + } + + @inlinable + public func copy(capacity: Int) -> Self { + precondition(capacity >= count, "RigidArray capacity overflow") + var result = RigidArray(capacity: capacity) + let initialized = unsafe result._storage.initialize(fromContentsOf: _items) + precondition(initialized == count) + result._count = count + return result } } + //MARK: - Opening and closing gaps extension RigidArray where Element: ~Copyable { @@ -423,9 +483,9 @@ extension RigidArray where Element: ~Copyable { /// - Complexity: O(1) @inlinable public mutating func append(_ item: consuming Element) { - precondition(!isFull) + precondition(!isFull, "RigidArray capacity overflow") unsafe _storage.initializeElement(at: _count, to: item) - _count += 1 + _count &+= 1 } } @@ -454,19 +514,9 @@ extension RigidArray { public mutating func append & ~Copyable & ~Escapable>( copying items: borrowing C ) { - var i = items.startIndex - var target = unsafe _freeSpace - while true { - let span = items.nextSpan(after: &i) - if span.isEmpty { break } - unsafe span.withUnsafeBufferPointer { source in - precondition(source.count <= target.count, "RigidArray capacity overflow") - unsafe target.baseAddress.unsafelyUnwrapped.initialize( - from: source.baseAddress.unsafelyUnwrapped, count: source.count) - _count += source.count - unsafe target = target.extracting(source.count...) - } - } + let (copied, end) = unsafe _freeSpace._initializePrefix(copying: items) + precondition(end == items.endIndex, "RigidArray capacity overflow") + _count += copied } } @@ -543,26 +593,42 @@ extension RigidArray { precondition(index >= 0 && index <= _count, "Index out of bounds") let c = items.count precondition(c <= freeCapacity, "RigidArray capacity overflow") - var i = items.startIndex - var target = unsafe _openGap(at: index, count: c) - while true { - let span = items.nextSpan(after: &i) - if span.isEmpty { break } - unsafe span.withUnsafeBufferPointer { source in - precondition(source.count <= target.count, "Broken Container: count doesn't match contents") - unsafe target.baseAddress.unsafelyUnwrapped.initialize( - from: source.baseAddress.unsafelyUnwrapped, count: source.count) - _count += source.count - unsafe target = target.extracting(source.count...) - } - } - precondition(target.count == 0, "Broken Container: count doesn't match contents") + let target = unsafe _openGap(at: index, count: c) + let (copied, end) = unsafe target._initializePrefix(copying: items) + precondition( + copied == c && end == items.endIndex, + "Broken Container: count doesn't match contents") + _count += c } } //MARK: - Range replacement extension RigidArray { + /// Perform a range replacement up to populating the newly opened gap. This deinitializes removed content, rearranges trailing + /// elements to be at their final size, and sets the container's new count. + /// + /// - Returns: A buffer pointer addressing the newly opened gap, to be initialized by the caller. + @inlinable + internal mutating func _gapForReplacement( + of subrange: Range, withNewCount newCount: Int + ) -> UnsafeMutableBufferPointer { + precondition( + subrange.lowerBound >= 0 && subrange.upperBound <= _count, + "Index range out of bounds") + precondition(newCount - subrange.count <= freeCapacity, "RigidArray capacity overflow") + unsafe _items.extracting(subrange).deinitialize() + if newCount > subrange.count { + _ = unsafe _openGap(at: subrange.upperBound, count: newCount - subrange.count) + } else if newCount < subrange.count { + _closeGap(at: subrange.lowerBound + newCount, count: subrange.count - newCount) + } + _count += newCount - subrange.count + let gapRange = unsafe Range( + uncheckedBounds: (subrange.lowerBound, subrange.lowerBound + newCount)) + return unsafe _storage.extracting(gapRange) + } + /// Replaces the specified subrange of elements by copying the elements of the given collection. /// /// This method has the effect of removing the specified range of elements @@ -591,56 +657,113 @@ extension RigidArray { _ subrange: Range, with newElements: __owned some Collection ) { - precondition( - subrange.lowerBound >= 0 && subrange.upperBound <= _count, - "Index range out of bounds") let c = newElements.count - precondition(c - subrange.count <= freeCapacity, "RigidArray capacity overflow") - unsafe _items.extracting(subrange).deinitialize() - if c > subrange.count { - _ = unsafe _openGap(at: subrange.upperBound, count: c - subrange.count) - } else if c < subrange.count { - _closeGap(at: subrange.lowerBound + c, count: subrange.count - c) - } - let gap = unsafe _storage.extracting(subrange.lowerBound ..< subrange.lowerBound + c) + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: c) var (it, copied) = unsafe newElements._copyContents(initializing: gap) precondition(it.next() == nil && copied == c, "Broken Collection: count doesn't match contents") - _count += c - subrange.count } - // FIXME: Add replaceSubrange(_:copying:) variants taking Span & Container -} - -//MARK: - Copying and moving helpers - -extension RigidArray { - // FIXME: Make these public, perhaps as initializers. - + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. @inlinable - internal func _copy() -> Self { - _copy(capacity: capacity) + public mutating func replaceSubrange( + _ subrange: Range, + with newElements: UnsafeBufferPointer + ) { + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newElements.count) + unsafe gap.initializeAll(fromContentsOf: newElements) } + /// Replaces the specified subrange of elements by copying the elements of the given span. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) @inlinable - internal func _copy(capacity: Int) -> Self { - precondition(capacity >= count) - var result = RigidArray(capacity: capacity) - let initialized = unsafe result._storage.initialize(fromContentsOf: _storage) - precondition(initialized == count) - result._count = count - return result + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: Span + ) { + unsafe newElements.withUnsafeBufferPointer { buffer in + unsafe self.replaceSubrange(subrange, with: buffer) + } } -} -extension RigidArray where Element: ~Copyable { + /// Replaces the specified subrange of elements by copying the elements of the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) @inlinable - internal mutating func _move(capacity: Int) -> Self { - precondition(capacity >= count) - var result = RigidArray(capacity: capacity) - let initialized = unsafe result._storage.moveInitialize(fromContentsOf: _storage) - precondition(initialized == count) - result._count = count - self._count = 0 - return result + public mutating func replaceSubrange & ~Copyable & ~Escapable>( + _ subrange: Range, + copying newElements: borrowing C + ) { + let c = newElements.count + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: c) + let (copied, end) = unsafe gap._initializePrefix(copying: newElements) + precondition( + copied == c && end == newElements.endIndex, + "Broken Container: count doesn't match contents") } } diff --git a/Sources/Future/Containers/RepeatingContainer.swift b/Sources/Future/Containers/RepeatingContainer.swift index 11de12fca..9285f90e1 100644 --- a/Sources/Future/Containers/RepeatingContainer.swift +++ b/Sources/Future/Containers/RepeatingContainer.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +#if false // FIXME: Disabled until I have time to track down a flaky compiler crash + #if false // FIXME: This is what we'd want import Builtin @@ -109,3 +111,5 @@ extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { } } #endif + +#endif diff --git a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift index ca033b55a..3a4c7271a 100644 --- a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift +++ b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift @@ -46,3 +46,72 @@ extension UnsafeMutableRawBufferPointer { unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) } } + + +extension UnsafeMutableBufferPointer { + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + internal mutating func _initializeAndDropPrefix(copying buffer: UnsafeBufferPointer) { + if buffer.isEmpty { return } + precondition(buffer.count <= self.count) + unsafe self.baseAddress.unsafelyUnwrapped.initialize( + from: buffer.baseAddress.unsafelyUnwrapped, count: buffer.count) + unsafe self = self.extracting(buffer.count...) + } + + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + internal mutating func _initializeAndDropPrefix(copying span: Span) { + unsafe span.withUnsafeBufferPointer { buffer in + unsafe self._initializeAndDropPrefix(copying: buffer) + } + } + + /// Initialize all slots in this buffer by copying data from `items`, which must fit entirely + /// in this buffer. + /// + /// If `items` contains more elements than can fit into this buffer, then this function + /// will return an index other than `items.endIndex`. In that case, `self` may not be fully + /// populated. + /// + /// If `Element` is not bitwise copyable, then this function must be called on an + /// entirely uninitialized buffer. + /// + /// - Returns: A pair of values `(count, end)`, where `count` is the number of items that were + /// successfully initialized, and `end` is the index into `items` after the last copied item. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + internal func _initializePrefix< + C: Container & ~Copyable & ~Escapable + >( + copying items: borrowing C + ) -> (copied: Int, end: C.Index) { + var target = unsafe self + var i = items.startIndex + while true { + let start = i + let span = items.nextSpan(after: &i) + if span.isEmpty { break } + guard span.count <= target.count else { + return (self.count - target.count, start) + } + unsafe target._initializeAndDropPrefix(copying: span) + } + return (self.count - target.count, i) + } +} diff --git a/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift b/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift new file mode 100644 index 000000000..f121d41a0 --- /dev/null +++ b/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift @@ -0,0 +1,110 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import _CollectionsTestSupport +import Future + +class StaccatoContainerTests: CollectionTestCase { + func checkStriding( + _ container: borrowing C, + spanCounts: [Int], + file: StaticString = #file, + line: UInt = #line + ) { + let entry = context.push("checkStriding", file: file, line: line) + defer { context.pop(entry) } + + var i = container.startIndex + var j = 0 + var seen = 0 + while true { + let span = container.nextSpan(after: &i) + if i == container.endIndex { + expectLessThanOrEqual(span.count, spanCounts[j]) + expectEqual(span.count, container.count - seen) + break + } + expectEqual(span.count, spanCounts[j]) + seen += span.count + j += 1 + if j == spanCounts.count { j = 0 } + } + } + + func test_Basic1() { + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< 10), + spanCounts: [1]) + checkContainer(items, expectedContents: 0 ..< 10) + } + + func test_Basic2() { + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< 10), + spanCounts: [3]) + checkContainer(items, expectedContents: 0 ..< 10) + } + + func test_Basic3() { + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< 13), + spanCounts: [1, 2]) + checkContainer(items, expectedContents: 0 ..< 13) + } + + func test_SingleSpec() { + withEvery("c", in: [0, 10, 20]) { c in + withEvery("spanCount", in: 1 ... 20) { spanCount in + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< c), + spanCounts: [spanCount]) + + checkStriding(items, spanCounts: [spanCount]) + checkContainer(items, expectedContents: 0 ..< c) + } + } + } + + func test_DoubleSpec() { + withEvery("c", in: [0, 10, 20]) { c in + withEvery("spanCount1", in: 1 ... 10) { spanCount1 in + withEvery("spanCount2", in: 1 ... 10) { spanCount2 in + let spanCounts = [spanCount1, spanCount2] + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< c), + spanCounts: spanCounts) + + checkStriding(items, spanCounts: spanCounts) + checkContainer(items, expectedContents: 0 ..< c) + } + } + } + } + + func test_TripleSpec() { + withEvery("c", in: [0, 10, 20]) { c in + withEvery("spanCount1", in: 1 ... 5) { spanCount1 in + withEvery("spanCount2", in: 1 ... 5) { spanCount2 in + withEvery("spanCount3", in: 1 ... 5) { spanCount3 in + let spanCounts = [spanCount1, spanCount2, spanCount3] + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< c), + spanCounts: spanCounts) + + checkStriding(items, spanCounts: spanCounts) + checkContainer(items, expectedContents: 0 ..< c) + } + } + } + } + } +} diff --git a/Tests/FutureTests/RepeatingContainerTests.swift b/Tests/FutureTests/RepeatingContainerTests.swift index b8de34219..4e0ec00d3 100644 --- a/Tests/FutureTests/RepeatingContainerTests.swift +++ b/Tests/FutureTests/RepeatingContainerTests.swift @@ -9,6 +9,7 @@ import XCTest import _CollectionsTestSupport import Future +#if false @available(SwiftStdlib 6.0, *) class RepeatingContainerTests: CollectionTestCase { func test_10() { @@ -21,3 +22,4 @@ class RepeatingContainerTests: CollectionTestCase { } } } +#endif diff --git a/Tests/FutureTests/RigidArrayTests.swift b/Tests/FutureTests/RigidArrayTests.swift index 04b39f330..60d1b3b3d 100644 --- a/Tests/FutureTests/RigidArrayTests.swift +++ b/Tests/FutureTests/RigidArrayTests.swift @@ -144,6 +144,47 @@ class RigidArrayTests: CollectionTestCase { } } + func test_init_copying_Collection() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let a = RigidArray(copying: (0 ..< c).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + expectTrue(a.isFull) + for i in 0 ..< c { + expectEqual(a[i].payload, i) + } + } + } + } + + func test_init_copying_Container() { + withEvery("c", in: [0, 10, 100]) { c in + withEvery("spanCounts", in: [ + [1], + [3, 5, 7], + [10, 3], + ] as [[Int]]) { spanCounts in + withLifetimeTracking { tracker in + let additions = StaccatoContainer( + contents: RigidArray(copying: (0 ..< c).map { tracker.instance(for: $0) }), + spanCounts: spanCounts) + + let array = RigidArray(copying: additions) + expectEqual(tracker.instances, c) + expectEqual(array.capacity, c) + expectEqual(array.count, c) + for i in 0 ..< c { + expectEqual(array[i].payload, i) + } + } + } + } + } + func test_span() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withLifetimeTracking { tracker in @@ -325,7 +366,7 @@ class RigidArrayTests: CollectionTestCase { } } - func test_resize_to() { + func test_setCapacity() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery( "newCapacity", @@ -333,7 +374,7 @@ class RigidArrayTests: CollectionTestCase { ) { newCapacity in withLifetimeTracking { tracker in var a = tracker.rigidArray(layout: layout) - a.resize(to: newCapacity) + a.setCapacity(newCapacity) expectEqual(a.count, layout.count) expectEqual(a.capacity, newCapacity) expectEqual(tracker.instances, layout.count) @@ -347,7 +388,9 @@ class RigidArrayTests: CollectionTestCase { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery( "newCapacity", - in: [layout.capacity, layout.count, layout.count + 1, layout.capacity + 1] as Set + in: [ + 0, layout.count - 1, layout.count, layout.count + 1, layout.capacity, layout.capacity + 1 + ] as Set ) { newCapacity in withLifetimeTracking { tracker in var a = tracker.rigidArray(layout: layout) @@ -361,6 +404,39 @@ class RigidArrayTests: CollectionTestCase { } } + func test_copy() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let b = a.copy() + expectEqual(b.count, layout.count) + expectEqual(b.capacity, layout.capacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_copy_capacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [layout.count, layout.count + 1, layout.capacity, layout.capacity + 1] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let b = a.copy(capacity: newCapacity) + expectEqual(b.count, layout.count) + expectEqual(b.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + func test_removeAll() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withLifetimeTracking { tracker in @@ -510,17 +586,19 @@ class RigidArrayTests: CollectionTestCase { func test_append_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in - withLifetimeTracking { tracker in - var a = tracker.rigidArray(layout: layout) - let b = tracker.rigidArray( // FIXME: Use TestContainer with various span counts - layout: ArrayLayout( - capacity: layout.capacity - layout.count, - count: layout.capacity - layout.count), - using: { layout.count + $0 }) - a.append(copying: b) - expectTrue(a.isFull) - expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) - expectEqual(tracker.instances, layout.capacity) + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + + let addition = (layout.count ..< layout.capacity).map { tracker.instance(for: $0) } + let b = StaccatoContainer( + contents: RigidArray(copying: addition), + spanCounts: [spanCount]) + a.append(copying: b) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } } } } @@ -587,21 +665,24 @@ class RigidArrayTests: CollectionTestCase { func test_insert_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("i", in: 0 ... layout.count) { i in - withLifetimeTracking { tracker in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in - var expected = Array(0 ..< layout.count) - let addition = Array(layout.count ..< layout.capacity) - expected.insert(contentsOf: addition, at: i) + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.capacity) + expected.insert(contentsOf: addition, at: i) - var a = tracker.rigidArray(layout: layout) - let rigidAddition = RigidArray(count: addition.count) { - tracker.instance(for: addition[$0]) - } - // FIXME: Use TestContainer with various span counts - a.insert(copying: rigidAddition, at: i) + var a = tracker.rigidArray(layout: layout) + let rigidAddition = StaccatoContainer( + contents: RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + }, + spanCounts: [Swift.max(spanCount, 1)]) + a.insert(copying: rigidAddition, at: i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) - expectEqual(tracker.instances, layout.capacity) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } } } } @@ -618,7 +699,6 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) let trackedAddition = addition.map { tracker.instance(for: $0) } - // FIXME: Use TestContainer with various span counts a.replaceSubrange(range, with: trackedAddition) expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) @@ -628,4 +708,50 @@ class RigidArrayTests: CollectionTestCase { } } } + + func test_replaceSubrange_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = RigidArray(copying: addition.map { tracker.instance(for: $0) }) + a.replaceSubrange(range, copying: trackedAddition.span) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = StaccatoContainer( + contents: RigidArray(copying: addition.map { tracker.instance(for: $0) }), + spanCounts: [spanCount]) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } } diff --git a/Tests/FutureTests/SpanTests/RawSpanTests.swift b/Tests/FutureTests/SpanTests/RawSpanTests.swift index 0abee0d82..7b87b555e 100644 --- a/Tests/FutureTests/SpanTests/RawSpanTests.swift +++ b/Tests/FutureTests/SpanTests/RawSpanTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import Future +import Future @available(macOS 9999, *) final class RawSpanTests: XCTestCase { diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift index cc2453778..1e4b58b2c 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift @@ -38,6 +38,7 @@ extension Container where Self: ~Copyable & ~Escapable { } @available(SwiftCompatibilitySpan 5.0, *) +@inlinable public func checkContainer< C: Container & ~Copyable & ~Escapable, Expected: Sequence @@ -55,6 +56,7 @@ public func checkContainer< } @available(SwiftCompatibilitySpan 5.0, *) +@inlinable public func checkContainer< C: Container & ~Copyable & ~Escapable, Expected: Sequence @@ -116,7 +118,7 @@ public func checkContainer< checkComparable(allIndices, oracle: { .comparing($0, $1) }) } - withEveryRange("range", in: 0 ..< allIndices.count - 1) { range in + withEveryRange("range", in: 0 ..< expectedContents.count) { range in let i = range.lowerBound let j = range.upperBound @@ -133,9 +135,7 @@ public func checkContainer< } // Check `formIndex(_,offsetBy:limitedBy:)` - let limits = - Set([0, allIndices.count - 1, allIndices.count / 2]) - .sorted() + let limits = Set([0, allIndices.count - 1, allIndices.count / 2]).sorted() withEvery("limit", in: limits) { limit in withEvery("i", in: 0 ..< allIndices.count) { i in let max = allIndices.count - i + (limit >= i ? 2 : 0) @@ -170,13 +170,13 @@ public func checkContainer< pos += span.count if span.isEmpty { expectEqual(origIndex, container.endIndex) - expectEqual(index, origIndex, "nextCount is not expected to move the end index") + expectEqual(index, origIndex, "nextSpan is not expected to move the end index") break } expectGreaterThan( - index, origIndex, "nextCount does not monotonically increase the index") + index, origIndex, "nextSpan does not monotonically increase the index") expectEqual( - index, allIndices[pos], "nextCount does not increase the index by the size of the span") + index, allIndices[pos], "nextSpan does not advance the index by the size of the returned span") r.append((origPos ..< pos, origIndex ..< index)) } return r diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift similarity index 61% rename from Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift rename to Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift index 825e7f9a7..eb2f3ca16 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/TestContainer.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift @@ -1,56 +1,60 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift.org open source project +// This source file is part of the Swift Collections open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// 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 // //===----------------------------------------------------------------------===// import Future -public struct TestContainer: ~Copyable { +/// A container type with user-defined contents and storage chunks. Useful for testing. +public struct StaccatoContainer: ~Copyable { internal let _contents: RigidArray internal let _spanCounts: [Int] + internal let _modulus: Int public init(contents: consuming RigidArray, spanCounts: [Int]) { - precondition(spanCounts.allSatisfy { $0 > 0 }) + // FIXME: Make this take an arbitrary consumable container + precondition(!spanCounts.isEmpty && spanCounts.allSatisfy { $0 > 0 }) self._contents = contents self._spanCounts = spanCounts + self._modulus = spanCounts.reduce(into: 0, { $0 += $1 }) } } -extension TestContainer where Element: ~Copyable { - public struct Index: Comparable { - internal var _offset: Int +public struct _StaccatoContainerIndex: Comparable { + internal var _offset: Int - internal init(_offset: Int) { - self._offset = _offset - } - - public static func == (left: Self, right: Self) -> Bool { left._offset == right._offset } - public static func < (left: Self, right: Self) -> Bool { left._offset < right._offset } + internal init(_offset: Int) { + self._offset = _offset } + public static func == (left: Self, right: Self) -> Bool { left._offset == right._offset } + public static func < (left: Self, right: Self) -> Bool { left._offset < right._offset } +} + +extension StaccatoContainer where Element: ~Copyable { + public typealias Index = _StaccatoContainerIndex // rdar://150240032 + func _spanCoordinates(atOffset offset: Int) -> (spanIndex: Int, spanOffset: Int) { precondition(offset >= 0 && offset <= _contents.count) - let modulus = _spanCounts.reduce(into: 0, { $0 += $1 }) - var remainder = offset % modulus - for i in 0 ..< _spanCounts.count { + var remainder = offset % _modulus + var i = 0 + while i < _spanCounts.count { let c = _spanCounts[i] - if remainder < c { - return (i, remainder) - } + if remainder < c { break } remainder -= c + i += 1 } - return (_spanCounts.count, 0) + return (i, remainder) } } -extension TestContainer where Element: ~Copyable { +extension StaccatoContainer where Element: ~Copyable { public var isEmpty: Bool { _contents.isEmpty } public var count: Int { _contents.count } public var startIndex: Index { Index(_offset: 0) } @@ -77,7 +81,7 @@ extension TestContainer where Element: ~Copyable { } @available(SwiftCompatibilitySpan 5.0, *) -extension TestContainer: Container where Element: ~Copyable { +extension StaccatoContainer: Container where Element: ~Copyable { public func formIndex(_ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index) { precondition(i >= startIndex && i <= endIndex) _contents.formIndex(&i._offset, offsetBy: &distance, limitedBy: limit._offset) @@ -85,18 +89,19 @@ extension TestContainer: Container where Element: ~Copyable { @lifetime(borrow self) public func borrowElement(at index: Index) -> Future.Borrow { - precondition(index >= startIndex && index < endIndex) + precondition(index >= startIndex && index < endIndex, "Index out of bounds") return _contents.borrowElement(at: index._offset) } @lifetime(borrow self) public func nextSpan(after index: inout Index) -> Span { precondition(index >= startIndex && index <= endIndex) - let (spanIndex, spanOffset) = _spanCoordinates(atOffset: index._offset) let span = _contents.span - guard spanIndex < _spanCounts.count else { return span._extracting(last: 0) } + let (spanIndex, spanOffset) = _spanCoordinates(atOffset: index._offset) let c = _spanCounts[spanIndex] - spanOffset - index._offset += c - return span._extracting(index._offset ..< index._offset + c) + let startOffset = index._offset + let endOffset = Swift.min(startOffset + c, span.count) + index._offset = endOffset + return span._extracting(startOffset ..< endOffset) } } From fea1d24e7be3284646858ef7bc9950f685adaa61 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 30 Apr 2025 19:41:15 -0700 Subject: [PATCH 192/195] Flesh out DynamicArray Also: - Tweak RigidArray. - Define some default implementations for Container requirements. - Beef up ContiguousContainer. - Add support for testing contiguous access on minimal collections. --- Sources/Future/Arrays/DynamicArray.swift | 893 +++++++++++++++++- Sources/Future/Arrays/RigidArray.swift | 805 +++++++++++++--- Sources/Future/Containers/Container.swift | 149 +++ .../Containers/ContiguousContainer.swift | 43 +- .../Containers/RandomAccessContainer.swift | 13 + ...afeBufferPointer+ContiguousContainer.swift | 31 + ...bleBufferPointer+ContiguousContainer.swift | 32 + ...r.swift => Span+ContiguousContainer.swift} | 8 + .../Span/UnsafeBufferPointer+Additions.swift | 64 +- Tests/DequeTests/RigidDequeTests.swift | 10 +- .../FutureTests/ArrayTests/ArrayLayout.swift | 96 ++ .../ArrayTests/DynamicArrayTests.swift | 804 ++++++++++++++++ .../{ => ArrayTests}/RigidArrayTests.swift | 217 ++--- .../FutureTests/ContiguousStorageTests.swift | 11 + Tests/FutureTests/DynamicArrayTests.swift | 212 ----- .../FutureTests/RepeatingContainerTests.swift | 10 +- .../MinimalBidirectionalCollection.swift | 16 +- .../MinimalTypes/MinimalCollection.swift | 16 +- ...MinimalMutableRandomAccessCollection.swift | 16 +- ...ngeReplaceableRandomAccessCollection.swift | 16 +- .../MinimalRandomAccessCollection.swift | 16 +- ...ngeReplaceableRandomAccessCollection.swift | 16 +- .../MinimalTypes/MinimalSequence.swift | 12 +- .../MinimalTypes/_MinimalCollectionCore.swift | 12 +- 24 files changed, 2929 insertions(+), 589 deletions(-) create mode 100644 Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift create mode 100644 Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift rename Sources/Future/Span/{Span+Container.swift => Span+ContiguousContainer.swift} (94%) create mode 100644 Tests/FutureTests/ArrayTests/ArrayLayout.swift create mode 100644 Tests/FutureTests/ArrayTests/DynamicArrayTests.swift rename Tests/FutureTests/{ => ArrayTests}/RigidArrayTests.swift (82%) delete mode 100644 Tests/FutureTests/DynamicArrayTests.swift diff --git a/Sources/Future/Arrays/DynamicArray.swift b/Sources/Future/Arrays/DynamicArray.swift index 2c3b03a79..e7e3000a4 100644 --- a/Sources/Future/Arrays/DynamicArray.swift +++ b/Sources/Future/Arrays/DynamicArray.swift @@ -20,29 +20,100 @@ public struct DynamicArray: ~Copyable { public init() { _storage = .init(capacity: 0) } +} + +extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} + +//MARK: - Initializers + +extension DynamicArray where Element: ~Copyable { + @inlinable + public init(consuming storage: consuming RigidArray) { + self._storage = storage + } @inlinable - public init(minimumCapacity: Int) { - _storage = .init(capacity: minimumCapacity) + public init(capacity: Int) { + _storage = .init(capacity: capacity) } + // FIXME: Remove in favor of an OutputSpan-based initializer @inlinable public init(count: Int, initializedWith generator: (Int) -> Element) { - _storage = .init(count: count, initializedWith: generator) + self.init(capacity: count, count: count, initializedWith: generator) + } + + // FIXME: Remove in favor of an OutputSpan-based initializer + @inlinable + public init( + capacity: Int, count: Int, initializedWith generator: (Int) -> Element + ) { + _storage = .init( + capacity: capacity, count: count, initializedWith: generator) } } -extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} +extension DynamicArray /*where Element: Copyable*/ { + /// Creates a new array containing the specified number of a single, + /// repeated value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the value passed in the + /// `repeating` parameter. `count` must be zero or greater. + public init(repeating repeatedValue: Element, count: Int) { + self.init(consuming: RigidArray(repeating: repeatedValue, count: count)) + } +} + +extension DynamicArray /*where Element: Copyable*/ { + @_alwaysEmitIntoClient + @inline(__always) + public init(capacity: Int? = nil, copying contents: some Sequence) { + self.init(capacity: capacity ?? 0) + self.append(copying: contents) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & ~Copyable & ~Escapable>( + capacity: Int? = nil, + copying contents: borrowing C + ) { + self.init(consuming: RigidArray(capacity: capacity, copying: contents)) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Sequence>( + capacity: Int? = nil, + copying contents: C + ) { + self.init(consuming: RigidArray(capacity: capacity, copying: contents)) + } +} + +//MARK: - Basics extension DynamicArray where Element: ~Copyable { @inlinable + @inline(__always) public var capacity: Int { _storage.capacity } + + @inlinable + @inline(__always) + public var freeCapacity: Int { capacity - count } } +//MARK: - Span creation + extension DynamicArray where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) public var span: Span { @lifetime(borrow self) + @inlinable get { _storage.span } @@ -60,109 +131,857 @@ extension DynamicArray where Element: ~Copyable { //MARK: RandomAccessContainer conformance -@available(SwiftCompatibilitySpan 5.0, *) -extension DynamicArray: RandomAccessContainer where Element: ~Copyable { - @lifetime(borrow self) - public func nextSpan(after index: inout Int) -> Span { - _storage.nextSpan(after: &index) - } - - @lifetime(borrow self) - public func previousSpan(before index: inout Int) -> Span { - _storage.previousSpan(before: &index) - } -} - extension DynamicArray where Element: ~Copyable { public typealias Index = Int @inlinable + @inline(__always) public var isEmpty: Bool { _storage.isEmpty } @inlinable + @inline(__always) public var count: Int { _storage.count } @inlinable - public var startIndex: Int { 0 } + @inline(__always) + public var startIndex: Int { _storage.startIndex } @inlinable + @inline(__always) public var endIndex: Int { _storage.count } @inlinable + @inline(__always) + public var indices: Range { _storage.indices} + + @inlinable + @inline(__always) @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { _storage.borrowElement(at: index) } } +@available(SwiftCompatibilitySpan 5.0, *) +extension DynamicArray: RandomAccessContainer where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _storage.nextSpan(after: &index) + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.previousSpan(before: &index) + } +} + // MARK: - MutableContainer conformance -extension DynamicArray: MutableContainer where Element: ~Copyable { +extension DynamicArray where Element: ~Copyable { @inlinable @lifetime(&self) public mutating func mutateElement(at index: Int) -> Inout { _storage.mutateElement(at: index) } + @inlinable + @lifetime(&self) + public mutating func swapAt(_ i: Int, _ j: Int) { + _storage.swapAt(i, j) + } +} + +extension DynamicArray: MutableContainer where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) @lifetime(&self) public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { _storage.nextMutableSpan(after: &index) } +} +//MARK: Unsafe access + +extension DynamicArray where Element: ~Copyable { + // FIXME: Replace this with an OutputSpan-based mutator @inlinable - @lifetime(&self) - public mutating func swapAt(_ i: Int, _ j: Int) { - _storage.swapAt(i, j) + public mutating func withUnsafeMutableBufferPointer( + _ body: (UnsafeMutableBufferPointer, inout Int) throws(E) -> R + ) throws(E) -> R { + unsafe try _storage.withUnsafeMutableBufferPointer(body) } } -// MARK: - Range replacement operations +//MARK: - Resizing + +@inlinable +@_transparent +internal func _growDynamicArrayCapacity(_ capacity: Int) -> Int { + 2 * capacity +} extension DynamicArray where Element: ~Copyable { + @inlinable @inline(never) + public mutating func reallocate(capacity: Int) { + _storage.reallocate(capacity: capacity) + } + + @inlinable @inline(never) + public mutating func reserveCapacity(_ n: Int) { + _storage.reserveCapacity(n) + } + @inlinable - @discardableResult - public mutating func remove(at index: Int) -> Element { - _storage.remove(at: index) + @_transparent + public mutating func _ensureFreeCapacity(_ freeCapacity: Int) { + guard _storage.freeCapacity < freeCapacity else { return } + _ensureFreeCapacitySlow(freeCapacity) + } + + @inlinable + internal mutating func _ensureFreeCapacitySlow(_ freeCapacity: Int) { + let newCapacity = Swift.max( + count + freeCapacity, + _growDynamicArrayCapacity(capacity)) + reallocate(capacity: newCapacity) } } +//MARK: - Removal operations + extension DynamicArray where Element: ~Copyable { + /// Removes all elements from the array, preserving its allocated capacity. + /// + /// - Complexity: O(*n*), where *n* is the original count of the array. @inlinable - public mutating func reserveCapacity(_ n: Int) { - _storage.reserveCapacity(n) + @inline(__always) + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + if keepCapacity { + _storage.removeAll() + } else { + _storage = RigidArray(capacity: 0) + } } + /// Removes and returns the last element of the array. + /// + /// The array must not be empty. + /// + /// - Returns: The last element of the original array. + /// + /// - Complexity: O(1) @inlinable - internal static func _grow(_ capacity: Int) -> Int { - 2 * capacity + @inline(__always) + @discardableResult + public mutating func removeLast() -> Element { + _storage.removeLast() } + /// Removes the specified number of elements from the end of the array. + /// + /// Attempting to remove more elements than exist in the array triggers a + /// runtime error. + /// + /// - Parameter k: The number of elements to remove from the array. + /// `k` must be greater than or equal to zero and must not exceed + /// the count of the array. + /// + /// - Complexity: O(`k`) @inlinable - public mutating func _ensureFreeCapacity(_ minimumCapacity: Int) { - guard _storage.freeCapacity < minimumCapacity else { return } - reserveCapacity(max(count + minimumCapacity, Self._grow(capacity))) + public mutating func removeLast(_ k: Int) { + _storage.removeLast(k) + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// gap. + /// + /// - Parameter i: The position of the element to remove. `index` must be + /// a valid index of the array that is not equal to the end index. + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @inline(__always) + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } + + /// Removes the specified subrange of elements from the array. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _storage.removeSubrange(bounds) + } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + // FIXME: Remove this in favor of a standard algorithm. + removeSubrange(bounds.relative(to: indices)) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a container that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the array. + /// + /// - Complexity: O(`count`) + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func removeAll( + where shouldBeRemoved: (borrowing Element) throws(E) -> Bool + ) throws(E) { + // FIXME: Remove this in favor of a standard algorithm. + let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) + removeSubrange(suffixStart...) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Removes and returns the last element of the array, if there is one. + /// + /// - Returns: The last element of the array if the array is not empty; + /// otherwise, `nil`. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public mutating func popLast() -> Element? { + if isEmpty { return nil } + return removeLast() } } + +//MARK: - Insertion operations + extension DynamicArray where Element: ~Copyable { + /// Adds an element to the end of the array. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameter item: The element to append to the collection. + /// + /// - Complexity: O(1) when amortized over many invocations on the same array @inlinable public mutating func append(_ item: consuming Element) { _ensureFreeCapacity(1) _storage.append(item) } +} + +extension DynamicArray { + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeBufferPointer + ) { + _ensureFreeCapacity(newElements.count) + unsafe _storage.append(copying: newElements) + } + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.append(copying: UnsafeBufferPointer(newElements)) + } + + /// Copies the elements of a span to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A span whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func append(copying newElements: Span) { + _ensureFreeCapacity(newElements.count) + _storage.append(copying: newElements) + } + + /// Copies the elements of a sequence to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. This + /// reallocation can happen multiple times. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`, when + /// amortized over many invocations over the same array. + @_alwaysEmitIntoClient + public mutating func append(copying newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + _ensureFreeCapacity(buffer.count) + unsafe _storage.append(copying: buffer) + return + } + if done != nil { return } + + _ensureFreeCapacity(newElements.underestimatedCount) + var it = _storage._append(prefixOf: newElements) + while let item = it.next() { + _ensureFreeCapacity(1) + _storage.append(item) + } + } + + @available(SwiftCompatibilitySpan 5.0, *) + public mutating func _appendContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + var i = newElements.startIndex + while true { + let span = newElements.nextSpan(after: &i) + if span.isEmpty { break } + self.append(copying: span) + } + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`), when amortized over many invocations + /// over the same array. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func append< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`), when amortized over many invocations + /// over the same array. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + public mutating func append< + C: Container & Sequence + >( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Inserts a new element into the array at the specified position. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this reallocates storage to extend its capacity. + /// + /// The new element is inserted before the element currently at the specified + /// index. If you pass the array's `endIndex` as the `index` parameter, then + /// the new element is appended to the container. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// - Parameter item: The new element to insert into the array. + /// - Parameter i: The position at which to insert the new element. + /// `index` must be a valid index in the array. + /// + /// - Complexity: O(`count`) @inlinable public mutating func insert(_ item: consuming Element, at index: Int) { precondition(index >= 0 && index <= count) + // FIXME: Avoiding moving the subsequent elements twice. _ensureFreeCapacity(1) _storage.insert(item, at: index) } +} +extension DynamicArray { + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. @inlinable - public mutating func append(contentsOf items: some Sequence) { - for item in items { - append(item) - } + public mutating func insert( + copying newElements: UnsafeBufferPointer, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + unsafe _storage.insert(copying: newElements, at: index) + } + + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: UnsafeMutableBufferPointer, + at index: Int + ) { + unsafe self.insert(copying: UnsafeBufferPointer(newElements), at: index) + } + + /// Copies the elements of a span into this array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + public mutating func insert( + copying newElements: Span, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + _storage.insert(copying: newElements, at: index) + } + + /// Copies the elements of a collection into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved + /// to make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: some Collection, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let newCount = newElements.count + _ensureFreeCapacity(newCount) + _storage._insertCollection( + at: index, copying: newElements, newCount: newCount) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + internal mutating func _insertContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let newCount = newElements.count + _ensureFreeCapacity(newCount) + _storage._insertContainer( + at: index, copying: newElements, newCount: newCount) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer(copying: newElements, at: index) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & Collection + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer(copying: newElements, at: index) + } +} + +//MARK: - Range replacement + +extension DynamicArray { + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeBufferPointer + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + unsafe _storage.replaceSubrange(subrange, copying: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.replaceSubrange( + subrange, copying: UnsafeBufferPointer(newElements)) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given span. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length span as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: Span + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + _storage.replaceSubrange(subrange, copying: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: __owned some Collection + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let c = newElements.count + _ensureFreeCapacity(c) + _storage._replaceSubrange( + subrange, copyingCollection: newElements, newCount: c) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + public mutating func _replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copyingContainer newElements: borrowing C + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let c = newElements.count + _ensureFreeCapacity(c) + _storage._replaceSubrange( + subrange, copyingContainer: newElements, newCount: c) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange(subrange, copyingContainer: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & Collection + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange(subrange, copyingContainer: newElements) } } diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift index 7a505fc26..bb7dd9d2d 100644 --- a/Sources/Future/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -45,14 +45,28 @@ extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} //MARK: - Initializers extension RigidArray where Element: ~Copyable { + // FIXME: Remove in favor of an OutputSpan-based initializer @inlinable - public init(count: Int, initializedWith generator: (Int) -> Element) { - unsafe _storage = .allocate(capacity: count) + public init( + capacity: Int, + count: Int, + initializedWith generator: (Int) -> Element + ) { + precondition( + count >= 0 && count <= capacity, + "RigidArray capacity overflow") + unsafe _storage = .allocate(capacity: capacity) for i in 0 ..< count { unsafe _storage.initializeElement(at: i, to: generator(i)) } _count = count } + + // FIXME: Remove in favor of an OutputSpan-based initializer + @inlinable + public init(count: Int, initializedWith generator: (Int) -> Element) { + self.init(capacity: count, count: count, initializedWith: generator) + } } extension RigidArray /*where Element: Copyable*/ { @@ -73,9 +87,33 @@ extension RigidArray /*where Element: Copyable*/ { extension RigidArray /*where Element: Copyable*/ { @_alwaysEmitIntoClient @inline(__always) - public init(capacity: Int? = nil, copying contents: some Collection) { + public init( + capacity: Int, + copying contents: some Sequence + ) { + self.init(capacity: capacity) + self.append(copying: contents) + } + + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Sequence>( + capacity: Int, + copying contents: C + ) { + self.init(capacity: capacity) + self.append(copying: contents) + } + + @_alwaysEmitIntoClient + @inline(__always) + public init( + capacity: Int? = nil, + copying contents: some Collection + ) { self.init(capacity: capacity ?? contents.count) - self.append(contentsOf: contents) + self.append(copying: contents) } @available(SwiftCompatibilitySpan 5.0, *) @@ -162,7 +200,9 @@ extension RigidArray where Element: ~Copyable { @available(SwiftCompatibilitySpan 5.0, *) @inlinable @lifetime(&self) - internal mutating func _mutableSpan(in range: Range) -> MutableSpan { + internal mutating func _mutableSpan( + in range: Range + ) -> MutableSpan { let result = unsafe MutableSpan(_unsafeElements: _items.extracting(range)) return unsafe _overrideLifetime(result, mutating: &self) } @@ -184,39 +224,29 @@ extension RigidArray where Element: ~Copyable { } } -//MARK: - RandomAccessContainer conformance - -@available(SwiftCompatibilitySpan 5.0, *) -extension RigidArray: RandomAccessContainer where Element: ~Copyable { - @inlinable - @lifetime(borrow self) - public func nextSpan(after index: inout Int) -> Span { - _span(in: _contiguousSubrange(following: &index)) - } - - @inlinable - @lifetime(borrow self) - public func previousSpan(before index: inout Int) -> Span { - _span(in: _contiguousSubrange(preceding: &index)) - } -} +//MARK: - RandomAccessContainer and MutableContainer conformance extension RigidArray where Element: ~Copyable { public typealias Index = Int @inlinable + @inline(__always) public var isEmpty: Bool { count == 0 } @inlinable + @inline(__always) public var count: Int { _count } @inlinable + @inline(__always) public var startIndex: Int { 0 } @inlinable + @inline(__always) public var endIndex: Int { count } @inlinable + @inline(__always) public var indices: Range { unsafe Range(uncheckedBounds: (0, count)) } @inlinable @@ -228,21 +258,20 @@ extension RigidArray where Element: ~Copyable { borrowing: self ) } +} +@available(SwiftCompatibilitySpan 5.0, *) +extension RigidArray: RandomAccessContainer where Element: ~Copyable { @inlinable - public mutating func swapAt(_ i: Int, _ j: Int) { - precondition(i >= 0 && i < _count && j >= 0 && j < _count, "Index out of bounds") - unsafe _items.swapAt(i, j) + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _span(in: _contiguousSubrange(following: &index)) } -} - -//MARK: - MutableContainer conformance -@available(SwiftCompatibilitySpan 5.0, *) -extension RigidArray: MutableContainer where Element: ~Copyable { - @lifetime(&self) - public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { - _mutableSpan(in: _contiguousSubrange(following: &index)) + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _span(in: _contiguousSubrange(preceding: &index)) } } @@ -256,6 +285,24 @@ extension RigidArray where Element: ~Copyable { mutating: &self ) } + + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition( + i >= 0 && i < _count && j >= 0 && j < _count, + "Index out of bounds") + unsafe _items.swapAt(i, j) + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension RigidArray: MutableContainer where Element: ~Copyable { + @lifetime(&self) + public mutating func nextMutableSpan( + after index: inout Int + ) -> MutableSpan { + _mutableSpan(in: _contiguousSubrange(following: &index)) + } } //MARK: Unsafe access @@ -275,10 +322,11 @@ extension RigidArray where Element: ~Copyable { extension RigidArray where Element: ~Copyable { @inlinable - public mutating func setCapacity(_ newCapacity: Int) { + public mutating func reallocate(capacity newCapacity: Int) { precondition(newCapacity >= count, "RigidArray capacity overflow") guard newCapacity != capacity else { return } - let newStorage: UnsafeMutableBufferPointer = .allocate(capacity: newCapacity) + let newStorage: UnsafeMutableBufferPointer = .allocate( + capacity: newCapacity) let i = unsafe newStorage.moveInitialize(fromContentsOf: self._items) assert(i == count) unsafe _storage.deallocate() @@ -288,7 +336,7 @@ extension RigidArray where Element: ~Copyable { @inlinable public mutating func reserveCapacity(_ n: Int) { guard capacity < n else { return } - setCapacity(n) + reallocate(capacity: n) } } @@ -320,8 +368,10 @@ extension RigidArray where Element: ~Copyable { at index: Int, count: Int ) { guard count > 0 else { return } - let source = unsafe _storage.extracting(Range(uncheckedBounds: (index + count, _count))) - let target = unsafe _storage.extracting(Range(uncheckedBounds: (index, index + source.count))) + let source = unsafe _storage.extracting( + Range(uncheckedBounds: (index + count, _count))) + let target = unsafe _storage.extracting( + Range(uncheckedBounds: (index, index + source.count))) let i = unsafe target.moveInitialize(fromContentsOf: source) assert(i == target.endIndex) } @@ -333,11 +383,14 @@ extension RigidArray where Element: ~Copyable { assert(index >= 0 && index <= _count) assert(count <= freeCapacity) guard count > 0 else { return unsafe _storage.extracting(index ..< index) } - let source = unsafe _storage.extracting(Range(uncheckedBounds: (index, _count))) - let target = unsafe _storage.extracting(Range(uncheckedBounds: (index + count, _count + count))) + let source = unsafe _storage.extracting( + Range(uncheckedBounds: (index, _count))) + let target = unsafe _storage.extracting( + Range(uncheckedBounds: (index + count, _count + count))) let i = unsafe target.moveInitialize(fromContentsOf: source) assert(i == target.count) - return unsafe _storage.extracting(Range(uncheckedBounds: (index, index + count))) + return unsafe _storage.extracting( + Range(uncheckedBounds: (index, index + count))) } } @@ -371,17 +424,23 @@ extension RigidArray where Element: ~Copyable { /// Removes the specified number of elements from the end of the array. /// - /// Attempting to remove more elements than exist in the array triggers a runtime error. + /// Attempting to remove more elements than exist in the array + /// triggers a runtime error. /// /// - Parameter k: The number of elements to remove from the array. - /// `k` must be greater than or equal to zero and must not exceed the count of the array. + /// `k` must be greater than or equal to zero and must not exceed + /// the count of the array. /// /// - Complexity: O(`k`) @inlinable public mutating func removeLast(_ k: Int) { if k == 0 { return } - precondition(k >= 0 && k <= _count, "Count of elements to remove is out of bounds") - unsafe _storage.extracting(Range(uncheckedBounds: (_count - k, _count))).deinitialize() + precondition( + k >= 0 && k <= _count, + "Count of elements to remove is out of bounds") + unsafe _storage.extracting( + Range(uncheckedBounds: (_count - k, _count)) + ).deinitialize() _count &-= k } @@ -407,8 +466,8 @@ extension RigidArray where Element: ~Copyable { /// Removes the specified subrange of elements from the array. /// - /// All the elements following the specified subrange are moved to close the resulting - /// gap. + /// All the elements following the specified subrange are moved to close the + /// resulting gap. /// /// - Parameter bounds: The subrange of the array to remove. The bounds /// of the range must be valid indices of the array. @@ -424,6 +483,18 @@ extension RigidArray where Element: ~Copyable { _closeGap(at: bounds.lowerBound, count: bounds.count) _count -= bounds.count } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds of the + /// range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + // FIXME: Remove this in favor of a standard algorithm. + removeSubrange(bounds.relative(to: indices)) + } } extension RigidArray where Element: ~Copyable { @@ -442,41 +513,34 @@ extension RigidArray where Element: ~Copyable { public mutating func removeAll( where shouldBeRemoved: (borrowing Element) throws(E) -> Bool ) throws(E) { + // FIXME: Remove this in favor of a standard algorithm. let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) removeSubrange(suffixStart...) } +} - /// Removes and returns the last element of the array. +extension RigidArray where Element: ~Copyable { + /// Removes and returns the last element of the array, if there is one. /// - /// - Returns: The last element of the array if the array is not empty; otherwise, `nil`. + /// - Returns: The last element of the array if the array is not empty; + /// otherwise, `nil`. /// /// - Complexity: O(1) @_alwaysEmitIntoClient public mutating func popLast() -> Element? { + // FIXME: Remove this in favor of a standard algorithm. if isEmpty { return nil } return removeLast() } - - /// Removes the specified subrange of elements from the array. - /// - /// - Parameter bounds: The subrange of the array to remove. The bounds - /// of the range must be valid indices of the array. - /// - /// - Complexity: O(`count`) - @_alwaysEmitIntoClient - public mutating func removeSubrange(_ bounds: some RangeExpression) { - removeSubrange(bounds.relative(to: indices)) - } } //MARK: - Insertion operations - extension RigidArray where Element: ~Copyable { /// Adds an element to the end of the array. /// - /// If the array does not have sufficient capacity to hold any more elements, then this - /// triggers a runtime error. + /// If the array does not have sufficient capacity to hold any more elements, + /// then this triggers a runtime error. /// /// - Parameter item: The element to append to the collection. /// @@ -490,48 +554,156 @@ extension RigidArray where Element: ~Copyable { } extension RigidArray { + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) @_alwaysEmitIntoClient - public mutating func append(contentsOf items: some Sequence) { - var (it, c) = unsafe items._copyContents(initializing: _freeSpace) - precondition(it.next() == nil, "RigidArray capacity overflow") - _count += c + public mutating func append( + copying newElements: UnsafeBufferPointer + ) { + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + guard newElements.count > 0 else { return } + unsafe _freeSpace.baseAddress.unsafelyUnwrapped.initialize( + from: newElements.baseAddress.unsafelyUnwrapped, count: newElements.count) + _count &+= newElements.count + } + + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) + @_alwaysEmitIntoClient + public mutating func append( + copying items: UnsafeMutableBufferPointer + ) { + unsafe self.append(copying: UnsafeBufferPointer(items)) } - // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. + /// Copies the elements of a span to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// span, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A span whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) @available(SwiftCompatibilitySpan 5.0, *) @_alwaysEmitIntoClient public mutating func append(copying items: Span) { - precondition(items.count <= freeCapacity, "RigidArray capacity overflow") unsafe items.withUnsafeBufferPointer { source in - let c = unsafe source._copyContents(initializing: _freeSpace).1 - _count &+= c + unsafe self.append(copying: source) + } + } + + @_alwaysEmitIntoClient + @inline(__always) + internal mutating func _append>( + prefixOf items: S + ) -> S.Iterator { + let (it, c) = unsafe items._copyContents(initializing: _freeSpace) + _count += c + return it + } + + /// Copies the elements of a sequence to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// sequence, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`. + @_alwaysEmitIntoClient + public mutating func append(copying newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + unsafe self.append(copying: buffer) + return } + if done != nil { return } + + var it = self._append(prefixOf: newElements) + precondition(it.next() == nil, "RigidArray capacity overflow") + } + + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + internal mutating func _appendContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + let (copied, end) = unsafe _freeSpace._initializePrefix( + copying: newElements) + precondition(end == newElements.endIndex, "RigidArray capacity overflow") + _count += copied } - // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// container, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) @available(SwiftCompatibilitySpan 5.0, *) @_alwaysEmitIntoClient + @inline(__always) public mutating func append & ~Copyable & ~Escapable>( - copying items: borrowing C + copying newElements: borrowing C ) { - let (copied, end) = unsafe _freeSpace._initializePrefix(copying: items) - precondition(end == items.endIndex, "RigidArray capacity overflow") - _count += copied + _appendContainer(copying: newElements) + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// container, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func append< + C: Container & Sequence + >(copying newElements: C) { + _appendContainer(copying: newElements) } } extension RigidArray where Element: ~Copyable { /// Inserts a new element into the array at the specified position. /// - /// If the array does not have sufficient capacity to hold any more elements, then this - /// triggers a runtime error. + /// If the array does not have sufficient capacity to hold any more elements, + /// then this triggers a runtime error. /// - /// The new element is inserted before the element currently at the specified index. If you pass - /// the array's `endIndex` as the `index` parameter, then the new element is appended to the - /// collection. + /// The new element is inserted before the element currently at the specified + /// index. If you pass the array's `endIndex` as the `index` parameter, then + /// the new element is appended to the container. /// - /// All existing elements at or following the specified position are moved to make room for the - /// new item. + /// All existing elements at or following the specified position are moved to + /// make room for the new item. /// /// - Parameter item: The new element to insert into the array. /// - Parameter i: The position at which to insert the new element. @@ -554,93 +726,294 @@ extension RigidArray where Element: ~Copyable { } extension RigidArray { + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. @inlinable - public mutating func insert(contentsOf items: some Collection, at index: Int) { - precondition(index >= 0 && index <= _count, "Index out of bounds") - let c = items.count - precondition(c <= freeCapacity, "RigidArray capacity overflow") - let gap = unsafe _openGap(at: index, count: c) - var (it, copied) = unsafe items._copyContents(initializing: gap) - precondition(it.next() == nil && copied == c, "Broken Collection: count doesn't match contents") - _count += c + public mutating func insert( + copying newElements: UnsafeBufferPointer, at index: Int + ) { + guard newElements.count > 0 else { return } + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: newElements.count) + unsafe gap.baseAddress.unsafelyUnwrapped.initialize( + from: newElements.baseAddress.unsafelyUnwrapped, count: newElements.count) + _count += newElements.count } - @available(SwiftCompatibilitySpan 5.0, *) + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. @inlinable - internal mutating func _insert(copying items: UnsafeBufferPointer, at index: Int) { - guard items.count > 0 else { return } - precondition(items.count <= freeCapacity, "RigidArray capacity overflow") - let gap = unsafe _openGap(at: index, count: items.count) - unsafe gap.baseAddress.unsafelyUnwrapped.initialize( - from: items.baseAddress.unsafelyUnwrapped, count: items.count) - _count += items.count + public mutating func insert( + copying newElements: UnsafeMutableBufferPointer, + at index: Int + ) { + unsafe self.insert(copying: UnsafeBufferPointer(newElements), at: index) } - // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. + /// Copies the elements of a span into this array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. @available(SwiftCompatibilitySpan 5.0, *) @inlinable - public mutating func insert(copying items: Span, at index: Int) { - precondition(items.count <= freeCapacity, "RigidArray capacity overflow") - unsafe items.withUnsafeBufferPointer { unsafe self._insert(copying: $0, at: index) } + public mutating func insert( + copying newElements: Span, at index: Int + ) { + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + unsafe newElements.withUnsafeBufferPointer { + unsafe self.insert(copying: $0, at: index) + } + } + + @inlinable + internal mutating func _insertCollection( + at index: Int, + copying items: some Collection, + newCount: Int + ) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + precondition(newCount <= freeCapacity, "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: newCount) + + let done: Void? = items.withContiguousStorageIfAvailable { buffer in + let i = unsafe gap._initializePrefix(copying: buffer) + precondition( + i == newCount, + "Broken Collection: count doesn't match contents") + _count += newCount + } + if done != nil { return } + + var (it, copied) = unsafe items._copyContents(initializing: gap) + precondition( + it.next() == nil && copied == newCount, + "Broken Collection: count doesn't match contents") + _count += newCount } - // FIXME: We need to nail the naming of this: we'll have consuming variants, too, and we need to interoperate with Collection's methods. @available(SwiftCompatibilitySpan 5.0, *) - @_alwaysEmitIntoClient - public mutating func insert & ~Copyable & ~Escapable>( - copying items: borrowing C, at index: Int + @inlinable + internal mutating func _insertContainer< + C: Container & ~Copyable & ~Escapable + >( + at index: Int, + copying items: borrowing C, + newCount: Int ) { precondition(index >= 0 && index <= _count, "Index out of bounds") - let c = items.count - precondition(c <= freeCapacity, "RigidArray capacity overflow") - let target = unsafe _openGap(at: index, count: c) + precondition(newCount <= freeCapacity, "RigidArray capacity overflow") + let target = unsafe _openGap(at: index, count: newCount) let (copied, end) = unsafe target._initializePrefix(copying: items) precondition( - copied == c && end == items.endIndex, + copied == newCount && end == items.endIndex, "Broken Container: count doesn't match contents") - _count += c + _count += newCount + } + + /// Copies the elements of a collection into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved + /// to make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func insert( + copying newElements: some Collection, at index: Int + ) { + _insertCollection( + at: index, copying: newElements, newCount: newElements.count) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer( + at: index, copying: newElements, newCount: newElements.count) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & Collection + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer( + at: index, copying: newElements, newCount: newElements.count) } } //MARK: - Range replacement -extension RigidArray { - /// Perform a range replacement up to populating the newly opened gap. This deinitializes removed content, rearranges trailing - /// elements to be at their final size, and sets the container's new count. +extension RigidArray where Element: ~Copyable { + /// Perform a range replacement up to populating the newly opened gap. This + /// deinitializes existing elements in the specified subrange, rearranges + /// following elements to be at their final location, and sets the container's + /// new count. /// - /// - Returns: A buffer pointer addressing the newly opened gap, to be initialized by the caller. + /// - Returns: A buffer pointer addressing the newly opened gap, to be + /// initialized by the caller. @inlinable internal mutating func _gapForReplacement( of subrange: Range, withNewCount newCount: Int ) -> UnsafeMutableBufferPointer { + // FIXME: Replace this with a public variant based on OutputSpan. precondition( subrange.lowerBound >= 0 && subrange.upperBound <= _count, "Index range out of bounds") - precondition(newCount - subrange.count <= freeCapacity, "RigidArray capacity overflow") + precondition( + newCount - subrange.count <= freeCapacity, + "RigidArray capacity overflow") unsafe _items.extracting(subrange).deinitialize() if newCount > subrange.count { - _ = unsafe _openGap(at: subrange.upperBound, count: newCount - subrange.count) + _ = unsafe _openGap( + at: subrange.upperBound, count: newCount - subrange.count) } else if newCount < subrange.count { - _closeGap(at: subrange.lowerBound + newCount, count: subrange.count - newCount) + _closeGap( + at: subrange.lowerBound + newCount, count: subrange.count - newCount) } _count += newCount - subrange.count let gapRange = unsafe Range( uncheckedBounds: (subrange.lowerBound, subrange.lowerBound + newCount)) return unsafe _storage.extracting(gapRange) } +} - /// Replaces the specified subrange of elements by copying the elements of the given collection. +extension RigidArray { + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. /// /// This method has the effect of removing the specified range of elements - /// from the array and inserting the new elements starting at the same location. - /// The number of new elements need not match the number of elements being - /// removed. + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. /// /// If you pass a zero-length range as the `subrange` parameter, this method /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling - /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// the `insert(copying:at:)` method instead is preferred in this case. /// - /// Likewise, if you pass a zero-length collection as the `newElements` + /// Likewise, if you pass a zero-length buffer as the `newElements` /// parameter, this method removes the elements in the given subrange /// without replacement. Calling the `removeSubrange(_:)` method instead is /// preferred in this case. @@ -655,25 +1028,28 @@ extension RigidArray { @inlinable public mutating func replaceSubrange( _ subrange: Range, - with newElements: __owned some Collection + copying newElements: UnsafeBufferPointer ) { - let c = newElements.count - let gap = unsafe _gapForReplacement(of: subrange, withNewCount: c) - var (it, copied) = unsafe newElements._copyContents(initializing: gap) - precondition(it.next() == nil && copied == c, "Broken Collection: count doesn't match contents") + let gap = unsafe _gapForReplacement( + of: subrange, withNewCount: newElements.count) + let i = unsafe gap._initializePrefix(copying: newElements) + assert(i == gap.count) } /// Replaces the specified subrange of elements by copying the elements of /// the given buffer pointer, which must be fully initialized. /// /// This method has the effect of removing the specified range of elements - /// from the array and inserting the new elements starting at the same location. - /// The number of new elements need not match the number of elements being - /// removed. + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. /// /// If you pass a zero-length range as the `subrange` parameter, this method /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling - /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// the `insert(copying:at:)` method instead is preferred in this case. /// /// Likewise, if you pass a zero-length buffer as the `newElements` /// parameter, this method removes the elements in the given subrange @@ -690,24 +1066,29 @@ extension RigidArray { @inlinable public mutating func replaceSubrange( _ subrange: Range, - with newElements: UnsafeBufferPointer + copying newElements: UnsafeMutableBufferPointer ) { - let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newElements.count) - unsafe gap.initializeAll(fromContentsOf: newElements) + unsafe self.replaceSubrange( + subrange, + copying: UnsafeBufferPointer(newElements)) } - /// Replaces the specified subrange of elements by copying the elements of the given span. + /// Replaces the specified subrange of elements by copying the elements of + /// the given span. /// /// This method has the effect of removing the specified range of elements - /// from the array and inserting the new elements starting at the same location. - /// The number of new elements need not match the number of elements being - /// removed. + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. /// /// If you pass a zero-length range as the `subrange` parameter, this method /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling - /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// the `insert(copying:at:)` method instead is preferred in this case. /// - /// Likewise, if you pass a zero-length collection as the `newElements` + /// Likewise, if you pass a zero-length span as the `newElements` /// parameter, this method removes the elements in the given subrange /// without replacement. Calling the `removeSubrange(_:)` method instead is /// preferred in this case. @@ -726,20 +1107,46 @@ extension RigidArray { copying newElements: Span ) { unsafe newElements.withUnsafeBufferPointer { buffer in - unsafe self.replaceSubrange(subrange, with: buffer) + unsafe self.replaceSubrange(subrange, copying: buffer) + } + } + + @inlinable + internal mutating func _replaceSubrange( + _ subrange: Range, + copyingCollection newElements: __owned some Collection, + newCount: Int + ) { + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newCount) + + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + let i = unsafe gap._initializePrefix(copying: buffer) + precondition( + i == newCount, + "Broken Collection: count doesn't match contents") } + if done != nil { return } + + var (it, copied) = unsafe newElements._copyContents(initializing: gap) + precondition( + it.next() == nil && copied == newCount, + "Broken Collection: count doesn't match contents") } - /// Replaces the specified subrange of elements by copying the elements of the given container. + /// Replaces the specified subrange of elements by copying the elements of + /// the given collection. /// /// This method has the effect of removing the specified range of elements - /// from the array and inserting the new elements starting at the same location. - /// The number of new elements need not match the number of elements being - /// removed. + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. /// /// If you pass a zero-length range as the `subrange` parameter, this method /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling - /// the `insert(contentsOf:at:)` method instead is preferred in this case. + /// the `insert(copying:at:)` method instead is preferred in this case. /// /// Likewise, if you pass a zero-length collection as the `newElements` /// parameter, this method removes the elements in the given subrange @@ -753,17 +1160,109 @@ extension RigidArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: __owned some Collection + ) { + _replaceSubrange( + subrange, copyingCollection: newElements, newCount: newElements.count) + } + @available(SwiftCompatibilitySpan 5.0, *) @inlinable - public mutating func replaceSubrange & ~Copyable & ~Escapable>( + public mutating func _replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( _ subrange: Range, - copying newElements: borrowing C + copyingContainer newElements: borrowing C, + newCount: Int ) { - let c = newElements.count - let gap = unsafe _gapForReplacement(of: subrange, withNewCount: c) + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newCount) let (copied, end) = unsafe gap._initializePrefix(copying: newElements) precondition( - copied == c && end == newElements.endIndex, + copied == newCount && end == newElements.endIndex, "Broken Container: count doesn't match contents") } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange( + subrange, copyingContainer: newElements, newCount: newElements.count) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & Collection + >( + _ subrange: Range, + copying newElements: C + ) { + _replaceSubrange( + subrange, copyingContainer: newElements, newCount: newElements.count) + } } diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index 7af024a26..f8b997fa4 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -85,6 +85,123 @@ public protocol Container: ~Copyable, ~Escapable { @available(SwiftCompatibilitySpan 5.0, *) extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public func _defaultIndex(_ i: Index, advancedBy distance: Int) -> Index { + precondition( + distance >= 0, + "Only BidirectionalContainers can be advanced by a negative amount") + + var i = index(alignedDown: i) + var distance = distance + + // FIXME: This implementation can be wasteful for contiguous containers, + // as iterating over spans will overshoot the target immediately. + // Reintroducing `nextSpan(after:maximumCount:)` would help avoid + // having to use a second loop to refine the result, but it would + // complicate conformances. + + // Skip forward until we find the span that contains our target. + while distance > 0 { + var j = i + let span = self.nextSpan(after: &j) + precondition( + !span.isEmpty, + "Can't advance index beyond the end of the container") + guard span.count <= distance else { break } + i = j + distance &-= span.count + } + // Step through to find the precise target. + while distance > 0 { + self.formIndex(after: &i) + distance &-= 1 + } + return i + } + + @inlinable + public func _defaultDistance(from start: Index, to end: Index) -> Int { + var start = index(alignedDown: start) + let end = index(alignedDown: end) + if start > end { + return -_defaultDistance(from: end, to: start) + } + var count = 0 + while start != end { + count = count + 1 + formIndex(after: &start) + } + return count + } + + @inlinable + public func _defaultFormIndex( + _ i: inout Index, + advancedBy distance: inout Int, + limitedBy limit: Index + ) { + precondition( + distance >= 0, + "Only BidirectionalContainers can be advanced by a negative amount") + + i = index(alignedDown: i) + let limit = index(alignedDown: limit) + + // Skip forward until we find the span that contains our target. + while distance > 0 { + var j = i + let span = self.nextSpan(after: &j) + precondition( + !span.isEmpty, + "Can't advance index beyond the end of the container") + guard span.count <= distance, j < limit else { + break + } + i = j + distance &-= span.count + } + // Step through to find the precise target. + while distance != 0 { + if i == limit { + return + } + formIndex(after: &i) + distance &-= 1 + } + } +} + +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public var isEmpty: Bool { + return startIndex == endIndex + } + + @inlinable + public func formIndex(after i: inout Index) { + i = self.index(after: i) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + _defaultDistance(from: start, to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + _defaultIndex(index, advancedBy: n) + } + + @inlinable + public func formIndex( + _ i: inout Index, + offsetBy distance: inout Int, + limitedBy limit: Index + ) { + _defaultFormIndex(&i, advancedBy: &distance, limitedBy: limit) + } + @inlinable public func index(alignedDown index: Index) -> Index { index } @@ -98,8 +215,40 @@ extension Container where Self: ~Copyable & ~Escapable { public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } } +@available(SwiftCompatibilitySpan 5.0, *) +extension Container where Self: Sequence { + @inlinable + public var underestimatedCount: Int { count } +} + @available(SwiftCompatibilitySpan 5.0, *) extension Container where Self: Collection { + // Resolve ambiguities between default implementations between Collection + // and Container. + + @inlinable + public var underestimatedCount: Int { count } + + @inlinable + public var isEmpty: Bool { + return startIndex == endIndex + } + + @inlinable + public func formIndex(after i: inout Index) { + i = self.index(after: i) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + _defaultDistance(from: start, to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + _defaultIndex(index, advancedBy: n) + } + @inlinable public func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } diff --git a/Sources/Future/Containers/ContiguousContainer.swift b/Sources/Future/Containers/ContiguousContainer.swift index 853e74627..7f966a16a 100644 --- a/Sources/Future/Containers/ContiguousContainer.swift +++ b/Sources/Future/Containers/ContiguousContainer.swift @@ -10,18 +10,38 @@ // //===----------------------------------------------------------------------===// -@available(SwiftCompatibilitySpan 5.0, *) -public protocol ContiguousContainer: /*RandomAccessContainer*/ ~Copyable, ~Escapable { - associatedtype Element: ~Copyable/* & ~Escapable*/ - - var span: Span { @lifetime(copy self) get } +@available(SwiftStdlib 6.2, *) +public protocol ContiguousContainer + : RandomAccessContainer, ~Copyable, ~Escapable +{ + var span: Span { @lifetime(borrow self) get } } -@available(SwiftCompatibilitySpan 5.0, *) -extension Span: ContiguousContainer where Element: ~Copyable { - public var span: Self { - @lifetime(copy self) - get { self } +@available(SwiftStdlib 6.2, *) +extension ContiguousContainer +where Self: ~Copyable & ~Escapable, Index == Int { + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Index) -> Borrow { + span.borrowElement(at: index - startIndex) + } + + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Index) -> Span { + var i = index - startIndex + let result = span.nextSpan(after: &i) + index = i + startIndex + return result + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Index) -> Span { + var i = index - startIndex + let result = span.previousSpan(before: &i) + index = i + startIndex + return result } } @@ -33,6 +53,3 @@ extension ContiguousArray: ContiguousContainer {} @available(SwiftStdlib 6.2, *) extension CollectionOfOne: ContiguousContainer {} - -@available(SwiftStdlib 6.2, *) -extension String.UTF8View: ContiguousContainer {} diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift index f2bfe9218..7caf20946 100644 --- a/Sources/Future/Containers/RandomAccessContainer.swift +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -66,6 +66,19 @@ where Self: ~Copyable & ~Escapable, Index: Strideable, Index.Stride == Int // Note: Range checks are deferred until element access. index.advance(by: &distance, limitedBy: limit) } + + + @lifetime(borrow self) + @inlinable + public func nextSpan( + after index: inout Index, maximumCount: Int + ) -> Span { + precondition(maximumCount >= 0, "Maximum count must be non-negative") + let span = self.nextSpan(after: &index) + if span.count <= maximumCount { return span } + index = index.advanced(by: maximumCount - span.count) + return span._extracting(first: maximumCount) + } } // Disambiguate parallel extensions on RandomAccessContainer and RandomAccessCollection diff --git a/Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift b/Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift new file mode 100644 index 000000000..a6206e9b5 --- /dev/null +++ b/Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +extension UnsafeBufferPointer: @unsafe ContiguousContainer +where Element: ~Copyable +{ + public var span: Span { + @inlinable + @lifetime(borrow self) + get { + unsafe Span(_unsafeElements: self) + } + } + + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < count, "Index out of bounds") + let ptr = unsafe baseAddress.unsafelyUnwrapped + index + return unsafe Borrow(unsafeAddress: ptr, borrowing: self) + } +} diff --git a/Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift b/Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift new file mode 100644 index 000000000..50a1bcab5 --- /dev/null +++ b/Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +extension UnsafeMutableBufferPointer: @unsafe ContiguousContainer +where Element: ~Copyable +{ + /// The contents of `self` must not be mutated while the returned span exists. + public var span: Span { + @inlinable + @lifetime(borrow self) + get { + unsafe Span(_unsafeElements: self) + } + } + + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < count, "Index out of bounds") + let ptr = unsafe baseAddress.unsafelyUnwrapped + index + return unsafe Borrow(unsafeAddress: ptr, borrowing: self) + } +} diff --git a/Sources/Future/Span/Span+Container.swift b/Sources/Future/Span/Span+ContiguousContainer.swift similarity index 94% rename from Sources/Future/Span/Span+Container.swift rename to Sources/Future/Span/Span+ContiguousContainer.swift index 5f032241e..6578e62e4 100644 --- a/Sources/Future/Span/Span+Container.swift +++ b/Sources/Future/Span/Span+ContiguousContainer.swift @@ -39,6 +39,14 @@ extension Span: RandomAccessContainer where Element: ~Copyable { } } +@available(SwiftStdlib 6.2, *) +extension Span: ContiguousContainer where Element: ~Copyable { + public var span: Self { + @lifetime(copy self) + get { self } + } +} + @available(SwiftStdlib 6.2, *) extension Span where Element: ~Copyable { @usableFromInline diff --git a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift index 3a4c7271a..efb123b08 100644 --- a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift +++ b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift @@ -49,37 +49,35 @@ extension UnsafeMutableRawBufferPointer { extension UnsafeMutableBufferPointer { - /// Initialize slots at the start of this buffer by copying data from `buffer`, then - /// shrink `self` to drop all initialized items from its front, leaving it addressing the - /// uninitialized remainder. + /// Initialize slots at the start of this buffer by copying data from `source`. /// /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be - /// entirely uninitialized, while `buffer` must be fully initialized. + /// entirely uninitialized, while `source` must be fully initialized. /// - /// The count of `buffer` must not be greater than `self.count`. + /// The `source` buffer must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. @inlinable - internal mutating func _initializeAndDropPrefix(copying buffer: UnsafeBufferPointer) { - if buffer.isEmpty { return } - precondition(buffer.count <= self.count) + internal func _initializePrefix(copying source: UnsafeBufferPointer) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) unsafe self.baseAddress.unsafelyUnwrapped.initialize( - from: buffer.baseAddress.unsafelyUnwrapped, count: buffer.count) - unsafe self = self.extracting(buffer.count...) + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count } - /// Initialize slots at the start of this buffer by copying data from `span`, then - /// shrink `self` to drop all initialized items from its front, leaving it addressing the - /// uninitialized remainder. + /// Initialize slots at the start of this buffer by copying data from `source`. /// /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be /// entirely uninitialized. /// - /// The count of `span` must not be greater than `self.count`. + /// The `source` span must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. @available(SwiftCompatibilitySpan 5.0, *) @inlinable - internal mutating func _initializeAndDropPrefix(copying span: Span) { - unsafe span.withUnsafeBufferPointer { buffer in - unsafe self._initializeAndDropPrefix(copying: buffer) - } + internal func _initializePrefix(copying source: Span) -> Int { + unsafe source.withUnsafeBufferPointer { unsafe self._initializePrefix(copying: $0) } } /// Initialize all slots in this buffer by copying data from `items`, which must fit entirely @@ -114,4 +112,34 @@ extension UnsafeMutableBufferPointer { } return (self.count - target.count, i) } + + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + internal mutating func _initializeAndDropPrefix(copying source: UnsafeBufferPointer) { + let i = unsafe _initializePrefix(copying: source) + unsafe self = self.extracting(i...) + } + + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftCompatibilitySpan 5.0, *) + @inlinable + internal mutating func _initializeAndDropPrefix(copying span: Span) { + unsafe span.withUnsafeBufferPointer { buffer in + unsafe self._initializeAndDropPrefix(copying: buffer) + } + } } diff --git a/Tests/DequeTests/RigidDequeTests.swift b/Tests/DequeTests/RigidDequeTests.swift index d0057ee67..15534d2f9 100644 --- a/Tests/DequeTests/RigidDequeTests.swift +++ b/Tests/DequeTests/RigidDequeTests.swift @@ -1,9 +1,13 @@ +//===----------------------------------------------------------------------===// // -// RepeatingContainerTests.swift -// swift-collections +// This source file is part of the Swift Collections open source project // -// Created by Karoy Lorentey on 2025-04-21. +// 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 +// +//===----------------------------------------------------------------------===// import XCTest diff --git a/Tests/FutureTests/ArrayTests/ArrayLayout.swift b/Tests/FutureTests/ArrayTests/ArrayLayout.swift new file mode 100644 index 000000000..9a8563ac9 --- /dev/null +++ b/Tests/FutureTests/ArrayTests/ArrayLayout.swift @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import _CollectionsTestSupport +import Future + +struct ArrayLayout { + var capacity: Int + var count: Int + + init(capacity: Int, count: Int) { + precondition(count >= 0 && count <= capacity) + self.capacity = capacity + self.count = count + } +} + +func withSomeArrayLayouts( + _ label: String, + ofCapacities capacities: some Sequence, + file: StaticString = #file, + line: UInt = #line, + run body: (ArrayLayout) throws(E) -> Void +) throws(E) { + let context = TestContext.current + for capacity in capacities { + var counts: Set = [] + counts.insert(0) + counts.insert(capacity) + counts.insert(capacity / 2) + if capacity >= 1 { + counts.insert(1) + counts.insert(capacity - 1) + } + if capacity >= 2 { + counts.insert(2) + counts.insert(capacity - 2) + } + for count in counts { + let layout = ArrayLayout(capacity: capacity, count: count) + let entry = context.push("\(label): \(layout)", file: file, line: line) + + var done = false + defer { + context.pop(entry) + if !done { + print(context.currentTrace(title: "Throwing trace")) + } + } + try body(layout) + done = true + } + } +} + +extension RigidArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init( + capacity: layout.capacity, + count: layout.count, + initializedWith: generator) + } +} + +extension DynamicArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init(consuming: RigidArray(layout: layout, using: generator)) + } +} + +extension LifetimeTracker { + func rigidArray( + layout: ArrayLayout, + using generator: (Int) -> Element = { $0 } + ) -> RigidArray> { + RigidArray(layout: layout, using: { self.instance(for: generator($0)) }) + } + + func dynamicArray( + layout: ArrayLayout, + using generator: (Int) -> Element = { $0 } + ) -> DynamicArray> { + let contents = RigidArray(layout: layout) { + self.instance(for: generator($0)) + } + return DynamicArray(consuming: contents) + } +} diff --git a/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift b/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift new file mode 100644 index 000000000..e45f14c68 --- /dev/null +++ b/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift @@ -0,0 +1,804 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import _CollectionsTestSupport +import Future +import Synchronization + +class DynamicArrayTests: CollectionTestCase { + func test_validate_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let items = DynamicArray(consuming: tracker.rigidArray(layout: layout)) + let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } + expectEqual(tracker.instances, 2 * layout.count) + checkContainer(items, expectedContents: expected) + } + } + } + + func test_basics() { + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + var array = DynamicArray() + expectTrue(array.isEmpty) + expectEqual(array.count, 0) + expectEqual(array.capacity, 0) + expectEqual(tracker.instances, 0) + + array.append(tracker.structInstance(for: 10)) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array.capacity, 1) // This assumes a specific growth behavior + expectEqual(array[0].payload, 10) + expectEqual(tracker.instances, 1) + + array.append(tracker.structInstance(for: 20)) + expectFalse(array.isEmpty) + expectEqual(array.count, 2) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectEqual(array[0].payload, 10) + expectEqual(array[1].payload, 20) + expectEqual(tracker.instances, 2) + + let old = array.remove(at: 0) + expectEqual(old.payload, 10) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectEqual(array[0].payload, 20) + expectEqual(tracker.instances, 2) + _ = consume old + expectEqual(tracker.instances, 1) + + let old2 = array.remove(at: 0) + expectEqual(old2.payload, 20) + expectEqual(array.count, 0) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectTrue(array.isEmpty) + expectEqual(tracker.instances, 1) + _ = consume old2 + expectEqual(tracker.instances, 0) + } + } + + func test_init_capacity() { + do { + let a = DynamicArray(capacity: 0) + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + } + + do { + let a = DynamicArray(capacity: 10) + expectEqual(a.capacity, 10) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 10) + expectTrue(a.isEmpty) + } + } + + func test_init_generator() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_init_repeating() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let value = tracker.instance(for: 0) + let a = DynamicArray(repeating: value, count: c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + for i in 0 ..< c { + expectIdentical(a[i], value) + } + } + } + } + + func test_init_copying_Sequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = DynamicArray( + capacity: layout.capacity, + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, layout.count) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.isEmpty, layout.count == 0) + for i in 0 ..< layout.count { + expectEqual(a[i].payload, i) + } + } + } + } + + func test_init_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCounts", in: [ + [1], + [3, 5, 7], + [10, 3], + ] as [[Int]]) { spanCounts in + withLifetimeTracking { tracker in + let additions = StaccatoContainer( + contents: RigidArray( + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }), + spanCounts: spanCounts) + + let array = DynamicArray( + capacity: layout.capacity, copying: additions) + expectEqual(tracker.instances, layout.count) + expectEqual(array.capacity, layout.capacity) + expectEqual(array.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(array[i].payload, i) + } + } + } + } + } + + func test_span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let span = a.span + expectEqual(span.count, layout.count) + for i in 0 ..< span.count { + expectEqual(span[i].payload, i) + } + } + } + } + + func test_mutableSpan() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + var span = a.mutableSpan + expectEqual(span.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(span[i].payload, i) + span[i] = tracker.instance(for: -i) + } + for i in 0 ..< layout.count { + expectEqual(span[i].payload, -i) + } + for i in 0 ..< layout.count { + expectEqual(a[i].payload, -i) + } + } + } + } + + func test_nextSpan() { + // DynamicArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let whole = a.span + var i = 0 + let first = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(first.isIdentical(to: whole)) + let second = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(second.isEmpty) + } + } + } + + func test_previousSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let whole = a.span + var i = layout.count + let first = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(first.isIdentical(to: whole)) + let second = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(second.isEmpty) + } + } + } + + func test_nextMutableSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + var i = 0 + var span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectEqual(span.count, layout.count) + span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(span.isEmpty) + } + } + } + + func test_index_properties() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + let a = DynamicArray(layout: layout, using: { $0 }) + expectEqual(a.startIndex, 0) + expectEqual(a.endIndex, layout.count) + expectEqual(a.indices, 0 ..< layout.count) + } + } + + func test_swapAt() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + withEvery("i", in: 0 ..< layout.count / 2) { i in + a.swapAt(i, layout.count - 1 - i) + } + let expected = (0 ..< layout.count).reversed() + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_borrowElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + for i in 0 ..< layout.count { + let item = a.borrowElement(at: i) + expectEqual(item[].payload, i) + } + } + } + } + + func test_mutateElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + for i in 0 ..< layout.count { + var item = a.mutateElement(at: i) + expectEqual(item[].payload, i) + item[] = tracker.instance(for: -i) + expectEqual(tracker.instances, layout.count) + } + } + } + } + + func test_withUnsafeMutableBufferPointer() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, layout.count) + unsafe buffer.extracting(0 ..< count).deinitialize() + expectEqual(tracker.instances, 0) + count = 0 + } + expectEqual(a.count, 0) + + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, 0) + for i in 0 ..< buffer.count { + unsafe buffer.initializeElement(at: i, to: tracker.instance(for: -i)) + count += 1 + } + expectEqual(tracker.instances, layout.capacity) + } + expectEqual(a.count, layout.capacity) + + struct TestError: Error {} + + expectThrows({ + unsafe try a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(tracker.instances, layout.capacity) + while count > 0 { + if count == layout.count { break } + unsafe buffer.deinitializeElement(at: count - 1) + count -= 1 + } + throw TestError() + } + }) { error in + expectTrue(error is TestError) + } + expectContainerContents( + a, + equivalentTo: (0 ..< layout.count).map { -$0 }, + by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_reallocate() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + layout.capacity, layout.count, layout.count + 1, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reallocate(capacity: newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_reserveCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + 0, layout.count - 1, layout.count, layout.count + 1, + layout.capacity, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reserveCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.removeAll() + expectTrue(a.isEmpty) + expectEqual(a.capacity, 0) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeAll_keepingCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.removeAll(keepingCapacity: true) + expectTrue(a.isEmpty) + expectEqual(a.capacity, layout.capacity) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + withEvery("i", in: 0 ..< layout.count) { i in + let old = a.removeLast() + expectEqual(old.payload, layout.count - 1 - i) + expectEqual(a.count, layout.count - 1 - i) + expectEqual(a.capacity, layout.capacity) + } + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast_k() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("k", in: 0 ..< layout.count) { k in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeLast(k) + + var a = tracker.dynamicArray(layout: layout) + expectEqual(tracker.instances, layout.count) + a.removeLast(k) + expectEqual(tracker.instances, layout.count - k) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_remove_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ..< layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.remove(at: i) + + var a = tracker.dynamicArray(layout: layout) + let old = a.remove(at: i) + expectEqual(old.payload, i) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeSubrange() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeSubrange(range) + + var a = tracker.dynamicArray(layout: layout) + a.removeSubrange(range) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll_where() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeAll(where: { $0.isMultiple(of: 2) }) + + var a = tracker.dynamicArray(layout: layout) + a.removeAll(where: { $0.payload.isMultiple(of: 2) }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_popLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let expectedItem = expected.popLast() + + var a = tracker.dynamicArray(layout: layout) + let item = a.popLast() + + expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_append_geometric_growth() { + // This test depends on the precise growth curve of DynamicArray, + // which is not part of its stable API. The test may need to be updated + // accordingly. + withLifetimeTracking { tracker in + typealias Value = LifetimeTracked + + var array = DynamicArray() + expectEqual(array.capacity, 0) + + array.append(tracker.instance(for: 0)) + expectEqual(array.capacity, 1) + array.append(tracker.instance(for: 1)) + expectEqual(array.capacity, 2) + array.append(tracker.instance(for: 2)) + expectEqual(array.capacity, 4) + array.append(tracker.instance(for: 3)) + expectEqual(array.capacity, 4) + array.append(tracker.instance(for: 4)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 5)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 6)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 7)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 8)) + expectEqual(array.capacity, 16) + + for i in 9 ..< 100 { + array.append(tracker.instance(for: i)) + } + expectEqual(tracker.instances, 100) + expectEqual(array.count, 100) + expectEqual(array.capacity, 128) + + + array.append( + copying: RigidArray(count: 300) { tracker.instance(for: 100 + $0) }) + expectEqual(array.capacity, 400) + + expectEqual(tracker.instances, 400) + for i in 0 ..< 400 { + expectEqual(array.borrowElement(at: i)[].payload, i) + expectEqual(array[i].payload, i) + } + + _ = consume array + expectEqual(tracker.instances, 0) + } + } + + func test_append() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let c = 2 * layout.capacity + 10 + var a = tracker.dynamicArray(layout: layout) + for i in layout.count ..< c { + a.append(tracker.instance(for: i)) + expectEqual(a.count, i + 1) + expectContainerContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) + } + expectEqual(tracker.instances, c) + } + } + } + + func test_append_copying_MinimalSequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + let c = 2 * layout.capacity + 10 + var a = tracker.dynamicArray(layout: layout) + a.append(copying: MinimalSequence( + elements: (layout.count ..< c).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: isContiguous)) + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1}) + expectEqual(tracker.instances, c) + } + } + } + } + + func test_append_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("additions", in: [0, 1, 10, 100]) { additions in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + let b = RigidArray(count: additions) { + tracker.instance(for: layout.count + $0) + } + a.append(copying: b.span) + let c = layout.count + additions + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) + expectEqual(tracker.instances, c) + } + } + } + } + + func test_append_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("additions", in: [0, 1, 10, 100]) { additions in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + + let c = layout.count + additions + let addition = (layout.count ..< c).map { + tracker.instance(for: $0) + } + let b = StaccatoContainer( + contents: RigidArray(copying: addition), + spanCounts: [spanCount]) + a.append(copying: b) + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) + expectEqual(tracker.instances, c) + } + } + } + } + } + + func test_insert_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.insert(-1, at: i) + + var a = tracker.dynamicArray(layout: layout) + a.insert(tracker.instance(for: -1), at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + 1) + } + } + } + } + + func test_insert_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.count + c) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let trackedAddition = addition.map { tracker.instance(for: $0) } + var a = tracker.dynamicArray(layout: layout) + a.insert(copying: trackedAddition, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + + func test_insert_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + let addition = Array(layout.count ..< layout.count + c) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let rigidAddition = RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + } + var a = tracker.dynamicArray(layout: layout) + a.insert(copying: rigidAddition.span, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + + func test_insert_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.count + c) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.dynamicArray(layout: layout) + let rigidAddition = StaccatoContainer( + contents: RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + }, + spanCounts: [spanCount]) + a.insert(copying: rigidAddition, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + } + + func test_replaceSubrange_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = addition.map { tracker.instance(for: $0) } + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = RigidArray( + copying: addition.map { tracker.instance(for: $0) }) + a.replaceSubrange(range, copying: trackedAddition.span) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = StaccatoContainer( + contents: RigidArray( + copying: addition.map { tracker.instance(for: $0) }), + spanCounts: [spanCount]) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } +} diff --git a/Tests/FutureTests/RigidArrayTests.swift b/Tests/FutureTests/ArrayTests/RigidArrayTests.swift similarity index 82% rename from Tests/FutureTests/RigidArrayTests.swift rename to Tests/FutureTests/ArrayTests/RigidArrayTests.swift index 60d1b3b3d..171f63055 100644 --- a/Tests/FutureTests/RigidArrayTests.swift +++ b/Tests/FutureTests/ArrayTests/RigidArrayTests.swift @@ -1,85 +1,18 @@ +//===----------------------------------------------------------------------===// // -// RepeatingContainerTests.swift -// swift-collections +// This source file is part of the Swift Collections open source project // -// Created by Karoy Lorentey on 2025-04-21. +// 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 +// +//===----------------------------------------------------------------------===// import XCTest import _CollectionsTestSupport import Future -struct ArrayLayout { - var capacity: Int - var count: Int - - init(capacity: Int, count: Int) { - precondition(count >= 0 && count <= capacity) - self.capacity = capacity - self.count = count - } -} - -extension RigidArray where Element: ~Copyable { - init(layout: ArrayLayout, using generator: (Int) -> Element) { - self.init(capacity: layout.capacity) - for i in 0 ..< layout.count { - self.append(generator(i)) - } - } -} - -extension LifetimeTracker { - func rigidArray(layout: ArrayLayout) -> RigidArray> { - rigidArray(layout: layout, using: { $0 }) - } - - func rigidArray( - layout: ArrayLayout, - using generator: (Int) -> Element - ) -> RigidArray> { - RigidArray(layout: layout, using: { self.instance(for: generator($0)) }) - } -} - -func withSomeArrayLayouts( - _ label: String, - ofCapacities capacities: some Sequence, - file: StaticString = #file, - line: UInt = #line, - run body: (ArrayLayout) throws(E) -> Void -) throws(E) { - let context = TestContext.current - for capacity in capacities { - var counts: Set = [] - counts.insert(0) - counts.insert(capacity) - counts.insert(capacity / 2) - if capacity >= 1 { - counts.insert(1) - counts.insert(capacity - 1) - } - if capacity >= 2 { - counts.insert(2) - counts.insert(capacity - 2) - } - for count in counts { - let layout = ArrayLayout(capacity: capacity, count: count) - let entry = context.push("\(label): \(layout)", file: file, line: line) - - var done = false - defer { - context.pop(entry) - if !done { - print(context.currentTrace(title: "Throwing trace")) - } - } - try body(layout) - done = true - } - } -} - @available(SwiftStdlib 6.0, *) class RigidArrayTests: CollectionTestCase { func test_validate_Container() { @@ -145,16 +78,18 @@ class RigidArrayTests: CollectionTestCase { } func test_init_copying_Collection() { - withEvery("c", in: [0, 10, 100]) { c in + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withLifetimeTracking { tracker in - let a = RigidArray(copying: (0 ..< c).map { tracker.instance(for: $0) }) - expectEqual(tracker.instances, c) - expectEqual(a.capacity, c) - expectEqual(a.count, c) - expectEqual(a.freeCapacity, 0) - expectEqual(a.isEmpty, c == 0) - expectTrue(a.isFull) - for i in 0 ..< c { + let a = RigidArray( + capacity: layout.capacity, + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, layout.count) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectEqual(a.isFull, layout.count == layout.capacity) + for i in 0 ..< layout.count { expectEqual(a[i].payload, i) } } @@ -162,7 +97,7 @@ class RigidArrayTests: CollectionTestCase { } func test_init_copying_Container() { - withEvery("c", in: [0, 10, 100]) { c in + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("spanCounts", in: [ [1], [3, 5, 7], @@ -170,14 +105,15 @@ class RigidArrayTests: CollectionTestCase { ] as [[Int]]) { spanCounts in withLifetimeTracking { tracker in let additions = StaccatoContainer( - contents: RigidArray(copying: (0 ..< c).map { tracker.instance(for: $0) }), + contents: RigidArray( + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }), spanCounts: spanCounts) - let array = RigidArray(copying: additions) - expectEqual(tracker.instances, c) - expectEqual(array.capacity, c) - expectEqual(array.count, c) - for i in 0 ..< c { + let array = RigidArray(capacity: layout.capacity, copying: additions) + expectEqual(tracker.instances, layout.count) + expectEqual(array.capacity, layout.capacity) + expectEqual(array.count, layout.count) + for i in 0 ..< layout.count { expectEqual(array[i].payload, i) } } @@ -268,7 +204,6 @@ class RigidArrayTests: CollectionTestCase { } } - func test_index_properties() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in let a = RigidArray(layout: layout, using: { $0 }) @@ -366,7 +301,7 @@ class RigidArrayTests: CollectionTestCase { } } - func test_setCapacity() { + func test_reallocate() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery( "newCapacity", @@ -374,11 +309,14 @@ class RigidArrayTests: CollectionTestCase { ) { newCapacity in withLifetimeTracking { tracker in var a = tracker.rigidArray(layout: layout) - a.setCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reallocate(capacity: newCapacity) expectEqual(a.count, layout.count) expectEqual(a.capacity, newCapacity) expectEqual(tracker.instances, layout.count) - expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } } @@ -389,16 +327,20 @@ class RigidArrayTests: CollectionTestCase { withEvery( "newCapacity", in: [ - 0, layout.count - 1, layout.count, layout.count + 1, layout.capacity, layout.capacity + 1 + 0, layout.count - 1, layout.count, layout.count + 1, + layout.capacity, layout.capacity + 1 ] as Set ) { newCapacity in withLifetimeTracking { tracker in var a = tracker.rigidArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) a.reserveCapacity(newCapacity) expectEqual(a.count, layout.count) expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) expectEqual(tracker.instances, layout.count) - expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } } @@ -454,7 +396,8 @@ class RigidArrayTests: CollectionTestCase { withLifetimeTracking { tracker in var a = tracker.rigidArray(layout: layout) withEvery("i", in: 0 ..< layout.count) { i in - a.removeLast() + let old = a.removeLast() + expectEqual(old.payload, layout.count - 1 - i) expectEqual(a.count, layout.count - 1 - i) expectEqual(a.capacity, layout.capacity) } @@ -474,7 +417,8 @@ class RigidArrayTests: CollectionTestCase { expectEqual(tracker.instances, layout.count) a.removeLast(k) expectEqual(tracker.instances, layout.count - k) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -488,7 +432,8 @@ class RigidArrayTests: CollectionTestCase { expected.remove(at: i) var a = tracker.rigidArray(layout: layout) - a.remove(at: i) + let old = a.remove(at: i) + expectEqual(old.payload, i) expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -518,7 +463,8 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) a.removeAll(where: { $0.payload.isMultiple(of: 2) }) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -533,7 +479,8 @@ class RigidArrayTests: CollectionTestCase { let item = a.popLast() expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -553,16 +500,18 @@ class RigidArrayTests: CollectionTestCase { } } - func test_append_contentsOf() { + func test_append_copying_MinimalSequence() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in - withLifetimeTracking { tracker in - var a = tracker.rigidArray(layout: layout) - a.append(contentsOf: MinimalSequence( - elements: (layout.count ..< layout.capacity).map { tracker.instance(for: $0) }, - underestimatedCount: .half, - isContiguous: false)) - expectTrue(a.isFull) - expectEqual(tracker.instances, layout.capacity) + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.append(copying: MinimalSequence( + elements: (layout.count ..< layout.capacity).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: isContiguous)) + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } } } } @@ -621,20 +570,25 @@ class RigidArrayTests: CollectionTestCase { } } - func test_insert_contentsOf() { + func test_insert_copying_Collection() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("i", in: 0 ... layout.count) { i in - withLifetimeTracking { tracker in - let addition = (layout.count ..< layout.capacity) + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.capacity) - var expected = Array(0 ..< layout.count) - expected.insert(contentsOf: addition, at: i) + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) - var a = tracker.rigidArray(layout: layout) - a.insert(contentsOf: addition.map { tracker.instance(for: $0) }, at: i) + let trackedAddition = MinimalCollection( + addition.map { tracker.instance(for: $0) }, + isContiguous: isContiguous) + var a = tracker.rigidArray(layout: layout) + a.insert(copying: trackedAddition, at: i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) - expectEqual(tracker.instances, layout.capacity) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } } } } @@ -691,24 +645,27 @@ class RigidArrayTests: CollectionTestCase { func test_replaceSubrange_Collection() { withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in withEveryRange("range", in: 0 ..< layout.count) { range in - withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in - withLifetimeTracking { tracker in - var expected = Array(0 ..< layout.count) - let addition = (0 ..< c).map { -100 - $0 } - expected.replaceSubrange(range, with: addition) + withEvery("isContiguous", in: [false, true]) { isContiguous in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) - var a = tracker.rigidArray(layout: layout) - let trackedAddition = addition.map { tracker.instance(for: $0) } - a.replaceSubrange(range, with: trackedAddition) + let trackedAddition = MinimalCollection( + addition.map { tracker.instance(for: $0) }, + isContiguous: isContiguous) + var a = tracker.rigidArray(layout: layout) + a.replaceSubrange(range, copying: trackedAddition) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) - expectEqual(tracker.instances, layout.count - range.count + c) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } } } } } } - func test_replaceSubrange_Span() { withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in withEveryRange("range", in: 0 ..< layout.count) { range in diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift index a4cbdce02..1862794f3 100644 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections 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 +// +//===----------------------------------------------------------------------===// + import XCTest import Future diff --git a/Tests/FutureTests/DynamicArrayTests.swift b/Tests/FutureTests/DynamicArrayTests.swift deleted file mode 100644 index e066a592a..000000000 --- a/Tests/FutureTests/DynamicArrayTests.swift +++ /dev/null @@ -1,212 +0,0 @@ -import XCTest -import _CollectionsTestSupport -import Future -import Synchronization - -class DynamicArrayTests: CollectionTestCase { - func test_validate_Container() { - let c = 100 - - withLifetimeTracking { tracker in - let expected = (0 ..< c).map { tracker.instance(for: $0) } - let items = DynamicArray(count: c, initializedWith: { expected[$0] }) - checkContainer(items, expectedContents: expected) - } - } - - func test_basics() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - var array = DynamicArray() - expectTrue(array.isEmpty) - expectEqual(array.count, 0) - expectEqual(array.capacity, 0) - expectEqual(tracker.instances, 0) - - array.append(tracker.structInstance(for: 42)) - expectFalse(array.isEmpty) - expectEqual(array.count, 1) - expectEqual(array[0].payload, 42) - expectEqual(tracker.instances, 1) - - array.append(tracker.structInstance(for: 23)) - expectFalse(array.isEmpty) - expectEqual(array.count, 2) - expectEqual(array[0].payload, 42) - expectEqual(array[1].payload, 23) - expectEqual(tracker.instances, 2) - - let old = array.remove(at: 0) - expectEqual(old.payload, 42) - expectFalse(array.isEmpty) - expectEqual(array.count, 1) - expectEqual(array[0].payload, 23) - expectEqual(tracker.instances, 2) - _ = consume old - expectEqual(tracker.instances, 1) - - let old2 = array.remove(at: 0) - expectEqual(old2.payload, 23) - expectEqual(array.count, 0) - expectTrue(array.isEmpty) - expectEqual(tracker.instances, 1) - _ = consume old2 - expectEqual(tracker.instances, 0) - } - } - - func test_read_access() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - let c = 100 - let array = DynamicArray(count: c) { tracker.structInstance(for: $0) } - - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i)[].payload, i) - expectEqual(array[i].payload, i) - } - } - } - - func test_update_access() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - let c = 100 - var array = DynamicArray(count: c) { tracker.structInstance(for: $0) } - - for i in 0 ..< c { - // FIXME: 'exclusive' or something instead of mutating subscript - var me = array.mutateElement(at: i) - me[].payload += 100 - array[i].payload += 100 - } - - for i in 0 ..< c { - expectEqual(array[i].payload, 200 + i) - } - - expectEqual(tracker.instances, c) - _ = consume array - expectEqual(tracker.instances, 0) - } - } - - func test_append() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - var array = DynamicArray() - let c = 100 - for i in 0 ..< c { - array.append(tracker.structInstance(for: 100 + i)) - } - expectEqual(tracker.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i)[].payload, 100 + i) - expectEqual(array[i].payload, 100 + i) - } - - _ = consume array - expectEqual(tracker.instances, 0) - } - } - - func test_insert() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - var array = DynamicArray() - let c = 100 - for i in 0 ..< c { - array.insert(tracker.structInstance(for: 100 + i), at: 0) - } - expectEqual(tracker.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - expectEqual(array.borrowElement(at: i)[].payload, c + 99 - i) - expectEqual(array[i].payload, c + 99 - i) - } - - _ = consume array - expectEqual(tracker.instances, 0) - } - } - - func test_remove() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - let c = 100 - var array = DynamicArray(count: c) { tracker.structInstance(for: 100 + $0) } - expectEqual(tracker.instances, c) - expectEqual(array.count, c) - - for i in 0 ..< c { - array.remove(at: 0) - expectEqual(array.count, c - 1 - i) - expectEqual(tracker.instances, c - 1 - i) - } - - expectTrue(array.isEmpty) - expectEqual(tracker.instances, 0) - } - } - - @available(SwiftCompatibilitySpan 5.0, *) - func test_iterate_full() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - let c = 100 - let array = DynamicArray(count: c) { tracker.structInstance(for: 100 + $0) } - - var index = 0 - do { - let span = array.nextSpan(after: &index) - expectEqual(span.count, c) - for i in 0 ..< span.count { - expectEqual(span[i].payload, 100 + i) - } - } - do { - let span2 = array.nextSpan(after: &index) - expectEqual(span2.count, 0) - } - } - } - - @available(SwiftCompatibilitySpan 5.0, *) - func test_iterate_stepped() { - withLifetimeTracking { tracker in - typealias Value = LifetimeTrackedStruct - - let c = 100 - let array = DynamicArray(count: c) { tracker.structInstance(for: $0) } - - withEvery("stride", in: 1 ... c) { stride in - var index = 0 - var i = 0 - while true { - expectEqual(index, i) - let span = array.nextSpan(after: &index, maximumCount: stride) - expectEqual(index, i + span.count) - if span.isEmpty { break } - expectEqual(span.count, i + stride <= c ? stride : c % stride) - for j in 0 ..< span.count { - expectEqual(span[j].payload, i) - i += 1 - } - } - expectEqual(i, c) - expectEqual(array.nextSpan(after: &index, maximumCount: Int.max).count, 0) - expectEqual(index, i) - } - } - } -} diff --git a/Tests/FutureTests/RepeatingContainerTests.swift b/Tests/FutureTests/RepeatingContainerTests.swift index 4e0ec00d3..da7eb606e 100644 --- a/Tests/FutureTests/RepeatingContainerTests.swift +++ b/Tests/FutureTests/RepeatingContainerTests.swift @@ -1,9 +1,13 @@ +//===----------------------------------------------------------------------===// // -// RepeatingContainerTests.swift -// swift-collections +// This source file is part of the Swift Collections open source project // -// Created by Karoy Lorentey on 2025-04-21. +// 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 +// +//===----------------------------------------------------------------------===// import XCTest import _CollectionsTestSupport diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift index 02b3dbba0..688a44794 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -29,9 +29,14 @@ public struct MinimalBidirectionalCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -51,6 +56,13 @@ extension MinimalBidirectionalCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalBidirectionalCollection: BidirectionalCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift index 50d0d9868..009cbc3cc 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -28,9 +28,14 @@ public struct MinimalCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -50,6 +55,13 @@ extension MinimalCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalCollection: Collection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift index 24998e2be..2b8d8c020 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -30,9 +30,14 @@ public struct MinimalMutableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalMutableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalMutableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift index b3eedec23..31e7809df 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -30,9 +30,14 @@ public struct MinimalMutableRangeReplaceableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalMutableRangeReplaceableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalMutableRangeReplaceableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift index bf666b8f9..d0405a683 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -26,9 +26,14 @@ public struct MinimalRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -48,6 +53,13 @@ extension MinimalRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift index 4a119be33..262d56c2a 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -30,9 +30,14 @@ public struct MinimalRangeReplaceableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalRangeReplaceableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalRangeReplaceableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift index 66f6924b3..c3b9e10e1 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift @@ -44,17 +44,17 @@ public enum UnderestimatedCountBehavior { /// narrow way possible. /// /// This sequence is consumed when its iterator is advanced. -public struct MinimalSequence: Sequence, CustomDebugStringConvertible { +public struct MinimalSequence: Sequence, CustomDebugStringConvertible { public let timesMakeIteratorCalled = ResettableValue(0) - internal let _sharedState: _MinimalIteratorSharedState + internal let _sharedState: _MinimalIteratorSharedState internal let _isContiguous: Bool public init( elements: S, underestimatedCount: UnderestimatedCountBehavior = .value(0), isContiguous: Bool = false - ) where S.Element == T { + ) where S.Element == Element { let data = Array(elements) self._sharedState = _MinimalIteratorSharedState(data) self._isContiguous = isContiguous @@ -73,7 +73,7 @@ public struct MinimalSequence: Sequence, CustomDebugStringConvertible { } } - public func makeIterator() -> MinimalIterator { + public func makeIterator() -> MinimalIterator { timesMakeIteratorCalled.value += 1 return MinimalIterator(_sharedState) } @@ -87,10 +87,10 @@ public struct MinimalSequence: Sequence, CustomDebugStringConvertible { } public func withContiguousStorageIfAvailable( - _ body: (UnsafeBufferPointer) throws -> R + _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { guard _isContiguous else { return nil } - return try _sharedState.data[_sharedState.i...] + return unsafe try _sharedState.data[_sharedState.i...] .withContiguousStorageIfAvailable(body) } } diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift b/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift index 6e6d86668..4d348ac77 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -13,23 +13,31 @@ struct _MinimalCollectionCore { var state: _CollectionState var elements: [Element] + let isContiguous: Bool var underestimatedCount: Int public init( context: TestContext, elements: S, + isContiguous: Bool, underestimatedCount: UnderestimatedCountBehavior? = nil ) where S.Element == Element { - self.init(context: context, elements: Array(elements), underestimatedCount: underestimatedCount) + self.init( + context: context, + elements: Array(elements), + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } public init( context: TestContext, elements: [Element], + isContiguous: Bool, underestimatedCount: UnderestimatedCountBehavior? = nil ) { self.state = _CollectionState(context: context, parent: nil, count: elements.count) self.elements = elements + self.isContiguous = isContiguous self.underestimatedCount = underestimatedCount?.value(forCount: elements.count) ?? elements.count } From b765db28be0063341ef9048a8c4f6efb8b0e4cbd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 May 2025 13:32:46 -0700 Subject: [PATCH 193/195] RigidArray: Add moving/consuming variants of range-replacement operations --- Sources/Future/Arrays/RigidArray.swift | 131 +++++++++++++++++- .../Span/UnsafeBufferPointer+Additions.swift | 24 +++- 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift index bb7dd9d2d..edf045a6f 100644 --- a/Sources/Future/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -553,6 +553,50 @@ extension RigidArray where Element: ~Copyable { } } +extension RigidArray where Element: ~Copyable { + /// Moves the elements of a buffer to the end of this array, leaving the + /// buffer uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + moving items: UnsafeMutableBufferPointer + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + let c = unsafe _freeSpace._moveInitializePrefix(from: items) + assert(c == items.count) + _count &+= items.count + } + + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout RigidArray + ) { + unsafe items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer.extracting(.. + ) { + var items = items + self.append(moving: &items) + } +} + extension RigidArray { /// Copies the elements of a buffer to the end of this array. /// @@ -725,8 +769,59 @@ extension RigidArray where Element: ~Copyable { } } +extension RigidArray where Element: ~Copyable { + /// Moves the elements of a fully initialized buffer into this array, + /// starting at the specified position, and leaving the buffer + /// uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: UnsafeMutableBufferPointer, + at index: Int + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + let target = unsafe _openGap(at: index, count: items.count) + let c = unsafe target._moveInitializePrefix(from: items) + assert(c == items.count) + _count &+= items.count + } + + @_alwaysEmitIntoClient + public mutating func insert( + moving items: inout RigidArray, + at index: Int + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + unsafe items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer.extracting(.., + at index: Int + ) { + var items = items + self.insert(moving: &items, at: index) + } + +} + extension RigidArray { - /// Copyies the elements of a fully initialized buffer pointer into this + /// Copies the elements of a fully initialized buffer pointer into this /// array at the specified position. /// /// The new elements are inserted before the element currently at the @@ -761,7 +856,7 @@ extension RigidArray { _count += newElements.count } - /// Copyies the elements of a fully initialized buffer pointer into this + /// Copies the elements of a fully initialized buffer pointer into this /// array at the specified position. /// /// The new elements are inserted before the element currently at the @@ -997,6 +1092,38 @@ extension RigidArray where Element: ~Copyable { } } +extension RigidArray where Element: ~Copyable { + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving items: UnsafeMutableBufferPointer, + ) { + let gap = unsafe _gapForReplacement( + of: subrange, withNewCount: items.count) + let c = unsafe gap._moveInitializePrefix(from: items) + assert(c == items.count) + } + + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving items: inout RigidArray, + ) { + unsafe items.withUnsafeMutableBufferPointer { buffer, count in + unsafe self.replaceSubrange(subrange, moving: buffer) + count = 0 + } + } + + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + consuming items: consuming RigidArray, + ) { + replaceSubrange(subrange, moving: &items) + } +} + extension RigidArray { /// Replaces the specified subrange of elements by copying the elements of /// the given buffer pointer, which must be fully initialized. diff --git a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift index efb123b08..860400d52 100644 --- a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift +++ b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift @@ -48,6 +48,19 @@ extension UnsafeMutableRawBufferPointer { } +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + internal func _moveInitializePrefix( + from source: UnsafeMutableBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + unsafe self.baseAddress.unsafelyUnwrapped.moveInitialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } +} + extension UnsafeMutableBufferPointer { /// Initialize slots at the start of this buffer by copying data from `source`. /// @@ -58,7 +71,9 @@ extension UnsafeMutableBufferPointer { /// /// - Returns: The index after the last item that was initialized in this buffer. @inlinable - internal func _initializePrefix(copying source: UnsafeBufferPointer) -> Int { + internal func _initializePrefix( + copying source: UnsafeBufferPointer + ) -> Int { if source.isEmpty { return 0 } precondition(source.count <= self.count) unsafe self.baseAddress.unsafelyUnwrapped.initialize( @@ -66,6 +81,13 @@ extension UnsafeMutableBufferPointer { return source.count } + @inlinable + internal func _initializePrefix( + copying source: UnsafeMutableBufferPointer + ) -> Int { + unsafe _initializePrefix(copying: UnsafeBufferPointer(source)) + } + /// Initialize slots at the start of this buffer by copying data from `source`. /// /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be From 93c7cd7e2c4d55c470875d4516266bea1cb22676 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 May 2025 18:18:29 -0700 Subject: [PATCH 194/195] Add support for older 6.2 compiler builds --- Package.swift | 4 +- Sources/DequeModule/Deque+Container.swift | 4 +- Sources/DequeModule/DynamicDeque.swift | 4 +- Sources/DequeModule/RigidDeque.swift | 4 +- Sources/Future/Arrays/DynamicArray.swift | 60 ++-- Sources/Future/Arrays/NewArray.swift | 14 +- Sources/Future/Arrays/RigidArray.swift | 286 +++++++++++++++--- .../Containers/BidirectionalContainer.swift | 4 +- Sources/Future/Containers/Container.swift | 18 +- .../Containers/ContainerAlgorithms.swift | 24 +- .../Future/Containers/MutableContainer.swift | 84 ++++- .../Containers/RandomAccessContainer.swift | 6 +- .../Containers/RepeatingContainer.swift | 4 +- Sources/Future/Span/LifetimeOverride.swift | 4 + Sources/Future/Span/OutputSpan.swift | 15 + Sources/Future/Span/SpanExtensions.swift | 4 +- .../Span/UnsafeBufferPointer+Additions.swift | 6 +- Sources/Future/Tools/Box.swift | 25 +- Sources/Future/Tools/Inout.swift | 6 +- Sources/Future/Tools/Shared.swift | 23 +- .../MinimalTypeConformances.swift | 2 +- .../StaccatoContainerTests.swift | 1 + .../ArrayTests/DynamicArrayTests.swift | 1 + .../ArrayTests/RigidArrayTests.swift | 2 +- .../FutureTests/ContiguousStorageTests.swift | 1 + .../Assertions+Containers.swift | 8 +- .../ConformanceCheckers/CheckContainer.swift | 6 +- .../MinimalTypes/StaccatoContainer.swift | 2 +- 28 files changed, 482 insertions(+), 140 deletions(-) diff --git a/Package.swift b/Package.swift index 7b65673d8..ece0e0f30 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.0 +// swift-tools-version:6.2 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project @@ -56,8 +56,6 @@ let availabilityMacros: KeyValuePairs = [ "SwiftStdlib 6.0": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", "SwiftStdlib 6.2": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999", - "SwiftCompatibilitySpan 5.0": "macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.1", - "SwiftCompatibilitySpan 6.2": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", ] let extraSettings: [SwiftSetting] = [ diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift index 3fa7eba18..9cedc3514 100644 --- a/Sources/DequeModule/Deque+Container.swift +++ b/Sources/DequeModule/Deque+Container.swift @@ -20,13 +20,13 @@ extension Deque: RandomAccessContainer { _storage.value.borrowElement(at: index) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @lifetime(borrow self) public func nextSpan(after index: inout Int) -> Span { _storage.value.nextSpan(after: &index) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @lifetime(borrow self) public func previousSpan(before index: inout Int) -> Span { _storage.value.previousSpan(before: &index) diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift index b20719ae3..beb8b99c7 100644 --- a/Sources/DequeModule/DynamicDeque.swift +++ b/Sources/DequeModule/DynamicDeque.swift @@ -33,13 +33,13 @@ public struct DynamicDeque: ~Copyable { extension DynamicDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @lifetime(borrow self) public func nextSpan(after index: inout Int) -> Span { _storage.nextSpan(after: &index) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @lifetime(borrow self) public func previousSpan(before index: inout Int) -> Span { _storage.previousSpan(before: &index) diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift index f342ff77c..31b5d8a61 100644 --- a/Sources/DequeModule/RigidDeque.swift +++ b/Sources/DequeModule/RigidDeque.swift @@ -61,7 +61,7 @@ extension RigidDeque where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RigidDeque where Element: ~Copyable { @inlinable @lifetime(borrow self) @@ -78,7 +78,7 @@ extension RigidDeque where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) diff --git a/Sources/Future/Arrays/DynamicArray.swift b/Sources/Future/Arrays/DynamicArray.swift index e7e3000a4..bf91308ad 100644 --- a/Sources/Future/Arrays/DynamicArray.swift +++ b/Sources/Future/Arrays/DynamicArray.swift @@ -74,7 +74,7 @@ extension DynamicArray /*where Element: Copyable*/ { self.append(copying: contents) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public init & ~Copyable & ~Escapable>( @@ -84,7 +84,7 @@ extension DynamicArray /*where Element: Copyable*/ { self.init(consuming: RigidArray(capacity: capacity, copying: contents)) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public init & Sequence>( @@ -110,7 +110,7 @@ extension DynamicArray where Element: ~Copyable { //MARK: - Span creation extension DynamicArray where Element: ~Copyable { - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) public var span: Span { @lifetime(borrow self) @inlinable @@ -118,8 +118,9 @@ extension DynamicArray where Element: ~Copyable { _storage.span } } - - @available(SwiftCompatibilitySpan 5.0, *) + +#if compiler(>=6.2) && $InoutLifetimeDependence + @available(SwiftStdlib 6.2, *) public var mutableSpan: MutableSpan { @lifetime(&self) @inlinable @@ -127,6 +128,16 @@ extension DynamicArray where Element: ~Copyable { _storage.mutableSpan } } +#else + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + @inlinable + mutating get { + _storage.mutableSpan + } + } +#endif } //MARK: RandomAccessContainer conformance @@ -162,7 +173,7 @@ extension DynamicArray where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension DynamicArray: RandomAccessContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) @@ -181,21 +192,28 @@ extension DynamicArray: RandomAccessContainer where Element: ~Copyable { extension DynamicArray where Element: ~Copyable { @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func mutateElement(at index: Int) -> Inout { _storage.mutateElement(at: index) } @inlinable - @lifetime(&self) public mutating func swapAt(_ i: Int, _ j: Int) { _storage.swapAt(i, j) } } extension DynamicArray: MutableContainer where Element: ~Copyable { - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { _storage.nextMutableSpan(after: &index) } @@ -348,7 +366,7 @@ extension DynamicArray where Element: ~Copyable { /// whether the element should be removed from the array. /// /// - Complexity: O(`count`) - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient public mutating func removeAll( where shouldBeRemoved: (borrowing Element) throws(E) -> Bool @@ -440,7 +458,7 @@ extension DynamicArray { /// /// - Complexity: O(`newElements.count`) when amortized over many /// invocations on the same array. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient public mutating func append(copying newElements: Span) { _ensureFreeCapacity(newElements.count) @@ -475,7 +493,7 @@ extension DynamicArray { } } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) public mutating func _appendContainer< C: Container & ~Copyable & ~Escapable >( @@ -499,7 +517,7 @@ extension DynamicArray { /// /// - Complexity: O(`newElements.count`), when amortized over many invocations /// over the same array. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient public mutating func append< C: Container & ~Copyable & ~Escapable @@ -519,7 +537,7 @@ extension DynamicArray { /// /// - Complexity: O(`newElements.count`), when amortized over many invocations /// over the same array. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient public mutating func append< C: Container & Sequence @@ -636,7 +654,7 @@ extension DynamicArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func insert( copying newElements: Span, at index: Int @@ -677,7 +695,7 @@ extension DynamicArray { at: index, copying: newElements, newCount: newCount) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable internal mutating func _insertContainer< C: Container & ~Copyable & ~Escapable @@ -711,7 +729,7 @@ extension DynamicArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public mutating func insert< @@ -742,7 +760,7 @@ extension DynamicArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public mutating func insert< @@ -850,7 +868,7 @@ extension DynamicArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func replaceSubrange( _ subrange: Range, @@ -898,7 +916,7 @@ extension DynamicArray { subrange, copyingCollection: newElements, newCount: c) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func _replaceSubrange< C: Container & ~Copyable & ~Escapable @@ -937,7 +955,7 @@ extension DynamicArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable @inline(__always) public mutating func replaceSubrange< @@ -973,7 +991,7 @@ extension DynamicArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable @inline(__always) public mutating func replaceSubrange< diff --git a/Sources/Future/Arrays/NewArray.swift b/Sources/Future/Arrays/NewArray.swift index fbbc7791f..b5db4e909 100644 --- a/Sources/Future/Arrays/NewArray.swift +++ b/Sources/Future/Arrays/NewArray.swift @@ -59,7 +59,7 @@ extension NewArray: RandomAccessCollection, MutableCollection { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension NewArray: RandomAccessContainer, MutableContainer { @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { @@ -76,13 +76,21 @@ extension NewArray: RandomAccessContainer, MutableContainer { return _storage.value.previousSpan(before: &index) } +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func mutateElement(at index: Int) -> Inout { _ensureUnique() return _storage.value.mutateElement(at: index) } +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { _ensureUnique() return _storage.value.nextMutableSpan(after: &index) @@ -172,7 +180,7 @@ extension NewArray { self._storage.value.append(newElement) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func insert(_ item: consuming Element, at index: Int) { precondition(index >= 0 && index <= count) @@ -211,7 +219,7 @@ extension NewArray { } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func removeLast() -> Element { precondition(count > 0, "Cannot remove last element from empty array") diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift index edf045a6f..f10ed9e0e 100644 --- a/Sources/Future/Arrays/RigidArray.swift +++ b/Sources/Future/Arrays/RigidArray.swift @@ -13,8 +13,42 @@ import InternalCollectionsUtilities #endif -/// A manually resizable, heap allocated, noncopyable array of -/// potentially noncopyable elements. +/// A fixed capacity, heap allocated, noncopyable array of potentially +/// noncopyable elements. +/// +/// `RigidArray` instances are created with a certain maximum capacity. Elements +/// can be added to the array up to that capacity, but no more: trying to add an +/// item to a full array results in a runtime trap. +/// +/// var items = RigidArray(capacity: 2) +/// items.append(1) +/// items.append(2) +/// items.append(3) // Runtime error: RigidArray capacity overflow +/// +/// Rigid arrays provide convenience properties to help verify that it has +/// enough available capacity: `isFull` and `freeCapacity`. +/// +/// guard items.freeCapacity >= 4 else { throw CapacityOverflow() } +/// items.append(copying: newItems) +/// +/// It is possible to extend or shrink the capacity of a rigid array instance, +/// but this needs to be done explicitly, with operations dedicated to this +/// purpose (such as ``reserveCapacity`` and ``reallocate(capacity:)``). +/// The array never resizes itself automatically. +/// +/// It therefore requires careful manual analysis or up front runtime capacity +/// checks to prevent the array from overflowing its storage. This makes +/// this type more difficult to use than a dynamic array. However, it allows +/// this construct to provide predictably stable performance. +/// +/// This trading of usability in favor of stable performance limits `RigidArray` +/// to the most resource-constrained of use cases, such as space-constrained +/// environments that require carefully accounting of every heap allocation, or +/// time-constrained applications that cannot accommodate unexpected latency +/// spikes due to a reallocation getting triggered at an inopportune moment. +/// +/// For use cases outside of these narrow domains, we generally recommmend +/// the use of ``DynamicArray`` rather than `RigidArray`. @safe @frozen public struct RigidArray: ~Copyable { @@ -95,7 +129,7 @@ extension RigidArray /*where Element: Copyable*/ { self.append(copying: contents) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public init & Sequence>( @@ -116,7 +150,7 @@ extension RigidArray /*where Element: Copyable*/ { self.append(copying: contents) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public init & ~Copyable & ~Escapable>( @@ -127,7 +161,7 @@ extension RigidArray /*where Element: Copyable*/ { self.append(copying: contents) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public init & Collection>( @@ -139,6 +173,8 @@ extension RigidArray /*where Element: Copyable*/ { } } +// FIXME: init(moving:), init(consuming:) + //MARK: - Basics extension RigidArray where Element: ~Copyable { @@ -170,7 +206,7 @@ extension RigidArray where Element: ~Copyable { //MARK: - Span creation extension RigidArray where Element: ~Copyable { - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) public var span: Span { @lifetime(borrow self) @inlinable @@ -180,7 +216,8 @@ extension RigidArray where Element: ~Copyable { } } - @available(SwiftCompatibilitySpan 5.0, *) +#if compiler(>=6.2) && $InoutLifetimeDependence + @available(SwiftStdlib 6.2, *) public var mutableSpan: MutableSpan { @lifetime(&self) @inlinable @@ -189,17 +226,32 @@ extension RigidArray where Element: ~Copyable { return unsafe _overrideLifetime(result, mutating: &self) } } +#else + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + @inlinable + mutating get { + let result = unsafe MutableSpan(_unsafeElements: _items) + return unsafe _overrideLifetime(result, mutating: &self) + } + } +#endif - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable @lifetime(borrow self) internal func _span(in range: Range) -> Span { span._extracting(range) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif internal mutating func _mutableSpan( in range: Range ) -> MutableSpan { @@ -260,7 +312,7 @@ extension RigidArray where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RigidArray: RandomAccessContainer where Element: ~Copyable { @inlinable @lifetime(borrow self) @@ -277,7 +329,11 @@ extension RigidArray: RandomAccessContainer where Element: ~Copyable { extension RigidArray where Element: ~Copyable { @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func mutateElement(at index: Int) -> Inout { precondition(index >= 0 && index < _count) return unsafe Inout( @@ -295,9 +351,13 @@ extension RigidArray where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RigidArray: MutableContainer where Element: ~Copyable { +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func nextMutableSpan( after index: inout Int ) -> MutableSpan { @@ -508,7 +568,7 @@ extension RigidArray where Element: ~Copyable { /// whether the element should be removed from the array. /// /// - Complexity: O(`count`) - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient public mutating func removeAll( where shouldBeRemoved: (borrowing Element) throws(E) -> Bool @@ -551,6 +611,24 @@ extension RigidArray where Element: ~Copyable { unsafe _storage.initializeElement(at: _count, to: item) _count &+= 1 } + + /// Adds an element to the end of the array, if possible. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this returns the given item without appending it; otherwise it + /// returns nil. + /// + /// - Parameter item: The element to append to the collection. + /// - Returns: `item` if the array is full; otherwise nil. + /// + /// - Complexity: O(1) + @inlinable + public mutating func pushLast(_ item: consuming Element) -> Element? { + // FIXME: Remove this in favor of a standard algorithm. + if isFull { return item } + append(item) + return nil + } } extension RigidArray where Element: ~Copyable { @@ -576,6 +654,18 @@ extension RigidArray where Element: ~Copyable { _count &+= items.count } + /// Appends the elements of a given array to the end of this array by moving + /// them between the containers. On return, the input array becomes empty, but + /// it is not destroyed, and it preserves its original storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) @_alwaysEmitIntoClient public mutating func append( moving items: inout RigidArray @@ -588,6 +678,17 @@ extension RigidArray where Element: ~Copyable { } } + /// Appends the elements of a given container to the end of this array by + /// consuming the source container. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) @_alwaysEmitIntoClient public mutating func append( consuming items: consuming RigidArray @@ -647,7 +748,7 @@ extension RigidArray { /// - newElements: A span whose contents to copy into the array. /// /// - Complexity: O(`newElements.count`) - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient public mutating func append(copying items: Span) { unsafe items.withUnsafeBufferPointer { source in @@ -686,7 +787,7 @@ extension RigidArray { precondition(it.next() == nil, "RigidArray capacity overflow") } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable internal mutating func _appendContainer< C: Container & ~Copyable & ~Escapable @@ -708,7 +809,7 @@ extension RigidArray { /// - newElements: A container whose contents to copy into the array. /// /// - Complexity: O(`newElements.count`) - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public mutating func append & ~Copyable & ~Escapable>( @@ -726,7 +827,7 @@ extension RigidArray { /// - newElements: The new elements to copy into the array. /// /// - Complexity: O(*m*), where *m* is the length of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public mutating func append< @@ -795,6 +896,18 @@ extension RigidArray where Element: ~Copyable { _count &+= items.count } + /// Inserts the elements of a given array into the given position in this + /// array by moving them between the containers. On return, the input array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: An array whose contents to move into `self`. + /// + /// - Complexity: O(`items.count`) @_alwaysEmitIntoClient public mutating func insert( moving items: inout RigidArray, @@ -809,6 +922,17 @@ extension RigidArray where Element: ~Copyable { } } + /// Inserts the elements of a given array into the given position in this + /// array by consuming the source container. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) @_alwaysEmitIntoClient public mutating func insert( consuming items: consuming RigidArray, @@ -817,7 +941,6 @@ extension RigidArray where Element: ~Copyable { var items = items self.insert(moving: &items, at: index) } - } extension RigidArray { @@ -904,7 +1027,7 @@ extension RigidArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func insert( copying newElements: Span, at index: Int @@ -943,7 +1066,7 @@ extension RigidArray { _count += newCount } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable internal mutating func _insertContainer< C: Container & ~Copyable & ~Escapable @@ -1011,7 +1134,7 @@ extension RigidArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public mutating func insert< @@ -1043,7 +1166,7 @@ extension RigidArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @_alwaysEmitIntoClient @inline(__always) public mutating func insert< @@ -1093,34 +1216,116 @@ extension RigidArray where Element: ~Copyable { } extension RigidArray where Element: ~Copyable { + /// Replaces the specified range of elements by moving the elements of a + /// fully initialized buffer into their place. On return, the buffer is left + /// in an uninitialized state. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) @_alwaysEmitIntoClient public mutating func replaceSubrange( _ subrange: Range, - moving items: UnsafeMutableBufferPointer, + moving newElements: UnsafeMutableBufferPointer, ) { let gap = unsafe _gapForReplacement( - of: subrange, withNewCount: items.count) - let c = unsafe gap._moveInitializePrefix(from: items) - assert(c == items.count) + of: subrange, withNewCount: newElements.count) + let c = unsafe gap._moveInitializePrefix(from: newElements) + assert(c == newElements.count) } + /// Replaces the specified range of elements by moving the elements of a + /// another array into their place. On return, the source array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) @_alwaysEmitIntoClient public mutating func replaceSubrange( _ subrange: Range, - moving items: inout RigidArray, + moving newElements: inout RigidArray, ) { - unsafe items.withUnsafeMutableBufferPointer { buffer, count in + unsafe newElements.withUnsafeMutableBufferPointer { buffer, count in unsafe self.replaceSubrange(subrange, moving: buffer) count = 0 } } + /// Replaces the specified range of elements by moving the elements of a + /// given array into their place, consuming it in the process. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) @_alwaysEmitIntoClient public mutating func replaceSubrange( _ subrange: Range, - consuming items: consuming RigidArray, + consuming newElements: consuming RigidArray, ) { - replaceSubrange(subrange, moving: &items) + replaceSubrange(subrange, moving: &newElements) } } @@ -1150,8 +1355,7 @@ extension RigidArray { /// the range must be valid indices in the array. /// - newElements: The new elements to copy into the collection. /// - /// - Complexity: O(*n* + *m*), where *n* is count of this array and - /// *m* is the count of `newElements`. + /// - Complexity: O(`self.count` + `newElements.count`) @inlinable public mutating func replaceSubrange( _ subrange: Range, @@ -1188,8 +1392,7 @@ extension RigidArray { /// the range must be valid indices in the array. /// - newElements: The new elements to copy into the collection. /// - /// - Complexity: O(*n* + *m*), where *n* is count of this array and - /// *m* is the count of `newElements`. + /// - Complexity: O(`self.count` + `newElements.count`) @inlinable public mutating func replaceSubrange( _ subrange: Range, @@ -1225,9 +1428,8 @@ extension RigidArray { /// the range must be valid indices in the array. /// - newElements: The new elements to copy into the collection. /// - /// - Complexity: O(*n* + *m*), where *n* is count of this array and - /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + /// - Complexity: O(`self.count` + `newElements.count`) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func replaceSubrange( _ subrange: Range, @@ -1285,8 +1487,7 @@ extension RigidArray { /// the range must be valid indices in the array. /// - newElements: The new elements to copy into the collection. /// - /// - Complexity: O(*n* + *m*), where *n* is count of this array and - /// *m* is the count of `newElements`. + /// - Complexity: O(`self.count` + `newElements.count`) @inlinable @inline(__always) public mutating func replaceSubrange( @@ -1297,7 +1498,7 @@ extension RigidArray { subrange, copyingCollection: newElements, newCount: newElements.count) } - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable public mutating func _replaceSubrange< C: Container & ~Copyable & ~Escapable @@ -1338,9 +1539,8 @@ extension RigidArray { /// the range must be valid indices in the array. /// - newElements: The new elements to copy into the collection. /// - /// - Complexity: O(*n* + *m*), where *n* is count of this array and - /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + /// - Complexity: O(`self.count` + `newElements.count`) + @available(SwiftStdlib 6.2, *) @inlinable @inline(__always) public mutating func replaceSubrange< @@ -1380,7 +1580,7 @@ extension RigidArray { /// /// - Complexity: O(*n* + *m*), where *n* is count of this array and /// *m* is the count of `newElements`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable @inline(__always) public mutating func replaceSubrange< diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift index f22fbefa6..401658a98 100644 --- a/Sources/Future/Containers/BidirectionalContainer.swift +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { func index(before i: Index) -> Index func formIndex(before i: inout Index) @@ -18,7 +18,7 @@ public protocol BidirectionalContainer: Container, ~Copyable, ~Escapabl func previousSpan(before index: inout Index) -> Span } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension BidirectionalContainer where Self: ~Copyable & ~Escapable { @inlinable @lifetime(borrow self) diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift index f8b997fa4..948292b98 100644 --- a/Sources/Future/Containers/Container.swift +++ b/Sources/Future/Containers/Container.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public protocol Container: ~Copyable, ~Escapable { associatedtype Element: ~Copyable/* & ~Escapable*/ associatedtype Index: Comparable @@ -30,7 +30,7 @@ public protocol Container: ~Copyable, ~Escapable { limitedBy limit: Index ) - #if compiler(>=9999) // FIXME: We can't do this yet + #if compiler(>=9999) // FIXME: Needs borrow accessors subscript(index: Index) -> Element { borrow } #else @lifetime(borrow self) @@ -83,7 +83,7 @@ public protocol Container: ~Copyable, ~Escapable { func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable public func _defaultIndex(_ i: Index, advancedBy distance: Int) -> Index { @@ -171,7 +171,7 @@ extension Container where Self: ~Copyable & ~Escapable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable public var isEmpty: Bool { @@ -215,13 +215,13 @@ extension Container where Self: ~Copyable & ~Escapable { public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: Sequence { @inlinable public var underestimatedCount: Int { count } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: Collection { // Resolve ambiguities between default implementations between Collection // and Container. @@ -256,7 +256,7 @@ extension Container where Self: Collection { public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable public subscript(index: Index) -> Element { @@ -269,7 +269,7 @@ extension Container where Self: ~Copyable & ~Escapable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable @lifetime(borrow self) @@ -287,7 +287,7 @@ extension Container where Self: ~Copyable & ~Escapable { #if false // DEMO -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { // This is just to demo the bulk iteration model func forEachSpan(_ body: (Span) throws(E) -> Void) throws(E) { diff --git a/Sources/Future/Containers/ContainerAlgorithms.swift b/Sources/Future/Containers/ContainerAlgorithms.swift index 14543889e..d1c97467b 100644 --- a/Sources/Future/Containers/ContainerAlgorithms.swift +++ b/Sources/Future/Containers/ContainerAlgorithms.swift @@ -11,7 +11,7 @@ //MARK: - firstIndex(where:) -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable internal func _containerFirstIndex( @@ -55,7 +55,7 @@ extension Container where Self: ~Copyable & ~Escapable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: Collection { /// Returns the first index that addresses an element of the container that /// satisfies the given predicate. @@ -83,7 +83,7 @@ extension Container where Self: Collection { //MARK: - firstIndex(of:) -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Element: Equatable { @inlinable internal func _containerFirstIndex(of element: Element) -> Index? { @@ -110,7 +110,7 @@ extension Container where Element: Equatable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: Collection, Element: Equatable { /// Returns the first index where the specified value appears in the container. /// @@ -131,7 +131,7 @@ extension Container where Self: Collection, Element: Equatable { //MARK: - lastIndex(where:) -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension BidirectionalContainer where Self: ~Copyable & ~Escapable { @inlinable internal func _containerLastIndex( @@ -175,7 +175,7 @@ extension BidirectionalContainer where Self: ~Copyable & ~Escapable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension BidirectionalContainer where Self: BidirectionalCollection { /// Returns the first index that addresses an element of the container that /// satisfies the given predicate. @@ -203,7 +203,7 @@ extension BidirectionalContainer where Self: BidirectionalCollection { //MARK: - lastIndex(of:) -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension BidirectionalContainer where Element: Equatable { @inlinable internal func _containerLastIndex(of element: Element) -> Index? { @@ -230,7 +230,7 @@ extension BidirectionalContainer where Element: Equatable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension BidirectionalContainer where Self: BidirectionalCollection, Element: Equatable { /// Returns the first index where the specified value appears in the container. /// @@ -251,7 +251,7 @@ extension BidirectionalContainer where Self: BidirectionalCollection, Element: E //MARK: - elementsEqual(_:,by:) -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable internal func _spanwiseZip( @@ -345,7 +345,7 @@ extension Container where Self: ~Copyable & ~Escapable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: Sequence { /// Returns a Boolean value indicating whether this container and another /// container contain equivalent elements in the same order, using the given @@ -379,7 +379,7 @@ extension Container where Self: Sequence { //MARK: - elementsEqual(_:) -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable, Element: Equatable { /// Returns a Boolean value indicating whether this container and another /// container contain the same elements in the same order. @@ -398,7 +398,7 @@ extension Container where Self: ~Copyable & ~Escapable, Element: Equatable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: Sequence, Element: Equatable { /// Returns a Boolean value indicating whether this container and another /// container contain the same elements in the same order. diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index 05197d4b0..368f7f532 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -9,17 +9,26 @@ // //===----------------------------------------------------------------------===// -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public protocol MutableContainer: Container, ~Copyable, ~Escapable { -#if compiler(>=9999) // FIXME: We can't do this yet +#if compiler(>=9999) // FIXME: Needs borrow accessors subscript(index: Index) -> Element { borrow mutate } #else + +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif @lifetime(self: copy self) mutating func mutateElement(at index: Index) -> Inout #endif +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif @lifetime(self: copy self) mutating func nextMutableSpan(after index: inout Index) -> MutableSpan @@ -29,39 +38,86 @@ public protocol MutableContainer: Container, ~Copyable, ~Escapable { mutating func swapAt(_ i: Index, _ j: Index) } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension MutableContainer where Self: ~Copyable & ~Escapable { - #if false // Hm... +#if false // FIXME: This has flagrant exclusivity violations. +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) - mutating func nextMutableSpan( +#else + @lifetime(borrow self) +#endif + public mutating func nextMutableSpan( after index: inout Index, maximumCount: Int ) -> MutableSpan { - let original = index + let start = index + do { + let span = self.nextMutableSpan(after: &index) + guard span.count > maximumCount else { return span } + } + // Index remains within the same span, so offseting it is expected to be quick + let end = self.index(start, offsetBy: maximumCount) + index = start var span = self.nextMutableSpan(after: &index) - if span.count > maximumCount { - span = span.extracting(first: maximumCount) - // Index remains within the same span, so offseting it is expected to be quick - index = self.index(original, offsetBy: maximumCount) + let extract = span.extracting(first: maximumCount) // FIXME: Oops + index = end + return extract + } +#endif + + @lifetime(self: copy self) + public mutating func withNextMutableSpan< + E: Error, R: ~Copyable + >( + after index: inout Index, maximumCount: Int, + body: (inout MutableSpan) throws(E) -> R + ) throws(E) -> R { + // FIXME: We don't want this to be closure-based, but MutableSpan cannot be sliced "in place". + let start = index + do { + var span = self.nextMutableSpan(after: &index) + if span.count <= maximumCount { + return try body(&span) + } } - return span + // Try again, but figure out what our target index is first. + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(start, offsetBy: maximumCount) + var i = start + var span = self.nextMutableSpan(after: &i) + var extract = span.extracting(first: maximumCount) + return try body(&extract) } - #endif +#if compiler(>=6.2) && $InoutLifetimeDependence @inlinable public subscript(index: Index) -> Element { @lifetime(borrow self) unsafeAddress { unsafe borrowElement(at: index)._pointer } - + @lifetime(&self) unsafeMutableAddress { unsafe mutateElement(at: index)._pointer } } +#else + @inlinable + public subscript(index: Index) -> Element { + @lifetime(borrow self) + unsafeAddress { + unsafe borrowElement(at: index)._pointer + } + + @lifetime(borrow self) + unsafeMutableAddress { + unsafe mutateElement(at: index)._pointer + } + } +#endif } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension MutableContainer where Self: ~Copyable & ~Escapable { /// Moves all elements satisfying `isSuffixElement` into a suffix of the /// container, returning the start position of the resulting suffix. diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift index 7caf20946..7da0a0c7f 100644 --- a/Sources/Future/Containers/RandomAccessContainer.swift +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -9,11 +9,11 @@ // //===----------------------------------------------------------------------===// -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public protocol RandomAccessContainer : BidirectionalContainer, ~Copyable, ~Escapable {} -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RandomAccessContainer where Self: ~Copyable & ~Escapable, Index: Strideable, Index.Stride == Int { @@ -83,7 +83,7 @@ where Self: ~Copyable & ~Escapable, Index: Strideable, Index.Stride == Int // Disambiguate parallel extensions on RandomAccessContainer and RandomAccessCollection -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RandomAccessCollection where Self: RandomAccessContainer, diff --git a/Sources/Future/Containers/RepeatingContainer.swift b/Sources/Future/Containers/RepeatingContainer.swift index 9285f90e1..712dbdcaa 100644 --- a/Sources/Future/Containers/RepeatingContainer.swift +++ b/Sources/Future/Containers/RepeatingContainer.swift @@ -53,7 +53,7 @@ extension RepeatingContainer where Element: ~Copyable { } #if false // FIXME: This is what we'd want -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { @@ -85,7 +85,7 @@ extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { } } #else -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { @lifetime(borrow self) public func borrowElement(at index: Int) -> Borrow { diff --git a/Sources/Future/Span/LifetimeOverride.swift b/Sources/Future/Span/LifetimeOverride.swift index e6fc96abe..8faa9e6cd 100644 --- a/Sources/Future/Span/LifetimeOverride.swift +++ b/Sources/Future/Span/LifetimeOverride.swift @@ -53,7 +53,11 @@ public func _overrideLifetime< @_unsafeNonescapableResult @_alwaysEmitIntoClient @_transparent +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&source) +#else +@lifetime(borrow source) +#endif public func _overrideLifetime< T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable diff --git a/Sources/Future/Span/OutputSpan.swift b/Sources/Future/Span/OutputSpan.swift index b54ea24f9..bf4fe6574 100644 --- a/Sources/Future/Span/OutputSpan.swift +++ b/Sources/Future/Span/OutputSpan.swift @@ -363,6 +363,7 @@ extension OutputSpan where Element: ~Copyable { } } +#if compiler(>=6.2) && $InoutLifetimeDependence @_alwaysEmitIntoClient public var mutableSpan: MutableSpan { @lifetime(&self) @@ -375,6 +376,20 @@ extension OutputSpan where Element: ~Copyable { return unsafe _overrideLifetime(span, mutating: &self) } } +#else + @_alwaysEmitIntoClient + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + mutating get { + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeMutableBufferPointer( + start: pointer, count: _initialized + ) + let span = unsafe MutableSpan(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } +#endif } @available(macOS 9999, *) diff --git a/Sources/Future/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift index f8ea9fb5b..a1af10451 100644 --- a/Sources/Future/Span/SpanExtensions.swift +++ b/Sources/Future/Span/SpanExtensions.swift @@ -13,7 +13,7 @@ @usableFromInline internal let immortalThing: [Void] = [] -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Span where Element: ~Copyable { public static var empty: Span { @lifetime(immortal) @@ -32,7 +32,7 @@ extension Span where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Span where Element: Equatable { /// Returns a Boolean value indicating whether this and another span /// contain equal elements in the same order. diff --git a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift index 860400d52..020eb6ee1 100644 --- a/Sources/Future/Span/UnsafeBufferPointer+Additions.swift +++ b/Sources/Future/Span/UnsafeBufferPointer+Additions.swift @@ -96,7 +96,7 @@ extension UnsafeMutableBufferPointer { /// The `source` span must fit entirely in `self`. /// /// - Returns: The index after the last item that was initialized in this buffer. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable internal func _initializePrefix(copying source: Span) -> Int { unsafe source.withUnsafeBufferPointer { unsafe self._initializePrefix(copying: $0) } @@ -114,7 +114,7 @@ extension UnsafeMutableBufferPointer { /// /// - Returns: A pair of values `(count, end)`, where `count` is the number of items that were /// successfully initialized, and `end` is the index into `items` after the last copied item. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable internal func _initializePrefix< C: Container & ~Copyable & ~Escapable @@ -157,7 +157,7 @@ extension UnsafeMutableBufferPointer { /// entirely uninitialized. /// /// The count of `span` must not be greater than `self.count`. - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) @inlinable internal mutating func _initializeAndDropPrefix(copying span: Span) { unsafe span.withUnsafeBufferPointer { buffer in diff --git a/Sources/Future/Tools/Box.swift b/Sources/Future/Tools/Box.swift index 5f636c86c..5b94e655f 100644 --- a/Sources/Future/Tools/Box.swift +++ b/Sources/Future/Tools/Box.swift @@ -40,9 +40,9 @@ extension Box where T: ~Copyable { return result } - @lifetime(immortal) @_alwaysEmitIntoClient @_transparent + @lifetime(immortal) public consuming func leak() -> Inout { let result = unsafe Inout(unsafeImmortalAddress: _pointer) discard self @@ -62,16 +62,20 @@ extension Box where T: ~Copyable { } } - @lifetime(borrow self) @_alwaysEmitIntoClient @_transparent + @lifetime(borrow self) public func borrow() -> Borrow { unsafe Borrow(unsafeAddress: UnsafePointer(_pointer), borrowing: self) } - @lifetime(&self) @_alwaysEmitIntoClient @_transparent +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func mutate() -> Inout { unsafe Inout(unsafeAddress: _pointer, mutating: &self) } @@ -86,7 +90,7 @@ extension Box where T: Copyable { } extension Box where T: ~Copyable { - @available(SwiftCompatibilitySpan 5.0, *) + @available(SwiftStdlib 6.2, *) public var span: Span { @_alwaysEmitIntoClient @lifetime(borrow self) @@ -95,7 +99,8 @@ extension Box where T: ~Copyable { } } - @available(SwiftCompatibilitySpan 5.0, *) +#if compiler(>=6.2) && $InoutLifetimeDependence + @available(SwiftStdlib 6.2, *) public var mutableSpan: MutableSpan { @_alwaysEmitIntoClient @lifetime(&self) @@ -103,4 +108,14 @@ extension Box where T: ~Copyable { unsafe MutableSpan(_unsafeStart: _pointer, count: 1) } } +#else + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating get { + unsafe MutableSpan(_unsafeStart: _pointer, count: 1) + } + } +#endif } diff --git a/Sources/Future/Tools/Inout.swift b/Sources/Future/Tools/Inout.swift index b4e7a5789..9c06f9aa7 100644 --- a/Sources/Future/Tools/Inout.swift +++ b/Sources/Future/Tools/Inout.swift @@ -41,10 +41,14 @@ public struct Inout: ~Copyable, ~Escapable { /// instance of type 'T'. /// - Parameter owner: The owning instance that this 'Inout' instance's /// lifetime is based on. - @lifetime(&owner) @unsafe @_alwaysEmitIntoClient @_transparent +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&owner) +#else + @lifetime(borrow owner) +#endif public init( unsafeAddress: UnsafeMutablePointer, mutating owner: inout Owner diff --git a/Sources/Future/Tools/Shared.swift b/Sources/Future/Tools/Shared.swift index 7eaaee1ce..1baf0d3ea 100644 --- a/Sources/Future/Tools/Shared.swift +++ b/Sources/Future/Tools/Shared.swift @@ -109,6 +109,7 @@ extension Shared where Storage: ~Copyable { } extension Shared where Storage: ~Copyable { +#if compiler(>=6.2) && $InoutLifetimeDependence @inlinable @inline(__always) public var value: Storage { @@ -122,6 +123,21 @@ extension Shared where Storage: ~Copyable { return unsafe _mutableAddress } } +#else + @inlinable + @inline(__always) + public var value: Storage { + @lifetime(borrow self) + unsafeAddress { + unsafe _address + } + @lifetime(borrow self) + unsafeMutableAddress { + precondition(isUnique()) + return unsafe _mutableAddress + } + } +#endif } extension Shared where Storage: ~Copyable { @@ -133,9 +149,14 @@ extension Shared where Storage: ~Copyable { // lifetime(self) == lifetime(_box.storage). unsafe Borrow(unsafeAddress: _address, borrowing: self) } - + + @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence @lifetime(&self) +#else + @lifetime(borrow self) +#endif public mutating func mutate() -> Inout { // This is gloriously (and very explicitly) unsafe, as it should be. // `Shared` is carefully constructed to guarantee that diff --git a/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift b/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift index f088532b7..6251d4630 100644 --- a/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift +++ b/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift @@ -47,7 +47,7 @@ final class MinimalTypeTests: CollectionTestCase { #if false // FIXME: Track down compiler crash func testTestContainer() { - guard #available(SwiftCompatibilitySpan 5.0, *) else { return } + guard #available(SwiftStdlib 6.2, *) else { return } let a = TestContainer( contents: RigidArray(count: 100, initializedWith: { $0 }), spanCounts: [3, 1, 7]) diff --git a/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift b/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift index f121d41a0..2e8fa873a 100644 --- a/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift +++ b/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift @@ -13,6 +13,7 @@ import XCTest import _CollectionsTestSupport import Future +@available(SwiftStdlib 6.2, *) class StaccatoContainerTests: CollectionTestCase { func checkStriding( _ container: borrowing C, diff --git a/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift b/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift index e45f14c68..2610f5345 100644 --- a/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift +++ b/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift @@ -14,6 +14,7 @@ import _CollectionsTestSupport import Future import Synchronization +@available(SwiftStdlib 6.2, *) class DynamicArrayTests: CollectionTestCase { func test_validate_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in diff --git a/Tests/FutureTests/ArrayTests/RigidArrayTests.swift b/Tests/FutureTests/ArrayTests/RigidArrayTests.swift index 171f63055..056d5ddf2 100644 --- a/Tests/FutureTests/ArrayTests/RigidArrayTests.swift +++ b/Tests/FutureTests/ArrayTests/RigidArrayTests.swift @@ -13,7 +13,7 @@ import XCTest import _CollectionsTestSupport import Future -@available(SwiftStdlib 6.0, *) +@available(SwiftStdlib 6.2, *) class RigidArrayTests: CollectionTestCase { func test_validate_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift index 1862794f3..7d5174699 100644 --- a/Tests/FutureTests/ContiguousStorageTests.swift +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -12,6 +12,7 @@ import XCTest import Future +@available(SwiftStdlib 6.2, *) final class ContiguousStorageTests: XCTestCase { @available(SwiftStdlib 6.2, *) diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift index 6b66bac87..81816702b 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift @@ -12,7 +12,7 @@ import Future -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public func expectContainersWithEquivalentElements< C1: Container & ~Copyable & ~Escapable, C2: Container & ~Copyable & ~Escapable @@ -32,7 +32,7 @@ public func expectContainersWithEquivalentElements< } /// Check if `left` and `right` contain equal elements in the same order. -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public func expectContainersWithEqualElements< Element: Equatable, C1: Container & ~Copyable & ~Escapable, @@ -52,7 +52,7 @@ public func expectContainersWithEqualElements< } /// Check if `left` and `right` contain equal elements in the same order. -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public func expectContainerContents< Element: Equatable, C1: Container & ~Copyable & ~Escapable, @@ -92,7 +92,7 @@ public func expectContainerContents< } /// Check if `left` and `right` contain equal elements in the same order. -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) public func expectContainerContents< C1: Container & ~Copyable & ~Escapable, C2: Collection, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift index 1e4b58b2c..335bd4fb9 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift @@ -12,7 +12,7 @@ import XCTest import Future -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension Container where Self: ~Copyable & ~Escapable { @inlinable internal func _indicesByIndexAfter() -> [Index] { @@ -37,7 +37,7 @@ extension Container where Self: ~Copyable & ~Escapable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) @inlinable public func checkContainer< C: Container & ~Copyable & ~Escapable, @@ -55,7 +55,7 @@ public func checkContainer< file: file, line: line) } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) @inlinable public func checkContainer< C: Container & ~Copyable & ~Escapable, diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift index eb2f3ca16..ae3517bef 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift @@ -80,7 +80,7 @@ extension StaccatoContainer where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) +@available(SwiftStdlib 6.2, *) extension StaccatoContainer: Container where Element: ~Copyable { public func formIndex(_ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index) { precondition(i >= startIndex && i <= endIndex) From 6385ebbe4d5fd7235dca833a4b041b14f06d46cf Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 May 2025 18:23:22 -0700 Subject: [PATCH 195/195] More workarounds for crashes in older 6.2 compiler builds --- Sources/Future/Containers/ContiguousContainer.swift | 2 ++ Sources/Future/Containers/MutableContainer.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Sources/Future/Containers/ContiguousContainer.swift b/Sources/Future/Containers/ContiguousContainer.swift index 7f966a16a..9f25b2b38 100644 --- a/Sources/Future/Containers/ContiguousContainer.swift +++ b/Sources/Future/Containers/ContiguousContainer.swift @@ -48,8 +48,10 @@ where Self: ~Copyable & ~Escapable, Index == Int { @available(SwiftStdlib 6.2, *) extension Array: ContiguousContainer {} +#if compiler(>=6.2) && $InoutLifetimeDependence // FIXME: Crashes older 6.2 compilers. @available(SwiftStdlib 6.2, *) extension ContiguousArray: ContiguousContainer {} +#endif @available(SwiftStdlib 6.2, *) extension CollectionOfOne: ContiguousContainer {} diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift index 368f7f532..ee846f245 100644 --- a/Sources/Future/Containers/MutableContainer.swift +++ b/Sources/Future/Containers/MutableContainer.swift @@ -64,6 +64,7 @@ extension MutableContainer where Self: ~Copyable & ~Escapable { } #endif +#if compiler(>=6.2) && $InoutLifetimeDependence // FIXME: Crashes older 6.2 compilers. @lifetime(self: copy self) public mutating func withNextMutableSpan< E: Error, R: ~Copyable @@ -87,6 +88,7 @@ extension MutableContainer where Self: ~Copyable & ~Escapable { var extract = span.extracting(first: maximumCount) return try body(&extract) } +#endif #if compiler(>=6.2) && $InoutLifetimeDependence @inlinable