diff --git a/Package.swift b/Package.swift index ccb80d957..aa9f90ea0 100644 --- a/Package.swift +++ b/Package.swift @@ -86,9 +86,15 @@ let package = Package( dependencies: ["_CollectionsTestSupport"], swiftSettings: settings), + .target( + name: "_CollectionsUtilities", + exclude: ["CMakeLists.txt"], + swiftSettings: settings), + // Deque .target( name: "DequeModule", + dependencies: ["_CollectionsUtilities"], exclude: ["CMakeLists.txt"], swiftSettings: settings), .testTarget( @@ -99,6 +105,7 @@ let package = Package( // OrderedSet, OrderedDictionary .target( name: "OrderedCollections", + dependencies: ["_CollectionsUtilities"], exclude: ["CMakeLists.txt"], swiftSettings: settings), .testTarget( @@ -109,6 +116,7 @@ let package = Package( // PersistentDictionary .target( name: "PersistentCollections", + dependencies: ["_CollectionsUtilities"], swiftSettings: settings), .testTarget( name: "PersistentCollectionsTests", diff --git a/Sources/DequeModule/Deque+CustomStringConvertible.swift b/Sources/DequeModule/Deque+CustomStringConvertible.swift deleted file mode 100644 index 915c339b4..000000000 --- a/Sources/DequeModule/Deque+CustomStringConvertible.swift +++ /dev/null @@ -1,28 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections 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 -// -//===----------------------------------------------------------------------===// - -extension Deque: CustomStringConvertible { - /// A textual representation of this instance. - public var description: String { - var result = "[" - var first = true - for item in self { - if first { - first = false - } else { - result += ", " - } - print(item, terminator: "", to: &result) - } - result += "]" - return result - } -} diff --git a/Sources/DequeModule/Deque+CustomDebugStringConvertible.swift b/Sources/DequeModule/Deque+Descriptions.swift similarity index 68% rename from Sources/DequeModule/Deque+CustomDebugStringConvertible.swift rename to Sources/DequeModule/Deque+Descriptions.swift index 5a8e258fc..532d5912c 100644 --- a/Sources/DequeModule/Deque+CustomDebugStringConvertible.swift +++ b/Sources/DequeModule/Deque+Descriptions.swift @@ -9,20 +9,19 @@ // //===----------------------------------------------------------------------===// +import _CollectionsUtilities + +extension Deque: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + _arrayDescription(for: self) + } +} + extension Deque: CustomDebugStringConvertible { /// A textual representation of this instance, suitable for debugging. public var debugDescription: String { - var result = "Deque<\(Element.self)>([" - var first = true - for item in self { - if first { - first = false - } else { - result += ", " - } - debugPrint(item, terminator: "", to: &result) - } - result += "])" - return result + _arrayDescription( + for: self, debug: true, typeName: "Deque<\(Element.self)>") } } diff --git a/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift b/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift deleted file mode 100644 index eb40ebab1..000000000 --- a/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift +++ /dev/null @@ -1,29 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections 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 -// -//===----------------------------------------------------------------------===// - -extension OrderedDictionary: CustomStringConvertible { - /// A textual representation of this instance. - public var description: String { - if isEmpty { return "[:]" } - var result = "[" - var first = true - for (key, value) in self { - if first { - first = false - } else { - result += ", " - } - result += "\(key): \(value)" - } - result += "]" - return result - } -} diff --git a/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift b/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Descriptions.swift similarity index 51% rename from Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift rename to Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Descriptions.swift index 4bc122a30..5598d2f5d 100644 --- a/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift +++ b/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Descriptions.swift @@ -2,43 +2,30 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2021 - 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 // //===----------------------------------------------------------------------===// +import _CollectionsUtilities + +extension OrderedDictionary: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + _dictionaryDescription(for: self.elements) + } +} + extension OrderedDictionary: CustomDebugStringConvertible { /// A textual representation of this instance, suitable for debugging. public var debugDescription: String { - _debugDescription(typeName: _debugTypeName()) + _dictionaryDescription( + for: self.elements, debug: true, typeName: _debugTypeName()) } internal func _debugTypeName() -> String { "OrderedDictionary<\(Key.self), \(Value.self)>" } - - internal func _debugDescription(typeName: String) -> String { - var result = "\(typeName)(" - if isEmpty { - result += "[:]" - } else { - result += "[" - var first = true - for (key, value) in self { - if first { - first = false - } else { - result += ", " - } - debugPrint(key, terminator: "", to: &result) - result += ": " - debugPrint(value, terminator: "", to: &result) - } - result += "]" - } - result += ")" - return result - } } diff --git a/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift b/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift index 50116a819..9f06c63a8 100644 --- a/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift +++ b/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import _CollectionsUtilities + extension OrderedDictionary { /// A view of the contents of an ordered dictionary as a random-access /// collection. @@ -333,7 +335,9 @@ extension OrderedDictionary.Elements: CustomStringConvertible { extension OrderedDictionary.Elements: CustomDebugStringConvertible { public var debugDescription: String { - _base._debugDescription( + _dictionaryDescription( + for: self, + debug: true, typeName: "OrderedDictionary<\(Key.self), \(Value.self)>.Elements") } } diff --git a/Sources/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift b/Sources/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift deleted file mode 100644 index 2678ed60a..000000000 --- a/Sources/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift +++ /dev/null @@ -1,28 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections 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 -// -//===----------------------------------------------------------------------===// - -extension OrderedSet: CustomStringConvertible { - /// A textual representation of this instance. - public var description: String { - var result = "[" - var first = true - for item in self { - if first { - first = false - } else { - result += ", " - } - print(item, terminator: "", to: &result) - } - result += "]" - return result - } -} diff --git a/Sources/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift b/Sources/OrderedCollections/OrderedSet/OrderedSet+Descriptions.swift similarity index 59% rename from Sources/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift rename to Sources/OrderedCollections/OrderedSet/OrderedSet+Descriptions.swift index effbe7d3a..31c516b6c 100644 --- a/Sources/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift +++ b/Sources/OrderedCollections/OrderedSet/OrderedSet+Descriptions.swift @@ -2,35 +2,30 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2021 - 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 // //===----------------------------------------------------------------------===// +import _CollectionsUtilities + +extension OrderedSet: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + _arrayDescription(for: self) + } +} + extension OrderedSet: CustomDebugStringConvertible { /// A textual representation of this instance, suitable for debugging. public var debugDescription: String { - _debugDescription(typeName: _debugTypeName()) + _arrayDescription( + for: self, debug: true, typeName: _debugTypeName()) } internal func _debugTypeName() -> String { "OrderedSet<\(Element.self)>" } - - internal func _debugDescription(typeName: String) -> String { - var result = "\(typeName)([" - var first = true - for item in self { - if first { - first = false - } else { - result += ", " - } - debugPrint(item, terminator: "", to: &result) - } - result += "])" - return result - } } diff --git a/Sources/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift b/Sources/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift index 88216a6d5..02ba7e542 100644 --- a/Sources/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift +++ b/Sources/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import _CollectionsUtilities + extension OrderedSet { /// An unordered view into an ordered set, providing `SetAlgebra` /// conformance. @@ -72,7 +74,10 @@ extension OrderedSet.UnorderedView: CustomStringConvertible { extension OrderedSet.UnorderedView: CustomDebugStringConvertible { /// A textual representation of this instance, suitable for debugging. public var debugDescription: String { - _base._debugDescription(typeName: "\(_base._debugTypeName()).UnorderedView") + _arrayDescription( + for: _base, + debug: true, + typeName: "\(_base._debugTypeName()).UnorderedView") } } diff --git a/Sources/PersistentCollections/PersistentDictionary+Codable.swift b/Sources/PersistentCollections/PersistentDictionary+Codable.swift new file mode 100644 index 000000000..87a1ca60b --- /dev/null +++ b/Sources/PersistentCollections/PersistentDictionary+Codable.swift @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Code in this file is a slightly adapted variant of `Dictionary`'s `Codable` +// implementation in the Standard Library as of Swift 5.7. +// `PersistentDictionary` therefore encodes/decodes itself exactly the same as +// `Dictionary`, and the two types can each decode data encoded by the other. + +/// A wrapper for dictionary keys which are Strings or Ints. +internal struct _DictionaryCodingKey: CodingKey { + internal let stringValue: String + internal let intValue: Int? + + internal init(stringValue: String) { + self.stringValue = stringValue + self.intValue = Int(stringValue) + } + + internal init(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + fileprivate init(codingKey: CodingKey) { + self.stringValue = codingKey.stringValue + self.intValue = codingKey.intValue + } +} + +extension PersistentDictionary: Encodable +where Key: Encodable, Value: Encodable +{ + public func encode(to encoder: Encoder) throws { + if Key.self == String.self { + // Since the keys are already Strings, we can use them as keys directly. + var container = encoder.container(keyedBy: _DictionaryCodingKey.self) + for (key, value) in self { + let codingKey = _DictionaryCodingKey(stringValue: key as! String) + try container.encode(value, forKey: codingKey) + } + } else if Key.self == Int.self { + // Since the keys are already Ints, we can use them as keys directly. + var container = encoder.container(keyedBy: _DictionaryCodingKey.self) + for (key, value) in self { + let codingKey = _DictionaryCodingKey(intValue: key as! Int) + try container.encode(value, forKey: codingKey) + } + } else if #available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *), + Key.self is CodingKeyRepresentable.Type { + // Since the keys are CodingKeyRepresentable, we can use the `codingKey` + // to create `_DictionaryCodingKey` instances. + var container = encoder.container(keyedBy: _DictionaryCodingKey.self) + for (key, value) in self { + let codingKey = (key as! CodingKeyRepresentable).codingKey + let dictionaryCodingKey = _DictionaryCodingKey(codingKey: codingKey) + try container.encode(value, forKey: dictionaryCodingKey) + } + } else { + // Keys are Encodable but not Strings or Ints, so we cannot arbitrarily + // convert to keys. We can encode as an array of alternating key-value + // pairs, though. + var container = encoder.unkeyedContainer() + for (key, value) in self { + try container.encode(key) + try container.encode(value) + } + } + } +} + +extension PersistentDictionary: Decodable +where Key: Decodable, Value: Decodable +{ + /// Creates a new dictionary by decoding from the given decoder. + /// + /// This initializer throws an error if reading from the decoder fails, or + /// if the data read is corrupted or otherwise invalid. + /// + /// - Parameter decoder: The decoder to read data from. + public init(from decoder: Decoder) throws { + self.init() + + if Key.self == String.self { + // The keys are Strings, so we should be able to expect a keyed container. + let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) + for key in container.allKeys { + let value = try container.decode(Value.self, forKey: key) + self[key.stringValue as! Key] = value + } + } else if Key.self == Int.self { + // The keys are Ints, so we should be able to expect a keyed container. + let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) + for key in container.allKeys { + guard key.intValue != nil else { + // We provide stringValues for Int keys; if an encoder chooses not to + // use the actual intValues, we've encoded string keys. + // So on init, _DictionaryCodingKey tries to parse string keys as + // Ints. If that succeeds, then we would have had an intValue here. + // We don't, so this isn't a valid Int key. + var codingPath = decoder.codingPath + codingPath.append(key) + throw DecodingError.typeMismatch( + Int.self, + DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected Int key but found String key instead." + ) + ) + } + + let value = try container.decode(Value.self, forKey: key) + self[key.intValue! as! Key] = value + } + } else if #available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *), + let keyType = Key.self as? CodingKeyRepresentable.Type { + // The keys are CodingKeyRepresentable, so we should be able to expect + // a keyed container. + let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) + for codingKey in container.allKeys { + guard let key: Key = keyType.init(codingKey: codingKey) as? Key else { + throw DecodingError.dataCorruptedError( + forKey: codingKey, + in: container, + debugDescription: "Could not convert key to type \(Key.self)" + ) + } + let value: Value = try container.decode(Value.self, forKey: codingKey) + self[key] = value + } + } else { + // We should have encoded as an array of alternating key-value pairs. + var container = try decoder.unkeyedContainer() + + // We're expecting to get pairs. If the container has a known count, it + // had better be even; no point in doing work if not. + if let count = container.count { + guard count % 2 == 0 else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Expected collection of key-value pairs; encountered odd-length array instead." + ) + ) + } + } + + while !container.isAtEnd { + let key = try container.decode(Key.self) + + guard !container.isAtEnd else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unkeyed container reached end before value in key-value pair." + ) + ) + } + + let value = try container.decode(Value.self) + self[key] = value + } + } + } +} + diff --git a/Sources/PersistentCollections/PersistentDictionary+Collection.swift b/Sources/PersistentCollections/PersistentDictionary+Collection.swift index ff9645625..a13507e2f 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Collection.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Collection.swift @@ -11,37 +11,41 @@ extension PersistentDictionary: Collection { public struct Index: Comparable { + @usableFromInline internal let _value: Int + @usableFromInline internal init(_value: Int) { self._value = _value } + @inlinable public static func < (lhs: Self, rhs: Self) -> Bool { lhs._value < rhs._value } } - /// - /// Manipulating Indices - /// - + @inlinable + public var isEmpty: Bool { _root.count == 0 } + + @inlinable + public var count: Int { _root.count } + + @inlinable public var startIndex: Index { Index(_value: 0) } - + + @inlinable public var endIndex: Index { Index(_value: count) } + @inlinable public func index(after i: Index) -> Index { Index(_value: i._value + 1) } - /// Returns the index for the given key. - public func index(forKey key: Key) -> Index? { - rootNode.index(forKey: key, _HashPath(key), 0) - } - /// Accesses the key-value pair at the specified position. + @inlinable public subscript(position: Index) -> Element { - rootNode.item(position: position._value) + _root.item(position: position._value) } } diff --git a/Sources/PersistentCollections/PersistentDictionary+CustomDebugStringConvertible.swift b/Sources/PersistentCollections/PersistentDictionary+CustomDebugStringConvertible.swift deleted file mode 100644 index 6bbdc2dbd..000000000 --- a/Sources/PersistentCollections/PersistentDictionary+CustomDebugStringConvertible.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: CustomDebugStringConvertible { -// public var debugDescription: String { -// <#code#> -// } -//} diff --git a/Sources/PersistentCollections/PersistentDictionary+CustomReflectible.swift b/Sources/PersistentCollections/PersistentDictionary+CustomReflectible.swift index 9533539bb..fde019a9a 100644 --- a/Sources/PersistentCollections/PersistentDictionary+CustomReflectible.swift +++ b/Sources/PersistentCollections/PersistentDictionary+CustomReflectible.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -//extension PersistentDictionary: CustomReflectable { -// public var customMirror: Mirror { -// <#code#> -// } -//} +extension PersistentDictionary: CustomReflectable { + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: self, displayStyle: .dictionary) + } +} diff --git a/Sources/PersistentCollections/PersistentDictionary+Decodable.swift b/Sources/PersistentCollections/PersistentDictionary+Decodable.swift deleted file mode 100644 index 1aeb87e68..000000000 --- a/Sources/PersistentCollections/PersistentDictionary+Decodable.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: Decodable where Key: Decodable, Value: Decodable { -// public init(from decoder: Decoder) throws { -// <#code#> -// } -//} diff --git a/Sources/PersistentCollections/PersistentDictionary+CustomStringConvertible.swift b/Sources/PersistentCollections/PersistentDictionary+Descriptions.swift similarity index 59% rename from Sources/PersistentCollections/PersistentDictionary+CustomStringConvertible.swift rename to Sources/PersistentCollections/PersistentDictionary+Descriptions.swift index f1abbd3ba..c14a3284d 100644 --- a/Sources/PersistentCollections/PersistentDictionary+CustomStringConvertible.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Descriptions.swift @@ -9,23 +9,21 @@ // //===----------------------------------------------------------------------===// +import _CollectionsUtilities + extension PersistentDictionary: CustomStringConvertible { public var description: String { - guard count > 0 else { - return "[:]" - } - - var result = "[" - var first = true - for (key, value) in self { - if first { - first = false - } else { - result += ", " - } - result += "\(key): \(value)" - } - result += "]" - return result + _dictionaryDescription(for: self) + } +} + +extension PersistentDictionary: CustomDebugStringConvertible { + public var debugDescription: String { + _dictionaryDescription( + for: self, debug: true, typeName: _debugTypeName()) + } + + internal func _debugTypeName() -> String { + "PersistentDictionary<\(Key.self), \(Value.self)>" } } diff --git a/Sources/PersistentCollections/PersistentDictionary+Encodable.swift b/Sources/PersistentCollections/PersistentDictionary+Encodable.swift deleted file mode 100644 index 38198266c..000000000 --- a/Sources/PersistentCollections/PersistentDictionary+Encodable.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: Encodable where Key: Encodable, Value: Encodable { -// public func encode(to encoder: Encoder) throws { -// <#code#> -// } -//} diff --git a/Sources/PersistentCollections/PersistentDictionary+Equatable.swift b/Sources/PersistentCollections/PersistentDictionary+Equatable.swift index ba6e0d377..6fee90495 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Equatable.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Equatable.swift @@ -10,7 +10,8 @@ //===----------------------------------------------------------------------===// extension PersistentDictionary: Equatable where Value: Equatable { + @inlinable public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode + lhs._root === rhs._root || lhs._root == rhs._root } } diff --git a/Sources/PersistentCollections/PersistentDictionary+Hashable.swift b/Sources/PersistentCollections/PersistentDictionary+Hashable.swift index b9331199b..d545be327 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Hashable.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Hashable.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// extension PersistentDictionary: Hashable where Value: Hashable { + @inlinable public func hash(into hasher: inout Hasher) { var commutativeHash = 0 for (key, value) in self { diff --git a/Sources/PersistentCollections/PersistentDictionary+Keys.swift b/Sources/PersistentCollections/PersistentDictionary+Keys.swift index 99b660d29..210ed1239 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Keys.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Keys.swift @@ -15,6 +15,7 @@ extension PersistentDictionary { public typealias Keys = [Key] /// A collection containing just the keys of the dictionary. + @inlinable public var keys: Self.Keys /* { get } */ { self.map { $0.key } } diff --git a/Sources/PersistentCollections/PersistentDictionary+Sequence.swift b/Sources/PersistentCollections/PersistentDictionary+Sequence.swift index 64230bd33..322c1ff75 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Sequence.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Sequence.swift @@ -13,65 +13,83 @@ extension PersistentDictionary: Sequence { public typealias Element = (key: Key, value: Value) public struct Iterator { - // Fixed-stack iterator for traversing a hash-trie. The iterator performs a - // depth-first pre-order traversal, which yields first all payload elements - // of the current node before traversing sub-nodes (left to right). + // Fixed-stack iterator for traversing a hash tree. + // The iterator performs a pre-order traversal, with items at a node visited + // before any items within children. - typealias _KeyValueBuffer = UnsafeBufferPointer<(key: Key, value: Value)> - typealias _NodeBuffer = UnsafeBufferPointer<_Node> + @usableFromInline + typealias _ItemBuffer = UnsafeBufferPointer - private var payloadIterator: _KeyValueBuffer.Iterator? + @usableFromInline + typealias _ChildBuffer = UnsafeBufferPointer<_Node> - private var trieIteratorStackTop: _NodeBuffer.Iterator? - private var trieIteratorStackRemainder: [_NodeBuffer.Iterator] + @usableFromInline + internal var _root: _Node - internal init(_root root: _Node) { - trieIteratorStackRemainder = [] - trieIteratorStackRemainder.reserveCapacity(_maxDepth) + @usableFromInline + internal var _itemIterator: _ItemBuffer.Iterator? - if root.hasNodes { - trieIteratorStackTop = root._trieSlice.makeIterator() + @usableFromInline + internal var _pathTop: _ChildBuffer.Iterator? + + @usableFromInline + internal var _pathRest: [_ChildBuffer.Iterator] + + @inlinable + internal init(_root: _Node) { + self._root = _root + self._pathRest = [] + self._pathRest.reserveCapacity(_maxDepth) + + if _root.hasItems { + self._itemIterator = _root._items.makeIterator() } - if root.hasPayload { - payloadIterator = root._dataSlice.makeIterator() + if _root.hasChildren { + self._pathTop = _root._children.makeIterator() } } } + @inlinable + public var underestimatedCount: Int { + _root.count + } + + @inlinable public __consuming func makeIterator() -> Iterator { - return Iterator(_root: rootNode) + return Iterator(_root: _root) } } extension PersistentDictionary.Iterator: IteratorProtocol { public typealias Element = (key: Key, value: Value) + + @inlinable public mutating func next() -> Element? { - if let payload = payloadIterator?.next() { - return payload + if let item = _itemIterator?.next() { + return item } - while trieIteratorStackTop != nil { - if let nextNode = trieIteratorStackTop!.next() { - if nextNode.hasNodes { - trieIteratorStackRemainder.append(trieIteratorStackTop!) - trieIteratorStackTop = nextNode._trieSlice.makeIterator() - } - if nextNode.hasPayload { - payloadIterator = nextNode._dataSlice.makeIterator() - return payloadIterator?.next() - } - } else { - trieIteratorStackTop = trieIteratorStackRemainder.popLast() + _itemIterator = nil + + while _pathTop != nil { + guard let nextNode = _pathTop!.next() else { + _pathTop = _pathRest.popLast() + continue + } + if nextNode.hasChildren { + _pathRest.append(_pathTop!) + _pathTop = nextNode._children.makeIterator() + } + if nextNode.hasItems { + _itemIterator = nextNode._items.makeIterator() + return _itemIterator!.next() } } - // Clean-up state - payloadIterator = nil - - assert(payloadIterator == nil) - assert(trieIteratorStackTop == nil) - assert(trieIteratorStackRemainder.isEmpty) - + assert(_itemIterator == nil) + assert(_pathTop == nil) + assert(_pathRest.isEmpty) return nil } } diff --git a/Sources/PersistentCollections/PersistentDictionary+Values.swift b/Sources/PersistentCollections/PersistentDictionary+Values.swift index 5ff663d7c..0952f8468 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Values.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Values.swift @@ -15,6 +15,7 @@ extension PersistentDictionary { public typealias Values = [Value] /// A collection containing just the values of the dictionary. + @inlinable public var values: Self.Values /* { get set } */ { self.map { $0.value } } diff --git a/Sources/PersistentCollections/PersistentDictionary._Node+CustomStringConvertible.swift b/Sources/PersistentCollections/PersistentDictionary._Node+CustomStringConvertible.swift index 88c31c947..cec26ae4d 100644 --- a/Sources/PersistentCollections/PersistentDictionary._Node+CustomStringConvertible.swift +++ b/Sources/PersistentCollections/PersistentDictionary._Node+CustomStringConvertible.swift @@ -10,30 +10,31 @@ //===----------------------------------------------------------------------===// extension PersistentDictionary._Node: CustomStringConvertible { - public var description: String { - guard count > 0 else { - return "[:]" - } - - var result = "[" - var first = true - for (key, value) in _dataSlice { - if first { - first = false - } else { - result += ", " - } - result += "\(key): \(value)" - } - for node in _trieSlice { - if first { - first = false - } else { - result += ", " - } - result += "\(node.description)" - } - result += "]" - return result + @usableFromInline + internal var description: String { + guard count > 0 else { + return "[:]" + } + + var result = "[" + var first = true + for (key, value) in _items { + if first { + first = false + } else { + result += ", " + } + result += "\(key): \(value)" + } + for node in _children { + if first { + first = false + } else { + result += ", " + } + result += "\(node.description)" } + result += "]" + return result + } } diff --git a/Sources/PersistentCollections/PersistentDictionary._Node.swift b/Sources/PersistentCollections/PersistentDictionary._Node.swift index 77a8fcff9..1bf75df06 100644 --- a/Sources/PersistentCollections/PersistentDictionary._Node.swift +++ b/Sources/PersistentCollections/PersistentDictionary._Node.swift @@ -9,165 +9,215 @@ // //===----------------------------------------------------------------------===// +@usableFromInline internal struct _NodeHeader { - internal var dataMap: _Bitmap - internal var trieMap: _Bitmap + @usableFromInline + internal var itemMap: _Bitmap - init(dataMap: _Bitmap, trieMap: _Bitmap) { - self.dataMap = dataMap - self.trieMap = trieMap + @usableFromInline + internal var childMap: _Bitmap + + @inlinable + init(itemMap: _Bitmap, childMap: _Bitmap) { + self.itemMap = itemMap + self.childMap = childMap } } extension _NodeHeader { + @inlinable internal var isCollisionNode: Bool { - !dataMap.intersection(trieMap).isEmpty + !itemMap.intersection(childMap).isEmpty } - internal var dataCount: Int { - isCollisionNode ? Int(dataMap._value) : dataMap.count + @inlinable + internal var itemCount: Int { + isCollisionNode ? Int(itemMap._value) : itemMap.count } - internal var trieCount: Int { - isCollisionNode ? 0 : trieMap.count + @inlinable + internal var childCount: Int { + isCollisionNode ? 0 : childMap.count } } extension _NodeHeader: Equatable { + @inlinable internal static func == (lhs: _NodeHeader, rhs: _NodeHeader) -> Bool { - lhs.dataMap == rhs.dataMap && lhs.trieMap == rhs.trieMap + lhs.itemMap == rhs.itemMap && lhs.childMap == rhs.childMap + } +} + +extension _NodeHeader { +#if COLLECTIONS_INTERNAL_CHECKS + @inline(never) + func _invariantCheck() { + if isCollisionNode { + precondition(itemMap == childMap) + precondition(!itemMap.isEmpty) + return + } + precondition(itemMap.intersection(childMap).isEmpty) } +#else + @inline(__always) + func _invariantCheck() {} +#endif } extension PersistentDictionary { + @usableFromInline internal final class _Node { + @usableFromInline typealias Element = (key: Key, value: Value) + + @usableFromInline typealias Index = PersistentDictionary.Index // TODO: restore type to `UInt8` after reworking hash-collisions to grow in // depth instead of width + @usableFromInline internal typealias Capacity = UInt32 + @usableFromInline var header: _NodeHeader + + @usableFromInline var count: Int - let dataCapacity: Capacity - let trieCapacity: Capacity + @usableFromInline + let itemCapacity: Capacity + + @usableFromInline + let childCapacity: Capacity + + @usableFromInline + let itemBaseAddress: UnsafeMutablePointer - let dataBaseAddress: UnsafeMutablePointer - let trieBaseAddress: UnsafeMutablePointer<_Node> + @usableFromInline + let childBaseAddress: UnsafeMutablePointer<_Node> deinit { - dataBaseAddress.deinitialize(count: header.dataCount) - trieBaseAddress.deinitialize(count: header.trieCount) + itemBaseAddress.deinitialize(count: header.itemCount) + childBaseAddress.deinitialize(count: header.childCount) rootBaseAddress.deallocate() } - init(dataCapacity: Capacity, trieCapacity: Capacity) { - let (dataBaseAddress, trieBaseAddress) = _Node._allocate( - dataCapacity: dataCapacity, - trieCapacity: trieCapacity) + @inlinable + init(itemCapacity: Capacity, childCapacity: Capacity) { + let (itemBaseAddress, childBaseAddress) = _Node._allocate( + itemCapacity: itemCapacity, + childCapacity: childCapacity) - self.header = _NodeHeader(dataMap: .empty, trieMap: .empty) + self.header = _NodeHeader(itemMap: .empty, childMap: .empty) self.count = 0 - self.dataBaseAddress = dataBaseAddress - self.trieBaseAddress = trieBaseAddress + self.itemBaseAddress = itemBaseAddress + self.childBaseAddress = childBaseAddress - self.dataCapacity = dataCapacity - self.trieCapacity = trieCapacity + self.itemCapacity = itemCapacity + self.childCapacity = childCapacity - assert(self.invariant) + _invariantCheck() } } } extension PersistentDictionary._Node { + @usableFromInline typealias _Node = PersistentDictionary._Node + @inlinable static var initialDataCapacity: Capacity { 4 } + + @inlinable static var initialTrieCapacity: Capacity { 1 } } extension PersistentDictionary._Node { @inlinable static func _allocate( - dataCapacity: Capacity, trieCapacity: Capacity + itemCapacity: Capacity, childCapacity: Capacity ) -> ( - dataBaseAddress: UnsafeMutablePointer, - trieBaseAddress: UnsafeMutablePointer<_Node> + itemBaseAddress: UnsafeMutablePointer, + childBaseAddress: UnsafeMutablePointer<_Node> ) { - let dataCapacityInBytes = Int(dataCapacity) * MemoryLayout.stride - let trieCapacityInBytes = Int(trieCapacity) * MemoryLayout<_Node>.stride + let itemBytes = Int(itemCapacity) * MemoryLayout.stride + let childBytes = Int(childCapacity) * MemoryLayout<_Node>.stride let alignment = Swift.max( MemoryLayout.alignment, MemoryLayout<_Node>.alignment) let memory = UnsafeMutableRawPointer.allocate( - byteCount: dataCapacityInBytes + trieCapacityInBytes, + byteCount: itemBytes + childBytes, alignment: alignment) - let dataBaseAddress = memory - .advanced(by: trieCapacityInBytes) - .bindMemory(to: Element.self, capacity: Int(dataCapacity)) - let trieBaseAddress = memory - .bindMemory(to: _Node.self, capacity: Int(trieCapacity)) + let itemBaseAddress = memory + .advanced(by: childBytes) + .bindMemory(to: Element.self, capacity: Int(itemCapacity)) + let childBaseAddress = memory + .bindMemory(to: _Node.self, capacity: Int(childCapacity)) - return (dataBaseAddress, trieBaseAddress) + return (itemBaseAddress, childBaseAddress) } + @inlinable func copy( - withDataCapacityFactor dataCapacityFactor: Capacity = 1, - withDataCapacityShrinkFactor dataCapacityShrinkFactor: Capacity = 1, - withTrieCapacityFactor trieCapacityFactor: Capacity = 1, - withTrieCapacityShrinkFactor trieCapacityShrinkFactor: Capacity = 1 + itemCapacityGrowthFactor itemGrowthFactor: Capacity = 1, + itemCapacityShrinkFactor itemShrinkFactor: Capacity = 1, + childCapacityGrowthFactor childGrowthFactor: Capacity = 1, + childCapacityShrinkFactor childShrinkFactor: Capacity = 1 ) -> _Node { let src = self - let dc = src.dataCapacity &* dataCapacityFactor / dataCapacityShrinkFactor - let tc = src.trieCapacity &* trieCapacityFactor / trieCapacityShrinkFactor - let dst = _Node(dataCapacity: dc, trieCapacity: tc) + let dc = src.itemCapacity &* itemGrowthFactor / itemShrinkFactor + let tc = src.childCapacity &* childGrowthFactor / childShrinkFactor + let dst = _Node(itemCapacity: dc, childCapacity: tc) dst.header = src.header dst.count = src.count - dst.dataBaseAddress.initialize( - from: src.dataBaseAddress, - count: src.header.dataCount) - dst.trieBaseAddress.initialize( - from: src.trieBaseAddress, - count: src.header.trieCount) + dst.itemBaseAddress.initialize( + from: src.itemBaseAddress, + count: src.header.itemCount) + dst.childBaseAddress.initialize( + from: src.childBaseAddress, + count: src.header.childCount) - assert(src.invariant) - assert(dst.invariant) + src._invariantCheck() + dst._invariantCheck() return dst } } extension PersistentDictionary._Node { + @inlinable convenience init() { self.init( - dataCapacity: _Node.initialDataCapacity, - trieCapacity: _Node.initialTrieCapacity) + itemCapacity: _Node.initialDataCapacity, + childCapacity: _Node.initialTrieCapacity) - self.header = _NodeHeader(dataMap: .empty, trieMap: .empty) + self.header = _NodeHeader(itemMap: .empty, childMap: .empty) - assert(self.invariant) + _invariantCheck() } - convenience init(dataMap: _Bitmap, _ item: Element) { - assert(dataMap.count == 1) + @inlinable + convenience init(itemMap: _Bitmap, _ item: Element) { + assert(itemMap.count == 1) self.init() - self.header = _NodeHeader(dataMap: dataMap, trieMap: .empty) + self.header = _NodeHeader(itemMap: itemMap, childMap: .empty) self.count = 1 - self.dataBaseAddress.initialize(to: item) - assert(self.invariant) + self.itemBaseAddress.initialize(to: item) + _invariantCheck() } + @inlinable convenience init(_ item: Element, at bucket: _Bucket) { - self.init(dataMap: _Bitmap(bucket), item) + self.init(itemMap: _Bitmap(bucket), item) } + @inlinable convenience init( _ item0: Element, at bucket0: _Bucket, _ item1: Element, at bucket1: _Bucket @@ -176,33 +226,35 @@ extension PersistentDictionary._Node { self.init() self.header = _NodeHeader( - dataMap: _Bitmap(bucket0, bucket1), - trieMap: .empty) + itemMap: _Bitmap(bucket0, bucket1), + childMap: .empty) self.count = 2 if bucket0 < bucket1 { - self.dataBaseAddress.initialize(to: item0) - self.dataBaseAddress.successor().initialize(to: item1) + self.itemBaseAddress.initialize(to: item0) + self.itemBaseAddress.successor().initialize(to: item1) } else { - self.dataBaseAddress.initialize(to: item1) - self.dataBaseAddress.successor().initialize(to: item0) + self.itemBaseAddress.initialize(to: item1) + self.itemBaseAddress.successor().initialize(to: item0) } - assert(self.invariant) + _invariantCheck() } + @inlinable convenience init(_ child: _Node, at bucket: _Bucket) { self.init() self.header = _NodeHeader( - dataMap: .empty, - trieMap: _Bitmap(bucket)) + itemMap: .empty, + childMap: _Bitmap(bucket)) self.count = child.count - self.trieBaseAddress.initialize(to: child) + self.childBaseAddress.initialize(to: child) - assert(self.invariant) + _invariantCheck() } + @inlinable convenience init( _ item: Element, at bucket0: _Bucket, _ child: _Node, at bucket1: _Bucket @@ -211,253 +263,269 @@ extension PersistentDictionary._Node { self.init() self.header = _NodeHeader( - dataMap: _Bitmap(bucket0), - trieMap: _Bitmap(bucket1)) + itemMap: _Bitmap(bucket0), + childMap: _Bitmap(bucket1)) self.count = 1 + child.count - self.dataBaseAddress.initialize(to: item) - self.trieBaseAddress.initialize(to: child) + self.itemBaseAddress.initialize(to: item) + self.childBaseAddress.initialize(to: child) - assert(self.invariant) + _invariantCheck() } - convenience init(collisions: [Element]) { - self.init(dataCapacity: Capacity(collisions.count), trieCapacity: 0) + @inlinable + convenience init(collisions: C) where C.Element == Element { + self.init(itemCapacity: Capacity(collisions.count), childCapacity: 0) - self.header = _NodeHeader( - dataMap: _Bitmap(bitPattern: collisions.count), - trieMap: _Bitmap(bitPattern: collisions.count)) self.count = collisions.count + self.header = _NodeHeader( + itemMap: _Bitmap(bitPattern: count), + childMap: _Bitmap(bitPattern: count)) - self.dataBaseAddress.initialize(from: collisions, count: collisions.count) + var (it, c) = self._mutableItems.initialize(from: collisions) + precondition(it.next() == nil && c == self.count) - assert(self.invariant) + _invariantCheck() } } extension PersistentDictionary._Node { + @inlinable var isRegularNode: Bool { !isCollisionNode } + @inlinable var isCollisionNode: Bool { header.isCollisionNode } - private var rootBaseAddress: UnsafeMutableRawPointer { - UnsafeMutableRawPointer(trieBaseAddress) + @inlinable + internal var rootBaseAddress: UnsafeMutableRawPointer { + UnsafeMutableRawPointer(childBaseAddress) } + @inlinable @inline(__always) - var dataMap: _Bitmap { - header.dataMap + var itemMap: _Bitmap { + header.itemMap } + @inlinable @inline(__always) - var trieMap: _Bitmap { - header.trieMap + var childMap: _Bitmap { + header.childMap } - var invariant: Bool { - guard headerInvariant else { - return false - } - - // let recursiveCount = self.reduce(0, { count, _ in count + 1 }) - // - // guard recursiveCount == count else { - // return false - // } - - guard count - payloadArity >= 2 * nodeArity else { - return false - } - - if isCollisionNode { - let hash = _HashValue(_dataSlice.first!.key) - - guard _dataSlice.allSatisfy({ _HashValue($0.key) == hash }) else { - return false - } - } - - return true + @inlinable + var _items: UnsafeBufferPointer { + UnsafeBufferPointer(start: itemBaseAddress, count: header.itemCount) } - var headerInvariant: Bool { - header.dataMap.intersection(header.trieMap).isEmpty - || (header.dataMap == header.trieMap) + @inlinable + var _children: UnsafeMutableBufferPointer<_Node> { + UnsafeMutableBufferPointer(start: childBaseAddress, count: header.childCount) } - var _dataSlice: UnsafeBufferPointer { - UnsafeBufferPointer(start: dataBaseAddress, count: header.dataCount) + @inlinable + var _mutableItems: UnsafeMutableBufferPointer { + UnsafeMutableBufferPointer(start: itemBaseAddress, count: header.itemCount) } - var _trieSlice: UnsafeMutableBufferPointer<_Node> { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount) + @inlinable + var isCandidateForCompaction: Bool { itemCount == 0 && childCount == 1 } + + @inlinable + func isChildUnique( + at offset: Int, uniqueParent isParentUnique: Bool + ) -> Bool { + guard isParentUnique else { return false } + return isKnownUniquelyReferenced(&_children[offset]) } +} - var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } +extension PersistentDictionary._Node { +#if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline + @inline(never) + func _invariantCheck() { + header._invariantCheck() - func isTrieNodeKnownUniquelyReferenced( - _ slotIndex: Int, - _ isParentNodeKnownUniquelyReferenced: Bool - ) -> Bool { - let isUnique = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) + precondition(itemCount <= itemCapacity) + precondition(childCount <= childCapacity) + + precondition(count >= itemCount + 2 * childCount) + + let c = self.itemCount + _children.reduce(0) { $0 + $1.count } + precondition(c == self.count) - return isParentNodeKnownUniquelyReferenced && isUnique + if isCollisionNode { + precondition(childCount == 0) + let hash = _HashValue(_items.first!.key) + precondition(_items.allSatisfy { _HashValue($0.key) == hash }) + } } +#else + @inlinable + @inline(__always) + func _invariantCheck() {} +#endif } extension PersistentDictionary._Node: _NodeProtocol { - var hasNodes: Bool { !header.trieMap.isEmpty } + @inlinable + var hasChildren: Bool { !header.childMap.isEmpty } - var nodeArity: Int { header.trieCount } + @inlinable + var childCount: Int { header.childCount } - func getNode(_ index: Int) -> _Node { - trieBaseAddress[index] + @inlinable + func child(at index: Int) -> _Node { + childBaseAddress[index] } - var hasPayload: Bool { !header.dataMap.isEmpty } + @inlinable + var hasItems: Bool { !header.itemMap.isEmpty } - var payloadArity: Int { header.dataCount } + @inlinable + var itemCount: Int { header.itemCount } - func getPayload(_ index: Int) -> (key: Key, value: Value) { - dataBaseAddress[index] + @inlinable + func item(at offset: Int) -> Element { + _items[offset] } } extension PersistentDictionary._Node: _DictionaryNodeProtocol { + @inlinable func get(_ key: Key, _ path: _HashPath) -> Value? { guard isRegularNode else { - let content: [Element] = Array(self) - let hash = _HashValue(content.first!.key) - guard path._hash == hash else { return nil } - return content.first(where: { key == $0.key }).map { $0.value } + let hash = _HashValue(_items.first!.key) + guard path.hash == hash else { return nil } + return _items.first(where: { key == $0.key })?.value } let bucket = path.currentBucket - if dataMap.contains(bucket) { - let offset = dataMap.offset(of: bucket) - let payload = self.getPayload(offset) + if itemMap.contains(bucket) { + let offset = itemMap.offset(of: bucket) + let payload = self.item(at: offset) return key == payload.key ? payload.value : nil } - if trieMap.contains(bucket) { - let offset = trieMap.offset(of: bucket) - return self.getNode(offset).get(key, path.descend()) + if childMap.contains(bucket) { + let offset = childMap.offset(of: bucket) + return self.child(at: offset).get(key, path.descend()) } return nil } + @inlinable func containsKey(_ key: Key, _ path: _HashPath) -> Bool { guard isRegularNode else { - let content: [Element] = Array(self) - let hash = _HashValue(content.first!.key) - guard path._hash == hash else { return false } - return content.contains(where: { key == $0.key }) + let hash = _HashValue(_items.first!.key) + guard path.hash == hash else { return false } + return _items.contains(where: { key == $0.key }) } let bucket = path.currentBucket - if dataMap.contains(bucket) { - let offset = dataMap.offset(of: bucket) - let payload = self.getPayload(offset) - return key == payload.key + if itemMap.contains(bucket) { + let offset = itemMap.offset(of: bucket) + return key == self._items[offset].key } - if trieMap.contains(bucket) { - let offset = trieMap.offset(of: bucket) + if childMap.contains(bucket) { + let offset = childMap.offset(of: bucket) return self - .getNode(offset) + .child(at: offset) .containsKey(key, path.descend()) } return false } + @inlinable func index( forKey key: Key, _ path: _HashPath, _ skippedBefore: Int ) -> Index? { guard isRegularNode else { - let content: [Element] = Array(self) - let hash = _HashValue(content.first!.key) - assert(path._hash == hash) - return content - .firstIndex(where: { _key, _ in _key == key }) - .map { Index(_value: $0) } + let hash = _HashValue(_items.first!.key) + assert(path.hash == hash) + return _items + .firstIndex(where: { $0.key == key }) + .map { Index(_value: skippedBefore + $0) } } let bucket = path.currentBucket - if dataMap.contains(bucket) { - let offset = dataMap.offset(of: bucket) - let payload = self.getPayload(offset) - guard key == payload.key else { return nil } + if itemMap.contains(bucket) { + let offset = itemMap.offset(of: bucket) + let item = self.item(at: offset) + guard key == item.key else { return nil } return Index(_value: skippedBefore + _count(upTo: bucket)) } - if trieMap.contains(bucket) { - let offset = trieMap.offset(of: bucket) + if childMap.contains(bucket) { + let offset = childMap.offset(of: bucket) let skipped = skippedBefore + _count(upTo: bucket) return self - .getNode(offset) + .child(at: offset) .index(forKey: key, path.descend(), skipped) } return nil } + @inlinable final func updateOrUpdating( _ isUnique: Bool, _ item: Element, _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> _Node { + defer { _invariantCheck() } guard isRegularNode else { return _updateOrUpdatingCollision(isUnique, item, path, &effect) } let bucket = path.currentBucket - if dataMap.contains(bucket) { - let offset = dataMap.offset(of: bucket) - let item0 = self.getPayload(offset) + if itemMap.contains(bucket) { + let offset = itemMap.offset(of: bucket) + let item0 = self.item(at: 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]) + if hash0 == path.hash { + let newChild = _Node(collisions: [item0, item]) effect.setModified() - if self.count == 1 { return subNodeNew } - return _copyAndMigrateFromInlineToNode(isUnique, bucket, subNodeNew) + if self.count == 1 { return newChild } + return _copyAndMigrateFromInlineToNode(isUnique, bucket, newChild) } - let subNodeNew = _mergeTwoKeyValPairs( + let newChild = _mergeTwoKeyValPairs( item, path.descend(), item0, hash0) effect.setModified() - return _copyAndMigrateFromInlineToNode(isUnique, bucket, subNodeNew) + return _copyAndMigrateFromInlineToNode(isUnique, bucket, newChild) } - if trieMap.contains(bucket) { - let offset = trieMap.offset(of: bucket) - let isUniqueChild = self.isTrieNodeKnownUniquelyReferenced( - offset, isUnique) + if childMap.contains(bucket) { + let offset = childMap.offset(of: bucket) + let isUniqueChild = self.isChildUnique(at: offset, uniqueParent: isUnique) - let subNode = self.getNode(offset) + let oldChild = self.child(at: offset) - let subNodeNew = subNode.updateOrUpdating( + let newChild = oldChild.updateOrUpdating( isUniqueChild, item, path.descend(), &effect) - guard effect.modified, subNode !== subNodeNew else { + guard effect.modified, oldChild !== newChild else { if effect.previousValue == nil { count += 1 } - assert(self.invariant) return self } @@ -465,51 +533,63 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { isUnique, bucket, offset, - subNodeNew, - updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) + newChild, + updateCount: { $0 -= oldChild.count ; $0 += newChild.count }) } effect.setModified() return _copyAndInsertValue(isUnique, bucket, item) } + @inlinable @inline(never) final func _updateOrUpdatingCollision( - _ isStorageKnownUniquelyReferenced: Bool, + _ isUnique: Bool, _ item: Element, _ path: _HashPath, _ effect: inout _DictionaryEffect ) -> _Node { assert(isCollisionNode) - let content: [Element] = Array(self) - let hash = _HashValue(content.first!.key) - - guard path._hash == hash else { + let hash = _HashValue(_items.first!.key) + guard path.hash == hash else { effect.setModified() return _mergeKeyValPairAndCollisionNode(item, path, self, hash) } - if let offset = content.firstIndex(where: { item.key == $0.key }) { - var updatedContent: [Element] = [] - updatedContent.reserveCapacity(content.count + 1) - updatedContent.append(contentsOf: content[0.. ) -> _Node { + defer { _invariantCheck() } guard isRegularNode else { return _removeOrRemovingCollision(isUnique, key, path, &effect) @@ -517,81 +597,73 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { let bucket = path.currentBucket - 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 - } + if itemMap.contains(bucket) { + let offset = itemMap.offset(of: bucket) + let item0 = self.item(at: offset) + guard item0.key == key else { return self } effect.setModified(previousValue: item0.value) - if self.payloadArity == 2, self.nodeArity == 0 { + if self.itemCount == 2, self.childCount == 0 { 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) + var newItemMap = itemMap + newItemMap.remove(bucket) + let remaining = item(at: 1 - offset) + return _Node(itemMap: newItemMap, remaining) } // create potential new root: will a) become new root, or b) inlined // on another level - let remaining = getPayload(1 - offset) + let remaining = item(at: 1 - offset) return _Node(remaining, at: path.top().currentBucket) } if - self.payloadArity == 1, - self.nodeArity == 1, - self.getNode(0).isCollisionNode + self.itemCount == 1, + self.childCount == 1, + self.child(at: 0).isCollisionNode { // escalate hash-collision node - return getNode(0) + return child(at: 0) } return _copyAndRemoveValue(isUnique, bucket) } - if trieMap.contains(bucket) { - let offset = trieMap.offset(of: bucket) - let isChildUnique = self.isTrieNodeKnownUniquelyReferenced(offset, isUnique) + if childMap.contains(bucket) { + let offset = childMap.offset(of: bucket) + let isChildUnique = self.isChildUnique(at: offset, uniqueParent: isUnique) - let subNode = self.getNode(offset) + let oldChild = self.child(at: offset) - let subNodeNew = subNode.removeOrRemoving( + let newChild = oldChild.removeOrRemoving( isChildUnique, key, path.descend(), &effect) - guard effect.modified, subNode !== subNodeNew else { + guard effect.modified, oldChild !== newChild else { if effect.modified { count -= 1 } - assert(self.invariant) return self } - assert(subNodeNew.count > 0, "Sub-node must have at least one element.") - if subNodeNew.count == 1 { - if self.isCandiateForCompaction { + assert(newChild.count > 0, "Sub-node must have at least one element.") + if newChild.count == 1 { + if self.isCandidateForCompaction { // escalate singleton - return subNodeNew + return newChild } // inline singleton return _copyAndMigrateFromNodeToInline( - isUnique, bucket, subNodeNew.getPayload(0)) + isUnique, bucket, newChild.item(at: 0)) } - if subNodeNew.isCollisionNode, self.isCandiateForCompaction { + if newChild.isCollisionNode, self.isCandidateForCompaction { // escalate singleton - return subNodeNew + return newChild } // modify current node (set replacement node) return _copyAndSetTrieNode( - isUnique, - bucket, - offset, - subNodeNew, - updateCount: { $0 -= 1 }) + isUnique, bucket, offset, newChild, updateCount: { $0 -= 1 }) } - return self } + @inlinable @inline(never) final func _removeOrRemovingCollision( _ isUnique: Bool, @@ -601,26 +673,30 @@ extension PersistentDictionary._Node: _DictionaryNodeProtocol { ) -> _Node { assert(isCollisionNode) - let content: [Element] = Array(self) - - guard let index = content.firstIndex(where: { key == $0.key }) else { + guard let offset = _items.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 { + effect.setModified(previousValue: _items[offset].value) + + let count = itemCount + if count == 2 { // 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(_items[1 - offset], at: path.top().currentBucket) } - return _Node(/* hash, */ collisions: updatedContent) + + let dst = isUnique ? self : self.copy() + _rangeRemove(at: offset, from: dst.itemBaseAddress, count: count) + dst.header.itemMap = _Bitmap(bitPattern: count - 1) + dst.header.childMap = dst.header.itemMap + dst.count = count - 1 + return dst } } extension PersistentDictionary._Node { + @inlinable func item(position: Int) -> Element { assert(position >= 0 && position < count) let counts = self._counts @@ -635,35 +711,37 @@ extension PersistentDictionary._Node { } let bucket = _Bucket(UInt(bitPattern: b)) - if dataMap.contains(bucket) { + if itemMap.contains(bucket) { assert(skipped == position) - let offset = dataMap.offset(of: bucket) - return self.getPayload(offset) + let offset = itemMap.offset(of: bucket) + return self.item(at: offset) } - precondition(trieMap.contains(bucket)) + precondition(childMap.contains(bucket)) assert(skipped <= position && skipped + counts[b] > position) return self - .getNode(trieMap.offset(of: bucket)) + .child(at: childMap.offset(of: bucket)) .item(position: position - skipped) } } extension PersistentDictionary._Node { + @inlinable func _mergeTwoKeyValPairs( _ item0: Element, _ path0: _HashPath, _ item1: Element, _ hash1: _HashValue ) -> _Node { - let path1 = _HashPath(_hash: hash1, shift: path0._shift) + let path1 = _HashPath(hash: hash1, level: path0.level) return _mergeTwoKeyValPairs(item0, path0, item1, path1) } + @inlinable func _mergeTwoKeyValPairs( _ item0: Element, _ path0: _HashPath, _ item1: Element, _ path1: _HashPath ) -> _Node { - assert(path0._hash != path1._hash) - assert(path0._shift == path1._shift) + assert(path0.hash != path1.hash) + assert(path0.level == path1.level) let bucket0 = path0.currentBucket let bucket1 = path1.currentBucket @@ -675,7 +753,7 @@ extension PersistentDictionary._Node { item1, at: bucket1) } // recurse: identical prefixes, payload must be disambiguated deeper - // in the trie + // in the prefix tree let node = _mergeTwoKeyValPairs( item0, path0.descend(), item1, path1.descend()) @@ -683,20 +761,22 @@ extension PersistentDictionary._Node { return _Node(node, at: bucket0) } + @inlinable final func _mergeKeyValPairAndCollisionNode( _ item0: Element, _ path0: _HashPath, _ node1: _Node, _ hash1: _HashValue ) -> _Node { - let path1 = _HashPath(_hash: hash1, shift: path0._shift) + let path1 = _HashPath(hash: hash1, level: path0.level) return _mergeKeyValPairAndCollisionNode(item0, path0, node1, path1) } + @inlinable final func _mergeKeyValPairAndCollisionNode( _ item0: Element, _ path0: _HashPath, _ node1: _Node, _ path1: _HashPath ) -> _Node { - assert(path0._hash != path1._hash) - assert(path0._shift == path1._shift) + assert(path0.hash != path1.hash) + assert(path0.level == path1.level) let bucket0 = path0.currentBucket let bucket1 = path1.currentBucket @@ -706,7 +786,8 @@ extension PersistentDictionary._Node { return _Node(item0, at: bucket0, node1, at: bucket1) } - // recurse: identical prefixes, payload must be disambiguated deeper in the trie + // recurse: identical prefixes, payload must be disambiguated deeper in the + // prefix trie let node = _mergeKeyValPairAndCollisionNode( item0, path0.descend(), node1, path1.descend()) @@ -714,144 +795,120 @@ extension PersistentDictionary._Node { return _Node(node, at: bucket0) } + @inlinable final func _count(upTo bucket: _Bucket) -> Int { - let dataCount = dataMap.intersection(_Bitmap(upTo: bucket)).count - let trieCount = trieMap.intersection(_Bitmap(upTo: bucket)).count + let itemCount = itemMap.intersection(_Bitmap(upTo: bucket)).count + let childCount = childMap.intersection(_Bitmap(upTo: bucket)).count let buffer = UnsafeMutableBufferPointer( - start: trieBaseAddress, count: header.trieCount) - let children = buffer.prefix(upTo: trieCount).map { $0.count }.reduce(0, +) + start: childBaseAddress, count: header.childCount) + let children = buffer.prefix(upTo: childCount).map { $0.count }.reduce(0, +) - return dataCount + children + return itemCount + children } + @inlinable final var _counts: [Int] { var counts = Array(repeating: 0, count: _Bitmap.capacity) - for bucket in dataMap { + for bucket in itemMap { counts[Int(bitPattern: bucket.value)] = 1 } - for (bucket, trieNode) in zip(trieMap, _trieSlice) { + for (bucket, trieNode) in zip(childMap, _children) { counts[Int(bitPattern: bucket.value)] = trieNode.count } return counts } + @inlinable func _copyAndSetValue( _ isUnique: Bool, _ bucket: _Bucket, _ newValue: Value ) -> _Node { - let src: _Node = self - let dst: _Node - - if isUnique { - dst = src - } else { - dst = src.copy() - } - - let offset = dataMap.offset(of: bucket) - - dst.dataBaseAddress[offset].value = newValue - - assert(src.invariant) - assert(dst.invariant) + let dst = isUnique ? self : self.copy() + let offset = itemMap.offset(of: bucket) + dst.itemBaseAddress[offset].value = newValue + _invariantCheck() + dst._invariantCheck() return dst } - private func _copyAndSetTrieNode( + @inlinable + internal func _copyAndSetTrieNode( _ isUnique: Bool, _ bucket: _Bucket, _ offset: Int, _ newNode: _Node, updateCount: (inout Int) -> Void ) -> _Node { - let src: _Node = self - let dst: _Node - - if isUnique { - dst = src - } else { - dst = src.copy() - } - - dst.trieBaseAddress[offset] = newNode + let dst = isUnique ? self : self.copy() + dst.childBaseAddress[offset] = newNode - // update metadata: `dataMap, nodeMap, collMap` + // update metadata: `itemMap, nodeMap, collMap` updateCount(&dst.count) - assert(src.invariant) - assert(dst.invariant) + self._invariantCheck() + dst._invariantCheck() return dst } + @inlinable func _copyAndInsertValue( _ isUnique: Bool, _ bucket: _Bucket, _ item: Element ) -> _Node { - let src: _Node = self - let dst: _Node - let hasRoomForData = header.dataCount < dataCapacity + let hasRoomForItem = header.itemCount < itemCapacity - if isUnique && hasRoomForData { - dst = src - } else { - dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) - } + let dst = ( + isUnique && hasRoomForItem + ? self + : self.copy(itemCapacityGrowthFactor: hasRoomForItem ? 1 : 2)) + assert(dst.itemCount < dst.itemCapacity) - let offset = dst.dataMap.offset(of: bucket) + let offset = dst.itemMap.offset(of: bucket) _rangeInsert( - item, - at: offset, - into: dst.dataBaseAddress, - count: dst.header.dataCount) + item, at: offset, into: dst.itemBaseAddress, count: dst.itemCount) - dst.header.dataMap.insert(bucket) + dst.header.itemMap.insert(bucket) dst.count += 1 - assert(src.invariant) - assert(dst.invariant) + self._invariantCheck() + dst._invariantCheck() return dst } + @inlinable func _copyAndRemoveValue(_ isUnique: Bool, _ bucket: _Bucket) -> _Node { - assert(dataMap.contains(bucket)) - let src: _Node = self - let dst: _Node + assert(itemMap.contains(bucket)) + let dst = isUnique ? self : self.copy() - if isUnique { - dst = src - } else { - dst = src.copy() - } - - let dataOffset = dst.dataMap.offset(of: bucket) + let dataOffset = dst.itemMap.offset(of: bucket) _rangeRemove( - at: dataOffset, from: dst.dataBaseAddress, count: dst.header.dataCount) + at: dataOffset, from: dst.itemBaseAddress, count: dst.header.itemCount) - // update metadata: `dataMap ^ bitpos, nodeMap, collMap` - dst.header.dataMap.remove(bucket) + // update metadata: `itemMap ^ bitpos, nodeMap, collMap` + dst.header.itemMap.remove(bucket) dst.count -= 1 - assert(src.invariant) - assert(dst.invariant) + self._invariantCheck() + dst._invariantCheck() return dst } + @inlinable func _copyAndMigrateFromInlineToNode( _ isUnique: Bool, _ bucket: _Bucket, _ node: _Node ) -> _Node { - assert(dataMap.contains(bucket)) - let src: _Node = self + assert(itemMap.contains(bucket)) let dst: _Node - let hasRoomForTrie = header.trieCount < trieCapacity + let hasRoomForChild = header.childCount < childCapacity - if isUnique && hasRoomForTrie { - dst = src + if isUnique && hasRoomForChild { + dst = self } else { // TODO reconsider the details of the heuristic // @@ -860,94 +917,95 @@ extension PersistentDictionary._Node { // // Notes currently can grow to a maximum size of 48 (tuple and sub-node) // slots. - let tooMuchForData = Swift.max(header.dataCount * 2 - 1, 4) < dataCapacity + let itemsNeedShrinking = Swift.max(header.itemCount * 2 - 1, 4) < itemCapacity - dst = src.copy( - withDataCapacityShrinkFactor: tooMuchForData ? 2 : 1, - withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) + dst = self.copy( + itemCapacityShrinkFactor: itemsNeedShrinking ? 2 : 1, + childCapacityGrowthFactor: hasRoomForChild ? 1 : 2) } - let dataOffset = dst.dataMap.offset(of: bucket) + let itemOffset = dst.itemMap.offset(of: bucket) _rangeRemove( - at: dataOffset, from: dst.dataBaseAddress, count: dst.header.dataCount) + at: itemOffset, from: dst.itemBaseAddress, count: dst.header.itemCount) - let trieOffset = dst.trieMap.offset(of: bucket) + let childOffset = dst.childMap.offset(of: bucket) _rangeInsert( - node, at: trieOffset, - into: dst.trieBaseAddress, count: dst.header.trieCount) + node, at: childOffset, + into: dst.childBaseAddress, count: dst.header.childCount) - // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` - dst.header.dataMap.remove(bucket) - dst.header.trieMap.insert(bucket) + // update metadata: `itemMap ^ bitpos, nodeMap | bitpos, collMap` + dst.header.itemMap.remove(bucket) + dst.header.childMap.insert(bucket) dst.count += 1 // assuming that `node.count == 2` - assert(src.invariant) - assert(dst.invariant) + self._invariantCheck() + dst._invariantCheck() return dst } + @inlinable func _copyAndMigrateFromNodeToInline( _ isUnique: Bool, _ bucket: _Bucket, _ item: Element ) -> _Node { - assert(trieMap.contains(bucket)) - let src: _Node = self - let dst: _Node - - let hasRoomForData = header.dataCount < dataCapacity + assert(childMap.contains(bucket)) - if isUnique && hasRoomForData { - dst = src + let hasRoomForItem = header.itemCount < itemCapacity + let dst: _Node + if isUnique && hasRoomForItem { + dst = self } else { - dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) + dst = self.copy(itemCapacityGrowthFactor: hasRoomForItem ? 1 : 2) } - let nodeOffset = dst.trieMap.offset(of: bucket) + let childOffset = dst.childMap.offset(of: bucket) _rangeRemove( - at: nodeOffset, from: dst.trieBaseAddress, count: dst.header.trieCount) + at: childOffset, from: dst.childBaseAddress, count: dst.header.childCount) - let dataOffset = dst.dataMap.offset(of: bucket) + let itemOffset = dst.itemMap.offset(of: bucket) _rangeInsert( - item, at: dataOffset, - into: dst.dataBaseAddress, count: dst.header.dataCount) + item, at: itemOffset, + into: dst.itemBaseAddress, count: dst.header.itemCount) - // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` - dst.header.dataMap.insert(bucket) - dst.header.trieMap.remove(bucket) + // update metadata: `itemMap | bitpos, nodeMap ^ bitpos, collMap` + dst.header.itemMap.insert(bucket) + dst.header.childMap.remove(bucket) dst.count -= 1 // assuming that updated `node.count == 1` - assert(src.invariant) - assert(dst.invariant) + self._invariantCheck() + dst._invariantCheck() return dst } } // TODO: `Equatable` needs more test coverage, apart from hash-collision smoke test extension PersistentDictionary._Node: Equatable where Value: Equatable { + @inlinable static func == (lhs: _Node, rhs: _Node) -> Bool { if lhs.isCollisionNode && rhs.isCollisionNode { - let l = Dictionary(uniqueKeysWithValues: Array(lhs)) - let r = Dictionary(uniqueKeysWithValues: Array(rhs)) + let l = Dictionary( + uniqueKeysWithValues: lhs._items.lazy.map { ($0.key, $0.value) }) + let r = Dictionary( + uniqueKeysWithValues: rhs._items.lazy.map { ($0.key, $0.value) }) return l == r } - return ( - lhs === rhs || - lhs.header == rhs.header && - lhs.count == rhs.count && - deepContentEquality(lhs, rhs)) + if lhs === rhs { return true } + return deepContentEquality(lhs, rhs) } - private static func deepContentEquality(_ lhs: _Node, _ rhs: _Node) -> Bool { + @inlinable + internal static func deepContentEquality(_ lhs: _Node, _ rhs: _Node) -> Bool { guard lhs.header == rhs.header else { return false } + guard lhs.count == rhs.count else { return false } - for index in 0...Iterator - - public __consuming func makeIterator() -> Iterator { - Iterator(_root: self) - } -} diff --git a/Sources/PersistentCollections/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift index aa4950f3a..b16ede9da 100644 --- a/Sources/PersistentCollections/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary.swift @@ -10,18 +10,24 @@ //===----------------------------------------------------------------------===// public struct PersistentDictionary where Key: Hashable { - var rootNode: _Node + @usableFromInline + var _root: _Node - fileprivate init(_ rootNode: _Node) { - self.rootNode = rootNode + @inlinable + internal init(_root: _Node) { + self._root = _root } +} +extension PersistentDictionary { + @inlinable public init() { - self.init(_Node()) + self.init(_root: _Node()) } - public init(_ map: PersistentDictionary) { - self.init(map.rootNode) + @inlinable + public init(_ other: PersistentDictionary) { + self = other } @inlinable @@ -29,17 +35,11 @@ public struct PersistentDictionary where Key: Hashable { public init( uniqueKeysWithValues keysAndValues: S ) where S.Element == (Key, Value) { - var builder = Self() - var expectedCount = 0 - keysAndValues.forEach { key, value in - builder.updateValue(value, forKey: key) - expectedCount += 1 - - guard expectedCount == builder.count else { - preconditionFailure("Duplicate key: '\(key)'") - } + self.init() + for (key, value) in keysAndValues { + let unique = updateValue(value, forKey: key) == nil + precondition(unique, "Duplicate key: '\(key)'") } - self.init(builder) } @inlinable @@ -51,22 +51,7 @@ public struct PersistentDictionary where Key: Hashable { self.init(uniqueKeysWithValues: zip(keys, values)) } - /// - /// Inspecting a Dictionary - /// - - public var isEmpty: Bool { rootNode.count == 0 } - - public var count: Int { rootNode.count } - - public var underestimatedCount: Int { rootNode.count } - - public var capacity: Int { rootNode.count } - - /// - /// Accessing Keys and Values - /// - + @inlinable public subscript(key: Key) -> Value? { get { return _get(key) @@ -80,6 +65,7 @@ public struct PersistentDictionary where Key: Hashable { } } + @inlinable public subscript( key: Key, default defaultValue: @autoclosure () -> Value @@ -92,24 +78,33 @@ public struct PersistentDictionary where Key: Hashable { } } + @inlinable public func contains(_ key: Key) -> Bool { - rootNode.containsKey(key, _HashPath(key)) + _root.containsKey(key, _HashPath(key)) } + @inlinable func _get(_ key: Key) -> Value? { - rootNode.get(key, _HashPath(key)) + _root.get(key, _HashPath(key)) + } + + /// Returns the index for the given key. + @inlinable + public func index(forKey key: Key) -> Index? { + _root.index(forKey: key, _HashPath(key), 0) } + @inlinable @discardableResult public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { - let isUnique = isKnownUniquelyReferenced(&self.rootNode) + let isUnique = isKnownUniquelyReferenced(&self._root) var effect = _DictionaryEffect() - let newRootNode = rootNode.updateOrUpdating( + let newRoot = _root.updateOrUpdating( isUnique, (key, value), _HashPath(key), &effect) if effect.modified { - self.rootNode = newRootNode + self._root = newRoot } // Note, always tracking discardable result negatively impacts batch use cases @@ -117,25 +112,27 @@ public struct PersistentDictionary where Key: Hashable { } // fluid/immutable API + @inlinable public func updatingValue(_ value: Value, forKey key: Key) -> Self { var effect = _DictionaryEffect() - let newRootNode = rootNode.updateOrUpdating( + let newRoot = _root.updateOrUpdating( false, (key, value), _HashPath(key), &effect) guard effect.modified else { return self } - return Self(newRootNode) + return Self(_root: newRoot) } + @inlinable @discardableResult public mutating func removeValue(forKey key: Key) -> Value? { - let isUnique = isKnownUniquelyReferenced(&self.rootNode) + let isUnique = isKnownUniquelyReferenced(&self._root) var effect = _DictionaryEffect() - let newRootNode = rootNode.removeOrRemoving( + let newRoot = _root.removeOrRemoving( isUnique, key, _HashPath(key), &effect) if effect.modified { - self.rootNode = newRootNode + self._root = newRoot } // Note, always tracking discardable result negatively impacts batch use cases @@ -143,14 +140,16 @@ public struct PersistentDictionary where Key: Hashable { } // fluid/immutable API + @inlinable public func removingValue(forKey key: Key) -> Self { var effect = _DictionaryEffect() - let newRootNode = rootNode.removeOrRemoving( + let newRoot = _root.removeOrRemoving( false, key, _HashPath(key), &effect) if effect.modified { - return Self(newRootNode) - } else { return self } + return Self(_root: newRoot) + } + return self } } diff --git a/Sources/PersistentCollections/Unused/PersistentDictionary.ReverseIterator.swift b/Sources/PersistentCollections/Unused/PersistentDictionary.ReverseIterator.swift index 1fe617379..3557a66e3 100644 --- a/Sources/PersistentCollections/Unused/PersistentDictionary.ReverseIterator.swift +++ b/Sources/PersistentCollections/Unused/PersistentDictionary.ReverseIterator.swift @@ -16,8 +16,8 @@ extension PersistentDictionary { public struct ReverseIterator { private var baseIterator: _BaseReverseIterator<_Node> - init(rootNode: _Node) { - self.baseIterator = _BaseReverseIterator(rootNode: rootNode) + init(root: _Node) { + self.baseIterator = _BaseReverseIterator(root: root) } } } @@ -28,7 +28,7 @@ extension PersistentDictionary.ReverseIterator: IteratorProtocol { let payload = baseIterator .currentValueNode! - .getPayload(baseIterator.currentValueCursor) + .item(at: baseIterator.currentValueCursor) baseIterator.currentValueCursor -= 1 return payload diff --git a/Sources/PersistentCollections/Unused/_BaseIterator.swift b/Sources/PersistentCollections/Unused/_BaseIterator.swift index e622010b7..a862cdd7b 100644 --- a/Sources/PersistentCollections/Unused/_BaseIterator.swift +++ b/Sources/PersistentCollections/Unused/_BaseIterator.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if false -/// Base class for fixed-stack iterators that traverse a hash-trie. The iterator +/// Base class for fixed-stack iterators that traverse a hash tree. The iterator /// performs a depth-first pre-order traversal, which yields first all payload /// elements of the current node before traversing sub-nodes (left to right). internal struct _BaseIterator { @@ -23,15 +23,15 @@ internal struct _BaseIterator { Array(repeating: 0, count: _maxDepth * 2)) private var nodes: [T?] = Array(repeating: nil, count: _maxDepth) - init(rootNode: T) { - if rootNode.hasNodes { pushNode(rootNode) } - if rootNode.hasPayload { setupPayloadNode(rootNode) } + init(root: T) { + if root.hasChildren { pushNode(root) } + if root.hasItems { setupPayloadNode(root) } } private mutating func setupPayloadNode(_ node: T) { currentValueNode = node currentValueCursor = 0 - currentValueLength = node.payloadArity + currentValueLength = node.itemCount } private mutating func pushNode(_ node: T) { @@ -42,7 +42,7 @@ internal struct _BaseIterator { nodes[currentStackLevel] = node nodeCursorsAndLengths[cursorIndex] = 0 - nodeCursorsAndLengths[lengthIndex] = node.nodeArity + nodeCursorsAndLengths[lengthIndex] = node.childCount } private mutating func popNode() { @@ -65,10 +65,10 @@ internal struct _BaseIterator { nodeCursorsAndLengths[cursorIndex] += 1 let currentNode = nodes[currentStackLevel]! - let nextNode = currentNode.getNode(nodeCursor) + let nextNode = currentNode.child(at: nodeCursor) - if nextNode.hasNodes { pushNode(nextNode) } - if nextNode.hasPayload { setupPayloadNode(nextNode) ; return true } + if nextNode.hasChildren { pushNode(nextNode) } + if nextNode.hasItems { setupPayloadNode(nextNode) ; return true } } else { popNode() } diff --git a/Sources/PersistentCollections/Unused/_BaseReverseIterator.swift b/Sources/PersistentCollections/Unused/_BaseReverseIterator.swift index b5f22c6d0..311b0d85e 100644 --- a/Sources/PersistentCollections/Unused/_BaseReverseIterator.swift +++ b/Sources/PersistentCollections/Unused/_BaseReverseIterator.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if false -/// Base class for fixed-stack iterators that traverse a hash-trie in reverse +/// Base class for fixed-stack iterators that traverse a hash tree in reverse /// order. The base iterator performs a depth-first post-order traversal, /// traversing sub-nodes (right to left). internal struct _BaseReverseIterator { @@ -21,21 +21,21 @@ internal struct _BaseReverseIterator { private var nodeIndex: [Int] = Array(repeating: 0, count: _maxDepth + 1) private var nodeStack: [T?] = Array(repeating: nil, count: _maxDepth + 1) - init(rootNode: T) { - pushNode(rootNode) + init(root: T) { + pushNode(root) searchNextValueNode() } private mutating func setupPayloadNode(_ node: T) { currentValueNode = node - currentValueCursor = node.payloadArity - 1 + currentValueCursor = node.itemCount - 1 } private mutating func pushNode(_ node: T) { currentStackLevel = currentStackLevel + 1 nodeStack[currentStackLevel] = node - nodeIndex[currentStackLevel] = node.nodeArity - 1 + nodeIndex[currentStackLevel] = node.childCount - 1 } private mutating func popNode() { @@ -54,13 +54,13 @@ internal struct _BaseReverseIterator { if nodeCursor >= 0 { let currentNode = nodeStack[currentStackLevel]! - let nextNode = currentNode.getNode(nodeCursor) + let nextNode = currentNode.child(at: nodeCursor) pushNode(nextNode) } else { let currNode = nodeStack[currentStackLevel]! popNode() - if currNode.hasPayload { + if currNode.hasItems { setupPayloadNode(currNode) return true } diff --git a/Sources/PersistentCollections/_Bitmap.swift b/Sources/PersistentCollections/_Bitmap.swift index a2d5f6f58..b2d4b581c 100644 --- a/Sources/PersistentCollections/_Bitmap.swift +++ b/Sources/PersistentCollections/_Bitmap.swift @@ -10,39 +10,39 @@ //===----------------------------------------------------------------------===// /// A set of `_Bucket` values, represented by a 32-bit wide bitset. +@usableFromInline +@frozen internal struct _Bitmap { + @usableFromInline internal typealias Value = UInt32 + @usableFromInline internal var _value: Value - @inline(__always) + @inlinable @inline(__always) init(_value: Value) { self._value = _value } - @inline(__always) + @inlinable @inline(__always) init(bitPattern: Int) { self._value = Value(bitPattern) } - @inline(__always) - internal init() { - _value = 0 - } - - @inline(__always) + @inlinable @inline(__always) internal init(_ bucket: _Bucket) { assert(bucket.value < Self.capacity) _value = (1 &<< bucket.value) } - @inline(__always) + @inlinable @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) } + @inlinable internal init(upTo bucket: _Bucket) { assert(bucket.value < Self.capacity) _value = (1 &<< bucket.value) &- 1 @@ -50,90 +50,95 @@ internal struct _Bitmap { } extension _Bitmap: Equatable { - @inline(__always) + @inlinable @inline(__always) internal static func ==(left: Self, right: Self) -> Bool { left._value == right._value } } extension _Bitmap { - @inline(__always) - internal static var empty: Self { .init() } + @inlinable @inline(__always) + internal static var empty: Self { .init(_value: 0) } - @inline(__always) + @inlinable @inline(__always) internal static var capacity: Int { Value.bitWidth } - @inline(__always) + @inlinable @inline(__always) internal var count: Int { _value.nonzeroBitCount } - @inline(__always) + @inlinable @inline(__always) internal var capacity: Int { Value.bitWidth } - @inline(__always) + @inlinable @inline(__always) internal var isEmpty: Bool { _value == 0 } } extension _Bitmap { - @inline(__always) + @inlinable @inline(__always) internal func contains(_ bucket: _Bucket) -> Bool { assert(bucket.value < capacity) return _value & (1 &<< bucket.value) != 0 } - @inline(__always) + @inlinable @inline(__always) internal mutating func insert(_ bucket: _Bucket) { assert(bucket.value < capacity) _value |= (1 &<< bucket.value) } - @inline(__always) + @inlinable @inline(__always) internal mutating func remove(_ bucket: _Bucket) { assert(bucket.value < capacity) _value &= ~(1 &<< bucket.value) } - @inline(__always) + @inlinable @inline(__always) internal func offset(of bucket: _Bucket) -> Int { _value._rank(ofBit: bucket.value) } - @inline(__always) + @inlinable @inline(__always) internal func bucket(at offset: Int) -> _Bucket { _Bucket(_value._bit(ranked: offset)!) } } extension _Bitmap { - @inline(__always) + @inlinable @inline(__always) internal func isDisjoint(with other: Self) -> Bool { _value & other._value != 0 } - @inline(__always) + @inlinable @inline(__always) internal func union(_ other: Self) -> Self { Self(_value: _value | other._value) } - @inline(__always) + @inlinable @inline(__always) internal func intersection(_ other: Self) -> Self { Self(_value: _value & other._value) } - @inline(__always) + @inlinable @inline(__always) internal func symmetricDifference(_ other: Self) -> Self { Self(_value: _value & other._value) } - @inline(__always) + @inlinable @inline(__always) internal func subtracting(_ other: Self) -> Self { Self(_value: _value & ~other._value) } } extension _Bitmap: Sequence, IteratorProtocol { - var underestimatedCount: Int { count } + @usableFromInline + internal typealias Element = _Bucket - func makeIterator() -> _Bitmap { self } + @inlinable + internal var underestimatedCount: Int { count } + + @inlinable + internal func makeIterator() -> _Bitmap { self } /// Return the index of the lowest set bit in this word, /// and also destructively clear it. diff --git a/Sources/PersistentCollections/_Bucket.swift b/Sources/PersistentCollections/_Bucket.swift index b072e0304..4891ee914 100644 --- a/Sources/PersistentCollections/_Bucket.swift +++ b/Sources/PersistentCollections/_Bucket.swift @@ -11,24 +11,34 @@ /// Identifies an entry in the hash table inside a node. /// (Internally, a number between 0 and 31.) +@usableFromInline +@frozen internal struct _Bucket { - var value: UInt + @usableFromInline + internal var value: UInt - init(_ value: UInt) { self.value = value } + @inlinable @inline(__always) + internal init(_ value: UInt) { self.value = value } + @inlinable @inline(__always) static var bitWidth: Int { _Bitmap.capacity.trailingZeroBitCount } + + @inlinable @inline(__always) static var bitMask: UInt { UInt(bitPattern: _Bitmap.capacity) &- 1 } + + @inlinable @inline(__always) + static var invalid: _Bucket { _Bucket(.max) } } extension _Bucket: Equatable { - @inline(__always) + @inlinable @inline(__always) internal static func ==(left: Self, right: Self) -> Bool { left.value == right.value } } extension _Bucket: Comparable { - @inline(__always) + @inlinable @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 c63b369dd..b9b2c1033 100644 --- a/Sources/PersistentCollections/_Common.swift +++ b/Sources/PersistentCollections/_Common.swift @@ -9,27 +9,27 @@ // //===----------------------------------------------------------------------===// +@inlinable @inline(__always) internal func _computeHash(_ value: T) -> Int { value.hashValue } -@inline(__always) +@inlinable @inline(__always) internal var _bitPartitionSize: Int { 5 } -@inline(__always) +@inlinable @inline(__always) internal var _bitPartitionMask: Int { (1 << _bitPartitionSize) - 1 } -@inline(__always) +@inlinable @inline(__always) internal var _hashCodeLength: Int { Int.bitWidth } -@inline(__always) +@inlinable @inline(__always) internal var _maxDepth: Int { (_hashCodeLength + _bitPartitionSize - 1) / _bitPartitionSize } // NEW -@inlinable -@inline(__always) +@inlinable @inline(__always) internal func _rangeInsert( _ element: T, at index: Int, @@ -45,8 +45,7 @@ internal func _rangeInsert( } // NEW -@inlinable -@inline(__always) +@inlinable @inline(__always) internal func _rangeRemove( at index: Int, from baseAddress: UnsafeMutablePointer, diff --git a/Sources/PersistentCollections/_DictionaryEffect.swift b/Sources/PersistentCollections/_DictionaryEffect.swift index 31fcd7069..93b0d0455 100644 --- a/Sources/PersistentCollections/_DictionaryEffect.swift +++ b/Sources/PersistentCollections/_DictionaryEffect.swift @@ -9,19 +9,30 @@ // //===----------------------------------------------------------------------===// +@usableFromInline +@frozen struct _DictionaryEffect { + @usableFromInline var modified: Bool = false + + @usableFromInline var previousValue: Value? - + + @inlinable + init() {} + + @inlinable mutating func setModified() { self.modified = true } + @inlinable mutating func setModified(previousValue: Value) { self.modified = true self.previousValue = previousValue } + @inlinable mutating func setReplacedValue(previousValue: Value) { self.modified = true self.previousValue = previousValue diff --git a/Sources/PersistentCollections/_HashPath.swift b/Sources/PersistentCollections/_HashPath.swift index 77847ef64..217b8820b 100644 --- a/Sources/PersistentCollections/_HashPath.swift +++ b/Sources/PersistentCollections/_HashPath.swift @@ -11,42 +11,59 @@ /// A structure for slicing up a hash value into a series of bucket values, /// representing a path inside the prefix tree. +@usableFromInline +@frozen internal struct _HashPath { - internal var _hash: _HashValue - internal var _shift: UInt + @usableFromInline + internal var hash: _HashValue - internal init(_hash: _HashValue, shift: UInt) { - self._hash = _hash - self._shift = shift + @usableFromInline + internal var level: _Level + + @inlinable + internal init(hash: _HashValue, level: _Level) { + self.hash = hash + self.level = level } - internal init(_ key: Key) { - _hash = _HashValue(key) - _shift = 0 + @inlinable + internal init(hash: _HashValue, shift: UInt) { + self.init(hash: hash, level: _Level(shift: shift)) } - internal var currentBucket: _Bucket { - precondition(_shift < UInt.bitWidth, "Ran out of hash bits") - return _Bucket((_hash.value &>> _shift) & _Bucket.bitMask) + @inlinable + internal init(_ key: Key, level: _Level = .top) { + self.init(hash: _HashValue(key), level: level) + } + + @inlinable + internal init(_ key: Key, shift: UInt) { + self.init(hash: _HashValue(key), level: _Level(shift: shift)) } - internal var isAtRoot: Bool { _shift == 0 } + @inlinable + internal var isAtRoot: Bool { level.isAtRoot } + + @inlinable + internal var currentBucket: _Bucket { + hash[level] + } + @inlinable 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) + _HashPath(hash: hash, level: level.descend()) } + @inlinable internal func ascend() -> _HashPath { - precondition(_shift >= _Bucket.bitWidth) - let s = _shift &- UInt(bitPattern: _Bucket.bitWidth) - return _HashPath(_hash: _hash, shift: s) + _HashPath(hash: hash, level: level.ascend()) } + @inlinable internal func top() -> _HashPath { var result = self - result._shift = 0 + result.level = .top return result } } diff --git a/Sources/PersistentCollections/_HashValue.swift b/Sources/PersistentCollections/_HashValue.swift index 69b4730bd..7a0c3838a 100644 --- a/Sources/PersistentCollections/_HashValue.swift +++ b/Sources/PersistentCollections/_HashValue.swift @@ -10,9 +10,13 @@ //===----------------------------------------------------------------------===// /// An abstract representation of a hash value. +@usableFromInline +@frozen internal struct _HashValue { + @usableFromInline internal var value: UInt + @inlinable internal init(_ key: Key) { let hashValue = key._rawHashValue(seed: 0) self.value = UInt(bitPattern: hashValue) @@ -20,8 +24,60 @@ internal struct _HashValue { } extension _HashValue: Equatable { - @inline(__always) + @inlinable @inline(__always) internal static func ==(left: Self, right: Self) -> Bool { left.value == right.value } } + +extension _HashValue { + @inlinable + internal subscript(_ level: _Level) -> _Bucket { + assert(!level.isAtBottom) + return _Bucket((value &>> level.shift) & _Bucket.bitMask) + } +} + +@usableFromInline +@frozen +internal struct _Level { + @usableFromInline + internal var shift: UInt + + @inlinable + init(shift: UInt) { + self.shift = shift + } +} + +extension _Level { + @inlinable + internal static var top: _Level { + _Level(shift: 0) + } + + @inlinable + internal var isAtRoot: Bool { shift == 0 } + + @inlinable + internal var isAtBottom: Bool { shift >= UInt.bitWidth } + + @inlinable + internal func descend() -> _Level { + // FIXME: Consider returning nil when we run out of bits + _Level(shift: shift &+ UInt(bitPattern: _Bucket.bitWidth)) + } + + @inlinable + internal func ascend() -> _Level { + assert(!isAtRoot) + return _Level(shift: shift &+ UInt(bitPattern: _Bucket.bitWidth)) + } +} + +extension _Level: Equatable { + @inlinable + internal static func ==(left: Self, right: Self) -> Bool { + left.shift == right.shift + } +} diff --git a/Sources/PersistentCollections/_NodeProtocol.swift b/Sources/PersistentCollections/_NodeProtocol.swift index e0e276f49..1b9608ecc 100644 --- a/Sources/PersistentCollections/_NodeProtocol.swift +++ b/Sources/PersistentCollections/_NodeProtocol.swift @@ -12,17 +12,17 @@ protocol _NodeProtocol: AnyObject { associatedtype Element - var hasNodes: Bool { get } + var hasChildren: Bool { get } - var nodeArity: Int { get } + var childCount: Int { get } - func getNode(_ index: Int) -> Self + func child(at offset: Int) -> Self - var hasPayload: Bool { get } + var hasItems: Bool { get } - var payloadArity: Int { get } + var itemCount: Int { get } - func getPayload(_ index: Int) -> Element + func item(at offset: Int) -> Element var count: Int { get } } diff --git a/Sources/_CollectionsUtilities/Descriptions.swift b/Sources/_CollectionsUtilities/Descriptions.swift new file mode 100644 index 000000000..961b32d5e --- /dev/null +++ b/Sources/_CollectionsUtilities/Descriptions.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@inlinable +public func _arrayDescription( + for elements: C, + debug: Bool = false, + typeName: String? = nil +) -> String { + var result = "" + if let typeName = typeName { + result += "\(typeName)(" + } + result += "[" + var first = true + for item in elements { + if first { + first = false + } else { + result += ", " + } + if debug { + debugPrint(item, terminator: "", to: &result) + } else { + print(item, terminator: "", to: &result) + } + } + result += "]" + if typeName != nil { result += ")" } + return result +} + +@inlinable +public func _dictionaryDescription( + for elements: C, + debug: Bool = false, + typeName: String? = nil +) -> String where C.Element == (key: Key, value: Value) { + var result = "" + if let typeName = typeName { + result += "\(typeName)(" + } + + if elements.isEmpty { + result += "[:]" + } else { + result += "[" + var first = true + for (key, value) in elements { + if first { + first = false + } else { + result += ", " + } + if debug { + debugPrint(key, terminator: "", to: &result) + result += ": " + debugPrint(value, terminator: "", to: &result) + } else { + result += "\(key): \(value)" + } + } + result += "]" + } + + if typeName != nil { + result += ")" + } + return result +} diff --git a/Sources/PersistentCollections/Utilities.swift b/Sources/_CollectionsUtilities/Integer tricks.swift similarity index 76% rename from Sources/PersistentCollections/Utilities.swift rename to Sources/_CollectionsUtilities/Integer tricks.swift index 0170a7a48..02fb81862 100644 --- a/Sources/PersistentCollections/Utilities.swift +++ b/Sources/_CollectionsUtilities/Integer tricks.swift @@ -9,15 +9,26 @@ // //===----------------------------------------------------------------------===// +extension FixedWidthInteger { + /// Round up `self` to the nearest power of two, assuming it's representable. + /// Returns 0 if `self` isn't positive. + @_effects(readnone) + public func _roundUpToPowerOfTwo() -> Self { + guard self > 0 else { return 0 } + let l = Self.bitWidth - (self &- 1).leadingZeroBitCount + return 1 << l + } +} + extension UInt32 { @inlinable @inline(__always) - var _nonzeroBitCount: Self { + internal var _nonzeroBitCount: Self { Self(truncatingIfNeeded: nonzeroBitCount) } @inlinable @inline(__always) @_effects(readnone) - func _rank(ofBit bit: UInt) -> Int { + public func _rank(ofBit bit: UInt) -> Int { assert(bit < Self.bitWidth) let mask: Self = (1 &<< bit) &- 1 return (self & mask).nonzeroBitCount @@ -26,8 +37,7 @@ extension UInt32 { // 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? { + public func _bit(ranked n: Int) -> UInt? { assert(n >= 0 && n < Self.bitWidth) var shift: Self = 0 var n: Self = UInt32(truncatingIfNeeded: n) diff --git a/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer extensions.swift b/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer extensions.swift new file mode 100644 index 000000000..891a8178e --- /dev/null +++ b/Sources/_CollectionsUtilities/UnsafeMutableBufferPointer extensions.swift @@ -0,0 +1,213 @@ +//===----------------------------------------------------------------------===// +// +// 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 UnsafeMutableBufferPointer { + /// Deinitializes every instance in this buffer. + /// + /// The region of memory underlying this buffer must be fully initialized. + /// After calling `deinitialize(count:)`, the memory is uninitialized, + /// but still bound to the `Element` type. + /// + /// - Note: All buffer elements must already be initialized. + /// + /// - Returns: A raw buffer to the same range of memory as this buffer. + /// The range of memory is still bound to `Element`. + @discardableResult + @inlinable + public func deinitialize() -> UnsafeMutableRawBufferPointer { + guard let start = baseAddress else { return .init(start: nil, count: 0) } + start.deinitialize(count: count) + return .init(start: UnsafeMutableRawPointer(start), + count: count * MemoryLayout.stride) + } +} + +extension UnsafeMutableBufferPointer { + /// Initializes the buffer's memory with + /// every element of the source. + /// + /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, + /// the memory referenced by the buffer must be uninitialized, + /// or the `Element` type must be a trivial type. After the call, + /// the memory referenced by the buffer up to, but not including, + /// the returned index is initialized. + /// The buffer must reference enough memory to accommodate + /// `source.count` elements. + /// + /// The returned index is the position of the next uninitialized element + /// in the buffer, one past the index of the last element written. + /// If `source` contains no elements, the returned index is equal to the + /// buffer's `startIndex`. If `source` contains as many elements as the buffer + /// can hold, the returned index is equal to the buffer's `endIndex`. + /// + /// - Precondition: `self.count` >= `source.count` + /// + /// - Note: The memory regions referenced by `source` and this buffer + /// must not overlap. + /// + /// - Parameter source: A collection of elements to be used to + /// initialize the buffer's storage. + /// - Returns: The index one past the last element of the buffer initialized + /// by this function. + @inlinable + public func initialize( + fromContentsOf source: C + ) -> Index + where C.Element == Element { + let count = source.withContiguousStorageIfAvailable { + guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { + return 0 + } + precondition( + $0.count <= self.count, + "buffer cannot contain every element from source." + ) + baseAddress?.initialize(from: sourceAddress, count: $0.count) + return $0.count + } + if let count { + return startIndex.advanced(by: count) + } + + var (iterator, copied) = source._copyContents(initializing: self) + precondition( + iterator.next() == nil, + "buffer cannot contain every element from source." + ) + return startIndex.advanced(by: copied) + } + + /// Moves every element of an initialized source buffer into the + /// uninitialized memory referenced by this buffer, leaving the source memory + /// uninitialized and this buffer's memory initialized. + /// + /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, + /// the memory it references must be uninitialized, + /// or its `Element` type must be a trivial type. After the call, + /// the memory referenced by the buffer up to, but not including, + /// the returned index is initialized. The memory referenced by + /// `source` is uninitialized after the function returns. + /// The buffer must reference enough memory to accommodate + /// `source.count` elements. + /// + /// The returned index is the position of the next uninitialized element + /// in the buffer, one past the index of the last element written. + /// If `source` contains no elements, the returned index is equal to the + /// buffer's `startIndex`. If `source` contains as many elements as the buffer + /// can hold, the returned index is equal to the buffer's `endIndex`. + /// + /// - Precondition: `self.count` >= `source.count` + /// + /// - Note: The memory regions referenced by `source` and this buffer + /// may overlap. + /// + /// - Parameter source: A buffer containing the values to copy. The memory + /// region underlying `source` must be initialized. + /// - Returns: The index one past the last element of the buffer initialized + /// by this function. + @inlinable + @_alwaysEmitIntoClient + public func moveInitialize(fromContentsOf source: Self) -> Index { + guard let sourceAddress = source.baseAddress, !source.isEmpty else { + return startIndex + } + precondition( + source.count <= self.count, + "buffer cannot contain every element from source." + ) + baseAddress?.moveInitialize(from: sourceAddress, count: source.count) + return startIndex.advanced(by: source.count) + } + + /// Moves every element of an initialized source buffer into the + /// uninitialized memory referenced by this buffer, leaving the source memory + /// uninitialized and this buffer's memory initialized. + /// + /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, + /// the memory it references must be uninitialized, + /// or its `Element` type must be a trivial type. After the call, + /// the memory referenced by the buffer up to, but not including, + /// the returned index is initialized. The memory referenced by + /// `source` is uninitialized after the function returns. + /// The buffer must reference enough memory to accommodate + /// `source.count` elements. + /// + /// The returned index is the position of the next uninitialized element + /// in the buffer, one past the index of the last element written. + /// If `source` contains no elements, the returned index is equal to the + /// buffer's `startIndex`. If `source` contains as many elements as the buffer + /// can hold, the returned index is equal to the buffer's `endIndex`. + /// + /// - Precondition: `self.count` >= `source.count` + /// + /// - Note: The memory regions referenced by `source` and this buffer + /// may overlap. + /// + /// - Parameter source: A buffer containing the values to copy. The memory + /// region underlying `source` must be initialized. + /// - Returns: The index one past the last element of the buffer initialized + /// by this function. + @inlinable + @_alwaysEmitIntoClient + public func moveInitialize(fromContentsOf source: Slice) -> Index { + return moveInitialize(fromContentsOf: Self(rebasing: source)) + } + + /// Initializes the element at `index` to the given value. + /// + /// The memory underlying the destination element must be uninitialized, + /// or `Element` must be a trivial type. After a call to `initialize(to:)`, + /// the memory underlying this element of the buffer is initialized. + /// + /// - Parameters: + /// - value: The value used to initialize the buffer element's memory. + /// - index: The index of the element to initialize + @inlinable + @_alwaysEmitIntoClient + public func initializeElement(at index: Index, to value: Element) { + assert(startIndex <= index && index < endIndex) + let p = baseAddress.unsafelyUnwrapped.advanced(by: index) + p.initialize(to: value) + } + + /// Retrieves and returns the element at `index`, + /// leaving that element's underlying memory uninitialized. + /// + /// The memory underlying the element at `index` must be initialized. + /// After calling `moveElement(from:)`, the memory underlying this element + /// of the buffer is uninitialized, and still bound to type `Element`. + /// + /// - Parameters: + /// - index: The index of the buffer element to retrieve and deinitialize. + /// - Returns: The instance referenced by this index in this buffer. + @inlinable + @_alwaysEmitIntoClient + public func moveElement(from index: Index) -> Element { + assert(startIndex <= index && index < endIndex) + return baseAddress.unsafelyUnwrapped.advanced(by: index).move() + } + + /// Deinitializes the memory underlying the element at `index`. + /// + /// The memory underlying the element at `index` must be initialized. + /// After calling `deinitializeElement()`, the memory underlying this element + /// of the buffer is uninitialized, and still bound to type `Element`. + /// + /// - Parameters: + /// - index: The index of the buffer element to deinitialize. + @inlinable + @_alwaysEmitIntoClient + public func deinitializeElement(at index: Index) { + assert(startIndex <= index && index < endIndex) + let p = baseAddress.unsafelyUnwrapped.advanced(by: index) + p.deinitialize(count: 1) + } +} diff --git a/Sources/_CollectionsUtilities/UnsafeRawPointer extensions.swift b/Sources/_CollectionsUtilities/UnsafeRawPointer extensions.swift new file mode 100644 index 000000000..a972d1a22 --- /dev/null +++ b/Sources/_CollectionsUtilities/UnsafeRawPointer extensions.swift @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// 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 compiler(<5.7) // SE-0334 +extension UnsafeRawPointer { + /// Obtain the next pointer properly aligned to store a value of type `T`. + /// + /// If `self` is properly aligned for accessing `T`, + /// this function returns `self`. + /// + /// - Parameters: + /// - type: the type to be stored at the returned address. + /// - Returns: a pointer properly aligned to store a value of type `T`. + @inlinable + @_alwaysEmitIntoClient + public func alignedUp(for type: T.Type) -> Self { + let mask = UInt(MemoryLayout.alignment) &- 1 + let bits = (UInt(bitPattern: self) &+ mask) & ~mask + return Self(bitPattern: bits)! + } + + /// Obtain the preceding pointer properly aligned to store a value of type `T`. + /// + /// If `self` is properly aligned for accessing `T`, + /// this function returns `self`. + /// + /// - Parameters: + /// - type: the type to be stored at the returned address. + /// - Returns: a pointer properly aligned to store a value of type `T`. + @inlinable + @_alwaysEmitIntoClient + public func alignedDown(for type: T.Type) -> Self { + let mask = UInt(MemoryLayout.alignment) &- 1 + let bits = UInt(bitPattern: self) & ~mask + return Self(bitPattern: bits)! + } +} + +extension UnsafeMutableRawPointer { + /// Obtain the next pointer properly aligned to store a value of type `T`. + /// + /// If `self` is properly aligned for accessing `T`, + /// this function returns `self`. + /// + /// - Parameters: + /// - type: the type to be stored at the returned address. + /// - Returns: a pointer properly aligned to store a value of type `T`. + @inlinable + @_alwaysEmitIntoClient + public func alignedUp(for type: T.Type) -> Self { + let mask = UInt(MemoryLayout.alignment) &- 1 + let bits = (UInt(bitPattern: self) &+ mask) & ~mask + return Self(bitPattern: bits)! + } + + /// Obtain the preceding pointer properly aligned to store a value of type `T`. + /// + /// If `self` is properly aligned for accessing `T`, + /// this function returns `self`. + /// + /// - Parameters: + /// - type: the type to be stored at the returned address. + /// - Returns: a pointer properly aligned to store a value of type `T`. + @inlinable + @_alwaysEmitIntoClient + public func alignedDown(for type: T.Type) -> Self { + let mask = UInt(MemoryLayout.alignment) &- 1 + let bits = UInt(bitPattern: self) & ~mask + return Self(bitPattern: bits)! + } +} +#endif diff --git a/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift index 286ea5986..922314501 100644 --- a/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift @@ -195,9 +195,9 @@ final class CapsuleSmokeTests: CollectionTestCase { CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769) ]) - expectEqual(map.rootNode.count, 3) - expectEqual(map.rootNode.reduce(0, { count, _ in count + 1 }), 3) - expectTrue(map.rootNode.invariant) + expectEqual(map._root.count, 3) + expectEqual(map.reduce(0, { count, _ in count + 1 }), 3) + map._root._invariantCheck() } func testCountForCopyOnWriteDeletion() { @@ -209,9 +209,9 @@ final class CapsuleSmokeTests: CollectionTestCase { map[CollidableInt(33, 33)] = CollidableInt(33, 33) map[CollidableInt(11, 1)] = nil map[CollidableInt(12, 1)] = nil - expectEqual(map.rootNode.count, 2) - expectEqual(map.rootNode.reduce(0, { count, _ in count + 1 }), 2) - expectTrue(map.rootNode.invariant) + expectEqual(map._root.count, 2) + expectEqual(map.reduce(0, { count, _ in count + 1 }), 2) + map._root._invariantCheck() } func testCompactionWhenDeletingFromHashCollisionNode1() { @@ -511,9 +511,10 @@ final class CapsuleSmokeTests: CollectionTestCase { // '-' prefixed values var map2: PersistentDictionary = map1 for index in 0..