Skip to content

[SE-0456] Span properties #78561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
13cdfaa
[stdlib] Update availability annotations for `Span`
glessard Feb 28, 2025
85b53bb
[stdlib] add `bytes` property to `Span`
glessard Dec 20, 2024
9cdd83b
[stdlib] add `span` properties to UnsafeBufferPointer family
glessard Feb 25, 2025
3863b81
[test] test `Span.bytes`
glessard Dec 21, 2024
acdfb37
[stdlib] refactor lazy-eager-bridging for reusability
glessard Jan 7, 2025
7bbb834
[stdlib] add `span` properties to array types
glessard Feb 25, 2025
a5e04e0
[test] span properties for `Array` family
glessard Jan 4, 2025
264747a
[stdlib] add `span` property to `CollectionOfOne`
glessard Feb 25, 2025
d0d5285
[stdlib] add `span` property to `InlineArray`
glessard Feb 25, 2025
d6ab6d0
[test] better names for test suites
glessard Feb 26, 2025
7188d0b
[stdlib] add `span` property to `KeyValuePairs`
glessard Feb 26, 2025
afb627d
[test] rename files
glessard Feb 26, 2025
83dc08d
[stdlib] remove an unneeded annotation
glessard Feb 26, 2025
39b4303
[stdlib] match `KeyValuePairs`’s `Element` type
glessard Feb 26, 2025
063b058
[test] ritual acts of contrition
glessard Feb 25, 2025
0e1d207
Update test/stdlib/Span/ArraySpanProperties.swift
glessard Feb 26, 2025
2980878
Update test/stdlib/Span/InlineSpanProperties.swift
glessard Feb 26, 2025
3d6070d
[test] add a test case
glessard Feb 27, 2025
96e9945
[stdlib] only enable “AddressableTypes” for the core stdlib
glessard Feb 26, 2025
940628a
[stdlib] add newly-required unsafe annotations
glessard Mar 1, 2025
35b8514
[gardening] update some copyright years
glessard Mar 12, 2025
d5cb7dd
[stdlib] changes to lifetime annotations
glessard Mar 20, 2025
31daf5c
[test] amend test command
glessard Mar 21, 2025
7b03593
[temporary] disable spans over inline elements
glessard Mar 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Runtimes/Core/cmake/modules/ExperimentalFeatures.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ add_compile_options(
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature FreestandingMacros>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature BitwiseCopyable>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature Extern>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature ValueGenerics>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature AddressableParameters>")
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature ValueGenerics>")
2 changes: 2 additions & 0 deletions Runtimes/Core/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ target_compile_options(swiftCore PRIVATE
# STAGING: Temporarily avoids having to write #fileID in Swift.swiftinterface.
# see also 327ea8bce2d1107a847d444651b19ca6a2901c9e
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -enable-experimental-concise-pound-file>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature AddressableParameters>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature AddressableTypes>"
$<$<COMPILE_LANGUAGE:Swift>:-parse-stdlib>
$<$<COMPILE_LANGUAGE:Swift>:-nostdimport>
$<$<COMPILE_LANGUAGE:Swift>:-explicit-module-build>
Expand Down
21 changes: 20 additions & 1 deletion stdlib/public/core/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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
Expand Down Expand Up @@ -1427,6 +1427,25 @@ extension Array: RangeReplaceableCollection {
return try unsafe body(bufferPointer)
}
}

@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
borrowing get {
#if _runtime(_ObjC)
if _slowPath(!_buffer._isNative) {
let buffer = _buffer.getOrAllocateAssociatedObjectBuffer()
let (pointer, count) = unsafe (buffer.firstElementAddress, buffer.count)
let span = unsafe Span(_unsafeStart: pointer, count: count)
return unsafe _overrideLifetime(span, borrowing: self)
}
#endif
let (pointer, count) = unsafe (_buffer.firstElementAddress, _buffer.count)
let span = unsafe Span(_unsafeStart: pointer, count: count)
return unsafe _overrideLifetime(span, borrowing: self)
}
}

@inlinable
public __consuming func _copyToContiguousArray() -> ContiguousArray<Element> {
Expand Down
27 changes: 15 additions & 12 deletions stdlib/public/core/ArrayBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
Expand Down Expand Up @@ -608,11 +608,10 @@ extension _ArrayBuffer {
1 //OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}

@_alwaysEmitIntoClient @inline(never)
internal func withUnsafeBufferPointer_nonNative<R, E>(
_ body: (UnsafeBufferPointer<Element>) throws(E) -> R
) throws(E) -> R {

@_alwaysEmitIntoClient
internal func getOrAllocateAssociatedObjectBuffer(
) -> _ContiguousArrayBuffer<Element> {
let unwrapped: _ContiguousArrayBuffer<Element>
// libobjc already provides the necessary memory barriers for
// double checked locking to be safe, per comments on
Expand All @@ -633,12 +632,16 @@ extension _ArrayBuffer {
defer { _fixLifetime(unwrapped) }
objc_sync_exit(lock)
}
return try unsafe body(
UnsafeBufferPointer(
start: unwrapped.firstElementAddress,
count: unwrapped.count
)
)
return unwrapped
}

@_alwaysEmitIntoClient @inline(never)
internal func withUnsafeBufferPointer_nonNative<R, E>(
_ body: (UnsafeBufferPointer<Element>) throws(E) -> R
) throws(E) -> R {
let buffer = getOrAllocateAssociatedObjectBuffer()
let (pointer, count) = unsafe (buffer.firstElementAddress, buffer.count)
return try unsafe body(UnsafeBufferPointer(start: pointer, count: count))
}

/// Call `body(p)`, where `p` is an `UnsafeBufferPointer` over the
Expand Down
13 changes: 12 additions & 1 deletion stdlib/public/core/ArraySlice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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
Expand Down Expand Up @@ -1116,6 +1116,17 @@ extension ArraySlice: RangeReplaceableCollection {
}
}

@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
borrowing get {
let (pointer, count) = (_buffer.firstElementAddress, _buffer.count)
let span = unsafe Span(_unsafeStart: pointer, count: count)
return unsafe _overrideLifetime(span, borrowing: self)
}
}

@inlinable
public __consuming func _copyToContiguousArray() -> ContiguousArray<Element> {
if let n = _buffer.requestNativeBuffer() {
Expand Down
18 changes: 17 additions & 1 deletion stdlib/public/core/CollectionOfOne.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
Expand All @@ -22,6 +22,7 @@
/// let b = a + CollectionOfOne(toAdd)
/// // b == [1, 2, 3, 4, 100]
@frozen // trivial-implementation
@_addressableForDependencies
public struct CollectionOfOne<Element> {
@usableFromInline // trivial-implementation
internal var _element: Element
Expand Down Expand Up @@ -158,6 +159,21 @@ extension CollectionOfOne: RandomAccessCollection, MutableCollection {
}
}

extension CollectionOfOne {

@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
get {
let pointer = unsafe UnsafePointer<Element>(Builtin.addressOfBorrow(self))
let span = unsafe Span(_unsafeStart: pointer, count: 1)
fatalError("Span over CollectionOfOne is not supported yet.")
return unsafe _overrideLifetime(span, borrowing: self)
}
}
}

@_unavailableInEmbedded
extension CollectionOfOne: CustomDebugStringConvertible {
/// A textual representation of the collection, suitable for debugging.
Expand Down
13 changes: 12 additions & 1 deletion stdlib/public/core/ContiguousArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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
Expand Down Expand Up @@ -1021,6 +1021,17 @@ extension ContiguousArray: RangeReplaceableCollection {
}
}

@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
borrowing get {
let (pointer, count) = unsafe (_buffer.firstElementAddress, _buffer.count)
let span = unsafe Span(_unsafeStart: pointer, count: count)
return unsafe _overrideLifetime(span, borrowing: self)
}
}

@inlinable
public __consuming func _copyToContiguousArray() -> ContiguousArray<Element> {
if let n = _buffer.requestNativeBuffer() {
Expand Down
21 changes: 21 additions & 0 deletions stdlib/public/core/InlineArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
@available(SwiftStdlib 6.2, *)
@frozen
@safe
@_addressableForDependencies
public struct InlineArray<let count: Int, Element: ~Copyable>: ~Copyable {
@usableFromInline
internal let _storage: Builtin.FixedArray<count, Element>
Expand Down Expand Up @@ -441,6 +442,26 @@ extension InlineArray where Element: ~Copyable {
}
}

//===----------------------------------------------------------------------===//
// MARK: Span
//===----------------------------------------------------------------------===//

@available(SwiftStdlib 6.2, *)
extension InlineArray where Element: ~Copyable {

@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
borrowing get {
let pointer = _address
let span = unsafe Span(_unsafeStart: pointer, count: count)
fatalError("Span over InlineArray is not supported yet.")
return unsafe _overrideLifetime(span, borrowing: self)
}
}
}

//===----------------------------------------------------------------------===//
// MARK: - Unsafe APIs
//===----------------------------------------------------------------------===//
Expand Down
19 changes: 18 additions & 1 deletion stdlib/public/core/KeyValuePairs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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
Expand Down Expand Up @@ -125,6 +125,23 @@ extension KeyValuePairs: RandomAccessCollection {
}
}

extension KeyValuePairs {

@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
get {
let rp = unsafe UnsafeRawPointer(_elements._buffer.firstElementAddress)
let span = unsafe Span(
_unsafeStart: unsafe rp.assumingMemoryBound(to: Element.self),
count: _elements.count
)
return unsafe _overrideLifetime(span, borrowing: self)
}
}
}

@_unavailableInEmbedded
extension KeyValuePairs: CustomStringConvertible {
/// A string that represents the contents of the dictionary.
Expand Down
18 changes: 11 additions & 7 deletions stdlib/public/core/Span/RawSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public struct RawSpan: ~Escapable, Copyable, BitwiseCopyable {
@usableFromInline
internal let _pointer: UnsafeRawPointer?

@unsafe
@_alwaysEmitIntoClient
internal func _start() -> UnsafeRawPointer {
unsafe _pointer._unsafelyUnwrappedUnchecked
Expand Down Expand Up @@ -309,14 +310,17 @@ extension RawSpan {
/// - span: An existing `Span<T>`, which will define both this
/// `RawSpan`'s lifetime and the memory it represents.
@_alwaysEmitIntoClient
@lifetime(borrow span)
@lifetime(copy span)
public init<Element: BitwiseCopyable>(
_elements span: borrowing Span<Element>
_elements span: Span<Element>
) {
unsafe self.init(
_unchecked: span._pointer,
byteCount: span.count &* MemoryLayout<Element>.stride
let pointer = span._pointer
let rawSpan = unsafe RawSpan(
_unchecked: pointer,
byteCount: span.count == 1 ? MemoryLayout<Element>.size
: span.count &* MemoryLayout<Element>.stride
Comment on lines +320 to +321
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is correct then other APIs may need to be updated:

  • RawSpan.init(_unsafeStart:count:) which uses count * MemoryLayout<T>.stride.

  • Span.withUnsafeBytes(_:) which uses _count * MemoryLayout<Element>.stride.

  • Span.indices(of:) which uses stride &* other._count and stride &* _count.

  • Span.init(_bytes:) that consumes a RawSpan.

Alternatively, perhaps CollectionOfOne should be removed from the proposal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is that safely converting a Span of 1 probably doesn't want to use stride; the need indeed comes from CollectionOfOne. I'll look at the others, taking this consideration in mind. It's clear that if accommodating CollectionOfOne is too intrusive then we shouldn't have it. We do have InlineArray<1, T> to fall back on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is that safely converting a Span of 1 probably doesn't want to use stride; the need indeed comes from CollectionOfOne.

The same issue could affect nonempty Spans of any length:

let one: StaticString = ""
let two: (StaticString, StaticString) = ("", "")

withUnsafePointer(to: one) { start in
  let buffer = UnsafeBufferPointer(start: start, count: 1)
  return buffer.span.bytes.byteCount //-> either 17 or 24.
}

withUnsafePointer(to: two) { start in
  let start = start.pointer(to: \.0)
  let buffer = UnsafeBufferPointer(start: start, count: 2)
  return buffer.span.bytes.byteCount //-> either 41 or 48.
}

Should the padding after the last element always be excluded?

Copy link
Contributor Author

@glessard glessard Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using tuples in this way is incorrect and always has been, because of that padding issue. The buffer in the wUP(to: two) closure is already invalid. We do get away with it with C imports because in that case stride is defined to be equal to size. We would really prefer to get this pattern (buffer-from-a-tuple) moved over to InlineArray.

)
self = unsafe _overrideLifetime(rawSpan, copying: span)
}
}

Expand Down Expand Up @@ -440,7 +444,7 @@ extension RawSpan {
public func _extracting(
unchecked bounds: ClosedRange<Int>
) -> Self {
let range = Range(
let range = unsafe Range(
_uncheckedBounds: (bounds.lowerBound, bounds.upperBound + 1)
)
return unsafe _extracting(unchecked: range)
Expand Down Expand Up @@ -663,7 +667,7 @@ extension RawSpan {
guard let spanStart = other._pointer, _count > 0 else {
return unsafe _pointer == other._pointer ? 0..<0 : nil
}
let start = _start()
let start = unsafe _start()
let spanEnd = unsafe spanStart + other._count
if unsafe spanStart < start || (start + _count) < spanEnd { return nil }
let lower = unsafe start.distance(to: spanStart)
Expand Down
18 changes: 16 additions & 2 deletions stdlib/public/core/Span/Span.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public struct Span<Element: ~Copyable & ~Escapable>
@usableFromInline
internal let _pointer: UnsafeRawPointer?

@unsafe
@_alwaysEmitIntoClient
internal func _start() -> UnsafeRawPointer {
unsafe _pointer._unsafelyUnwrappedUnchecked
Expand Down Expand Up @@ -493,6 +494,19 @@ extension Span where Element: BitwiseCopyable {
}
}

@available(SwiftStdlib 6.2, *)
extension Span where Element: BitwiseCopyable {

public var bytes: RawSpan {
@lifetime(copy self)
@_alwaysEmitIntoClient
get {
let rawSpan = RawSpan(_elements: self)
return unsafe _overrideLifetime(rawSpan, copying: self)
}
}
}

// MARK: sub-spans
@available(SwiftStdlib 6.2, *)
extension Span where Element: ~Copyable {
Expand Down Expand Up @@ -590,7 +604,7 @@ extension Span where Element: ~Copyable {
public func _extracting(
unchecked bounds: ClosedRange<Index>
) -> Self {
let range = Range(
let range = unsafe Range(
_uncheckedBounds: (bounds.lowerBound, bounds.upperBound + 1)
)
return unsafe _extracting(unchecked: range)
Expand Down Expand Up @@ -699,7 +713,7 @@ extension Span where Element: ~Copyable {
guard let spanStart = other._pointer, _count > 0 else {
return unsafe _pointer == other._pointer ? 0..<0 : nil
}
let start = _start()
let start = unsafe _start()
let stride = MemoryLayout<Element>.stride
let spanEnd = unsafe spanStart + stride &* other._count
if unsafe spanStart < start || spanEnd > (start + stride &* _count) {
Expand Down
13 changes: 12 additions & 1 deletion stdlib/public/core/UnsafeBufferPointer.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -564,6 +564,17 @@ extension Unsafe${Mutable}BufferPointer where Element: ~Copyable {
public func extracting(_ bounds: UnboundedRange) -> Self {
unsafe self
}

@unsafe
@available(SwiftStdlib 6.2, *)
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
get {
let span = unsafe Span(_unsafeElements: self)
return unsafe _overrideLifetime(span, borrowing: self)
}
}
}

extension Unsafe${Mutable}BufferPointer {
Expand Down
Loading