Skip to content

[5.5][stdlib] Implement _copyContents on internal Array types #37960

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 2 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions stdlib/public/core/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1865,6 +1865,22 @@ extension Array {
}
}

#if INTERNAL_CHECKS_ENABLED
extension Array {
// This allows us to test the `_copyContents` implementation in
// `_ArrayBuffer`. (It's like `_copyToContiguousArray` but it always makes a
// copy.)
@_alwaysEmitIntoClient
public func _copyToNewArray() -> [Element] {
Array(unsafeUninitializedCapacity: self.count) { buffer, count in
var (it, c) = self._buffer._copyContents(initializing: buffer)
_precondition(it.next() == nil)
count = c
}
}
}
#endif

#if _runtime(_ObjC)
// We isolate the bridging of the Cocoa Array -> Swift Array here so that
// in the future, we can eagerly bridge the Cocoa array. We need this function
Expand Down
18 changes: 13 additions & 5 deletions stdlib/public/core/ArrayBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,20 @@ extension _ArrayBuffer {
return UnsafeMutableRawPointer(result).assumingMemoryBound(to: Element.self)
}

public __consuming func _copyContents(
@inlinable
internal __consuming func _copyContents(
initializing buffer: UnsafeMutableBufferPointer<Element>
) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) {
// This customization point is not implemented for internal types.
// Accidentally calling it would be a catastrophic performance bug.
fatalError("unsupported")
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
if _fastPath(_isNative) {
let (_, c) = _native._copyContents(initializing: buffer)
return (IndexingIterator(_elements: self, _position: c), c)
}
guard buffer.count > 0 else { return (makeIterator(), 0) }
let ptr = UnsafeMutableRawPointer(buffer.baseAddress)?
.assumingMemoryBound(to: AnyObject.self)
let (_, c) = _nonNative._copyContents(
initializing: UnsafeMutableBufferPointer(start: ptr, count: buffer.count))
return (IndexingIterator(_elements: self, _position: c), c)
}

/// Returns a `_SliceBuffer` containing the given sub-range of elements in
Expand Down
16 changes: 16 additions & 0 deletions stdlib/public/core/ArraySlice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1520,3 +1520,19 @@ extension ArraySlice {

extension ArraySlice: Sendable, UnsafeSendable
where Element: Sendable { }

#if INTERNAL_CHECKS_ENABLED
extension ArraySlice {
// This allows us to test the `_copyContents` implementation in
// `_SliceBuffer`. (It's like `_copyToContiguousArray` but it always makes a
// copy.)
@_alwaysEmitIntoClient
public func _copyToNewArray() -> [Element] {
Array(unsafeUninitializedCapacity: self.count) { buffer, count in
var (it, c) = self._buffer._copyContents(initializing: buffer)
_precondition(it.next() == nil)
count = c
}
}
}
#endif
11 changes: 11 additions & 0 deletions stdlib/public/core/CocoaArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,16 @@ internal struct _CocoaArrayWrapper: RandomAccessCollection {
}
return result
}

@_alwaysEmitIntoClient
internal __consuming func _copyContents(
initializing buffer: UnsafeMutableBufferPointer<Element>
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
guard buffer.count > 0 else { return (makeIterator(), 0) }
let start = buffer.baseAddress!
let c = Swift.min(self.count, buffer.count)
let end = _copyContents(subRange: 0 ..< c, initializing: start)
return (IndexingIterator(_elements: self, _position: c), c)
}
}
#endif
15 changes: 10 additions & 5 deletions stdlib/public/core/ContiguousArrayBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -644,12 +644,17 @@ internal struct _ContiguousArrayBuffer<Element>: _ArrayBufferProtocol {
return target + initializedCount
}

public __consuming func _copyContents(
@inlinable
internal __consuming func _copyContents(
initializing buffer: UnsafeMutableBufferPointer<Element>
) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) {
// This customization point is not implemented for internal types.
// Accidentally calling it would be a catastrophic performance bug.
fatalError("unsupported")
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
guard buffer.count > 0 else { return (makeIterator(), 0) }
let c = Swift.min(self.count, buffer.count)
buffer.baseAddress!.initialize(
from: firstElementAddress,
count: c)
_fixLifetime(owner)
return (IndexingIterator(_elements: self, _position: c), c)
}

/// Returns a `_SliceBuffer` containing the given `bounds` of values
Expand Down
16 changes: 11 additions & 5 deletions stdlib/public/core/SliceBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,18 @@ internal struct _SliceBuffer<Element>
return target + c
}

public __consuming func _copyContents(
@inlinable
internal __consuming func _copyContents(
initializing buffer: UnsafeMutableBufferPointer<Element>
) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) {
// This customization point is not implemented for internal types.
// Accidentally calling it would be a catastrophic performance bug.
fatalError("unsupported")
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
_invariantCheck()
guard buffer.count > 0 else { return (makeIterator(), 0) }
let c = Swift.min(self.count, buffer.count)
buffer.baseAddress!.initialize(
from: firstElementAddress,
count: c)
_fixLifetime(owner)
return (IndexingIterator(_elements: self, _position: startIndex + c), c)
}

/// True, if the array is native and does not need a deferred type check.
Expand Down
69 changes: 68 additions & 1 deletion test/stdlib/ArrayBridge.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 - 2017 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2021 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 @@ -471,6 +471,8 @@ tests.test("testMutableArray") {
}

tests.test("rdar://problem/27905230") {
// Casting an NSArray to Array<Any> would trap because of an erroneous
// precondition.
let dict = RDar27905230.mutableDictionaryOfMutableLists()!
let arr = dict["list"]!
expectEqual(arr[0] as! NSNull, NSNull())
Expand All @@ -482,4 +484,69 @@ tests.test("rdar://problem/27905230") {
expectEqual(arr[5] as! Date, Date(timeIntervalSince1970: 0))
}

tests.test("verbatimBridged/Base/withUnsafeBufferPointer") {
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
let b = a as! [Base]
let success: Bool = b.withUnsafeBufferPointer { buffer in
expectEqual(buffer.count, 4)
guard buffer.count == 4 else { return false }
expectEqual(buffer[0].value, 0)
expectEqual(buffer[1].value, 1)
expectEqual(buffer[2].value, 2)
expectEqual(buffer[3].value, 3)
return true
}
expectTrue(success)
}

// https://bugs.swift.org/browse/SR-14663
tests.test("verbatimBridged/AnyObject/withUnsafeBufferPointer") {
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
let b = a as [AnyObject]
let success: Bool = b.withUnsafeBufferPointer { buffer in
expectEqual(buffer.count, 4)
guard buffer.count == 4 else { return false }
expectEqual((buffer[0] as? Base)?.value, 0)
expectEqual((buffer[1] as? Base)?.value, 1)
expectEqual((buffer[2] as? Base)?.value, 2)
expectEqual((buffer[3] as? Base)?.value, 3)
return true
}
expectTrue(success)
}

tests.test("verbatimBridged/Base/withUnsafeMutableBufferPointer") {
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
var b = a as! [Base]
let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
expectEqual(buffer.count, 4)
guard buffer.count == 4 else { return false }
expectEqual(buffer[0].value, 0)
expectEqual(buffer[1].value, 1)
expectEqual(buffer[2].value, 2)
expectEqual(buffer[3].value, 3)
buffer[0] = Base(4)
return true
}
expectTrue(success)
expectEqual(b[0].value, 4)
}

tests.test("verbatimBridged/AnyObject/withUnsafeMutableBufferPointer") {
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
var b = a as [AnyObject]
let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
expectEqual(buffer.count, 4)
guard buffer.count == 4 else { return false }
expectEqual((buffer[0] as? Base)?.value, 0)
expectEqual((buffer[1] as? Base)?.value, 1)
expectEqual((buffer[2] as? Base)?.value, 2)
expectEqual((buffer[3] as? Base)?.value, 3)
buffer[0] = Base(4)
return true
}
expectTrue(success)
expectEqual((b[0] as? Base)?.value, 4)
}

runAllTests()
80 changes: 80 additions & 0 deletions test/stdlib/ArrayBuffer_CopyContents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//===--- ArrayBuffer_CopyContents.swift -----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2021 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-simple-swift
// REQUIRES: executable_test
// REQUIRES: swift_stdlib_asserts
// REQUIRES: foundation

import Foundation
import StdlibUnittest

let suite = TestSuite("ArrayBuffer_CopyContents")
defer { runAllTests() }


var trackedCount = 0
var nextBaseSerialNumber = 0

/// A type that will be bridged verbatim to Objective-C
class Thing: NSObject {
var value: Int
var serialNumber: Int

func foo() { }

required init(_ value: Int) {
trackedCount += 1
nextBaseSerialNumber += 1
serialNumber = nextBaseSerialNumber
self.value = value
}

deinit {
assert(serialNumber > 0, "double destruction!")
trackedCount -= 1
serialNumber = -serialNumber
}

override func isEqual(_ other: Any?) -> Bool {
return (other as? Thing)?.value == self.value
}

override var hash: Int { value }
}


suite.test("nativeArray/_copyContents") {
let array = [Thing(0), Thing(1), Thing(2), Thing(3)]
expectEqualSequence(array._copyToNewArray(), array)
}

suite.test("nativeArraySlice/_copyContents") {
let array = (0 ..< 100).map { Thing($0) }
expectEqualSequence(
array[20 ..< 30]._copyToNewArray(),
(20 ..< 30).map { Thing($0) })
}

suite.test("bridgedArray/_copyContents") {
let array = NSArray(array: (0 ..< 5).map { Thing($0) }) as! [Thing]
expectEqualSequence(
array._copyToNewArray(),
(0 ..< 5).map { Thing($0) })
}

suite.test("bridgedArraySlice/_copyContents") {
let array = NSArray(array: (0 ..< 100).map { Thing($0) }) as! [Thing]
expectEqualSequence(
array[20 ..< 30]._copyToNewArray(),
(20 ..< 30).map { Thing($0) })
}