diff --git a/Sources/PersistentCollections/CMakeLists.txt b/Sources/PersistentCollections/CMakeLists.txt index c162866ba..eac88614e 100644 --- a/Sources/PersistentCollections/CMakeLists.txt +++ b/Sources/PersistentCollections/CMakeLists.txt @@ -16,7 +16,6 @@ add_library(PersistentCollections "Node/_HashTreeStatistics.swift" "Node/_Level.swift" "Node/_Node+Builder.swift" - "Node/_Node+CustomStringConvertible.swift" "Node/_Node+Debugging.swift" "Node/_Node+Initializers.swift" "Node/_Node+Invariants.swift" diff --git a/Sources/PersistentCollections/Node/_Bitmap.swift b/Sources/PersistentCollections/Node/_Bitmap.swift index 051a77322..cb06f271d 100644 --- a/Sources/PersistentCollections/Node/_Bitmap.swift +++ b/Sources/PersistentCollections/Node/_Bitmap.swift @@ -86,6 +86,11 @@ extension _Bitmap { @inlinable @inline(__always) internal var isEmpty: Bool { _value == 0 } + @inlinable @inline(__always) + internal var hasExactlyOneMember: Bool { + _value != 0 && _value & (_value &- 1) == 0 + } + @inlinable @inline(__always) internal var first: _Bucket? { guard !isEmpty else { return nil } diff --git a/Sources/PersistentCollections/Node/_Hash.swift b/Sources/PersistentCollections/Node/_Hash.swift index 43bf0818b..ad26ab1b8 100644 --- a/Sources/PersistentCollections/Node/_Hash.swift +++ b/Sources/PersistentCollections/Node/_Hash.swift @@ -70,7 +70,7 @@ extension _Hash { extension _Hash { @inlinable - internal static var emptyPrefix: _Hash { + internal static var emptyPath: _Hash { _Hash(_value: 0) } diff --git a/Sources/PersistentCollections/Node/_Node+Builder.swift b/Sources/PersistentCollections/Node/_Node+Builder.swift index b2b233472..7a7b1ea2e 100644 --- a/Sources/PersistentCollections/Node/_Node+Builder.swift +++ b/Sources/PersistentCollections/Node/_Node+Builder.swift @@ -12,166 +12,276 @@ extension _Node { @usableFromInline @frozen - internal enum Builder { + internal struct Builder { @usableFromInline typealias Element = _Node.Element - case empty - case item(Element, _Hash) - case node(_Node, _Hash) + @usableFromInline + @frozen + internal enum Kind { + case empty + case item(Element, at: _Bucket) + case node(_Node) + case collisionNode(_Node) + } + + @usableFromInline + internal var level: _Level + + @usableFromInline + internal var kind: Kind + + @inlinable + internal init(_ level: _Level, _ kind: Kind) { + self.level = level + self.kind = kind + } + } +} + +extension _Node.Builder { + @usableFromInline + internal func dump() { + let head = "Builder(level: \(level.depth), kind: " + switch self.kind { + case .empty: + print(head + "empty)") + case .item(let item, at: let bucket): + print(head + "item(\(_Node._itemString(for: item)), at: \(bucket))") + case .node(let node): + print(head + "node)") + node.dump() + case .collisionNode(let node): + print(head + "collisionNode)") + node.dump() + } + } +} + +extension _Node.Builder { + @inlinable @inline(__always) + internal static func empty(_ level: _Level) -> Self { + Self(level, .empty) + } + + @inlinable @inline(__always) + internal static func item( + _ level: _Level, _ item: __owned Element, at bucket: _Bucket + ) -> Self { + Self(level, .item(item, at: bucket)) + } + + @inlinable @inline(__always) + internal static func node( + _ level: _Level, _ node: __owned _Node + ) -> Self { + assert(!node.isCollisionNode) + return Self(level, .node(node)) + } + + @inlinable @inline(__always) + internal static func collisionNode( + _ level: _Level, _ node: __owned _Node + ) -> Self { + assert(node.isCollisionNode) + return Self(level, .collisionNode(node)) } } extension _Node.Builder { @inlinable internal var count: Int { - switch self { + switch kind { case .empty: return 0 case .item: return 1 - case .node(let node, _): + case .node(let node): + return node.count + case .collisionNode(let node): return node.count } } @inlinable internal var isEmpty: Bool { - guard case .empty = self else { return false } + guard case .empty = kind else { return false } return true } } extension _Node.Builder { @inlinable - internal init(_ level: _Level, _ node: _Node, _ hashPrefix: _Hash) { + internal init(_ level: _Level, _ node: _Node) { + self.level = level if node.count == 0 { - self = .empty + kind = .empty + } else if node.isCollisionNode { + assert(!node.hasSingletonItem) + kind = .collisionNode(node) } else if node.hasSingletonItem { - let item = node.read { $0[item: .zero] } - self = .item(item, hashPrefix) + kind = node.read { .item($0[item: .zero], at: $0.itemMap.first!) } } else { - self = .node(node, hashPrefix) + kind = .node(node) } } @inlinable - internal func finalize(_ level: _Level) -> _Node { - assert(level.isAtRoot) - switch self { + internal __consuming func finalize(_ level: _Level) -> _Node { + assert(level.isAtRoot && self.level.isAtRoot) + switch kind { case .empty: - return ._empty() - case .item(let item, let h): - return ._regularNode(item, h[level]) - case .node(let node, _): + return ._emptyNode() + case .item(let item, let bucket): + return ._regularNode(item, bucket) + case .node(let node): + return node + case .collisionNode(let node): return node } } +} +extension _Node { @inlinable - internal mutating func addNewCollision(_ newItem: Element, _ hash: _Hash) { - switch self { + internal mutating func applyReplacement( + _ level: _Level, + _ replacement: Builder + ) -> Element? { + assert(level == replacement.level) + switch replacement.kind { case .empty: - self = .item(newItem, hash) - case .item(let oldItem, let h): - assert(hash == h) + self = ._emptyNode() + case .node(let n), .collisionNode(let n): + self = n + case .item(let item, let bucket): + guard level.isAtRoot else { + self = ._emptyNode() + return item + } + self = ._regularNode(item, bucket) + } + return nil + } +} + +extension _Node.Builder { + @inlinable + internal mutating func addNewCollision( + _ level: _Level, _ newItem: __owned Element, _ hash: _Hash + ) { + assert(level == self.level) + switch kind { + case .empty: + kind = .item(newItem, at: hash[level]) + case .item(let oldItem, at: let bucket): + assert(hash[level] == bucket) let node = _Node._collisionNode(hash, oldItem, newItem) - self = .node(node, hash) - case .node(var node, let h): - self = .empty + kind = .collisionNode(node) + case .collisionNode(var node): + kind = .empty assert(node.isCollisionNode) - assert(hash == h) assert(hash == node.collisionHash) _ = node.ensureUniqueAndAppendCollision(isUnique: true, newItem) - self = .node(node, h) + kind = .collisionNode(node) + case .node: + fatalError() } } @inlinable internal mutating func addNewItem( - _ level: _Level, _ newItem: Element, _ hashPrefix: _Hash + _ level: _Level, _ newItem: __owned Element, at newBucket: _Bucket ) { - switch self { + assert(level == self.level) + switch kind { case .empty: - self = .item(newItem, hashPrefix) - case .item(let oldItem, let oldHash): - let bucket1 = oldHash[level] - let bucket2 = hashPrefix[level] - assert(bucket1 != bucket2) - assert(oldHash.isEqual(to: hashPrefix, upTo: level)) - let node = _Node._regularNode(oldItem, bucket1, newItem, bucket2) - self = .node(node, hashPrefix) - case .node(var node, let nodeHash): - self = .empty - if node.isCollisionNode { - // Expansion - assert(!level.isAtBottom) - node = _Node._regularNode(node, nodeHash[level]) - } - assert(nodeHash.isEqual(to: hashPrefix, upTo: level)) - let bucket = hashPrefix[level] - node.ensureUniqueAndInsertItem(isUnique: true, newItem, at: bucket) - self = .node(node, nodeHash) + kind = .item(newItem, at: newBucket) + case .item(let oldItem, let oldBucket): + assert(oldBucket != newBucket) + let node = _Node._regularNode(oldItem, oldBucket, newItem, newBucket) + kind = .node(node) + case .node(var node): + kind = .empty + let isUnique = node.isUnique() + node.ensureUniqueAndInsertItem(isUnique: isUnique, newItem, at: newBucket) + kind = .node(node) + case .collisionNode(var node): + // Expansion + assert(!level.isAtBottom) + self.kind = .empty + node = _Node._regularNode( + newItem, newBucket, node, node.collisionHash[level]) + kind = .node(node) } } @inlinable - internal mutating func addNewChildBranch( - _ level: _Level, _ branch: Self + internal mutating func addNewChildNode( + _ level: _Level, _ newChild: __owned _Node, at newBucket: _Bucket ) { - switch (self, branch) { - case (_, .empty): - break - case (.empty, .item): - self = branch - case (.empty, .node(let child, let childHash)): - if child.isCollisionNode { + assert(level == self.level) + switch self.kind { + case .empty: + if newChild.isCollisionNode { // Compression assert(!level.isAtBottom) - self = branch + self.kind = .collisionNode(newChild) } else { - let node = _Node._regularNode(child, childHash[level]) - self = .node(node, childHash) + self.kind = .node(._regularNode(newChild, newBucket)) } - case let (.item(li, lh), .item(ri, rh)): - let node = _Node._regularNode(li, lh[level], ri, rh[level]) - self = .node(node, lh) - case let (.item(item, itemHash), .node(child, childHash)): - assert(itemHash.isEqual(to: childHash, upTo: level)) - let node = _Node._regularNode( - item, itemHash[level], - child, childHash[level]) - self = .node(node, childHash) - case (.node(var node, let nodeHash), .item(let item, let itemHash)): - if node.isCollisionNode { - // Expansion - assert(!level.isAtBottom) - node = _Node._regularNode(node, nodeHash[level]) - } - assert(!node.isCollisionNode) - assert(nodeHash.isEqual(to: itemHash, upTo: level)) - node.ensureUniqueAndInsertItem( - isUnique: true, item, at: itemHash[level]) - self = .node(node, nodeHash) - case (.node(var node, let nodeHash), .node(let child, let childHash)): - if node.isCollisionNode { - // Expansion - assert(!level.isAtBottom) - node = _Node._regularNode(node, nodeHash[level]) - } - assert(nodeHash.isEqual(to: childHash, upTo: level)) - node.ensureUnique(isUnique: true, withFreeSpace: _Node.spaceForNewChild) - node.insertChild(child, childHash[level]) - self = .node(node, nodeHash) + case let .item(oldItem, oldBucket): + let node = _Node._regularNode(oldItem, oldBucket, newChild, newBucket) + self.kind = .node(node) + case .node(var node): + self.kind = .empty + let isUnique = node.isUnique() + node.ensureUnique( + isUnique: isUnique, withFreeSpace: _Node.spaceForNewChild) + node.insertChild(newChild, newBucket) + self.kind = .node(node) + case .collisionNode(var node): + // Expansion + self.kind = .empty + assert(!level.isAtBottom) + node = _Node._regularNode( + node, node.collisionHash[level], newChild, newBucket) + self.kind = .node(node) + } + } + + @inlinable + internal mutating func addNewChildBranch( + _ level: _Level, _ newChild: __owned Self, at newBucket: _Bucket + ) { + assert(level == self.level) + assert(newChild.level == self.level.descend()) + switch newChild.kind { + case .empty: + break + case .item(let newItem, _): + self.addNewItem(level, newItem, at: newBucket) + case .node(let newNode), .collisionNode(let newNode): + self.addNewChildNode(level, newNode, at: newBucket) } } @inlinable internal static func childBranch( - _ level: _Level, _ branch: Self + _ level: _Level, _ child: Self, at bucket: _Bucket ) -> Self { - var result: Self = .empty - result.addNewChildBranch(level, branch) - return result + assert(child.level == level.descend()) + switch child.kind { + case .empty: + return self.empty(level) + case .item(let item, _): + return self.item(level, item, at: bucket) + case .node(let n): + return self.node(level, ._regularNode(n, bucket)) + case .collisionNode(let node): + // Compression + assert(!level.isAtBottom) + return self.collisionNode(level, node) + } } } @@ -186,42 +296,38 @@ extension _Node.Builder { assert(end < source.itemsEndSlot) let h = source.collisionHash for slot: _Slot in stride(from: .zero, to: end, by: 1) { - self.addNewCollision(source[item: slot], h) + self.addNewCollision(self.level, source[item: slot], h) } } @inlinable internal mutating func copyItems( _ level: _Level, - _ hashPrefix: _Hash, from source: _Node.UnsafeHandle, upTo end: _Bucket ) { + assert(level == self.level) assert(isEmpty) assert(!source.isCollisionNode) for (b, s) in source.itemMap.intersection(_Bitmap(upTo: end)) { - let h = hashPrefix.appending(b, at: level) - self.addNewItem(level, source[item: s], h) + self.addNewItem(level, source[item: s], at: b) } } @inlinable internal mutating func copyItemsAndChildren( _ level: _Level, - _ hashPrefix: _Hash, from source: _Node.UnsafeHandle, upTo end: _Bucket ) { + assert(level == self.level) assert(isEmpty) assert(!source.isCollisionNode) for (b, s) in source.itemMap { - let h = hashPrefix.appending(b, at: level) - self.addNewItem(level, source[item: s], h) + self.addNewItem(level, source[item: s], at: b) } for (b, s) in source.childMap.intersection(_Bitmap(upTo: end)) { - let h = hashPrefix.appending(b, at: level) - self.addNewChildBranch(level, .node(source[child: s], h)) + self.addNewChildNode(level, source[child: s], at: b) } } - } diff --git a/Sources/PersistentCollections/Node/_Node+CustomStringConvertible.swift b/Sources/PersistentCollections/Node/_Node+CustomStringConvertible.swift deleted file mode 100644 index 36a62d27a..000000000 --- a/Sources/PersistentCollections/Node/_Node+CustomStringConvertible.swift +++ /dev/null @@ -1,42 +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 _Node: CustomStringConvertible { - @usableFromInline - internal var description: String { - guard count > 0 else { - return "[:]" - } - - var result = "[" - var first = true - read { - for (key, value) in $0.reverseItems.reversed() { - if first { - first = false - } else { - result += ", " - } - result += "\(key): \(value)" - } - for child in $0.children { - if first { - first = false - } else { - result += ", " - } - result += "\(child.description)" - } - } - result += "]" - return result - } -} diff --git a/Sources/PersistentCollections/Node/_Node+Debugging.swift b/Sources/PersistentCollections/Node/_Node+Debugging.swift index 3b0dd1c7f..094a24766 100644 --- a/Sources/PersistentCollections/Node/_Node+Debugging.swift +++ b/Sources/PersistentCollections/Node/_Node+Debugging.swift @@ -39,11 +39,16 @@ extension _Node.Storage { } } +extension _Node { + internal static func _itemString(for item: Element) -> String { + let hash = _Hash(item.key).description + return "hash: \(hash), key: \(item.key), value: \(item.value)" + } +} extension _Node.UnsafeHandle { internal func _itemString(at slot: _Slot) -> String { let item = self[item: slot] - let hash = _Hash(item.key).description - return "hash: \(hash), key: \(item.key), value: \(item.value)" + return _Node._itemString(for: item) } @usableFromInline diff --git a/Sources/PersistentCollections/Node/_Node+Initializers.swift b/Sources/PersistentCollections/Node/_Node+Initializers.swift index 1de96ee50..1620de96b 100644 --- a/Sources/PersistentCollections/Node/_Node+Initializers.swift +++ b/Sources/PersistentCollections/Node/_Node+Initializers.swift @@ -11,7 +11,7 @@ extension _Node { @inlinable @inline(__always) - internal static func _empty() -> _Node { + internal static func _emptyNode() -> _Node { _Node(storage: _emptySingleton, count: 0) } @@ -98,7 +98,8 @@ extension _Node { @inlinable internal static func _regularNode( - _ child: __owned _Node, _ bucket: _Bucket + _ child: __owned _Node, + _ bucket: _Bucket ) -> _Node { let r = _Node.allocate( itemMap: .empty, diff --git a/Sources/PersistentCollections/Node/_Node+Invariants.swift b/Sources/PersistentCollections/Node/_Node+Invariants.swift index af02f666f..570df8cca 100644 --- a/Sources/PersistentCollections/Node/_Node+Invariants.swift +++ b/Sources/PersistentCollections/Node/_Node+Invariants.swift @@ -56,9 +56,14 @@ extension _Node { internal func _invariantCheck() {} #endif + @inlinable @inline(__always) + public func _fullInvariantCheck() { + self._fullInvariantCheck(.top, .emptyPath) + } + #if COLLECTIONS_INTERNAL_CHECKS @inlinable @inline(never) - public func _fullInvariantCheck(_ level: _Level, _ path: _Hash) { + internal func _fullInvariantCheck(_ level: _Level, _ path: _Hash) { _invariantCheck() read { precondition(level.isAtRoot || !hasSingletonItem) @@ -96,7 +101,7 @@ extension _Node { } #else @inlinable @inline(__always) - public func _fullInvariantCheck(_ level: _Level, _ path: _Hash) {} + internal func _fullInvariantCheck(_ level: _Level, _ path: _Hash) {} #endif } diff --git a/Sources/PersistentCollections/Node/_Node+Primitive Removals.swift b/Sources/PersistentCollections/Node/_Node+Primitive Removals.swift index 050805cf8..367bf7871 100644 --- a/Sources/PersistentCollections/Node/_Node+Primitive Removals.swift +++ b/Sources/PersistentCollections/Node/_Node+Primitive Removals.swift @@ -67,14 +67,14 @@ extension _Node { at bucket: _Bucket ) -> Element { let slot = read { $0.itemMap.slot(of: bucket) } - return removeItem(at: slot, bucket, by: { $0.move() }) + return removeItem(at: bucket, slot, by: { $0.move() }) } @inlinable internal mutating func removeItem( - at slot: _Slot, _ bucket: _Bucket + at bucket: _Bucket, _ slot: _Slot ) -> Element { - removeItem(at: slot, bucket, by: { $0.move() }) + removeItem(at: bucket, slot, by: { $0.move() }) } /// Remove the item at `slot`, increasing the amount of free @@ -84,7 +84,7 @@ extension _Node { /// storage slot corresponding to the item to be removed. @inlinable internal mutating func removeItem( - at slot: _Slot, _ bucket: _Bucket, + at bucket: _Bucket, _ slot: _Slot, by remover: (UnsafeMutablePointer) -> R ) -> R { defer { _invariantCheck() } @@ -106,9 +106,8 @@ extension _Node { @inlinable internal mutating func removeChild( - at slot: _Slot, _ bucket: _Bucket + at bucket: _Bucket, _ slot: _Slot ) -> _Node { - defer { _invariantCheck() } assert(!isCollisionNode) let child: _Node = update { assert($0.childMap.contains(bucket)) @@ -128,7 +127,7 @@ extension _Node { assert(count == 1) count = 0 return update { - assert($0.itemCount == 1 && $0.childCount == 0) + assert($0.hasSingletonItem) let old = $0._removeItem(at: .zero) { $0.move() } $0.clear() return old @@ -139,7 +138,7 @@ extension _Node { internal mutating func removeSingletonChild() -> _Node { defer { _invariantCheck() } let child: _Node = update { - assert($0.itemCount == 0 && $0.childCount == 1) + assert($0.hasSingletonChild) let child = $0._removeChild(at: .zero) $0.childMap = .empty return child diff --git a/Sources/PersistentCollections/Node/_Node+Primitive Replacement.swift b/Sources/PersistentCollections/Node/_Node+Primitive Replacement.swift index 2d013915b..a687a3cee 100644 --- a/Sources/PersistentCollections/Node/_Node+Primitive Replacement.swift +++ b/Sources/PersistentCollections/Node/_Node+Primitive Replacement.swift @@ -12,7 +12,7 @@ extension _Node { @inlinable internal mutating func replaceItem( - at bucket: _Bucket, _ slot: _Slot, with item: Element + at bucket: _Bucket, _ slot: _Slot, with item: __owned Element ) { update { assert($0.isCollisionNode || $0.itemMap.contains(bucket)) @@ -24,7 +24,7 @@ extension _Node { @inlinable internal mutating func replaceChild( - at bucket: _Bucket, with child: _Node + at bucket: _Bucket, with child: __owned _Node ) -> Int { let slot = read { $0.childMap.slot(of: bucket) } return replaceChild(at: bucket, slot, with: child) @@ -32,7 +32,7 @@ extension _Node { @inlinable internal mutating func replaceChild( - at bucket: _Bucket, _ slot: _Slot, with child: _Node + at bucket: _Bucket, _ slot: _Slot, with child: __owned _Node ) -> Int { let delta: Int = update { assert(!$0.isCollisionNode) @@ -50,41 +50,41 @@ extension _Node { @inlinable internal func replacingChild( _ level: _Level, - _ hashPrefix: _Hash, + at bucket: _Bucket, _ slot: _Slot, with child: __owned Builder ) -> Builder { - let bucket = hashPrefix[level] + assert(child.level == level.descend()) read { assert(!$0.isCollisionNode) assert($0.childMap.contains(bucket)) assert(slot == $0.childMap.slot(of: bucket)) } - switch child { + switch child.kind { case .empty: - return _removingChild(level, hashPrefix, bucket, slot) - case let .item(item, hash): - assert(hash.isEqual(to: hashPrefix, upTo: level)) - assert(hash[level] == bucket) + return _removingChild(level, at: bucket, slot) + case let .item(item, _): if hasSingletonChild { - return child + return .item(level, item, at: bucket) } var node = self.copy(withFreeSpace: _Node.spaceForInlinedChild) - _ = node.removeChild(at: slot, bucket) + _ = node.removeChild(at: bucket, slot) node.insertItem(item, at: bucket) node._invariantCheck() - return .node(node, hashPrefix) - case let .node(node, hash): - assert(hash.isEqual(to: hashPrefix, upTo: level)) - assert(hash[level] == bucket) - if node.isCollisionNode, self.hasSingletonChild { + return .node(level, node) + case let .node(node): + var copy = self.copy() + _ = copy.replaceChild(at: bucket, slot, with: node) + return .node(level, copy) + case let .collisionNode(node): + if hasSingletonChild { // Compression assert(!level.isAtBottom) - return child + return .collisionNode(level, node) } var copy = self.copy() _ = copy.replaceChild(at: bucket, slot, with: node) - return .node(copy, hashPrefix) + return .node(level, copy) } } } diff --git a/Sources/PersistentCollections/Node/_Node+Structural compactMapValues.swift b/Sources/PersistentCollections/Node/_Node+Structural compactMapValues.swift index 1cd0e6d32..a52b4384f 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural compactMapValues.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural compactMapValues.swift @@ -13,17 +13,16 @@ extension _Node { @inlinable internal func compactMapValues( _ level: _Level, - _ hashPrefix: _Hash, _ transform: (Value) throws -> T? ) rethrows -> _Node.Builder { return try self.read { - var result: _Node.Builder = .empty + var result: _Node.Builder = .empty(level) if isCollisionNode { let items = $0.reverseItems for i in items.indices { if let v = try transform(items[i].value) { - result.addNewCollision((items[i].key, v), $0.collisionHash) + result.addNewCollision(level, (items[i].key, v), $0.collisionHash) } } return result @@ -32,16 +31,14 @@ extension _Node { for (bucket, slot) in $0.itemMap { let p = $0.itemPtr(at: slot) if let v = try transform(p.pointee.value) { - let h = hashPrefix.appending(bucket, at: level) - result.addNewItem(level, (p.pointee.key, v), h) + result.addNewItem(level, (p.pointee.key, v), at: bucket) } } for (bucket, slot) in $0.childMap { - let h = hashPrefix.appending(bucket, at: level) - let branch = try $0[child: slot].compactMapValues( - level.descend(), h, transform) - result.addNewChildBranch(level, branch) + let branch = try $0[child: slot] + .compactMapValues(level.descend(), transform) + result.addNewChildBranch(level, branch, at: bucket) } return result } diff --git a/Sources/PersistentCollections/Node/_Node+Structural filter.swift b/Sources/PersistentCollections/Node/_Node+Structural filter.swift index 2931d9df6..93cc75c9a 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural filter.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural filter.swift @@ -13,14 +13,13 @@ extension _Node { @inlinable internal func filter( _ level: _Level, - _ hashPrefix: _Hash, _ isIncluded: (Element) throws -> Bool ) rethrows -> Builder? { guard !isCollisionNode else { - return try _filter_slow(isIncluded) + return try _filter_slow(level, isIncluded) } return try self.read { - var result: Builder = .empty + var result: Builder = .empty(level) var removing = false // true if we need to remove something for (bucket, slot) in $0.itemMap { @@ -28,29 +27,26 @@ extension _Node { let include = try isIncluded(p.pointee) switch (include, removing) { case (true, true): - let h = hashPrefix.appending(bucket, at: level) - result.addNewItem(level, p.pointee, h) + result.addNewItem(level, p.pointee, at: bucket) case (false, false): removing = true - result.copyItems(level, hashPrefix, from: $0, upTo: bucket) + result.copyItems(level, from: $0, upTo: bucket) default: break } } for (bucket, slot) in $0.childMap { - let h = hashPrefix.appending(bucket, at: level) - let branch = try $0[child: slot].filter(level.descend(), h, isIncluded) + let branch = try $0[child: slot].filter(level.descend(), isIncluded) if let branch = branch { assert(branch.count < self.count) if !removing { removing = true - result.copyItemsAndChildren( - level, hashPrefix, from: $0, upTo: bucket) + result.copyItemsAndChildren(level, from: $0, upTo: bucket) } - result.addNewChildBranch(level, branch) + result.addNewChildBranch(level, branch, at: bucket) } else if removing { - result.addNewChildBranch(level, .node($0[child: slot], h)) + result.addNewChildNode(level, $0[child: slot], at: bucket) } } @@ -61,17 +57,18 @@ extension _Node { @inlinable @inline(never) internal func _filter_slow( + _ level: _Level, _ isIncluded: (Element) throws -> Bool ) rethrows -> Builder? { try self.read { - var result: Builder = .empty + var result: Builder = .empty(level) var removing = false for slot: _Slot in stride(from: .zero, to: $0.itemsEndSlot, by: 1) { let p = $0.itemPtr(at: slot) let include = try isIncluded(p.pointee) if include, removing { - result.addNewCollision(p.pointee, $0.collisionHash) + result.addNewCollision(level, p.pointee, $0.collisionHash) } else if !include, !removing { removing = true diff --git a/Sources/PersistentCollections/Node/_Node+Structural intersection.swift b/Sources/PersistentCollections/Node/_Node+Structural intersection.swift index ed7d0c77e..4fbebcbcc 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural intersection.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural intersection.swift @@ -13,18 +13,17 @@ extension _Node { @inlinable internal func intersection( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder? { if self.raw.storage === other.raw.storage { return nil } if self.isCollisionNode || other.isCollisionNode { - return _intersection_slow(level, hashPrefix, other) + return _intersection_slow(level, other) } return self.read { l in other.read { r in - var result: Builder = .empty + var result: Builder = .empty(level) var removing = false for (bucket, lslot) in l.itemMap { @@ -43,12 +42,11 @@ extension _Node { else { include = false} if include, removing { - let hashPrefix = hashPrefix.appending(bucket, at: level) - result.addNewItem(level, lp.pointee, hashPrefix) + result.addNewItem(level, lp.pointee, at: bucket) } else if !include, !removing { removing = true - result.copyItems(level, hashPrefix, from: l, upTo: bucket) + result.copyItems(level, from: l, upTo: bucket) } } @@ -56,43 +54,35 @@ extension _Node { if r.itemMap.contains(bucket) { if !removing { removing = true - result.copyItemsAndChildren( - level, hashPrefix, from: l, upTo: bucket) + result.copyItemsAndChildren(level, from: l, upTo: bucket) } let rslot = r.itemMap.slot(of: bucket) let rp = r.itemPtr(at: rslot) let h = _Hash(rp.pointee.key) let res = l[child: lslot].lookup(level.descend(), rp.pointee.key, h) if let res = res { - UnsafeHandle.read(res.node) { - let p = $0.itemPtr(at: res.slot) - result.addNewItem(level, p.pointee, h) - } + let item = UnsafeHandle.read(res.node) { $0[item: res.slot] } + result.addNewItem(level, item, at: bucket) } } else if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let branch = l[child: lslot].intersection( - level.descend(), - hashPrefix.appending(bucket, at: level), - r[child: rslot]) + let branch = l[child: lslot] + .intersection(level.descend(), r[child: rslot]) if let branch = branch { assert(branch.count < self.count) if !removing { removing = true - result.copyItemsAndChildren( - level, hashPrefix, from: l, upTo: bucket) + result.copyItemsAndChildren(level, from: l, upTo: bucket) } - result.addNewChildBranch(level, branch) + result.addNewChildBranch(level, branch, at: bucket) } else if removing { - let h = hashPrefix.appending(bucket, at: level) - result.addNewChildBranch(level, .node(l[child: lslot], h)) + result.addNewChildNode(level, l[child: lslot], at: bucket) } } else if !removing { removing = true - result.copyItemsAndChildren( - level, hashPrefix, from: l, upTo: bucket) + result.copyItemsAndChildren(level, from: l, upTo: bucket) } } guard removing else { return nil } @@ -104,7 +94,6 @@ extension _Node { @inlinable @inline(never) internal func _intersection_slow( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder? { let lc = self.isCollisionNode @@ -112,16 +101,16 @@ extension _Node { if lc && rc { return read { l in other.read { r in - guard l.collisionHash == r.collisionHash else { return .empty } - var result: Builder = .empty - var removing = false + var result: Builder = .empty(level) + guard l.collisionHash == r.collisionHash else { return result } + var removing = false let ritems = r.reverseItems for lslot: _Slot in stride(from: .zero, to: l.itemsEndSlot, by: 1) { let lp = l.itemPtr(at: lslot) let include = ritems.contains { $0.key == lp.pointee.key } if include, removing { - result.addNewCollision(lp.pointee, l.collisionHash) + result.addNewCollision(level, lp.pointee, l.collisionHash) } else if !include, !removing { removing = true @@ -149,17 +138,15 @@ extension _Node { let ritem = r.itemPtr(at: rslot) let litems = l.reverseItems let i = litems.firstIndex { $0.key == ritem.pointee.key } - guard let i = i else { return .empty } - return .item(litems[i], l.collisionHash) + guard let i = i else { return .empty(level) } + return .item(level, litems[i], at: l.collisionHash[level]) } if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - return intersection( - level.descend(), - hashPrefix.appending(bucket, at: level), - r[child: rslot]) + return intersection(level.descend(), r[child: rslot]) + .map { .childBranch(level, $0, at: bucket) } } - return .empty + return .empty(level) } } } @@ -174,24 +161,21 @@ extension _Node { let litem = l.itemPtr(at: lslot) let ritems = r.reverseItems let found = ritems.contains { $0.key == litem.pointee.key } - guard found else { return .empty } - return .item(litem.pointee, r.collisionHash) + guard found else { return .empty(level) } + return .item(level, litem.pointee, at: bucket) } if l.childMap.contains(bucket) { let lslot = l.childMap.slot(of: bucket) - let branch = l[child: lslot].intersection( - level.descend(), - hashPrefix.appending(bucket, at: level), - other) + let branch = l[child: lslot].intersection(level.descend(), other) guard let branch = branch else { assert(l[child: lslot].isCollisionNode) assert(l[child: lslot].collisionHash == r.collisionHash) // Compression - return .node(l[child: lslot], r.collisionHash) + return .collisionNode(level, l[child: lslot]) } - return .childBranch(level, branch) + return .childBranch(level, branch, at: bucket) } - return .empty + return .empty(level) } } } diff --git a/Sources/PersistentCollections/Node/_Node+Structural merge.swift b/Sources/PersistentCollections/Node/_Node+Structural merge.swift index ab1216286..08a36e60d 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural merge.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural merge.swift @@ -13,7 +13,6 @@ extension _Node { @inlinable internal mutating func merge( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node, _ combine: (Value, Value) throws -> Value ) rethrows { @@ -45,13 +44,12 @@ extension _Node { return } - try _merge(level, hashPrefix, other, combine) + try _merge(level, other, combine) } @inlinable internal mutating func _merge( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node, _ combine: (Value, Value) throws -> Value ) rethrows { @@ -59,7 +57,7 @@ extension _Node { // of identical nodes. if self.isCollisionNode || other.isCollisionNode { - try _merge_slow(level, hashPrefix, other, combine) + try _merge_slow(level, other, combine) return } @@ -154,7 +152,6 @@ extension _Node { try self.update { l in try l[child: lslot].merge( level.descend(), - hashPrefix.appending(bucket, at: level), r[child: rslot], combine) } @@ -187,7 +184,6 @@ extension _Node { @inlinable @inline(never) internal mutating func _merge_slow( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node, _ combine: (Value, Value) throws -> Value ) rethrows { @@ -260,8 +256,7 @@ extension _Node { if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let h = hashPrefix.appending(bucket, at: level) - try self._merge(level.descend(), h, r[child: rslot], combine) + try self._merge(level.descend(), r[child: rslot], combine) var node = other.copy() _ = node.replaceChild(at: bucket, rslot, with: self) self = node @@ -303,10 +298,9 @@ extension _Node { self.ensureUnique(isUnique: isUnique) let delta: Int = try self.update { l in let lslot = l.childMap.slot(of: bucket) - let h = hashPrefix.appending(bucket, at: level) let lchild = l.childPtr(at: lslot) let origCount = lchild.pointee.count - try lchild.pointee._merge(level.descend(), h, other, combine) + try lchild.pointee._merge(level.descend(), other, combine) return lchild.pointee.count &- origCount } assert(delta >= 0) diff --git a/Sources/PersistentCollections/Node/_Node+Structural subtracting.swift b/Sources/PersistentCollections/Node/_Node+Structural subtracting.swift index b49dd23cc..498447d06 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural subtracting.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural subtracting.swift @@ -13,18 +13,17 @@ extension _Node { @inlinable internal func subtracting( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder? { - if self.raw.storage === other.raw.storage { return .empty } + if self.raw.storage === other.raw.storage { return .empty(level) } if self.isCollisionNode || other.isCollisionNode { - return _subtracting_slow(level, hashPrefix, other) + return _subtracting_slow(level, other) } return self.read { l in other.read { r in - var result: Builder = .empty + var result: Builder = .empty(level) var removing = false for (bucket, lslot) in l.itemMap { @@ -45,59 +44,48 @@ extension _Node { } if include, removing { - let h = hashPrefix.appending(bucket, at: level) - result.addNewItem(level, lp.pointee, h) + result.addNewItem(level, lp.pointee, at: bucket) } else if !include, !removing { removing = true - result.copyItems(level, hashPrefix, from: l, upTo: bucket) + result.copyItems(level, from: l, upTo: bucket) } } for (bucket, lslot) in l.childMap { + var done = false if r.itemMap.contains(bucket) { let rslot = r.itemMap.slot(of: bucket) let rp = r.itemPtr(at: rslot) let h = _Hash(rp.pointee.key) let child = l[child: lslot] - .removing2(level.descend(), rp.pointee.key, h)?.replacement + .removing(level.descend(), rp.pointee.key, h)?.replacement if let child = child { assert(child.count < self.count) if !removing { removing = true - result.copyItemsAndChildren( - level, hashPrefix, from: l, upTo: bucket) + result.copyItemsAndChildren(level, from: l, upTo: bucket) } - result.addNewChildBranch(level, child) - } - else if removing { - let h = hashPrefix.appending(bucket, at: level) - result.addNewChildBranch(level, .node(l[child: lslot], h)) + result.addNewChildBranch(level, child, at: bucket) + done = true } } else if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let child = l[child: lslot].subtracting( - level.descend(), - hashPrefix.appending(bucket, at: level), - r[child: rslot]) + let child = l[child: lslot] + .subtracting(level.descend(), r[child: rslot]) if let child = child { assert(child.count < self.count) if !removing { removing = true - result.copyItemsAndChildren( - level, hashPrefix, from: l, upTo: bucket) + result.copyItemsAndChildren(level, from: l, upTo: bucket) } - result.addNewChildBranch(level, child) - } - else if removing { - let h = hashPrefix.appending(bucket, at: level) - result.addNewChildBranch(level, .node(l[child: lslot], h)) + result.addNewChildBranch(level, child, at: bucket) + done = true } } - else if removing { - let h = hashPrefix.appending(bucket, at: level) - result.addNewChildBranch(level, .node(l[child: lslot], h)) + if !done, removing { + result.addNewChildNode(level, l[child: lslot], at: bucket) } } guard removing else { return nil } @@ -109,7 +97,6 @@ extension _Node { @inlinable @inline(never) internal func _subtracting_slow( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder? { let lc = self.isCollisionNode @@ -120,7 +107,7 @@ extension _Node { guard l.collisionHash == r.collisionHash else { return nil } - var result: Builder = .empty + var result: Builder = .empty(level) var removing = false let ritems = r.reverseItems @@ -128,7 +115,7 @@ extension _Node { let lp = l.itemPtr(at: lslot) let include = !ritems.contains { $0.key == lp.pointee.key } if include, removing { - result.addNewCollision(lp.pointee, l.collisionHash) + result.addNewCollision(level, lp.pointee, l.collisionHash) } else if !include, !removing { removing = true @@ -157,14 +144,13 @@ extension _Node { let h = _Hash(ritem.pointee.key) let res = l.find(level, ritem.pointee.key, h) guard let res = res else { return nil } - return self._removingItemFromLeaf( - level, hashPrefix.appending(bucket, at: level), res.slot - ).replacement + return self._removingItemFromLeaf(level, at: bucket, res.slot) + .replacement } else if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let h = hashPrefix.appending(bucket, at: level) - return self.subtracting(level.descend(), h, r[child: rslot]) + return subtracting(level.descend(), r[child: rslot]) + .map { .childBranch(level, $0, at: bucket) } } return nil } @@ -182,19 +168,15 @@ extension _Node { let h = _Hash(litem.pointee.key) let res = r.find(level, litem.pointee.key, h) if res == nil { return nil } - return self._removingItemFromLeaf( - level, hashPrefix.appending(bucket, at: level), lslot - ).replacement + return self._removingItemFromLeaf(level, at: bucket, lslot) + .replacement } if l.childMap.contains(bucket) { let lslot = l.childMap.slot(of: bucket) - let branch = l[child: lslot].subtracting( - level.descend(), - hashPrefix.appending(bucket, at: level), - other) + let branch = l[child: lslot].subtracting(level.descend(), other) guard let branch = branch else { return nil } - var result = self._removingChild(level, hashPrefix, bucket, lslot) - result.addNewChildBranch(level, branch) + var result = self._removingChild(level, at: bucket, lslot) + result.addNewChildBranch(level, branch, at: bucket) return result } return nil diff --git a/Sources/PersistentCollections/Node/_Node+Structural symmetricDifference.swift b/Sources/PersistentCollections/Node/_Node+Structural symmetricDifference.swift index 89c8ad896..a37d1ed2c 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural symmetricDifference.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural symmetricDifference.swift @@ -13,32 +13,30 @@ extension _Node { @inlinable internal func symmetricDifference( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder? { - guard self.count > 0 else { return .node(other, hashPrefix) } + guard self.count > 0 else { return Builder(level, other) } guard other.count > 0 else { return nil } - return _symmetricDifference(level, hashPrefix, other) + return _symmetricDifference(level, other) } @inlinable internal func _symmetricDifference( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder { assert(self.count > 0 && other.count > 0) if self.raw.storage === other.raw.storage { - return .empty + return .empty(level) } if self.isCollisionNode || other.isCollisionNode { - return _symmetricDifference_slow(level, hashPrefix, other) + return _symmetricDifference_slow(level, other) } return self.read { l in other.read { r in - var result: Builder = .empty + var result: Builder = .empty(level) for (bucket, lslot) in l.itemMap { let lp = l.itemPtr(at: lslot) @@ -52,7 +50,7 @@ extension _Node { level: level.descend(), item1: lp.pointee, h1, item2: { $0.initialize(to: rp.pointee) }, h2) - result.addNewChildBranch(level, .node(child.top, h1)) + result.addNewChildNode(level, child.top, at: bucket) } } else if r.childMap.contains(bucket) { @@ -60,19 +58,18 @@ extension _Node { let rp = r.childPtr(at: rslot) let h = _Hash(lp.pointee.key) let child = rp.pointee - .removing2(level.descend(), lp.pointee.key, h)?.replacement + .removing(level.descend(), lp.pointee.key, h)?.replacement if let child = child { - result.addNewChildBranch(level, child) + result.addNewChildBranch(level, child, at: bucket) } else { let child2 = rp.pointee.inserting(level.descend(), lp.pointee, h) assert(child2.inserted) - result.addNewChildBranch(level, .node(child2.node, h)) + result.addNewChildNode(level, child2.node, at: bucket) } } else { - let h = hashPrefix.appending(bucket, at: level) - result.addNewItem(level, lp.pointee, h) + result.addNewItem(level, lp.pointee, at: bucket) } } @@ -83,39 +80,35 @@ extension _Node { let rp = r.itemPtr(at: rslot) let h = _Hash(rp.pointee.key) let child = lp.pointee - .removing2(level.descend(), rp.pointee.key, h)?.replacement + .removing(level.descend(), rp.pointee.key, h)?.replacement if let child = child { - result.addNewChildBranch(level, child) + result.addNewChildBranch(level, child, at: bucket) } else { let child2 = lp.pointee.inserting(level.descend(), rp.pointee, h) assert(child2.inserted) - result.addNewChildBranch(level, .node(child2.node, h)) + result.addNewChildNode(level, child2.node, at: bucket) } } else if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let hp = hashPrefix.appending(bucket, at: level) let b = l[child: lslot]._symmetricDifference( - level.descend(), hp, r[child: rslot]) - result.addNewChildBranch(level, b) + level.descend(), r[child: rslot]) + result.addNewChildBranch(level, b, at: bucket) } else { - let h = hashPrefix.appending(bucket, at: level) - result.addNewChildBranch(level, .node(lp.pointee, h)) + result.addNewChildNode(level, lp.pointee, at: bucket) } } let seen = l.itemMap.union(l.childMap) for (bucket, rslot) in r.itemMap { guard !seen.contains(bucket) else { continue } - let h = hashPrefix.appending(bucket, at: level) - result.addNewItem(level, r[item: rslot], h) + result.addNewItem(level, r[item: rslot], at: bucket) } for (bucket, rslot) in r.childMap { guard !seen.contains(bucket) else { continue } - let h = hashPrefix.appending(bucket, at: level) - result.addNewChildBranch(level, .node(r[child: rslot], h)) + result.addNewChildNode(level, r[child: rslot], at: bucket) } return result } @@ -125,23 +118,21 @@ extension _Node { @inlinable @inline(never) internal func _symmetricDifference_slow( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder { switch (self.isCollisionNode, other.isCollisionNode) { case (true, true): - return self._symmetricDifference_slow_both(level, hashPrefix, other) + return self._symmetricDifference_slow_both(level, other) case (true, false): - return self._symmetricDifference_slow_left(level, hashPrefix, other) + return self._symmetricDifference_slow_left(level, other) case (false, _): - return other._symmetricDifference_slow_left(level, hashPrefix, self) + return other._symmetricDifference_slow_left(level, self) } } @inlinable internal func _symmetricDifference_slow_both( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder { read { l in @@ -151,15 +142,15 @@ extension _Node { level: level, child1: self, l.collisionHash, child2: other, r.collisionHash) - return .node(node, l.collisionHash) + return .node(level, node) } - var result: Builder = .empty + var result: Builder = .empty(level) let ritems = r.reverseItems for ls: _Slot in stride(from: .zero, to: l.itemsEndSlot, by: 1) { let lp = l.itemPtr(at: ls) let include = !ritems.contains(where: { $0.key == lp.pointee.key }) if include { - result.addNewCollision(lp.pointee, l.collisionHash) + result.addNewCollision(level, lp.pointee, l.collisionHash) } } // FIXME: Consider remembering slots of shared items in r by @@ -169,7 +160,7 @@ extension _Node { let rp = r.itemPtr(at: rs) let include = !litems.contains(where: { $0.key == rp.pointee.key }) if include { - result.addNewCollision(rp.pointee, r.collisionHash) + result.addNewCollision(level, rp.pointee, r.collisionHash) } } return result @@ -180,7 +171,6 @@ extension _Node { @inlinable internal func _symmetricDifference_slow_left( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> Builder { // `self` is a collision node on a compressed path. The other tree might @@ -192,40 +182,60 @@ extension _Node { if r.itemMap.contains(bucket) { let rslot = r.itemMap.slot(of: bucket) let rp = r.itemPtr(at: rslot) + let rh = _Hash(rp.pointee.key) + guard rh == l.collisionHash else { + var copy = other.copy(withFreeSpace: _Node.spaceForSpawningChild) + let item = copy.removeItem(at: bucket, rslot) + let child = _Node.build( + level: level.descend(), + item1: { $0.initialize(to: item) }, rh, + child2: self, l.collisionHash) + copy.insertChild(child.top, bucket) + return .node(level, copy) + } let litems = l.reverseItems if let li = litems.firstIndex(where: { $0.key == rp.pointee.key }) { if l.itemCount == 2 { var node = other.copy() node.replaceItem(at: bucket, rslot, with: litems[1 &- li]) - return .node(node, l.collisionHash) + return .node(level, node) } let lslot = _Slot(litems.count &- 1 &- li) var child = self.copy() - _ = child.removeItem(at: lslot, .invalid) + _ = child.removeItem(at: .invalid, lslot) + if other.hasSingletonItem { + // Compression + return .collisionNode(level, child) + } var node = other.copy(withFreeSpace: _Node.spaceForSpawningChild) - _ = node.removeItem(at: rslot, bucket) + _ = node.removeItem(at: bucket, rslot) node.insertChild(child, bucket) - return .node(node, hashPrefix) + return .node(level, node) + } + if other.hasSingletonItem { + // Compression + let copy = self.copyNodeAndAppendCollision { + $0.initialize(to: r[item: .zero]) + } + return .collisionNode(level, copy.node) } var node = other.copy(withFreeSpace: _Node.spaceForSpawningChild) - let item = node.removeItem(at: rslot, bucket) + let item = node.removeItem(at: bucket, rslot) let child = self.copyNodeAndAppendCollision { $0.initialize(to: item) } node.insertChild(child.node, bucket) - return .node(node, hashPrefix) + return .node(level, node) } if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) let rp = r.childPtr(at: rslot) - let hp = hashPrefix.appending(bucket, at: level) - let child = rp.pointee._symmetricDifference( - level.descend(), hp, self) - return other.replacingChild(level, hp, rslot, with: child) + let child = rp.pointee._symmetricDifference(level.descend(), self) + return other.replacingChild(level, at: bucket, rslot, with: child) } var node = other.copy(withFreeSpace: _Node.spaceForNewChild) node.insertChild(self, bucket) - return .node(node, hashPrefix) + return .node(level, node) } } } diff --git a/Sources/PersistentCollections/Node/_Node+Structural union.swift b/Sources/PersistentCollections/Node/_Node+Structural union.swift index 01b0de649..a504505e8 100644 --- a/Sources/PersistentCollections/Node/_Node+Structural union.swift +++ b/Sources/PersistentCollections/Node/_Node+Structural union.swift @@ -13,7 +13,6 @@ extension _Node { @inlinable internal func union( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> (copied: Bool, node: _Node) { guard self.count > 0 else { return (true, other) } @@ -38,13 +37,12 @@ extension _Node { return (true, copy) } } - return _union(level, hashPrefix, other) + return _union(level, other) } @inlinable internal func _union( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> (copied: Bool, node: _Node) { if self.raw.storage === other.raw.storage { @@ -52,7 +50,7 @@ extension _Node { } if self.isCollisionNode || other.isCollisionNode { - return _union_slow(level, hashPrefix, other) + return _union_slow(level, other) } return self.read { l in @@ -116,10 +114,7 @@ extension _Node { } else if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let child = l[child: lslot]._union( - level.descend(), - hashPrefix.appending(bucket, at: level), - r[child: rslot]) + let child = l[child: lslot]._union(level.descend(), r[child: rslot]) guard child.copied else { // Nothing to do continue @@ -158,7 +153,6 @@ extension _Node { @inlinable @inline(never) internal func _union_slow( _ level: _Level, - _ hashPrefix: _Hash, _ other: _Node ) -> (copied: Bool, node: _Node) { let lc = self.isCollisionNode @@ -208,17 +202,13 @@ extension _Node { return (false, self) } let node = other.copyNodeAndPushItemIntoNewChild( - level: level, - self, - at: bucket, - itemSlot: rslot) + level: level, self, at: bucket, itemSlot: rslot) return (true, node) } if r.childMap.contains(bucket) { let rslot = r.childMap.slot(of: bucket) - let h = hashPrefix.appending(bucket, at: level) - let res = self._union(level.descend(), h, r[child: rslot]) + let res = self._union(level.descend(), r[child: rslot]) var node = other.copy() let delta = node.replaceChild(at: bucket, rslot, with: res.node) assert(delta >= 0) @@ -241,16 +231,12 @@ extension _Node { let lslot = l.itemMap.slot(of: bucket) assert(!l.hasSingletonItem) // Handled up front above let node = self.copyNodeAndPushItemIntoNewChild( - level: level, - other, - at: bucket, - itemSlot: lslot) + level: level, other, at: bucket, itemSlot: lslot) return (true, node) } if l.childMap.contains(bucket) { let lslot = l.childMap.slot(of: bucket) - let h = hashPrefix.appending(bucket, at: level) - let child = l[child: lslot]._union(level.descend(), h, other) + let child = l[child: lslot]._union(level.descend(), other) guard child.copied else { return (false, self) } var node = self.copy() let delta = node.replaceChild(at: bucket, lslot, with: child.node) diff --git a/Sources/PersistentCollections/Node/_Node+Subtree Insertions.swift b/Sources/PersistentCollections/Node/_Node+Subtree Insertions.swift index 758f4f6d6..1c131781c 100644 --- a/Sources/PersistentCollections/Node/_Node+Subtree Insertions.swift +++ b/Sources/PersistentCollections/Node/_Node+Subtree Insertions.swift @@ -204,8 +204,7 @@ extension _Node { assert(!isCollisionNode) if !isUnique { - self = copyNodeAndInsertItem( - at: bucket, itemSlot: slot, inserter) + self = copyNodeAndInsertItem(at: bucket, itemSlot: slot, inserter) return } if !hasFreeSpace(Self.spaceForNewItem) { @@ -220,17 +219,6 @@ extension _Node { self.count &+= 1 } - @inlinable @inline(__always) - internal func copyNodeAndInsertItem( - at bucket: _Bucket, - _ item: __owned Element - ) -> _Node { - let slot = read { $0.itemMap.slot(of: bucket) } - return copyNodeAndInsertItem(at: bucket, itemSlot: slot) { - $0.initialize(to: item) - } - } - @inlinable @inline(never) internal func copyNodeAndInsertItem( at bucket: _Bucket, @@ -466,43 +454,6 @@ extension _Node { } extension _Node { - @inlinable - internal mutating func ensureUniqueAndPushItemIntoNewChild( - isUnique: Bool, - level: _Level, - _ newChild: _Node, - at bucket: _Bucket, - itemSlot: _Slot - ) { - if !isUnique { - self = copyNodeAndPushItemIntoNewChild( - level: level, - newChild, - at: bucket, - itemSlot: itemSlot) - return - } - if !hasFreeSpace(Self.spaceForSpawningChild) { - resizeNodeAndPushItemIntoNewChild( - level: level, - newChild, - at: bucket, - itemSlot: itemSlot) - return - } - - assert(!isCollisionNode) - let item = removeItem(at: itemSlot, bucket) - let hash = _Hash(item.key) - let r = newChild.inserting(level.descend(), item, hash) - if self.count == 0, r.node.isCollisionNode { - // Compression - self = newChild - } else { - insertChild(r.node, bucket) - } - } - @inlinable @inline(never) internal func copyNodeAndPushItemIntoNewChild( level: _Level, @@ -520,24 +471,6 @@ extension _Node { at: bucket, itemSlot: itemSlot) } - - @inlinable @inline(never) - internal mutating func resizeNodeAndPushItemIntoNewChild( - level: _Level, - _ newChild: __owned _Node, - at bucket: _Bucket, - itemSlot: _Slot - ) { - assert(!isCollisionNode) - let item = update { $0.itemPtr(at: itemSlot).move() } - let hash = _Hash(item.key) - let r = newChild.inserting(level, item, hash) - _resizeNodeAndReplaceItemWithNewChild( - level: level, - r.node, - at: bucket, - itemSlot: itemSlot) - } } extension _Node { @@ -579,7 +512,7 @@ extension _Node { inserter) } - let existing = removeItem(at: itemSlot, bucket) + let existing = removeItem(at: bucket, itemSlot) let r = _Node.build( level: level.descend(), item1: existing, existingHash, diff --git a/Sources/PersistentCollections/Node/_Node+Subtree Modify.swift b/Sources/PersistentCollections/Node/_Node+Subtree Modify.swift index 25701bc29..fcf5bac06 100644 --- a/Sources/PersistentCollections/Node/_Node+Subtree Modify.swift +++ b/Sources/PersistentCollections/Node/_Node+Subtree Modify.swift @@ -124,7 +124,8 @@ extension _Node { } case (true, false): // Removal - _finalizeRemoval(.top, state.hash, at: state.path) + let remainder = _finalizeRemoval(.top, state.hash, at: state.path) + assert(remainder == nil) case (false, true): // Insertion let r = updateValue(.top, forKey: state.key, state.hash) { @@ -140,21 +141,19 @@ extension _Node { @inlinable internal mutating func _finalizeRemoval( _ level: _Level, _ hash: _Hash, at path: _UnsafePath - ) { + ) -> Element? { assert(isUnique()) if level == path.level { - _removeItemFromUniqueLeafNode( - level, hash[level], path.currentItemSlot, by: { _ in }) - } else { - let slot = path.childSlot(at: level) - let needsInlining: Bool = update { - let child = $0.childPtr(at: slot) - child.pointee._finalizeRemoval(level.descend(), hash, at: path) - return child.pointee.hasSingletonItem - } - _fixupUniqueAncestorAfterItemRemoval( - slot, { _ in hash[level] }, needsInlining: needsInlining) + return _removeItemFromUniqueLeafNode( + level, at: hash[level], path.currentItemSlot, by: { _ in } + ).remainder + } + let slot = path.childSlot(at: level) + let remainder = update { + $0[child: slot]._finalizeRemoval(level.descend(), hash, at: path) } + return _fixupUniqueAncestorAfterItemRemoval( + level, at: { _ in hash[level] }, slot, remainder: remainder) } } diff --git a/Sources/PersistentCollections/Node/_Node+Subtree Removals.swift b/Sources/PersistentCollections/Node/_Node+Subtree Removals.swift index 109580f47..302b91c46 100644 --- a/Sources/PersistentCollections/Node/_Node+Subtree Removals.swift +++ b/Sources/PersistentCollections/Node/_Node+Subtree Removals.swift @@ -20,63 +20,47 @@ extension _Node { @inlinable internal mutating func remove( _ level: _Level, _ key: Key, _ hash: _Hash - ) -> Element? { - defer { _invariantCheck() } + ) -> (removed: Element, remainder: Element?)? { guard self.isUnique() else { guard let r = removing(level, key, hash) else { return nil } - self = r.replacement - return r.old + let remainder = self.applyReplacement(level, r.replacement) + return (r.removed, remainder) } guard let r = find(level, key, hash) else { return nil } + let bucket = hash[level] guard r.descend else { - let bucket = hash[level] - return _removeItemFromUniqueLeafNode(level, bucket, r.slot) { $0.move() } + let r = _removeItemFromUniqueLeafNode(level, at: bucket, r.slot) { + $0.move() + } + return (r.result, r.remainder) } - let (old, needsInlining): (Element?, Bool) = update { - let child = $0.childPtr(at: r.slot) - let old = child.pointee.remove(level.descend(), key, hash) - guard old != nil else { return (old, false) } - let needsInlining = child.pointee.hasSingletonItem - return (old, needsInlining) - } - guard old != nil else { return nil } - _fixupUniqueAncestorAfterItemRemoval( - r.slot, { _ in hash[level] }, needsInlining: needsInlining) - return old + let r2 = update { $0[child: r.slot].remove(level.descend(), key, hash) } + guard let r2 = r2 else { return nil } + let remainder = _fixupUniqueAncestorAfterItemRemoval( + level, + at: { _ in hash[level] }, + r.slot, + remainder: r2.remainder) + return (r2.removed, remainder) } } extension _Node { - // FIXME: Make this return a Builder @inlinable internal func removing( _ level: _Level, _ key: Key, _ hash: _Hash - ) -> (replacement: _Node, old: Element)? { + ) -> (removed: Element, replacement: Builder)? { guard let r = find(level, key, hash) else { return nil } + let bucket = hash[level] guard r.descend else { - return _removingItemFromLeaf(level, hash[level], r.slot) + return _removingItemFromLeaf(level, at: bucket, r.slot) } let r2 = read { $0[child: r.slot].removing(level.descend(), key, hash) } guard let r2 = r2 else { return nil } - return ( - _fixedUpAncestorAfterItemRemoval( - level, r.slot, hash[level], r2.replacement), - r2.old) - } - - @inlinable - internal func removing2( - _ level: _Level, _ key: Key, _ hash: _Hash - ) -> (replacement: Builder, old: Element)? { - guard let r = find(level, key, hash) else { return nil } - guard r.descend else { - return _removingItemFromLeaf(level, hash, r.slot) - } - let r2 = read { $0[child: r.slot].removing2(level.descend(), key, hash) } - guard let r2 = r2 else { return nil } - let result = self.replacingChild(level, hash, r.slot, with: r2.replacement) - return (result, r2.old) + let replacement = self.replacingChild( + level, at: bucket, r.slot, with: r2.replacement) + return (r2.removed, replacement) } } @@ -84,50 +68,47 @@ extension _Node { @inlinable internal mutating func remove( _ level: _Level, at path: _UnsafePath - ) -> Element { + ) -> (removed: Element, remainder: Element?) { defer { _invariantCheck() } guard self.isUnique() else { let r = removing(level, at: path) - self = r.replacement - return r.old + let remainder = applyReplacement(level, r.replacement) + return (r.removed, remainder) } if level == path.level { let slot = path.currentItemSlot let bucket = read { $0.itemBucket(at: slot) } - return _removeItemFromUniqueLeafNode( - level, bucket, slot, by: { $0.move() }) + let r = _removeItemFromUniqueLeafNode( + level, at: bucket, slot, by: { $0.move() }) + return (r.result, r.remainder) } let slot = path.childSlot(at: level) - let (item, needsInlining): (Element, Bool) = update { - let child = $0.childPtr(at: slot) - let item = child.pointee.remove(level.descend(), at: path) - return (item, child.pointee.hasSingletonItem) - } - _fixupUniqueAncestorAfterItemRemoval( + let r = update { $0[child: slot].remove(level.descend(), at: path) } + let remainder = _fixupUniqueAncestorAfterItemRemoval( + level, + at: { $0.childMap.bucket(at: slot) }, slot, - { $0.read { $0.childMap.bucket(at: slot) } }, - needsInlining: needsInlining) - return item + remainder: r.remainder) + return (r.removed, remainder) } @inlinable internal func removing( _ level: _Level, at path: _UnsafePath - ) -> (replacement: _Node, old: Element) { + ) -> (removed: Element, replacement: Builder) { if level == path.level { let slot = path.currentItemSlot let bucket = read { $0.itemBucket(at: slot) } - return _removingItemFromLeaf(level, bucket, slot) + return _removingItemFromLeaf(level, at: bucket, slot) } let slot = path.childSlot(at: level) - let (bucket, r) = read { - ($0.childMap.bucket(at: slot), - $0[child: slot].removing(level.descend(), at: path)) + return read { + let bucket = $0.childMap.bucket(at: slot) + let r = $0[child: slot].removing(level.descend(), at: path) + return ( + r.removed, + self.replacingChild(level, at: bucket, slot, with: r.replacement)) } - return ( - _fixedUpAncestorAfterItemRemoval(level, slot, bucket, r.replacement), - r.old - ) } } @@ -135,70 +116,82 @@ extension _Node { @inlinable internal mutating func _removeItemFromUniqueLeafNode( _ level: _Level, - _ bucket: _Bucket, + at bucket: _Bucket, _ slot: _Slot, by remover: (UnsafeMutablePointer) -> R - ) -> R { + ) -> (result: R, remainder: Element?) { assert(isUnique()) - let result = removeItem(at: slot, bucket, by: remover) + let result = removeItem(at: bucket, slot, by: remover) if isAtrophied { self = removeSingletonChild() } - if level.isAtRoot, isCollisionNode, hasSingletonItem { - self._convertToRegularNode() - } - return result - } - - @inlinable - internal func _removingItemFromLeaf( - _ level: _Level, _ bucket: _Bucket, _ slot: _Slot - ) -> (replacement: _Node, old: Element) { - // Don't copy the node if we'd immediately discard it. - let willAtrophy = read { - $0.itemCount == 1 - && $0.childCount == 1 - && $0[child: .zero].isCollisionNode - } - if willAtrophy { - return read { (replacement: $0[child: .zero], old: $0[item: .zero]) } - } - var node = self.copy() - let old = node.removeItem(at: slot, bucket) - if level.isAtRoot, node.isCollisionNode, node.hasSingletonItem { - node._convertToRegularNode() + if hasSingletonItem { + if level.isAtRoot { + if isCollisionNode { + _convertToRegularNode() + } + return (result, nil) + } + let item = removeSingletonItem() + return (result, item) } - node._invariantCheck() - return (node, old) + return (result, nil) } @inlinable internal func _removingItemFromLeaf( - _ level: _Level, _ hash: _Hash, _ slot: _Slot - ) -> (replacement: Builder, old: Element) { + _ level: _Level, at bucket: _Bucket, _ slot: _Slot + ) -> (removed: Element, replacement: Builder) { read { - assert($0.isCollisionNode || $0.itemMap.contains(hash[level])) - assert(!$0.isCollisionNode || slot.value < $0.collisionCount ) - assert($0.isCollisionNode || slot == $0.itemMap.slot(of: hash[level])) + if $0.isCollisionNode { + assert(slot.value < $0.collisionCount ) + + if $0.collisionCount == 2 { + // Node will evaporate + let remainder = _Slot(1 &- slot.value) + let bucket = $0.collisionHash[level] + return ( + removed: $0[item: slot], + replacement: .item(level, $0[item: remainder], at: bucket)) + } + + var node = self.copy() + let old = node.removeItem(at: bucket, slot) + node._invariantCheck() + return (old, .collisionNode(level, node)) + } + + assert($0.itemMap.contains(bucket)) + assert(slot == $0.itemMap.slot(of: bucket)) + let willAtrophy = ( - $0.itemCount == 1 - && $0.childCount == 1 + !$0.isCollisionNode + && $0.itemMap.hasExactlyOneMember + && $0.childMap.hasExactlyOneMember && $0[child: .zero].isCollisionNode) if willAtrophy { + // Compression let child = $0[child: .zero] let old = $0[item: .zero] - return (.node(child, child.collisionHash), old) + return (old, .collisionNode(level, child)) } - let willEvaporate = ($0.itemCount == 2 && $0.childCount == 0) - if willEvaporate { - let remainder = $0[item: _Slot(1 &- slot.value)] - let old = $0[item: slot] - return (.item(remainder, hash), old) + + if $0.itemMap.count == 2 && $0.childMap.isEmpty { + // Evaporating node + let remainder = _Slot(1 &- slot.value) + + var map = $0.itemMap + if remainder != .zero { _ = map.popFirst() } + let bucket = map.first! + + return ( + removed: $0[item: slot], + replacement: .item(level, $0[item: remainder], at: bucket)) } var node = self.copy() - let old = node.removeItem(at: slot, hash[level]) + let old = node.removeItem(at: bucket, slot) node._invariantCheck() - return (.node(node, hash), old) + return (old, .node(level, node)) } } } @@ -206,31 +199,32 @@ extension _Node { extension _Node { @inlinable internal func _removingChild( - _ level: _Level, _ hashPrefix: _Hash, _ bucket: _Bucket, _ slot: _Slot + _ level: _Level, at bucket: _Bucket, _ slot: _Slot ) -> Builder { read { assert(!$0.isCollisionNode && $0.childMap.contains(bucket)) let willAtrophy = ( - $0.itemCount == 0 + $0.itemMap.isEmpty && $0.childCount == 2 && $0[child: _Slot(1 &- slot.value)].isCollisionNode ) if willAtrophy { + // Compression let child = $0[child: _Slot(1 &- slot.value)] - return .node(child, child.collisionHash) + return .collisionNode(level, child) } - let willTurnIntoItem = ($0.itemCount == 1 && $0.childCount == 1) - if willTurnIntoItem { - return .item($0[item: .zero], hashPrefix) + if $0.itemMap.hasExactlyOneMember && $0.childMap.hasExactlyOneMember { + return .item(level, $0[item: .zero], at: $0.itemMap.first!) } - let willEvaporate = ($0.itemCount == 0 && $0.childCount == 1) - if willEvaporate { - return .empty + if $0.hasSingletonChild { + // Evaporate node + return .empty(level) } + var node = self.copy() - _ = node.removeChild(at: slot, bucket) + _ = node.removeChild(at: bucket, slot) node._invariantCheck() - return .node(node, hashPrefix) + return .node(level, node) } } } @@ -238,49 +232,32 @@ extension _Node { extension _Node { @inlinable internal mutating func _fixupUniqueAncestorAfterItemRemoval( - _ slot: _Slot, - _ bucket: (inout Self) -> _Bucket, - needsInlining: Bool - ) { + _ level: _Level, + at bucket: (UnsafeHandle) -> _Bucket, + _ childSlot: _Slot, + remainder: Element? + ) -> Element? { assert(isUnique()) count &-= 1 - if needsInlining { + if let remainder = remainder { + if hasSingletonChild, !level.isAtRoot { + self = ._emptyNode() + return remainder + } + // Child to be inlined has already been cleared, so we need to adjust + // the count manually. + assert(read { $0[child: childSlot].count == 0 }) + count &-= 1 + let bucket = read { bucket($0) } ensureUnique(isUnique: true, withFreeSpace: Self.spaceForInlinedChild) - let bucket = bucket(&self) - var child = self.removeChild(at: slot, bucket) - let item = child.removeSingletonItem() - insertItem(item, at: bucket) + _ = self.removeChild(at: bucket, childSlot) + insertItem(remainder, at: bucket) + return nil } if isAtrophied { self = removeSingletonChild() } - } - - @inlinable - internal func _fixedUpAncestorAfterItemRemoval( - _ level: _Level, - _ slot: _Slot, - _ bucket: _Bucket, - _ newChild: __owned _Node - ) -> _Node { - var newChild = newChild - if newChild.hasSingletonItem { - var node = copy(withFreeSpace: Self.spaceForInlinedChild) - _ = node.removeChild(at: slot, bucket) - let item = newChild.removeSingletonItem() - node.insertItem(item, at: bucket) - node._invariantCheck() - return node - } - if newChild.isCollisionNode && self.hasSingletonChild { - // Don't return an atrophied node. - return newChild - } - var node = copy() - node.update { $0[child: slot] = newChild } - node.count &-= 1 - node._invariantCheck() - return node + return nil } } diff --git a/Sources/PersistentCollections/Node/_Node+UnsafeHandle.swift b/Sources/PersistentCollections/Node/_Node+UnsafeHandle.swift index f10d4e5e6..680e0517c 100644 --- a/Sources/PersistentCollections/Node/_Node+UnsafeHandle.swift +++ b/Sources/PersistentCollections/Node/_Node+UnsafeHandle.swift @@ -280,12 +280,12 @@ extension _Node.UnsafeHandle { extension _Node.UnsafeHandle { @inlinable internal var hasSingletonItem: Bool { - itemCount == 1 && childCount == 0 + _header.pointee.hasSingletonItem } @inlinable internal var hasSingletonChild: Bool { - itemMap.isEmpty && childCount == 1 + _header.pointee.hasSingletonChild } @inlinable diff --git a/Sources/PersistentCollections/Node/_StorageHeader.swift b/Sources/PersistentCollections/Node/_StorageHeader.swift index e5b6225fc..3684b4a9b 100644 --- a/Sources/PersistentCollections/Node/_StorageHeader.swift +++ b/Sources/PersistentCollections/Node/_StorageHeader.swift @@ -87,6 +87,19 @@ extension _StorageHeader { : itemMap.count) } + @inlinable + internal var hasSingletonChild: Bool { + itemMap.isEmpty && childMap.hasExactlyOneMember + } + + @inlinable + internal var hasSingletonItem: Bool { + if itemMap == childMap { + return itemMap._value == 1 + } + return childMap.isEmpty && itemMap.hasExactlyOneMember + } + @inlinable @inline(__always) internal var childrenEndSlot: _Slot { _Slot(childCount) diff --git a/Sources/PersistentCollections/Node/_UnsafePath.swift b/Sources/PersistentCollections/Node/_UnsafePath.swift index a3a878a39..8800d41be 100644 --- a/Sources/PersistentCollections/Node/_UnsafePath.swift +++ b/Sources/PersistentCollections/Node/_UnsafePath.swift @@ -161,13 +161,13 @@ extension _UnsafePath: CustomStringConvertible { l = l.descend() } if isPlaceholder { - d += "[\(self.nodeSlot)]?" + d += ".end[\(self.nodeSlot)]" } else if isOnItem { d += "[\(self.nodeSlot)]" } else if isOnChild { d += ".\(self.nodeSlot)" } else if isOnNodeEnd { - d += ".$(\(self.nodeSlot))" + d += ".end(\(self.nodeSlot))" } return d } @@ -216,15 +216,6 @@ extension _UnsafePath { internal var isOnNodeEnd: Bool { !_isItem && nodeSlot.value == node.childCount } - - /// Returns true if this path addresses an item on the leftmost branch of the - /// tree, or the empty slot at the end of an empty tree. - @inlinable - internal var isOnLeftmostItem: Bool { - // We are on the leftmost item in the tree if we are currently - // addressing an item and the ancestors path is all zero bits. - _isItem && ancestors == .empty && nodeSlot == .zero - } } extension _UnsafePath { @@ -375,30 +366,6 @@ extension _UnsafePath { self = best return true } - /// Ascend to the parent node of this path. Because paths do not contain - /// references to every node on them, you need to manually supply a valid - /// reference to the root node. This method visits every node - /// between the root and the current final node on the path. - /// - /// - Note: It is undefined behavior to call this on a path that is no longer - /// valid. - internal mutating func ascend(under root: _RawNode) { - assert(!self.level.isAtRoot) - var n = root.unmanaged - var l: _Level = .top - while l.descend() < self.level { - n = n.unmanagedChild(at: self.ancestors[l]) - l = l.descend() - } - assert(l.descend() == self.level) - self._isItem = false - self.nodeSlot = self.ancestors[l] - let oldNode = self.node - self.node = n - self.ancestors.clear(l) - self.level = l - assert(self.currentChild == oldNode) - } } extension _UnsafePath { diff --git a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Debugging.swift b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Debugging.swift index 5ad90df9c..65e7a49bb 100644 --- a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Debugging.swift +++ b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Debugging.swift @@ -24,7 +24,7 @@ extension PersistentDictionary { @inlinable public func _invariantCheck() { - _root._fullInvariantCheck(.top, .emptyPrefix) + _root._fullInvariantCheck() } public func _dump(iterationOrder: Bool = false) { diff --git a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Filter.swift b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Filter.swift index de07b1635..6e6272fe6 100644 --- a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Filter.swift +++ b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Filter.swift @@ -14,7 +14,7 @@ extension PersistentDictionary { public func filter( _ isIncluded: (Element) throws -> Bool ) rethrows -> Self { - let result = try _root.filter(.top, .emptyPrefix, isIncluded) + let result = try _root.filter(.top, isIncluded) guard let result = result else { return self } return PersistentDictionary(_new: result.finalize(.top)) } diff --git a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Initializers.swift b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Initializers.swift index 5ba31a3d4..e8dedf1e9 100644 --- a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Initializers.swift +++ b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Initializers.swift @@ -12,7 +12,7 @@ extension PersistentDictionary { @inlinable public init() { - self.init(_new: ._empty()) + self.init(_new: ._emptyNode()) } @inlinable diff --git a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+MapValues.swift b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+MapValues.swift index ae9968792..ebd35e274 100644 --- a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+MapValues.swift +++ b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+MapValues.swift @@ -22,7 +22,7 @@ extension PersistentDictionary { public func compactMapValues( _ transform: (Value) throws -> T? ) rethrows -> PersistentDictionary { - let result = try _root.compactMapValues(.top, .emptyPrefix, transform) + let result = try _root.compactMapValues(.top, transform) return PersistentDictionary(_new: result.finalize(.top)) } } diff --git a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Merge.swift b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Merge.swift index d50533f8e..1954ba5b6 100644 --- a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Merge.swift +++ b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary+Merge.swift @@ -16,7 +16,7 @@ extension PersistentDictionary { uniquingKeysWith combine: (Value, Value) throws -> Value ) rethrows { _invalidateIndices() - try _root.merge(.top, .emptyPrefix, keysAndValues._root, combine) + try _root.merge(.top, keysAndValues._root, combine) } @inlinable diff --git a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary.swift index 138cd793d..e31e46041 100644 --- a/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary/PersistentDictionary.swift @@ -382,16 +382,18 @@ extension PersistentDictionary { @inlinable @discardableResult public mutating func removeValue(forKey key: Key) -> Value? { + guard let r = _root.remove(.top, key, _Hash(key)) else { return nil } _invalidateIndices() - return _root.remove(.top, key, _Hash(key))?.value + assert(r.remainder == nil) + _invariantCheck() + return r.removed.value } // fluid/immutable API @inlinable public func removingValue(forKey key: Key) -> Self { - var copy = self - copy.removeValue(forKey: key) - return copy + guard let r = _root.removing(.top, key, _Hash(key)) else { return self } + return Self(_new: r.replacement.finalize(.top)) } @inlinable @@ -399,7 +401,9 @@ extension PersistentDictionary { precondition(_isValid(index), "Invalid index") precondition(index._path._isItem, "Can't remove item at end index") _invalidateIndices() - return _root.remove(.top, at: index._path) + let r = _root.remove(.top, at: index._path) + assert(r.remainder == nil) + return r.removed } } diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+Debugging.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+Debugging.swift index 9c108c377..58e4cfb77 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+Debugging.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+Debugging.swift @@ -24,7 +24,7 @@ extension PersistentSet { @inlinable public func _invariantCheck() { - _root._fullInvariantCheck(.top, .emptyPrefix) + _root._fullInvariantCheck() } public func _dump(iterationOrder: Bool = false) { diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+Extras.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+Extras.swift index a5539c893..bd9ff73b7 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+Extras.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+Extras.swift @@ -18,7 +18,9 @@ extension PersistentSet { public mutating func remove(at position: Index) -> Element { precondition(_isValid(position)) _invalidateIndices() - return _root.remove(.top, at: position._path).key + let r = _root.remove(.top, at: position._path) + precondition(r.remainder == nil) + return r.removed.key } /// Replace the member at the given index with a new value that compares equal diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+Filter.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+Filter.swift index 888a4447a..31d02a489 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+Filter.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+Filter.swift @@ -24,9 +24,7 @@ extension PersistentSet { public func filter( _ isIncluded: (Element) throws -> Bool ) rethrows -> Self { - let result = try _root.filter(.top, .emptyPrefix) { - try isIncluded($0.key) - } + let result = try _root.filter(.top) { try isIncluded($0.key) } guard let result = result else { return self } return PersistentSet(_new: result.finalize(.top)) } diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra Initializers.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra Initializers.swift index 266084940..66861b6e1 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra Initializers.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra Initializers.swift @@ -18,7 +18,7 @@ extension PersistentSet { /// - Complexity: O(1) @inlinable public init() { - self.init(_new: ._empty()) + self.init(_new: ._emptyNode()) } /// Creates a new set from a finite sequence of items. diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra basics.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra basics.swift index 99ece7681..9d62afe63 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra basics.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra basics.swift @@ -85,8 +85,10 @@ extension PersistentSet: SetAlgebra { @inlinable public mutating func remove(_ member: Element) -> Element? { let hash = _Hash(member) + guard let r = _root.remove(.top, member, hash) else { return nil } _invalidateIndices() - return _root.remove(.top, member, hash)?.key + assert(r.remainder == nil) + return r.removed.key } /// Inserts the given element into the set unconditionally. diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra fluent basics.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra fluent basics.swift index 9764db721..45cf7d268 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra fluent basics.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra fluent basics.swift @@ -39,7 +39,8 @@ extension PersistentSet { let hash = _Hash(member) let r = _root.removing(.top, member, hash) guard let r = r else { return self } - return PersistentSet(_new: r.replacement) + let root = r.replacement.finalize(.top) + return PersistentSet(_new: root) } @inlinable diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra intersection.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra intersection.swift index 309d0d484..567d3c11c 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra intersection.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra intersection.swift @@ -44,10 +44,10 @@ extension PersistentSet { internal func _intersection( _ other: PersistentCollections._Node ) -> Self { - let builder = _root.intersection(.top, .emptyPrefix, other) + let builder = _root.intersection(.top, other) guard let builder = builder else { return self } let root = builder.finalize(.top) - root._fullInvariantCheck(.top, .emptyPrefix) + root._fullInvariantCheck() return Self(_new: root) } @@ -80,7 +80,7 @@ extension PersistentSet { return self.filter { other.contains($0) } } - var result: _Node = ._empty() + var result: _Node = ._emptyNode() for item in other { let hash = _Hash(item) if let r = self._root.lookup(.top, item, hash) { diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isEqual.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isEqual.swift index 4bd0d7392..fa2941bd3 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isEqual.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isEqual.swift @@ -102,7 +102,7 @@ extension PersistentSet { } // FIXME: Would making this a BitSet of seen positions be better? - var seen: _Node? = ._empty() + var seen: _Node? = ._emptyNode() var it = other.makeIterator() while let item = it.next() { let hash = _Hash(item) diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSubset.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSubset.swift index c545278ea..9ccb06b8f 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSubset.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSubset.swift @@ -111,7 +111,7 @@ extension PersistentSet { } // FIXME: Would making this a BitSet of seen positions be better? - var seen: _Node = ._empty() + var seen: _Node = ._emptyNode() var doneCollecting = false var isStrict = false var it = other.makeIterator() @@ -126,7 +126,7 @@ extension PersistentSet { if isStrict { return true } // Stop collecting seen items -- we just need to decide // strictness now. - seen = ._empty() + seen = ._emptyNode() doneCollecting = true } } else { diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSuperset.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSuperset.swift index 5616be58d..6625c7ebf 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSuperset.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isStrictSuperset.swift @@ -108,7 +108,7 @@ extension PersistentSet { } // FIXME: Would making this a BitSet of seen positions be better? - var seen: _Node = ._empty() + var seen: _Node = ._emptyNode() for item in other { let hash = _Hash(item) guard self._root.containsKey(.top, item, hash) else { return false } diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isSubset.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isSubset.swift index e3af14d9a..542d1df29 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isSubset.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra isSubset.swift @@ -99,7 +99,7 @@ extension PersistentSet { } // FIXME: Would making this a BitSet of seen positions be better? - var seen: _Node = ._empty() + var seen: _Node = ._emptyNode() for item in other { let hash = _Hash(item) guard _root.containsKey(.top, item, hash) else { continue } diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra subtracting.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra subtracting.swift index b30739ed3..01f0dec91 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra subtracting.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra subtracting.swift @@ -40,10 +40,10 @@ extension PersistentSet { internal __consuming func _subtracting( _ other: PersistentCollections._Node ) -> Self { - let builder = _root.subtracting(.top, .emptyPrefix, other) + let builder = _root.subtracting(.top, other) guard let builder = builder else { return self } let root = builder.finalize(.top) - root._fullInvariantCheck(.top, .emptyPrefix) + root._fullInvariantCheck() return Self(_new: root) } diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra symmetricDifference.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra symmetricDifference.swift index 53b379ea1..d210d21db 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra symmetricDifference.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra symmetricDifference.swift @@ -26,9 +26,10 @@ extension PersistentSet { /// parts of the input trees directly into the result. @inlinable public func symmetricDifference(_ other: __owned Self) -> Self { - let branch = _root.symmetricDifference(.top, .emptyPrefix, other._root) + let branch = _root.symmetricDifference(.top, other._root) guard let branch = branch else { return self } let root = branch.finalize(.top) + root._fullInvariantCheck() return PersistentSet(_new: root) } diff --git a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra union.swift b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra union.swift index 525272b81..80f1399d7 100644 --- a/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra union.swift +++ b/Sources/PersistentCollections/PersistentSet/PersistentSet+SetAlgebra union.swift @@ -29,9 +29,9 @@ extension PersistentSet { @inlinable public func union(_ other: __owned Self) -> Self { - let r = _root.union(.top, .emptyPrefix, other._root) + let r = _root.union(.top, other._root) guard r.copied else { return self } - r.node._fullInvariantCheck(.top, .emptyPrefix) + r.node._fullInvariantCheck() return PersistentSet(_new: r.node) }