diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index a3d5962daca16..7edcc50e743f7 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -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 diff --git a/stdlib/public/core/ArrayBuffer.swift b/stdlib/public/core/ArrayBuffer.swift index 1f0e0bf5dcdfd..ba8b39f32a8ed 100644 --- a/stdlib/public/core/ArrayBuffer.swift +++ b/stdlib/public/core/ArrayBuffer.swift @@ -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 - ) -> (Iterator,UnsafeMutableBufferPointer.Index) { - // This customization point is not implemented for internal types. - // Accidentally calling it would be a catastrophic performance bug. - fatalError("unsupported") + ) -> (Iterator, UnsafeMutableBufferPointer.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 diff --git a/stdlib/public/core/ArraySlice.swift b/stdlib/public/core/ArraySlice.swift index 3cc8a964c5147..75130e368bf33 100644 --- a/stdlib/public/core/ArraySlice.swift +++ b/stdlib/public/core/ArraySlice.swift @@ -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 diff --git a/stdlib/public/core/CocoaArray.swift b/stdlib/public/core/CocoaArray.swift index 9ba41d0ebaa48..ff61076d079a6 100644 --- a/stdlib/public/core/CocoaArray.swift +++ b/stdlib/public/core/CocoaArray.swift @@ -146,5 +146,16 @@ internal struct _CocoaArrayWrapper: RandomAccessCollection { } return result } + + @_alwaysEmitIntoClient + internal __consuming func _copyContents( + initializing buffer: UnsafeMutableBufferPointer + ) -> (Iterator, UnsafeMutableBufferPointer.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 diff --git a/stdlib/public/core/ContiguousArrayBuffer.swift b/stdlib/public/core/ContiguousArrayBuffer.swift index 95ba75697ba3f..a4e7cb5fec4e0 100644 --- a/stdlib/public/core/ContiguousArrayBuffer.swift +++ b/stdlib/public/core/ContiguousArrayBuffer.swift @@ -644,12 +644,17 @@ internal struct _ContiguousArrayBuffer: _ArrayBufferProtocol { return target + initializedCount } - public __consuming func _copyContents( + @inlinable + internal __consuming func _copyContents( initializing buffer: UnsafeMutableBufferPointer - ) -> (Iterator,UnsafeMutableBufferPointer.Index) { - // This customization point is not implemented for internal types. - // Accidentally calling it would be a catastrophic performance bug. - fatalError("unsupported") + ) -> (Iterator, UnsafeMutableBufferPointer.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 diff --git a/stdlib/public/core/SliceBuffer.swift b/stdlib/public/core/SliceBuffer.swift index 6da4a339bb7e4..34e30c1817de5 100644 --- a/stdlib/public/core/SliceBuffer.swift +++ b/stdlib/public/core/SliceBuffer.swift @@ -245,12 +245,18 @@ internal struct _SliceBuffer return target + c } - public __consuming func _copyContents( + @inlinable + internal __consuming func _copyContents( initializing buffer: UnsafeMutableBufferPointer - ) -> (Iterator,UnsafeMutableBufferPointer.Index) { - // This customization point is not implemented for internal types. - // Accidentally calling it would be a catastrophic performance bug. - fatalError("unsupported") + ) -> (Iterator, UnsafeMutableBufferPointer.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. diff --git a/test/stdlib/ArrayBridge.swift.gyb b/test/stdlib/ArrayBridge.swift.gyb index 6e02d9bd8c448..cfbbdf65697a9 100644 --- a/test/stdlib/ArrayBridge.swift.gyb +++ b/test/stdlib/ArrayBridge.swift.gyb @@ -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 @@ -471,6 +471,8 @@ tests.test("testMutableArray") { } tests.test("rdar://problem/27905230") { + // Casting an NSArray to Array would trap because of an erroneous + // precondition. let dict = RDar27905230.mutableDictionaryOfMutableLists()! let arr = dict["list"]! expectEqual(arr[0] as! NSNull, NSNull()) @@ -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() diff --git a/test/stdlib/ArrayBuffer_CopyContents.swift b/test/stdlib/ArrayBuffer_CopyContents.swift new file mode 100644 index 0000000000000..81cdc88872076 --- /dev/null +++ b/test/stdlib/ArrayBuffer_CopyContents.swift @@ -0,0 +1,79 @@ +//===--- 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 + +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) }) +}