diff --git a/Runtimes/Core/core/CMakeLists.txt b/Runtimes/Core/core/CMakeLists.txt index 8d63c32504a48..bb65771d3db92 100644 --- a/Runtimes/Core/core/CMakeLists.txt +++ b/Runtimes/Core/core/CMakeLists.txt @@ -154,6 +154,8 @@ add_library(swiftCore Span/RawSpan.swift Span/MutableSpan.swift Span/MutableRawSpan.swift + Span/OutputSpan.swift + Span/OutputRawSpan.swift StaticString.swift StaticPrint.swift Stride.swift diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index c1d638818169a..ee0bace1ef00d 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -158,6 +158,8 @@ split_embedded_sources( EMBEDDED Sort.swift EMBEDDED Span/MutableRawSpan.swift EMBEDDED Span/MutableSpan.swift + EMBEDDED Span/OutputRawSpan.swift + EMBEDDED Span/OutputSpan.swift EMBEDDED Span/RawSpan.swift EMBEDDED Span/Span.swift EMBEDDED StaticString.swift diff --git a/stdlib/public/core/GroupInfo.json b/stdlib/public/core/GroupInfo.json index ab9f72243d030..685f038aacb9b 100644 --- a/stdlib/public/core/GroupInfo.json +++ b/stdlib/public/core/GroupInfo.json @@ -202,6 +202,8 @@ "Span": [ "MutableRawSpan.swift", "MutableSpan.swift", + "OutputRawSpan.swift", + "OutputSpan.swift", "RawSpan.swift", "Span.swift" ], diff --git a/stdlib/public/core/InlineArray.swift b/stdlib/public/core/InlineArray.swift index 1e90bc20bed6a..c07fd6e71667e 100644 --- a/stdlib/public/core/InlineArray.swift +++ b/stdlib/public/core/InlineArray.swift @@ -296,6 +296,25 @@ extension InlineArray where Element: ~Copyable { } #else fatalError() +#endif + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public init( + initializingWith initializer: (inout OutputSpan) throws(E) -> Void + ) throws(E) { +#if $BuiltinEmplaceTypedThrows + _storage = try Builtin.emplace { (rawPtr) throws(E) -> () in + let buffer = unsafe Self._initializationBuffer(start: rawPtr) + _internalInvariant(Self.count == buffer.count) + var output = unsafe OutputSpan(buffer: buffer, initializedCount: 0) + try initializer(&output) + let initialized = unsafe output.finalize(for: buffer) + _precondition(count == initialized, "InlineArray initialization underflow") + } +#else + fatalError() #endif } } diff --git a/stdlib/public/core/Span/MutableRawSpan.swift b/stdlib/public/core/Span/MutableRawSpan.swift index c2c8a6171a8c9..0af43453505bb 100644 --- a/stdlib/public/core/Span/MutableRawSpan.swift +++ b/stdlib/public/core/Span/MutableRawSpan.swift @@ -360,108 +360,6 @@ extension MutableRawSpan { } } -// FIXME: The functions in this extension crash the SIL optimizer when built inside -// the stub. But these declarations don't generate a public symbol anyway. -#if !SPAN_COMPATIBILITY_STUB - -//MARK: copyMemory -@available(SwiftCompatibilitySpan 5.0, *) -@_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) -extension MutableRawSpan { - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - 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 - @lifetime(self: copy self) - 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 - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: C - ) -> Int where C.Element: BitwiseCopyable { - let newOffset = source.withContiguousStorageIfAvailable { - self.update(fromContentsOf: unsafe 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 - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: Span - ) -> Int { -// update(from: source.bytes) - unsafe source.withUnsafeBytes { - unsafe update(fromContentsOf: $0) - } - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: borrowing MutableSpan - ) -> Int { -// update(from: source.span.bytes) - unsafe source.withUnsafeBytes { - unsafe update(fromContentsOf: $0) - } - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: RawSpan - ) -> Int { - _precondition( - source.byteCount <= self.byteCount, - "destination span cannot contain every byte from source." - ) - if source.byteCount == 0 { return 0 } - unsafe source.withUnsafeBytes { - unsafe _start().copyMemory(from: $0.baseAddress!, byteCount: $0.count) - } - return source.byteCount - } - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func update( - fromContentsOf source: borrowing MutableRawSpan - ) -> Int { - update(fromContentsOf: source.bytes) - } -} -#endif - // MARK: sub-spans @available(SwiftCompatibilitySpan 5.0, *) @_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) @@ -482,13 +380,31 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) + mutating public func _mutatingExtracting(_ 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 _mutatingExtracting(unchecked: bounds) + } + + @available(*, deprecated, renamed: "_mutatingExtracting(_:)") + @_alwaysEmitIntoClient + @lifetime(&self) mutating public func extracting(_ bounds: Range) -> Self { + _mutatingExtracting(bounds) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(_ 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) + return unsafe _consumingExtracting(unchecked: bounds) } /// Constructs a new span over the items within the supplied range of @@ -509,12 +425,29 @@ extension MutableRawSpan { @unsafe @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(unchecked bounds: Range) -> Self { + mutating public func _mutatingExtracting(unchecked bounds: Range) -> Self { let newStart = unsafe _pointer?.advanced(by: bounds.lowerBound) let newSpan = unsafe Self(_unchecked: newStart, byteCount: bounds.count) return unsafe _overrideLifetime(newSpan, mutating: &self) } + @unsafe + @available(*, deprecated, renamed: "_mutatingExtracting(unchecked:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(unchecked bounds: Range) -> Self { + unsafe _mutatingExtracting(unchecked: bounds) + } + + @unsafe + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(unchecked bounds: Range) -> Self { + let newStart = unsafe _pointer?.advanced(by: bounds.lowerBound) + let newSpan = unsafe Self(_unchecked: newStart, byteCount: bounds.count) + return unsafe _overrideLifetime(newSpan, copying: self) + } + /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -530,10 +463,27 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) + mutating public func _mutatingExtracting( + _ bounds: some RangeExpression + ) -> Self { + _mutatingExtracting(bounds.relative(to: byteOffsets)) + } + + @available(*, deprecated, renamed: "_mutatingExtracting(_:)") + @_alwaysEmitIntoClient + @lifetime(&self) mutating public func extracting( _ bounds: some RangeExpression ) -> Self { - extracting(bounds.relative(to: byteOffsets)) + _mutatingExtracting(bounds) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting( + _ bounds: some RangeExpression + ) -> Self { + _consumingExtracting(bounds.relative(to: byteOffsets)) } /// Constructs a new span over the items within the supplied range of @@ -554,11 +504,35 @@ extension MutableRawSpan { @unsafe @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(unchecked bounds: ClosedRange) -> Self { + mutating public func _mutatingExtracting( + unchecked bounds: ClosedRange + ) -> Self { + let range = unsafe Range( + _uncheckedBounds: (bounds.lowerBound, bounds.upperBound + 1) + ) + return unsafe _mutatingExtracting(unchecked: range) + } + + @unsafe + @available(*, deprecated, renamed: "_mutatingExtracting(unchecked:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting( + unchecked bounds: ClosedRange + ) -> Self { + unsafe _mutatingExtracting(unchecked: bounds) + } + + @unsafe + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting( + unchecked bounds: ClosedRange + ) -> Self { let range = unsafe Range( - _uncheckedBounds: (bounds.lowerBound, bounds.upperBound+1) + _uncheckedBounds: (bounds.lowerBound, bounds.upperBound + 1) ) - return unsafe extracting(unchecked: range) + return unsafe _consumingExtracting(unchecked: range) } /// Constructs a new span over all the items of this span. @@ -572,10 +546,23 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(_: UnboundedRange) -> Self { + mutating public func _mutatingExtracting(_: UnboundedRange) -> Self { let newSpan = unsafe Self(_unchecked: _pointer, byteCount: _count) return unsafe _overrideLifetime(newSpan, mutating: &self) } + + @available(*, deprecated, renamed: "_mutatingExtracting(_:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(_: UnboundedRange) -> Self { + _mutatingExtracting(...) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(_: UnboundedRange) -> Self { + self + } } // MARK: prefixes and suffixes @@ -600,7 +587,7 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(first maxLength: Int) -> Self { + mutating public func _mutatingExtracting(first maxLength: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(maxLength >= 0, "Can't have a prefix of negative length") let newCount = min(maxLength, byteCount) @@ -611,6 +598,26 @@ extension MutableRawSpan { #endif } + @available(*, deprecated, renamed: "_mutatingExtracting(first:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(first maxLength: Int) -> Self { + _mutatingExtracting(first: maxLength) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(first maxLength: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = min(maxLength, byteCount) + let newSpan = unsafe Self(_unchecked: _pointer, byteCount: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") +#endif + } + /// 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 @@ -627,7 +634,7 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(droppingLast k: Int) -> Self { + mutating public func _mutatingExtracting(droppingLast k: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, byteCount) @@ -639,6 +646,27 @@ extension MutableRawSpan { #endif } + @available(*, deprecated, renamed: "_mutatingExtracting(droppingLast:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(droppingLast k: Int) -> Self { + _mutatingExtracting(droppingLast: k) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(droppingLast k: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _precondition(k >= 0, "Can't drop a negative number of elements") + let droppedCount = min(k, byteCount) + let newCount = byteCount &- droppedCount + let newSpan = unsafe Self(_unchecked: _pointer, byteCount: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") +#endif + } + /// Returns a span containing the final elements of the span, /// up to the given maximum length. /// @@ -656,12 +684,37 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) + mutating public func _mutatingExtracting(last maxLength: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _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 = unsafe Self(_unchecked: newStart, byteCount: newCount) + return unsafe _overrideLifetime(newSpan, mutating: &self) +#else + fatalError("Unsupported compiler") +#endif + } + + @available(*, deprecated, renamed: "_mutatingExtracting(last:)") + @_alwaysEmitIntoClient + @lifetime(&self) mutating public func extracting(last maxLength: Int) -> Self { + _mutatingExtracting(last: maxLength) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(last maxLength: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _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 = unsafe Self(_unchecked: newStart, byteCount: newCount) return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") +#endif } /// Returns a span over all but the given number of initial elements. @@ -680,7 +733,7 @@ extension MutableRawSpan { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(droppingFirst k: Int) -> Self { + mutating public func _mutatingExtracting(droppingFirst k: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(k >= 0, "Can't drop a negative number of bytes") let droppedCount = min(k, byteCount) @@ -690,6 +743,28 @@ extension MutableRawSpan { return unsafe _overrideLifetime(newSpan, mutating: &self) #else fatalError("Unsupported compiler") +#endif + } + + @available(*, deprecated, renamed: "_mutatingExtracting(droppingFirst:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(droppingFirst k: Int) -> Self { + _mutatingExtracting(droppingFirst: k) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(droppingFirst k: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _precondition(k >= 0, "Can't drop a negative number of bytes") + let droppedCount = min(k, byteCount) + let newStart = unsafe _pointer?.advanced(by: droppedCount) + let newCount = byteCount &- droppedCount + let newSpan = unsafe Self(_unchecked: newStart, byteCount: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") #endif } } diff --git a/stdlib/public/core/Span/MutableSpan.swift b/stdlib/public/core/Span/MutableSpan.swift index fc9c4f6cd7d4d..c4c88282621a9 100644 --- a/stdlib/public/core/Span/MutableSpan.swift +++ b/stdlib/public/core/Span/MutableSpan.swift @@ -355,56 +355,6 @@ extension MutableSpan where Element: ~Copyable { } } -@available(SwiftCompatibilitySpan 5.0, *) -@_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) -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] - } - @lifetime(self: copy self) - 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 - ) - } - @lifetime(self: copy self) - set { - let offset = position&*MemoryLayout.stride - unsafe _start().storeBytes( - of: newValue, toByteOffset: offset, as: Element.self - ) - } - } -} - @available(SwiftCompatibilitySpan 5.0, *) @_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) extension MutableSpan where Element: ~Copyable { @@ -476,220 +426,6 @@ extension MutableSpan { 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: unsafe 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 - unsafe 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(SwiftCompatibilitySpan 5.0, *) -@_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) -extension MutableSpan where Element: ~Copyable { - -// @_alwaysEmitIntoClient -// 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 source = OutputSpan(_initializing: source, initialized: source.count) -// return self.moveUpdate(fromContentsOf: source) - unsafe withUnsafeMutableBufferPointer { - unsafe $0.moveUpdate(fromContentsOf: source) - } - } -} - -@available(SwiftCompatibilitySpan 5.0, *) -@_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) -extension MutableSpan { - - @_alwaysEmitIntoClient - @lifetime(self: copy self) - public mutating func moveUpdate( - fromContentsOf source: Slice> - ) -> Index { - unsafe moveUpdate(fromContentsOf: .init(rebasing: source)) - } -} - -@available(SwiftCompatibilitySpan 5.0, *) -@_originallyDefinedIn(module: "Swift;CompatibilitySpan", SwiftCompatibilitySpan 6.2) -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 = unsafe _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: unsafe 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." - ) - unsafe 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 @@ -712,13 +448,31 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) + mutating public func _mutatingExtracting(_ 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 _mutatingExtracting(unchecked: bounds) + } + + @available(*, deprecated, renamed: "_mutatingExtracting(_:)") + @_alwaysEmitIntoClient + @lifetime(&self) mutating public func extracting(_ bounds: Range) -> Self { + _mutatingExtracting(bounds) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(_ 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) + return unsafe _consumingExtracting(unchecked: bounds) } /// Constructs a new span over the items within the supplied range of @@ -739,13 +493,31 @@ extension MutableSpan where Element: ~Copyable { @unsafe @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(unchecked bounds: Range) -> Self { + mutating public func _mutatingExtracting(unchecked bounds: Range) -> Self { let delta = bounds.lowerBound &* MemoryLayout.stride let newStart = unsafe _pointer?.advanced(by: delta) let newSpan = unsafe Self(_unchecked: newStart, count: bounds.count) return unsafe _overrideLifetime(newSpan, mutating: &self) } + @unsafe + @available(*, deprecated, renamed: "_mutatingExtracting(unchecked:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(unchecked bounds: Range) -> Self { + unsafe _mutatingExtracting(unchecked: bounds) + } + + @unsafe + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(unchecked bounds: Range) -> Self { + let delta = bounds.lowerBound &* MemoryLayout.stride + let newStart = unsafe _pointer?.advanced(by: delta) + let newSpan = unsafe Self(_unchecked: newStart, count: bounds.count) + return unsafe _overrideLifetime(newSpan, copying: self) + } + /// Constructs a new span over the items within the supplied range of /// positions within this span. /// @@ -761,10 +533,27 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) + mutating public func _mutatingExtracting( + _ bounds: some RangeExpression + ) -> Self { + _mutatingExtracting(bounds.relative(to: indices)) + } + + @available(*, deprecated, renamed: "_mutatingExtracting(_:)") + @_alwaysEmitIntoClient + @lifetime(&self) mutating public func extracting( _ bounds: some RangeExpression ) -> Self { - extracting(bounds.relative(to: indices)) + _mutatingExtracting(bounds) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting( + _ bounds: some RangeExpression + ) -> Self { + _consumingExtracting(bounds.relative(to: indices)) } /// Constructs a new span over the items within the supplied range of @@ -785,13 +574,35 @@ extension MutableSpan where Element: ~Copyable { @unsafe @_alwaysEmitIntoClient @lifetime(&self) + mutating public func _mutatingExtracting( + unchecked bounds: ClosedRange + ) -> Self { + let range = unsafe Range( + _uncheckedBounds: (bounds.lowerBound, bounds.upperBound + 1) + ) + return unsafe _mutatingExtracting(unchecked: range) + } + + @unsafe + @available(*, deprecated, renamed: "_mutatingExtracting(unchecked:)") + @_alwaysEmitIntoClient + @lifetime(&self) mutating public func extracting( unchecked bounds: ClosedRange + ) -> Self { + unsafe _mutatingExtracting(unchecked: bounds) + } + + @unsafe + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting( + unchecked bounds: ClosedRange ) -> Self { let range = unsafe Range( - _uncheckedBounds: (bounds.lowerBound, bounds.upperBound&+1) + _uncheckedBounds: (bounds.lowerBound, bounds.upperBound + 1) ) - return unsafe extracting(unchecked: range) + return unsafe _consumingExtracting(unchecked: range) } /// Constructs a new span over all the items of this span. @@ -805,10 +616,23 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(_: UnboundedRange) -> Self { + mutating public func _mutatingExtracting(_: UnboundedRange) -> Self { let newSpan = unsafe Self(_unchecked: _pointer, count: _count) return unsafe _overrideLifetime(newSpan, mutating: &self) } + + @available(*, deprecated, renamed: "_mutatingExtracting(_:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(_: UnboundedRange) -> Self { + _mutatingExtracting(...) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(_: UnboundedRange) -> Self { + self + } } // MARK: prefixes and suffixes @@ -833,7 +657,7 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(first maxLength: Int) -> Self { + mutating public func _mutatingExtracting(first maxLength: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(maxLength >= 0, "Can't have a prefix of negative length") let newCount = min(maxLength, count) @@ -844,6 +668,26 @@ extension MutableSpan where Element: ~Copyable { #endif } + @available(*, deprecated, renamed: "_mutatingExtracting(first:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(first maxLength: Int) -> Self { + _mutatingExtracting(first: maxLength) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(first maxLength: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = min(maxLength, count) + let newSpan = unsafe Self(_unchecked: _pointer, count: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") +#endif + } + /// 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 @@ -860,7 +704,7 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(droppingLast k: Int) -> Self { + mutating public func _mutatingExtracting(droppingLast k: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, count) @@ -872,6 +716,27 @@ extension MutableSpan where Element: ~Copyable { #endif } + @available(*, deprecated, renamed: "_mutatingExtracting(droppingLast:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(droppingLast k: Int) -> Self { + _mutatingExtracting(droppingLast: k) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(droppingLast k: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _precondition(k >= 0, "Can't drop a negative number of elements") + let droppedCount = min(k, count) + let newCount = count &- droppedCount + let newSpan = unsafe Self(_unchecked: _pointer, count: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") +#endif + } + /// Returns a span containing the final elements of the span, /// up to the given maximum length. /// @@ -889,7 +754,7 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(last maxLength: Int) -> Self { + mutating public func _mutatingExtracting(last maxLength: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(maxLength >= 0, "Can't have a suffix of negative length") let newCount = min(maxLength, count) @@ -902,6 +767,28 @@ extension MutableSpan where Element: ~Copyable { #endif } + @available(*, deprecated, renamed: "_mutatingExtracting(last:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(last maxLength: Int) -> Self { + _mutatingExtracting(last: maxLength) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(last maxLength: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _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 = unsafe Self(_unchecked: newStart, count: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") +#endif + } + /// 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 @@ -918,7 +805,7 @@ extension MutableSpan where Element: ~Copyable { /// - Complexity: O(1) @_alwaysEmitIntoClient @lifetime(&self) - mutating public func extracting(droppingFirst k: Int) -> Self { + mutating public func _mutatingExtracting(droppingFirst k: Int) -> Self { #if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) _precondition(k >= 0, "Can't drop a negative number of elements") let droppedCount = min(k, count) @@ -929,6 +816,29 @@ extension MutableSpan where Element: ~Copyable { return unsafe _overrideLifetime(newSpan, mutating: &self) #else fatalError("Unsupported compiler") +#endif + } + + @available(*, deprecated, renamed: "_mutatingExtracting(droppingFirst:)") + @_alwaysEmitIntoClient + @lifetime(&self) + mutating public func extracting(droppingFirst k: Int) -> Self { + _mutatingExtracting(droppingFirst: k) + } + + @_alwaysEmitIntoClient + @lifetime(copy self) + consuming public func _consumingExtracting(droppingFirst k: Int) -> Self { +#if compiler(>=5.3) && hasFeature(SendableCompletionHandlers) + _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 newCount = count &- droppedCount + let newSpan = unsafe Self(_unchecked: newStart, count: newCount) + return unsafe _overrideLifetime(newSpan, copying: self) +#else + fatalError("Unsupported compiler") #endif } } diff --git a/stdlib/public/core/Span/OutputRawSpan.swift b/stdlib/public/core/Span/OutputRawSpan.swift new file mode 100644 index 0000000000000..16bf6e3c8990e --- /dev/null +++ b/stdlib/public/core/Span/OutputRawSpan.swift @@ -0,0 +1,360 @@ +//===--- OutputRawSpan.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 +// +//===----------------------------------------------------------------------===// + +// OutputRawSpan is a reference to a contiguous region of memory which starts +// some number of initialized bytes, followed by uninitialized memory. +@safe +@frozen +@available(SwiftStdlib 6.2, *) +public struct OutputRawSpan: ~Copyable, ~Escapable { + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? + + public let capacity: Int + + @usableFromInline + internal var _count: Int + + /// Create an OutputRawSpan with zero capacity + @_alwaysEmitIntoClient + @lifetime(immortal) + public init() { + unsafe _pointer = nil + capacity = 0 + _count = 0 + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan: @unchecked Sendable {} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + @_alwaysEmitIntoClient + @_transparent + @unsafe + internal func _start() -> UnsafeMutableRawPointer { + unsafe _pointer._unsafelyUnwrappedUnchecked + } + + @_alwaysEmitIntoClient + @_transparent + @unsafe + internal func _tail() -> UnsafeMutableRawPointer { + // NOTE: `_pointer` must be known to be not-nil. + unsafe _start().advanced(by: _count) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + /// The number of initialized bytes in this span. + @_alwaysEmitIntoClient + public var byteCount: Int { _count } + + /// The number of additional bytes that can be appended to this span. + @_alwaysEmitIntoClient + public var freeCapacity: Int { capacity &- _count } + + /// A Boolean value indicating whether the span is empty. + @_alwaysEmitIntoClient + public var isEmpty: Bool { _count == 0 } + + /// A Boolean value indicating whether the span is full. + @_alwaysEmitIntoClient + public var isFull: Bool { _count == capacity } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + internal init( + _uncheckedBuffer buffer: UnsafeMutableRawBufferPointer, + initializedCount: Int + ) { + unsafe _pointer = .init(buffer.baseAddress) + capacity = buffer.count + _count = initializedCount + } + + /// Unsafely create an OutputRawSpan over partly-initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime + /// of the newly-created `OutputRawSpan`. Its prefix must contain + /// `initializedCount` initialized bytes, followed by uninitialized + /// memory. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to be initialized + /// - initializedCount: the number of initialized bytes + /// at the beginning of `buffer`. + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + buffer: UnsafeMutableRawBufferPointer, + initializedCount: Int + ) { + if let baseAddress = buffer.baseAddress { + _precondition( + unsafe baseAddress.advanced(by: buffer.count) >= baseAddress, + "Buffer must not wrap around the address space" + ) + } + _precondition( + 0 <= initializedCount && initializedCount <= buffer.count, + "OutputSpan count is not within capacity" + ) + unsafe self.init( + _uncheckedBuffer: buffer, initializedCount: initializedCount + ) + } + + /// Unsafely create an OutputRawSpan over partly-initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime + /// of the newly-created `OutputRawSpan`. Its prefix must contain + /// `initializedCount` initialized bytes, followed by uninitialized + /// memory. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to be initialized + /// - initializedCount: the number of initialized bytes + /// at the beginning of `buffer`. + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + buffer: borrowing Slice, + initializedCount: Int + ) { + let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) + let os = unsafe OutputRawSpan( + buffer: rebased, initializedCount: initializedCount + ) + self = unsafe _overrideLifetime(os, borrowing: buffer) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + + /// Append a single byte to this span. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(_ value: UInt8) { + _precondition(_count < capacity, "OutputRawSpan capacity overflow") + unsafe _tail().storeBytes(of: value, as: UInt8.self) + _count &+= 1 + } + + /// Remove the last byte from this span. + @_alwaysEmitIntoClient + @discardableResult + @lifetime(self: copy self) + public mutating func removeLast() -> UInt8 { + _precondition(!isEmpty, "OutputRawSpan underflow") + _count &-= 1 + return unsafe _tail().load(as: UInt8.self) + } + + /// Remove the last N elements, returning the memory they occupy + /// to the uninitialized state. + /// + /// `n` must not be greater than `count` + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func removeLast(_ k: Int) { + _precondition(k >= 0, "Can't remove a negative number of bytes") + _precondition(k <= _count, "OutputRawSpan underflow") + _count &-= k + } + + /// Remove all this span's elements and return its memory + /// to the uninitialized state. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func removeAll() { + // TODO: Consider an option to zero the `_count` bytes being removed. + _count = 0 + } +} + +//MARK: bulk-append functions +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + /// Appends the given value's bytes to this span's bytes. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(_ value: T, as type: T.Type) { + _precondition( + MemoryLayout.size <= freeCapacity, "OutputRawSpan capacity overflow" + ) + unsafe _tail().initializeMemory(as: T.self, to: value) + _count &+= MemoryLayout.size + } + + /// Appends the given value's bytes repeatedly to this span's bytes. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append( + repeating repeatedValue: T, count: Int, as type: T.Type + ) { + let total = count * MemoryLayout.stride + _precondition(total <= freeCapacity, "OutputRawSpan capacity overflow") + unsafe _tail().initializeMemory( + as: T.self, repeating: repeatedValue, count: count + ) + _count &+= total + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + /// Borrow the underlying initialized memory for read-only access. + @_alwaysEmitIntoClient + public var bytes: RawSpan { + @lifetime(borrow self) + borrowing get { + let buffer = unsafe UnsafeRawBufferPointer(start: _pointer, count: _count) + let span = unsafe RawSpan(_unsafeBytes: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } + + /// Exclusively borrow the underlying initialized memory for mutation. + @_alwaysEmitIntoClient + public var mutableBytes: MutableRawSpan { + @lifetime(&self) + mutating get { + let buffer = unsafe UnsafeMutableRawBufferPointer( + start: _pointer, count: _count + ) + let span = unsafe MutableRawSpan(_unsafeBytes: buffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + + /// Call the given closure with the unsafe buffer pointer addressed by this + /// OutputRawSpan and a mutable reference to its count of initialized bytes. + /// + /// This method provides a way to process or populate an `OutputRawSpan` using + /// unsafe operations, such as dispatching to code written in legacy + /// (memory-unsafe) languages. + /// + /// The supplied closure may process the buffer in any way it wants; however, + /// when it finishes (whether by returning or throwing), it must leave the + /// buffer in a state that satisfies the invariants of the output span: + /// + /// 1. The inout integer passed in as the second argument must be the exact + /// number of initialized bytes in the buffer passed in as the first + /// argument. + /// 2. These initialized elements must be located in a single contiguous + /// region starting at the beginning of the buffer. The rest of the buffer + /// must hold uninitialized memory. + /// + /// This function cannot verify these two invariants, and therefore + /// this is an unsafe operation. Violating the invariants of `OutputRawSpan` + /// may result in undefined behavior. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func withUnsafeMutableBytes( + _ body: ( + UnsafeMutableRawBufferPointer, + _ initializedCount: inout Int + ) throws(E) -> R + ) throws(E) -> R { + guard let start = unsafe _pointer, capacity > 0 else { + let buffer = UnsafeMutableRawBufferPointer(_empty: ()) + var initializedCount = 0 + defer { + _precondition(initializedCount == 0, "OutputRawSpan capacity overflow") + } + return unsafe try body(buffer, &initializedCount) + } + let buffer = unsafe UnsafeMutableRawBufferPointer( + _uncheckedStart: start, count: capacity + ) + var initializedCount = _count + defer { + _precondition( + 0 <= initializedCount && initializedCount <= capacity, + "OutputRawSpan capacity overflow" + ) + _count = initializedCount + } + return unsafe try body(buffer, &initializedCount) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputRawSpan { + /// Consume the output span (relinquishing its control over the buffer it is + /// addressing), and return the number of initialized bytes in it. + /// + /// This method should be invoked in the scope where the `OutputRawSpan` was + /// created, when it is time to commit the contents of the updated buffer + /// back into the construct being initialized. + /// + /// The context that created the output span is expected to remember what + /// memory region the span is addressing. This consuming method expects to + /// receive a copy of the same buffer pointer as a (loose) proof of ownership. + /// + /// - Parameter buffer: The buffer we expect the `OutputRawSpan` to reference. + /// This must be the same region of memory passed to + /// the `OutputRawSpan` initializer. + /// - Returns: The number of initialized bytes in the same buffer, as + /// tracked by the consumed `OutputRawSpan` instance. + @unsafe + @_alwaysEmitIntoClient + public consuming func finalize( + for buffer: UnsafeMutableRawBufferPointer + ) -> Int { + _precondition( + unsafe buffer.baseAddress == self._pointer + && buffer.count == self.capacity, + "OutputRawSpan identity mismatch") + return _count + } + + /// Consume the output span (relinquishing its control over the buffer it is + /// addressing), and return the number of initialized bytes in it. + /// + /// This method should be invoked in the scope where the `OutputRawSpan` was + /// created, when it is time to commit the contents of the updated buffer + /// back into the construct being initialized. + /// + /// The context that created the output span is expected to remember what + /// memory region the span is addressing. This consuming method expects to + /// receive a copy of the same buffer pointer as a (loose) proof of ownership. + /// + /// - Parameter buffer: The buffer we expect the `OutputRawSpan` to reference. + /// This must be the same region of memory passed to + /// the `OutputRawSpan` initializer. + /// - Returns: The number of initialized bytes in the same buffer, as + /// tracked by the consumed `OutputRawSpan` instance. + @unsafe + @_alwaysEmitIntoClient + public consuming func finalize( + for buffer: Slice + ) -> Int { + let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) + return unsafe self.finalize(for: rebased) + } +} diff --git a/stdlib/public/core/Span/OutputSpan.swift b/stdlib/public/core/Span/OutputSpan.swift new file mode 100644 index 0000000000000..d9a0f6ca26fe2 --- /dev/null +++ b/stdlib/public/core/Span/OutputSpan.swift @@ -0,0 +1,474 @@ +//===--- OutputSpan.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 +// +//===----------------------------------------------------------------------===// + +// `OutputSpan` is a reference to a contiguous region of memory that starts with +// some number of initialized `Element` instances followed by uninitialized +// memory. It provides operations to access the items it stores, as well as to +// add new elements and to remove existing ones. +@safe +@frozen +@available(SwiftStdlib 6.2, *) +public struct OutputSpan: ~Copyable, ~Escapable { + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? + + public let capacity: Int + + @usableFromInline + internal var _count: Int + + @_alwaysEmitIntoClient + @inlinable + deinit { + if _count > 0 { + unsafe _start().withMemoryRebound( + to: Element.self, capacity: _count + ) { + [ workaround = _count ] in + _ = unsafe $0.deinitialize(count: workaround) + } + } + } + + /// Create an OutputSpan with zero capacity + @_alwaysEmitIntoClient + @lifetime(immortal) + public init() { + unsafe _pointer = nil + capacity = 0 + _count = 0 + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan: @unchecked Sendable where Element: Sendable & ~Copyable {} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + @unsafe + internal func _start() -> UnsafeMutableRawPointer { + unsafe _pointer._unsafelyUnwrappedUnchecked + } + + @_alwaysEmitIntoClient + @_transparent + @unsafe + internal func _tail() -> UnsafeMutableRawPointer { + // NOTE: `_pointer` must be known to be not-nil. + unsafe _start().advanced(by: _count &* MemoryLayout.stride) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + /// The number of initialized elements in this span. + @_alwaysEmitIntoClient + public var count: Int { _count } + + /// The number of additional elements that can be added to this span. + @_alwaysEmitIntoClient + public var freeCapacity: Int { capacity &- _count } + + /// A Boolean value indicating whether the span is empty. + @_alwaysEmitIntoClient + public var isEmpty: Bool { _count == 0 } + + /// A Boolean value indicating whether the span is full. + @_alwaysEmitIntoClient + public var isFull: Bool { _count == capacity } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + @usableFromInline + internal init( + _uncheckedBuffer buffer: UnsafeMutableBufferPointer, + initializedCount: Int + ) { + unsafe _pointer = .init(buffer.baseAddress) + capacity = buffer.count + _count = initializedCount + } + + /// Unsafely create an OutputSpan over partly-initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime + /// of the newly-created `OutputSpan`. Its prefix must contain + /// `initializedCount` initialized instances, followed by uninitialized + /// memory. The default value of `initializedCount` is 0, representing + /// the common case of a completely uninitialized `buffer`. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to be initialized + /// - initializedCount: the number of initialized elements + /// at the beginning of `buffer`. + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + buffer: UnsafeMutableBufferPointer, + initializedCount: Int + ) { + _precondition(buffer._isWellAligned(), "Misaligned OutputSpan") + if let baseAddress = buffer.baseAddress { + _precondition( + unsafe baseAddress.advanced(by: buffer.count) >= baseAddress, + "Buffer must not wrap around the address space" + ) + } + _precondition( + 0 <= initializedCount && initializedCount <= buffer.count, + "OutputSpan count is not within capacity" + ) + unsafe self.init( + _uncheckedBuffer: buffer, initializedCount: initializedCount + ) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan { + + /// Unsafely create an OutputSpan over partly-initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime + /// of the newly-created `OutputSpan`. Its prefix must contain + /// `initializedCount` initialized instances, followed by uninitialized + /// memory. The default value of `initializedCount` is 0, representing + /// the common case of a completely uninitialized `buffer`. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` to be initialized + /// - initializedCount: the number of initialized elements + /// at the beginning of `buffer`. + @unsafe + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + buffer: borrowing Slice>, + initializedCount: Int + ) { + let rebased = unsafe UnsafeMutableBufferPointer(rebasing: buffer) + let os = unsafe OutputSpan( + buffer: rebased, initializedCount: initializedCount + ) + self = unsafe _overrideLifetime(os, borrowing: buffer) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + /// The type that represents an initialized position in an `OutputSpan`. + public typealias Index = Int + + /// The range of initialized positions for this `OutputSpan`. + @_alwaysEmitIntoClient + public var indices: Range { + unsafe Range(_uncheckedBounds: (0, _count)) + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: A valid index into this span. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public subscript(_ index: Index) -> Element { + unsafeAddress { + _precondition(indices.contains(index), "index out of bounds") + return unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: index)) + } + @lifetime(self: copy self) + unsafeMutableAddress { + _precondition(indices.contains(index), "index out of bounds") + return unsafe _unsafeAddressOfElement(unchecked: index) + } + } + + /// Accesses the element at the specified position. + /// + /// This subscript does not validate `position`; this is an unsafe operation. + /// + /// - Parameter index: A valid index into this span. + /// + /// - Complexity: O(1) + @unsafe + @_alwaysEmitIntoClient + public subscript(unchecked index: Index) -> Element { + unsafeAddress { + unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: index)) + } + @lifetime(self: copy self) + unsafeMutableAddress { + unsafe _unsafeAddressOfElement(unchecked: index) + } + } + + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + unchecked index: Index + ) -> UnsafeMutablePointer { + let elementOffset = index &* MemoryLayout.stride + let address = unsafe _start().advanced(by: elementOffset) + return unsafe address.assumingMemoryBound(to: Element.self) + } + + /// Exchange the elements at the two given offsets + /// + /// - Parameter i: A valid index into this span. + /// - Parameter j: A valid index into this span. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func swapAt(_ i: Index, _ j: Index) { + _precondition(indices.contains(Index(i))) + _precondition(indices.contains(Index(j))) + unsafe swapAt(unchecked: i, unchecked: j) + } + + /// Exchange the elements at the two given offsets + /// + /// This subscript does not validate `i` or `j`; this is an unsafe operation. + /// + /// - Parameter i: A valid index into this span. + /// - Parameter j: A valid index into this span. + @unsafe + @_alwaysEmitIntoClient + @lifetime(self: copy self) + 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(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + /// Append a single element to this span. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(_ value: consuming Element) { + _precondition(_count < capacity, "OutputSpan capacity overflow") + unsafe _tail().initializeMemory(as: Element.self, to: value) + _count &+= 1 + } + + /// Remove the last initialized element from this span. + /// + /// Returns the last element. The `OutputSpan` must not be empty. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func removeLast() -> Element { + _precondition(!isEmpty, "OutputSpan underflow") + _count &-= 1 + return unsafe _tail().withMemoryRebound(to: Element.self, capacity: 1) { + unsafe $0.move() + } + } + + /// Remove the last N elements of this span, returning the memory they occupy + /// to the uninitialized state. + /// + /// `n` must not be greater than `count` + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func removeLast(_ k: Int) { + _precondition(k >= 0, "Can't remove a negative number of elements") + _precondition(k <= _count, "OutputSpan underflow") + _count &-= k + unsafe _tail().withMemoryRebound(to: Element.self, capacity: k) { + _ = unsafe $0.deinitialize(count: k) + } + } + + /// Remove all this span's elements and return its memory + /// to the uninitialized state. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func removeAll() { + _ = unsafe _start().withMemoryRebound(to: Element.self, capacity: _count) { + unsafe $0.deinitialize(count: _count) + } + _count = 0 + } +} + +//MARK: bulk-append functions +@available(SwiftStdlib 6.2, *) +extension OutputSpan { + + /// Repeatedly append an element to this span. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(repeating repeatedValue: Element, count: Int) { + _precondition(count <= freeCapacity, "OutputSpan capacity overflow") + unsafe _tail().initializeMemory( + as: Element.self, repeating: repeatedValue, count: count + ) + _count &+= count + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + /// Borrow the underlying initialized memory for read-only access. + @_alwaysEmitIntoClient + public var span: Span { + @lifetime(borrow self) + borrowing get { + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeBufferPointer(start: pointer, count: _count) + let span = unsafe Span(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } + + /// Exclusively borrow the underlying initialized memory for mutation. + @_alwaysEmitIntoClient + public var mutableSpan: MutableSpan { + @lifetime(&self) + mutating get { + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeMutableBufferPointer( + start: pointer, count: _count + ) + let span = unsafe MutableSpan(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + /// Call the given closure with the unsafe buffer pointer addressed by this + /// OutputSpan and a mutable reference to its count of initialized elements. + /// + /// This method provides a way to process or populate an `OutputSpan` using + /// unsafe operations, such as dispatching to code written in legacy + /// (memory-unsafe) languages. + /// + /// The supplied closure may process the buffer in any way it wants; however, + /// when it finishes (whether by returning or throwing), it must leave the + /// buffer in a state that satisfies the invariants of the output span: + /// + /// 1. The inout integer passed in as the second argument must be the exact + /// number of initialized items in the buffer passed in as the first + /// argument. + /// 2. These initialized elements must be located in a single contiguous + /// region starting at the beginning of the buffer. The rest of the buffer + /// must hold uninitialized memory. + /// + /// This function cannot verify these two invariants, and therefore + /// this is an unsafe operation. Violating the invariants of `OutputSpan` + /// may result in undefined behavior. + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func withUnsafeMutableBufferPointer( + _ body: ( + UnsafeMutableBufferPointer, + _ initializedCount: inout Int + ) throws(E) -> R + ) throws(E) -> R { + guard let start = unsafe _pointer, capacity > 0 else { + let buffer = UnsafeMutableBufferPointer(_empty: ()) + var initializedCount = 0 + defer { + _precondition(initializedCount == 0, "OutputSpan capacity overflow") + } + return unsafe try body(buffer, &initializedCount) + } + // bind memory by hand to sidestep alignment concerns + let binding = Builtin.bindMemory( + start._rawValue, capacity._builtinWordValue, Element.self + ) + defer { Builtin.rebindMemory(start._rawValue, binding) } + let buffer = unsafe UnsafeMutableBufferPointer( + _uncheckedStart: .init(start._rawValue), count: capacity + ) + var initializedCount = self._count + defer { + _precondition( + 0 <= initializedCount && initializedCount <= capacity, + "OutputSpan capacity overflow" + ) + self._count = initializedCount + } + return unsafe try body(buffer, &initializedCount) + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan where Element: ~Copyable { + /// Consume the output span and return the number of initialized elements. + /// + /// This method should be invoked in the scope where the `OutputSpan` was + /// created, when it is time to commit the contents of the updated buffer + /// back into the construct being initialized. + /// + /// The context that created the output span is expected to remember what + /// memory region the span is addressing. This consuming method expects to + /// receive a copy of the same buffer pointer as a (loose) proof of ownership. + /// + /// - Parameter buffer: The buffer we expect the `OutputSpan` to reference. + /// This must be the same region of memory passed to + /// the `OutputSpan` initializer. + /// - Returns: The number of initialized elements in the same buffer, as + /// tracked by the consumed `OutputSpan` instance. + @unsafe + @_alwaysEmitIntoClient + public consuming func finalize( + for buffer: UnsafeMutableBufferPointer + ) -> Int { + _precondition( + unsafe UnsafeMutableRawPointer(buffer.baseAddress) == self._pointer + && buffer.count == self.capacity, + "OutputSpan identity mismatch" + ) + let count = self._count + discard self + return count + } +} + +@available(SwiftStdlib 6.2, *) +extension OutputSpan { + /// Consume the output span and return the number of initialized elements. + /// + /// This method should be invoked in the scope where the `OutputSpan` was + /// created, when it is time to commit the contents of the updated buffer + /// back into the construct being initialized. + /// + /// The context that created the output span is expected to remember what + /// memory region the span is addressing. This consuming method expects to + /// receive a copy of the same buffer pointer as a (loose) proof of ownership. + /// + /// - Parameter buffer: The buffer we expect the `OutputSpan` to reference. + /// This must be the same region of memory passed to + /// the `OutputSpan` initializer. + /// - Returns: The number of initialized elements in the same buffer, as + /// tracked by the consumed `OutputSpan` instance. + @unsafe + @_alwaysEmitIntoClient + public consuming func finalize( + for buffer: Slice> + ) -> Int { + unsafe finalize(for: UnsafeMutableBufferPointer(rebasing: buffer)) + } +} diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index b0b6937458281..dcd0d23129db8 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -795,6 +795,15 @@ extension Unsafe${Mutable}BufferPointer: % end } +extension Unsafe${Mutable}BufferPointer where Element: ~Copyable { + @safe + @_alwaysEmitIntoClient + public func _isWellAligned() -> Bool { + guard let p = baseAddress else { return true } + return p._isWellAligned() + } +} + extension Unsafe${Mutable}BufferPointer { % if not Mutable: /// Creates a buffer over the same memory as the given buffer slice. diff --git a/stdlib/public/core/UnsafePointer.swift b/stdlib/public/core/UnsafePointer.swift index db53ea3a0e57b..a4e8b9558b9f1 100644 --- a/stdlib/public/core/UnsafePointer.swift +++ b/stdlib/public/core/UnsafePointer.swift @@ -469,6 +469,13 @@ extension UnsafePointer where Pointee: ~Copyable { } } +extension UnsafePointer where Pointee: ~Copyable { + @safe + @_alwaysEmitIntoClient + public func _isWellAligned() -> Bool { + (Int(bitPattern: self) & (MemoryLayout.alignment &- 1)) == 0 + } +} /// A pointer for accessing and manipulating data of a /// specific type. @@ -1382,3 +1389,11 @@ extension UnsafeMutablePointer where Pointee: ~Copyable { )._unsafelyUnwrappedUnchecked } } + +extension UnsafeMutablePointer where Pointee: ~Copyable { + @safe + @_alwaysEmitIntoClient + public func _isWellAligned() -> Bool { + (Int(bitPattern: self) & (MemoryLayout.alignment &- 1)) == 0 + } +} diff --git a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb index acd3c85c776ae..5091ca3ec0d78 100644 --- a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb @@ -100,6 +100,17 @@ public struct Unsafe${Mutable}RawBufferPointer { @usableFromInline internal let _position, _end: Unsafe${Mutable}RawPointer? + // This works around _debugPrecondition() impacting the performance of + // optimized code. (rdar://72246338) + @_alwaysEmitIntoClient + internal init( + @_nonEphemeral _uncheckedStart start: Unsafe${Mutable}RawPointer?, + count: Int + ) { + unsafe _position = start + unsafe _end = start.map { unsafe $0 + _assumeNonNegative(count) } + } + /// Creates a buffer over the specified number of contiguous bytes starting /// at the given pointer. /// @@ -116,9 +127,14 @@ public struct Unsafe${Mutable}RawBufferPointer { _debugPrecondition(count >= 0, "${Self} with negative count") _debugPrecondition(unsafe count == 0 || start != nil, "${Self} has a nil start and nonzero count") + unsafe self.init(_uncheckedStart: start, count: count) + } - unsafe _position = start - unsafe _end = start.map { unsafe $0 + _assumeNonNegative(count) } + @safe + @_alwaysEmitIntoClient + public init(_empty: ()) { + unsafe _position = nil + unsafe _end = nil } } diff --git a/test/abi/macOS/arm64/stdlib.swift b/test/abi/macOS/arm64/stdlib.swift index e4e0bbe4fc0db..ac0404f6936b2 100644 --- a/test/abi/macOS/arm64/stdlib.swift +++ b/test/abi/macOS/arm64/stdlib.swift @@ -927,6 +927,23 @@ Added: _$ss14MutableRawSpanVN Added: _$sSS8UTF8ViewV4spans4SpanVys5UInt8VGvg Added: _$sSs8UTF8ViewV4spans4SpanVys5UInt8VGvg +// OutputSpan +Added: _$ss10OutputSpanVMa +Added: _$ss10OutputSpanVMn +Added: _$ss10OutputSpanVsRi_zrlE6_countSivM +Added: _$ss10OutputSpanVsRi_zrlE6_countSivg +Added: _$ss10OutputSpanVsRi_zrlE6_countSivs +Added: _$ss10OutputSpanVsRi_zrlE8_pointerSvSgvg +Added: _$ss10OutputSpanVsRi_zrlE8capacitySivg +Added: _$ss13OutputRawSpanV6_countSivM +Added: _$ss13OutputRawSpanV6_countSivg +Added: _$ss13OutputRawSpanV6_countSivs +Added: _$ss13OutputRawSpanV8_pointerSvSgvg +Added: _$ss13OutputRawSpanV8capacitySivg +Added: _$ss13OutputRawSpanVMa +Added: _$ss13OutputRawSpanVMn +Added: _$ss13OutputRawSpanVN + // _SwiftifyInfo enum for _SwiftifyImports macro Added: _$ss13_SwiftifyExprO5paramyABSicABmFWC Added: _$ss13_SwiftifyExprO6returnyA2BmFWC diff --git a/test/abi/macOS/x86_64/stdlib.swift b/test/abi/macOS/x86_64/stdlib.swift index 0b0d6e7b6a5e2..8f4057450c8fc 100644 --- a/test/abi/macOS/x86_64/stdlib.swift +++ b/test/abi/macOS/x86_64/stdlib.swift @@ -928,6 +928,23 @@ Added: _$ss14MutableRawSpanVN Added: _$sSS8UTF8ViewV4spans4SpanVys5UInt8VGvg Added: _$sSs8UTF8ViewV4spans4SpanVys5UInt8VGvg +// OutputSpan +Added: _$ss10OutputSpanVMa +Added: _$ss10OutputSpanVMn +Added: _$ss10OutputSpanVsRi_zrlE6_countSivM +Added: _$ss10OutputSpanVsRi_zrlE6_countSivg +Added: _$ss10OutputSpanVsRi_zrlE6_countSivs +Added: _$ss10OutputSpanVsRi_zrlE8_pointerSvSgvg +Added: _$ss10OutputSpanVsRi_zrlE8capacitySivg +Added: _$ss13OutputRawSpanV6_countSivM +Added: _$ss13OutputRawSpanV6_countSivg +Added: _$ss13OutputRawSpanV6_countSivs +Added: _$ss13OutputRawSpanV8_pointerSvSgvg +Added: _$ss13OutputRawSpanV8capacitySivg +Added: _$ss13OutputRawSpanVMa +Added: _$ss13OutputRawSpanVMn +Added: _$ss13OutputRawSpanVN + // _SwiftifyInfo enum for _SwiftifyImports macro Added: _$ss13_SwiftifyExprO5paramyABSicABmFWC Added: _$ss13_SwiftifyExprO6returnyA2BmFWC diff --git a/test/stdlib/InlineArray.swift b/test/stdlib/InlineArray.swift index caf6558af27f8..1892ba7de4d53 100644 --- a/test/stdlib/InlineArray.swift +++ b/test/stdlib/InlineArray.swift @@ -168,14 +168,14 @@ enum InlineArrayTests { let error = CancellationError() do { expectDoesNotThrow { - let a = try InlineArray<0, String> { _ in throw error } + let a = try InlineArray<0, String>({ _ in throw error }) _checkInlineArray(a, oracle: []) } _expectThrows { - let _ = try InlineArray<1, String> { _ in throw error } + let _ = try InlineArray<1, String>({ _ in throw error }) } _expectThrows { - let _ = try InlineArray<1, any P> { _ in throw error } + let _ = try InlineArray<1, any P>({ _ in throw error }) } _expectThrows { let _ = try InlineArray<2, String> { index in diff --git a/test/stdlib/Span/MutableRawSpanTests.swift b/test/stdlib/Span/MutableRawSpanTests.swift index 6578b879da7a9..e15736c28d7ed 100644 --- a/test/stdlib/Span/MutableRawSpanTests.swift +++ b/test/stdlib/Span/MutableRawSpanTests.swift @@ -249,7 +249,7 @@ suite.test("unsafeLoadUnaligned(as:)") a.withUnsafeMutableBytes { var span = MutableRawSpan(_unsafeBytes: $0) - let suffix = span.extracting(droppingFirst: 2) + let suffix = span._mutatingExtracting(droppingFirst: 2) let u0 = suffix.unsafeLoadUnaligned(as: UInt64.self) expectEqual(u0 & 0xff, 2) expectEqual(u0.byteSwapped & 0xff, 9) @@ -281,111 +281,7 @@ suite.test("storeBytes(of:as:)") expectEqual(a[0].bigEndian & 0xffff, 0xffff) } -suite.test("update(from: some Sequence)") -.skip(.custom( - { if #available(SwiftStdlib 6.2, *) { false } else { true } }, - reason: "Requires Swift 6.2's standard library" -)) -.code { - guard #available(SwiftStdlib 6.2, *) else { return } - - let capacity = 8 - var a = Array(repeating: Int.max, count: capacity) - expectEqual(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) - expectNil(iterator.next()) - expectEqual(updated, 0) - - span = MutableRawSpan(_unsafeElements: $0) - (iterator, updated) = span.update(from: 0..<0) - expectNil(iterator.next()) - expectEqual(updated, 0) - - (iterator, updated) = span.update(from: 0..<10000) - expectNotNil(iterator.next()) - expectEqual(updated, capacity*MemoryLayout.stride) - } - expectEqual(a.elementsEqual(0..)") -.skip(.custom( - { if #available(SwiftStdlib 6.2, *) { false } else { true } }, - reason: "Requires Swift 6.2's standard library" -)) -.code { - guard #available(SwiftStdlib 6.2, *) else { return } - - let capacity = 8 - var a = Array(repeating: Int.max, count: capacity) - let e = Array(EmptyCollection()) - expectEqual(a.allSatisfy({ $0 == .max }), true) - a.withUnsafeMutableBytes { - let emptyPrefix = $0.prefix(0) - var span = MutableRawSpan(_unsafeBytes: emptyPrefix) - var updated = span.update(fromContentsOf: e) - expectEqual(updated, 0) - - - updated = span.update(fromContentsOf: AnyCollection(e)) - expectEqual(updated, 0) - - span = MutableRawSpan(_unsafeBytes: $0) - updated = span.update(fromContentsOf: 0...stride) - } - expectEqual(a.elementsEqual(0...stride) - } - expectEqual(a.elementsEqual(0...stride) - } - } - expectEqual(a.allSatisfy({ $0 == Int.min }), true) - - a.withUnsafeMutableBytes { - var span = MutableRawSpan(_unsafeBytes: $0) - let array = Array(0...stride) - } - } - expectEqual(a.elementsEqual(0..(start: nil, count: 0) - var span = MutableSpan(_unsafeElements: empty) - var (iterator, updated) = span.update(from: 0..<0) - expectNil(iterator.next()) - expectEqual(updated, 0) - - span = MutableSpan(_unsafeElements: $0) - (iterator, updated) = span.update(from: 0..<0) - expectNil(iterator.next()) - expectEqual(updated, 0) - - (iterator, updated) = span.update(from: 0..<10000) - expectNotNil(iterator.next()) - expectEqual(updated, capacity) - } - expectEqual(a.elementsEqual(0...allocate(capacity: 2*capacity) - let i = b.initialize(fromContentsOf: (0..<2*capacity).map(ID.init(id:))) - expectEqual(i, 2*capacity) - - a.withUnsafeMutableBufferPointer { - var span = MutableSpan(_unsafeElements: $0) - let updated = span.moveUpdate(fromContentsOf: b.suffix(capacity)) - expectEqual(updated, capacity) - } - expectEqual(a.map(\.id).elementsEqual(capacity..<2*capacity), true) - - a = [] - b.prefix(capacity).deinitialize() - b.deallocate() -} - suite.test("span property") .skip(.custom( { if #available(SwiftStdlib 6.2, *) { false } else { true } }, @@ -596,7 +370,7 @@ suite.test("swapAt") expectEqual(array, (0...allocate(capacity: c) + defer { b.deallocate() } + _ = b.initialize(fromContentsOf: 0..(start: nil, count: 0) var span = MutableSpan(_unsafeElements: b) expectEqual(span.count, b.count) - expectEqual(span.extracting(first: 1).count, b.count) - expectEqual(span.extracting(droppingLast: 1).count, b.count) + expectEqual(span._mutatingExtracting(first: 1).count, b.count) + expectEqual(span._mutatingExtracting(droppingLast: 1).count, b.count) } } -suite.test("extracting suffixes") +suite.test("_consumingExtracting prefixes") +.require(.stdlib_6_2).code { + + let capacity = 4 + var a = Array(0..(start: nil, count: 0) + var span = b.mutableSpan + expectEqual(span.count, b.count) + span = b.mutableSpan._consumingExtracting(first: 1) + expectEqual(span.count, b.count) + span = b.mutableSpan._consumingExtracting(droppingLast: 1) + expectEqual(span.count, b.count) + } +} + +suite.test("_mutatingExtracting suffixes") .skip(.custom( { if #available(SwiftStdlib 6.2, *) { false } else { true } }, reason: "Requires Swift 6.2's standard library" @@ -698,19 +548,19 @@ suite.test("extracting suffixes") var span = MutableSpan(_unsafeElements: $0) expectEqual(span.count, capacity) - suffix = span.extracting(last: capacity) + suffix = span._mutatingExtracting(last: capacity) expectEqual(suffix[0], 0) - suffix = span.extracting(last: capacity-1) + suffix = span._mutatingExtracting(last: capacity-1) expectEqual(suffix[0], 1) - suffix = span.extracting(last: 1) + suffix = span._mutatingExtracting(last: 1) expectEqual(suffix[0], UInt8(capacity-1)) - suffix = span.extracting(droppingFirst: capacity) + suffix = span._mutatingExtracting(droppingFirst: capacity) expectTrue(suffix.isEmpty) - suffix = span.extracting(droppingFirst: 1) + suffix = span._mutatingExtracting(droppingFirst: 1) expectEqual(suffix[0], 1) } @@ -718,8 +568,44 @@ suite.test("extracting suffixes") let b = UnsafeMutableBufferPointer(start: nil, count: 0) var span = MutableSpan(_unsafeElements: b) expectEqual(span.count, b.count) - expectEqual(span.extracting(last: 1).count, b.count) - expectEqual(span.extracting(droppingFirst: 1).count, b.count) + expectEqual(span._mutatingExtracting(last: 1).count, b.count) + expectEqual(span._mutatingExtracting(droppingFirst: 1).count, b.count) + } +} + +suite.test("_consumingExtracting suffixes") +.require(.stdlib_6_2).code { + + let capacity = 4 + var a = Array(0..(start: nil, count: 0) + var span = b.mutableSpan + expectEqual(span.count, b.count) + span = b.mutableSpan._consumingExtracting(last: 1) + expectEqual(span.count, b.count) + span = b.mutableSpan._consumingExtracting(droppingFirst: 1) + expectEqual(span.count, b.count) } } diff --git a/test/stdlib/Span/OutputSpanTests.swift b/test/stdlib/Span/OutputSpanTests.swift new file mode 100644 index 0000000000000..09d4b50cdce0f --- /dev/null +++ b/test/stdlib/Span/OutputSpanTests.swift @@ -0,0 +1,248 @@ +//===--- OutputSpanTests.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 +// +//===----------------------------------------------------------------------===// + +// RUN: %target-run-stdlib-swift + +// REQUIRES: executable_test + +import StdlibUnittest + +var suite = TestSuite("OutputSpan Tests") +defer { runAllTests() } + +@available(SwiftStdlib 6.2, *) +struct Allocation: ~Copyable { + let allocation: UnsafeMutableBufferPointer + var count: Int? = nil + + init(of count: Int = 1, _ t: T.Type) { + precondition(count >= 0) + allocation = .allocate(capacity: count) + } + + var isEmpty: Bool { (count ?? 0) == 0 } + + mutating func initialize( + _ body: (inout OutputSpan) throws(E) -> Void + ) throws(E) { + if count != nil { fatalError() } + var outputBuffer = OutputSpan(buffer: allocation, initializedCount: 0) + do { + try body(&outputBuffer) + let initialized = outputBuffer.finalize(for: allocation) + count = initialized + } + catch { + outputBuffer.removeAll() + let initialized = outputBuffer.finalize(for: allocation) + assert(initialized == 0) + throw error + } + } + + borrowing func withSpan( + _ body: (borrowing Span) throws(E) -> R + ) throws(E) -> R { + try body(Span(_unsafeElements: allocation[0...allocate(capacity: c) + defer { allocation.deallocate() } + + let ob = unsafe OutputSpan(buffer: allocation, initializedCount: 0) + let initialized = ob.finalize(for: allocation) + expectEqual(initialized, 0) +} + +suite.test("deinit without relinquishing memory") +.require(.stdlib_6_2).code { + guard #available(SwiftStdlib 6.2, *) else { return } + + let c = 48 + let allocation = UnsafeMutableBufferPointer.allocate(capacity: c) + defer { allocation.deallocate() } + + var ob = unsafe OutputSpan(buffer: allocation, initializedCount: 0) + // OutputSpan(buffer: Slice(base: allocation, bounds: 0...allocate(capacity: capacity) + defer { b.deallocate() } + _ = b.initialize(fromContentsOf: 0...allocate(capacity: capacity) + defer { b.deallocate() } + _ = b.initialize(fromContentsOf: 0.. 0) + throw MyTestError.error + } + } + catch MyTestError.error { + expectEqual(a.isEmpty, true) + } + catch { + expectTrue(false) + } +} + +suite.test("InlineArray initialization") +.require(.stdlib_6_2).code { + guard #available(SwiftStdlib 6.2, *) else { return } + + let i = InlineArray<10, Int> { + (o: inout OutputSpan) in + expectEqual(o.count, 0) + for i in 0.. { + $0.append(1) + } +} + +suite.test("InlineArray initialization throws") +.require(.stdlib_6_2).code { + guard #available(SwiftStdlib 6.2, *) else { return } + + enum LocalError: Error { case error } + + class I { + static var count = 0 + init() { Self.count += 1 } + deinit { Self.count -= 1 } + } + + let a: InlineArray<4, I> + do throws(LocalError) { + a = try InlineArray { + o throws(LocalError) in + o.append(I()) + o.append(I()) + o.append(I()) + o.append(I()) + expectEqual(I.count, 4) + throw LocalError.error + } + _ = a + } catch { + expectEqual(I.count, 0) + } +}