From 5d8bb6820938a1ed53c3c93dc90aab5a07b58d08 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 8 Sep 2022 16:05:53 -0700 Subject: [PATCH 1/6] =?UTF-8?q?[PersistentCollections]=20ReturnBitmapIndex?= =?UTF-8?q?edNode=20=E2=86=92=20=5FNode/Self?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PersistentDictionary._Node.swift | 27 +++++++++---------- .../_DictionaryNodeProtocol.swift | 4 +-- .../PersistentCollections/_NodeProtocol.swift | 1 - 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Sources/PersistentCollections/PersistentDictionary._Node.swift b/Sources/PersistentCollections/PersistentDictionary._Node.swift index 3282caf5b..83a1bb932 100644 --- a/Sources/PersistentCollections/PersistentDictionary._Node.swift +++ b/Sources/PersistentCollections/PersistentDictionary._Node.swift @@ -49,7 +49,7 @@ extension PersistentDictionary { typealias Capacity = _NodeHeader.Capacity typealias DataBufferElement = ReturnPayload - typealias TrieBufferElement = ReturnBitmapIndexedNode + typealias TrieBufferElement = _Node var header: _NodeHeader var count: Int @@ -314,7 +314,6 @@ extension PersistentDictionary._Node { extension PersistentDictionary._Node: _NodeProtocol { typealias ReturnPayload = (key: Key, value: Value) - typealias ReturnBitmapIndexedNode = _Node var hasNodes: Bool { header.trieMap != 0 } @@ -820,8 +819,8 @@ extension PersistentDictionary._Node { _ bitpos: _NodeHeader.Bitmap, _ newValue: Value ) -> _Node { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode + let src: _Node = self + let dst: _Node if isStorageKnownUniquelyReferenced { dst = src @@ -845,8 +844,8 @@ extension PersistentDictionary._Node { _ newNode: TrieBufferElement, updateCount: (inout Int) -> Void ) -> _Node { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode + let src: _Node = self + let dst: _Node if isStorageKnownUniquelyReferenced { dst = src @@ -870,8 +869,8 @@ extension PersistentDictionary._Node { _ key: Key, _ value: Value ) -> _Node { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode + let src: _Node = self + let dst: _Node let hasRoomForData = header.dataCount < dataCapacity @@ -901,8 +900,8 @@ extension PersistentDictionary._Node { _ isStorageKnownUniquelyReferenced: Bool, _ bitpos: _NodeHeader.Bitmap ) -> _Node { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode + let src: _Node = self + let dst: _Node if isStorageKnownUniquelyReferenced { dst = src @@ -928,8 +927,8 @@ extension PersistentDictionary._Node { _ bitpos: _NodeHeader.Bitmap, _ node: TrieBufferElement ) -> _Node { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode + let src: _Node = self + let dst: _Node let hasRoomForTrie = header.trieCount < trieCapacity @@ -973,8 +972,8 @@ extension PersistentDictionary._Node { _ bitpos: _NodeHeader.Bitmap, _ tuple: (key: Key, value: Value) ) -> _Node { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode + let src: _Node = self + let dst: _Node let hasRoomForData = header.dataCount < dataCapacity diff --git a/Sources/PersistentCollections/_DictionaryNodeProtocol.swift b/Sources/PersistentCollections/_DictionaryNodeProtocol.swift index f40f5750a..53ff93db4 100644 --- a/Sources/PersistentCollections/_DictionaryNodeProtocol.swift +++ b/Sources/PersistentCollections/_DictionaryNodeProtocol.swift @@ -27,12 +27,12 @@ internal protocol _DictionaryNodeProtocol: _NodeProtocol { _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout _DictionaryEffect - ) -> ReturnBitmapIndexedNode + ) -> Self func removeOrRemoving( _ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout _DictionaryEffect - ) -> ReturnBitmapIndexedNode + ) -> Self } diff --git a/Sources/PersistentCollections/_NodeProtocol.swift b/Sources/PersistentCollections/_NodeProtocol.swift index 8b0d2db22..bc5f661ba 100644 --- a/Sources/PersistentCollections/_NodeProtocol.swift +++ b/Sources/PersistentCollections/_NodeProtocol.swift @@ -11,7 +11,6 @@ protocol _NodeProtocol: AnyObject { associatedtype ReturnPayload - associatedtype ReturnBitmapIndexedNode: _NodeProtocol var hasNodes: Bool { get } From 36c7f9270caa4352738d77cbd09f91f28fcf644d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 8 Sep 2022 16:10:59 -0700 Subject: [PATCH 2/6] =?UTF-8?q?[PersistentCollections]=20TrieBufferElement?= =?UTF-8?q?=20=E2=86=92=20=5FNode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PersistentDictionary._Node.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Sources/PersistentCollections/PersistentDictionary._Node.swift b/Sources/PersistentCollections/PersistentDictionary._Node.swift index 83a1bb932..9f9b083b3 100644 --- a/Sources/PersistentCollections/PersistentDictionary._Node.swift +++ b/Sources/PersistentCollections/PersistentDictionary._Node.swift @@ -49,7 +49,6 @@ extension PersistentDictionary { typealias Capacity = _NodeHeader.Capacity typealias DataBufferElement = ReturnPayload - typealias TrieBufferElement = _Node var header: _NodeHeader var count: Int @@ -58,7 +57,7 @@ extension PersistentDictionary { let trieCapacity: Capacity let dataBaseAddress: UnsafeMutablePointer - let trieBaseAddress: UnsafeMutablePointer + let trieBaseAddress: UnsafeMutablePointer<_Node> deinit { dataBaseAddress.deinitialize(count: header.dataCount) @@ -99,14 +98,14 @@ extension PersistentDictionary._Node { dataCapacity: Capacity, trieCapacity: Capacity ) -> ( dataBaseAddress: UnsafeMutablePointer, - trieBaseAddress: UnsafeMutablePointer + trieBaseAddress: UnsafeMutablePointer<_Node> ) { let dataCapacityInBytes = Int(dataCapacity) * MemoryLayout.stride - let trieCapacityInBytes = Int(trieCapacity) * MemoryLayout.stride + let trieCapacityInBytes = Int(trieCapacity) * MemoryLayout<_Node>.stride let alignment = Swift.max( MemoryLayout.alignment, - MemoryLayout.alignment) + MemoryLayout<_Node>.alignment) let memory = UnsafeMutableRawPointer.allocate( byteCount: dataCapacityInBytes + trieCapacityInBytes, alignment: alignment) @@ -115,7 +114,7 @@ extension PersistentDictionary._Node { .advanced(by: trieCapacityInBytes) .bindMemory(to: DataBufferElement.self, capacity: Int(dataCapacity)) let trieBaseAddress = memory - .bindMemory(to: TrieBufferElement.self, capacity: Int(trieCapacity)) + .bindMemory(to: _Node.self, capacity: Int(trieCapacity)) return (dataBaseAddress, trieBaseAddress) } @@ -288,7 +287,7 @@ extension PersistentDictionary._Node { UnsafeBufferPointer(start: dataBaseAddress, count: header.dataCount) } - var _trieSlice: UnsafeMutableBufferPointer { + var _trieSlice: UnsafeMutableBufferPointer<_Node> { UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount) } @@ -841,7 +840,7 @@ extension PersistentDictionary._Node { _ isStorageKnownUniquelyReferenced: Bool, _ bitpos: _NodeHeader.Bitmap, _ idx: Int, - _ newNode: TrieBufferElement, + _ newNode: _Node, updateCount: (inout Int) -> Void ) -> _Node { let src: _Node = self @@ -925,7 +924,7 @@ extension PersistentDictionary._Node { func _copyAndMigrateFromInlineToNode( _ isStorageKnownUniquelyReferenced: Bool, _ bitpos: _NodeHeader.Bitmap, - _ node: TrieBufferElement + _ node: _Node ) -> _Node { let src: _Node = self let dst: _Node From 8d96e3d847b0e492f7b5cb88ac7c7d763be463dc Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 8 Sep 2022 16:16:27 -0700 Subject: [PATCH 3/6] =?UTF-8?q?[PersistentCollections]=20DataBufferElement?= =?UTF-8?q?=20=E2=86=92=20Element,=20ReturnPayload=20=E2=86=92=20Element?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Payload` wouldn’t be a bad term for this, though, and we may return to it when we start thinking about PersistentSet. --- .../PersistentDictionary._Node.swift | 33 +++++++++---------- .../PersistentCollections/_NodeProtocol.swift | 4 +-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Sources/PersistentCollections/PersistentDictionary._Node.swift b/Sources/PersistentCollections/PersistentDictionary._Node.swift index 9f9b083b3..4f8cc6335 100644 --- a/Sources/PersistentCollections/PersistentDictionary._Node.swift +++ b/Sources/PersistentCollections/PersistentDictionary._Node.swift @@ -47,8 +47,7 @@ extension PersistentDictionary { internal final class _Node { typealias Index = PersistentDictionary.Index typealias Capacity = _NodeHeader.Capacity - - typealias DataBufferElement = ReturnPayload + typealias Element = (key: Key, value: Value) var header: _NodeHeader var count: Int @@ -56,7 +55,7 @@ extension PersistentDictionary { let dataCapacity: Capacity let trieCapacity: Capacity - let dataBaseAddress: UnsafeMutablePointer + let dataBaseAddress: UnsafeMutablePointer let trieBaseAddress: UnsafeMutablePointer<_Node> deinit { @@ -97,14 +96,14 @@ extension PersistentDictionary._Node { static func _allocate( dataCapacity: Capacity, trieCapacity: Capacity ) -> ( - dataBaseAddress: UnsafeMutablePointer, + dataBaseAddress: UnsafeMutablePointer, trieBaseAddress: UnsafeMutablePointer<_Node> ) { - let dataCapacityInBytes = Int(dataCapacity) * MemoryLayout.stride + let dataCapacityInBytes = Int(dataCapacity) * MemoryLayout.stride let trieCapacityInBytes = Int(trieCapacity) * MemoryLayout<_Node>.stride let alignment = Swift.max( - MemoryLayout.alignment, + MemoryLayout.alignment, MemoryLayout<_Node>.alignment) let memory = UnsafeMutableRawPointer.allocate( byteCount: dataCapacityInBytes + trieCapacityInBytes, @@ -112,7 +111,7 @@ extension PersistentDictionary._Node { let dataBaseAddress = memory .advanced(by: trieCapacityInBytes) - .bindMemory(to: DataBufferElement.self, capacity: Int(dataCapacity)) + .bindMemory(to: Element.self, capacity: Int(dataCapacity)) let trieBaseAddress = memory .bindMemory(to: _Node.self, capacity: Int(trieCapacity)) @@ -216,7 +215,7 @@ extension PersistentDictionary._Node { assert(self.invariant) } - convenience init(collisions: [ReturnPayload]) { + convenience init(collisions: [Element]) { self.init(dataCapacity: Capacity(collisions.count), trieCapacity: 0) self.header = _NodeHeader( @@ -283,7 +282,7 @@ extension PersistentDictionary._Node { (header.dataMap & header.trieMap) == 0 || (header.dataMap == header.trieMap) } - var _dataSlice: UnsafeBufferPointer { + var _dataSlice: UnsafeBufferPointer { UnsafeBufferPointer(start: dataBaseAddress, count: header.dataCount) } @@ -312,8 +311,6 @@ extension PersistentDictionary._Node { } extension PersistentDictionary._Node: _NodeProtocol { - typealias ReturnPayload = (key: Key, value: Value) - var hasNodes: Bool { header.trieMap != 0 } var nodeArity: Int { header.trieCount } @@ -338,7 +335,7 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { let bitpos = _bitposFrom(mask) guard collisionFree else { - let content: [ReturnPayload] = Array(self) + let content: [Element] = Array(self) let hash = _computeHash(content.first!.key) guard keyHash == hash else { @@ -367,7 +364,7 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { let bitpos = _bitposFrom(mask) guard collisionFree else { - let content: [ReturnPayload] = Array(self) + let content: [Element] = Array(self) let hash = _computeHash(content.first!.key) guard keyHash == hash else { @@ -400,7 +397,7 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { _ skippedBefore: Int ) -> Index? { guard collisionFree else { - let content: [ReturnPayload] = Array(self) + let content: [Element] = Array(self) let hash = _computeHash(content.first!.key) assert(keyHash == hash) @@ -525,7 +522,7 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { ) -> _Node { assert(hashCollision) - let content: [ReturnPayload] = Array(self) + let content: [Element] = Array(self) let hash = _computeHash(content.first!.key) guard keyHash == hash else { @@ -535,7 +532,7 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { } if let index = content.firstIndex(where: { key == $0.key }) { - let updatedContent: [ReturnPayload] = ( + let updatedContent: [Element] = ( content[0.. _Node { assert(hashCollision) - let content: [ReturnPayload] = Array(self) + let content: [Element] = Array(self) let _ = _computeHash(content.first!.key) if let index = content.firstIndex(where: { key == $0.key }) { @@ -688,7 +685,7 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { } extension PersistentDictionary._Node { - func get(position: Index, _ shift: Int, _ stillToSkip: Int) -> ReturnPayload { + func get(position: Index, _ shift: Int, _ stillToSkip: Int) -> Element { var cumulativeCounts = self._counts for i in 1 ..< cumulativeCounts.count { diff --git a/Sources/PersistentCollections/_NodeProtocol.swift b/Sources/PersistentCollections/_NodeProtocol.swift index bc5f661ba..e0e276f49 100644 --- a/Sources/PersistentCollections/_NodeProtocol.swift +++ b/Sources/PersistentCollections/_NodeProtocol.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// protocol _NodeProtocol: AnyObject { - associatedtype ReturnPayload + associatedtype Element var hasNodes: Bool { get } @@ -22,7 +22,7 @@ protocol _NodeProtocol: AnyObject { var payloadArity: Int { get } - func getPayload(_ index: Int) -> ReturnPayload + func getPayload(_ index: Int) -> Element var count: Int { get } } From 7d6d779377e63cd5ed78b8865b6ee4e3987a44ca Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 8 Sep 2022 16:20:14 -0700 Subject: [PATCH 4/6] [PersistentCollections] Convert global lets to computed properties This is just to prevent these from getting turned into global variables that are initialized on first access, especially once we make things inlinable. (I expect these will all get moved under some type eventually.) --- Sources/PersistentCollections/_Common.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/PersistentCollections/_Common.swift b/Sources/PersistentCollections/_Common.swift index 617bd171e..436c3393e 100644 --- a/Sources/PersistentCollections/_Common.swift +++ b/Sources/PersistentCollections/_Common.swift @@ -13,13 +13,19 @@ internal func _computeHash(_ value: T) -> Int { value.hashValue } -internal let _bitPartitionSize: Int = 5 +@inline(__always) +internal var _bitPartitionSize: Int { 5 } -internal let _bitPartitionMask: Int = (1 << _bitPartitionSize) - 1 +@inline(__always) +internal var _bitPartitionMask: Int { (1 << _bitPartitionSize) - 1 } -internal let _hashCodeLength: Int = Int.bitWidth +@inline(__always) +internal var _hashCodeLength: Int { Int.bitWidth } -internal let _maxDepth = (_hashCodeLength + _bitPartitionSize - 1) / _bitPartitionSize +@inline(__always) +internal var _maxDepth: Int { + (_hashCodeLength + _bitPartitionSize - 1) / _bitPartitionSize +} internal func _maskFrom(_ hash: Int, _ shift: Int) -> Int { (hash >> shift) & _bitPartitionMask From 8429144ae7cfbd718b66eceab8d8c04f3bd88cf7 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 8 Sep 2022 23:56:43 -0700 Subject: [PATCH 5/6] [PersistentDictionary] Remove CVarArg placeholder --- .../PersistentDictionary+CVarArg.swift | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 Sources/PersistentCollections/PersistentDictionary+CVarArg.swift diff --git a/Sources/PersistentCollections/PersistentDictionary+CVarArg.swift b/Sources/PersistentCollections/PersistentDictionary+CVarArg.swift deleted file mode 100644 index 6785ed80e..000000000 --- a/Sources/PersistentCollections/PersistentDictionary+CVarArg.swift +++ /dev/null @@ -1,16 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 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 -// -//===----------------------------------------------------------------------===// - -//extension PersistentDictionary: CVarArg { -// public var _cVarArgEncoding: [Int] { -// <#code#> -// } -//} From 14f90620c89e311a067a1fa49813d8bbcd539950 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 9 Sep 2022 00:19:29 -0700 Subject: [PATCH 6/6] [PersistentCollections] Introduce _Bucket, _Bitmap, _HashPath, and _HashValue These provide nicer tools for slicing up hash values and dealing with node bitmaps. _Bucket is a logical entry in the hash table inside a node. _Bitmap is a set of bucket values, represented by a bitset. _HashPath facilitates slicing up a hash value into a series of bucket values. _HashValue represents a hash value. This is still just mostly superficial refactoring, with little to no functional changes. --- .../PersistentDictionary+Collection.swift | 4 +- .../PersistentDictionary._Node.swift | 767 ++++++++---------- .../PersistentDictionary.swift | 28 +- .../{ => Unused}/_BaseReverseIterator.swift | 3 +- .../Unused/_Node+Unused.swift | 29 - Sources/PersistentCollections/Utilities.swift | 62 ++ Sources/PersistentCollections/_Bitmap.swift | 147 ++++ Sources/PersistentCollections/_Bucket.swift | 35 + Sources/PersistentCollections/_Common.swift | 23 - .../_DictionaryNodeProtocol.swift | 20 +- Sources/PersistentCollections/_HashPath.swift | 52 ++ .../PersistentCollections/_HashValue.swift | 27 + .../PersistentCollections/_NonzeroBits.swift | 43 - .../BitmapSmokeTests.swift | 8 +- .../_CollidableInt.swift | 20 +- .../xcschemes/PersistentCollections.xcscheme | 1 + 16 files changed, 712 insertions(+), 557 deletions(-) rename Sources/PersistentCollections/{ => Unused}/_BaseReverseIterator.swift (99%) delete mode 100644 Sources/PersistentCollections/Unused/_Node+Unused.swift create mode 100644 Sources/PersistentCollections/Utilities.swift create mode 100644 Sources/PersistentCollections/_Bitmap.swift create mode 100644 Sources/PersistentCollections/_Bucket.swift create mode 100644 Sources/PersistentCollections/_HashPath.swift create mode 100644 Sources/PersistentCollections/_HashValue.swift delete mode 100644 Sources/PersistentCollections/_NonzeroBits.swift diff --git a/Sources/PersistentCollections/PersistentDictionary+Collection.swift b/Sources/PersistentCollections/PersistentDictionary+Collection.swift index 4a108f3bd..ff9645625 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Collection.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Collection.swift @@ -36,12 +36,12 @@ extension PersistentDictionary: Collection { /// Returns the index for the given key. public func index(forKey key: Key) -> Index? { - rootNode.index(key, _computeHash(key), 0, 0) + rootNode.index(forKey: key, _HashPath(key), 0) } /// Accesses the key-value pair at the specified position. public subscript(position: Index) -> Element { - rootNode.get(position: position, 0, position._value) + rootNode.item(position: position._value) } } diff --git a/Sources/PersistentCollections/PersistentDictionary._Node.swift b/Sources/PersistentCollections/PersistentDictionary._Node.swift index 4f8cc6335..77a8fcff9 100644 --- a/Sources/PersistentCollections/PersistentDictionary._Node.swift +++ b/Sources/PersistentCollections/PersistentDictionary._Node.swift @@ -9,35 +9,31 @@ // //===----------------------------------------------------------------------===// -internal struct _NodeHeader: Equatable { - internal typealias Bitmap = UInt32 +internal struct _NodeHeader { + internal var dataMap: _Bitmap + internal var trieMap: _Bitmap - // TODO: restore type to `UInt8` after reworking hash-collisions to grow in - // depth instead of width - internal typealias Capacity = UInt32 - - internal var dataMap: Bitmap - internal var trieMap: Bitmap - - init(dataMap: Bitmap, trieMap: Bitmap) { + init(dataMap: _Bitmap, trieMap: _Bitmap) { self.dataMap = dataMap self.trieMap = trieMap } } extension _NodeHeader { - internal var hashCollision: Bool { - (dataMap & trieMap) != 0 + internal var isCollisionNode: Bool { + !dataMap.intersection(trieMap).isEmpty } internal var dataCount: Int { - hashCollision ? Int(dataMap) : dataMap.nonzeroBitCount + isCollisionNode ? Int(dataMap._value) : dataMap.count } internal var trieCount: Int { - hashCollision ? 0 : trieMap.nonzeroBitCount + isCollisionNode ? 0 : trieMap.count } +} +extension _NodeHeader: Equatable { internal static func == (lhs: _NodeHeader, rhs: _NodeHeader) -> Bool { lhs.dataMap == rhs.dataMap && lhs.trieMap == rhs.trieMap } @@ -45,9 +41,12 @@ extension _NodeHeader { extension PersistentDictionary { internal final class _Node { - typealias Index = PersistentDictionary.Index - typealias Capacity = _NodeHeader.Capacity typealias Element = (key: Key, value: Value) + typealias Index = PersistentDictionary.Index + + // TODO: restore type to `UInt8` after reworking hash-collisions to grow in + // depth instead of width + internal typealias Capacity = UInt32 var header: _NodeHeader var count: Int @@ -70,7 +69,7 @@ extension PersistentDictionary { dataCapacity: dataCapacity, trieCapacity: trieCapacity) - self.header = _NodeHeader(dataMap: 0, trieMap: 0) + self.header = _NodeHeader(dataMap: .empty, trieMap: .empty) self.count = 0 self.dataBaseAddress = dataBaseAddress @@ -151,66 +150,73 @@ extension PersistentDictionary._Node { dataCapacity: _Node.initialDataCapacity, trieCapacity: _Node.initialTrieCapacity) - self.header = _NodeHeader(dataMap: 0, trieMap: 0) + self.header = _NodeHeader(dataMap: .empty, trieMap: .empty) assert(self.invariant) } - convenience init( - dataMap: _NodeHeader.Bitmap, firstKey: Key, firstValue: Value) { + convenience init(dataMap: _Bitmap, _ item: Element) { + assert(dataMap.count == 1) self.init() - - self.header = _NodeHeader(dataMap: dataMap, trieMap: 0) + self.header = _NodeHeader(dataMap: dataMap, trieMap: .empty) self.count = 1 - - self.dataBaseAddress.initialize(to: (firstKey, firstValue)) - + self.dataBaseAddress.initialize(to: item) assert(self.invariant) } + convenience init(_ item: Element, at bucket: _Bucket) { + self.init(dataMap: _Bitmap(bucket), item) + } + convenience init( - dataMap: _NodeHeader.Bitmap, - firstKey: Key, - firstValue: Value, - secondKey: Key, - secondValue: Value + _ item0: Element, at bucket0: _Bucket, + _ item1: Element, at bucket1: _Bucket ) { + assert(bucket0 != bucket1) self.init() - self.header = _NodeHeader(dataMap: dataMap, trieMap: 0) + self.header = _NodeHeader( + dataMap: _Bitmap(bucket0, bucket1), + trieMap: .empty) self.count = 2 - self.dataBaseAddress.initialize(to: (firstKey, firstValue)) - self.dataBaseAddress.successor().initialize(to: (secondKey, secondValue)) - + if bucket0 < bucket1 { + self.dataBaseAddress.initialize(to: item0) + self.dataBaseAddress.successor().initialize(to: item1) + } else { + self.dataBaseAddress.initialize(to: item1) + self.dataBaseAddress.successor().initialize(to: item0) + } assert(self.invariant) } - convenience init(trieMap: _NodeHeader.Bitmap, firstNode: _Node) { + convenience init(_ child: _Node, at bucket: _Bucket) { self.init() - self.header = _NodeHeader(dataMap: 0, trieMap: trieMap) - self.count = firstNode.count + self.header = _NodeHeader( + dataMap: .empty, + trieMap: _Bitmap(bucket)) + self.count = child.count - self.trieBaseAddress.initialize(to: firstNode) + self.trieBaseAddress.initialize(to: child) assert(self.invariant) } convenience init( - dataMap: _NodeHeader.Bitmap, - trieMap: _NodeHeader.Bitmap, - firstKey: Key, - firstValue: Value, - firstNode: _Node + _ item: Element, at bucket0: _Bucket, + _ child: _Node, at bucket1: _Bucket ) { + assert(bucket0 != bucket1) self.init() - self.header = _NodeHeader(dataMap: dataMap, trieMap: trieMap) - self.count = 1 + firstNode.count + self.header = _NodeHeader( + dataMap: _Bitmap(bucket0), + trieMap: _Bitmap(bucket1)) + self.count = 1 + child.count - self.dataBaseAddress.initialize(to: (firstKey, firstValue)) - self.trieBaseAddress.initialize(to: firstNode) + self.dataBaseAddress.initialize(to: item) + self.trieBaseAddress.initialize(to: child) assert(self.invariant) } @@ -219,8 +225,8 @@ extension PersistentDictionary._Node { self.init(dataCapacity: Capacity(collisions.count), trieCapacity: 0) self.header = _NodeHeader( - dataMap: _NodeHeader.Bitmap(collisions.count), - trieMap: _NodeHeader.Bitmap(collisions.count)) + dataMap: _Bitmap(bitPattern: collisions.count), + trieMap: _Bitmap(bitPattern: collisions.count)) self.count = collisions.count self.dataBaseAddress.initialize(from: collisions, count: collisions.count) @@ -230,12 +236,12 @@ extension PersistentDictionary._Node { } extension PersistentDictionary._Node { - var collisionFree: Bool { - !hashCollision + var isRegularNode: Bool { + !isCollisionNode } - var hashCollision: Bool { - header.hashCollision + var isCollisionNode: Bool { + header.isCollisionNode } private var rootBaseAddress: UnsafeMutableRawPointer { @@ -243,12 +249,12 @@ extension PersistentDictionary._Node { } @inline(__always) - var dataMap: _NodeHeader.Bitmap { + var dataMap: _Bitmap { header.dataMap } @inline(__always) - var trieMap: _NodeHeader.Bitmap { + var trieMap: _Bitmap { header.trieMap } @@ -267,10 +273,10 @@ extension PersistentDictionary._Node { return false } - if hashCollision { - let hash = _computeHash(_dataSlice.first!.key) + if isCollisionNode { + let hash = _HashValue(_dataSlice.first!.key) - guard _dataSlice.allSatisfy({ _computeHash($0.key) == hash }) else { + guard _dataSlice.allSatisfy({ _HashValue($0.key) == hash }) else { return false } } @@ -279,7 +285,8 @@ extension PersistentDictionary._Node { } var headerInvariant: Bool { - (header.dataMap & header.trieMap) == 0 || (header.dataMap == header.trieMap) + header.dataMap.intersection(header.trieMap).isEmpty + || (header.dataMap == header.trieMap) } var _dataSlice: UnsafeBufferPointer { @@ -292,14 +299,6 @@ extension PersistentDictionary._Node { var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } - func dataIndex(_ bitpos: _NodeHeader.Bitmap) -> Int { - (dataMap & (bitpos &- 1)).nonzeroBitCount - } - - func trieIndex(_ bitpos: _NodeHeader.Bitmap) -> Int { - (trieMap & (bitpos &- 1)).nonzeroBitCount - } - func isTrieNodeKnownUniquelyReferenced( _ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool @@ -311,7 +310,7 @@ extension PersistentDictionary._Node { } extension PersistentDictionary._Node: _NodeProtocol { - var hasNodes: Bool { header.trieMap != 0 } + var hasNodes: Bool { !header.trieMap.isEmpty } var nodeArity: Int { header.trieCount } @@ -319,179 +318,143 @@ extension PersistentDictionary._Node: _NodeProtocol { trieBaseAddress[index] } - var hasPayload: Bool { header.dataMap != 0 } + var hasPayload: Bool { !header.dataMap.isEmpty } var payloadArity: Int { header.dataCount } func getPayload(_ index: Int) -> (key: Key, value: Value) { dataBaseAddress[index] } - } extension PersistentDictionary._Node: _DictionaryNodeProtocol { - func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { - let mask = _maskFrom(keyHash, shift) - let bitpos = _bitposFrom(mask) - - guard collisionFree else { + func get(_ key: Key, _ path: _HashPath) -> Value? { + guard isRegularNode else { let content: [Element] = Array(self) - let hash = _computeHash(content.first!.key) - - guard keyHash == hash else { - return nil - } - + let hash = _HashValue(content.first!.key) + guard path._hash == hash else { return nil } return content.first(where: { key == $0.key }).map { $0.value } } - guard (dataMap & bitpos) == 0 else { - let index = _indexFrom(dataMap, mask, bitpos) - let payload = self.getPayload(index) + let bucket = path.currentBucket + + if dataMap.contains(bucket) { + let offset = dataMap.offset(of: bucket) + let payload = self.getPayload(offset) return key == payload.key ? payload.value : nil } - guard (trieMap & bitpos) == 0 else { - let index = _indexFrom(trieMap, mask, bitpos) - return self.getNode(index).get(key, keyHash, shift + _bitPartitionSize) + if trieMap.contains(bucket) { + let offset = trieMap.offset(of: bucket) + return self.getNode(offset).get(key, path.descend()) } return nil } - func containsKey(_ key: Key, _ keyHash: Int, _ shift: Int) -> Bool { - let mask = _maskFrom(keyHash, shift) - let bitpos = _bitposFrom(mask) - - guard collisionFree else { + func containsKey(_ key: Key, _ path: _HashPath) -> Bool { + guard isRegularNode else { let content: [Element] = Array(self) - let hash = _computeHash(content.first!.key) - - guard keyHash == hash else { - return false - } - + let hash = _HashValue(content.first!.key) + guard path._hash == hash else { return false } return content.contains(where: { key == $0.key }) } - guard (dataMap & bitpos) == 0 else { - let index = _indexFrom(dataMap, mask, bitpos) - let payload = self.getPayload(index) + let bucket = path.currentBucket + + if dataMap.contains(bucket) { + let offset = dataMap.offset(of: bucket) + let payload = self.getPayload(offset) return key == payload.key } - guard (trieMap & bitpos) == 0 else { - let index = _indexFrom(trieMap, mask, bitpos) + if trieMap.contains(bucket) { + let offset = trieMap.offset(of: bucket) return self - .getNode(index) - .containsKey(key, keyHash, shift + _bitPartitionSize) + .getNode(offset) + .containsKey(key, path.descend()) } return false } func index( - _ key: Key, - _ keyHash: Int, - _ shift: Int, + forKey key: Key, + _ path: _HashPath, _ skippedBefore: Int ) -> Index? { - guard collisionFree else { + guard isRegularNode else { let content: [Element] = Array(self) - let hash = _computeHash(content.first!.key) - - assert(keyHash == hash) + let hash = _HashValue(content.first!.key) + assert(path._hash == hash) return content .firstIndex(where: { _key, _ in _key == key }) .map { Index(_value: $0) } } - let mask = _maskFrom(keyHash, shift) - let bitpos = _bitposFrom(mask) + let bucket = path.currentBucket - let skipped = self._counts.prefix(upTo: mask).reduce(0, +) - - guard (dataMap & bitpos) == 0 else { - let index = _indexFrom(dataMap, mask, bitpos) - let payload = self.getPayload(index) + if dataMap.contains(bucket) { + let offset = dataMap.offset(of: bucket) + let payload = self.getPayload(offset) guard key == payload.key else { return nil } - - return Index(_value: skippedBefore + skipped) + return Index(_value: skippedBefore + _count(upTo: bucket)) } - guard (trieMap & bitpos) == 0 else { - let index = _indexFrom(trieMap, mask, bitpos) + if trieMap.contains(bucket) { + let offset = trieMap.offset(of: bucket) + let skipped = skippedBefore + _count(upTo: bucket) return self - .getNode(index) - .index(key, keyHash, shift + _bitPartitionSize, skippedBefore + skipped) + .getNode(offset) + .index(forKey: key, path.descend(), skipped) } return nil } final func updateOrUpdating( - _ isStorageKnownUniquelyReferenced: Bool, - _ key: Key, - _ value: Value, - _ keyHash: Int, - _ shift: Int, + _ isUnique: Bool, + _ item: Element, + _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> _Node { - guard collisionFree else { - return _updateOrUpdatingCollision( - isStorageKnownUniquelyReferenced, key, value, keyHash, shift, &effect) + guard isRegularNode else { + return _updateOrUpdatingCollision(isUnique, item, path, &effect) } - let mask = _maskFrom(keyHash, shift) - let bitpos = _bitposFrom(mask) - - guard (dataMap & bitpos) == 0 else { - let index = _indexFrom(dataMap, mask, bitpos) - let (key0, value0) = self.getPayload(index) - - if key0 == key { - effect.setReplacedValue(previousValue: value0) - return _copyAndSetValue(isStorageKnownUniquelyReferenced, bitpos, value) - } else { - let keyHash0 = _computeHash(key0) - - if keyHash0 == keyHash { - let subNodeNew = _Node( - /* hash, */ collisions: [(key0, value0), (key, value)]) - - effect.setModified() - if self.count == 1 { - return subNodeNew - } else { - return _copyAndMigrateFromInlineToNode( - isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } - } else { - let subNodeNew = _mergeTwoKeyValPairs( - key0, value0, keyHash0, - key, value, keyHash, - shift + _bitPartitionSize) - - effect.setModified() - return _copyAndMigrateFromInlineToNode( - isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } + let bucket = path.currentBucket + if dataMap.contains(bucket) { + let offset = dataMap.offset(of: bucket) + let item0 = self.getPayload(offset) + + if item0.key == item.key { + effect.setReplacedValue(previousValue: item0.value) + return _copyAndSetValue(isUnique, bucket, item.value) + } + let hash0 = _HashValue(item0.key) + if hash0 == path._hash { + let subNodeNew = _Node(collisions: [item0, item]) + effect.setModified() + if self.count == 1 { return subNodeNew } + return _copyAndMigrateFromInlineToNode(isUnique, bucket, subNodeNew) } + let subNodeNew = _mergeTwoKeyValPairs( + item, path.descend(), + item0, hash0) + effect.setModified() + return _copyAndMigrateFromInlineToNode(isUnique, bucket, subNodeNew) } - guard (trieMap & bitpos) == 0 else { - let index = _indexFrom(trieMap, mask, bitpos) - let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced( - index, isStorageKnownUniquelyReferenced) + if trieMap.contains(bucket) { + let offset = trieMap.offset(of: bucket) + let isUniqueChild = self.isTrieNodeKnownUniquelyReferenced( + offset, isUnique) - let subNode = self.getNode(index) + let subNode = self.getNode(offset) let subNodeNew = subNode.updateOrUpdating( - subNodeModifyInPlace, - key, value, keyHash, - shift + _bitPartitionSize, - &effect) + isUniqueChild, item, path.descend(), &effect) guard effect.modified, subNode !== subNodeNew else { if effect.previousValue == nil { count += 1 } assert(self.invariant) @@ -499,118 +462,103 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { } return _copyAndSetTrieNode( - isStorageKnownUniquelyReferenced, - bitpos, - index, + isUnique, + bucket, + offset, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) } effect.setModified() - return _copyAndInsertValue( - isStorageKnownUniquelyReferenced, bitpos, key, value) + return _copyAndInsertValue(isUnique, bucket, item) } @inline(never) final func _updateOrUpdatingCollision( _ isStorageKnownUniquelyReferenced: Bool, - _ key: Key, - _ value: Value, - _ keyHash: Int, - _ shift: Int, + _ item: Element, + _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> _Node { - assert(hashCollision) + assert(isCollisionNode) let content: [Element] = Array(self) - let hash = _computeHash(content.first!.key) + let hash = _HashValue(content.first!.key) - guard keyHash == hash else { + guard path._hash == hash else { effect.setModified() - return _mergeKeyValPairAndCollisionNode( - key, value, keyHash, self, hash, shift) + return _mergeKeyValPairAndCollisionNode(item, path, self, hash) } - if let index = content.firstIndex(where: { key == $0.key }) { - let updatedContent: [Element] = ( - content[0.. ) -> _Node { - guard collisionFree else { - return _removeOrRemovingCollision( - isStorageKnownUniquelyReferenced, - key, keyHash, - shift, - &effect) + guard isRegularNode else { + return _removeOrRemovingCollision(isUnique, key, path, &effect) } - let mask = _maskFrom(keyHash, shift) - let bitpos = _bitposFrom(mask) + let bucket = path.currentBucket - guard (dataMap & bitpos) == 0 else { - let index = _indexFrom(dataMap, mask, bitpos) - let (key0, value0) = self.getPayload(index) - guard key0 == key else { + if dataMap.contains(bucket) { + let offset = dataMap.offset(of: bucket) + let item0 = self.getPayload(offset) + guard item0.key == key else { assert(self.invariant) return self } - effect.setModified(previousValue: value0) + effect.setModified(previousValue: item0.value) if self.payloadArity == 2, self.nodeArity == 0 { - if shift == 0 { - // keep remaining pair on root level - let newDataMap = (dataMap ^ bitpos) - let (remainingKey, remainingValue) = getPayload(1 - index) - return _Node( - dataMap: newDataMap, - firstKey: remainingKey, - firstValue: remainingValue) - } else { - // create potential new root: will a) become new root, or b) inlined - // on another level - let newDataMap = _bitposFrom(_maskFrom(keyHash, 0)) - let (remainingKey, remainingValue) = getPayload(1 - index) - return _Node( - dataMap: newDataMap, - firstKey: remainingKey, - firstValue: remainingValue) + if path.isAtRoot { + // keep remaining item on root level + var newDataMap = dataMap + newDataMap.remove(bucket) + let remaining = getPayload(1 - offset) + return _Node(dataMap: newDataMap, remaining) } - } else if + // create potential new root: will a) become new root, or b) inlined + // on another level + let remaining = getPayload(1 - offset) + return _Node(remaining, at: path.top().currentBucket) + } + + if self.payloadArity == 1, self.nodeArity == 1, - self.getNode(0).hashCollision + self.getNode(0).isCollisionNode { // escalate hash-collision node return getNode(0) - } else { - return _copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } + return _copyAndRemoveValue(isUnique, bucket) } - guard (trieMap & bitpos) == 0 else { - let index = _indexFrom(trieMap, mask, bitpos) - let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced( - index, isStorageKnownUniquelyReferenced) + if trieMap.contains(bucket) { + let offset = trieMap.offset(of: bucket) + let isChildUnique = self.isTrieNodeKnownUniquelyReferenced(offset, isUnique) - let subNode = self.getNode(index) + let subNode = self.getNode(offset) let subNodeNew = subNode.removeOrRemoving( - subNodeModifyInPlace, key, keyHash, shift + _bitPartitionSize, &effect) + isChildUnique, key, path.descend(), &effect) guard effect.modified, subNode !== subNodeNew else { if effect.modified { count -= 1 } assert(self.invariant) @@ -618,31 +566,27 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { } assert(subNodeNew.count > 0, "Sub-node must have at least one element.") - switch subNodeNew.count { - case 1: + if subNodeNew.count == 1 { if self.isCandiateForCompaction { // escalate singleton return subNodeNew - } else { - // inline singleton - return _copyAndMigrateFromNodeToInline( - isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } + // inline singleton + return _copyAndMigrateFromNodeToInline( + isUnique, bucket, subNodeNew.getPayload(0)) + } - case _: - if subNodeNew.hashCollision, self.isCandiateForCompaction { - // escalate singleton - return subNodeNew - } else { - // modify current node (set replacement node) - return _copyAndSetTrieNode( - isStorageKnownUniquelyReferenced, - bitpos, - index, - subNodeNew, - updateCount: { $0 -= 1 }) - } + if subNodeNew.isCollisionNode, self.isCandiateForCompaction { + // escalate singleton + return subNodeNew } + // modify current node (set replacement node) + return _copyAndSetTrieNode( + isUnique, + bucket, + offset, + subNodeNew, + updateCount: { $0 -= 1 }) } return self @@ -650,183 +594,166 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { @inline(never) final func _removeOrRemovingCollision( - _ isStorageKnownUniquelyReferenced: Bool, + _ isUnique: Bool, _ key: Key, - _ keyHash: Int, - _ shift: Int, + _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> _Node { - assert(hashCollision) + assert(isCollisionNode) let content: [Element] = Array(self) - let _ = _computeHash(content.first!.key) - - if let index = content.firstIndex(where: { key == $0.key }) { - effect.setModified(previousValue: content[index].value) - var updatedContent = content; updatedContent.remove(at: index) - assert(updatedContent.count == content.count - 1) - if updatedContent.count == 1 { - // create potential new root: will a) become new root, or b) inlined - // on another level - let newDataMap = _bitposFrom(_maskFrom(keyHash, 0)) - let (remainingKey, remainingValue) = updatedContent.first! - return _Node( - dataMap: newDataMap, - firstKey: remainingKey, - firstValue: remainingValue) - } else { - return _Node(/* hash, */ collisions: updatedContent) - } - } else { + guard let index = content.firstIndex(where: { key == $0.key }) else { return self } + effect.setModified(previousValue: content[index].value) + var updatedContent = content + updatedContent.remove(at: index) + + if updatedContent.count == 1 { + // create potential new root: will a) become new root, or b) inlined + // on another level + let remaining = updatedContent.first! + return _Node(remaining, at: path.top().currentBucket) + } + return _Node(/* hash, */ collisions: updatedContent) } } extension PersistentDictionary._Node { - func get(position: Index, _ shift: Int, _ stillToSkip: Int) -> Element { - var cumulativeCounts = self._counts + func item(position: Int) -> Element { + assert(position >= 0 && position < count) + let counts = self._counts - for i in 1 ..< cumulativeCounts.count { - cumulativeCounts[i] += cumulativeCounts[i - 1] + var b = 0 + var skipped = 0 + while b < counts.count { + let c = skipped + counts[b] + if c > position { break } + skipped = c + b += 1 } + let bucket = _Bucket(UInt(bitPattern: b)) - var mask = 0 - - for i in 0 ..< cumulativeCounts.count { - if cumulativeCounts[i] <= stillToSkip { - mask = i - } else { - mask = i - break - } + if dataMap.contains(bucket) { + assert(skipped == position) + let offset = dataMap.offset(of: bucket) + return self.getPayload(offset) } - let skipped = (mask == 0) ? 0 : cumulativeCounts[mask - 1] - - let bitpos = _bitposFrom(mask) - - guard (dataMap & bitpos) == 0 else { - let index = _indexFrom(dataMap, mask, bitpos) - return self.getPayload(index) - } - - guard (trieMap & bitpos) == 0 else { - let index = _indexFrom(trieMap, mask, bitpos) - return self - .getNode(index) - .get(position: position, shift + _bitPartitionSize, stillToSkip - skipped) - } - - fatalError("Should not reach here.") + precondition(trieMap.contains(bucket)) + assert(skipped <= position && skipped + counts[b] > position) + return self + .getNode(trieMap.offset(of: bucket)) + .item(position: position - skipped) } } extension PersistentDictionary._Node { func _mergeTwoKeyValPairs( - _ key0: Key, _ value0: Value, _ keyHash0: Int, - _ key1: Key, _ value1: Value, _ keyHash1: Int, - _ shift: Int + _ item0: Element, _ path0: _HashPath, + _ item1: Element, _ hash1: _HashValue ) -> _Node { - assert(keyHash0 != keyHash1) + let path1 = _HashPath(_hash: hash1, shift: path0._shift) + return _mergeTwoKeyValPairs(item0, path0, item1, path1) + } - let mask0 = _maskFrom(keyHash0, shift) - let mask1 = _maskFrom(keyHash1, shift) + func _mergeTwoKeyValPairs( + _ item0: Element, _ path0: _HashPath, + _ item1: Element, _ path1: _HashPath + ) -> _Node { + assert(path0._hash != path1._hash) + assert(path0._shift == path1._shift) - if mask0 != mask1 { + let bucket0 = path0.currentBucket + let bucket1 = path1.currentBucket + + if bucket0 != bucket1 { // unique prefixes, payload fits on same level - if mask0 < mask1 { - return _Node( - dataMap: _bitposFrom(mask0) | _bitposFrom(mask1), - firstKey: key0, - firstValue: value0, - secondKey: key1, - secondValue: value1) - } else { - return Self( - dataMap: _bitposFrom(mask1) | _bitposFrom(mask0), - firstKey: key1, - firstValue: value1, - secondKey: key0, - secondValue: value0) - } - } else { - // recurse: identical prefixes, payload must be disambiguated deeper - // in the trie - let node = _mergeTwoKeyValPairs( - key0, value0, keyHash0, - key1, value1, keyHash1, - shift + _bitPartitionSize) - - return _Node(trieMap: _bitposFrom(mask0), firstNode: node) + return _Node( + item0, at: bucket0, + item1, at: bucket1) } + // recurse: identical prefixes, payload must be disambiguated deeper + // in the trie + let node = _mergeTwoKeyValPairs( + item0, path0.descend(), + item1, path1.descend()) + + return _Node(node, at: bucket0) } final func _mergeKeyValPairAndCollisionNode( - _ key0: Key, _ value0: Value, _ keyHash0: Int, - _ node1: _Node, - _ nodeHash1: Int, - _ shift: Int + _ item0: Element, _ path0: _HashPath, + _ node1: _Node, _ hash1: _HashValue ) -> _Node { - assert(keyHash0 != nodeHash1) + let path1 = _HashPath(_hash: hash1, shift: path0._shift) + return _mergeKeyValPairAndCollisionNode(item0, path0, node1, path1) + } - let mask0 = _maskFrom(keyHash0, shift) - let mask1 = _maskFrom(nodeHash1, shift) + final func _mergeKeyValPairAndCollisionNode( + _ item0: Element, _ path0: _HashPath, + _ node1: _Node, _ path1: _HashPath + ) -> _Node { + assert(path0._hash != path1._hash) + assert(path0._shift == path1._shift) + + let bucket0 = path0.currentBucket + let bucket1 = path1.currentBucket - if mask0 != mask1 { + if bucket0 != bucket1 { // unique prefixes, payload and collision node fit on same level - return _Node( - dataMap: _bitposFrom(mask0), - trieMap: _bitposFrom(mask1), - firstKey: key0, - firstValue: value0, - firstNode: node1) - } else { - // recurse: identical prefixes, payload must be disambiguated deeper in the trie - let node = _mergeKeyValPairAndCollisionNode( - key0, - value0, - keyHash0, - node1, - nodeHash1, - shift + _bitPartitionSize) - - return _Node(trieMap: _bitposFrom(mask0), firstNode: node) + return _Node(item0, at: bucket0, node1, at: bucket1) } + + // recurse: identical prefixes, payload must be disambiguated deeper in the trie + let node = _mergeKeyValPairAndCollisionNode( + item0, path0.descend(), + node1, path1.descend()) + + return _Node(node, at: bucket0) + } + + final func _count(upTo bucket: _Bucket) -> Int { + let dataCount = dataMap.intersection(_Bitmap(upTo: bucket)).count + let trieCount = trieMap.intersection(_Bitmap(upTo: bucket)).count + + let buffer = UnsafeMutableBufferPointer( + start: trieBaseAddress, count: header.trieCount) + let children = buffer.prefix(upTo: trieCount).map { $0.count }.reduce(0, +) + + return dataCount + children } final var _counts: [Int] { - var counts = Array(repeating: 0, count: _NodeHeader.Bitmap.bitWidth) + var counts = Array(repeating: 0, count: _Bitmap.capacity) - zip(header.dataMap._nonzeroBits(), _dataSlice).forEach { (index, _) in - counts[index] = 1 + for bucket in dataMap { + counts[Int(bitPattern: bucket.value)] = 1 } - zip(header.trieMap._nonzeroBits(), _trieSlice).forEach { (index, trieNode) in - counts[index] = trieNode.count + for (bucket, trieNode) in zip(trieMap, _trieSlice) { + counts[Int(bitPattern: bucket.value)] = trieNode.count } return counts } func _copyAndSetValue( - _ isStorageKnownUniquelyReferenced: Bool, - _ bitpos: _NodeHeader.Bitmap, - _ newValue: Value + _ isUnique: Bool, _ bucket: _Bucket, _ newValue: Value ) -> _Node { let src: _Node = self let dst: _Node - if isStorageKnownUniquelyReferenced { + if isUnique { dst = src } else { dst = src.copy() } - let idx = dataIndex(bitpos) + let offset = dataMap.offset(of: bucket) - dst.dataBaseAddress[idx].value = newValue + dst.dataBaseAddress[offset].value = newValue assert(src.invariant) assert(dst.invariant) @@ -834,22 +761,22 @@ extension PersistentDictionary._Node { } private func _copyAndSetTrieNode( - _ isStorageKnownUniquelyReferenced: Bool, - _ bitpos: _NodeHeader.Bitmap, - _ idx: Int, + _ isUnique: Bool, + _ bucket: _Bucket, + _ offset: Int, _ newNode: _Node, updateCount: (inout Int) -> Void ) -> _Node { let src: _Node = self let dst: _Node - if isStorageKnownUniquelyReferenced { + if isUnique { dst = src } else { dst = src.copy() } - dst.trieBaseAddress[idx] = newNode + dst.trieBaseAddress[offset] = newNode // update metadata: `dataMap, nodeMap, collMap` updateCount(&dst.count) @@ -860,31 +787,29 @@ extension PersistentDictionary._Node { } func _copyAndInsertValue( - _ isStorageKnownUniquelyReferenced: Bool, - _ bitpos: _NodeHeader.Bitmap, - _ key: Key, - _ value: Value + _ isUnique: Bool, + _ bucket: _Bucket, + _ item: Element ) -> _Node { let src: _Node = self let dst: _Node let hasRoomForData = header.dataCount < dataCapacity - if isStorageKnownUniquelyReferenced && hasRoomForData { + if isUnique && hasRoomForData { dst = src } else { dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) } - let dataIdx = _indexFrom(dataMap, bitpos) + let offset = dst.dataMap.offset(of: bucket) _rangeInsert( - (key, value), - at: dataIdx, + item, + at: offset, into: dst.dataBaseAddress, count: dst.header.dataCount) - // update metadata: `dataMap | bitpos, nodeMap, collMap` - dst.header.dataMap |= bitpos + dst.header.dataMap.insert(bucket) dst.count += 1 assert(src.invariant) @@ -892,25 +817,23 @@ extension PersistentDictionary._Node { return dst } - func _copyAndRemoveValue( - _ isStorageKnownUniquelyReferenced: Bool, - _ bitpos: _NodeHeader.Bitmap - ) -> _Node { + func _copyAndRemoveValue(_ isUnique: Bool, _ bucket: _Bucket) -> _Node { + assert(dataMap.contains(bucket)) let src: _Node = self let dst: _Node - if isStorageKnownUniquelyReferenced { + if isUnique { dst = src } else { dst = src.copy() } - let dataIdx = _indexFrom(dataMap, bitpos) + let dataOffset = dst.dataMap.offset(of: bucket) _rangeRemove( - at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) + at: dataOffset, from: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap ^ bitpos, nodeMap, collMap` - dst.header.dataMap ^= bitpos + dst.header.dataMap.remove(bucket) dst.count -= 1 assert(src.invariant) @@ -919,16 +842,15 @@ extension PersistentDictionary._Node { } func _copyAndMigrateFromInlineToNode( - _ isStorageKnownUniquelyReferenced: Bool, - _ bitpos: _NodeHeader.Bitmap, - _ node: _Node + _ isUnique: Bool, _ bucket: _Bucket, _ node: _Node ) -> _Node { + assert(dataMap.contains(bucket)) let src: _Node = self let dst: _Node let hasRoomForTrie = header.trieCount < trieCapacity - if isStorageKnownUniquelyReferenced && hasRoomForTrie { + if isUnique && hasRoomForTrie { dst = src } else { // TODO reconsider the details of the heuristic @@ -945,17 +867,18 @@ extension PersistentDictionary._Node { withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) } - let dataIdx = _indexFrom(dataMap, bitpos) + let dataOffset = dst.dataMap.offset(of: bucket) _rangeRemove( - at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) + at: dataOffset, from: dst.dataBaseAddress, count: dst.header.dataCount) - let trieIdx = _indexFrom(trieMap, bitpos) + let trieOffset = dst.trieMap.offset(of: bucket) _rangeInsert( - node, at: trieIdx, into: dst.trieBaseAddress, count: dst.header.trieCount) + node, at: trieOffset, + into: dst.trieBaseAddress, count: dst.header.trieCount) // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` - dst.header.dataMap ^= bitpos - dst.header.trieMap |= bitpos + dst.header.dataMap.remove(bucket) + dst.header.trieMap.insert(bucket) dst.count += 1 // assuming that `node.count == 2` assert(src.invariant) @@ -964,32 +887,32 @@ extension PersistentDictionary._Node { } func _copyAndMigrateFromNodeToInline( - _ isStorageKnownUniquelyReferenced: Bool, - _ bitpos: _NodeHeader.Bitmap, - _ tuple: (key: Key, value: Value) + _ isUnique: Bool, _ bucket: _Bucket, _ item: Element ) -> _Node { + assert(trieMap.contains(bucket)) let src: _Node = self let dst: _Node let hasRoomForData = header.dataCount < dataCapacity - if isStorageKnownUniquelyReferenced && hasRoomForData { + if isUnique && hasRoomForData { dst = src } else { dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) } - let nodeIdx = _indexFrom(trieMap, bitpos) + let nodeOffset = dst.trieMap.offset(of: bucket) _rangeRemove( - at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) + at: nodeOffset, from: dst.trieBaseAddress, count: dst.header.trieCount) - let dataIdx = _indexFrom(dataMap, bitpos) + let dataOffset = dst.dataMap.offset(of: bucket) _rangeInsert( - tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) + item, at: dataOffset, + into: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` - dst.header.dataMap |= bitpos - dst.header.trieMap ^= bitpos + dst.header.dataMap.insert(bucket) + dst.header.trieMap.remove(bucket) dst.count -= 1 // assuming that updated `node.count == 1` assert(src.invariant) @@ -1001,7 +924,7 @@ extension PersistentDictionary._Node { // TODO: `Equatable` needs more test coverage, apart from hash-collision smoke test extension PersistentDictionary._Node: Equatable where Value: Equatable { static func == (lhs: _Node, rhs: _Node) -> Bool { - if lhs.hashCollision && rhs.hashCollision { + if lhs.isCollisionNode && rhs.isCollisionNode { let l = Dictionary(uniqueKeysWithValues: Array(lhs)) let r = Dictionary(uniqueKeysWithValues: Array(rhs)) return l == r diff --git a/Sources/PersistentCollections/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift index 9e56d2794..aa4950f3a 100644 --- a/Sources/PersistentCollections/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary.swift @@ -69,7 +69,7 @@ public struct PersistentDictionary where Key: Hashable { public subscript(key: Key) -> Value? { get { - return get(key) + return _get(key) } mutating set(optionalValue) { if let value = optionalValue { @@ -85,7 +85,7 @@ public struct PersistentDictionary where Key: Hashable { default defaultValue: @autoclosure () -> Value ) -> Value { get { - return get(key) ?? defaultValue() + return _get(key) ?? defaultValue() } mutating set(value) { updateValue(value, forKey: key) @@ -93,11 +93,11 @@ public struct PersistentDictionary where Key: Hashable { } public func contains(_ key: Key) -> Bool { - rootNode.containsKey(key, _computeHash(key), 0) + rootNode.containsKey(key, _HashPath(key)) } - func get(_ key: Key) -> Value? { - rootNode.get(key, _computeHash(key), 0) + func _get(_ key: Key) -> Value? { + rootNode.get(key, _HashPath(key)) } @discardableResult @@ -105,9 +105,8 @@ public struct PersistentDictionary where Key: Hashable { let isUnique = isKnownUniquelyReferenced(&self.rootNode) var effect = _DictionaryEffect() - let keyHash = _computeHash(key) let newRootNode = rootNode.updateOrUpdating( - isUnique, key, value, keyHash, 0, &effect) + isUnique, (key, value), _HashPath(key), &effect) if effect.modified { self.rootNode = newRootNode @@ -120,13 +119,11 @@ public struct PersistentDictionary where Key: Hashable { // fluid/immutable API public func updatingValue(_ value: Value, forKey key: Key) -> Self { var effect = _DictionaryEffect() - let keyHash = _computeHash(key) let newRootNode = rootNode.updateOrUpdating( - false, key, value, keyHash, 0, &effect) + false, (key, value), _HashPath(key), &effect) - if effect.modified { - return Self(newRootNode) - } else { return self } + guard effect.modified else { return self } + return Self(newRootNode) } @discardableResult @@ -134,9 +131,8 @@ public struct PersistentDictionary where Key: Hashable { let isUnique = isKnownUniquelyReferenced(&self.rootNode) var effect = _DictionaryEffect() - let keyHash = _computeHash(key) let newRootNode = rootNode.removeOrRemoving( - isUnique, key, keyHash, 0, &effect) + isUnique, key, _HashPath(key), &effect) if effect.modified { self.rootNode = newRootNode @@ -149,8 +145,8 @@ public struct PersistentDictionary where Key: Hashable { // fluid/immutable API public func removingValue(forKey key: Key) -> Self { var effect = _DictionaryEffect() - let keyHash = _computeHash(key) - let newRootNode = rootNode.removeOrRemoving(false, key, keyHash, 0, &effect) + let newRootNode = rootNode.removeOrRemoving( + false, key, _HashPath(key), &effect) if effect.modified { return Self(newRootNode) diff --git a/Sources/PersistentCollections/_BaseReverseIterator.swift b/Sources/PersistentCollections/Unused/_BaseReverseIterator.swift similarity index 99% rename from Sources/PersistentCollections/_BaseReverseIterator.swift rename to Sources/PersistentCollections/Unused/_BaseReverseIterator.swift index 5b7c7ba48..b5f22c6d0 100644 --- a/Sources/PersistentCollections/_BaseReverseIterator.swift +++ b/Sources/PersistentCollections/Unused/_BaseReverseIterator.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +#if false /// Base class for fixed-stack iterators that traverse a hash-trie in reverse /// order. The base iterator performs a depth-first post-order traversal, /// traversing sub-nodes (right to left). @@ -73,4 +74,4 @@ internal struct _BaseReverseIterator { return (currentValueCursor >= 0) || searchNextValueNode() } } - +#endif diff --git a/Sources/PersistentCollections/Unused/_Node+Unused.swift b/Sources/PersistentCollections/Unused/_Node+Unused.swift deleted file mode 100644 index f3ad57be9..000000000 --- a/Sources/PersistentCollections/Unused/_Node+Unused.swift +++ /dev/null @@ -1,29 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 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 -// -//===----------------------------------------------------------------------===// - -#if false -extension PersistentDictionary._Node { - final func count(upTo mask: Int) -> Int { - let bitpos = _bitposFrom(mask) - - let dataIndex = _indexFrom(dataMap, mask, bitpos) - let trieIndex = _indexFrom(trieMap, mask, bitpos) - - let buffer = UnsafeMutableBufferPointer( - start: trieBaseAddress, count: header.trieCount) - let children = buffer.prefix(upTo: trieIndex).map { $0.count }.reduce(0, +) - let count = dataIndex + children - - assert(count == _counts.prefix(upTo: mask).reduce(0, +)) - return count - } -} -#endif diff --git a/Sources/PersistentCollections/Utilities.swift b/Sources/PersistentCollections/Utilities.swift new file mode 100644 index 000000000..0170a7a48 --- /dev/null +++ b/Sources/PersistentCollections/Utilities.swift @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +extension UInt32 { + @inlinable @inline(__always) + var _nonzeroBitCount: Self { + Self(truncatingIfNeeded: nonzeroBitCount) + } + + @inlinable @inline(__always) + @_effects(readnone) + func _rank(ofBit bit: UInt) -> Int { + assert(bit < Self.bitWidth) + let mask: Self = (1 &<< bit) &- 1 + return (self & mask).nonzeroBitCount + } + + // Returns the position of the `n`th set bit in `self`, i.e., the bit with + // rank `n`. + @_effects(readnone) + @usableFromInline + func _bit(ranked n: Int) -> UInt? { + assert(n >= 0 && n < Self.bitWidth) + var shift: Self = 0 + var n: Self = UInt32(truncatingIfNeeded: n) + let c16 = (self & 0xFFFF)._nonzeroBitCount + if n >= c16 { + shift = 16 + n -= c16 + } + let c8 = ((self &>> shift) & 0xFF)._nonzeroBitCount + if n >= c8 { + shift &+= 8 + n -= c8 + } + let c4 = ((self &>> shift) & 0xF)._nonzeroBitCount + if n >= c4 { + shift &+= 4 + n -= c4 + } + let c2 = ((self &>> shift) & 0x3)._nonzeroBitCount + if n >= c2 { + shift &+= 2 + n -= c2 + } + let c1 = (self &>> shift) & 0x1 + if n >= c1 { + shift &+= 1 + n -= c1 + } + guard n == 0, (self &>> shift) & 0x1 == 1 else { return nil } + return UInt(truncatingIfNeeded: shift) + } +} diff --git a/Sources/PersistentCollections/_Bitmap.swift b/Sources/PersistentCollections/_Bitmap.swift new file mode 100644 index 000000000..a2d5f6f58 --- /dev/null +++ b/Sources/PersistentCollections/_Bitmap.swift @@ -0,0 +1,147 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +/// A set of `_Bucket` values, represented by a 32-bit wide bitset. +internal struct _Bitmap { + internal typealias Value = UInt32 + + internal var _value: Value + + @inline(__always) + init(_value: Value) { + self._value = _value + } + + @inline(__always) + init(bitPattern: Int) { + self._value = Value(bitPattern) + } + + @inline(__always) + internal init() { + _value = 0 + } + + @inline(__always) + internal init(_ bucket: _Bucket) { + assert(bucket.value < Self.capacity) + _value = (1 &<< bucket.value) + } + + @inline(__always) + internal init(_ bucket1: _Bucket, _ bucket2: _Bucket) { + assert(bucket1.value < Self.capacity && bucket2.value < Self.capacity) + assert(bucket1 != bucket2) + _value = (1 &<< bucket1.value) | (1 &<< bucket2.value) + } + + internal init(upTo bucket: _Bucket) { + assert(bucket.value < Self.capacity) + _value = (1 &<< bucket.value) &- 1 + } +} + +extension _Bitmap: Equatable { + @inline(__always) + internal static func ==(left: Self, right: Self) -> Bool { + left._value == right._value + } +} + +extension _Bitmap { + @inline(__always) + internal static var empty: Self { .init() } + + @inline(__always) + internal static var capacity: Int { Value.bitWidth } + + @inline(__always) + internal var count: Int { _value.nonzeroBitCount } + + @inline(__always) + internal var capacity: Int { Value.bitWidth } + + @inline(__always) + internal var isEmpty: Bool { _value == 0 } +} + +extension _Bitmap { + @inline(__always) + internal func contains(_ bucket: _Bucket) -> Bool { + assert(bucket.value < capacity) + return _value & (1 &<< bucket.value) != 0 + } + + @inline(__always) + internal mutating func insert(_ bucket: _Bucket) { + assert(bucket.value < capacity) + _value |= (1 &<< bucket.value) + } + + @inline(__always) + internal mutating func remove(_ bucket: _Bucket) { + assert(bucket.value < capacity) + _value &= ~(1 &<< bucket.value) + } + + @inline(__always) + internal func offset(of bucket: _Bucket) -> Int { + _value._rank(ofBit: bucket.value) + } + + @inline(__always) + internal func bucket(at offset: Int) -> _Bucket { + _Bucket(_value._bit(ranked: offset)!) + } +} + +extension _Bitmap { + @inline(__always) + internal func isDisjoint(with other: Self) -> Bool { + _value & other._value != 0 + } + + @inline(__always) + internal func union(_ other: Self) -> Self { + Self(_value: _value | other._value) + } + + @inline(__always) + internal func intersection(_ other: Self) -> Self { + Self(_value: _value & other._value) + } + + @inline(__always) + internal func symmetricDifference(_ other: Self) -> Self { + Self(_value: _value & other._value) + } + + @inline(__always) + internal func subtracting(_ other: Self) -> Self { + Self(_value: _value & ~other._value) + } +} + +extension _Bitmap: Sequence, IteratorProtocol { + var underestimatedCount: Int { count } + + func makeIterator() -> _Bitmap { self } + + /// Return the index of the lowest set bit in this word, + /// and also destructively clear it. + @inlinable + internal mutating func next() -> _Bucket? { + guard _value != 0 else { return nil } + let bucket = _Bucket(UInt(bitPattern: _value.trailingZeroBitCount)) + _value &= _value &- 1 // Clear lowest nonzero bit. + return bucket + } +} diff --git a/Sources/PersistentCollections/_Bucket.swift b/Sources/PersistentCollections/_Bucket.swift new file mode 100644 index 000000000..b072e0304 --- /dev/null +++ b/Sources/PersistentCollections/_Bucket.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +/// Identifies an entry in the hash table inside a node. +/// (Internally, a number between 0 and 31.) +internal struct _Bucket { + var value: UInt + + init(_ value: UInt) { self.value = value } + + static var bitWidth: Int { _Bitmap.capacity.trailingZeroBitCount } + static var bitMask: UInt { UInt(bitPattern: _Bitmap.capacity) &- 1 } +} + +extension _Bucket: Equatable { + @inline(__always) + internal static func ==(left: Self, right: Self) -> Bool { + left.value == right.value + } +} + +extension _Bucket: Comparable { + @inline(__always) + internal static func <(left: Self, right: Self) -> Bool { + left.value < right.value + } +} diff --git a/Sources/PersistentCollections/_Common.swift b/Sources/PersistentCollections/_Common.swift index 436c3393e..c63b369dd 100644 --- a/Sources/PersistentCollections/_Common.swift +++ b/Sources/PersistentCollections/_Common.swift @@ -27,29 +27,6 @@ internal var _maxDepth: Int { (_hashCodeLength + _bitPartitionSize - 1) / _bitPartitionSize } -internal func _maskFrom(_ hash: Int, _ shift: Int) -> Int { - (hash >> shift) & _bitPartitionMask -} - -internal func _bitposFrom(_ mask: Int) -> _NodeHeader.Bitmap { - 1 << mask -} - -internal func _indexFrom( - _ bitmap: _NodeHeader.Bitmap, - _ bitpos: _NodeHeader.Bitmap -) -> Int { - (bitmap & (bitpos &- 1)).nonzeroBitCount -} - -internal func _indexFrom( - _ bitmap: _NodeHeader.Bitmap, - _ mask: Int, - _ bitpos: _NodeHeader.Bitmap -) -> Int { - (bitmap == _NodeHeader.Bitmap.max) ? mask : _indexFrom(bitmap, bitpos) -} - // NEW @inlinable @inline(__always) diff --git a/Sources/PersistentCollections/_DictionaryNodeProtocol.swift b/Sources/PersistentCollections/_DictionaryNodeProtocol.swift index 53ff93db4..e234939cf 100644 --- a/Sources/PersistentCollections/_DictionaryNodeProtocol.swift +++ b/Sources/PersistentCollections/_DictionaryNodeProtocol.swift @@ -10,29 +10,31 @@ //===----------------------------------------------------------------------===// // FIXME: Remove -internal protocol _DictionaryNodeProtocol: _NodeProtocol { +internal protocol _DictionaryNodeProtocol: _NodeProtocol +where Element == (key: Key, value: Value) +{ associatedtype Key: Hashable associatedtype Value - func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? + func get(_ key: Key, _ path: _HashPath) -> Value? - func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool + func containsKey(_ key: Key, _ path: _HashPath) -> Bool func index( - _ key: Key, _ hash: Int, _ shift: Int, _ skippedBefore: Int + forKey key: Key, _ path: _HashPath, _ skippedBefore: Int ) -> PersistentDictionary.Index? func updateOrUpdating( - _ isStorageKnownUniquelyReferenced: Bool, - _ key: Key, _ value: Value, _ hash: Int, - _ shift: Int, + _ isUnique: Bool, + _ item: Element, + _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> Self func removeOrRemoving( _ isStorageKnownUniquelyReferenced: Bool, - _ key: Key, _ hash: Int, - _ shift: Int, + _ key: Key, + _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> Self } diff --git a/Sources/PersistentCollections/_HashPath.swift b/Sources/PersistentCollections/_HashPath.swift new file mode 100644 index 000000000..77847ef64 --- /dev/null +++ b/Sources/PersistentCollections/_HashPath.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +/// A structure for slicing up a hash value into a series of bucket values, +/// representing a path inside the prefix tree. +internal struct _HashPath { + internal var _hash: _HashValue + internal var _shift: UInt + + internal init(_hash: _HashValue, shift: UInt) { + self._hash = _hash + self._shift = shift + } + + internal init(_ key: Key) { + _hash = _HashValue(key) + _shift = 0 + } + + internal var currentBucket: _Bucket { + precondition(_shift < UInt.bitWidth, "Ran out of hash bits") + return _Bucket((_hash.value &>> _shift) & _Bucket.bitMask) + } + + internal var isAtRoot: Bool { _shift == 0 } + + internal func descend() -> _HashPath { + // FIXME: Consider returning nil when we run out of bits + let s = _shift &+ UInt(bitPattern: _Bucket.bitWidth) + return _HashPath(_hash: _hash, shift: s) + } + + internal func ascend() -> _HashPath { + precondition(_shift >= _Bucket.bitWidth) + let s = _shift &- UInt(bitPattern: _Bucket.bitWidth) + return _HashPath(_hash: _hash, shift: s) + } + + internal func top() -> _HashPath { + var result = self + result._shift = 0 + return result + } +} diff --git a/Sources/PersistentCollections/_HashValue.swift b/Sources/PersistentCollections/_HashValue.swift new file mode 100644 index 000000000..69b4730bd --- /dev/null +++ b/Sources/PersistentCollections/_HashValue.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +/// An abstract representation of a hash value. +internal struct _HashValue { + internal var value: UInt + + internal init(_ key: Key) { + let hashValue = key._rawHashValue(seed: 0) + self.value = UInt(bitPattern: hashValue) + } +} + +extension _HashValue: Equatable { + @inline(__always) + internal static func ==(left: Self, right: Self) -> Bool { + left.value == right.value + } +} diff --git a/Sources/PersistentCollections/_NonzeroBits.swift b/Sources/PersistentCollections/_NonzeroBits.swift deleted file mode 100644 index 4c908568e..000000000 --- a/Sources/PersistentCollections/_NonzeroBits.swift +++ /dev/null @@ -1,43 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 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 -// -//===----------------------------------------------------------------------===// - -extension FixedWidthInteger { - internal func _nonzeroBits() -> _NonzeroBits { - return _NonzeroBits(from: self) - } - - internal func _zeroBits() -> _NonzeroBits { - return _NonzeroBits(from: ~self) - } -} - -internal struct _NonzeroBits<_Bitmap: FixedWidthInteger> -: Sequence, IteratorProtocol, CustomStringConvertible -{ - var bitmap: _Bitmap - - init(from bitmap: _Bitmap) { - self.bitmap = bitmap - } - - public mutating func next() -> Int? { - guard bitmap != 0 else { return nil } - - let index = bitmap.trailingZeroBitCount - bitmap ^= 1 << index - - return index - } - - public var description: String { - "[\(self.map { $0.description }.joined(separator: ", "))]" - } -} diff --git a/Tests/PersistentCollectionsTests/BitmapSmokeTests.swift b/Tests/PersistentCollectionsTests/BitmapSmokeTests.swift index 5456401f8..fb85ddece 100644 --- a/Tests/PersistentCollectionsTests/BitmapSmokeTests.swift +++ b/Tests/PersistentCollectionsTests/BitmapSmokeTests.swift @@ -13,19 +13,14 @@ import _CollectionsTestSupport import XCTest @testable import PersistentCollections +#if false final class BitmapSmokeTests: CollectionTestCase { - typealias Bitmap = _NodeHeader.Bitmap - typealias Capacity = _NodeHeader.Capacity func test_BitPartitionSize_isValid() { expectTrue(_bitPartitionSize > 0) expectTrue((2 << (_bitPartitionSize - 1)) != 0) expectTrue((2 << (_bitPartitionSize - 1)) <= Bitmap.bitWidth) } - func test_Capacity_isValid() { - expectTrue(Int(2 << _bitPartitionSize) <= Int(Capacity.max)) - } - func test_Bitmap_nonzeroBits() { let bitmap: Bitmap = 0b0100_0000_0000_1011 @@ -66,3 +61,4 @@ final class BitmapSmokeTests: CollectionTestCase { expectNil(zipIterator.next()) } } +#endif diff --git a/Tests/PersistentCollectionsTests/_CollidableInt.swift b/Tests/PersistentCollectionsTests/_CollidableInt.swift index e4038dc3d..c07e58ed8 100644 --- a/Tests/PersistentCollectionsTests/_CollidableInt.swift +++ b/Tests/PersistentCollectionsTests/_CollidableInt.swift @@ -13,16 +13,16 @@ final class CollidableInt: CustomStringConvertible, CustomDebugStringConvertible, Equatable, Hashable { let value: Int - let hashValue: Int + let _hashValue: Int init(_ value: Int) { self.value = value - self.hashValue = value + self._hashValue = value } init(_ value: Int, _ hashValue: Int) { self.value = value - self.hashValue = hashValue + self._hashValue = hashValue } var description: String { @@ -30,16 +30,24 @@ final class CollidableInt: } var debugDescription: String { - return "\(value) [hash = \(hashValue)]" + return "\(value) [hash = \(_hashValue)]" + } + + func _rawHashValue(seed: Int) -> Int { + _hashValue } func hash(into hasher: inout Hasher) { - hasher.combine(hashValue) + fatalError() + } + + var hashValue: Int { + fatalError() } static func == (lhs: CollidableInt, rhs: CollidableInt) -> Bool { if lhs.value == rhs.value { - precondition(lhs.hashValue == rhs.hashValue) + precondition(lhs._hashValue == rhs._hashValue) return true } return false diff --git a/Utils/swift-collections.xcworkspace/xcshareddata/xcschemes/PersistentCollections.xcscheme b/Utils/swift-collections.xcworkspace/xcshareddata/xcschemes/PersistentCollections.xcscheme index ca1b18d98..0ad4a1be6 100644 --- a/Utils/swift-collections.xcworkspace/xcshareddata/xcschemes/PersistentCollections.xcscheme +++ b/Utils/swift-collections.xcworkspace/xcshareddata/xcschemes/PersistentCollections.xcscheme @@ -41,6 +41,7 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" + enableASanStackUseAfterReturn = "YES" codeCoverageEnabled = "YES">