Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ set(SWIFT_BENCH_MODULES
single-source/ArrayOfRef
single-source/ArrayRemoveAll
single-source/ArraySetElement
single-source/ArraySliceTests
single-source/ArraySubscript
single-source/ArrayTests
single-source/AsyncTree
single-source/BinaryFloatingPointConversionFromBinaryInteger
single-source/BinaryFloatingPointProperties
Expand All @@ -65,6 +67,7 @@ set(SWIFT_BENCH_MODULES
single-source/ClassArrayGetter
single-source/CodableTest
single-source/Combos
single-source/ContiguousArrayTests
single-source/CountAlgo
single-source/DataBenchmarks
single-source/DeadArray
Expand Down
46 changes: 46 additions & 0 deletions benchmark/single-source/ArraySliceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===--- ArraySliceTests.swift --------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import TestsUtils

public let benchmarks = [
BenchmarkInfo(name: "ArraySliceEqualUnique", runFunction: run_ArraySliceEqualUnique, tags: [.validation, .api, .Array]),
BenchmarkInfo(name: "ArraySliceEqualShared", runFunction: run_ArraySliceEqualShared, tags: [.validation, .api, .Array]),
BenchmarkInfo(name: "ArraySliceIdentical", runFunction: run_ArraySliceIdentical, tags: [.validation, .api, .Array]),
]

@inline(never)
public func run_ArraySliceEqualUnique(_ n: Int) {
let a1 = ArraySlice(0 ..< n)
let a2 = ArraySlice(0 ..< n)
for _ in 0 ..< 100_000 {
check(a1 == a2)
}
}

@inline(never)
public func run_ArraySliceEqualShared(_ n: Int) {
let a1 = ArraySlice(0 ..< n)
let a2 = a1
for _ in 0 ..< 100_000 {
check(a1 == a2)
}
}

@inline(never)
public func run_ArraySliceIdentical(_ n: Int) {
let a1 = ArraySlice(0 ..< n)
let a2 = a1
for _ in 0 ..< 100_000 {
check(a1.isTriviallyIdentical(to: a2))
}
}
46 changes: 46 additions & 0 deletions benchmark/single-source/ArrayTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===--- ArrayTests.swift -------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import TestsUtils

public let benchmarks = [
BenchmarkInfo(name: "ArrayEqualUnique", runFunction: run_ArrayEqualUnique, tags: [.validation, .api, .Array]),
BenchmarkInfo(name: "ArrayEqualShared", runFunction: run_ArrayEqualShared, tags: [.validation, .api, .Array]),
BenchmarkInfo(name: "ArrayIdentical", runFunction: run_ArrayIdentical, tags: [.validation, .api, .Array]),
]

@inline(never)
public func run_ArrayEqualUnique(_ n: Int) {
let a1 = Array(0 ..< n)
let a2 = Array(0 ..< n)
for _ in 0 ..< 100_000 {
check(a1 == a2)
}
}

@inline(never)
public func run_ArrayEqualShared(_ n: Int) {
let a1 = Array(0 ..< n)
let a2 = a1
for _ in 0 ..< 100_000 {
check(a1 == a2)
}
}

@inline(never)
public func run_ArrayIdentical(_ n: Int) {
let a1 = Array(0 ..< n)
let a2 = a1
for _ in 0 ..< 100_000 {
check(a1.isTriviallyIdentical(to: a2))
}
}
46 changes: 46 additions & 0 deletions benchmark/single-source/ContiguousArrayTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===--- ContiguousArrayTests.swift ---------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import TestsUtils

public let benchmarks = [
BenchmarkInfo(name: "ContiguousArrayEqualUnique", runFunction: run_ContiguousArrayEqualUnique, tags: [.validation, .api, .Array]),
BenchmarkInfo(name: "ContiguousArrayEqualShared", runFunction: run_ContiguousArrayEqualShared, tags: [.validation, .api, .Array]),
BenchmarkInfo(name: "ContiguousArrayIdentical", runFunction: run_ContiguousArrayIdentical, tags: [.validation, .api, .Array]),
]

@inline(never)
public func run_ContiguousArrayEqualUnique(_ n: Int) {
let a1 = ContiguousArray(0 ..< n)
let a2 = ContiguousArray(0 ..< n)
for _ in 0 ..< 100_000 {
check(a1 == a2)
}
}

@inline(never)
public func run_ContiguousArrayEqualShared(_ n: Int) {
let a1 = ContiguousArray(0 ..< n)
let a2 = a1
for _ in 0 ..< 100_000 {
check(a1 == a2)
}
}

@inline(never)
public func run_ContiguousArrayIdentical(_ n: Int) {
let a1 = ContiguousArray(0 ..< n)
let a2 = a1
for _ in 0 ..< 100_000 {
check(a1.isTriviallyIdentical(to: a2))
}
}
7 changes: 7 additions & 0 deletions benchmark/utils/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import ArrayOfPOD
import ArrayOfRef
import ArrayRemoveAll
import ArraySetElement
import ArraySliceTests
import ArraySubscript
import ArrayTests
import AsyncTree
import BinaryFloatingPointConversionFromBinaryInteger
import BinaryFloatingPointProperties
Expand All @@ -53,6 +55,7 @@ import Chars
import ClassArrayGetter
import CodableTest
import Combos
import ContiguousArrayTests
import CountAlgo
import CreateObjects
// rdar://128520766
Expand Down Expand Up @@ -227,7 +230,9 @@ register(ArrayOfPOD.benchmarks)
register(ArrayOfRef.benchmarks)
register(ArrayRemoveAll.benchmarks)
register(ArraySetElement.benchmarks)
register(ArraySliceTests.benchmarks)
register(ArraySubscript.benchmarks)
register(ArrayTests.benchmarks)
register(AsyncTree.benchmarks)
register(BinaryFloatingPointConversionFromBinaryInteger.benchmarks)
register(BinaryFloatingPointProperties.benchmarks)
Expand All @@ -252,8 +257,10 @@ register(CharacterRecognizer.benchmarks)
register(Chars.benchmarks)
register(CodableTest.benchmarks)
register(Combos.benchmarks)
register(ContiguousArrayTests.benchmarks)
register(CountAlgo.benchmarks)
register(ClassArrayGetter.benchmarks)
register(ContiguousArrayTests.benchmarks)
register(CreateObjects.benchmarks)
// rdar://128520766
// register(CxxSetToCollection.benchmarks)
Expand Down
41 changes: 41 additions & 0 deletions stdlib/public/core/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2160,3 +2160,44 @@ internal struct _ArrayAnyHashableBox<Element: Hashable>
}

extension Array: @unchecked Sendable where Element: Sendable { }

extension Array {
/// Returns a boolean value indicating whether this array is identical to
/// `other`.
///
/// Two array values are identical if there is no way to distinguish between
/// them.
///
/// For any values `a`, `b`, and `c`:
///
/// - `a.isTriviallyIdentical(to: a)` is always `true`. (Reflexivity)
/// - `a.isTriviallyIdentical(to: b)` implies `b.isTriviallyIdentical(to: a)`.
/// (Symmetry)
/// - If `a.isTriviallyIdentical(to: b)` and `b.isTriviallyIdentical(to: c)`
/// are both `true`, then `a.isTriviallyIdentical(to: c)` is also `true`.
/// (Transitivity)
/// - If `a` and `b` are `Equatable`, then `a.isTriviallyIdentical(b)` implies
/// `a == b`
/// - `a == b` does not imply `a.isTriviallyIdentical(b)`
///
/// Values produced by copying the same value, with no intervening mutations,
/// will compare identical:
///
/// ```swift
/// let d = c
/// print(c.isTriviallyIdentical(to: d))
/// // Prints true
/// ```
///
/// Comparing arrays this way includes comparing (normally) hidden
/// implementation details such as the memory location of any underlying
/// array storage object. Therefore, identical arrays are guaranteed to
/// compare equal with `==`, but not all equal arrays are considered
/// identical.
///
/// - Performance: O(1)
@_alwaysEmitIntoClient
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lorentey Did we want to ship these opaque?

Copy link
Member

Choose a reason for hiding this comment

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

That would prevent generic specialization, so the answer is no, sadly.

public func isTriviallyIdentical(to other: Self) -> Bool {
self._buffer.identity == other._buffer.identity
}
}
42 changes: 42 additions & 0 deletions stdlib/public/core/ArraySlice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1607,3 +1607,45 @@ extension ArraySlice {
}
}
#endif

extension ArraySlice {
/// Returns a boolean value indicating whether this array is identical to
/// `other`.
///
/// Two array values are identical if there is no way to distinguish between
/// them.
///
/// For any values `a`, `b`, and `c`:
///
/// - `a.isTriviallyIdentical(to: a)` is always `true`. (Reflexivity)
/// - `a.isTriviallyIdentical(to: b)` implies `b.isTriviallyIdentical(to: a)`.
/// (Symmetry)
/// - If `a.isTriviallyIdentical(to: b)` and `b.isTriviallyIdentical(to: c)`
/// are both `true`, then `a.isTriviallyIdentical(to: c)` is also `true`.
/// (Transitivity)
/// - If `a` and `b` are `Equatable`, then `a.isTriviallyIdentical(b)` implies
/// `a == b`
/// - `a == b` does not imply `a.isTriviallyIdentical(b)`
///
/// Values produced by copying the same value, with no intervening mutations,
/// will compare identical:
///
/// ```swift
/// let d = c
/// print(c.isTriviallyIdentical(to: d))
/// // Prints true
/// ```
///
/// Comparing arrays this way includes comparing (normally) hidden
/// implementation details such as the memory location of any underlying
/// array storage object. Therefore, identical arrays are guaranteed to
/// compare equal with `==`, but not all equal arrays are considered
/// identical.
///
/// - Performance: O(1)
@_alwaysEmitIntoClient
public func isTriviallyIdentical(to other: Self) -> Bool {
self._buffer.identity == other._buffer.identity &&
self.count == other.count
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lorentey I think we have to check the count here… or else we could have the same buffer with two different ranges that compare as identical. But if the buffer identity itself already defends against that then I can remove this extra check.

Copy link
Member

@lorentey lorentey Oct 17, 2025

Choose a reason for hiding this comment

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

This does appear sound for all cases I've seen in practice -- the buffer identity is simply the address of its first element, and including the count guarantees that the two slices are backed by the same region of memory.

But two slices being backed by the same memory does not necessarily imply that they must be identical slices. It does leave me worried that this shortcut may misfire in some exotic/hypothetical cases, such as if two distinct bridged read-only NSArray instances somehow end up getting backed by overlapping memory regions, and therefore two array slices may be backed by the same memory, but still be distinguishable -- for example, by looking at their startIndex. I'm not aware of any actual NSArray subclass that would do that, but I do believe it is technically possible.

To avoid this (and (perhaps more practical) similar cases I may be missing), I think we should just follow best practice and simply compare all stored properties, with no clever shortcuts, like in the draft below:

extension _SliceBuffer {
  @_alwaysEmitIntoClient
  internal func isTriviallyIdentical(to other: Self) -> Bool {
    self.owner == other.owner 
    && self.subscriptBaseAddress == other.subscriptBaseAddress
    && self.startIndex == other.startIndex
    && self.endIndexAndFlags == other.endIndexAndFlags
  }
}

extension ArraySlice {
  @_alwaysEmitIntoClient
  public func isTriviallyIdentical(to other: Self) -> Bool {
    self._buffer.isTriviallyIdentical(to: other._buffer)
  }
}

(Note that this only applies to array slices; I believe comparing _buffer.identity will suffice for Array and ContiguousArray. In the bridged case, the identity is simply the object identity of the NSArray instance.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lorentey Ahh… that's a good idea! I'll push a new commit. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm… I'm running into trouble now trying to compare self.owner to other.owner because of the Builtin.NativeObject type…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://github.com/swiftlang/swift/blob/swift-6.2-RELEASE/stdlib/public/core/Builtin.swift#L135-L140

I think I need this one… but for some reason I'm not able to call it without a compiler error…

}
}
41 changes: 41 additions & 0 deletions stdlib/public/core/ContiguousArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1515,3 +1515,44 @@ extension ContiguousArray {

extension ContiguousArray: @unchecked Sendable
where Element: Sendable { }

extension ContiguousArray {
/// Returns a boolean value indicating whether this array is identical to
/// `other`.
///
/// Two array values are identical if there is no way to distinguish between
/// them.
///
/// For any values `a`, `b`, and `c`:
///
/// - `a.isTriviallyIdentical(to: a)` is always `true`. (Reflexivity)
/// - `a.isTriviallyIdentical(to: b)` implies `b.isTriviallyIdentical(to: a)`.
/// (Symmetry)
/// - If `a.isTriviallyIdentical(to: b)` and `b.isTriviallyIdentical(to: c)`
/// are both `true`, then `a.isTriviallyIdentical(to: c)` is also `true`.
/// (Transitivity)
/// - If `a` and `b` are `Equatable`, then `a.isTriviallyIdentical(b)` implies
/// `a == b`
/// - `a == b` does not imply `a.isTriviallyIdentical(b)`
///
/// Values produced by copying the same value, with no intervening mutations,
/// will compare identical:
///
/// ```swift
/// let d = c
/// print(c.isTriviallyIdentical(to: d))
/// // Prints true
/// ```
///
/// Comparing arrays this way includes comparing (normally) hidden
/// implementation details such as the memory location of any underlying
/// array storage object. Therefore, identical arrays are guaranteed to
/// compare equal with `==`, but not all equal arrays are considered
/// identical.
///
/// - Performance: O(1)
@_alwaysEmitIntoClient
public func isTriviallyIdentical(to other: Self) -> Bool {
self._buffer.identity == other._buffer.identity
}
}
43 changes: 43 additions & 0 deletions test/stdlib/ArraySliceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// 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-simple-swift(-parse-as-library)
// REQUIRES: executable_test
// END.
//
//===----------------------------------------------------------------------===//

import StdlibUnittest

@main
enum ArraySliceTests {
static func main() {
let testSuite = TestSuite("ArraySliceTests")
testSuite.test("Identical", testIdentical)
runAllTests()
}

static func testIdentical() {
let a1: ArraySlice = [0, 1, 2, 3]
expectTrue(a1.isTriviallyIdentical(to: a1))

let a2: ArraySlice = a1
expectTrue(a1.isTriviallyIdentical(to: a2))

var a3: ArraySlice = a2
a3.reserveCapacity(0)
expectFalse(a1.isTriviallyIdentical(to: a3))

let a4: ArraySlice = [0, 1, 2, 3]
expectFalse(a1.isTriviallyIdentical(to: a4))
}
}
Loading